Skip to content

Commit d73447d

Browse files
authored
(feat) support foreign namespace behaviour (#937)
This allows for case sensitive attributes in the JSX via the foreign namespace. Which then allows svelte-nodegui and svelte-native to use the correct casing of attribute names and avoid errors at runtime.
1 parent fd7865b commit d73447d

File tree

12 files changed

+62
-23
lines changed

12 files changed

+62
-23
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"scripts": {
1111
"bootstrap": "yarn workspace svelte2tsx build && yarn workspace svelte-vscode build:grammar",
1212
"build": "tsc -b",
13-
"test": "CI=true yarn workspaces run test",
13+
"test": "cross-env CI=true yarn workspaces run test",
1414
"watch": "tsc -b -watch",
1515
"format": "prettier --write .",
1616
"lint": "prettier --check . && eslint \"packages/**/*.{ts,js}\""
@@ -25,6 +25,7 @@
2525
"eslint": "^7.7.0",
2626
"eslint-plugin-import": "^2.22.1",
2727
"eslint-plugin-svelte3": "^2.7.3",
28-
"prettier": "2.2.1"
28+
"prettier": "2.2.1",
29+
"cross-env": "^7.0.2"
2930
}
3031
}

packages/language-server/src/plugins/typescript/DocumentSnapshot.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,8 @@ function preprocessSvelteFile(document: Document, options: SvelteSnapshotOptions
176176
strictMode: options.strictMode,
177177
filename: document.getFilePath() ?? undefined,
178178
isTsFile: scriptKind === ts.ScriptKind.TSX,
179-
emitOnTemplateError: options.transformOnTemplateError
179+
emitOnTemplateError: options.transformOnTemplateError,
180+
namespace: document.config?.compilerOptions?.namespace
180181
});
181182
text = tsx.code;
182183
tsxMap = tsx.map;

packages/svelte2tsx/index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,9 @@ export default function svelte2tsx(
3636
* Whether to try emitting result when there's a syntax error in the template
3737
*/
3838
emitOnTemplateError?: boolean;
39+
/**
40+
* The namespace option from svelte config
41+
*/
42+
namespace?: string;
3943
}
4044
): SvelteCompiledToTsx

