Skip to content

Commit

Permalink
Token hints for missing closing braces: classes, enums, jsx, modules,…
Browse files Browse the repository at this point in the history
… types

This commit adds token hints for missing close braces in

- Class definitions
- Enum definitions
- JSX expressions
- Module definitions (this includes augmentations and namespaces)
- Type member lists (this includes interfaces)

The token hint refers to the opening brace for which the parser expected
a corresponding closing brace.

Note that this commit *does not* update all occurences of an expectation
for a close brace to provide token hints. Constructs for which this
token hint is not provided include:

- Mapped types
- Switch statements
- Object binding patterns
- JSX spread attributes (`<div {...props}></div>`)
- JSDoc implements/augments tags (`/* @implements {SomeInterface} */`)

Part of #35597.
  • Loading branch information
Ayaz Hafiz committed Mar 20, 2020
1 parent 5e9c436 commit f2d57da
Show file tree
Hide file tree
Showing 48 changed files with 349 additions and 34 deletions.
2 changes: 1 addition & 1 deletion src/compiler/diagnosticMessages.json
Expand Up @@ -15,7 +15,7 @@
"category": "Error",
"code": 1006
},
"The parser expected to find a '}' to match the '{' token here.": {
"The parser expected to find a '{0}' to match the '{1}' token here.": {
"category": "Error",
"code": 1007
},
Expand Down
54 changes: 30 additions & 24 deletions src/compiler/parser.ts
Expand Up @@ -1308,6 +1308,23 @@ namespace ts {
return false;
}

function parseExpectedCloseToken(closeTokenKind: SyntaxKind, openTokenKind: SyntaxKind, openTokenPos: number, shouldAdvance = true): boolean {
if (!parseExpected(closeTokenKind, /*diagnosticMessage*/ undefined, shouldAdvance)) {
const lastError = lastOrUndefined(parseDiagnostics);
if (lastError && lastError.code === Diagnostics._0_expected.code) {
const openTokenStr = tokenToString(openTokenKind)!;
const closeTokenStr = tokenToString(closeTokenKind)!;
addRelatedInfo(
lastError,
createFileDiagnostic(sourceFile, openTokenPos, openTokenStr.length,
Diagnostics.The_parser_expected_to_find_a_0_to_match_the_1_token_here, closeTokenStr, openTokenStr)
);
}
return false;
}
return true;
}

function parseExpectedJSDoc(kind: JSDocSyntaxKind) {
if (token() === kind) {
nextTokenJSDoc();
Expand Down Expand Up @@ -2988,9 +3005,10 @@ namespace ts {

function parseObjectTypeMembers(): NodeArray<TypeElement> {
let members: NodeArray<TypeElement>;
const openBracePosition = scanner.getTokenPos();
if (parseExpected(SyntaxKind.OpenBraceToken)) {
members = parseList(ParsingContext.TypeMembers, parseTypeMember);
parseExpected(SyntaxKind.CloseBraceToken);
parseExpectedCloseToken(SyntaxKind.CloseBraceToken, SyntaxKind.OpenBraceToken, openBracePosition);
}
else {
members = createMissingList<TypeElement>();
Expand Down Expand Up @@ -4654,6 +4672,7 @@ namespace ts {
function parseJsxExpression(inExpressionContext: boolean): JsxExpression | undefined {
const node = <JsxExpression>createNode(SyntaxKind.JsxExpression);

const openBracePosition = scanner.getTokenPos();
if (!parseExpected(SyntaxKind.OpenBraceToken)) {
return undefined;
}
Expand All @@ -4666,10 +4685,10 @@ namespace ts {
node.expression = parseExpression();
}
if (inExpressionContext) {
parseExpected(SyntaxKind.CloseBraceToken);
parseExpectedCloseToken(SyntaxKind.CloseBraceToken, SyntaxKind.OpenBraceToken, openBracePosition);
}
else {
if (parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*shouldAdvance*/ false)) {
if (parseExpectedCloseToken(SyntaxKind.CloseBraceToken, SyntaxKind.OpenBraceToken, openBracePosition, /*shouldAdvance*/ false)) {
scanJsxText();
}
}
Expand Down Expand Up @@ -5121,15 +5140,7 @@ namespace ts {
}

node.properties = parseDelimitedList(ParsingContext.ObjectLiteralMembers, parseObjectLiteralElement, /*considerSemicolonAsDelimiter*/ true);
if (!parseExpected(SyntaxKind.CloseBraceToken)) {
const lastError = lastOrUndefined(parseDiagnostics);
if (lastError && lastError.code === Diagnostics._0_expected.code) {
addRelatedInfo(
lastError,
createFileDiagnostic(sourceFile, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_to_match_the_token_here)
);
}
}
parseExpectedCloseToken(SyntaxKind.CloseBraceToken, SyntaxKind.OpenBraceToken, openBracePosition);
return finishNode(node);
}

Expand Down Expand Up @@ -5214,15 +5225,7 @@ namespace ts {
}

node.statements = parseList(ParsingContext.BlockStatements, parseStatement);
if (!parseExpected(SyntaxKind.CloseBraceToken)) {
const lastError = lastOrUndefined(parseDiagnostics);
if (lastError && lastError.code === Diagnostics._0_expected.code) {
addRelatedInfo(
lastError,
createFileDiagnostic(sourceFile, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_to_match_the_token_here)
);
}
}
parseExpectedCloseToken(SyntaxKind.CloseBraceToken, SyntaxKind.OpenBraceToken, openBracePosition);
}
else {
node.statements = createMissingList<Statement>();
Expand Down Expand Up @@ -6262,11 +6265,12 @@ namespace ts {
node.typeParameters = parseTypeParameters();
node.heritageClauses = parseHeritageClauses();

const openBracePosition = scanner.getTokenPos();
if (parseExpected(SyntaxKind.OpenBraceToken)) {
// ClassTail[Yield,Await] : (Modified) See 14.5
// ClassHeritage[?Yield,?Await]opt { ClassBody[?Yield,?Await]opt }
node.members = parseClassMembers();
parseExpected(SyntaxKind.CloseBraceToken);
parseExpectedCloseToken(SyntaxKind.CloseBraceToken, SyntaxKind.OpenBraceToken, openBracePosition);
}
else {
node.members = createMissingList<ClassElement>();
Expand Down Expand Up @@ -6367,9 +6371,10 @@ namespace ts {
node.kind = SyntaxKind.EnumDeclaration;
parseExpected(SyntaxKind.EnumKeyword);
node.name = parseIdentifier();
const openBracePosition = scanner.getTokenPos();
if (parseExpected(SyntaxKind.OpenBraceToken)) {
node.members = doOutsideOfYieldAndAwaitContext(() => parseDelimitedList(ParsingContext.EnumMembers, parseEnumMember));
parseExpected(SyntaxKind.CloseBraceToken);
parseExpectedCloseToken(SyntaxKind.CloseBraceToken, SyntaxKind.OpenBraceToken, openBracePosition);
}
else {
node.members = createMissingList<EnumMember>();
Expand All @@ -6379,9 +6384,10 @@ namespace ts {

function parseModuleBlock(): ModuleBlock {
const node = <ModuleBlock>createNode(SyntaxKind.ModuleBlock);
const openBracePosition = scanner.getTokenPos();
if (parseExpected(SyntaxKind.OpenBraceToken)) {
node.statements = parseList(ParsingContext.BlockStatements, parseStatement);
parseExpected(SyntaxKind.CloseBraceToken);
parseExpectedCloseToken(SyntaxKind.CloseBraceToken, SyntaxKind.OpenBraceToken, openBracePosition);
}
else {
node.statements = createMissingList<Statement>();
Expand Down
Expand Up @@ -10,6 +10,7 @@ tests/cases/compiler/classMemberWithMissingIdentifier.ts(3,1): error TS1128: Dec
!!! error TS1146: Declaration expected.
~
!!! error TS1005: ';' expected.
!!! related TS1007 tests/cases/compiler/classMemberWithMissingIdentifier.ts:1:9: The parser expected to find a '}' to match the '{' token here.
}
~
!!! error TS1128: Declaration or statement expected.
Expand Up @@ -14,6 +14,7 @@ tests/cases/compiler/classMemberWithMissingIdentifier2.ts(3,1): error TS1128: De
!!! error TS1146: Declaration expected.
~
!!! error TS1005: ';' expected.
!!! related TS1007 tests/cases/compiler/classMemberWithMissingIdentifier2.ts:1:9: The parser expected to find a '}' to match the '{' token here.
~
!!! error TS1005: ',' expected.
~~~~~~
Expand Down
Expand Up @@ -142,6 +142,7 @@ tests/cases/compiler/constructorWithIncompleteTypeAnnotation.ts(261,1): error TS
if (retValue != 0) {
~~
!!! error TS1005: ',' expected.
!!! related TS1007 tests/cases/compiler/constructorWithIncompleteTypeAnnotation.ts:15:26: The parser expected to find a '}' to match the '{' token here.
~
!!! error TS1005: ';' expected.

Expand Down
Expand Up @@ -50,6 +50,7 @@ tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassS
!!! error TS2304: Cannot find name 'super'.
~
!!! error TS1005: ';' expected.
!!! related TS1007 tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts:7:28: The parser expected to find a '}' to match the '{' token here.
~
!!! error TS1109: Expression expected.
b() {
Expand Down
Expand Up @@ -17,4 +17,5 @@ tests/cases/compiler/errorRecoveryWithDotFollowedByNamespaceKeyword.ts(9,2): err

!!! error TS1005: '}' expected.
!!! related TS1007 tests/cases/compiler/errorRecoveryWithDotFollowedByNamespaceKeyword.ts:3:19: The parser expected to find a '}' to match the '{' token here.
!!! related TS1007 tests/cases/compiler/errorRecoveryWithDotFollowedByNamespaceKeyword.ts:2:20: The parser expected to find a '}' to match the '{' token here.
!!! related TS1007 tests/cases/compiler/errorRecoveryWithDotFollowedByNamespaceKeyword.ts:2:20: The parser expected to find a '}' to match the '{' token here.
!!! related TS1007 tests/cases/compiler/errorRecoveryWithDotFollowedByNamespaceKeyword.ts:1:13: The parser expected to find a '}' to match the '{' token here.
15 changes: 14 additions & 1 deletion tests/baselines/reference/jsxAndTypeAssertion.errors.txt
Expand Up @@ -21,9 +21,11 @@ tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(18,69): error TS1381: Unexpe
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(18,76): error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: ':' expected.
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: '</' expected.
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: '</' expected.
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: '</' expected.


==== tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx (23 errors) ====
==== tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx (25 errors) ====
declare var createElement: any;

class foo {}
Expand All @@ -36,6 +38,7 @@ tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: '</' ex
!!! error TS2582: Cannot find name 'test'. Do you need to install type definitions for a test runner? Try `npm i @types/jest` or `npm i @types/mocha`.
~
!!! error TS1005: '}' expected.
!!! related TS1007 tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx:6:11: The parser expected to find a '}' to match the '{' token here.
~
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?

Expand All @@ -50,6 +53,7 @@ tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: '</' ex
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
~
!!! error TS1005: '}' expected.
!!! related TS1007 tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx:10:16: The parser expected to find a '}' to match the '{' token here.

x = <foo test={<foo>{}}>hello</foo>;
~
Expand All @@ -58,6 +62,7 @@ tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: '</' ex
!!! error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
~
!!! error TS1005: '}' expected.
!!! related TS1007 tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx:12:15: The parser expected to find a '}' to match the '{' token here.

x = <foo test={<foo>{}}>hello{<foo>{}}</foo>;
~~~
Expand All @@ -70,6 +75,7 @@ tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: '</' ex
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
~
!!! error TS1005: '}' expected.
!!! related TS1007 tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx:14:30: The parser expected to find a '}' to match the '{' token here.

x = <foo>x</foo>, x = <foo/>;

Expand All @@ -89,5 +95,12 @@ tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: '</' ex


!!! error TS1005: ':' expected.
!!! related TS1007 tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx:18:17: The parser expected to find a '}' to match the '{' token here.

!!! error TS1005: '</' expected.
!!! related TS1007 tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx:14:15: The parser expected to find a '}' to match the '{' token here.

!!! error TS1005: '</' expected.
!!! related TS1007 tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx:18:6: The parser expected to find a '}' to match the '{' token here.

!!! error TS1005: '</' expected.
Expand Up @@ -244,6 +244,7 @@ tests/cases/conformance/jsx/9.tsx(1,16): error TS1109: Expression expected.
<a>{"str";}</a>;
~
!!! error TS1005: '}' expected.
!!! related TS1007 tests/cases/conformance/jsx/20.tsx:1:4: The parser expected to find a '}' to match the '{' token here.
~
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
==== tests/cases/conformance/jsx/21.tsx (1 errors) ====
Expand Down
@@ -0,0 +1,14 @@
tests/cases/compiler/missingCloseBraceInClassDeclaration.ts(7,1): error TS1005: '}' expected.


==== tests/cases/compiler/missingCloseBraceInClassDeclaration.ts (1 errors) ====
class TestCls {
prop = 0;
method() {
return this.prop;
}



!!! error TS1005: '}' expected.
!!! related TS1007 tests/cases/compiler/missingCloseBraceInClassDeclaration.ts:1:15: The parser expected to find a '}' to match the '{' token here.
19 changes: 19 additions & 0 deletions tests/baselines/reference/missingCloseBraceInClassDeclaration.js
@@ -0,0 +1,19 @@
//// [missingCloseBraceInClassDeclaration.ts]
class TestCls {
prop = 0;
method() {
return this.prop;
}



//// [missingCloseBraceInClassDeclaration.js]
var TestCls = /** @class */ (function () {
function TestCls() {
this.prop = 0;
}
TestCls.prototype.method = function () {
return this.prop;
};
return TestCls;
}());
@@ -0,0 +1,17 @@
=== tests/cases/compiler/missingCloseBraceInClassDeclaration.ts ===
class TestCls {
>TestCls : Symbol(TestCls, Decl(missingCloseBraceInClassDeclaration.ts, 0, 0))

prop = 0;
>prop : Symbol(TestCls.prop, Decl(missingCloseBraceInClassDeclaration.ts, 0, 15))

method() {
>method : Symbol(TestCls.method, Decl(missingCloseBraceInClassDeclaration.ts, 1, 11))

return this.prop;
>this.prop : Symbol(TestCls.prop, Decl(missingCloseBraceInClassDeclaration.ts, 0, 15))
>this : Symbol(TestCls, Decl(missingCloseBraceInClassDeclaration.ts, 0, 0))
>prop : Symbol(TestCls.prop, Decl(missingCloseBraceInClassDeclaration.ts, 0, 15))
}


@@ -0,0 +1,18 @@
=== tests/cases/compiler/missingCloseBraceInClassDeclaration.ts ===
class TestCls {
>TestCls : TestCls

prop = 0;
>prop : number
>0 : 0

method() {
>method : () => number

return this.prop;
>this.prop : number
>this : this
>prop : number
}


13 changes: 13 additions & 0 deletions tests/baselines/reference/missingCloseBraceInEnum.errors.txt
@@ -0,0 +1,13 @@
tests/cases/compiler/missingCloseBraceInEnum.ts(6,1): error TS1005: '}' expected.


==== tests/cases/compiler/missingCloseBraceInEnum.ts (1 errors) ====
enum Colors {
Red,
Green,
Blue,



!!! error TS1005: '}' expected.
!!! related TS1007 tests/cases/compiler/missingCloseBraceInEnum.ts:1:13: The parser expected to find a '}' to match the '{' token here.
15 changes: 15 additions & 0 deletions tests/baselines/reference/missingCloseBraceInEnum.js
@@ -0,0 +1,15 @@
//// [missingCloseBraceInEnum.ts]
enum Colors {
Red,
Green,
Blue,



//// [missingCloseBraceInEnum.js]
var Colors;
(function (Colors) {
Colors[Colors["Red"] = 0] = "Red";
Colors[Colors["Green"] = 1] = "Green";
Colors[Colors["Blue"] = 2] = "Blue";
})(Colors || (Colors = {}));
14 changes: 14 additions & 0 deletions tests/baselines/reference/missingCloseBraceInEnum.symbols
@@ -0,0 +1,14 @@
=== tests/cases/compiler/missingCloseBraceInEnum.ts ===
enum Colors {
>Colors : Symbol(Colors, Decl(missingCloseBraceInEnum.ts, 0, 0))

Red,
>Red : Symbol(Colors.Red, Decl(missingCloseBraceInEnum.ts, 0, 13))

Green,
>Green : Symbol(Colors.Green, Decl(missingCloseBraceInEnum.ts, 1, 6))

Blue,
>Blue : Symbol(Colors.Blue, Decl(missingCloseBraceInEnum.ts, 2, 8))


14 changes: 14 additions & 0 deletions tests/baselines/reference/missingCloseBraceInEnum.types
@@ -0,0 +1,14 @@
=== tests/cases/compiler/missingCloseBraceInEnum.ts ===
enum Colors {
>Colors : Colors

Red,
>Red : Colors.Red

Green,
>Green : Colors.Green

Blue,
>Blue : Colors.Blue


0 comments on commit f2d57da

Please sign in to comment.