Skip to content

Commit 8523ca4

Browse files
authored
Don’t create missing nodes for identifiers that would be valid in a newer script target (microsoft#42520)
* Add test * Don’t create missing nodes for identifiers that would be valid in a newer script target * Add to test * Remove unnecessary assignment
1 parent d7d7b88 commit 8523ca4

File tree

7 files changed

+88
-11
lines changed

7 files changed

+88
-11
lines changed

Diff for: src/compiler/parser.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1637,8 +1637,8 @@ namespace ts {
16371637
// with magic property names like '__proto__'. The 'identifiers' object is used to share a single string instance for
16381638
// each identifier in order to reduce memory consumption.
16391639
function createIdentifier(isIdentifier: boolean, diagnosticMessage?: DiagnosticMessage, privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier {
1640-
identifierCount++;
16411640
if (isIdentifier) {
1641+
identifierCount++;
16421642
const pos = getNodePos();
16431643
// Store original token kind if it is not just an Identifier so we can report appropriate error later in type checker
16441644
const originalKeywordKind = token();
@@ -1652,6 +1652,12 @@ namespace ts {
16521652
return createIdentifier(/*isIdentifier*/ true);
16531653
}
16541654

1655+
if (token() === SyntaxKind.Unknown && scanner.tryScan(() => scanner.reScanInvalidIdentifier() === SyntaxKind.Identifier)) {
1656+
// Scanner has already recorded an 'Invalid character' error, so no need to add another from the parser.
1657+
return createIdentifier(/*isIdentifier*/ true);
1658+
}
1659+
1660+
identifierCount++;
16551661
// Only for end of file because the error gets reported incorrectly on embedded script tags.
16561662
const reportAtCurrentPosition = token() === SyntaxKind.EndOfFileToken;
16571663

Diff for: src/compiler/scanner.ts

+31-8
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ namespace ts {
4343
reScanJsxToken(): JsxTokenSyntaxKind;
4444
reScanLessThanToken(): SyntaxKind;
4545
reScanQuestionToken(): SyntaxKind;
46+
reScanInvalidIdentifier(): SyntaxKind;
4647
scanJsxToken(): JsxTokenSyntaxKind;
4748
scanJsDocToken(): JSDocSyntaxKind;
4849
scan(): SyntaxKind;
@@ -966,6 +967,7 @@ namespace ts {
966967
reScanJsxToken,
967968
reScanLessThanToken,
968969
reScanQuestionToken,
970+
reScanInvalidIdentifier,
969971
scanJsxToken,
970972
scanJsDocToken,
971973
scan,
@@ -2041,14 +2043,9 @@ namespace ts {
20412043
}
20422044
return token = SyntaxKind.PrivateIdentifier;
20432045
default:
2044-
if (isIdentifierStart(ch, languageVersion)) {
2045-
pos += charSize(ch);
2046-
while (pos < end && isIdentifierPart(ch = codePointAt(text, pos), languageVersion)) pos += charSize(ch);
2047-
tokenValue = text.substring(tokenPos, pos);
2048-
if (ch === CharacterCodes.backslash) {
2049-
tokenValue += scanIdentifierParts();
2050-
}
2051-
return token = getIdentifierToken();
2046+
const identifierKind = scanIdentifier(ch, languageVersion);
2047+
if (identifierKind) {
2048+
return token = identifierKind;
20522049
}
20532050
else if (isWhiteSpaceSingleLine(ch)) {
20542051
pos += charSize(ch);
@@ -2066,6 +2063,32 @@ namespace ts {
20662063
}
20672064
}
20682065

2066+
function reScanInvalidIdentifier(): SyntaxKind {
2067+
Debug.assert(token === SyntaxKind.Unknown, "'reScanInvalidIdentifier' should only be called when the current token is 'SyntaxKind.Unknown'.");
2068+
pos = tokenPos = startPos;
2069+
tokenFlags = 0;
2070+
const ch = codePointAt(text, pos);
2071+
const identifierKind = scanIdentifier(ch, ScriptTarget.ESNext);
2072+
if (identifierKind) {
2073+
return token = identifierKind;
2074+
}
2075+
pos += charSize(ch);
2076+
return token; // Still `SyntaKind.Unknown`
2077+
}
2078+
2079+
function scanIdentifier(startCharacter: number, languageVersion: ScriptTarget) {
2080+
let ch = startCharacter;
2081+
if (isIdentifierStart(ch, languageVersion)) {
2082+
pos += charSize(ch);
2083+
while (pos < end && isIdentifierPart(ch = codePointAt(text, pos), languageVersion)) pos += charSize(ch);
2084+
tokenValue = text.substring(tokenPos, pos);
2085+
if (ch === CharacterCodes.backslash) {
2086+
tokenValue += scanIdentifierParts();
2087+
}
2088+
return getIdentifierToken();
2089+
}
2090+
}
2091+
20692092
function reScanGreaterToken(): SyntaxKind {
20702093
if (token === SyntaxKind.GreaterThanToken) {
20712094
if (text.charCodeAt(pos) === CharacterCodes.greaterThan) {

Diff for: src/harness/fourslashImpl.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1453,9 +1453,9 @@ namespace FourSlash {
14531453
}
14541454

14551455
public baselineRename(marker: string, options: FourSlashInterface.RenameOptions) {
1456-
const position = this.getMarkerByName(marker).position;
1456+
const { fileName, position } = this.getMarkerByName(marker);
14571457
const locations = this.languageService.findRenameLocations(
1458-
this.activeFile.fileName,
1458+
fileName,
14591459
position,
14601460
options.findInStrings ?? false,
14611461
options.findInComments ?? false,

Diff for: tests/baselines/reference/api/tsserverlibrary.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3970,6 +3970,7 @@ declare namespace ts {
39703970
reScanJsxToken(): JsxTokenSyntaxKind;
39713971
reScanLessThanToken(): SyntaxKind;
39723972
reScanQuestionToken(): SyntaxKind;
3973+
reScanInvalidIdentifier(): SyntaxKind;
39733974
scanJsxToken(): JsxTokenSyntaxKind;
39743975
scanJsDocToken(): JSDocSyntaxKind;
39753976
scan(): SyntaxKind;

Diff for: tests/baselines/reference/api/typescript.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3970,6 +3970,7 @@ declare namespace ts {
39703970
reScanJsxToken(): JsxTokenSyntaxKind;
39713971
reScanLessThanToken(): SyntaxKind;
39723972
reScanQuestionToken(): SyntaxKind;
3973+
reScanInvalidIdentifier(): SyntaxKind;
39733974
scanJsxToken(): JsxTokenSyntaxKind;
39743975
scanJsDocToken(): JSDocSyntaxKind;
39753976
scan(): SyntaxKind;
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*====== /tests/cases/fourslash/decl.js ======*/
2+
3+
var RENAME = {};
4+
5+
/*====== /tests/cases/fourslash/unicode1.js ======*/
6+
7+
RENAME.𝒜 ;
8+
9+
/*====== /tests/cases/fourslash/unicode2.js ======*/
10+
11+
RENAME.¬ ;
12+
13+
/*====== /tests/cases/fourslash/unicode3.js ======*/
14+
15+
RENAME¬
16+
17+
/*====== /tests/cases/fourslash/forof.js ======*/
18+
19+
for (RENAME.prop of arr) {
20+
21+
}

Diff for: tests/cases/fourslash/processInvalidSyntax1.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @allowJs: true
4+
5+
// Test validates that language service getChildren() doesn't
6+
// crash due to invalid identifier in unicode.js.
7+
8+
// @Filename: decl.js
9+
//// var obj = {};
10+
11+
// @Filename: unicode1.js
12+
//// obj.𝒜 ;
13+
14+
// @Filename: unicode2.js
15+
//// obj.¬ ;
16+
17+
// @Filename: unicode3.js
18+
//// obj¬
19+
20+
// @Filename: forof.js
21+
//// for (obj/**/.prop of arr) {
22+
////
23+
//// }
24+
25+
verify.baselineRename("", {});

0 commit comments

Comments
 (0)