Skip to content
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

Merged
merged 5 commits into from
Dec 12, 2016

Conversation

minestarks
Copy link
Member

@minestarks minestarks commented Dec 12, 2016

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

@msftclas
Copy link

Hi @minestarks, I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution!


It looks like you're a Microsoft contributor (Mine Yalcinalp). If you're full-time, we DON'T require a Contribution License Agreement. If you are a vendor, please DO sign the electronic Contribution License Agreement. It will take 2 minutes and there's no faxing! https://cla.microsoft.com.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: tags: quickInfo.tags || []

Copy link
Member

@sandersn sandersn left a 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 => {
Copy link
Member

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 {
Copy link
Member

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.

Copy link
Member Author

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;
Copy link
Member

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.

Copy link
Member Author

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...

Copy link
Member

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);
Copy link
Contributor

@vladima vladima Dec 12, 2016

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is type assertion necessary?

Copy link
Member Author

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) {
Copy link
Contributor

@vladima vladima Dec 12, 2016

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) {
Copy link
Contributor

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?

Copy link
Member Author

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) {
Copy link
Contributor

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

@minestarks minestarks merged commit 17d7aa8 into microsoft:release-2.1 Dec 12, 2016
@minestarks minestarks deleted the includejsdoctags branch December 12, 2016 23:29
Copy link
Contributor

@mihailik mihailik left a 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[];
Copy link
Contributor

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]) : [];
}
Copy link
Contributor

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[] = [];
Copy link
Contributor

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);
}
Copy link
Contributor

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
Copy link
Contributor

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": []
Copy link
Contributor

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.

minestarks added a commit to minestarks/TypeScript that referenced this pull request Apr 1, 2017
Expose JSDoc tags through the language service
minestarks added a commit to minestarks/TypeScript that referenced this pull request Apr 1, 2017
Expose JSDoc tags through the language service
minestarks added a commit to minestarks/TypeScript that referenced this pull request Apr 3, 2017
Expose JSDoc tags through the language service
minestarks added a commit to minestarks/TypeScript that referenced this pull request Apr 3, 2017
Expose JSDoc tags through the language service
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants