Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Token hints for missing closing braces: classes, enums, jsx, modules, types #37497

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -5143,15 +5162,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 @@ -5239,15 +5250,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 @@ -6287,11 +6290,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 @@ -6392,9 +6396,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 @@ -6404,9 +6409,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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it looks like the heuristic on this is actually...not working correctly given

// Don't report another error if it would just be at the same position as the last error.

~
!!! 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