Skip to content

Commit 8ed251d

Browse files
authored
Support xml namespace prefix for JSX elements and attributes (#37421)
* Support xml namespace prefix for JSX elements and attributes Just as with the `-` character, `:` is now also treated specially in JSX element and attribute names, but is only allowed a single time, and not at the beginning or end of the name, as is specified in the JSX spec. All tests in jsxInvalidEsprimaTestSuite still fail, but for slightly different reasons now. Two lines in jsxEsprimaFbTestSuite were uncommented as they included elements with namespaces, and they now pass without error. * Add case for colons at ends of identifier * Add case for jsx namepsace intrinsics * Add cases with upcase idents for jsx namespaces * Add case for jsx namespaces with react option * Always consider jsx names with colon to be intrinsics * Adjust comment about chars valid in jsx names but not js idents * Fix minor typo in namespace prefix test case variable name * Remove misleading comments on isUnhyphenatedJsxName
1 parent 3f92a64 commit 8ed251d

27 files changed

+1266
-82
lines changed

src/compiler/checker.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24922,11 +24922,7 @@ namespace ts {
2492224922
return getJsxElementTypeAt(node) || anyType;
2492324923
}
2492424924

24925-
/**
24926-
* Returns true iff the JSX element name would be a valid JS identifier, ignoring restrictions about keywords not being identifiers
24927-
*/
2492824925
function isUnhyphenatedJsxName(name: string | __String) {
24929-
// - is the only character supported in JSX attribute names that isn't valid in JavaScript identifiers
2493024926
return !stringContains(name as string, "-");
2493124927
}
2493224928

src/compiler/scanner.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2302,22 +2302,35 @@ namespace ts {
23022302
// they allow dashes
23032303
function scanJsxIdentifier(): SyntaxKind {
23042304
if (tokenIsIdentifierOrKeyword(token)) {
2305-
// An identifier or keyword has already been parsed - check for a `-` and then append it and everything after it to the token
2305+
// An identifier or keyword has already been parsed - check for a `-` or a single instance of `:` and then append it and
2306+
// everything after it to the token
23062307
// Do note that this means that `scanJsxIdentifier` effectively _mutates_ the visible token without advancing to a new token
23072308
// Any caller should be expecting this behavior and should only read the pos or token value after calling it.
2309+
let namespaceSeparator = false;
23082310
while (pos < end) {
23092311
const ch = text.charCodeAt(pos);
23102312
if (ch === CharacterCodes.minus) {
23112313
tokenValue += "-";
23122314
pos++;
23132315
continue;
23142316
}
2317+
else if (ch === CharacterCodes.colon && !namespaceSeparator) {
2318+
tokenValue += ":";
2319+
pos++;
2320+
namespaceSeparator = true;
2321+
continue;
2322+
}
23152323
const oldPos = pos;
23162324
tokenValue += scanIdentifierParts(); // reuse `scanIdentifierParts` so unicode escapes are handled
23172325
if (pos === oldPos) {
23182326
break;
23192327
}
23202328
}
2329+
// Do not include a trailing namespace separator in the token, since this is against the spec.
2330+
if (tokenValue.slice(-1) === ":") {
2331+
tokenValue = tokenValue.slice(0, -1);
2332+
pos--;
2333+
}
23212334
}
23222335
return token;
23232336
}

src/compiler/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3844,7 +3844,7 @@ namespace ts {
38443844

38453845
export function isIntrinsicJsxName(name: __String | string) {
38463846
const ch = (name as string).charCodeAt(0);
3847-
return (ch >= CharacterCodes.a && ch <= CharacterCodes.z) || stringContains((name as string), "-");
3847+
return (ch >= CharacterCodes.a && ch <= CharacterCodes.z) || stringContains((name as string), "-") || stringContains((name as string), ":");
38483848
}
38493849

38503850
const indentStrings: string[] = ["", " "];

tests/baselines/reference/jsxEsprimaFbTestSuite.errors.txt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,1): error TS2657: JSX expressions must have one parent element.
2-
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,17): error TS1005: '{' expected.
3-
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,23): error TS1005: ';' expected.
4-
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,23): error TS2304: Cannot find name 'right'.
5-
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,41): error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
6-
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,57): error TS1109: Expression expected.
7-
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,58): error TS1109: Expression expected.
1+
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(40,1): error TS2657: JSX expressions must have one parent element.
2+
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(40,17): error TS1005: '{' expected.
3+
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(40,23): error TS1005: ';' expected.
4+
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(40,23): error TS2304: Cannot find name 'right'.
5+
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(40,41): error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
6+
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(40,57): error TS1109: Expression expected.
7+
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(40,58): error TS1109: Expression expected.
88

