Skip to content

Insert JsDoc comment templates for additional nodes #19544

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Nov 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/harness/harnessLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ namespace Harness.LanguageService {
getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: ts.FormatCodeOptions): ts.TextChange[] {
return unwrapJSONCallResult(this.shim.getFormattingEditsAfterKeystroke(fileName, position, key, JSON.stringify(options)));
}
getDocCommentTemplateAtPosition(fileName: string, position: number): ts.TextInsertion {
getDocCommentTemplateAtPosition(fileName: string, position: number): ts.TextInsertion | undefined {
return unwrapJSONCallResult(this.shim.getDocCommentTemplateAtPosition(fileName, position));
}
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean {
Expand Down
72 changes: 30 additions & 42 deletions src/services/jsDoc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* @internal */
namespace ts.JsDoc {
const singleLineTemplate = { newText: "/** */", caretOffset: 3 };
const jsDocTagNames = [
"augments",
"author",
Expand Down Expand Up @@ -170,13 +171,9 @@ namespace ts.JsDoc {
/**
* Checks if position points to a valid position to add JSDoc comments, and if so,
* returns the appropriate template. Otherwise returns an empty string.
* Valid positions are
* - outside of comments, statements, and expressions, and
* - preceding a:
* - function/constructor/method declaration
* - class declarations
* - variable statements
* - namespace declarations
* Invalid positions are
* - within comments, strings (including template literals and regex), and JSXText
* - within a token
*
* Hosts should ideally check that:
* - The line is all whitespace up to 'position' before performing the insertion.
Expand All @@ -187,7 +184,8 @@ namespace ts.JsDoc {
* @param position The (character-indexed) position in the file where the check should
* be performed.
*/
export function getDocCommentTemplateAtPosition(newLine: string, sourceFile: SourceFile, position: number): TextInsertion {

export function getDocCommentTemplateAtPosition(newLine: string, sourceFile: SourceFile, position: number): TextInsertion | undefined {
// Check if in a context where we don't want to perform any insertion
if (isInString(sourceFile, position) || isInComment(sourceFile, position) || hasDocComment(sourceFile, position)) {
return undefined;
Expand All @@ -201,35 +199,33 @@ namespace ts.JsDoc {

const commentOwnerInfo = getCommentOwnerInfo(tokenAtPos);
if (!commentOwnerInfo) {
return undefined;
// if climbing the tree did not find a declaration with parameters, complete to a single line comment
return singleLineTemplate;
}
const { commentOwner, parameters } = commentOwnerInfo;
if (commentOwner.getStart() < position) {

if (commentOwner.kind === SyntaxKind.JsxText) {
return undefined;
}

if (commentOwner.getStart() < position || parameters.length === 0) {
// if climbing the tree found a declaration with parameters but the request was made inside it
// or if there are no parameters, complete to a single line comment
return singleLineTemplate;
}

const posLineAndChar = sourceFile.getLineAndCharacterOfPosition(position);
const lineStart = sourceFile.getLineStarts()[posLineAndChar.line];

// replace non-whitespace characters in prefix with spaces.
const indentationStr = sourceFile.text.substr(lineStart, posLineAndChar.character).replace(/\S/i, () => " ");
const isJavaScriptFile = hasJavaScriptFileExtension(sourceFile.fileName);

let docParams = "";
if (parameters) {
for (let i = 0; i < parameters.length; i++) {
const currentName = parameters[i].name;
const paramName = currentName.kind === SyntaxKind.Identifier ?
(<Identifier>currentName).escapedText :
"param" + i;
if (isJavaScriptFile) {
docParams += `${indentationStr} * @param {any} ${paramName}${newLine}`;
}
else {
docParams += `${indentationStr} * @param ${paramName}${newLine}`;
}
}
}
const docParams = parameters.map(({name}, i) => {
const nameText = isIdentifier(name) ? name.text : `param${i}`;
const type = isJavaScriptFile ? "{any} " : "";
return `${indentationStr} * @param ${type}${nameText}${newLine}`;
}).join("");

// A doc comment consists of the following
// * The opening comment line
Expand All @@ -251,43 +247,30 @@ namespace ts.JsDoc {

interface CommentOwnerInfo {
readonly commentOwner: Node;
readonly parameters?: ReadonlyArray<ParameterDeclaration>;
readonly parameters: ReadonlyArray<ParameterDeclaration>;
}
function getCommentOwnerInfo(tokenAtPos: Node): CommentOwnerInfo | undefined {
// TODO: add support for:
// - enums/enum members
// - interfaces
// - property declarations
// - potentially property assignments
for (let commentOwner = tokenAtPos; commentOwner; commentOwner = commentOwner.parent) {
switch (commentOwner.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.Constructor:
const { parameters } = commentOwner as FunctionDeclaration | MethodDeclaration | ConstructorDeclaration;
case SyntaxKind.MethodSignature:
const { parameters } = commentOwner as FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature;
return { commentOwner, parameters };

case SyntaxKind.ClassDeclaration:
return { commentOwner };

case SyntaxKind.VariableStatement: {
const varStatement = <VariableStatement>commentOwner;
const varDeclarations = varStatement.declarationList.declarations;
const parameters = varDeclarations.length === 1 && varDeclarations[0].initializer
? getParametersFromRightHandSideOfAssignment(varDeclarations[0].initializer)
: undefined;
return { commentOwner, parameters };
return parameters ? { commentOwner, parameters } : undefined;
}

case SyntaxKind.SourceFile:
return undefined;

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) === ts.SpecialPropertyAssignmentKind.None) {
Expand All @@ -296,6 +279,11 @@ namespace ts.JsDoc {
const parameters = isFunctionLike(be.right) ? be.right.parameters : emptyArray;
return { commentOwner, parameters };
}

case SyntaxKind.JsxText: {
const parameters: ReadonlyArray<ParameterDeclaration> = emptyArray;
return { commentOwner, parameters };
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1791,7 +1791,7 @@ namespace ts {
}
}

function getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion {
function getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion | undefined {
return JsDoc.getDocCommentTemplateAtPosition(getNewLineOrDefaultFromHost(host), syntaxTreeCache.getCurrentSourceFile(fileName), position);
}

Expand Down
7 changes: 2 additions & 5 deletions tests/cases/fourslash/docCommentTemplateClassDecl01.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,5 @@
//// }
////}

verify.docCommentTemplateAt("decl", /*newTextOffset*/ 8,
`/**
*
*/
`);
verify.docCommentTemplateAt("decl", /*newTextOffset*/ 3,
"/** */");
26 changes: 10 additions & 16 deletions tests/cases/fourslash/docCommentTemplateClassDeclMethods01.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
/// <reference path='fourslash.ts' />

const enum Indentation {
Standard = 8,
Indented = 12,
}
const singleLineOffset = 3;
const multiLineOffset = 12;


////class C {
Expand All @@ -16,42 +14,38 @@ const enum Indentation {
//// }
////}

verify.docCommentTemplateAt("0", Indentation.Standard,
`/**
*
*/`);
verify.docCommentTemplateAt("0", singleLineOffset,
"/** */");


verify.docCommentTemplateAt("1", Indentation.Indented,
`/**
*
*/`);
verify.docCommentTemplateAt("1", singleLineOffset,
"/** */");


verify.docCommentTemplateAt("2", Indentation.Indented,
verify.docCommentTemplateAt("2", multiLineOffset,
`/**
*
* @param a
*/
`);

verify.docCommentTemplateAt("3", Indentation.Indented,
verify.docCommentTemplateAt("3", multiLineOffset,
`/**
*
* @param a
* @param b
*/
`);

verify.docCommentTemplateAt("4", Indentation.Indented,
verify.docCommentTemplateAt("4", multiLineOffset,
`/**
*
* @param a
* @param param1
* @param param2
*/`);

verify.docCommentTemplateAt("5", Indentation.Indented,
verify.docCommentTemplateAt("5", multiLineOffset,
`/**
*
* @param a
Expand Down
13 changes: 5 additions & 8 deletions tests/cases/fourslash/docCommentTemplateClassDeclMethods02.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/// <reference path='fourslash.ts' />

const enum Indentation {
Indented = 12,
}
const singleLineOffset = 3;
const multiLineOffset = 12;

////class C {
//// /*0*/
Expand All @@ -13,12 +12,10 @@ const enum Indentation {
//// [1 + 2 + 3 + Math.rand()](x: number, y: string, z = true) { }
////}

verify.docCommentTemplateAt("0", Indentation.Indented,
`/**
*
*/`);
verify.docCommentTemplateAt("0", singleLineOffset,
"/** */");

verify.docCommentTemplateAt("1", Indentation.Indented,
verify.docCommentTemplateAt("1", multiLineOffset,
`/**
*
* @param x
Expand Down
2 changes: 1 addition & 1 deletion tests/cases/fourslash/docCommentTemplateEmptyFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
// @Filename: emptyFile.ts
/////*0*/

verify.noDocCommentTemplateAt("0");
verify.docCommentTemplateAt("0", 3, "/** */");
13 changes: 4 additions & 9 deletions tests/cases/fourslash/docCommentTemplateIndentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,8 @@
//// /*1*/
/////*0*/ function foo() { }

const noIndentEmptyScaffolding = "/**\r\n * \r\n */";
const oneIndentEmptyScaffolding = "/**\r\n * \r\n */";
const twoIndentEmptyScaffolding = "/**\r\n * \r\n */";
const noIndentOffset = 8;
const oneIndentOffset = noIndentOffset + 4;
const twoIndentOffset = oneIndentOffset + 4;
const singleLineComment = "/** */";

verify.docCommentTemplateAt("0", noIndentOffset, noIndentEmptyScaffolding);
verify.docCommentTemplateAt("1", oneIndentOffset, oneIndentEmptyScaffolding);
verify.docCommentTemplateAt("2", twoIndentOffset, twoIndentEmptyScaffolding);
verify.docCommentTemplateAt("0", 3, singleLineComment);
verify.docCommentTemplateAt("1", 3, singleLineComment);
verify.docCommentTemplateAt("2", 3, singleLineComment);
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
// @Filename: functionDecl.ts
////f/*0*/unction /*1*/foo/*2*/(/*3*/) /*4*/{ /*5*/}

for (const marker of test.markers()) {
verify.noDocCommentTemplateAt(marker);
}
verify.noDocCommentTemplateAt("0");

verify.docCommentTemplateAt("1", 3, "/** */");
verify.docCommentTemplateAt("2", 3, "/** */");
verify.docCommentTemplateAt("3", 3, "/** */");
verify.docCommentTemplateAt("4", 3, "/** */");
verify.docCommentTemplateAt("5", 3, "/** */");
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/// <reference path='fourslash.ts' />

/////*interfaceFoo*/
////interface Foo {
//// /*propertybar*/
//// bar: any;
////
//// /*methodbaz*/
//// baz(message: any): void;
////
//// /*methodUnit*/
//// unit(): void;
////}
////
/////*enumStatus*/
////const enum Status {
//// /*memberOpen*/
//// Open,
////
//// /*memberClosed*/
//// Closed
////}
////
/////*aliasBar*/
////type Bar = Foo & any;

verify.docCommentTemplateAt("interfaceFoo", /*expectedOffset*/ 3,
"/** */");

verify.docCommentTemplateAt("propertybar", /*expectedOffset*/ 3,
"/** */");

verify.docCommentTemplateAt("methodbaz", /*expectedOffset*/ 12,
`/**
*
* @param message
*/`);

verify.docCommentTemplateAt("methodUnit", /*expectedOffset*/ 3,
"/** */");

verify.docCommentTemplateAt("enumStatus", /*expectedOffset*/ 3,
"/** */");

verify.docCommentTemplateAt("memberOpen", /*expectedOffset*/ 3,
"/** */");

verify.docCommentTemplateAt("memberClosed", /*expectedOffset*/ 3,
"/** */");
12 changes: 12 additions & 0 deletions tests/cases/fourslash/docCommentTemplateJSXText.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// <reference path="fourslash.ts" />

//@Filename: file.tsx
////
//// var x = <div>
//// /*0*/hello/*1*/
//// /*2*/goodbye/*3*/
//// </div>;

for (const marker in test.markers()) {
verify.noDocCommentTemplateAt(marker);
}
18 changes: 6 additions & 12 deletions tests/cases/fourslash/docCommentTemplateNamespacesAndModules01.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,11 @@
////module "ambientModule" {
////}

verify.docCommentTemplateAt("namespaceN", /*indentation*/ 8,
`/**
*
*/`);
verify.docCommentTemplateAt("namespaceN", /*indentation*/ 3,
"/** */");

verify.docCommentTemplateAt("namespaceM", /*indentation*/ 8,
`/**
*
*/`);
verify.docCommentTemplateAt("namespaceM", /*indentation*/ 3,
"/** */");

verify.docCommentTemplateAt("namespaceM", /*indentation*/ 8,
`/**
*
*/`);
verify.docCommentTemplateAt("namespaceM", /*indentation*/ 3,
"/** */");
Loading