Skip to content

Commit ec81e4d

Browse files
author
Josh Goldberg
committed
Specific diagnostic suggestions for unexpected keywords or identifier
1 parent 26bbdf1 commit ec81e4d

File tree

47 files changed

+1123
-288
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1123
-288
lines changed

src/compiler/checker.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
2+
3+
console.log("updated");
4+
15
/* @internal */
26
namespace ts {
37
const ambientModuleSymbolRegex = /^".+"$/;

src/compiler/diagnosticMessages.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,50 @@
13601360
"category": "Error",
13611361
"code": 1432
13621362
},
1363+
"Unexpected keyword or identifier.": {
1364+
"category": "Error",
1365+
"code": 1433
1366+
},
1367+
"Unknown keyword or identifier. Did you mean '{0}'?": {
1368+
"category": "Error",
1369+
"code": 1434
1370+
},
1371+
"Decorators must precede all other keywords for property declarations.": {
1372+
"category": "Error",
1373+
"code": 1435
1374+
},
1375+
"Namespace must be given a name.": {
1376+
"category": "Error",
1377+
"code": 1436
1378+
},
1379+
"Interface must be given a name.": {
1380+
"category": "Error",
1381+
"code": 1437
1382+
},
1383+
"Type alias must be given a name.": {
1384+
"category": "Error",
1385+
"code": 1438
1386+
},
1387+
"Variable declaration not allowed at this location.": {
1388+
"category": "Error",
1389+
"code": 1439
1390+
},
1391+
"Function call not allowed at this location.": {
1392+
"category": "Error",
1393+
"code": 1440
1394+
},
1395+
"Missing '=' before default property value.": {
1396+
"category": "Error",
1397+
"code": 1441
1398+
},
1399+
"Template literal not allowed as a string at this position.": {
1400+
"category": "Error",
1401+
"code": 1442
1402+
},
1403+
"Template literal not allowed as a string at this position. Did you mean '{0}'?": {
1404+
"category": "Error",
1405+
"code": 1443
1406+
},
13631407

13641408
"The types of '{0}' are incompatible between these types.": {
13651409
"category": "Error",
@@ -3264,6 +3308,10 @@
32643308
"category": "Error",
32653309
"code": 2802
32663310
},
3311+
"Namespace name cannot be '{0}'.": {
3312+
"category": "Error",
3313+
"code": 2803
3314+
},
32673315