99

1010
==== tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx (7 errors) ====
@@ -15,12 +15,13 @@ tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,58): error TS1109: Expr
1515
declare var x;
1616
declare var a;
1717
declare var props;
18+
declare var value;
1819

1920
<a />;
2021

21-
//<n:a n:v />; Namespace unsuported
22+
<n:a n:v />;
2223

23-
//<a n:foo="bar"> {value} <b><c /></b></a>; Namespace unsuported
24+
<a n:foo="bar"> {value} <b><c /></b></a>;
2425

2526
<a b={" "} c=" " d="&amp;" e="id=1&group=2" f="&#123456789" g="&#123*;" h="&#x;" />;
2627

tests/baselines/reference/jsxEsprimaFbTestSuite.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ declare var LeftRight;
66
declare var x;
77
declare var a;
88
declare var props;
9+
declare var value;
910

1011
<a />;
1112

12-
//<n:a n:v />; Namespace unsuported
13+
<n:a n:v />;
1314

14-
//<a n:foo="bar"> {value} <b><c /></b></a>; Namespace unsuported
15+
<a n:foo="bar"> {value} <b><c /></b></a>;
1516

1617
<a b={" "} c=" " d="&amp;" e="id=1&group=2" f="&#123456789" g="&#123*;" h="&#x;" />;
1718

@@ -56,8 +57,8 @@ baz
5657

5758
//// [jsxEsprimaFbTestSuite.jsx]
5859
<a />;
59-
//<n:a n:v />; Namespace unsuported
60-
//<a n:foo="bar"> {value} <b><c /></b></a>; Namespace unsuported
60+
<n:a n:v/>;
61+
<a n:foo="bar"> {value} <b><c /></b></a>;
6162
<a b={" "} c=" " d="&amp;" e="id=1&group=2" f="&#123456789" g="&#123*;" h="&#x;"/>;
6263
<a b="&notanentity;"/>;
6364
<a />;

tests/baselines/reference/jsxEsprimaFbTestSuite.symbols

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,29 @@ declare var a;
2020
declare var props;
2121
>props : Symbol(props, Decl(jsxEsprimaFbTestSuite.tsx, 6, 11))
2222

23+
declare var value;
24+
>value : Symbol(value, Decl(jsxEsprimaFbTestSuite.tsx, 7, 11))
25+
2326
<a />;
2427

25-
//<n:a n:v />; Namespace unsuported
28+
<n:a n:v />;
29+
>n:v : Symbol(n:v, Decl(jsxEsprimaFbTestSuite.tsx, 11, 4))
2630

27-
//<a n:foo="bar"> {value} <b><c /></b></a>; Namespace unsuported
31+
<a n:foo="bar"> {value} <b><c /></b></a>;
32+
>n:foo : Symbol(n:foo, Decl(jsxEsprimaFbTestSuite.tsx, 13, 2))
33+
>value : Symbol(value, Decl(jsxEsprimaFbTestSuite.tsx, 7, 11))
2834

