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) {
|
||||
parseErrorAtPosition(pos, 0, Diagnostics.Identifier_expected);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let preName: Identifier, postName: Identifier;
|
||||
|
|
|
@ -132,6 +132,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))();
|
||||
|
@ -2971,6 +3011,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));
|
||||
|
@ -2981,8 +3023,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);
|
||||
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();
|
||||
|
@ -3068,7 +3146,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
|
||||
|
@ -3708,9 +3786,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());
|
||||
|
@ -3724,7 +3807,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
// Add keywords if this is not a member completion list
|
||||
if (!isMemberCompletion) {
|
||||
if (!isMemberCompletion && !isJsDocTagName) {
|
||||
addRange(entries, keywordCompletions);
|
||||
}
|
||||
|
||||
|
@ -3757,6 +3840,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
|
||||
|
|
|
@ -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 {
|
||||
// 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.
|
||||
|
@ -640,7 +673,6 @@ namespace ts {
|
|||
else if (flags & SymbolFlags.TypeAlias) { return SymbolDisplayPartKind.aliasName; }
|
||||
else if (flags & SymbolFlags.Alias) { return SymbolDisplayPartKind.aliasName; }
|
||||
|
||||
|
||||
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