diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7d7c9cb89d6..7b1fa6203a6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18454,8 +18454,9 @@ namespace ts { function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) { checkGrammarTypeArguments(node, node.typeArguments); - if (node.kind === SyntaxKind.TypeReference && node.typeName.jsdocDot && !isInJavaScriptFile(node) && !findAncestor(node, n => n.kind === SyntaxKind.JSDocTypeExpression)) { - grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + if (node.kind === SyntaxKind.TypeReference && node.typeName.jsdocDotPos !== undefined && !isInJavaScriptFile(node) && !isInJSDoc(node)) { + grammarErrorAtPos(getSourceFileOfNode(node), node.typeName.jsdocDotPos, 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + } const type = getTypeFromTypeReference(node); if (type !== unknownType) { @@ -22046,7 +22047,7 @@ namespace ts { case SyntaxKind.JSDocNullableType: case SyntaxKind.JSDocAllType: case SyntaxKind.JSDocUnknownType: - if (!isInJavaScriptFile(node) && !findAncestor(node, n => n.kind === SyntaxKind.JSDocTypeExpression)) { + if (!isInJavaScriptFile(node) && !isInJSDoc(node)) { grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); } return; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 1ecd654d86a..3e850245e7f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1925,10 +1925,11 @@ namespace ts { // The allowReservedWords parameter controls whether reserved words are permitted after the first dot function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName { let entity: EntityName = allowReservedWords ? parseIdentifierName() : parseIdentifier(diagnosticMessage); + let dotPos = scanner.getStartPos(); while (parseOptional(SyntaxKind.DotToken)) { if (token() === SyntaxKind.LessThanToken) { - // the entity is part of a JSDoc-style generic, so record this for later in case it's an error - entity.jsdocDot = true; + // the entity is part of a JSDoc-style generic, so record the trailing dot for later error reporting + entity.jsdocDotPos = dotPos; break; } const node: QualifiedName = createNode(SyntaxKind.QualifiedName, entity.pos); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c10cc299728..6ba1591d54f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -576,7 +576,7 @@ namespace ts { /*@internal*/ autoGenerateId?: number; // Ensures unique generated identifiers get unique names, but clones get the same name. isInJSDocNamespace?: boolean; // if the node is a member in a JSDoc namespace /*@internal*/ typeArguments?: NodeArray; // Only defined on synthesized nodes. Though not syntactically valid, used in emitting diagnostics. - /*@internal*/ jsdocDot?: boolean; // Identifier occurs in JSDoc-style generic: Id. + /*@internal*/ jsdocDotPos?: number; // Identifier occurs in JSDoc-style generic: Id. } // Transient identifier node (marked by id === -1) @@ -596,7 +596,7 @@ namespace ts { kind: SyntaxKind.QualifiedName; left: EntityName; right: Identifier; - /*@internal*/ jsdocDot?: boolean; // Identifier occurs in JSDoc-style generic: Id1.Id2. + /*@internal*/ jsdocDotPos?: number; // QualifiedName occurs in JSDoc-style generic: Id1.Id2. } export type EntityName = Identifier | QualifiedName; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 3a4ece29f75..5ae1d835e46 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1292,6 +1292,10 @@ namespace ts { return node && !!(node.flags & NodeFlags.JavaScriptFile); } + export function isInJSDoc(node: Node): boolean { + return node && !!(node.flags & NodeFlags.JSDoc); + } + /** * Returns true if the node is a CallExpression to the identifier 'require' with * exactly one argument (of the form 'require("name")'). @@ -3299,7 +3303,7 @@ namespace ts { } export function isJSDocTypeReference(node: TypeReferenceType): node is TypeReferenceNode { - return node.kind === SyntaxKind.TypeReference && !!findAncestor(node, n => n.kind === SyntaxKind.JSDocTypeExpression); + return node.flags & NodeFlags.JSDoc && node.kind === SyntaxKind.TypeReference; } /**