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

View File

@@ -862,7 +862,34 @@ namespace ts {
case SyntaxKind.EnumMember:
return emitEnumMember(<EnumMember>node);
// JSDoc nodes (ignored)
// JSDoc nodes (only used in codefixes currently)
case SyntaxKind.JSDocParameterTag:
case SyntaxKind.JSDocPropertyTag:
return emitJSDocPropertyLikeTag(node as JSDocPropertyLikeTag);
case SyntaxKind.JSDocReturnTag:
case SyntaxKind.JSDocTypeTag:
case SyntaxKind.JSDocThisTag:
case SyntaxKind.JSDocEnumTag:
return emitJSDocSimpleTypedTag(node as JSDocTypeTag);
case SyntaxKind.JSDocAugmentsTag:
return emitJSDocAugmentsTag(node as JSDocAugmentsTag);
case SyntaxKind.JSDocTemplateTag:
return emitJSDocTemplateTag(node as JSDocTemplateTag);
case SyntaxKind.JSDocTypedefTag:
return emitJSDocTypedefTag(node as JSDocTypedefTag);
case SyntaxKind.JSDocCallbackTag:
return emitJSDocCallbackTag(node as JSDocCallbackTag);
case SyntaxKind.JSDocSignature:
return emitJSDocSignature(node as JSDocSignature);
case SyntaxKind.JSDocTypeLiteral:
return emitJSDocTypeLiteral(node as JSDocTypeLiteral);
case SyntaxKind.JSDocClassTag:
case SyntaxKind.JSDocTag:
return emitJSDocSimpleTag(node as JSDocTag);
case SyntaxKind.JSDocComment:
return emitJSDoc(node as JSDoc);
// Transformation nodes (ignored)
}
@@ -2584,6 +2611,154 @@ namespace ts {
emitInitializer(node.initializer, node.name.end, node);
}
//
// JSDoc
//
function emitJSDoc(node: JSDoc) {
write("/**");
if (node.comment) {
const lines = node.comment.split(/\r\n?|\n/g);
for (const line of lines) {
writeLine();
writeSpace();
writePunctuation("*");
writeSpace();
write(line);
}
}
if (node.tags) {
if (node.tags.length === 1 && node.tags[0].kind === SyntaxKind.JSDocTypeTag && !node.comment) {
writeSpace();
emit(node.tags[0]);
}
else {
emitList(node, node.tags, ListFormat.JSDocComment);
}
}
writeSpace();
write("*/");
}
function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag) {
emitJSDocTagName(tag.tagName);
emitJSDocTypeExpression(tag.typeExpression);
emitJSDocComment(tag.comment);
}
function emitJSDocAugmentsTag(tag: JSDocAugmentsTag) {
emitJSDocTagName(tag.tagName);
writeSpace();
writePunctuation("{");
emit(tag.class);
writePunctuation("}");
emitJSDocComment(tag.comment);
}
function emitJSDocTemplateTag(tag: JSDocTemplateTag) {
emitJSDocTagName(tag.tagName);
emitJSDocTypeExpression(tag.constraint);
writeSpace();
emitList(tag, tag.typeParameters, ListFormat.CommaListElements);
emitJSDocComment(tag.comment);
}
function emitJSDocTypedefTag(tag: JSDocTypedefTag) {
emitJSDocTagName(tag.tagName);
if (tag.typeExpression) {
if (tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) {
emitJSDocTypeExpression(tag.typeExpression);
}
else {
writeSpace();
writePunctuation("{");
write("Object");
if (tag.typeExpression.isArrayType) {
writePunctuation("[");
writePunctuation("]");
}
writePunctuation("}");
}
}
if (tag.fullName) {
writeSpace();
emit(tag.fullName);
}
emitJSDocComment(tag.comment);
if (tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeLiteral) {
emitJSDocTypeLiteral(tag.typeExpression);
}
}
function emitJSDocCallbackTag(tag: JSDocCallbackTag) {
emitJSDocTagName(tag.tagName);
if (tag.name) {
writeSpace();
emit(tag.name);
}
emitJSDocComment(tag.comment);
emitJSDocSignature(tag.typeExpression);
}
function emitJSDocSimpleTag(tag: JSDocTag) {
emitJSDocTagName(tag.tagName);
emitJSDocComment(tag.comment);
}
function emitJSDocTypeLiteral(lit: JSDocTypeLiteral) {
emitList(lit, createNodeArray(lit.jsDocPropertyTags), ListFormat.JSDocComment);
}
function emitJSDocSignature(sig: JSDocSignature) {
if (sig.typeParameters) {
emitList(sig, createNodeArray(sig.typeParameters), ListFormat.JSDocComment);
}
if (sig.parameters) {
emitList(sig, createNodeArray(sig.parameters), ListFormat.JSDocComment);
}
if (sig.type) {
writeLine();
writeSpace();
writePunctuation("*");
writeSpace();
emit(sig.type);
}
}
function emitJSDocPropertyLikeTag(param: JSDocPropertyLikeTag) {
emitJSDocTagName(param.tagName);
emitJSDocTypeExpression(param.typeExpression);
writeSpace();
if (param.isBracketed) {
writePunctuation("[");
}
emit(param.name);
if (param.isBracketed) {
writePunctuation("]");
}
emitJSDocComment(param.comment);
}
function emitJSDocTagName(tagName: Identifier) {
writePunctuation("@");
emit(tagName);
}
function emitJSDocComment(comment: string | undefined) {
if (comment) {
writeSpace();
write(comment);
}
}
function emitJSDocTypeExpression(typeExpression: JSDocTypeExpression | undefined) {
if (typeExpression) {
writeSpace();
writePunctuation("{");
emit(typeExpression.type);
writePunctuation("}");
}
}
//
// Top-level nodes
//
@@ -2875,6 +3050,11 @@ namespace ts {
writeSpace();
writePunctuation("|");
break;
case ListFormat.AsteriskDelimited:
writeSpace();
writePunctuation("*");
writeSpace();
break;
case ListFormat.AmpersandDelimited:
writeSpace();
writePunctuation("&");
@@ -2944,7 +3124,12 @@ namespace ts {
const child = children![start + i];
// Write the delimiter if this is not the first node.
if (previousSibling) {
if (format & ListFormat.AsteriskDelimited) {
// always write JSDoc in the format "\n *"
writeLine();
writeDelimiter(format);
}
else if (previousSibling) {
// i.e
// function commentedParameters(
// /* Parameter a */

View File

@@ -2170,6 +2170,57 @@ namespace ts {
: node;
}
// JSDoc
/* @internal */
export function createJSDocTypeExpression(type: TypeNode): JSDocTypeExpression {
const node = createSynthesizedNode(SyntaxKind.JSDocTypeExpression) as JSDocTypeExpression;
node.type = type;
return node;
}
/* @internal */
export function createJSDocTypeTag(typeExpression?: JSDocTypeExpression, comment?: string): JSDocTypeTag {
const tag = createJSDocTag<JSDocTypeTag>(SyntaxKind.JSDocTypeTag, "type");
tag.typeExpression = typeExpression;
tag.comment = comment;
return tag;
}
/* @internal */
export function createJSDocReturnTag(typeExpression?: JSDocTypeExpression, comment?: string): JSDocReturnTag {
const tag = createJSDocTag<JSDocReturnTag>(SyntaxKind.JSDocReturnTag, "returns");
tag.typeExpression = typeExpression;
tag.comment = comment;
return tag;
}
/* @internal */
export function createJSDocParamTag(name: EntityName, isBracketed: boolean, typeExpression?: JSDocTypeExpression, comment?: string): JSDocParameterTag {
const tag = createJSDocTag<JSDocParameterTag>(SyntaxKind.JSDocParameterTag, "param");
tag.typeExpression = typeExpression;
tag.name = name;
tag.isBracketed = isBracketed;
tag.comment = comment;
return tag;
}
/* @internal */
export function createJSDocComment(comment?: string | undefined, tags?: NodeArray<JSDocTag> | undefined) {
const node = createSynthesizedNode(SyntaxKind.JSDocComment) as JSDoc;
node.comment = comment;
node.tags = tags;
return node;
}
/* @internal */
function createJSDocTag<T extends JSDocTag>(kind: T["kind"], tagName: string): T {
const node = createSynthesizedNode(kind) as T;
node.atToken = createToken(SyntaxKind.AtToken);
node.tagName = createIdentifier(tagName);
return node;
}
// JSX
export function createJsxElement(openingElement: JsxOpeningElement, children: ReadonlyArray<JsxChild>, closingElement: JsxClosingElement) {

View File

@@ -2447,7 +2447,7 @@ namespace ts {
export interface JSDocTemplateTag extends JSDocTag {
kind: SyntaxKind.JSDocTemplateTag;
constraint: TypeNode | undefined;
constraint: JSDocTypeExpression | undefined;
typeParameters: NodeArray<TypeParameterDeclaration>;
}
@@ -5532,33 +5532,34 @@ namespace ts {
BarDelimited = 1 << 2, // Each list item is space-and-bar (" |") delimited.
AmpersandDelimited = 1 << 3, // Each list item is space-and-ampersand (" &") delimited.
CommaDelimited = 1 << 4, // Each list item is comma (",") delimited.
DelimitersMask = BarDelimited | AmpersandDelimited | CommaDelimited,
AsteriskDelimited = 1 << 5, // Each list item is asterisk ("\n *") delimited, used with JSDoc.
DelimitersMask = BarDelimited | AmpersandDelimited | CommaDelimited | AsteriskDelimited,
AllowTrailingComma = 1 << 5, // Write a trailing comma (",") if present.
AllowTrailingComma = 1 << 6, // Write a trailing comma (",") if present.
// Whitespace
Indented = 1 << 6, // The list should be indented.
SpaceBetweenBraces = 1 << 7, // Inserts a space after the opening brace and before the closing brace.
SpaceBetweenSiblings = 1 << 8, // Inserts a space between each sibling node.
Indented = 1 << 7, // The list should be indented.
SpaceBetweenBraces = 1 << 8, // Inserts a space after the opening brace and before the closing brace.
SpaceBetweenSiblings = 1 << 9, // Inserts a space between each sibling node.
// Brackets/Braces
Braces = 1 << 9, // The list is surrounded by "{" and "}".
Parenthesis = 1 << 10, // The list is surrounded by "(" and ")".
AngleBrackets = 1 << 11, // The list is surrounded by "<" and ">".
SquareBrackets = 1 << 12, // The list is surrounded by "[" and "]".
Braces = 1 << 10, // The list is surrounded by "{" and "}".
Parenthesis = 1 << 11, // The list is surrounded by "(" and ")".
AngleBrackets = 1 << 12, // The list is surrounded by "<" and ">".
SquareBrackets = 1 << 13, // The list is surrounded by "[" and "]".
BracketsMask = Braces | Parenthesis | AngleBrackets | SquareBrackets,
OptionalIfUndefined = 1 << 13, // Do not emit brackets if the list is undefined.
OptionalIfEmpty = 1 << 14, // Do not emit brackets if the list is empty.
OptionalIfUndefined = 1 << 14, // Do not emit brackets if the list is undefined.
OptionalIfEmpty = 1 << 15, // Do not emit brackets if the list is empty.
Optional = OptionalIfUndefined | OptionalIfEmpty,
// Other
PreferNewLine = 1 << 15, // Prefer adding a LineTerminator between synthesized nodes.
NoTrailingNewLine = 1 << 16, // Do not emit a trailing NewLine for a MultiLine list.
NoInterveningComments = 1 << 17, // Do not emit comments between each node
PreferNewLine = 1 << 16, // Prefer adding a LineTerminator between synthesized nodes.
NoTrailingNewLine = 1 << 17, // Do not emit a trailing NewLine for a MultiLine list.
NoInterveningComments = 1 << 18, // Do not emit comments between each node
NoSpaceIfEmpty = 1 << 18, // If the literal is empty, do not add spaces between braces.
SingleElement = 1 << 19,
NoSpaceIfEmpty = 1 << 19, // If the literal is empty, do not add spaces between braces.
SingleElement = 1 << 20,
// Precomputed Formats
Modifiers = SingleLine | SpaceBetweenSiblings | NoInterveningComments,
@@ -5598,6 +5599,7 @@ namespace ts {
TypeParameters = CommaDelimited | SpaceBetweenSiblings | SingleLine | AngleBrackets | Optional,
Parameters = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis,
IndexSignatureParameters = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented | SquareBrackets,
JSDocComment = MultiLine | AsteriskDelimited,
}
/* @internal */