diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index a7e94da09d995..d6d253e1d77e7 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -282,17 +282,8 @@ namespace ts { const index = indexOf(functionType.parameters, node); return "arg" + index as __String; case SyntaxKind.JSDocTypedefTag: - const parentNode = node.parent && node.parent.parent; - let nameFromParentNode: __String; - if (parentNode && parentNode.kind === SyntaxKind.VariableStatement) { - if ((parentNode).declarationList.declarations.length > 0) { - const nameIdentifier = (parentNode).declarationList.declarations[0].name; - if (isIdentifier(nameIdentifier)) { - nameFromParentNode = nameIdentifier.escapedText; - } - } - } - return nameFromParentNode; + const name = getNameOfJSDocTypedef(node as JSDocTypedefTag); + return typeof name !== "undefined" ? name.escapedText : undefined; } } @@ -598,7 +589,7 @@ namespace ts { // Binding of JsDocComment should be done before the current block scope container changes. // because the scope of JsDocComment should not be affected by whether the current node is a // container or not. - if (node.jsDoc) { + if (hasJSDocNodes(node)) { if (isInJavaScriptFile(node)) { for (const j of node.jsDoc) { bind(j); @@ -1931,7 +1922,7 @@ namespace ts { } function bindJSDocTypedefTagIfAny(node: Node) { - if (!node.jsDoc) { + if (!hasJSDocNodes(node)) { return; } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b7f0894d2840e..91653c140c83e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19158,6 +19158,8 @@ namespace ts { switch (d.kind) { case SyntaxKind.InterfaceDeclaration: case SyntaxKind.TypeAliasDeclaration: + // A jsdoc typedef is, by definition, a type alias + case SyntaxKind.JSDocTypedefTag: return DeclarationSpaces.ExportType; case SyntaxKind.ModuleDeclaration: return isAmbientModule(d) || getModuleInstanceState(d) !== ModuleInstanceState.NonInstantiated @@ -19827,7 +19829,7 @@ namespace ts { } } else if (compilerOptions.noUnusedLocals) { - forEach(local.declarations, d => errorUnusedLocal(getNameOfDeclaration(d) || d, unescapeLeadingUnderscores(local.escapedName))); + forEach(local.declarations, d => errorUnusedLocal(d, unescapeLeadingUnderscores(local.escapedName))); } } }); @@ -19842,7 +19844,8 @@ namespace ts { return false; } - function errorUnusedLocal(node: Node, name: string) { + function errorUnusedLocal(declaration: Declaration, name: string) { + const node = getNameOfDeclaration(declaration) || declaration; if (isIdentifierThatStartsWithUnderScore(node)) { const declaration = getRootDeclaration(node.parent); if (declaration.kind === SyntaxKind.VariableDeclaration && isForInOrOfStatement(declaration.parent.parent)) { @@ -19909,7 +19912,7 @@ namespace ts { if (!local.isReferenced && !local.exportSymbol) { for (const declaration of local.declarations) { if (!isAmbientModule(declaration)) { - errorUnusedLocal(getNameOfDeclaration(declaration), unescapeLeadingUnderscores(local.escapedName)); + errorUnusedLocal(declaration, unescapeLeadingUnderscores(local.escapedName)); } } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index c76cd24f1b0b9..2c0416c071157 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2441,7 +2441,7 @@ namespace ts { } } - export function fail(message?: string, stackCrawlMark?: Function): void { + export function fail(message?: string, stackCrawlMark?: Function): never { debugger; const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure."); if ((Error).captureStackTrace) { @@ -2450,6 +2450,10 @@ namespace ts { throw e; } + export function assertNever(member: never, message?: string, stackCrawlMark?: Function): never { + return fail(message || `Illegal value: ${member}`, stackCrawlMark || assertNever); + } + export function getFunctionName(func: Function) { if (typeof func !== "function") { return ""; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 71c7d3aac4963..e30d5dbe1e400 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -729,7 +729,7 @@ namespace ts { } - function addJSDocComment(node: T): T { + function addJSDocComment(node: T): T { const comments = getJSDocCommentRanges(node, sourceFile.text); if (comments) { for (const comment of comments) { @@ -768,7 +768,7 @@ namespace ts { const saveParent = parent; parent = n; forEachChild(n, visitNode); - if (n.jsDoc) { + if (hasJSDocNodes(n)) { for (const jsDoc of n.jsDoc) { jsDoc.parent = n; parent = jsDoc; @@ -2158,7 +2158,7 @@ namespace ts { const result = createNode(SyntaxKind.JSDocFunctionType); nextToken(); fillSignature(SyntaxKind.ColonToken, SignatureFlags.Type | SignatureFlags.JSDoc, result); - return finishNode(result); + return addJSDocComment(finishNode(result)); } const node = createNode(SyntaxKind.TypeReference); node.typeName = parseIdentifierName(); @@ -2365,7 +2365,7 @@ namespace ts { parseSemicolon(); } - function parseSignatureMember(kind: SyntaxKind): CallSignatureDeclaration | ConstructSignatureDeclaration { + function parseSignatureMember(kind: SyntaxKind.CallSignature | SyntaxKind.ConstructSignature): CallSignatureDeclaration | ConstructSignatureDeclaration { const node = createNode(kind); if (kind === SyntaxKind.ConstructSignature) { parseExpected(SyntaxKind.NewKeyword); @@ -2445,7 +2445,7 @@ namespace ts { node.parameters = parseBracketedList(ParsingContext.Parameters, parseParameter, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); node.type = parseTypeAnnotation(); parseTypeMemberSemicolon(); - return finishNode(node); + return addJSDocComment(finishNode(node)); } function parsePropertyOrMethodSignature(fullStart: number, modifiers: NodeArray): PropertySignature | MethodSignature { @@ -2605,7 +2605,7 @@ namespace ts { parseExpected(SyntaxKind.NewKeyword); } fillSignature(SyntaxKind.EqualsGreaterThanToken, SignatureFlags.Type, node); - return finishNode(node); + return addJSDocComment(finishNode(node)); } function parseKeywordAndNoDot(): TypeNode | undefined { @@ -6182,7 +6182,7 @@ namespace ts { return jsDoc ? { jsDoc, diagnostics } : undefined; } - export function parseJSDocComment(parent: Node, start: number, length: number): JSDoc { + export function parseJSDocComment(parent: HasJSDoc, start: number, length: number): JSDoc { const saveToken = currentToken; const saveParseDiagnosticsLength = parseDiagnostics.length; const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; @@ -6997,7 +6997,7 @@ namespace ts { } forEachChild(node, visitNode, visitArray); - if (node.jsDoc) { + if (hasJSDocNodes(node)) { for (const jsDocComment of node.jsDoc) { forEachChild(jsDocComment, visitNode, visitArray); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 75e1cb05bc94e..55baf9763c2ed 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -516,8 +516,6 @@ namespace ts { parent?: Node; // Parent node (initialized by binding) /* @internal */ original?: Node; // The original node if this is an updated node. /* @internal */ startsOnNewLine?: boolean; // Whether a synthesized node should start on a new line (used by transforms). - /* @internal */ jsDoc?: JSDoc[]; // JSDoc that directly precedes this node - /* @internal */ jsDocCache?: ReadonlyArray; // Cache for getJSDocTags /* @internal */ symbol?: Symbol; // Symbol declared by node (initialized by binding) /* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding) /* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding) @@ -528,6 +526,44 @@ namespace ts { /* @internal */ contextualMapper?: TypeMapper; // Mapper for contextual type } + export interface JSDocContainer { + /* @internal */ jsDoc?: JSDoc[]; // JSDoc that directly precedes this node + /* @internal */ jsDocCache?: ReadonlyArray; // Cache for getJSDocTags + } + + export type HasJSDoc = + | ParameterDeclaration + | CallSignatureDeclaration + | ConstructSignatureDeclaration + | MethodSignature + | PropertySignature + | ArrowFunction + | ParenthesizedExpression + | SpreadAssignment + | ShorthandPropertyAssignment + | PropertyAssignment + | FunctionExpression + | LabeledStatement + | ExpressionStatement + | VariableStatement + | FunctionDeclaration + | ConstructorDeclaration + | MethodDeclaration + | PropertyDeclaration + | AccessorDeclaration + | ClassLikeDeclaration + | InterfaceDeclaration + | TypeAliasDeclaration + | EnumMember + | EnumDeclaration + | ModuleDeclaration + | ImportEqualsDeclaration + | IndexSignatureDeclaration + | FunctionTypeNode + | ConstructorTypeNode + | JSDocFunctionType + | EndOfFileToken; + /* @internal */ export type MutableNodeArray = NodeArray & T[]; @@ -546,7 +582,7 @@ namespace ts { export type EqualsToken = Token; export type AsteriskToken = Token; export type EqualsGreaterThanToken = Token; - export type EndOfFileToken = Token; + export type EndOfFileToken = Token & JSDocContainer; export type AtToken = Token; export type ReadonlyToken = Token; export type AwaitKeywordToken = Token; @@ -651,32 +687,34 @@ namespace ts { expression?: Expression; } - export interface SignatureDeclaration extends NamedDeclaration { - kind: SyntaxKind.CallSignature - | SyntaxKind.ConstructSignature - | SyntaxKind.MethodSignature - | SyntaxKind.IndexSignature - | SyntaxKind.FunctionType - | SyntaxKind.ConstructorType - | SyntaxKind.JSDocFunctionType - | SyntaxKind.FunctionDeclaration - | SyntaxKind.MethodDeclaration - | SyntaxKind.Constructor - | SyntaxKind.GetAccessor - | SyntaxKind.SetAccessor - | SyntaxKind.FunctionExpression - | SyntaxKind.ArrowFunction; + export interface SignatureDeclarationBase extends NamedDeclaration, JSDocContainer { + kind: SignatureDeclaration["kind"]; name?: PropertyName; typeParameters?: NodeArray; parameters: NodeArray; type: TypeNode | undefined; } - export interface CallSignatureDeclaration extends SignatureDeclaration, TypeElement { + export type SignatureDeclaration = + | CallSignatureDeclaration + | ConstructSignatureDeclaration + | MethodSignature + | IndexSignatureDeclaration + | FunctionTypeNode + | ConstructorTypeNode + | JSDocFunctionType + | FunctionDeclaration + | MethodDeclaration + | ConstructorDeclaration + | AccessorDeclaration + | FunctionExpression + | ArrowFunction; + + export interface CallSignatureDeclaration extends SignatureDeclarationBase, TypeElement { kind: SyntaxKind.CallSignature; } - export interface ConstructSignatureDeclaration extends SignatureDeclaration, TypeElement { + export interface ConstructSignatureDeclaration extends SignatureDeclarationBase, TypeElement { kind: SyntaxKind.ConstructSignature; } @@ -696,7 +734,7 @@ namespace ts { declarations: NodeArray; } - export interface ParameterDeclaration extends NamedDeclaration { + export interface ParameterDeclaration extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.Parameter; parent?: SignatureDeclaration; dotDotDotToken?: DotDotDotToken; // Present on rest parameter @@ -715,7 +753,7 @@ namespace ts { initializer?: Expression; // Optional initializer } - export interface PropertySignature extends TypeElement { + export interface PropertySignature extends TypeElement, JSDocContainer { kind: SyntaxKind.PropertySignature; name: PropertyName; // Declared property name questionToken?: QuestionToken; // Present on optional property @@ -723,7 +761,7 @@ namespace ts { initializer?: Expression; // Optional initializer } - export interface PropertyDeclaration extends ClassElement { + export interface PropertyDeclaration extends ClassElement, JSDocContainer { kind: SyntaxKind.PropertyDeclaration; questionToken?: QuestionToken; // Present for use with reporting a grammar error name: PropertyName; @@ -744,7 +782,7 @@ namespace ts { | AccessorDeclaration ; - export interface PropertyAssignment extends ObjectLiteralElement { + export interface PropertyAssignment extends ObjectLiteralElement, JSDocContainer { parent: ObjectLiteralExpression; kind: SyntaxKind.PropertyAssignment; name: PropertyName; @@ -752,7 +790,7 @@ namespace ts { initializer: Expression; } - export interface ShorthandPropertyAssignment extends ObjectLiteralElement { + export interface ShorthandPropertyAssignment extends ObjectLiteralElement, JSDocContainer { parent: ObjectLiteralExpression; kind: SyntaxKind.ShorthandPropertyAssignment; name: Identifier; @@ -763,7 +801,7 @@ namespace ts { objectAssignmentInitializer?: Expression; } - export interface SpreadAssignment extends ObjectLiteralElement { + export interface SpreadAssignment extends ObjectLiteralElement, JSDocContainer { parent: ObjectLiteralExpression; kind: SyntaxKind.SpreadAssignment; expression: Expression; @@ -816,7 +854,7 @@ namespace ts { * - MethodDeclaration * - AccessorDeclaration */ - export interface FunctionLikeDeclarationBase extends SignatureDeclaration { + export interface FunctionLikeDeclarationBase extends SignatureDeclarationBase { _functionLikeDeclarationBrand: any; asteriskToken?: AsteriskToken; @@ -847,7 +885,7 @@ namespace ts { body?: FunctionBody; } - export interface MethodSignature extends SignatureDeclaration, TypeElement { + export interface MethodSignature extends SignatureDeclarationBase, TypeElement { kind: SyntaxKind.MethodSignature; name: PropertyName; } @@ -861,13 +899,13 @@ namespace ts { // Because of this, it may be necessary to determine what sort of MethodDeclaration you have // at later stages of the compiler pipeline. In that case, you can either check the parent kind // of the method, or use helpers like isObjectLiteralMethodDeclaration - export interface MethodDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement { + export interface MethodDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { kind: SyntaxKind.MethodDeclaration; name: PropertyName; body?: FunctionBody; } - export interface ConstructorDeclaration extends FunctionLikeDeclarationBase, ClassElement { + export interface ConstructorDeclaration extends FunctionLikeDeclarationBase, ClassElement, JSDocContainer { kind: SyntaxKind.Constructor; parent?: ClassDeclaration | ClassExpression; body?: FunctionBody; @@ -881,7 +919,7 @@ namespace ts { // See the comment on MethodDeclaration for the intuition behind GetAccessorDeclaration being a // ClassElement and an ObjectLiteralElement. - export interface GetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement { + export interface GetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { kind: SyntaxKind.GetAccessor; parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; name: PropertyName; @@ -890,7 +928,7 @@ namespace ts { // See the comment on MethodDeclaration for the intuition behind SetAccessorDeclaration being a // ClassElement and an ObjectLiteralElement. - export interface SetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement { + export interface SetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { kind: SyntaxKind.SetAccessor; parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; name: PropertyName; @@ -899,7 +937,7 @@ namespace ts { export type AccessorDeclaration = GetAccessorDeclaration | SetAccessorDeclaration; - export interface IndexSignatureDeclaration extends SignatureDeclaration, ClassElement, TypeElement { + export interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement { kind: SyntaxKind.IndexSignature; parent?: ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeLiteralNode; } @@ -928,11 +966,11 @@ namespace ts { export type FunctionOrConstructorTypeNode = FunctionTypeNode | ConstructorTypeNode; - export interface FunctionTypeNode extends TypeNode, SignatureDeclaration { + export interface FunctionTypeNode extends TypeNode, SignatureDeclarationBase { kind: SyntaxKind.FunctionType; } - export interface ConstructorTypeNode extends TypeNode, SignatureDeclaration { + export interface ConstructorTypeNode extends TypeNode, SignatureDeclarationBase { kind: SyntaxKind.ConstructorType; } @@ -1355,13 +1393,13 @@ namespace ts { export type FunctionBody = Block; export type ConciseBody = FunctionBody | Expression; - export interface FunctionExpression extends PrimaryExpression, FunctionLikeDeclarationBase { + export interface FunctionExpression extends PrimaryExpression, FunctionLikeDeclarationBase, JSDocContainer { kind: SyntaxKind.FunctionExpression; name?: Identifier; body: FunctionBody; // Required, whereas the member inherited from FunctionDeclaration is optional } - export interface ArrowFunction extends Expression, FunctionLikeDeclarationBase { + export interface ArrowFunction extends Expression, FunctionLikeDeclarationBase, JSDocContainer { kind: SyntaxKind.ArrowFunction; equalsGreaterThanToken: EqualsGreaterThanToken; body: ConciseBody; @@ -1440,7 +1478,7 @@ namespace ts { literal: TemplateMiddle | TemplateTail; } - export interface ParenthesizedExpression extends PrimaryExpression { + export interface ParenthesizedExpression extends PrimaryExpression, JSDocContainer { kind: SyntaxKind.ParenthesizedExpression; expression: Expression; } @@ -1696,12 +1734,12 @@ namespace ts { /*@internal*/ multiLine?: boolean; } - export interface VariableStatement extends Statement { + export interface VariableStatement extends Statement, JSDocContainer { kind: SyntaxKind.VariableStatement; declarationList: VariableDeclarationList; } - export interface ExpressionStatement extends Statement { + export interface ExpressionStatement extends Statement, JSDocContainer { kind: SyntaxKind.ExpressionStatement; expression: Expression; } @@ -1807,7 +1845,7 @@ namespace ts { export type CaseOrDefaultClause = CaseClause | DefaultClause; - export interface LabeledStatement extends Statement { + export interface LabeledStatement extends Statement, JSDocContainer { kind: SyntaxKind.LabeledStatement; label: Identifier; statement: Statement; @@ -1834,7 +1872,7 @@ namespace ts { export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag; - export interface ClassLikeDeclaration extends NamedDeclaration { + export interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; name?: Identifier; typeParameters?: NodeArray; @@ -1842,15 +1880,17 @@ namespace ts { members: NodeArray; } - export interface ClassDeclaration extends ClassLikeDeclaration, DeclarationStatement { + export interface ClassDeclaration extends ClassLikeDeclarationBase, DeclarationStatement { kind: SyntaxKind.ClassDeclaration; name?: Identifier; } - export interface ClassExpression extends ClassLikeDeclaration, PrimaryExpression { + export interface ClassExpression extends ClassLikeDeclarationBase, PrimaryExpression { kind: SyntaxKind.ClassExpression; } + export type ClassLikeDeclaration = ClassDeclaration | ClassExpression; + export interface ClassElement extends NamedDeclaration { _classElementBrand: any; name?: PropertyName; @@ -1862,7 +1902,7 @@ namespace ts { questionToken?: QuestionToken; } - export interface InterfaceDeclaration extends DeclarationStatement { + export interface InterfaceDeclaration extends DeclarationStatement, JSDocContainer { kind: SyntaxKind.InterfaceDeclaration; name: Identifier; typeParameters?: NodeArray; @@ -1877,14 +1917,14 @@ namespace ts { types: NodeArray; } - export interface TypeAliasDeclaration extends DeclarationStatement { + export interface TypeAliasDeclaration extends DeclarationStatement, JSDocContainer { kind: SyntaxKind.TypeAliasDeclaration; name: Identifier; typeParameters?: NodeArray; type: TypeNode; } - export interface EnumMember extends NamedDeclaration { + export interface EnumMember extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.EnumMember; parent?: EnumDeclaration; // This does include ComputedPropertyName, but the parser will give an error @@ -1893,7 +1933,7 @@ namespace ts { initializer?: Expression; } - export interface EnumDeclaration extends DeclarationStatement { + export interface EnumDeclaration extends DeclarationStatement, JSDocContainer { kind: SyntaxKind.EnumDeclaration; name: Identifier; members: NodeArray; @@ -1903,7 +1943,7 @@ namespace ts { export type ModuleBody = NamespaceBody | JSDocNamespaceBody; - export interface ModuleDeclaration extends DeclarationStatement { + export interface ModuleDeclaration extends DeclarationStatement, JSDocContainer { kind: SyntaxKind.ModuleDeclaration; parent?: ModuleBody | SourceFile; name: ModuleName; @@ -1937,7 +1977,7 @@ namespace ts { * - import x = require("mod"); * - import x = M.x; */ - export interface ImportEqualsDeclaration extends DeclarationStatement { + export interface ImportEqualsDeclaration extends DeclarationStatement, JSDocContainer { kind: SyntaxKind.ImportEqualsDeclaration; parent?: SourceFile | ModuleBlock; name: Identifier; @@ -2090,7 +2130,7 @@ namespace ts { type: TypeNode; } - export interface JSDocFunctionType extends JSDocType, SignatureDeclaration { + export interface JSDocFunctionType extends JSDocType, SignatureDeclarationBase { kind: SyntaxKind.JSDocFunctionType; } @@ -2103,6 +2143,7 @@ namespace ts { export interface JSDoc extends Node { kind: SyntaxKind.JSDocComment; + parent?: HasJSDoc; tags: NodeArray | undefined; comment: string | undefined; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 725070c02bfbe..11c30a2d8abd2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -277,7 +277,7 @@ namespace ts { return skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.pos, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); } - if (includeJsDoc && node.jsDoc && node.jsDoc.length > 0) { + if (includeJsDoc && hasJSDocNodes(node)) { return getTokenPosOfNode(node.jsDoc[0]); } @@ -1510,10 +1510,10 @@ namespace ts { } export function getJSDocTags(node: Node): ReadonlyArray | undefined { - let tags = node.jsDocCache; + let tags = (node as JSDocContainer).jsDocCache; // If cache is 'null', that means we did the work of searching for JSDoc tags and came up with nothing. if (tags === undefined) { - node.jsDocCache = tags = flatMap(getJSDocCommentsAndTags(node), j => isJSDoc(j) ? j.tags : j); + (node as JSDocContainer).jsDocCache = tags = flatMap(getJSDocCommentsAndTags(node), j => isJSDoc(j) ? j.tags : j); } return tags; } @@ -1567,11 +1567,13 @@ namespace ts { result = addRange(result, getJSDocParameterTags(node as ParameterDeclaration)); } - if (isVariableLike(node) && node.initializer) { + if (isVariableLike(node) && node.initializer && hasJSDocNodes(node.initializer)) { result = addRange(result, node.initializer.jsDoc); } - result = addRange(result, node.jsDoc); + if (hasJSDocNodes(node)) { + result = addRange(result, node.jsDoc); + } } } @@ -3958,7 +3960,66 @@ namespace ts { return id; } - export function getNameOfDeclaration(declaration: Declaration): DeclarationName | undefined { + /** + * A JSDocTypedef tag has an _optional_ name field - if a name is not directly present, we should + * attempt to draw the name from the node the declaration is on (as that declaration is what its' symbol + * will be merged with) + */ + function nameForNamelessJSDocTypedef(declaration: JSDocTypedefTag): Identifier | undefined { + const hostNode = declaration.parent.parent; + if (!hostNode) { + return undefined; + } + // Covers classes, functions - any named declaration host node + if (isDeclaration(hostNode)) { + return getDeclarationIdentifier(hostNode); + } + // Covers remaining cases + switch (hostNode.kind) { + case SyntaxKind.VariableStatement: + if ((hostNode as VariableStatement).declarationList && + (hostNode as VariableStatement).declarationList.declarations[0]) { + return getDeclarationIdentifier((hostNode as VariableStatement).declarationList.declarations[0]); + } + return undefined; + case SyntaxKind.ExpressionStatement: + const expr = (hostNode as ExpressionStatement).expression; + switch (expr.kind) { + case SyntaxKind.PropertyAccessExpression: + return (expr as PropertyAccessExpression).name; + case SyntaxKind.ElementAccessExpression: + const arg = (expr as ElementAccessExpression).argumentExpression; + if (isIdentifier(arg)) { + return arg; + } + } + return undefined; + case SyntaxKind.EndOfFileToken: + return undefined; + case SyntaxKind.ParenthesizedExpression: { + return getDeclarationIdentifier(hostNode.expression); + } + case SyntaxKind.LabeledStatement: { + if (isDeclaration(hostNode.statement) || isExpression(hostNode.statement)) { + return getDeclarationIdentifier(hostNode.statement); + } + return undefined; + } + default: + Debug.assertNever(hostNode, "Found typedef tag attached to node which it should not be!"); + } + } + + function getDeclarationIdentifier(node: Declaration | Expression) { + const name = getNameOfDeclaration(node); + return isIdentifier(name) ? name : undefined; + } + + export function getNameOfJSDocTypedef(declaration: JSDocTypedefTag): Identifier | undefined { + return declaration.name || nameForNamelessJSDocTypedef(declaration as JSDocTypedefTag); + } + + export function getNameOfDeclaration(declaration: Declaration | Expression): DeclarationName | undefined { if (!declaration) { return undefined; } @@ -3977,6 +4038,9 @@ namespace ts { return undefined; } } + else if (declaration.kind === SyntaxKind.JSDocTypedefTag) { + return getNameOfJSDocTypedef(declaration as JSDocTypedefTag); + } else { return (declaration as NamedDeclaration).name; } @@ -5365,4 +5429,10 @@ namespace ts { export function isJSDocTag(node: Node): boolean { return node.kind >= SyntaxKind.FirstJSDocTagNode && node.kind <= SyntaxKind.LastJSDocTagNode; } + + /** True if has jsdoc nodes attached to it. */ + /* @internal */ + export function hasJSDocNodes(node: Node): node is HasJSDoc { + return !!(node as JSDocContainer).jsDoc && (node as JSDocContainer).jsDoc.length > 0; + } } diff --git a/src/services/classifier.ts b/src/services/classifier.ts index 4552d8bf98571..18eec066ea215 100644 --- a/src/services/classifier.ts +++ b/src/services/classifier.ts @@ -699,7 +699,8 @@ namespace ts { // specially. const docCommentAndDiagnostics = parseIsolatedJSDocComment(sourceFile.text, start, width); if (docCommentAndDiagnostics && docCommentAndDiagnostics.jsDoc) { - docCommentAndDiagnostics.jsDoc.parent = token; + // TODO: This should be predicated on `token["kind"]` being compatible with `HasJSDoc["kind"]` + docCommentAndDiagnostics.jsDoc.parent = token as HasJSDoc; classifyJSDocComment(docCommentAndDiagnostics.jsDoc); return; } diff --git a/src/services/completions.ts b/src/services/completions.ts index 97998ec724b73..15a20798508b8 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1739,7 +1739,7 @@ namespace ts.Completions { /** Get the corresponding JSDocTag node if the position is in a jsDoc comment */ function getJsDocTagAtPosition(node: Node, position: number): JSDocTag | undefined { - const { jsDoc } = getJsDocHavingNode(node); + const { jsDoc } = getJsDocHavingNode(node) as JSDocContainer; if (!jsDoc) return undefined; for (const { pos, end, tags } of jsDoc) { diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 35357b9331a9c..f7ed515a18fe3 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -263,13 +263,15 @@ namespace ts.NavigationBar { break; default: - forEach(node.jsDoc, jsDoc => { - forEach(jsDoc.tags, tag => { - if (tag.kind === SyntaxKind.JSDocTypedefTag) { - addLeafNode(tag); - } + if (hasJSDocNodes(node)) { + forEach(node.jsDoc, jsDoc => { + forEach(jsDoc.tags, tag => { + if (tag.kind === SyntaxKind.JSDocTypedefTag) { + addLeafNode(tag); + } + }); }); - }); + } forEachChild(node, addChildrenRecursively); } diff --git a/src/services/services.ts b/src/services/services.ts index e0e3bea79ff86..1feafdd55f5c4 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2111,7 +2111,7 @@ namespace ts { } forEachChild(node, walk); - if (node.jsDoc) { + if (hasJSDocNodes(node)) { for (const jsDoc of node.jsDoc) { forEachChild(jsDoc, walk); } diff --git a/tests/baselines/reference/jsdocTypedefNoCrash.js b/tests/baselines/reference/jsdocTypedefNoCrash.js new file mode 100644 index 0000000000000..803f6b1bb8547 --- /dev/null +++ b/tests/baselines/reference/jsdocTypedefNoCrash.js @@ -0,0 +1,13 @@ +//// [export.js] +/** + * @typedef {{ + * }} + */ +export const foo = 5; + +//// [export.js] +/** + * @typedef {{ + * }} + */ +export const foo = 5; diff --git a/tests/baselines/reference/jsdocTypedefNoCrash.symbols b/tests/baselines/reference/jsdocTypedefNoCrash.symbols new file mode 100644 index 0000000000000..8724c9da8d171 --- /dev/null +++ b/tests/baselines/reference/jsdocTypedefNoCrash.symbols @@ -0,0 +1,8 @@ +=== tests/cases/compiler/export.js === +/** + * @typedef {{ + * }} + */ +export const foo = 5; +>foo : Symbol(foo, Decl(export.js, 4, 12)) + diff --git a/tests/baselines/reference/jsdocTypedefNoCrash.types b/tests/baselines/reference/jsdocTypedefNoCrash.types new file mode 100644 index 0000000000000..e05c4421a794f --- /dev/null +++ b/tests/baselines/reference/jsdocTypedefNoCrash.types @@ -0,0 +1,9 @@ +=== tests/cases/compiler/export.js === +/** + * @typedef {{ + * }} + */ +export const foo = 5; +>foo : 5 +>5 : 5 + diff --git a/tests/baselines/reference/jsdocTypedefNoCrash2.errors.txt b/tests/baselines/reference/jsdocTypedefNoCrash2.errors.txt new file mode 100644 index 0000000000000..6c4a15e594720 --- /dev/null +++ b/tests/baselines/reference/jsdocTypedefNoCrash2.errors.txt @@ -0,0 +1,12 @@ +tests/cases/compiler/export.js(1,13): error TS8008: 'type aliases' can only be used in a .ts file. + + +==== tests/cases/compiler/export.js (1 errors) ==== + export type foo = 5; + ~~~ +!!! error TS8008: 'type aliases' can only be used in a .ts file. + /** + * @typedef {{ + * }} + */ + export const foo = 5; \ No newline at end of file diff --git a/tests/baselines/reference/jsdocTypedefNoCrash2.js b/tests/baselines/reference/jsdocTypedefNoCrash2.js new file mode 100644 index 0000000000000..397ca973d1e07 --- /dev/null +++ b/tests/baselines/reference/jsdocTypedefNoCrash2.js @@ -0,0 +1,14 @@ +//// [export.js] +export type foo = 5; +/** + * @typedef {{ + * }} + */ +export const foo = 5; + +//// [export.js] +/** + * @typedef {{ + * }} + */ +export const foo = 5; diff --git a/tests/cases/compiler/jsdocTypedefNoCrash.ts b/tests/cases/compiler/jsdocTypedefNoCrash.ts new file mode 100644 index 0000000000000..cb8f5df09efd4 --- /dev/null +++ b/tests/cases/compiler/jsdocTypedefNoCrash.ts @@ -0,0 +1,9 @@ +// @target: es6 +// @allowJs: true +// @outDir: ./dist +// @filename: export.js +/** + * @typedef {{ + * }} + */ +export const foo = 5; \ No newline at end of file diff --git a/tests/cases/compiler/jsdocTypedefNoCrash2.ts b/tests/cases/compiler/jsdocTypedefNoCrash2.ts new file mode 100644 index 0000000000000..d41fb62e44650 --- /dev/null +++ b/tests/cases/compiler/jsdocTypedefNoCrash2.ts @@ -0,0 +1,11 @@ +// @target: es6 +// @allowJs: true +// @outDir: ./dist +// @filename: export.js + +export type foo = 5; +/** + * @typedef {{ + * }} + */ +export const foo = 5; \ No newline at end of file