Skip to content

Commit a8eb4a2

Browse files
author
Andy Hanson
committed
Also decode entities when emitting attributes. Also, lexer should not process string escapes in jsx attributes.
1 parent eea0380 commit a8eb4a2

File tree

8 files changed

+111
-7
lines changed

8 files changed

+111
-7
lines changed

src/compiler/emitter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2030,7 +2030,7 @@ const _super = (function (geti, seti) {
20302030
emitTrailingCommentsOfPosition(commentRange.pos);
20312031
}
20322032

2033-
emitExpression(node.initializer);
2033+
emitExpression(initializer);
20342034
}
20352035

20362036
function emitShorthandPropertyAssignment(node: ShorthandPropertyAssignment) {

src/compiler/parser.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,10 @@ namespace ts {
905905
return currentToken = scanner.scanJsxToken();
906906
}
907907

908+
function scanJsxAttributeValue(): SyntaxKind {
909+
return currentToken = scanner.scanJsxAttributeValue();
910+
}
911+
908912
function speculationHelper<T>(callback: () => T, isLookAhead: boolean): T {
909913
// Keep track of the state we'll need to rollback to if lookahead fails (or if the
910914
// caller asked us to always reset our state).
@@ -3831,8 +3835,8 @@ namespace ts {
38313835
scanJsxIdentifier();
38323836
const node = <JsxAttribute>createNode(SyntaxKind.JsxAttribute);
38333837
node.name = parseIdentifierName();
3834-
if (parseOptional(SyntaxKind.EqualsToken)) {
3835-
switch (token()) {
3838+
if (token() === SyntaxKind.EqualsToken) {
3839+
switch (scanJsxAttributeValue()) {
38363840
case SyntaxKind.StringLiteral:
38373841
node.initializer = parseLiteralNode();
38383842
break;

src/compiler/scanner.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ namespace ts {
2727
reScanSlashToken(): SyntaxKind;
2828
reScanTemplateToken(): SyntaxKind;
2929
scanJsxIdentifier(): SyntaxKind;
30+
scanJsxAttributeValue(): SyntaxKind;
3031
reScanJsxToken(): SyntaxKind;
3132
scanJsxToken(): SyntaxKind;
3233
scanJSDocToken(): SyntaxKind;
@@ -817,6 +818,7 @@ namespace ts {
817818
reScanSlashToken,
818819
reScanTemplateToken,
819820
scanJsxIdentifier,
821+
scanJsxAttributeValue,
820822
reScanJsxToken,
821823
scanJsxToken,
822824
scanJSDocToken,
@@ -911,7 +913,7 @@ namespace ts {
911913
return value;
912914
}
913915

914-
function scanString(): string {
916+
function scanString(allowEscapes = true): string {
915917
const quote = text.charCodeAt(pos);
916918
pos++;
917919
let result = "";
@@ -929,7 +931,7 @@ namespace ts {
929931
pos++;
930932
break;
931933
}
932-
if (ch === CharacterCodes.backslash) {
934+
if (ch === CharacterCodes.backslash && allowEscapes) {
933935
result += text.substring(start, pos);
934936
result += scanEscapeSequence();
935937
start = pos;
@@ -1737,6 +1739,20 @@ namespace ts {
17371739
return token;
17381740
}
17391741

1742+
function scanJsxAttributeValue(): SyntaxKind {
1743+
startPos = pos;
1744+
1745+
switch (text.charCodeAt(pos)) {
1746+
case CharacterCodes.doubleQuote:
1747+
case CharacterCodes.singleQuote:
1748+
tokenValue = scanString(/*allowEscapes*/ false);
1749+
return token = SyntaxKind.StringLiteral;
1750+
default:
1751+
// If this scans anything other than `{`, it's a parse error.
1752+
return scan();
1753+
}
1754+
}
1755+
17401756
function scanJSDocToken(): SyntaxKind {
17411757
if (pos >= end) {
17421758
return token = SyntaxKind.EndOfFileToken;

src/compiler/transformers/jsx.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ namespace ts {
140140
return createLiteral(true);
141141
}
142142
else if (node.kind === SyntaxKind.StringLiteral) {
143-
return node;
143+
const decoded = tryDecodeEntities((<StringLiteral>node).text);
144+
return decoded ? createLiteral(decoded, /*location*/ node) : node;
144145
}
145146
else if (node.kind === SyntaxKind.JsxExpression) {
146147
return visitJsxExpression(<JsxExpression>node);
@@ -213,7 +214,7 @@ namespace ts {
213214
* Replace entities like "&nbsp;", "&#123;", and "&#xDEADBEEF;" with the characters they encode.
214215
* See https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
215216
*/
216-
function decodeEntities(text: string) {
217+
function decodeEntities(text: string): string {
217218
return text.replace(/&((#((\d+)|x([\da-fA-F]+)))|(\w+));/g, (match, _all, _number, _digits, decimal, hex, word) => {
218219
if (decimal) {
219220
return String.fromCharCode(parseInt(decimal, 10));
@@ -229,6 +230,12 @@ namespace ts {
229230
});
230231
}
231232

233+
/** Like `decodeEntities` but returns `undefined` if there were no entities to decode. */
234+
function tryDecodeEntities(text: string): string | undefined {
235+
const decoded = decodeEntities(text);
236+
return decoded === text ? undefined : decoded;
237+
}
238+
232239
function getTagName(node: JsxElement | JsxOpeningLikeElement): Expression {
233240
if (node.kind === SyntaxKind.JsxElement) {
234241
return getTagName((<JsxElement>node).openingElement);

tests/baselines/reference/tsxReactEmitEntities.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,26 @@ declare var React: any;
1010
<div>Dot goes here: &middot; &notAnEntity; </div>;
1111
<div>Be careful of &quot;-ed strings!</div>;
1212
<div>&#0123;&#123;braces&#x7d;&#x7D;</div>;
13+
// Escapes do nothing
14+
<div>\n</div>;
15+
16+
// Also works in string literal attributes
17+
<div attr="&#0123;&hellip;&#x7D;\"></div>;
18+
// Does not happen for a string literal that happens to be inside an attribute (and escapes then work)
19+
<div attr={"&#0123;&hellip;&#x7D;\""}></div>;
20+
// Preserves single quotes
21+
<div attr='"'></div>
1322

1423

1524
//// [file.js]
1625
React.createElement("div", null, "Dot goes here: \u00B7 &notAnEntity; ");
1726
React.createElement("div", null, "Be careful of \"-ed strings!");
1827
React.createElement("div", null, "{{braces}}");
28+
// Escapes do nothing
29+
React.createElement("div", null, "\\n");
30+
// Also works in string literal attributes
31+
React.createElement("div", { attr: "{\u2026}\\" });
32+
// Does not happen for a string literal that happens to be inside an attribute (and escapes then work)
33+
React.createElement("div", { attr: "&#0123;&hellip;&#x7D;\"" });
34+
// Preserves single quotes
35+
React.createElement("div", { attr: '"' });

tests/baselines/reference/tsxReactEmitEntities.symbols

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,26 @@ declare var React: any;
2727
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
2828
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
2929

30+
// Escapes do nothing
31+
<div>\n</div>;
32+
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
33+
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
34+
35+
// Also works in string literal attributes
36+
<div attr="&#0123;&hellip;&#x7D;\"></div>;
37+
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
38+
>attr : Symbol(unknown)
39+
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
40+
41+
// Does not happen for a string literal that happens to be inside an attribute (and escapes then work)
42+
<div attr={"&#0123;&hellip;&#x7D;\""}></div>;
43+
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
44+
>attr : Symbol(unknown)
45+
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
46+
47+
// Preserves single quotes
48+
<div attr='"'></div>
49+
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
50+
>attr : Symbol(unknown)
51+
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
52+

tests/baselines/reference/tsxReactEmitEntities.types

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,31 @@ declare var React: any;
3030
>div : any
3131
>div : any
3232

33+
// Escapes do nothing
34+
<div>\n</div>;
35+
><div>\n</div> : JSX.Element
36+
>div : any
37+
>div : any
38+
39+
// Also works in string literal attributes
40+
<div attr="&#0123;&hellip;&#x7D;\"></div>;
41+
><div attr="&#0123;&hellip;&#x7D;\"></div> : JSX.Element
42+
>div : any
43+
>attr : any
44+
>div : any
45+
46+
// Does not happen for a string literal that happens to be inside an attribute (and escapes then work)
47+
<div attr={"&#0123;&hellip;&#x7D;\""}></div>;
48+
><div attr={"&#0123;&hellip;&#x7D;\""}></div> : JSX.Element
49+
>div : any
50+
>attr : any
51+
>"&#0123;&hellip;&#x7D;\"" : string
52+
>div : any
53+
54+
// Preserves single quotes
55+
<div attr='"'></div>
56+
><div attr='"'></div> : JSX.Element
57+
>div : any
58+
>attr : any
59+
>div : any
60+

tests/cases/conformance/jsx/tsxReactEmitEntities.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,12 @@ declare var React: any;
1111
<div>Dot goes here: &middot; &notAnEntity; </div>;
1212
<div>Be careful of &quot;-ed strings!</div>;
1313
<div>&#0123;&#123;braces&#x7d;&#x7D;</div>;
14+
// Escapes do nothing
15+
<div>\n</div>;
16+
17+
// Also works in string literal attributes
18+
<div attr="&#0123;&hellip;&#x7D;\"></div>;
19+
// Does not happen for a string literal that happens to be inside an attribute (and escapes then work)
20+
<div attr={"&#0123;&hellip;&#x7D;\""}></div>;
21+
// Preserves single quotes
22+
<div attr='"'></div>

0 commit comments

Comments
 (0)