diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index 8b45c762da1..7874ddfcb3a 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -269,34 +269,35 @@ namespace ts.JsDoc { if (!commentOwnerInfo) { return undefined; } - const { commentOwner, parameters } = commentOwnerInfo; + + const { commentOwner, parameters, hasReturn } = commentOwnerInfo; if (commentOwner.getStart(sourceFile) < position) { return undefined; } - if (!parameters || parameters.length === 0) { - // if there are no parameters, just complete to a single line JSDoc comment - const singleLineResult = "/** */"; - return { newText: singleLineResult, caretOffset: 3 }; - } - const indentationStr = getIndentationStringAtPosition(sourceFile, position); + const isJavaScriptFile = hasJSFileExtension(sourceFile.fileName); + const tags = + (parameters ? parameterDocComments(parameters || [], isJavaScriptFile, indentationStr, newLine) : "") + + (hasReturn ? returnsDocComment(indentationStr, newLine) : ""); // A doc comment consists of the following // * The opening comment line // * the first line (without a param) for the object's untagged info (this is also where the caret ends up) // * the '@param'-tagged lines + // * the '@returns'-tag // * TODO: other tags. // * the closing comment line // * if the caret was directly in front of the object, then we add an extra line and indentation. - const preamble = "/**" + newLine + indentationStr + " * "; - const result = - preamble + newLine + - parameterDocComments(parameters, hasJSFileExtension(sourceFile.fileName), indentationStr, newLine) + - indentationStr + " */" + - (tokenStart === position ? newLine + indentationStr : ""); - - return { newText: result, caretOffset: preamble.length }; + const openComment = "/**"; + const closeComment = " */"; + if (tags) { + const preamble = openComment + newLine + indentationStr + " * "; + const endLine = tokenStart === position ? newLine + indentationStr : ""; + const result = preamble + newLine + tags + indentationStr + closeComment + endLine; + return { newText: result, caretOffset: preamble.length }; + } + return { newText: openComment + closeComment, caretOffset: 3 }; } function getIndentationStringAtPosition(sourceFile: SourceFile, position: number): string { @@ -315,9 +316,14 @@ namespace ts.JsDoc { }).join(""); } + function returnsDocComment(indentationStr: string, newLine: string) { + return `${indentationStr} * @returns${newLine}`; + } + interface CommentOwnerInfo { readonly commentOwner: Node; readonly parameters?: readonly ParameterDeclaration[]; + readonly hasReturn?: boolean; } function getCommentOwnerInfo(tokenAtPos: Node): CommentOwnerInfo | undefined { return forEachAncestor(tokenAtPos, getCommentOwnerInfoWorker); @@ -330,8 +336,8 @@ namespace ts.JsDoc { case SyntaxKind.Constructor: case SyntaxKind.MethodSignature: case SyntaxKind.ArrowFunction: - const { parameters } = commentOwner as FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature; - return { commentOwner, parameters }; + const host = commentOwner as ArrowFunction | FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature; + return { commentOwner, parameters: host.parameters, hasReturn: hasReturn(host) }; case SyntaxKind.PropertyAssignment: return getCommentOwnerInfoWorker((commentOwner as PropertyAssignment).initializer); @@ -347,10 +353,12 @@ namespace ts.JsDoc { case SyntaxKind.VariableStatement: { const varStatement = commentOwner; const varDeclarations = varStatement.declarationList.declarations; - const parameters = varDeclarations.length === 1 && varDeclarations[0].initializer - ? getParametersFromRightHandSideOfAssignment(varDeclarations[0].initializer) + const host = varDeclarations.length === 1 && varDeclarations[0].initializer + ? getRightHandSideOfAssignment(varDeclarations[0].initializer) : undefined; - return { commentOwner, parameters }; + return host + ? { commentOwner, parameters: host.parameters, hasReturn: hasReturn(host) } + : { commentOwner }; } case SyntaxKind.SourceFile: @@ -369,26 +377,24 @@ namespace ts.JsDoc { if (getAssignmentDeclarationKind(be) === AssignmentDeclarationKind.None) { return "quit"; } - const parameters = isFunctionLike(be.right) ? be.right.parameters : emptyArray; - return { commentOwner, parameters }; + return isFunctionLike(be.right) + ? { commentOwner, parameters: be.right.parameters, hasReturn: hasReturn(be.right) } + : { commentOwner }; } case SyntaxKind.PropertyDeclaration: const init = (commentOwner as PropertyDeclaration).initializer; if (init && (isFunctionExpression(init) || isArrowFunction(init))) { - return { commentOwner, parameters: init.parameters }; + return { commentOwner, parameters: init.parameters, hasReturn: hasReturn(init) }; } } } - /** - * Digs into an an initializer or RHS operand of an assignment operation - * to get the parameters of an apt signature corresponding to a - * function expression or a class expression. - * - * @param rightHandSide the expression which may contain an appropriate set of parameters - * @returns the parameters of a signature found on the RHS if one exists; otherwise 'emptyArray'. - */ - function getParametersFromRightHandSideOfAssignment(rightHandSide: Expression): readonly ParameterDeclaration[] { + function hasReturn(node: Node) { + return isArrowFunction(node) && isExpression(node.body) + || isFunctionLikeDeclaration(node) && node.body && isBlock(node.body) && !!forEachReturnStatement(node.body, n => n); + } + + function getRightHandSideOfAssignment(rightHandSide: Expression): FunctionExpression | ArrowFunction | ConstructorDeclaration | undefined { while (rightHandSide.kind === SyntaxKind.ParenthesizedExpression) { rightHandSide = (rightHandSide).expression; } @@ -396,13 +402,9 @@ namespace ts.JsDoc { switch (rightHandSide.kind) { case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: - return (rightHandSide).parameters; - case SyntaxKind.ClassExpression: { - const ctr = find((rightHandSide as ClassExpression).members, isConstructorDeclaration); - return ctr ? ctr.parameters : emptyArray; - } + return (rightHandSide); + case SyntaxKind.ClassExpression: + return find((rightHandSide as ClassExpression).members, isConstructorDeclaration); } - - return emptyArray; } } diff --git a/tests/cases/fourslash/docCommentTemplateClassDeclMethods02.ts b/tests/cases/fourslash/docCommentTemplateClassDeclMethods02.ts index 7a35e60ae9e..5eed624a915 100644 --- a/tests/cases/fourslash/docCommentTemplateClassDeclMethods02.ts +++ b/tests/cases/fourslash/docCommentTemplateClassDeclMethods02.ts @@ -1,6 +1,5 @@ /// -const singleLineOffset = 3; const multiLineOffset = 12; ////class C { @@ -12,8 +11,11 @@ const multiLineOffset = 12; //// [1 + 2 + 3 + Math.rand()](x: number, y: string, z = true) { } ////} -verify.docCommentTemplateAt("0", singleLineOffset, -"/** */"); +verify.docCommentTemplateAt("0", multiLineOffset, + `/** + * + * @returns + */`); verify.docCommentTemplateAt("1", multiLineOffset, `/** diff --git a/tests/cases/fourslash/docCommentTemplateClassDeclProperty01.ts b/tests/cases/fourslash/docCommentTemplateClassDeclProperty01.ts index ee235f34cec..221bf619308 100644 --- a/tests/cases/fourslash/docCommentTemplateClassDeclProperty01.ts +++ b/tests/cases/fourslash/docCommentTemplateClassDeclProperty01.ts @@ -23,17 +23,18 @@ verify.docCommentTemplateAt("0", multiLineOffset, `/** * * @param p0 + * @returns */`); verify.docCommentTemplateAt("1", multiLineOffset, `/** * * @param p1 + * @returns */`); verify.docCommentTemplateAt("2", multiLineOffset, `/** * * @param p2 * @param p3 + * @returns */`); - - diff --git a/tests/cases/fourslash/docCommentTemplateObjectLiteralMethods01.ts b/tests/cases/fourslash/docCommentTemplateObjectLiteralMethods01.ts index 3e2194e2bd6..567fa89495f 100644 --- a/tests/cases/fourslash/docCommentTemplateObjectLiteralMethods01.ts +++ b/tests/cases/fourslash/docCommentTemplateObjectLiteralMethods01.ts @@ -1,6 +1,5 @@ /// -const singleLineOffset = 3; const multiLineOffset = 12; ////var x = { @@ -19,7 +18,11 @@ const multiLineOffset = 12; //// m2: (a: string, b: string) => {} ////} -verify.docCommentTemplateAt("0", singleLineOffset, "/** */"); +verify.docCommentTemplateAt("0", multiLineOffset, + `/** + * + * @returns + */`); verify.docCommentTemplateAt("1", multiLineOffset, `/** diff --git a/tests/cases/fourslash/docCommentTemplateReturnsTag.ts b/tests/cases/fourslash/docCommentTemplateReturnsTag.ts new file mode 100644 index 00000000000..09efa26036f --- /dev/null +++ b/tests/cases/fourslash/docCommentTemplateReturnsTag.ts @@ -0,0 +1,56 @@ +/// + +/////*0*/ +////function f1() {} + +/////*1*/ +////function f2() { +//// return 1; +////} + +/////*2*/ +////const f3 = () => 1; + +/////*3*/ +////const f3 = () => { +//// return 1; +////} + +////class Foo { +//// /*4*/ +//// m1() {} +//// +//// /*5*/ +//// m2() { +//// return 1; +//// } +////} + +verify.docCommentTemplateAt("0", 3, "/** */"); + +verify.docCommentTemplateAt("1", 8, + `/** + * + * @returns + */`); + +verify.docCommentTemplateAt("2", 8, + `/** + * + * @returns + */`); + +verify.docCommentTemplateAt("3", 8, + `/** + * + * @returns + */`); + +verify.docCommentTemplateAt("4", 3, "/** */"); + + +verify.docCommentTemplateAt("5", 12, + `/** + * + * @returns + */`); diff --git a/tests/cases/fourslash/docCommentTemplateVariableStatements01.ts b/tests/cases/fourslash/docCommentTemplateVariableStatements01.ts index 9112c9a8cab..974ce1641ab 100644 --- a/tests/cases/fourslash/docCommentTemplateVariableStatements01.ts +++ b/tests/cases/fourslash/docCommentTemplateVariableStatements01.ts @@ -39,6 +39,7 @@ verify.docCommentTemplateAt("e", /*newTextOffset*/ 8, * @param x * @param y * @param z + * @returns */`); verify.docCommentTemplateAt("f", /*newTextOffset*/ 8, diff --git a/tests/cases/fourslash/docCommentTemplateVariableStatements03.ts b/tests/cases/fourslash/docCommentTemplateVariableStatements03.ts index 6971b86a312..a998760fc74 100644 --- a/tests/cases/fourslash/docCommentTemplateVariableStatements03.ts +++ b/tests/cases/fourslash/docCommentTemplateVariableStatements03.ts @@ -33,6 +33,7 @@ verify.docCommentTemplateAt("a", /*newTextOffset*/ 8, `/** * * @param x + * @returns */`); verify.docCommentTemplateAt("b", /*newTextOffset*/ 8, @@ -41,12 +42,14 @@ verify.docCommentTemplateAt("b", /*newTextOffset*/ 8, * @param x * @param y * @param z + * @returns */`); verify.docCommentTemplateAt("c", /*newTextOffset*/ 8, `/** * * @param x + * @returns */`); verify.docCommentTemplateAt("d", /*newTextOffset*/ 3, @@ -56,6 +59,7 @@ verify.docCommentTemplateAt("e", /*newTextOffset*/ 8, `/** * * @param param0 + * @returns */`); verify.docCommentTemplateAt("f", /*newTextOffset*/ 3, diff --git a/tests/cases/fourslash/docCommentTemplate_insideEmptyComment.ts b/tests/cases/fourslash/docCommentTemplate_insideEmptyComment.ts index f521c4a2c51..e5d20a49df0 100644 --- a/tests/cases/fourslash/docCommentTemplate_insideEmptyComment.ts +++ b/tests/cases/fourslash/docCommentTemplate_insideEmptyComment.ts @@ -10,6 +10,7 @@ verify.docCommentTemplateAt("", /*newTextOffset*/ 8, `/** * * @param p + * @returns */`); verify.noDocCommentTemplateAt("1");