Skip to content

Commit d90795e

Browse files
authoredSep 20, 2022
Improve escape sequence handling in private names (#50856)
* Add tests for identifiers and private identifiers with escape sequences. * Accepted baselines. * Store the tokenValue instead of tokenText on PrivateIdentifiers, since the latter can contain escapes and lead to semantic discrepancies. * Accepted baselines. * Check for leading escape sequences in PrivateIdentifiers. * Accepted baselines. * Fix lints.
1 parent 938a69a commit d90795e

10 files changed

+1761
-4
lines changed
 

‎src/compiler/parser.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2281,7 +2281,7 @@ namespace ts {
22812281

22822282
function parsePrivateIdentifier(): PrivateIdentifier {
22832283
const pos = getNodePos();
2284-
const node = factory.createPrivateIdentifier(internPrivateIdentifier(scanner.getTokenText()));
2284+
const node = factory.createPrivateIdentifier(internPrivateIdentifier(scanner.getTokenValue()));
22852285
nextToken();
22862286
return finishNode(node, pos);
22872287
}

‎src/compiler/scanner.ts

+29-3
Original file line numberDiff line numberDiff line change
@@ -2052,12 +2052,38 @@ namespace ts {
20522052
return token = SyntaxKind.Unknown;
20532053
}
20542054

2055-
if (isIdentifierStart(codePointAt(text, pos + 1), languageVersion)) {
2055+
const charAfterHash = codePointAt(text, pos + 1);
2056+
if (charAfterHash === CharacterCodes.backslash) {
20562057
pos++;
2057-
scanIdentifier(codePointAt(text, pos), languageVersion);
2058+
const extendedCookedChar = peekExtendedUnicodeEscape();
2059+
if (extendedCookedChar >= 0 && isIdentifierStart(extendedCookedChar, languageVersion)) {
2060+
pos += 3;
2061+
tokenFlags |= TokenFlags.ExtendedUnicodeEscape;
2062+
tokenValue = "#" + scanExtendedUnicodeEscape() + scanIdentifierParts();
2063+
return token = SyntaxKind.PrivateIdentifier;
2064+
}
2065+
2066+
const cookedChar = peekUnicodeEscape();
2067+
if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) {
2068+
pos += 6;
2069+
tokenFlags |= TokenFlags.UnicodeEscape;
2070+
tokenValue = "#" + String.fromCharCode(cookedChar) + scanIdentifierParts();
2071+
return token = SyntaxKind.PrivateIdentifier;
2072+
}
2073+
pos--;
2074+
}
2075+
2076+
if (isIdentifierStart(charAfterHash, languageVersion)) {
2077+
pos++;
2078+
// We're relying on scanIdentifier's behavior and adjusting the token kind after the fact.
2079+
// Notably absent from this block is the fact that calling a function named "scanIdentifier",
2080+
// but identifiers don't include '#', and that function doesn't deal with it at all.
2081+
// This works because 'scanIdentifier' tries to reuse source characters and builds up substrings;
2082+
// however, it starts at the 'tokenPos' which includes the '#', and will "accidentally" prepend the '#' for us.
2083+
scanIdentifier(charAfterHash, languageVersion);
20582084
}
20592085
else {
2060-
tokenValue = String.fromCharCode(codePointAt(text, pos));
2086+
tokenValue = "#";
20612087
error(Diagnostics.Invalid_character, pos++, charSize(ch));
20622088
}
20632089
return token = SyntaxKind.PrivateIdentifier;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
//// [tests/cases/conformance/classes/members/privateNames/privateNamesEscapeSequences01.ts] ////
2+
3+
//// [IdentifierNameWithEscape1.ts]
4+
export class IdentifierNameWithEscape1 {
5+
\u0078: number;
6+
7+
constructor() {
8+
this.\u0078 = 0;
9+
}
10+
11+
doThing() {
12+
this.x = 42;
13+
}
14+
}
15+
16+
//// [IdentifierNameWithEscape2.ts]
17+
export class IdentifierNameWithEscape2 {
18+
x\u0078: number;
19+
20+
constructor() {
21+
this.x\u0078 = 0;
22+
}
23+
24+
doThing() {
25+
this.xx = 42;
26+
}
27+
}
28+
29+
//// [IdentifierNameWithExtendedEscape1.ts]
30+
export class IdentifierNameWithExtendedEscape1 {
31+
\u{78}: number;
32+
33+
constructor() {
34+
this.\u{78} = 0;
35+
}
36+
37+
doThing() {
38+
this.x = 42;
39+
}
40+
}
41+
42+
//// [IdentifierNameWithExtendedEscape2.ts]
43+
export class IdentifierNameWithExtendedEscape2 {
44+
x\u{78}: number;
45+
46+
constructor() {
47+
this.x\u{78} = 0;
48+
}
49+
50+
doThing() {
51+
this.xx = 42;
52+
}
53+
}
54+
55+
//// [PrivateIdentifierNameWithEscape1.ts]
56+
export class PrivateIdentifierWithEscape1 {
57+
#\u0078: number;
58+
59+
constructor() {
60+
this.#\u0078 = 0;
61+
}
62+
63+
doThing() {
64+
this.#x = 42;
65+
}
66+
}
67+
68+
//// [PrivateIdentifierNameWithEscape2.ts]
69+
export class PrivateIdentifierWithEscape2 {
70+
#x\u0078: number;
71+
72+
constructor() {
73+
this.#x\u0078 = 0;
74+
}
75+
76+
doThing() {
77+
this.#xx = 42;
78+
}
79+
}
80+
81+
//// [PrivateIdentifierNameWithExtendedEscape1.ts]
82+
export class PrivateIdentifierWithExtendedEscape1 {
83+
#\u{78}: number;
84+
85+
constructor() {
86+
this.#\u{78} = 0;
87+
}
88+
89+
doThing() {
90+
this.#x = 42;
91+
}
92+
}
93+
94+
//// [PrivateIdentifierNameWithExtendedEscape2.ts]
95+
export class PrivateIdentifierWithExtendedEscape2 {
96+
#x\u{78}: number;
97+
98+
constructor() {
99+
this.#x\u{78} = 0;
100+
}
101+
102+
doThing() {
103+
this.#xx = 42;
104+
}
105+
}
106+
107+
108+
//// [IdentifierNameWithEscape1.js]
109+
export class IdentifierNameWithEscape1 {
110+
constructor() {
111+
this.\u0078 = 0;
112+
}
113+
doThing() {
114+
this.x = 42;
115+
}
116+
}
117+
//// [IdentifierNameWithEscape2.js]
118+
export class IdentifierNameWithEscape2 {
119+
constructor() {
120+
this.x\u0078 = 0;
121+
}
122+
doThing() {
123+
this.xx = 42;
124+
}
125+
}
126+
//// [IdentifierNameWithExtendedEscape1.js]
127+
export class IdentifierNameWithExtendedEscape1 {
128+
constructor() {
129+
this.\u{78} = 0;
130+
}
131+
doThing() {
132+
this.x = 42;
133+
}
134+
}
135+
//// [IdentifierNameWithExtendedEscape2.js]
136+
export class IdentifierNameWithExtendedEscape2 {
137+
constructor() {
138+
this.x\u{78} = 0;
139+
}
140+
doThing() {
141+
this.xx = 42;
142+
}
143+
}
144+
//// [PrivateIdentifierNameWithEscape1.js]
145+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
146+
if (kind === "m") throw new TypeError("Private method is not writable");
147+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
148+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
149+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
150+
};
151+
var _PrivateIdentifierWithEscape1_x;
152+
export class PrivateIdentifierWithEscape1 {
153+
constructor() {
154+
_PrivateIdentifierWithEscape1_x.set(this, void 0);
155+
__classPrivateFieldSet(this, _PrivateIdentifierWithEscape1_x, 0, "f");
156+
}
157+
doThing() {
158+
__classPrivateFieldSet(this, _PrivateIdentifierWithEscape1_x, 42, "f");
159+
}
160+
}
161+
_PrivateIdentifierWithEscape1_x = new WeakMap();
162+
//// [PrivateIdentifierNameWithEscape2.js]
163+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
164+
if (kind === "m") throw new TypeError("Private method is not writable");
165+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
166+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
167+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
168+
};
169+
var _PrivateIdentifierWithEscape2_xx;
170+
export class PrivateIdentifierWithEscape2 {
171+
constructor() {
172+
_PrivateIdentifierWithEscape2_xx.set(this, void 0);
173+
__classPrivateFieldSet(this, _PrivateIdentifierWithEscape2_xx, 0, "f");
174+
}
175+
doThing() {
176+
__classPrivateFieldSet(this, _PrivateIdentifierWithEscape2_xx, 42, "f");
177+
}
178+
}
179+
_PrivateIdentifierWithEscape2_xx = new WeakMap();
180+
//// [PrivateIdentifierNameWithExtendedEscape1.js]
181+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
182+
if (kind === "m") throw new TypeError("Private method is not writable");
183+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
184+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
185+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
186+
};
187+
var _PrivateIdentifierWithExtendedEscape1_x;
188+
export class PrivateIdentifierWithExtendedEscape1 {
189+
constructor() {
190+
_PrivateIdentifierWithExtendedEscape1_x.set(this, void 0);
191+
__classPrivateFieldSet(this, _PrivateIdentifierWithExtendedEscape1_x, 0, "f");
192+
}
193+
doThing() {
194+
__classPrivateFieldSet(this, _PrivateIdentifierWithExtendedEscape1_x, 42, "f");
195+
}
196+
}
197+
_PrivateIdentifierWithExtendedEscape1_x = new WeakMap();
198+
//// [PrivateIdentifierNameWithExtendedEscape2.js]
199+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
200+
if (kind === "m") throw new TypeError("Private method is not writable");
201+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
202+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
203+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
204+
};
205+
var _PrivateIdentifierWithExtendedEscape2_xx;
206+
export class PrivateIdentifierWithExtendedEscape2 {
207+
constructor() {
208+
_PrivateIdentifierWithExtendedEscape2_xx.set(this, void 0);
209+
__classPrivateFieldSet(this, _PrivateIdentifierWithExtendedEscape2_xx, 0, "f");
210+
}
211+
doThing() {
212+
__classPrivateFieldSet(this, _PrivateIdentifierWithExtendedEscape2_xx, 42, "f");
213+
}
214+
}
215+
_PrivateIdentifierWithExtendedEscape2_xx = new WeakMap();

0 commit comments

Comments
 (0)
Please sign in to comment.