2935
<a b={" "} c=" " d="&amp;" e="id=1&group=2" f="&#123456789" g="&#123*;" h="&#x;" />;
30-
>b : Symbol(b, Decl(jsxEsprimaFbTestSuite.tsx, 14, 2))
31-
>c : Symbol(c, Decl(jsxEsprimaFbTestSuite.tsx, 14, 10))
32-
>d : Symbol(d, Decl(jsxEsprimaFbTestSuite.tsx, 14, 16))
33-
>e : Symbol(e, Decl(jsxEsprimaFbTestSuite.tsx, 14, 26))
34-
>f : Symbol(f, Decl(jsxEsprimaFbTestSuite.tsx, 14, 43))
35-
>g : Symbol(g, Decl(jsxEsprimaFbTestSuite.tsx, 14, 59))
36-
>h : Symbol(h, Decl(jsxEsprimaFbTestSuite.tsx, 14, 71))
36+
>b : Symbol(b, Decl(jsxEsprimaFbTestSuite.tsx, 15, 2))
37+
>c : Symbol(c, Decl(jsxEsprimaFbTestSuite.tsx, 15, 10))
38+
>d : Symbol(d, Decl(jsxEsprimaFbTestSuite.tsx, 15, 16))
39+
>e : Symbol(e, Decl(jsxEsprimaFbTestSuite.tsx, 15, 26))
40+
>f : Symbol(f, Decl(jsxEsprimaFbTestSuite.tsx, 15, 43))
41+
>g : Symbol(g, Decl(jsxEsprimaFbTestSuite.tsx, 15, 59))
42+
>h : Symbol(h, Decl(jsxEsprimaFbTestSuite.tsx, 15, 71))
3743

3844
<a b="&notanentity;" />;
39-
>b : Symbol(b, Decl(jsxEsprimaFbTestSuite.tsx, 16, 2))
45+
>b : Symbol(b, Decl(jsxEsprimaFbTestSuite.tsx, 17, 2))
4046

4147
<a
4248
/>;
@@ -49,15 +55,15 @@ declare var props;
4955
>AbC_def : Symbol(AbC_def, Decl(jsxEsprimaFbTestSuite.tsx, 2, 11))
5056

5157
test="&#x0026;&#38;">
52-
>test : Symbol(test, Decl(jsxEsprimaFbTestSuite.tsx, 22, 8))
58+
>test : Symbol(test, Decl(jsxEsprimaFbTestSuite.tsx, 23, 8))
5359

5460
bar
5561
baz
5662
</AbC_def>;
5763
>AbC_def : Symbol(AbC_def, Decl(jsxEsprimaFbTestSuite.tsx, 2, 11))
5864

5965
<a b={x ? <c /> : <d />} />;
60-
>b : Symbol(b, Decl(jsxEsprimaFbTestSuite.tsx, 28, 2))
66+
>b : Symbol(b, Decl(jsxEsprimaFbTestSuite.tsx, 29, 2))
6167
>x : Symbol(x, Decl(jsxEsprimaFbTestSuite.tsx, 4, 11))
6268

6369
<a>{}</a>;
@@ -70,7 +76,7 @@ baz
7076

7177
<LeftRight left=<a /> right=<b>monkeys /> gorillas</b> />;
7278
>LeftRight : Symbol(LeftRight, Decl(jsxEsprimaFbTestSuite.tsx, 3, 11))
73-
>left : Symbol(left, Decl(jsxEsprimaFbTestSuite.tsx, 38, 10))
79+
>left : Symbol(left, Decl(jsxEsprimaFbTestSuite.tsx, 39, 10))
7480

7581
<a.b></a.b>;
7682
>a : Symbol(a, Decl(jsxEsprimaFbTestSuite.tsx, 5, 11))
@@ -88,11 +94,11 @@ baz
8894

8995
<div {...props} post="attribute" />;
9096
>props : Symbol(props, Decl(jsxEsprimaFbTestSuite.tsx, 6, 11))
91-
>post : Symbol(post, Decl(jsxEsprimaFbTestSuite.tsx, 48, 15))
97+
>post : Symbol(post, Decl(jsxEsprimaFbTestSuite.tsx, 49, 15))
9298

