diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 479c662c86ed3..fbe37e73e9e88 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -142,6 +142,16 @@ namespace ts { return undefined; } + export function forEachAncestor(node: Node, callback: (n: Node) => T | undefined | "quit"): T | undefined { + while (true) { + const res = callback(node); + if (res === "quit") return undefined; + if (res !== undefined) return res; + if (isSourceFile(node)) return undefined; + node = node.parent; + } + } + /** * Calls `callback` for each entry in the map, returning the first truthy result. * Use `map.forEach` instead for normal iteration. diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index bb90a601a4943..23fe3e63a9db9 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -338,50 +338,54 @@ namespace ts.JsDoc { readonly parameters?: ReadonlyArray; } function getCommentOwnerInfo(tokenAtPos: Node): CommentOwnerInfo | undefined { - for (let commentOwner = tokenAtPos; commentOwner; commentOwner = commentOwner.parent) { - switch (commentOwner.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.MethodSignature: - const { parameters } = commentOwner as FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature; - return { commentOwner, parameters }; - - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.EnumMember: - case SyntaxKind.TypeAliasDeclaration: - return { commentOwner }; - - case SyntaxKind.VariableStatement: { - const varStatement = commentOwner; - const varDeclarations = varStatement.declarationList.declarations; - const parameters = varDeclarations.length === 1 && varDeclarations[0].initializer - ? getParametersFromRightHandSideOfAssignment(varDeclarations[0].initializer!) - : undefined; - return { commentOwner, parameters }; - } + return forEachAncestor(tokenAtPos, getCommentOwnerInfoWorker); + } + function getCommentOwnerInfoWorker(commentOwner: Node): CommentOwnerInfo | undefined | "quit" { + switch (commentOwner.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.MethodSignature: + const { parameters } = commentOwner as FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature; + return { commentOwner, parameters }; + + case SyntaxKind.PropertyAssignment: + return getCommentOwnerInfoWorker((commentOwner as PropertyAssignment).initializer); + + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.EnumMember: + case SyntaxKind.TypeAliasDeclaration: + return { commentOwner }; + + case SyntaxKind.VariableStatement: { + const varStatement = commentOwner; + const varDeclarations = varStatement.declarationList.declarations; + const parameters = varDeclarations.length === 1 && varDeclarations[0].initializer + ? getParametersFromRightHandSideOfAssignment(varDeclarations[0].initializer!) + : undefined; + return { commentOwner, parameters }; + } - case SyntaxKind.SourceFile: - return undefined; + case SyntaxKind.SourceFile: + return "quit"; - case SyntaxKind.ModuleDeclaration: - // If in walking up the tree, we hit a a nested namespace declaration, - // then we must be somewhere within a dotted namespace name; however we don't - // want to give back a JSDoc template for the 'b' or 'c' in 'namespace a.b.c { }'. - return commentOwner.parent.kind === SyntaxKind.ModuleDeclaration ? undefined : { commentOwner }; + case SyntaxKind.ModuleDeclaration: + // If in walking up the tree, we hit a a nested namespace declaration, + // then we must be somewhere within a dotted namespace name; however we don't + // want to give back a JSDoc template for the 'b' or 'c' in 'namespace a.b.c { }'. + return commentOwner.parent.kind === SyntaxKind.ModuleDeclaration ? undefined : { commentOwner }; - case SyntaxKind.BinaryExpression: { - const be = commentOwner as BinaryExpression; - if (getSpecialPropertyAssignmentKind(be) === SpecialPropertyAssignmentKind.None) { - return undefined; - } - const parameters = isFunctionLike(be.right) ? be.right.parameters : emptyArray; - return { commentOwner, parameters }; + case SyntaxKind.BinaryExpression: { + const be = commentOwner as BinaryExpression; + if (getSpecialPropertyAssignmentKind(be) === SpecialPropertyAssignmentKind.None) { + return "quit"; } + const parameters = isFunctionLike(be.right) ? be.right.parameters : emptyArray; + return { commentOwner, parameters }; } } } diff --git a/tests/cases/fourslash/docCommentTemplateObjectLiteralMethods01.ts b/tests/cases/fourslash/docCommentTemplateObjectLiteralMethods01.ts index 029d685e3a0e3..08f1b99702145 100644 --- a/tests/cases/fourslash/docCommentTemplateObjectLiteralMethods01.ts +++ b/tests/cases/fourslash/docCommentTemplateObjectLiteralMethods01.ts @@ -10,6 +10,8 @@ const multiLineOffset = 12; //// } //// /*1*/ //// [1 + 2 + 3 + Math.rand()](x: number, y: string, z = true) { } +//// /*2*/ +//// m: function(a) {} ////} verify.docCommentTemplateAt("0", singleLineOffset, "/** */"); @@ -21,3 +23,9 @@ verify.docCommentTemplateAt("1", multiLineOffset, * @param y * @param z */`); + +verify.docCommentTemplateAt("2", multiLineOffset, + `/** + * + * @param a + */`);