32683316
"Import declaration '{0}' is using private name '{1}'.": {
32693317
"category": "Error",

src/compiler/parser.ts

Lines changed: 136 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,6 +1518,139 @@ namespace ts {
15181518
return false;
15191519
}
15201520

1521+
const commonKeywords = [
1522+
"async",
1523+
"class",
1524+
"const",
1525+
"declare",
1526+
"export",
1527+
"function",
1528+
"interface",
1529+
"let",
1530+
"type",
1531+
"var",
1532+
];
1533+
1534+
function parseSemicolonAfter(expression: Expression | PropertyName) {
1535+
// Consume the semicolon if it was explicitly provided.
1536+
if (canParseSemicolon()) {
1537+
if (token() === SyntaxKind.SemicolonToken) {
1538+
nextToken();
1539+
}
1540+
1541+
return;
1542+
}
1543+
1544+
// Specialized diagnostics for a keyword expression are redundant if the related token is already complaining.
1545+
const lastError = lastOrUndefined(parseDiagnostics);
1546+
if (lastError && scanner.getTokenPos() < lastError.start + 2) {
1547+
parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken));
1548+
return;
1549+
}
1550+
1551+
// Tagged template literals are sometimes used in places where only simple strings are allowed, e.g.:
1552+
// module `M1` {
1553+
// ^^^^^^^^^^^ This block is parsed as a template literal as with module`M1`.
1554+
if (isTaggedTemplateExpression(expression)) {
1555+
parseErrorAt(skipTrivia(sourceText, expression.template.pos), expression.template.end, Diagnostics.Template_literal_not_allowed_as_a_string_at_this_position);
1556+
return;
1557+
}
1558+
1559+
// Otherwise, if this isn't a well-known keyword-like identifier, give the generic fallback message.
1560+
const expressionText = getExpressionText(expression);
1561+
if (!expressionText || !isIdentifierText(expressionText, languageVersion)) {
1562+
parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken));
1563+
return;
1564+
}
1565+
1566+
const pos = skipTrivia(sourceText, expression.pos);
1567+
1568+
// Some known keywords are likely signs of syntax being used improperly.
1569+
switch (expressionText) {
1570+
case "const":
1571+
case "let":
1572+
case "var":
1573+
parseErrorAt(pos, expression.end, Diagnostics.Variable_declaration_not_allowed_at_this_location);
1574+
return;
1575+
1576+
case "interface":
1577+
parseErrorForExpectedName(scanner.getTokenText(), "{", Diagnostics.Interface_must_be_given_a_name, Diagnostics.Interface_name_cannot_be_0);
1578+
return;
1579+
1580+
case "is":
1581+
parseErrorAt(pos, scanner.getTextPos(), Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods);
1582+
return;
1583+
1584+
case "module":
1585+
case "namespace":
1586+
parseErrorForExpectedName(scanner.getTokenText(), "{", Diagnostics.Namespace_must_be_given_a_name, Diagnostics.Namespace_name_cannot_be_0);
1587+
return;
1588+
1589+
case "type":
1590+
parseErrorForExpectedName(scanner.getTokenText(), "=", Diagnostics.Type_alias_must_be_given_a_name, Diagnostics.Type_alias_name_cannot_be_0);
1591+
return;
1592+
}
1593+
1594+
// The user alternately might have misspelled or forgotten to add a space after a common keyword.
1595+
const suggestion = getSpellingSuggestion(expressionText, commonKeywords, n => n) || getSpaceSuggestion(expressionText);
1596+
if (suggestion) {
1597+
parseErrorAt(pos, expression.end, Diagnostics.Unknown_keyword_or_identifier_Did_you_mean_0, suggestion);
1598+
}
1599+
1600+
// We know this is a slightly more precise case than a missing expected semicolon.
1601+
parseErrorAt(pos, expression.end, Diagnostics.Unexpected_keyword_or_identifier);
1602+
}
1603+
1604+
function parseErrorForExpectedName(name: string, normalToken: string, blankDiagnostic: DiagnosticMessage, nameDiagnostic: DiagnosticMessage) {
1605+
if (name === normalToken) {
1606+
parseErrorAtCurrentToken(blankDiagnostic);
1607+
}
1608+
else {
1609+
parseErrorAtCurrentToken(nameDiagnostic, name);
1610+
}
1611+
}
1612+
1613+
function getSpaceSuggestion(expressionText: string) {
1614+
for (const keyword of commonKeywords) {
1615+
if (expressionText.length > keyword.length + 2 && startsWith(expressionText, keyword)) {
1616+
return `${keyword} ${expressionText.slice(keyword.length)}`;
1617+
}
1618+
}
1619+
1620+
return undefined;
1621+
}
1622+
1623+
function parseSemicolonAfterPropertyName(name: PropertyName, type: TypeNode | undefined, initializer: Expression | undefined) {
1624+
switch (scanner.getTokenText()) {
1625+
case "@":
1626+
parseErrorAtCurrentToken(Diagnostics.Decorators_must_precede_all_other_keywords_for_property_declarations);
1627+
return;
1628+
1629+
case "(":
1630+
parseErrorAtCurrentToken(Diagnostics.Function_call_not_allowed_at_this_location);
1631+
nextToken();
1632+
return;
1633+
}
1634+
1635+
if (type && !canParseSemicolon()) {
1636+
if (initializer) {
1637+
parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken));
1638+
}
1639+
else {
1640+
parseErrorAtCurrentToken(Diagnostics.Missing_before_default_property_value);
1641+
}
1642+
return;
1643+
}
1644+
1645+
return parseSemicolonAfter(name);
1646+
}
1647+
1648+
function getExpressionText(expression: Expression | PropertyName) {
1649+
return expression && ts.isIdentifier(expression)
1650+
? expression.escapedText.toString()
1651+
: undefined;
1652+
}
1653+
15211654
function parseExpectedJSDoc(kind: JSDocSyntaxKind) {
15221655
if (token() === kind) {
15231656
nextTokenJSDoc();
@@ -5786,7 +5919,7 @@ namespace ts {
57865919
identifierCount++;
57875920
expression = finishNode(factory.createIdentifier(""), getNodePos());
57885921
}
5789-
parseSemicolon();
5922+
parseSemicolonAfter(expression);
57905923
return finishNode(factory.createThrowStatement(expression), pos);
57915924
}
57925925

@@ -5847,7 +5980,7 @@ namespace ts {
58475980
node = factory.createLabeledStatement(expression, parseStatement());
58485981
}
58495982
else {
5850-
parseSemicolon();
5983+
parseSemicolonAfter(expression);
58515984
node = factory.createExpressionStatement(expression);
58525985
if (hasParen) {
58535986
// do not parse the same jsdoc twice
@@ -6440,7 +6573,7 @@ namespace ts {
64406573
const exclamationToken = !questionToken && !scanner.hasPrecedingLineBreak() ? parseOptionalToken(SyntaxKind.ExclamationToken) : undefined;
64416574
const type = parseTypeAnnotation();
64426575
const initializer = doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext | NodeFlags.DisallowInContext, parseInitializer);
6443-
parseSemicolon();
6576+
parseSemicolonAfterPropertyName(name, type, initializer);
64446577
const node = factory.createPropertyDeclaration(decorators, modifiers, name, questionToken || exclamationToken, type, initializer);
64456578
return withJSDoc(finishNode(node, pos), hasJSDoc);
64466579
}

tests/baselines/reference/ClassDeclaration26.errors.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
tests/cases/compiler/ClassDeclaration26.ts(2,22): error TS1005: ';' expected.
1+
tests/cases/compiler/ClassDeclaration26.ts(2,18): error TS1439: Variable declaration not allowed at this location.
22
tests/cases/compiler/ClassDeclaration26.ts(4,5): error TS1068: Unexpected token. A constructor, method, accessor, or property was expected.
33
tests/cases/compiler/ClassDeclaration26.ts(4,20): error TS1005: ',' expected.
44
tests/cases/compiler/ClassDeclaration26.ts(4,23): error TS1005: '=>' expected.
@@ -8,8 +8,8 @@ tests/cases/compiler/ClassDeclaration26.ts(5,1): error TS1128: Declaration or st
88
==== tests/cases/compiler/ClassDeclaration26.ts (5 errors) ====
99
class C {
1010
public const var export foo = 10;
11-
~~~~~~
12-
!!! error TS1005: ';' expected.
11+
~~~
12+
!!! error TS1439: Variable declaration not allowed at this location.
1313

1414
var constructor() { }
1515
~~~
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
tests/cases/compiler/anonymousModules.ts(1,1): error TS2580: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.
2-
tests/cases/compiler/anonymousModules.ts(1,8): error TS1005: ';' expected.
2+
tests/cases/compiler/anonymousModules.ts(1,8): error TS1436: Namespace must be given a name.
33
tests/cases/compiler/anonymousModules.ts(4,2): error TS2580: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.
4-
tests/cases/compiler/anonymousModules.ts(4,9): error TS1005: ';' expected.
4+
tests/cases/compiler/anonymousModules.ts(4,9): error TS1436: Namespace must be given a name.
55
tests/cases/compiler/anonymousModules.ts(10,2): error TS2580: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.
6-
tests/cases/compiler/anonymousModules.ts(10,9): error TS1005: ';' expected.
6+
tests/cases/compiler/anonymousModules.ts(10,9): error TS1436: Namespace must be given a name.
77

88

99
==== tests/cases/compiler/anonymousModules.ts (6 errors) ====
1010
module {
1111
~~~~~~
1212
!!! error TS2580: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.
1313
~
14-
!!! error TS1005: ';' expected.
14+
!!! error TS1436: Namespace must be given a name.
1515
export var foo = 1;
1616

1717
module {
1818
~~~~~~
1919
!!! error TS2580: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.
2020
~
21-
!!! error TS1005: ';' expected.
21+
!!! error TS1436: Namespace must be given a name.
2222
export var bar = 1;
2323
}
2424

@@ -28,7 +28,7 @@ tests/cases/compiler/anonymousModules.ts(10,9): error TS1005: ';' expected.
2828
~~~~~~
2929
!!! error TS2580: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.
3030
~
31-
!!! error TS1005: ';' expected.
31+
!!! error TS1436: Namespace must be given a name.
3232
var x = bar;
3333
}
3434
}

tests/baselines/reference/classUpdateTests.errors.txt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ tests/cases/compiler/classUpdateTests.ts(95,1): error TS1128: Declaration or sta
1111
tests/cases/compiler/classUpdateTests.ts(99,3): error TS1128: Declaration or statement expected.
1212
tests/cases/compiler/classUpdateTests.ts(101,1): error TS1128: Declaration or statement expected.
1313
tests/cases/compiler/classUpdateTests.ts(105,3): error TS1128: Declaration or statement expected.
14-
tests/cases/compiler/classUpdateTests.ts(105,14): error TS1005: ';' expected.
14+
tests/cases/compiler/classUpdateTests.ts(105,10): error TS1433: Unexpected keyword or identifier.
15+
tests/cases/compiler/classUpdateTests.ts(105,14): error TS1068: Unexpected token. A constructor, method, accessor, or property was expected.
1516
tests/cases/compiler/classUpdateTests.ts(107,1): error TS1128: Declaration or statement expected.
1617
tests/cases/compiler/classUpdateTests.ts(111,3): error TS1128: Declaration or statement expected.
17-
tests/cases/compiler/classUpdateTests.ts(111,15): error TS1005: ';' expected.
18+
tests/cases/compiler/classUpdateTests.ts(111,11): error TS1433: Unexpected keyword or identifier.
19+
tests/cases/compiler/classUpdateTests.ts(111,15): error TS1068: Unexpected token. A constructor, method, accessor, or property was expected.
1820
tests/cases/compiler/classUpdateTests.ts(113,1): error TS1128: Declaration or statement expected.
1921

2022

21-
==== tests/cases/compiler/classUpdateTests.ts (16 errors) ====
23+
==== tests/cases/compiler/classUpdateTests.ts (18 errors) ====
2224
//
2325
// test codegen for instance properties
2426
//
@@ -154,8 +156,10 @@ tests/cases/compiler/classUpdateTests.ts(113,1): error TS1128: Declaration or st
154156
public this.p1 = 0; // ERROR
155157
~~~~~~
156158
!!! error TS1128: Declaration or statement expected.
159+
~~~~
160+
!!! error TS1433: Unexpected keyword or identifier.
157161
~
158-
!!! error TS1005: ';' expected.
162+
!!! error TS1068: Unexpected token. A constructor, method, accessor, or property was expected.
159163
}
160164
}
161165
~
@@ -166,8 +170,10 @@ tests/cases/compiler/classUpdateTests.ts(113,1): error TS1128: Declaration or st
166170
private this.p1 = 0; // ERROR
167171
~~~~~~~
168172
!!! error TS1128: Declaration or statement expected.
173+
~~~~
174+
!!! error TS1433: Unexpected keyword or identifier.
169175
~
170-
!!! error TS1005: ';' expected.
176+
!!! error TS1068: Unexpected token. A constructor, method, accessor, or property was expected.
171177
}
172178
}
173179
~

0 commit comments

Comments
 (0)