9399
<div pre="leading" pre2="attribute" {...props}></div>;
94-
>pre : Symbol(pre, Decl(jsxEsprimaFbTestSuite.tsx, 50, 4))
95-
>pre2 : Symbol(pre2, Decl(jsxEsprimaFbTestSuite.tsx, 50, 18))
100+
>pre : Symbol(pre, Decl(jsxEsprimaFbTestSuite.tsx, 51, 4))
101+
>pre2 : Symbol(pre2, Decl(jsxEsprimaFbTestSuite.tsx, 51, 18))
96102
>props : Symbol(props, Decl(jsxEsprimaFbTestSuite.tsx, 6, 11))
97103

98104
<a> </a>;

tests/baselines/reference/jsxEsprimaFbTestSuite.types

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,29 @@ declare var a;
2020
declare var props;
2121
>props : any
2222

23+
declare var value;
24+
>value : any
25+
2326
<a />;
2427
><a /> : any
2528
>a : any
2629

27-
//<n:a n:v />; Namespace unsuported
30+
<n:a n:v />;
31+
><n:a n:v /> : any
32+
>n:a : any
33+
>n:v : true
2834

29-
//<a n:foo="bar"> {value} <b><c /></b></a>; Namespace unsuported
35+
<a n:foo="bar"> {value} <b><c /></b></a>;
36+
><a n:foo="bar"> {value} <b><c /></b></a> : any
37+
>a : any
38+
>n:foo : string
39+
>value : any
40+
><b><c /></b> : any
41+
>b : any
42+
><c /> : any
43+
>c : any
44+
>b : any
45+
>a : any
3046

3147
<a b={" "} c=" " d="&amp;" e="id=1&group=2" f="&#123456789" g="&#123*;" h="&#x;" />;
3248
><a b={" "} c=" " d="&amp;" e="id=1&group=2" f="&#123456789" g="&#123*;" h="&#x;" /> : any

tests/baselines/reference/jsxInvalidEsprimaTestSuite.errors.txt

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,9 @@ tests/cases/conformance/jsx/5.tsx(1,2): error TS17008: JSX element 'a' has no co
7575
tests/cases/conformance/jsx/5.tsx(1,5): error TS1005: '</' expected.
7676
tests/cases/conformance/jsx/6.tsx(1,4): error TS17002: Expected corresponding JSX closing tag for 'a'.
7777
tests/cases/conformance/jsx/7.tsx(1,13): error TS1002: Unterminated string literal.
78-
tests/cases/conformance/jsx/8.tsx(1,3): error TS1003: Identifier expected.
79-
tests/cases/conformance/jsx/8.tsx(1,6): error TS17002: Expected corresponding JSX closing tag for 'a'.
80-
tests/cases/conformance/jsx/9.tsx(1,3): error TS1003: Identifier expected.
81-
tests/cases/conformance/jsx/9.tsx(1,5): error TS1003: Identifier expected.
82-
tests/cases/conformance/jsx/9.tsx(1,11): error TS1005: '>' expected.
83-
tests/cases/conformance/jsx/9.tsx(1,12): error TS2304: Cannot find name 'b'.
84-
tests/cases/conformance/jsx/9.tsx(1,16): error TS1109: Expression expected.
78+
tests/cases/conformance/jsx/8.tsx(1,6): error TS17002: Expected corresponding JSX closing tag for 'a:b'.
79+
tests/cases/conformance/jsx/9.tsx(1,2): error TS2304: Cannot find name 'a:b'.
80+
tests/cases/conformance/jsx/9.tsx(1,10): error TS2304: Cannot find name 'a:b'.
8581

8682

8783
==== tests/cases/conformance/jsx/1.tsx (3 errors) ====
@@ -128,24 +124,16 @@ tests/cases/conformance/jsx/9.tsx(1,16): error TS1109: Expression expected.
128124
<a foo="bar;
129125

