-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Add Intellisense to JsDoc #4283
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
Changes from all commits
0b6aeec
5e9186b
03ea38f
ffaf8e0
9dac516
2a3867a
b16536b
d1253d5
582b0aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -131,6 +131,46 @@ namespace ts { | |
| let scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); | ||
|
|
||
| let emptyArray: any[] = []; | ||
|
|
||
| const jsDocTagNames = [ | ||
| "augments", | ||
| "author", | ||
| "argument", | ||
| "borrows", | ||
| "class", | ||
| "constant", | ||
| "constructor", | ||
| "constructs", | ||
| "default", | ||
| "deprecated", | ||
| "description", | ||
| "event", | ||
| "example", | ||
| "extends", | ||
| "field", | ||
| "fileOverview", | ||
| "function", | ||
| "ignore", | ||
| "inner", | ||
| "lends", | ||
| "link", | ||
| "memberOf", | ||
| "name", | ||
| "namespace", | ||
| "param", | ||
| "private", | ||
| "property", | ||
| "public", | ||
| "requires", | ||
| "returns", | ||
| "see", | ||
| "since", | ||
| "static", | ||
| "throws", | ||
| "type", | ||
| "version" | ||
| ]; | ||
| let jsDocCompletionEntries: CompletionEntry[]; | ||
|
|
||
| function createNode(kind: SyntaxKind, pos: number, end: number, flags: NodeFlags, parent?: Node): NodeObject { | ||
| let node = <NodeObject> new (getNodeConstructor(kind))(); | ||
|
|
@@ -2918,6 +2958,8 @@ namespace ts { | |
| let sourceFile = getValidSourceFile(fileName); | ||
| let isJavaScriptFile = isJavaScript(fileName); | ||
|
|
||
| let isJsDocTagName = false; | ||
|
|
||
| let start = new Date().getTime(); | ||
| let currentToken = getTokenAtPosition(sourceFile, position); | ||
| log("getCompletionData: Get current token: " + (new Date().getTime() - start)); | ||
|
|
@@ -2928,8 +2970,44 @@ namespace ts { | |
| log("getCompletionData: Is inside comment: " + (new Date().getTime() - start)); | ||
|
|
||
| if (insideComment) { | ||
| log("Returning an empty list because completion was inside a comment."); | ||
| return undefined; | ||
| // The current position is next to the '@' sign, when no tag name being provided yet. | ||
| // Provide a full list of tag names | ||
| if (hasDocComment(sourceFile, position) && sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) { | ||
| isJsDocTagName = true; | ||
| } | ||
|
|
||
| // Completion should work inside certain JsDoc tags. For example: | ||
| // /** @type {number | string} */ | ||
| // Completion should work in the brackets | ||
| let insideJsDocTagExpression = false; | ||
| let tag = getJsDocTagAtPosition(sourceFile, position); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think this logic should be in getJsDocCompletionEntries instead.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just get the previous token and see if it's the @ token. Similar to how we look back to see if we're after a dot token?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As the previous token I got was the one in code, not in the comment. Or I can modify the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That might be preferred, though I'll defer to Cyrus on this one. |
||
| if (tag) { | ||
| if (tag.tagName.pos <= position && position <= tag.tagName.end) { | ||
| isJsDocTagName = true; | ||
| } | ||
|
|
||
| switch (tag.kind) { | ||
| case SyntaxKind.JSDocTypeTag: | ||
| case SyntaxKind.JSDocParameterTag: | ||
| case SyntaxKind.JSDocReturnTag: | ||
| let tagWithExpression = <JSDocTypeTag | JSDocParameterTag | JSDocReturnTag>tag; | ||
| if (tagWithExpression.typeExpression) { | ||
| insideJsDocTagExpression = tagWithExpression.typeExpression.pos < position && position < tagWithExpression.typeExpression.end; | ||
| } | ||
| break; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These branches are identical, just let each fall through and use a variable |
||
| } | ||
| } | ||
|
|
||
| if (isJsDocTagName) { | ||
| return { symbols: undefined, isMemberCompletion: false, isNewIdentifierLocation: false, location: undefined, isRightOfDot: false, isJsDocTagName }; | ||
| } | ||
|
|
||
| if (!insideJsDocTagExpression) { | ||
| // Proceed if the current position is in jsDoc tag expression; otherwise it is a normal | ||
| // comment or the plain text part of a jsDoc comment, so no completion should be available | ||
| log("Returning an empty list because completion was inside a regular comment or plain text part of a JsDoc comment."); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we want normal intellisense here.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? It is inside either a normal comment or the plain text part of a JsDoc
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. /**
* @type { | }
*/completion there should show you all global types, e.g. string.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, though that is when |
||
| return undefined; | ||
| } | ||
| } | ||
|
|
||
| start = new Date().getTime(); | ||
|
|
@@ -3015,7 +3093,7 @@ namespace ts { | |
|
|
||
| log("getCompletionData: Semantic work: " + (new Date().getTime() - semanticStart)); | ||
|
|
||
| return { symbols, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag) }; | ||
| return { symbols, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), isJsDocTagName }; | ||
|
|
||
| function getTypeScriptMemberSymbols(): void { | ||
| // Right of dot member completion list | ||
|
|
@@ -3656,9 +3734,14 @@ namespace ts { | |
| return undefined; | ||
| } | ||
|
|
||
| let { symbols, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot } = completionData; | ||
| let { symbols, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot, isJsDocTagName } = completionData; | ||
|
|
||
| let entries: CompletionEntry[]; | ||
| if (isJsDocTagName) { | ||
| // If the current position is a jsDoc tag name, only tag names should be provided for completion | ||
| return { isMemberCompletion: false, isNewIdentifierLocation: false, entries: getAllJsDocCompletionEntries() }; | ||
| } | ||
|
|
||
| if (isRightOfDot && isJavaScript(fileName)) { | ||
| entries = getCompletionEntriesFromSymbols(symbols); | ||
| addRange(entries, getJavaScriptCompletionEntries()); | ||
|
|
@@ -3672,7 +3755,7 @@ namespace ts { | |
| } | ||
|
|
||
| // Add keywords if this is not a member completion list | ||
| if (!isMemberCompletion) { | ||
| if (!isMemberCompletion && !isJsDocTagName) { | ||
| addRange(entries, keywordCompletions); | ||
| } | ||
|
|
||
|
|
@@ -3705,6 +3788,17 @@ namespace ts { | |
| return entries; | ||
| } | ||
|
|
||
| function getAllJsDocCompletionEntries(): CompletionEntry[] { | ||
| return jsDocCompletionEntries || (jsDocCompletionEntries = ts.map(jsDocTagNames, tagName => { | ||
| return { | ||
| name: tagName, | ||
| kind: ScriptElementKind.keyword, | ||
| kindModifiers: "", | ||
| sortText: "0", | ||
| } | ||
| })); | ||
| } | ||
|
|
||
| function createCompletionEntry(symbol: Symbol, location: Node): CompletionEntry { | ||
| // Try to get a valid display name for this symbol, if we could not find one, then ignore it. | ||
| // We would like to only show things that can be added after a dot, so for instance numeric properties can | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| ///<reference path="fourslash.ts" /> | ||
|
|
||
| // @allowNonTsExtensions: true | ||
| // @Filename: Foo.js | ||
| /////** @/*1*/ */ | ||
| ////var v1; | ||
| //// | ||
| /////** @p/*2*/ */ | ||
| ////var v2; | ||
| //// | ||
| /////** @param /*3*/ */ | ||
| ////var v3; | ||
| //// | ||
| /////** @param { n/*4*/ } bar */ | ||
| ////var v4; | ||
| //// | ||
| /////** @type { n/*5*/ } */ | ||
| ////var v5; | ||
| //// | ||
| ////// @/*6*/ | ||
| ////var v6; | ||
| //// | ||
| ////// @pa/*7*/ | ||
| ////var v7; | ||
| //// | ||
| /////** @param { n/*8*/ } */ | ||
| ////var v8; | ||
| //// | ||
| /////** @return { n/*9*/ } */ | ||
| ////var v9; | ||
|
|
||
| goTo.marker('1'); | ||
| verify.completionListContains("constructor"); | ||
| verify.completionListContains("param"); | ||
| verify.completionListContains("type"); | ||
|
|
||
| goTo.marker('2'); | ||
| verify.completionListContains("constructor"); | ||
| verify.completionListContains("param"); | ||
| verify.completionListContains("type"); | ||
|
|
||
| goTo.marker('3'); | ||
| verify.completionListIsEmpty(); | ||
|
|
||
| goTo.marker('4'); | ||
| verify.completionListContains('number'); | ||
|
|
||
| goTo.marker('5'); | ||
| verify.completionListContains('number'); | ||
|
|
||
| goTo.marker('6'); | ||
| verify.completionListIsEmpty(); | ||
|
|
||
| goTo.marker('7'); | ||
| verify.completionListIsEmpty(); | ||
|
|
||
| goTo.marker('8'); | ||
| verify.completionListContains('number'); | ||
|
|
||
| goTo.marker('9'); | ||
| verify.completionListContains('number'); | ||
|
|
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 wonder if you could just calculate it regardless of whether you're in a
.jsfile. Check with @mhegazy and @CyrusNajmabadi.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.
What do you mean "calculate" it? For now the main difference between js file and ts file is that
node.jsDocCommentis only useful in js filesThere 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 mean that if you're going to just map over it once, do it here regardless of whether it's in a TypeScript or JavaScript file. It would be easier to read than the current lazy initialization, but the way it's done here is fine too.