Merge existing JSDoc comments (#27978)

* Correct indentation, using correct (I hope) indentation code

Note that part of the code, in formatting.ts, is cloned but should be
extracted to a function instead.

* Remove some possibly-superfluous code

But I see 4 failures with whitespace, so perhaps not.

* Restrict indentation change to avoid breaking baselines

The indentation code is very complex so I'm just going to avoid breaking
our single-line tests for now, plus add a simple jsdoc test to show that
multiline jsdoc indentation isn't destroyed in the common case.

* Switched over to construction for @return/@type

Still doesn't merge correctly though

* Add @return tags to emitter

* Merge multiple jsdocs

(not for @param yet)

* Merge multiple jsdoc for parameters too

* Emit more jsdoc tags

Not all of them; I got cold feet since I'll have to write tests for
them. I'll do that tomorrow.

* Many fixes to JSDoc emit

And single tests (at least) for all tags

* Cleanup in textChanges.ts

* Cleanup in formatting.ts

(Plus a little more in textChanges.ts)

* Cleanup in inferFromUsage.ts

* Fix minor omissions

* Separate merged top-level JSDoc comments with \n

instead of space.

* Don't delete intrusive non-jsdoc comments

* Cleanup from PR comments

1. Refactor emit code into smaller functions.
2. Preceding-whitespace utility is slightly easier to use.
3. Better casts and types in inferFromUsage make it easier to read.

* Fix bogus newline

* Use @andy-ms' cleanup annotateJSDocParameters
This commit is contained in:
Nathan Shively-Sanders
2018-10-24 16:14:52 -07:00
committed by GitHub
parent e46c846ee6
commit fe2a33fcbc
20 changed files with 654 additions and 249 deletions
+13 -48
View File
@@ -209,12 +209,6 @@ namespace ts.textChanges {
export type TypeAnnotatable = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature;
interface JSDocParameter {
declaration: ParameterDeclaration;
typeNode: TypeNode;
isOptional?: boolean;
}
export class ChangeTracker {
private readonly changes: Change[] = [];
private readonly newFiles: { readonly oldFile: SourceFile | undefined, readonly fileName: string, readonly statements: ReadonlyArray<Statement> }[] = [];
@@ -345,10 +339,19 @@ namespace ts.textChanges {
this.insertText(sourceFile, token.getStart(sourceFile), text);
}
public insertCommentThenNewline(sourceFile: SourceFile, character: number, position: number, commentText: string): void {
const token = getTouchingToken(sourceFile, position);
const text = "/**" + commentText + "*/" + this.newLineCharacter + repeatString(" ", character);
this.insertText(sourceFile, token.getStart(sourceFile), text);
public insertJsdocCommentBefore(sourceFile: SourceFile, node: HasJSDoc, tag: JSDoc) {
const fnStart = node.getStart(sourceFile);
if (node.jsDoc) {
for (const jsdoc of node.jsDoc) {
this.deleteRange(sourceFile, {
pos: getLineStartPositionForPosition(jsdoc.getStart(sourceFile), sourceFile),
end: getAdjustedEndPosition(sourceFile, jsdoc, /*options*/ {})
});
}
}
const startPosition = getPrecedingNonSpaceCharacterPosition(sourceFile.text, fnStart - 1);
const indent = sourceFile.text.slice(startPosition, fnStart);
this.insertNodeAt(sourceFile, fnStart, tag, { preserveLeadingWhitespace: false, suffix: this.newLineCharacter + indent });
}
public replaceRangeWithText(sourceFile: SourceFile, range: TextRange, text: string) {
@@ -359,23 +362,6 @@ namespace ts.textChanges {
this.replaceRangeWithText(sourceFile, createRange(pos), text);
}
public tryInsertJSDocParameters(sourceFile: SourceFile, parameters: JSDocParameter[]) {
if (parameters.length === 0) {
return;
}
const parent = parameters[0].declaration.parent;
const indent = getLineAndCharacterOfPosition(sourceFile, parent.getStart()).character;
let commentText = "\n";
for (const { declaration, typeNode, isOptional } of parameters) {
if (isIdentifier(declaration.name)) {
const printed = changesToText.getNonformattedText(typeNode, sourceFile, this.newLineCharacter).text;
commentText += this.printJSDocParameter(indent, printed, declaration.name, isOptional);
}
}
commentText += repeatString(" ", indent + 1);
this.insertCommentThenNewline(sourceFile, indent, parent.getStart(), commentText);
}
/** Prefer this over replacing a node with another that has a type annotation, as it avoids reformatting the other parts of the node. */
public tryInsertTypeAnnotation(sourceFile: SourceFile, node: TypeAnnotatable, type: TypeNode): void {
let endNode: Node | undefined;
@@ -394,27 +380,6 @@ namespace ts.textChanges {
this.insertNodeAt(sourceFile, endNode.end, type, { prefix: ": " });
}
public tryInsertJSDocType(sourceFile: SourceFile, node: Node, type: TypeNode): void {
const printed = changesToText.getNonformattedText(type, sourceFile, this.newLineCharacter).text;
let commentText;
if (isGetAccessorDeclaration(node)) {
commentText = ` @return {${printed}} `;
}
else {
commentText = ` @type {${printed}} `;
node = node.parent;
}
this.insertCommentThenNewline(sourceFile, getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).character, node.getStart(sourceFile), commentText);
}
private printJSDocParameter(indent: number, printed: string, name: Identifier, isOptionalParameter: boolean | undefined) {
let printName = unescapeLeadingUnderscores(name.escapedText);
if (isOptionalParameter) {
printName = `[${printName}]`;
}
return repeatString(" ", indent) + ` * @param {${printed}} ${printName}\n`;
}
public insertTypeParameters(sourceFile: SourceFile, node: SignatureDeclaration, typeParameters: ReadonlyArray<TypeParameterDeclaration>): void {
// If no `(`, is an arrow function `x => x`, so use the pos of the first parameter
const start = (findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile) || first(node.parameters)).getStart(sourceFile);