130126
!!! error TS1002: Unterminated string literal.
131-
==== tests/cases/conformance/jsx/8.tsx (2 errors) ====
127+
==== tests/cases/conformance/jsx/8.tsx (1 errors) ====
132128
<a:b></b>;
133-
~
134-
!!! error TS1003: Identifier expected.
135129
~~~~
136-
!!! error TS17002: Expected corresponding JSX closing tag for 'a'.
137-
==== tests/cases/conformance/jsx/9.tsx (5 errors) ====
130+
!!! error TS17002: Expected corresponding JSX closing tag for 'a:b'.
131+
==== tests/cases/conformance/jsx/9.tsx (2 errors) ====
138132
<a:b.c></a:b.c>;
139-
~
140-
!!! error TS1003: Identifier expected.
141-
~
142-
!!! error TS1003: Identifier expected.
143-
~
144-
!!! error TS1005: '>' expected.
145-
~
146-
!!! error TS2304: Cannot find name 'b'.
147-
~
148-
!!! error TS1109: Expression expected.
133+
~~~
134+
!!! error TS2304: Cannot find name 'a:b'.
135+
~~~
136+
!!! error TS2304: Cannot find name 'a:b'.
149137
==== tests/cases/conformance/jsx/10.tsx (6 errors) ====
150138
<a.b:c></a.b:c>;
151139
~

tests/baselines/reference/jsxInvalidEsprimaTestSuite.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,9 @@ a / > ;
9090
//// [7.jsx]
9191
<a foo="bar;/>;
9292
//// [8.jsx]
93-
<a b></b>;
93+
<a:b></b>;
9494
//// [9.jsx]
95-
<a b c></a>;
96-
b.c > ;
95+
<a:b.c></a:b.c>;
9796
//// [10.jsx]
9897
<a.b c></a.b>;
9998
c > ;

tests/baselines/reference/jsxInvalidEsprimaTestSuite.symbols

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,9 @@ No type information for this code.=== tests/cases/conformance/jsx/7.tsx ===
2222

2323
=== tests/cases/conformance/jsx/8.tsx ===
2424
<a:b></b>;
25-
>b : Symbol(b, Decl(8.tsx, 0, 3))
26-
27-
=== tests/cases/conformance/jsx/9.tsx ===
25+
No type information for this code.=== tests/cases/conformance/jsx/9.tsx ===
2826
<a:b.c></a:b.c>;
29-
>b : Symbol(b, Decl(9.tsx, 0, 3))
30-
>c : Symbol(c, Decl(9.tsx, 0, 5))
31-
32-
=== tests/cases/conformance/jsx/10.tsx ===
27+
No type information for this code.=== tests/cases/conformance/jsx/10.tsx ===
3328
<a.b:c></a.b:c>;
3429
>c : Symbol(c, Decl(10.tsx, 0, 5))
3530

tests/baselines/reference/jsxInvalidEsprimaTestSuite.types

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,18 @@ declare var React: any;
5151
=== tests/cases/conformance/jsx/8.tsx ===
5252
<a:b></b>;
5353
><a:b></b> : any
54-
>a : any
55-
>b : true
54+
>a:b : any
5655
>b : any
5756

5857
=== tests/cases/conformance/jsx/9.tsx ===
5958
<a:b.c></a:b.c>;
60-
><a:b.c></a : any
61-
>a : any
62-
>b : true
63-
>c : true
64-
>a : any
65-
>b.c> : boolean
66-
>b.c : any
67-
>b : any
59+
><a:b.c></a:b.c> : any
60+
>a:b.c : any
61+
>a:b : any
62+
>c : any
63+
>a:b.c : any
64+
>a:b : any
6865
>c : any
69-
> : any
7066

7167
=== tests/cases/conformance/jsx/10.tsx ===
7268
<a.b:c></a.b:c>;

0 commit comments

Comments
 (0)