-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Expose JSDoc tags through the language service #12856
Expose JSDoc tags through the language service #12856
Conversation
Hi @minestarks, I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution!
TTYL, MSBOT; |
return { | ||
kind: quickInfo.kind, | ||
kindModifiers: quickInfo.kindModifiers, | ||
start: scriptInfo.positionToLineOffset(quickInfo.textSpan.start), | ||
end: scriptInfo.positionToLineOffset(ts.textSpanEnd(quickInfo.textSpan)), | ||
displayString: displayString, | ||
documentation: docString, | ||
tags: tags |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: tags: quickInfo.tags || []
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reviewed this on an iPad and it looks good but I'd like to take one more look with codeflow to make sure I don't miss anything before signing off.
return; | ||
} | ||
const jsDocTags: JSDocTag[] = []; | ||
jsDocs.forEach(doc => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just for (const doc in jsDocs)
?
@@ -492,12 +494,18 @@ namespace ts { | |||
kind: string; | |||
} | |||
|
|||
export interface JSDocTagInfo { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about the naming scheme in services, but I'd prefer a terser name, like JSDocTag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Me too. But there's already a ts.JSDocTag type and it's not quite what I want....
@@ -6468,7 +6468,7 @@ namespace ts { | |||
|
|||
function parseTagComments(indent: number) { | |||
const comments: string[] = []; | |||
let state = JSDocState.SawAsterisk; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this just a drive-by bug fix? I didn't see any real difference in parsing functionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes I should clarify. This isn't irrelevant. It fixes a bug I discovered while running the new quickinfo test I added. Consider this comment:
/**
* @mytag1
* here all the comments are on a new line
* @mytag2
*/
When the comment parser starts out at "SawAsterisk" state I get this tag comment for mytag1: "* here all the comments are on a new line"
With my change it's fixed: "here all the comments are on a new line"
This is because we skip all the whitespace (including newlines) before we begin parsing so we start right at the *. This would have been handled in the AsteriskToken case below, but only if the last state had been BeginningOfLine.
Going over the whole logic I believe it just makes more sense this way. Starting at BeginningOfLine is more logical since we didn't actually see an asterisk so why start at SawAsterisk...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. There was a similar bug in the start state for the main jsdoc parsing, so it makes sense that you ran into it here too.
@@ -759,13 +759,14 @@ namespace ts.Completions { | |||
const symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location) === entryName ? s : undefined); | |||
|
|||
if (symbol) { | |||
const { displayParts, documentation, symbolKind } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All); | |||
const { displayParts, documentation, symbolKind, jsDocTags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: tags
to enable shorthand property assignment below
} | ||
const jsDocTags: JSDocTag[] = []; | ||
jsDocs.forEach(doc => { | ||
const tagsForDoc = (doc as JSDoc).tags; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is type assertion necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess not. I got confused by the 2.0 language service giving me an implicit any warning...
jsDocTags.push(...tagsForDoc.filter(tag => tag.kind === SyntaxKind.JSDocTag)); | ||
} | ||
}); | ||
if (!jsDocTags) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
always false
since jsDocTags
array is pre-allocated
if (!jsDocTags) { | ||
return; | ||
} | ||
for (const tag of jsDocTags) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
silly question: do we need intermediate jsDocTags
? Can we just put data into tags
directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure I don't see why not. Takes care of your other comment below too.
return; | ||
} | ||
for (const tag of jsDocTags) { | ||
if (tag) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can it ever be null
or undefined
? I think in this case line 83
should fail
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: avoid empty array allocation in default case.
@@ -404,6 +416,10 @@ namespace ts { | |||
// symbol has no doc comment, then the empty string will be returned. | |||
documentationComment: SymbolDisplayPart[]; | |||
|
|||
// Undefined is used to indicate the value has not been computed. If, after computing, the | |||
// symbol has no doc comment, then the empty array will be returned. | |||
jsDocTags: JSDocTagInfo[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty array is expensive here.
Most of SignatureObject
instances will have no JSDoc comments. Allocating empty arrays will add bunch of fluff to GC. Would be best to avoid that.
getJsDocTags(): JSDocTagInfo[] { | ||
if (this.jsDocTags === undefined) { | ||
this.jsDocTags = this.declaration ? JsDoc.getJsDocTagsFromDeclarations([this.declaration]) : []; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about using undefined
for storing un-computed state and null
for computed and empty?
@@ -68,6 +68,28 @@ namespace ts.JsDoc { | |||
return documentationComment; | |||
} | |||
|
|||
export function getJsDocTagsFromDeclarations(declarations: Declaration[]) { | |||
// Only collect doc comments from duplicate declarations once. | |||
const tags: JSDocTagInfo[] = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here too the array may be lazy-allocated when first tag is encountered -- meaning no allocation for the default case.
getJsDocTags(): JSDocTagInfo[] { | ||
if (this.tags === undefined) { | ||
this.tags = JsDoc.getJsDocTagsFromDeclarations(this.declarations); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here too the check would need to change to avoid empty array allocation for the default no-JSDoc case.
(to mark the difference between un-computed value and empty value in this.tags
-- undefined
versus null
can be used)
@@ -1315,7 +1339,8 @@ namespace ts { | |||
kindModifiers: ScriptElementKindModifier.none, | |||
textSpan: createTextSpan(node.getStart(), node.getWidth()), | |||
displayParts: typeToDisplayParts(typeChecker, type, getContainerNode(node)), | |||
documentation: type.symbol ? type.symbol.getDocumentationComment() : undefined | |||
documentation: type.symbol ? type.symbol.getDocumentationComment() : undefined, | |||
tags: type.symbol ? type.symbol.getJsDocTags() : undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the API may return undefined
here, so returning null
or undefined
instead of an empty array shouldn't break expectations.
@@ -73,7 +73,8 @@ | |||
"kind": "keyword" | |||
} | |||
], | |||
"documentation": [] | |||
"documentation": [], | |||
"tags": [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And now look at those billions of empty arrays peppering the ***.baseline down from here.
Expose JSDoc tags through the language service
Expose JSDoc tags through the language service
Expose JSDoc tags through the language service
Expose JSDoc tags through the language service
Expose JSDoc tags through the language service
The main motivation behind this PR is to fix UWP API documentation behavior in Dev15, which relies on a custom JSDoc tag (
@xmldoc
), and was broken between 2.0 and 2.1.The behavior of getDocumentationComment() was changed by #10671 to not include JSDoc tags anymore. I added them back as a new property.
Note the "tags" property I added to these calls will only expose the unknown tags (i.e. things that are not specially handled like
@returns,
@param
etc) ... I couldn't think of a justification to expose the known tags too (well, also I wouldn't have been able to do so without making more significant changes to the parser which I want to avoid at the moment).Fixes #11956
You might be interested @sandersn @vladima