packages/svelte2tsx/src/htmlxtojsx/index.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ export function convertHtmlxToJsx(
4545
str: MagicString,
4646
ast: TemplateNode,
4747
onWalk: Walker = null,
48-
onLeave: Walker = null
48+
onLeave: Walker = null,
49+
options: { preserveAttributeCase?: boolean } = {}
4950
): void {
5051
const htmlx = str.original;
5152
stripDoctype(str);
@@ -139,7 +140,13 @@ export function convertHtmlxToJsx(
139140
handleAnimateDirective(htmlx, str, node as BaseDirective, parent);
140141
break;
141142
case 'Attribute':
142-
handleAttribute(htmlx, str, node as Attribute, parent);
143+
handleAttribute(
144+
htmlx,
145+
str,
146+
node as Attribute,
147+
parent,
148+
options.preserveAttributeCase
149+
);
143150
break;
144151
case 'EventHandler':
145152
handleEventHandler(htmlx, str, node as BaseDirective, parent);
@@ -218,11 +225,14 @@ export function convertHtmlxToJsx(
218225
/**
219226
* @internal For testing only
220227
*/
221-
export function htmlx2jsx(htmlx: string, options?: { emitOnTemplateError?: boolean }) {
228+
export function htmlx2jsx(
229+
htmlx: string,
230+
options?: { emitOnTemplateError?: boolean; preserveAttributeCase: boolean }
231+
) {
222232
const ast = parseHtmlx(htmlx, options);
223233
const str = new MagicString(htmlx);
224234

225-
convertHtmlxToJsx(str, ast);
235+
convertHtmlxToJsx(str, ast, null, null, options);
226236

227237
return {
228238
map: str.generateMap({ hires: true }),

packages/svelte2tsx/src/htmlxtojsx/nodes/attribute.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,19 @@ export function handleAttribute(
3939
htmlx: string,
4040
str: MagicString,
4141
attr: Attribute,
42-
parent: BaseNode
42+
parent: BaseNode,
43+
preserveCase: boolean
4344
): void {
4445
let transformedFromDirectiveOrNamespace = false;
4546

47+
const transformAttributeCase = (name: string) => {
48+
if (!preserveCase && !svgAttributes.find((x) => x == name)) {
49+
return name.toLowerCase();
50+
} else {
51+
return name;
52+
}
53+
};
54+
4655
//if we are on an "element" we are case insensitive, lowercase to match our JSX
4756
if (parent.type == 'Element') {
4857
const sapperLinkActions = ['sapper:prefetch', 'sapper:noscroll'];
@@ -58,10 +67,7 @@ export function handleAttribute(
5867
sapperLinkActions.includes(attr.name) ||
5968
sveltekitLinkActions.includes(attr.name)
6069
) {
61-
let name = attr.name;
62-
if (!svgAttributes.find((x) => x == name)) {
63-
name = name.toLowerCase();
64-
}
70+
let name = transformAttributeCase(attr.name);
6571

6672
//strip ":" from out attribute name and uppercase the next letter to convert to jsx attribute
6773
const colonIndex = name.indexOf(':');
@@ -83,7 +89,7 @@ export function handleAttribute(
8389
!transformedFromDirectiveOrNamespace &&
8490
parent.name !== '!DOCTYPE'
8591
) {
86-
str.overwrite(attr.start, attr.end, attr.name.toLowerCase());
92+
str.overwrite(attr.start, attr.end, transformAttributeCase(attr.name));
8793
}
8894
return;
8995
}
@@ -101,10 +107,7 @@ export function handleAttribute(
101107
if (attrVal.type == 'AttributeShorthand') {
102108
let attrName = attrVal.expression.name;
103109
if (parent.type == 'Element') {
104-
// eslint-disable-next-line max-len
105-
attrName = svgAttributes.find((a) => a == attrName)
106-
? attrName
107-
: attrName.toLowerCase();
110+
attrName = transformAttributeCase(attrName);
108111
}
109112

110113
str.appendRight(attr.start, `${attrName}=`);

packages/svelte2tsx/src/svelte2tsx/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const COMPONENT_SUFFIX = '__SvelteComponent_';
7474

7575
function processSvelteTemplate(
7676
str: MagicString,
77-
options?: { emitOnTemplateError?: boolean }
77+
options?: { emitOnTemplateError?: boolean; namespace?: string }
7878
): TemplateProcessResult {
7979
const htmlxAst = parseHtmlx(str.original, options);
8080

@@ -287,7 +287,9 @@ function processSvelteTemplate(
287287
}
288288
};
289289

290-
convertHtmlxToJsx(str, htmlxAst, onHtmlxWalk, onHtmlxLeave);
290+
convertHtmlxToJsx(str, htmlxAst, onHtmlxWalk, onHtmlxLeave, {
291+
preserveAttributeCase: options.namespace == 'foreign'
292+
});
291293

292294
// resolve scripts
293295
const { scriptTag, moduleScriptTag } = scripts.getTopLevelScriptTags();
@@ -458,6 +460,7 @@ export function svelte2tsx(
458460
strictMode?: boolean;
459461
isTsFile?: boolean;
460462
emitOnTemplateError?: boolean;
463+
namespace?: string;
461464
}
462465
) {
463466
const str = new MagicString(svelte);

packages/svelte2tsx/test/helpers.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ type TransformSampleFn = (
199199
filename: string;
200200
sampleName: string;
201201
emitOnTemplateError: boolean;
202+
preserveAttributeCase: boolean;
202203
}
203204
) => ReturnType<typeof htmlx2jsx | typeof svelte2tsx>;
204205

@@ -214,7 +215,8 @@ export function test_samples(dir: string, transform: TransformSampleFn, jsx: 'js
214215
const config = {
215216
filename: svelteFile,
216217
sampleName: sample.name,
217-
emitOnTemplateError: false
218+
emitOnTemplateError: false,
219+
preserveAttributeCase: sample.name.endsWith('-foreign-ns')
218220
};
219221

220222
if (process.env.CI) {
@@ -295,7 +297,8 @@ export function get_svelte2tsx_config(base: BaseConfig, sampleName: string): Sve
295297
filename: base.filename,
296298
emitOnTemplateError: base.emitOnTemplateError,
297299
strictMode: sampleName.includes('strictMode'),
298-
isTsFile: sampleName.startsWith('ts-')
300+
isTsFile: sampleName.startsWith('ts-'),
301+
namespace: sampleName.endsWith('-foreign-ns') ? 'foreign' : null
299302
};
300303
}
301304

packages/svelte2tsx/test/htmlx2jsx/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { test_samples } from '../helpers';
44
describe('htmlx2jsx', () => {
55
test_samples(
66
__dirname,
7-
(input, { emitOnTemplateError }) => {
8-
return htmlx2jsx(input, { emitOnTemplateError });
7+
(input, { emitOnTemplateError, preserveAttributeCase }) => {
8+
return htmlx2jsx(input, { emitOnTemplateError, preserveAttributeCase });
99
},
1010
'jsx'
1111
);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<><SomeComponent attrName="text" attrCase="text" />
2+
<someelement attrName="text" attrCase /></>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<SomeComponent attrName="text" attrCase=text />
2+
<someelement attrName="text" attrCase />

0 commit comments

Comments
 (0)