Skip to content

Commit a1669bb

Browse files
authored
handle multiline jsx strings correctly, emit escapes in jsx attributes correctly (microsoft#20309)
1 parent a625dec commit a1669bb

15 files changed

Lines changed: 618 additions & 164 deletions

src/compiler/scanner.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,7 @@ namespace ts {
932932
return value;
933933
}
934934

935-
function scanString(allowEscapes = true): string {
935+
function scanString(jsxAttributeString = false): string {
936936
const quote = text.charCodeAt(pos);
937937
pos++;
938938
let result = "";
@@ -950,13 +950,13 @@ namespace ts {
950950
pos++;
951951
break;
952952
}
953-
if (ch === CharacterCodes.backslash && allowEscapes) {
953+
if (ch === CharacterCodes.backslash && !jsxAttributeString) {
954954
result += text.substring(start, pos);
955955
result += scanEscapeSequence();
956956
start = pos;
957957
continue;
958958
}
959-
if (isLineBreak(ch)) {
959+
if (isLineBreak(ch) && !jsxAttributeString) {
960960
result += text.substring(start, pos);
961961
tokenFlags |= TokenFlags.Unterminated;
962962
error(Diagnostics.Unterminated_string_literal);
@@ -1811,7 +1811,7 @@ namespace ts {
18111811
switch (text.charCodeAt(pos)) {
18121812
case CharacterCodes.doubleQuote:
18131813
case CharacterCodes.singleQuote:
1814-
tokenValue = scanString(/*allowEscapes*/ false);
1814+
tokenValue = scanString(/*jsxAttributeString*/ true);
18151815
return token = SyntaxKind.StringLiteral;
18161816
default:
18171817
// If this scans anything other than `{`, it's a parse error.

src/compiler/transformers/jsx.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
namespace ts {
77
export function transformJsx(context: TransformationContext) {
88
const compilerOptions = context.getCompilerOptions();
9+
let currentSourceFile: SourceFile;
910

1011
return transformSourceFile;
1112

@@ -19,6 +20,7 @@ namespace ts {
1920
return node;
2021
}
2122

23+
currentSourceFile = node;
2224
const visited = visitEachChild(node, visitor, context);
2325
addEmitHelpers(visited, context.readEmitHelpers());
2426
return visited;
@@ -167,8 +169,11 @@ namespace ts {
167169
return createTrue();
168170
}
169171
else if (node.kind === SyntaxKind.StringLiteral) {
170-
const decoded = tryDecodeEntities((<StringLiteral>node).text);
171-
return decoded ? setTextRange(createLiteral(decoded), node) : node;
172+
// Always recreate the literal to escape any escape sequences or newlines which may be in the original jsx string and which
173+
// Need to be escaped to be handled correctly in a normal string
174+
const literal = createLiteral(tryDecodeEntities((<StringLiteral>node).text) || (<StringLiteral>node).text);
175+
literal.singleQuote = (node as StringLiteral).singleQuote !== undefined ? (node as StringLiteral).singleQuote : !isStringDoubleQuoted(node as StringLiteral, currentSourceFile);
176+
return setTextRange(literal, node);
172177
}
173178
else if (node.kind === SyntaxKind.JsxExpression) {
174179
if (node.expression === undefined) {

tests/baselines/reference/jsxInvalidEsprimaTestSuite.errors.txt

Lines changed: 166 additions & 81 deletions
Large diffs are not rendered by default.
Lines changed: 128 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,159 @@
1-
//// [jsxInvalidEsprimaTestSuite.tsx]
1+
//// [tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx] ////
2+
3+
//// [1.tsx]
24
declare var React: any;
35

4-
</>;
5-
<a: />;
6-
<:a />;
7-
<a b=d />;
8-
<a>;
9-
<a></b>;
10-
<a foo="bar;
11-
<a:b></b>;
12-
<a:b.c></a:b.c>;
13-
<a.b:c></a.b:c>;
14-
<a.b.c></a>;
15-
<.a></.a>;
16-
<a.></a.>;
17-
<a[foo]></a[foo]>;
18-
<a['foo']></a['foo']>;
19-
<a><a />;
20-
<a b={}>;
21-
var x = <div>one</div><div>two</div>;;
22-
var x = <div>one</div> /* intervening comment */ <div>two</div>;;
23-
<a>{"str";}</a>;
24-
<span className="a", id="b" />;
25-
<div className"app">;
6+
</>;
7+
//// [2.tsx]
8+
<a: />;
9+
//// [3.tsx]
10+
<:a />;
11+
//// [4.tsx]
12+
<a b=d />;
13+
//// [5.tsx]
14+
<a>;
15+
//// [6.tsx]
16+
<a></b>;
17+
//// [7.tsx]
18+
<a foo="bar;
19+
//// [8.tsx]
20+
<a:b></b>;
21+
//// [9.tsx]
22+
<a:b.c></a:b.c>;
23+
//// [10.tsx]
24+
<a.b:c></a.b:c>;
25+
//// [11.tsx]
26+
<a.b.c></a>;
27+
//// [12.tsx]
28+
<.a></.a>;
29+
//// [13.tsx]
30+
<a.></a.>;
31+
//// [14.tsx]
32+
<a[foo]></a[foo]>;
33+
//// [15.tsx]
34+
<a['foo']></a['foo']>;
35+
//// [16.tsx]
36+
<a><a />;
37+
//// [17.tsx]
38+
<a b={}>;
39+
//// [18.tsx]
40+
var x = <div>one</div><div>two</div>;;
41+
//// [19.tsx]
42+
var x = <div>one</div> /* intervening comment */ <div>two</div>;;
43+
//// [20.tsx]
44+
<a>{"str";}</a>;
45+
//// [21.tsx]
46+
<span className="a", id="b" />;
47+
//// [22.tsx]
48+
<div className"app">;
49+
//// [23.tsx]
2650
<div {props} />;
27-
51+
52+
//// [24.tsx]
2853
<div>stuff</div {...props}>;
54+
55+
//// [25.tsx]
2956
<div {...props}>stuff</div {...props}>;
3057

58+
59+
//// [26.tsx]
3160
<a>></a>;
61+
62+
//// [27.tsx]
3263
<a> ></a>;
64+
65+
//// [28.tsx]
3366
<a b=}>;
67+
68+
//// [29.tsx]
3469
<a b=<}>;
70+
71+
//// [30.tsx]
3572
<a>}</a>;
73+
74+
//// [31.tsx]
3675
<a .../*hai*/asdf/>;
3776

38-
//// [jsxInvalidEsprimaTestSuite.jsx]
77+
//// [1.jsx]
3978
> ;
79+
//// [2.jsx]
4080
<a />;
41-
< ;
81+
//// [3.jsx]
82+
< ;
4283
a / > ;
43-
<a b={d / > }/>
44-
,
45-
<a>;
46-
<a></b>;
47-
<a foo="bar;/>a:b></b>;
84+
//// [4.jsx]
85+
<a b={d / > }/>;
86+
//// [5.jsx]
87+
<a>;</>;
88+
//// [6.jsx]
89+
<a></b>;
90+
//// [7.jsx]
91+
<a foo="bar;/>;
92+
//// [8.jsx]
93+
<a b></b>;
94+
//// [9.jsx]
4895
<a b c></a>;
4996
b.c > ;
97+
//// [10.jsx]
5098
<a.b c></a.b>;
5199
c > ;
100+
//// [11.jsx]
52101
<a.b.c></a>;
53-
< .a > ;
102+
//// [12.jsx]
103+
< .a > ;
54104
a > ;
105+
//// [13.jsx]
55106
<a.></a.>;
107+
//// [14.jsx]
56108
<a />;
57109
[foo] > ;
58110
a[foo] > ;
111+
//// [15.jsx]
59112
<a />;
60113
['foo'] > ;
61114
a['foo'] > ;
62-
<a><a />;
63-
<a b=>;
64-
var x = <div>one</div><div>two</div>;;
65-
var x = <div>one</div> /* intervening comment */ /* intervening comment */ <div>two</div>;;
66-
<a>{"str"}}</a>;
67-
<span className="a"/> id="b" />;
68-
<div className/>>;
69-
<div {...props}/>;
70-
71-
<div>stuff</div>...props}>;
72-
<div {...props}>stuff</div>...props}>;
73-
74-
<a>></a>;
75-
<a> ></a>;
115+
//// [16.jsx]
116+
<a><a />;</>;
117+
//// [17.jsx]
118+
<a b=>;</>;
119+
//// [18.jsx]
120+
var x = <div>one</div>, <div>two</div>;
121+
;
122+
//// [19.jsx]
123+
var x = <div>one</div> /* intervening comment */, /* intervening comment */ <div>two</div>;
124+
;
125+
//// [20.jsx]
126+
<a>{"str"}}</a>;
127+
//// [21.jsx]
128+
<span className="a" id="b"/>;
129+
//// [22.jsx]
130+
<div className/>;
131+
"app" > ;
132+
//// [23.jsx]
133+
<div {...props}/>;
134+
//// [24.jsx]
135+
<div>stuff</div>;
136+
{
137+
props;
138+
}
139+
> ;
140+
//// [25.jsx]
141+
<div {...props}>stuff</div>;
142+
{
143+
props;
144+
}
145+
> ;
146+
//// [26.jsx]
147+
<a>></a>;
148+
//// [27.jsx]
149+
<a> ></a>;
150+
//// [28.jsx]
76151
<a b=>;
152+
</>;
153+
//// [29.jsx]
77154
<a b={ < }>;
78-
<a>}</a>;
79-
<a /> /*hai*//*hai*/asdf/>;</></></></>;
155+
</>;
156+
//// [30.jsx]
157+
<a>}</a>;
158+
//// [31.jsx]
159+
<a asdf/>;

0 commit comments

Comments
 (0)