Merge pull request #4283 from zhengbli/JsDocIntellisense2
Add Intellisense to JsDoc
This commit is contained in:
commit
ecaf16df4b
|
@ -5831,7 +5831,6 @@ namespace ts {
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
parseErrorAtPosition(pos, 0, Diagnostics.Identifier_expected);
|
parseErrorAtPosition(pos, 0, Diagnostics.Identifier_expected);
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let preName: Identifier, postName: Identifier;
|
let preName: Identifier, postName: Identifier;
|
||||||
|
|
|
@ -132,6 +132,46 @@ namespace ts {
|
||||||
let scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true);
|
let scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true);
|
||||||
|
|
||||||
let emptyArray: any[] = [];
|
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 {
|
function createNode(kind: SyntaxKind, pos: number, end: number, flags: NodeFlags, parent?: Node): NodeObject {
|
||||||
let node = <NodeObject> new (getNodeConstructor(kind))();
|
let node = <NodeObject> new (getNodeConstructor(kind))();
|
||||||
|
@ -2971,6 +3011,8 @@ namespace ts {
|
||||||
let sourceFile = getValidSourceFile(fileName);
|
let sourceFile = getValidSourceFile(fileName);
|
||||||
let isJavaScriptFile = isJavaScript(fileName);
|
let isJavaScriptFile = isJavaScript(fileName);
|
||||||
|
|
||||||
|
let isJsDocTagName = false;
|
||||||
|
|
||||||
let start = new Date().getTime();
|
let start = new Date().getTime();
|
||||||
let currentToken = getTokenAtPosition(sourceFile, position);
|
let currentToken = getTokenAtPosition(sourceFile, position);
|
||||||
log("getCompletionData: Get current token: " + (new Date().getTime() - start));
|
log("getCompletionData: Get current token: " + (new Date().getTime() - start));
|
||||||
|
@ -2981,8 +3023,44 @@ namespace ts {
|
||||||
log("getCompletionData: Is inside comment: " + (new Date().getTime() - start));
|
log("getCompletionData: Is inside comment: " + (new Date().getTime() - start));
|
||||||
|
|
||||||
if (insideComment) {
|
if (insideComment) {
|
||||||
log("Returning an empty list because completion was inside a comment.");
|
// The current position is next to the '@' sign, when no tag name being provided yet.
|
||||||
return undefined;
|
// 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);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start = new Date().getTime();
|
start = new Date().getTime();
|
||||||
|
@ -3068,7 +3146,7 @@ namespace ts {
|
||||||
|
|
||||||
log("getCompletionData: Semantic work: " + (new Date().getTime() - semanticStart));
|
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 {
|
function getTypeScriptMemberSymbols(): void {
|
||||||
// Right of dot member completion list
|
// Right of dot member completion list
|
||||||
|
@ -3708,9 +3786,14 @@ namespace ts {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { symbols, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot } = completionData;
|
let { symbols, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot, isJsDocTagName } = completionData;
|
||||||
|
|
||||||
let entries: CompletionEntry[];
|
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)) {
|
if (isRightOfDot && isJavaScript(fileName)) {
|
||||||
entries = getCompletionEntriesFromSymbols(symbols);
|
entries = getCompletionEntriesFromSymbols(symbols);
|
||||||
addRange(entries, getJavaScriptCompletionEntries());
|
addRange(entries, getJavaScriptCompletionEntries());
|
||||||
|
@ -3724,7 +3807,7 @@ namespace ts {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add keywords if this is not a member completion list
|
// Add keywords if this is not a member completion list
|
||||||
if (!isMemberCompletion) {
|
if (!isMemberCompletion && !isJsDocTagName) {
|
||||||
addRange(entries, keywordCompletions);
|
addRange(entries, keywordCompletions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3757,6 +3840,17 @@ namespace ts {
|
||||||
return entries;
|
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 {
|
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.
|
// 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
|
// We would like to only show things that can be added after a dot, so for instance numeric properties can
|
||||||
|
|
|
@ -469,6 +469,39 @@ namespace ts {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the corresponding JSDocTag node if the position is in a jsDoc comment
|
||||||
|
*/
|
||||||
|
export function getJsDocTagAtPosition(sourceFile: SourceFile, position: number): JSDocTag {
|
||||||
|
let node = ts.getTokenAtPosition(sourceFile, position);
|
||||||
|
if (isToken(node)) {
|
||||||
|
switch (node.kind) {
|
||||||
|
case SyntaxKind.VarKeyword:
|
||||||
|
case SyntaxKind.LetKeyword:
|
||||||
|
case SyntaxKind.ConstKeyword:
|
||||||
|
// if the current token is var, let or const, skip the VariableDeclarationList
|
||||||
|
node = node.parent === undefined ? undefined : node.parent.parent;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
node = node.parent;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
let jsDocComment = node.jsDocComment;
|
||||||
|
if (jsDocComment) {
|
||||||
|
for (let tag of jsDocComment.tags) {
|
||||||
|
if (tag.pos <= position && position <= tag.end) {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function nodeHasTokens(n: Node): boolean {
|
function nodeHasTokens(n: Node): boolean {
|
||||||
// If we have a token or node that has a non-zero width, it must have tokens.
|
// If we have a token or node that has a non-zero width, it must have tokens.
|
||||||
// Note, that getWidth() does not take trivia into account.
|
// Note, that getWidth() does not take trivia into account.
|
||||||
|
@ -640,7 +673,6 @@ namespace ts {
|
||||||
else if (flags & SymbolFlags.TypeAlias) { return SymbolDisplayPartKind.aliasName; }
|
else if (flags & SymbolFlags.TypeAlias) { return SymbolDisplayPartKind.aliasName; }
|
||||||
else if (flags & SymbolFlags.Alias) { return SymbolDisplayPartKind.aliasName; }
|
else if (flags & SymbolFlags.Alias) { return SymbolDisplayPartKind.aliasName; }
|
||||||
|
|
||||||
|
|
||||||
return SymbolDisplayPartKind.text;
|
return SymbolDisplayPartKind.text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
62
tests/cases/fourslash/completionInJsDoc.ts
Normal file
62
tests/cases/fourslash/completionInJsDoc.ts
Normal file
|
@ -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');
|
||||||
|
|
Loading…
Reference in a new issue