From d9ddebedb1187e74c94502e1d7f06988e8115c2a Mon Sep 17 00:00:00 2001 From: jbondc Date: Sat, 19 Sep 2015 20:04:58 -0400 Subject: [PATCH 1/7] Improve parsing of type identifier for better error message. Add a 'NodeFlags.Missing' to easily check for missing node. --- src/compiler/diagnosticMessages.json | 8 +++ src/compiler/parser.ts | 71 +++++++++++++++---- src/compiler/types.ts | 1 + .../errors/errorParserTypeIdentifier.ts | 20 ++++++ 4 files changed, 88 insertions(+), 12 deletions(-) create mode 100644 tests/cases/compiler/errors/errorParserTypeIdentifier.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 9ea9514b4af73..5b0bc7b09f315 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2477,5 +2477,13 @@ "A type assertion expression is not allowed in the left-hand side of an exponentiation expression. Consider enclosing the expression in parentheses.": { "category": "Error", "code": 17007 + }, + "Invalid type. To avoid ambiguity, add parentheses: '{0}'": { + "category": "Error", + "code": 17008 + }, + "Identifier expected, reserved word '{0}' not allowed here.": { + "category": "Error", + "code": 17009 } } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 3e93e31340d40..5116b7fb53d22 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -804,7 +804,7 @@ namespace ts { function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any): void { // Don't report another error if it would just be at the same position as the last error. let lastError = lastOrUndefined(parseDiagnostics); - if (!lastError || start !== lastError.start) { + if ((!lastError || start !== lastError.start) && (contextFlags & ParserContextFlags.NoError) === 0) { parseDiagnostics.push(createFileDiagnostic(sourceFile, start, length, message, arg0)); } @@ -1026,6 +1026,7 @@ namespace ts { } let result = createNode(kind, scanner.getStartPos()); + result.flags |= NodeFlags.Missing; (result).text = ""; return finishNode(result); } @@ -1814,7 +1815,14 @@ namespace ts { return entity; } - function parseRightSideOfDot(allowIdentifierNames: boolean): Identifier { + function parseEntityNameNoError(allowReservedWords: boolean): EntityName { + contextFlags |= ParserContextFlags.NoError; + let entity = parseEntityName(allowReservedWords); + contextFlags &= ~ParserContextFlags.NoError; + return entity; + } + + function parseRightSideOfDot(allowReservedWords: boolean): Identifier { // Technically a keyword is valid here as all identifiers and keywords are identifier names. // However, often we'll encounter this in error situations when the identifier or keyword // is actually starting another valid construct. @@ -1844,8 +1852,7 @@ namespace ts { return createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentToken*/ true, Diagnostics.Identifier_expected); } } - - return allowIdentifierNames ? parseIdentifierName() : parseIdentifier(); + return allowReservedWords ? parseIdentifierName() : parseIdentifier(); } function parseTemplateExpression(): TemplateExpression { @@ -1922,22 +1929,62 @@ namespace ts { // TYPES function parseTypeReferenceOrTypePredicate(): TypeReferenceNode | TypePredicateNode { - let typeName = parseEntityName(/*allowReservedWords*/ false, Diagnostics.Type_expected); + let typeName = parseEntityNameNoError(/*allowReservedWords*/ false); + if (typeName.kind === SyntaxKind.Identifier && token === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { nextToken(); - let node = createNode(SyntaxKind.TypePredicate, typeName.pos); - node.parameterName = typeName; - node.type = parseType(); - return finishNode(node); + let pnode = createNode(SyntaxKind.TypePredicate, typeName.pos); + pnode.parameterName = typeName; + pnode.type = parseType(); + return finishNode(pnode); } + let node = createNode(SyntaxKind.TypeReference, typeName.pos); - node.typeName = typeName; - if (!scanner.hasPrecedingLineBreak() && token === SyntaxKind.LessThanToken) { - node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); + if (typeName.flags & NodeFlags.Missing) { + errorOnInvalidTypeReference(typeName); + } else { + node.typeName = typeName; + if (token === SyntaxKind.LessThanToken && !scanner.hasPrecedingLineBreak()) { + node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); + } + } + if ((typeName).right && ((typeName).right.flags & NodeFlags.Missing)) { + errorOnQualifiedTypeIdentifier() } return finishNode(node); } + function errorOnInvalidTypeReference(node: Identifier | QualifiedName): void { + let needsParentheses = true; + if (isStartOfFunctionType()) { + parseFunctionOrConstructorType(SyntaxKind.FunctionType); + } + else if (token === SyntaxKind.NewKeyword) { + parseFunctionOrConstructorType(SyntaxKind.ConstructorType); + } else { + needsParentheses = false; + } + if (needsParentheses) { + let hint = "(" + sourceFile.text.substring(node.pos, scanner.getStartPos()).trim() + ")"; + parseErrorAtPosition(node.pos, scanner.getStartPos() - node.pos, Diagnostics.Invalid_type_To_avoid_ambiguity_add_parentheses_Colon_0, hint); + } else { + parseErrorAtCurrentToken(Diagnostics.Type_expected); + } + } + + function errorOnQualifiedTypeIdentifier(): void { + if (scanner.isReservedWord()) { + parseErrorAtCurrentToken(Diagnostics.Identifier_expected_reserved_word_0_not_allowed_here, tokenToString(token)); + } + else { + if (scanner.hasPrecedingLineBreak()) { + parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected); + } else { + parseErrorAtCurrentToken(Diagnostics.Identifier_expected); + } + } + } + function parseTypeQuery(): TypeQueryNode { let node = createNode(SyntaxKind.TypeQuery); parseExpected(SyntaxKind.TypeOfKeyword); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8e0338696dde9..e299829cb4846 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -380,6 +380,7 @@ namespace ts { Namespace = 0x00020000, // Namespace declaration ExportContext = 0x00040000, // Export context (initialized by binding) ContainsThis = 0x00080000, // Interface contains references to "this" + Missing = 0x00100000, // Node which is invalid/missing Modifier = Export | Ambient | Public | Private | Protected | Static | Abstract | Default | Async, AccessibilityModifier = Public | Private | Protected, diff --git a/tests/cases/compiler/errors/errorParserTypeIdentifier.ts b/tests/cases/compiler/errors/errorParserTypeIdentifier.ts new file mode 100644 index 0000000000000..81628b5ee65f7 --- /dev/null +++ b/tests/cases/compiler/errors/errorParserTypeIdentifier.ts @@ -0,0 +1,20 @@ + +// Union or intersection ambiguity +function test(x: string | new() => void) { } + +let a: string | new () => void; +let b: string | (new () => void); +let c: string | new () => void /* not part of error */ +let d: string | new () => void | number; +let e: string | (...) => void | void; + +// Missing identifier or keyword +let f: TypeModule1. + +module TypeModule2 { +} + +let g: x.void; +let h = (a: ) => { +} +Foo(); From bcb3883a86dc1798e329330ecd63ae7dee9262de Mon Sep 17 00:00:00 2001 From: jbondc Date: Sat, 19 Sep 2015 20:05:54 -0400 Subject: [PATCH 2/7] Accept baselines. --- .../errorParserTypeIdentifier.errors.txt | 66 +++++++++++++++++++ .../reference/errorParserTypeIdentifier.js | 37 +++++++++++ .../parserSkippedTokens20.errors.txt | 6 +- .../parservoidInQualifiedName2.errors.txt | 4 +- 4 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/errorParserTypeIdentifier.errors.txt create mode 100644 tests/baselines/reference/errorParserTypeIdentifier.js diff --git a/tests/baselines/reference/errorParserTypeIdentifier.errors.txt b/tests/baselines/reference/errorParserTypeIdentifier.errors.txt new file mode 100644 index 0000000000000..9528b6e839eeb --- /dev/null +++ b/tests/baselines/reference/errorParserTypeIdentifier.errors.txt @@ -0,0 +1,66 @@ +tests/cases/compiler/errorParserTypeIdentifier.ts(3,26): error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new() => void)' +tests/cases/compiler/errorParserTypeIdentifier.ts(5,16): error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new () => void)' +tests/cases/compiler/errorParserTypeIdentifier.ts(7,16): error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new () => void)' +tests/cases/compiler/errorParserTypeIdentifier.ts(8,16): error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new () => void | number)' +tests/cases/compiler/errorParserTypeIdentifier.ts(9,16): error TS17006: Invalid type. To avoid ambiguity, add parentheses: '((...) => void | void)' +tests/cases/compiler/errorParserTypeIdentifier.ts(9,24): error TS1003: Identifier expected. +tests/cases/compiler/errorParserTypeIdentifier.ts(12,8): error TS2503: Cannot find namespace 'TypeModule1'. +tests/cases/compiler/errorParserTypeIdentifier.ts(12,20): error TS1003: Identifier expected. +tests/cases/compiler/errorParserTypeIdentifier.ts(17,8): error TS2503: Cannot find namespace 'x'. +tests/cases/compiler/errorParserTypeIdentifier.ts(17,10): error TS17007: Identifier expected, reserved word 'void' not allowed here. +tests/cases/compiler/errorParserTypeIdentifier.ts(17,14): error TS1109: Expression expected. +tests/cases/compiler/errorParserTypeIdentifier.ts(18,13): error TS1110: Type expected. +tests/cases/compiler/errorParserTypeIdentifier.ts(20,1): error TS2304: Cannot find name 'Foo'. +tests/cases/compiler/errorParserTypeIdentifier.ts(20,8): error TS1110: Type expected. + + +==== tests/cases/compiler/errorParserTypeIdentifier.ts (14 errors) ==== + + // Union or intersection ambiguity + function test(x: string | new() => void) { } + ~~~~~~~~~~~~~~ +!!! error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new() => void)' + + let a: string | new () => void; + ~~~~~~~~~~~~~~~ +!!! error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new () => void)' + let b: string | (new () => void); + let c: string | new () => void /* not part of error */ + ~~~~~~~~~~~~~~~ +!!! error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new () => void)' + let d: string | new () => void | number; + ~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new () => void | number)' + let e: string | (...) => void | void; + ~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS17006: Invalid type. To avoid ambiguity, add parentheses: '((...) => void | void)' + ~ +!!! error TS1003: Identifier expected. + + // Missing identifier or keyword + let f: TypeModule1. + ~~~~~~~~~~~ +!!! error TS2503: Cannot find namespace 'TypeModule1'. + +!!! error TS1003: Identifier expected. + + module TypeModule2 { + } + + let g: x.void; + ~ +!!! error TS2503: Cannot find namespace 'x'. + ~~~~ +!!! error TS17007: Identifier expected, reserved word 'void' not allowed here. + ~ +!!! error TS1109: Expression expected. + let h = (a: ) => { + ~ +!!! error TS1110: Type expected. + } + Foo(); + ~~~ +!!! error TS2304: Cannot find name 'Foo'. + ~ +!!! error TS1110: Type expected. + \ No newline at end of file diff --git a/tests/baselines/reference/errorParserTypeIdentifier.js b/tests/baselines/reference/errorParserTypeIdentifier.js new file mode 100644 index 0000000000000..dfb5e2a6e40cd --- /dev/null +++ b/tests/baselines/reference/errorParserTypeIdentifier.js @@ -0,0 +1,37 @@ +//// [errorParserTypeIdentifier.ts] + +// Union or intersection ambiguity +function test(x: string | new() => void) { } + +let a: string | new () => void; +let b: string | (new () => void); +let c: string | new () => void /* not part of error */ +let d: string | new () => void | number; +let e: string | (...) => void | void; + +// Missing identifier or keyword +let f: TypeModule1. + +module TypeModule2 { +} + +let g: x.void; +let h = (a: ) => { +} +Foo(); + + +//// [errorParserTypeIdentifier.js] +// Union or intersection ambiguity +function test(x) { } +var a; +var b; +var c; /* not part of error */ +var d; +var e; +// Missing identifier or keyword +var f; +var g = void ; +var h = function (a) { +}; +Foo(); diff --git a/tests/baselines/reference/parserSkippedTokens20.errors.txt b/tests/baselines/reference/parserSkippedTokens20.errors.txt index 4e66b51f48d17..5f40a9f23e384 100644 --- a/tests/baselines/reference/parserSkippedTokens20.errors.txt +++ b/tests/baselines/reference/parserSkippedTokens20.errors.txt @@ -1,5 +1,5 @@ tests/cases/conformance/parser/ecmascript5/SkippedTokens/parserSkippedTokens20.ts(1,8): error TS2304: Cannot find name 'X'. -tests/cases/conformance/parser/ecmascript5/SkippedTokens/parserSkippedTokens20.ts(1,12): error TS1127: Invalid character. +tests/cases/conformance/parser/ecmascript5/SkippedTokens/parserSkippedTokens20.ts(1,12): error TS1005: ',' expected. tests/cases/conformance/parser/ecmascript5/SkippedTokens/parserSkippedTokens20.ts(1,13): error TS1005: '>' expected. @@ -7,7 +7,7 @@ tests/cases/conformance/parser/ecmascript5/SkippedTokens/parserSkippedTokens20.t var v: X' expected. \ No newline at end of file diff --git a/tests/baselines/reference/parservoidInQualifiedName2.errors.txt b/tests/baselines/reference/parservoidInQualifiedName2.errors.txt index 7ca94a8989ad2..a7eb62f7448c7 100644 --- a/tests/baselines/reference/parservoidInQualifiedName2.errors.txt +++ b/tests/baselines/reference/parservoidInQualifiedName2.errors.txt @@ -1,5 +1,5 @@ tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts(1,9): error TS2503: Cannot find namespace 'x'. -tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts(1,11): error TS1003: Identifier expected. +tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts(1,11): error TS17007: Identifier expected, reserved word 'void' not allowed here. tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts(1,15): error TS1109: Expression expected. @@ -8,6 +8,6 @@ tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts(1,15): ~ !!! error TS2503: Cannot find namespace 'x'. ~~~~ -!!! error TS1003: Identifier expected. +!!! error TS17007: Identifier expected, reserved word 'void' not allowed here. ~ !!! error TS1109: Expression expected. \ No newline at end of file From ea971fa94a070b112ac04e302b9b9ffc7311d27c Mon Sep 17 00:00:00 2001 From: jbondc Date: Sun, 20 Sep 2015 11:30:04 -0400 Subject: [PATCH 3/7] Fix style. --- src/compiler/parser.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 5116b7fb53d22..158261aa9a4bf 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1942,14 +1942,15 @@ namespace ts { let node = createNode(SyntaxKind.TypeReference, typeName.pos); if (typeName.flags & NodeFlags.Missing) { errorOnInvalidTypeReference(typeName); - } else { + } + else { node.typeName = typeName; if (token === SyntaxKind.LessThanToken && !scanner.hasPrecedingLineBreak()) { node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); } } if ((typeName).right && ((typeName).right.flags & NodeFlags.Missing)) { - errorOnQualifiedTypeIdentifier() + errorOnInvalidTypeQualifiedName() } return finishNode(node); } @@ -1961,25 +1962,28 @@ namespace ts { } else if (token === SyntaxKind.NewKeyword) { parseFunctionOrConstructorType(SyntaxKind.ConstructorType); - } else { + } + else { needsParentheses = false; } if (needsParentheses) { let hint = "(" + sourceFile.text.substring(node.pos, scanner.getStartPos()).trim() + ")"; parseErrorAtPosition(node.pos, scanner.getStartPos() - node.pos, Diagnostics.Invalid_type_To_avoid_ambiguity_add_parentheses_Colon_0, hint); - } else { + } + else { parseErrorAtCurrentToken(Diagnostics.Type_expected); } } - function errorOnQualifiedTypeIdentifier(): void { + function errorOnInvalidTypeQualifiedName(): void { if (scanner.isReservedWord()) { parseErrorAtCurrentToken(Diagnostics.Identifier_expected_reserved_word_0_not_allowed_here, tokenToString(token)); } else { if (scanner.hasPrecedingLineBreak()) { parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected); - } else { + } + else { parseErrorAtCurrentToken(Diagnostics.Identifier_expected); } } From 30ea46042c6e2a0f29a259f4010ef2356003fd07 Mon Sep 17 00:00:00 2001 From: jbondc Date: Fri, 23 Oct 2015 18:28:57 -0400 Subject: [PATCH 4/7] Refactor using 'contextual error'. --- src/compiler/parser.ts | 139 ++++++++++++++++++++++++----------------- 1 file changed, 83 insertions(+), 56 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 158261aa9a4bf..d00ad0527c90f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -13,6 +13,12 @@ namespace ts { return new (getNodeConstructor(kind))(pos, end); } + interface ContextualError { + node?: Node + callback?: (node?: Node) => void, + count: number + } + function visitNode(cbNode: (node: Node) => T, node: Node): T { if (node) { return cbNode(node); @@ -526,6 +532,9 @@ namespace ts { // attached to the EOF token. let parseErrorBeforeNextFinishedNode = false; + // For more detailed errors, a contextual error node & callback may be used + let contextualError: ContextualError = {count: 0}; + export function parseSourceFile(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor, setParentNodes?: boolean): SourceFile { initializeState(fileName, _sourceText, languageVersion, _syntaxCursor); @@ -804,7 +813,10 @@ namespace ts { function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any): void { // Don't report another error if it would just be at the same position as the last error. let lastError = lastOrUndefined(parseDiagnostics); - if ((!lastError || start !== lastError.start) && (contextFlags & ParserContextFlags.NoError) === 0) { + if (contextualError.callback) { + contextualError.count++; + } + else if (!lastError || start !== lastError.start) { parseDiagnostics.push(createFileDiagnostic(sourceFile, start, length, message, arg0)); } @@ -923,6 +935,51 @@ namespace ts { return token > SyntaxKind.LastReservedWord; } + module errors { + export function invalidTypeReferenceOrTypePredicate(typeName: Identifier | QualifiedName): void { + if (typeName.flags & NodeFlags.Missing) { + onInvalidTypeReference(typeName); + } + if ((typeName).right && ((typeName).right.flags & NodeFlags.Missing)) { + onInvalidTypeQualifiedName() + } + } + + function onInvalidTypeReference(node: Identifier | QualifiedName): void { + let needsParentheses = true; + if (isStartOfFunctionType()) { + parseFunctionOrConstructorType(SyntaxKind.FunctionType); + } + else if (token === SyntaxKind.NewKeyword) { + parseFunctionOrConstructorType(SyntaxKind.ConstructorType); + } + else { + needsParentheses = false; + } + if (needsParentheses) { + let hint = "(" + sourceFile.text.substring(node.pos, scanner.getStartPos()).trim() + ")"; + parseErrorAtPosition(node.pos, scanner.getStartPos() - node.pos, Diagnostics.Invalid_type_To_avoid_ambiguity_add_parentheses_Colon_0, hint); + } + else { + parseErrorAtCurrentToken(Diagnostics.Type_expected); + } + } + + function onInvalidTypeQualifiedName(): void { + if (scanner.isReservedWord()) { + parseErrorAtCurrentToken(Diagnostics.Identifier_expected_reserved_word_0_not_allowed_here, tokenToString(token)); + } + else { + if (scanner.hasPrecedingLineBreak()) { + parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected); + } + else { + parseErrorAtCurrentToken(Diagnostics.Identifier_expected); + } + } + } + } + function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage, shouldAdvance = true): boolean { if (token === kind) { if (shouldAdvance) { @@ -1013,7 +1070,21 @@ namespace ts { parseErrorBeforeNextFinishedNode = false; node.parserContextFlags |= ParserContextFlags.ThisNodeHasError; } + return node; + } + + function finishNodeContextualError(node: T, end ?: number): T { + finishNode(node, end); + // If contextual error count, try to use callback for more detailed errors + if (contextualError.count) { + if (contextualError.callback) { + let errorCallback = contextualError.callback; + contextualError.callback = undefined; + errorCallback(contextualError.node); + } + contextualError.count = 0; + } return node; } @@ -1815,13 +1886,6 @@ namespace ts { return entity; } - function parseEntityNameNoError(allowReservedWords: boolean): EntityName { - contextFlags |= ParserContextFlags.NoError; - let entity = parseEntityName(allowReservedWords); - contextFlags &= ~ParserContextFlags.NoError; - return entity; - } - function parseRightSideOfDot(allowReservedWords: boolean): Identifier { // Technically a keyword is valid here as all identifiers and keywords are identifier names. // However, often we'll encounter this in error situations when the identifier or keyword @@ -1929,64 +1993,27 @@ namespace ts { // TYPES function parseTypeReferenceOrTypePredicate(): TypeReferenceNode | TypePredicateNode { - let typeName = parseEntityNameNoError(/*allowReservedWords*/ false); + contextualError.callback = errors.invalidTypeReferenceOrTypePredicate + + let typeName = parseEntityName(/*allowReservedWords*/ false); + contextualError.node = typeName; if (typeName.kind === SyntaxKind.Identifier && token === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { nextToken(); - let pnode = createNode(SyntaxKind.TypePredicate, typeName.pos); - pnode.parameterName = typeName; - pnode.type = parseType(); - return finishNode(pnode); + let predicateNode = createNode(SyntaxKind.TypePredicate, typeName.pos); + predicateNode.parameterName = typeName; + predicateNode.type = parseType(); + return finishNode(predicateNode); } let node = createNode(SyntaxKind.TypeReference, typeName.pos); - if (typeName.flags & NodeFlags.Missing) { - errorOnInvalidTypeReference(typeName); - } - else { - node.typeName = typeName; + node.typeName = typeName; + if ((typeName.flags & NodeFlags.Missing) === 0) { if (token === SyntaxKind.LessThanToken && !scanner.hasPrecedingLineBreak()) { node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); } } - if ((typeName).right && ((typeName).right.flags & NodeFlags.Missing)) { - errorOnInvalidTypeQualifiedName() - } - return finishNode(node); - } - - function errorOnInvalidTypeReference(node: Identifier | QualifiedName): void { - let needsParentheses = true; - if (isStartOfFunctionType()) { - parseFunctionOrConstructorType(SyntaxKind.FunctionType); - } - else if (token === SyntaxKind.NewKeyword) { - parseFunctionOrConstructorType(SyntaxKind.ConstructorType); - } - else { - needsParentheses = false; - } - if (needsParentheses) { - let hint = "(" + sourceFile.text.substring(node.pos, scanner.getStartPos()).trim() + ")"; - parseErrorAtPosition(node.pos, scanner.getStartPos() - node.pos, Diagnostics.Invalid_type_To_avoid_ambiguity_add_parentheses_Colon_0, hint); - } - else { - parseErrorAtCurrentToken(Diagnostics.Type_expected); - } - } - - function errorOnInvalidTypeQualifiedName(): void { - if (scanner.isReservedWord()) { - parseErrorAtCurrentToken(Diagnostics.Identifier_expected_reserved_word_0_not_allowed_here, tokenToString(token)); - } - else { - if (scanner.hasPrecedingLineBreak()) { - parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected); - } - else { - parseErrorAtCurrentToken(Diagnostics.Identifier_expected); - } - } + return finishNodeContextualError(node); } function parseTypeQuery(): TypeQueryNode { From 72337f05db6ce954b07f79fa3ea48ad91c80caf5 Mon Sep 17 00:00:00 2001 From: jbondc Date: Mon, 26 Oct 2015 11:48:50 -0400 Subject: [PATCH 5/7] Fix error handling. Better error on bad type predicate. --- src/compiler/parser.ts | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index d00ad0527c90f..84b7a3513818e 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -14,9 +14,8 @@ namespace ts { } interface ContextualError { - node?: Node - callback?: (node?: Node) => void, - count: number + callback?: (node: Node) => void; + count: number; } function visitNode(cbNode: (node: Node) => T, node: Node): T { @@ -533,7 +532,7 @@ namespace ts { let parseErrorBeforeNextFinishedNode = false; // For more detailed errors, a contextual error node & callback may be used - let contextualError: ContextualError = {count: 0}; + let contextualError: ContextualError = { count: 0 }; export function parseSourceFile(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor, setParentNodes?: boolean): SourceFile { initializeState(fileName, _sourceText, languageVersion, _syntaxCursor); @@ -557,6 +556,7 @@ namespace ts { contextFlags = isJavaScript(fileName) ? ParserContextFlags.JavaScriptFile : ParserContextFlags.None; parseErrorBeforeNextFinishedNode = false; + contextualError = { count: 0 }; // Initialize and prime the scanner before parsing the source elements. scanner.setText(sourceText); @@ -878,6 +878,7 @@ namespace ts { // descent nature of our parser. However, we still store this here just so we can // assert that that invariant holds. let saveContextFlags = contextFlags; + let saveContextErrorCount = contextualError.count; // If we're only looking ahead, then tell the scanner to only lookahead as well. // Otherwise, if we're actually speculatively parsing, then tell the scanner to do the @@ -894,6 +895,7 @@ namespace ts { token = saveToken; parseDiagnostics.length = saveParseDiagnosticsLength; parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; + contextualError.count = saveContextErrorCount; } return result; @@ -936,12 +938,19 @@ namespace ts { } module errors { - export function invalidTypeReferenceOrTypePredicate(typeName: Identifier | QualifiedName): void { + export function invalidTypeReferenceOrTypePredicate(node: TypeReferenceNode | TypePredicateNode): void { + if (node.kind === SyntaxKind.TypePredicate) { + if ((node).type.parserContextFlags & ParserContextFlags.ThisNodeHasError) { + parseErrorAtPosition((node).type.pos, 0, Diagnostics.Type_expected); + } + return; + } + let typeName = (node).typeName; if (typeName.flags & NodeFlags.Missing) { onInvalidTypeReference(typeName); } if ((typeName).right && ((typeName).right.flags & NodeFlags.Missing)) { - onInvalidTypeQualifiedName() + onInvalidTypeQualifiedName(); } } @@ -1073,16 +1082,15 @@ namespace ts { return node; } - function finishNodeContextualError(node: T, end ?: number): T { + function finishNodeContextualError(node: T, end?: number): T { finishNode(node, end); - // If contextual error count, try to use callback for more detailed errors - if (contextualError.count) { - if (contextualError.callback) { - let errorCallback = contextualError.callback; - contextualError.callback = undefined; - errorCallback(contextualError.node); - } + let errorCallback = contextualError.count ? contextualError.callback : undefined; + contextualError.callback = undefined; + + // If contextual error, use callback for more detailed error(s) + if (errorCallback) { + errorCallback(node); contextualError.count = 0; } return node; @@ -1993,17 +2001,15 @@ namespace ts { // TYPES function parseTypeReferenceOrTypePredicate(): TypeReferenceNode | TypePredicateNode { - contextualError.callback = errors.invalidTypeReferenceOrTypePredicate + contextualError.callback = errors.invalidTypeReferenceOrTypePredicate; let typeName = parseEntityName(/*allowReservedWords*/ false); - contextualError.node = typeName; - if (typeName.kind === SyntaxKind.Identifier && token === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { nextToken(); let predicateNode = createNode(SyntaxKind.TypePredicate, typeName.pos); predicateNode.parameterName = typeName; predicateNode.type = parseType(); - return finishNode(predicateNode); + return finishNodeContextualError(predicateNode); } let node = createNode(SyntaxKind.TypeReference, typeName.pos); From 1868d43188f1d6a55c2fbcc2a491400628c874ab Mon Sep 17 00:00:00 2001 From: jbondc Date: Mon, 26 Oct 2015 11:50:01 -0400 Subject: [PATCH 6/7] Accept baselines. --- .../errorParserTypeIdentifier.errors.txt | 24 +++++++++---------- .../parserUnterminatedGeneric1.errors.txt | 4 ++-- .../parserUnterminatedGeneric2.errors.txt | 4 ++-- .../parservoidInQualifiedName2.errors.txt | 4 ++-- .../typeGuardFunctionErrors.errors.txt | 6 ++--- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/baselines/reference/errorParserTypeIdentifier.errors.txt b/tests/baselines/reference/errorParserTypeIdentifier.errors.txt index 9528b6e839eeb..abc17ed09ac73 100644 --- a/tests/baselines/reference/errorParserTypeIdentifier.errors.txt +++ b/tests/baselines/reference/errorParserTypeIdentifier.errors.txt @@ -1,13 +1,13 @@ -tests/cases/compiler/errorParserTypeIdentifier.ts(3,26): error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new() => void)' -tests/cases/compiler/errorParserTypeIdentifier.ts(5,16): error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new () => void)' -tests/cases/compiler/errorParserTypeIdentifier.ts(7,16): error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new () => void)' -tests/cases/compiler/errorParserTypeIdentifier.ts(8,16): error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new () => void | number)' -tests/cases/compiler/errorParserTypeIdentifier.ts(9,16): error TS17006: Invalid type. To avoid ambiguity, add parentheses: '((...) => void | void)' +tests/cases/compiler/errorParserTypeIdentifier.ts(3,26): error TS17008: Invalid type. To avoid ambiguity, add parentheses: '(new() => void)' +tests/cases/compiler/errorParserTypeIdentifier.ts(5,16): error TS17008: Invalid type. To avoid ambiguity, add parentheses: '(new () => void)' +tests/cases/compiler/errorParserTypeIdentifier.ts(7,16): error TS17008: Invalid type. To avoid ambiguity, add parentheses: '(new () => void)' +tests/cases/compiler/errorParserTypeIdentifier.ts(8,16): error TS17008: Invalid type. To avoid ambiguity, add parentheses: '(new () => void | number)' +tests/cases/compiler/errorParserTypeIdentifier.ts(9,16): error TS17008: Invalid type. To avoid ambiguity, add parentheses: '((...) => void | void)' tests/cases/compiler/errorParserTypeIdentifier.ts(9,24): error TS1003: Identifier expected. tests/cases/compiler/errorParserTypeIdentifier.ts(12,8): error TS2503: Cannot find namespace 'TypeModule1'. tests/cases/compiler/errorParserTypeIdentifier.ts(12,20): error TS1003: Identifier expected. tests/cases/compiler/errorParserTypeIdentifier.ts(17,8): error TS2503: Cannot find namespace 'x'. -tests/cases/compiler/errorParserTypeIdentifier.ts(17,10): error TS17007: Identifier expected, reserved word 'void' not allowed here. +tests/cases/compiler/errorParserTypeIdentifier.ts(17,10): error TS17009: Identifier expected, reserved word 'void' not allowed here. tests/cases/compiler/errorParserTypeIdentifier.ts(17,14): error TS1109: Expression expected. tests/cases/compiler/errorParserTypeIdentifier.ts(18,13): error TS1110: Type expected. tests/cases/compiler/errorParserTypeIdentifier.ts(20,1): error TS2304: Cannot find name 'Foo'. @@ -19,21 +19,21 @@ tests/cases/compiler/errorParserTypeIdentifier.ts(20,8): error TS1110: Type expe // Union or intersection ambiguity function test(x: string | new() => void) { } ~~~~~~~~~~~~~~ -!!! error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new() => void)' +!!! error TS17008: Invalid type. To avoid ambiguity, add parentheses: '(new() => void)' let a: string | new () => void; ~~~~~~~~~~~~~~~ -!!! error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new () => void)' +!!! error TS17008: Invalid type. To avoid ambiguity, add parentheses: '(new () => void)' let b: string | (new () => void); let c: string | new () => void /* not part of error */ ~~~~~~~~~~~~~~~ -!!! error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new () => void)' +!!! error TS17008: Invalid type. To avoid ambiguity, add parentheses: '(new () => void)' let d: string | new () => void | number; ~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS17006: Invalid type. To avoid ambiguity, add parentheses: '(new () => void | number)' +!!! error TS17008: Invalid type. To avoid ambiguity, add parentheses: '(new () => void | number)' let e: string | (...) => void | void; ~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS17006: Invalid type. To avoid ambiguity, add parentheses: '((...) => void | void)' +!!! error TS17008: Invalid type. To avoid ambiguity, add parentheses: '((...) => void | void)' ~ !!! error TS1003: Identifier expected. @@ -51,7 +51,7 @@ tests/cases/compiler/errorParserTypeIdentifier.ts(20,8): error TS1110: Type expe ~ !!! error TS2503: Cannot find namespace 'x'. ~~~~ -!!! error TS17007: Identifier expected, reserved word 'void' not allowed here. +!!! error TS17009: Identifier expected, reserved word 'void' not allowed here. ~ !!! error TS1109: Expression expected. let h = (a: ) => { diff --git a/tests/baselines/reference/parserUnterminatedGeneric1.errors.txt b/tests/baselines/reference/parserUnterminatedGeneric1.errors.txt index 53a98b2d7f9e1..beaf7eb01ca38 100644 --- a/tests/baselines/reference/parserUnterminatedGeneric1.errors.txt +++ b/tests/baselines/reference/parserUnterminatedGeneric1.errors.txt @@ -1,6 +1,6 @@ tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGeneric1.ts(2,23): error TS2304: Cannot find name 'IPromise'. tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGeneric1.ts(2,45): error TS2304: Cannot find name 'IPromise'. -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGeneric1.ts(2,54): error TS1005: '>' expected. +tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGeneric1.ts(2,54): error TS1005: '}' expected. ==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGeneric1.ts (3 errors) ==== @@ -11,4 +11,4 @@ tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGener ~~~~~~~~ !!! error TS2304: Cannot find name 'IPromise'. -!!! error TS1005: '>' expected. \ No newline at end of file +!!! error TS1005: '}' expected. \ No newline at end of file diff --git a/tests/baselines/reference/parserUnterminatedGeneric2.errors.txt b/tests/baselines/reference/parserUnterminatedGeneric2.errors.txt index a2be22d5ee0c6..a7a08f97a6d99 100644 --- a/tests/baselines/reference/parserUnterminatedGeneric2.errors.txt +++ b/tests/baselines/reference/parserUnterminatedGeneric2.errors.txt @@ -12,7 +12,7 @@ tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGener tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGeneric2.ts(4,43): error TS2304: Cannot find name 'any'. tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGeneric2.ts(8,23): error TS2304: Cannot find name 'IPromise'. tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGeneric2.ts(8,45): error TS2304: Cannot find name 'IPromise'. -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGeneric2.ts(8,54): error TS1005: '>' expected. +tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGeneric2.ts(8,54): error TS1005: '}' expected. ==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGeneric2.ts (15 errors) ==== @@ -53,4 +53,4 @@ tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGener ~~~~~~~~ !!! error TS2304: Cannot find name 'IPromise'. -!!! error TS1005: '>' expected. \ No newline at end of file +!!! error TS1005: '}' expected. \ No newline at end of file diff --git a/tests/baselines/reference/parservoidInQualifiedName2.errors.txt b/tests/baselines/reference/parservoidInQualifiedName2.errors.txt index a7eb62f7448c7..04d707b1ccf0d 100644 --- a/tests/baselines/reference/parservoidInQualifiedName2.errors.txt +++ b/tests/baselines/reference/parservoidInQualifiedName2.errors.txt @@ -1,5 +1,5 @@ tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts(1,9): error TS2503: Cannot find namespace 'x'. -tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts(1,11): error TS17007: Identifier expected, reserved word 'void' not allowed here. +tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts(1,11): error TS17009: Identifier expected, reserved word 'void' not allowed here. tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts(1,15): error TS1109: Expression expected. @@ -8,6 +8,6 @@ tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts(1,15): ~ !!! error TS2503: Cannot find namespace 'x'. ~~~~ -!!! error TS17007: Identifier expected, reserved word 'void' not allowed here. +!!! error TS17009: Identifier expected, reserved word 'void' not allowed here. ~ !!! error TS1109: Expression expected. \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardFunctionErrors.errors.txt b/tests/baselines/reference/typeGuardFunctionErrors.errors.txt index b178b02c6b457..204611e61ad81 100644 --- a/tests/baselines/reference/typeGuardFunctionErrors.errors.txt +++ b/tests/baselines/reference/typeGuardFunctionErrors.errors.txt @@ -2,7 +2,7 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(15,12) tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(22,33): error TS2304: Cannot find name 'x'. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(26,33): error TS1225: Cannot find parameter 'x'. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(30,10): error TS2391: Function implementation is missing or not immediately following the declaration. -tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(31,5): error TS1131: Property or signature expected. +tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(30,48): error TS1110: Type expected. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(32,1): error TS1128: Declaration or statement expected. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(34,38): error TS1225: Cannot find parameter 'x'. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(38,51): error TS2322: Type 'B' is not assignable to type 'A'. @@ -76,9 +76,9 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(137,39 function hasMissingTypeInTypeGuardType(x): x is { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2391: Function implementation is missing or not immediately following the declaration. + +!!! error TS1110: Type expected. return true; - ~~~~~~ -!!! error TS1131: Property or signature expected. } ~ !!! error TS1128: Declaration or statement expected. From 57e0b1c7612c570655097e8a6268112160c1594d Mon Sep 17 00:00:00 2001 From: jbondc Date: Mon, 26 Oct 2015 12:22:07 -0400 Subject: [PATCH 7/7] Make lint happy. --- src/compiler/parser.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 84b7a3513818e..264ba36c9bd96 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -937,8 +937,8 @@ namespace ts { return token > SyntaxKind.LastReservedWord; } - module errors { - export function invalidTypeReferenceOrTypePredicate(node: TypeReferenceNode | TypePredicateNode): void { + // ERRORS + function invalidTypeReferenceOrTypePredicate(node: TypeReferenceNode | TypePredicateNode): void { if (node.kind === SyntaxKind.TypePredicate) { if ((node).type.parserContextFlags & ParserContextFlags.ThisNodeHasError) { parseErrorAtPosition((node).type.pos, 0, Diagnostics.Type_expected); @@ -987,7 +987,6 @@ namespace ts { } } } - } function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage, shouldAdvance = true): boolean { if (token === kind) { @@ -2001,7 +2000,7 @@ namespace ts { // TYPES function parseTypeReferenceOrTypePredicate(): TypeReferenceNode | TypePredicateNode { - contextualError.callback = errors.invalidTypeReferenceOrTypePredicate; + contextualError.callback = invalidTypeReferenceOrTypePredicate; let typeName = parseEntityName(/*allowReservedWords*/ false); if (typeName.kind === SyntaxKind.Identifier && token === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) {