Completion list in the type expression should show types

This commit is contained in:
Sheetal Nandi 2017-06-01 16:20:30 -07:00
parent 893ba1de15
commit ee5d1d33c1
7 changed files with 446 additions and 25 deletions

View file

@ -822,8 +822,8 @@ namespace FourSlash {
function filterByTextOrDocumentation(entry: ts.CompletionEntry) {
const details = that.getCompletionEntryDetails(entry.name);
const documentation = ts.displayPartsToString(details.documentation);
const text = ts.displayPartsToString(details.displayParts);
const documentation = details && ts.displayPartsToString(details.documentation);
const text = details && ts.displayPartsToString(details.displayParts);
// If any of the expected values are undefined, assume that users don't
// care about them.
@ -860,6 +860,9 @@ namespace FourSlash {
if (expectedKind) {
error += "Expected kind: " + expectedKind + " to equal: " + filterCompletions[0].kind + ".";
}
else {
error += "kind: " + filterCompletions[0].kind + ".";
}
if (replacementSpan) {
const spanText = filterCompletions[0].replacementSpan ? stringify(filterCompletions[0].replacementSpan) : undefined;
error += "Expected replacement span: " + stringify(replacementSpan) + " to equal: " + spanText + ".";

View file

@ -354,7 +354,7 @@ namespace ts.Completions {
let requestJsDocTag = false;
let start = timestamp();
const currentToken = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); // TODO: GH#15853
let currentToken = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); // TODO: GH#15853
log("getCompletionData: Get current token: " + (timestamp() - start));
start = timestamp();
@ -362,6 +362,7 @@ namespace ts.Completions {
const insideComment = isInComment(sourceFile, position, currentToken);
log("getCompletionData: Is inside comment: " + (timestamp() - start));
let insideJsDocTagTypeExpression = false;
if (insideComment) {
if (hasDocComment(sourceFile, position)) {
// The current position is next to the '@' sign, when no tag name being provided yet.
@ -394,22 +395,20 @@ namespace ts.Completions {
// Completion should work inside certain JsDoc tags. For example:
// /** @type {number | string} */
// Completion should work in the brackets
let insideJsDocTagExpression = false;
const tag = getJsDocTagAtPosition(sourceFile, position);
if (tag) {
if (tag.tagName.pos <= position && position <= tag.tagName.end) {
requestJsDocTagName = true;
}
switch (tag.kind) {
case SyntaxKind.JSDocTypeTag:
case SyntaxKind.JSDocParameterTag:
case SyntaxKind.JSDocReturnTag:
const tagWithExpression = <JSDocTypeTag | JSDocParameterTag | JSDocReturnTag>tag;
if (tagWithExpression.typeExpression) {
insideJsDocTagExpression = tagWithExpression.typeExpression.pos < position && position < tagWithExpression.typeExpression.end;
}
break;
if (isTagWithTypeExpression(tag) && tag.typeExpression) {
currentToken = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ true);
if (!currentToken ||
(!isDeclarationName(currentToken) &&
(currentToken.parent.kind !== SyntaxKind.JSDocPropertyTag ||
(<JSDocPropertyTag>currentToken.parent).name !== currentToken))) {
// Use as type location if inside tag's type expression
insideJsDocTagTypeExpression = isCurrentlyEditingNode(tag.typeExpression);
}
}
}
@ -417,7 +416,7 @@ namespace ts.Completions {
return { symbols: undefined, isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, location: undefined, isRightOfDot: false, requestJsDocTagName, requestJsDocTag, hasFilteredClassMemberKeywords: false };
}
if (!insideJsDocTagExpression) {
if (!insideJsDocTagTypeExpression) {
// 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.");
@ -426,7 +425,7 @@ namespace ts.Completions {
}
start = timestamp();
const previousToken = findPrecedingToken(position, sourceFile);
const previousToken = findPrecedingToken(position, sourceFile, /*startNode*/ undefined, insideJsDocTagTypeExpression);
log("getCompletionData: Get previous token 1: " + (timestamp() - start));
// The decision to provide completion depends on the contextToken, which is determined through the previousToken.
@ -437,7 +436,7 @@ namespace ts.Completions {
// Skip this partial identifier and adjust the contextToken to the token that precedes it.
if (contextToken && position <= contextToken.end && isWord(contextToken.kind)) {
const start = timestamp();
contextToken = findPrecedingToken(contextToken.getFullStart(), sourceFile);
contextToken = findPrecedingToken(contextToken.getFullStart(), sourceFile, /*startNode*/ undefined, insideJsDocTagTypeExpression);
log("getCompletionData: Get previous token 2: " + (timestamp() - start));
}
@ -449,7 +448,7 @@ namespace ts.Completions {
let isRightOfOpenTag = false;
let isStartingCloseTag = false;
let location = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ false); // TODO: GH#15853
let location = getTouchingPropertyName(sourceFile, position, insideJsDocTagTypeExpression); // TODO: GH#15853
if (contextToken) {
// Bail out if this is a known invalid completion location
if (isCompletionListBlocker(contextToken)) {
@ -553,6 +552,20 @@ namespace ts.Completions {
return { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), requestJsDocTagName, requestJsDocTag, hasFilteredClassMemberKeywords };
type JSDocTagWithTypeExpression = JSDocAugmentsTag | JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag;
function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression {
switch (tag.kind) {
case SyntaxKind.JSDocAugmentsTag:
case SyntaxKind.JSDocParameterTag:
case SyntaxKind.JSDocPropertyTag:
case SyntaxKind.JSDocReturnTag:
case SyntaxKind.JSDocTypeTag:
case SyntaxKind.JSDocTypedefTag:
return true;
}
}
function getTypeScriptMemberSymbols(): void {
// Right of dot member completion list
isGlobalCompletion = false;
@ -560,7 +573,7 @@ namespace ts.Completions {
isNewIdentifierLocation = false;
// Since this is qualified name check its a type node location
const isTypeLocation = isPartOfTypeNode(node.parent);
const isTypeLocation = isPartOfTypeNode(node.parent) || insideJsDocTagTypeExpression;
const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node);
if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression) {
let symbol = typeChecker.getSymbolAtLocation(node);
@ -722,8 +735,9 @@ namespace ts.Completions {
return !!(symbol.flags & SymbolFlags.Namespace);
}
if (!isContextTokenValueLocation(contextToken) &&
(isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken))) {
if (insideJsDocTagTypeExpression ||
(!isContextTokenValueLocation(contextToken) &&
(isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken)))) {
// Its a type, but you can reach it by namespace.type as well
return symbolCanbeReferencedAtTypeLocation(symbol);
}
@ -770,14 +784,16 @@ namespace ts.Completions {
symbol = typeChecker.getAliasedSymbol(symbol);
}
if (symbol.flags & SymbolFlags.Type) {
return true;
}
if (symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule)) {
const exportedSymbols = typeChecker.getExportsOfModule(symbol);
// If the exported symbols contains type,
// symbol can be referenced at locations where type is allowed
return forEach(exportedSymbols, symbolCanbeReferencedAtTypeLocation);
}
return !!(symbol.flags & (SymbolFlags.NamespaceModule | SymbolFlags.Type));
}
/**

View file

@ -707,7 +707,7 @@ namespace ts {
}
}
export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node): Node {
export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node, includeJsDocComment?: boolean): Node {
return find(startNode || sourceFile);
function findRightmostToken(n: Node): Node {
@ -738,7 +738,7 @@ namespace ts {
// NOTE: JsxText is a weird kind of node that can contain only whitespaces (since they are not counted as trivia).
// if this is the case - then we should assume that token in question is located in previous child.
if (position < child.end && (nodeHasTokens(child) || child.kind === SyntaxKind.JsxText)) {
const start = child.getStart(sourceFile);
const start = child.getStart(sourceFile, includeJsDocComment);
const lookInPreviousChild =
(start >= position) || // cursor in the leading trivia
(child.kind === SyntaxKind.JsxText && start === child.end); // whitespace only JsxText

View file

@ -0,0 +1,70 @@
/// <reference path="fourslash.ts"/>
// @allowNonTsExtensions: true
// @Filename: jsFileJsdocTypedefTagTypeExpressionCompletion_typedef.js
//// /**
//// * @typedef {/*1*/string | /*2*/number} T.NumberLike
//// * @typedef {{/*propertyName*/age: /*3*/number}} T.People
//// * @typedef {string | number} T.O.Q.NumberLike
//// * @type {/*4*/T./*1TypeMember*/NumberLike}
//// */
//// var x;
//// /** @type {/*5*/T./*2TypeMember*/O.Q.NumberLike} */
//// var x1;
//// /** @type {/*6*/T./*3TypeMember*/People} */
//// var x1;
//// /*globalValue*/
interface VeriferCompletionsInJsDoc {
verifyType(symbol: string, kind: string): void;
verifyValue(symbol: string, kind: string): void;
verifyTypeMember(symbol: string, kind: string): void;
}
function verifyCompletionsInJsDocType(marker: string, { verifyType, verifyValue, verifyTypeMember }: VeriferCompletionsInJsDoc) {
goTo.marker(marker);
verifyType("T", "module");
// TODO: May be filter keywords based on context
//verifyType("string", "keyword");
//verifyType("number", "keyword");
verifyValue("x", "var");
verifyValue("x1", "var");
verifyTypeMember("NumberLike", "type");
verifyTypeMember("People", "type");
verifyTypeMember("O", "module");
}
function verifySymbolPresentWithKind(symbol: string, kind: string) {
return verify.completionListContains(symbol, /*text*/ undefined, /*documentation*/ undefined, kind);
}
function verifySymbolPresentWithWarning(symbol: string) {
return verifySymbolPresentWithKind(symbol, "warning");
}
for (let i = 1; i <= 6; i++) {
verifyCompletionsInJsDocType(i.toString(), {
verifyType: verifySymbolPresentWithKind,
verifyValue: verifySymbolPresentWithWarning,
verifyTypeMember: verifySymbolPresentWithWarning,
});
}
verifyCompletionsInJsDocType("globalValue", {
verifyType: verifySymbolPresentWithWarning,
verifyValue: verifySymbolPresentWithKind,
verifyTypeMember: verifySymbolPresentWithWarning,
});
for (let i = 1; i <= 3; i++) {
verifyCompletionsInJsDocType(i.toString() + "TypeMember", {
verifyType: verifySymbolPresentWithWarning,
verifyValue: verifySymbolPresentWithWarning,
verifyTypeMember: verifySymbolPresentWithKind,
});
}
goTo.marker("propertyName");
verify.completionListIsEmpty();

View file

@ -0,0 +1,65 @@
/// <reference path="fourslash.ts"/>
// @allowNonTsExtensions: true
// @Filename: jsFileJsdocTypedefTagTypeExpressionCompletion2_typedef.js
//// class Foo {
//// constructor(value: number) { this.property1 = "hello"; }
//// static method1() {}
//// method3() { return 3; }
//// /**
//// * @param {string} foo A value.
//// * @returns {number} Another value
//// * @mytag
//// */
//// method4(foo) { return 3; }
//// }
//// /**
//// * @type { /*type*/Foo }
//// */
////var x;
/////*globalValue*/
////x./*valueMember*/
interface VeriferCompletionsInJsDoc {
verifyValueOrType(symbol: string, kind: string): void;
verifyValue(symbol: string, kind: string): void;
verifyValueMember(symbol: string, kind: string): void;
}
function verifyCompletionsInJsDocType(marker: string, { verifyValueOrType, verifyValue, verifyValueMember }: VeriferCompletionsInJsDoc) {
goTo.marker(marker);
verifyValueOrType("Foo", "class");
verifyValue("x", "var");
verifyValueMember("property1", "property");
verifyValueMember("method3", "method");
verifyValueMember("method4", "method");
verifyValueMember("foo", "warning");
}
function verifySymbolPresentWithKind(symbol: string, kind: string) {
return verify.completionListContains(symbol, /*text*/ undefined, /*documentation*/ undefined, kind);
}
function verifySymbolPresentWithWarning(symbol: string) {
return verifySymbolPresentWithKind(symbol, "warning");
}
verifyCompletionsInJsDocType("type", {
verifyValueOrType: verifySymbolPresentWithKind,
verifyValue: verifySymbolPresentWithWarning,
verifyValueMember: verifySymbolPresentWithWarning,
});
verifyCompletionsInJsDocType("globalValue", {
verifyValueOrType: verifySymbolPresentWithKind,
verifyValue: verifySymbolPresentWithKind,
verifyValueMember: verifySymbolPresentWithWarning,
});
verifyCompletionsInJsDocType("valueMember", {
verifyValueOrType: verifySymbolPresentWithWarning,
verifyValue: verifySymbolPresentWithWarning,
verifyValueMember: verifySymbolPresentWithKind,
});

View file

@ -0,0 +1,127 @@
/// <reference path="fourslash.ts"/>
// @allowNonTsExtensions: true
// @Filename: jsFileJsdocTypedefTagTypeExpressionCompletion3_typedef.js
//// /**
//// * @typedef {{ age: number }} Foo.Namespace.SomeType
//// */
//// class Foo {
//// constructor(value) { this.property1 = "hello"; }
//// static method1() {}
//// method3() { return 3; }
//// /**
//// * @param {string} foo A value.
//// * @returns {number} Another value
//// * @mytag
//// */
//// method4(foo) { return 3; }
//// }
//// /**
//// * @type { /*type1*/Foo./*typeFooMember*/Namespace./*NamespaceMember*/SomeType }
//// */
////var x;
/////*globalValue*/
////x./*valueMemberOfSomeType*/
//// /**
//// * @type { /*type2*/Foo }
//// */
////var x1;
////x1./*valueMemberOfFooInstance*/;
////Foo./*valueMemberOfFoo*/;
function verifySymbolPresentWithKind(symbol: string, kind: string) {
return verify.completionListContains(symbol, /*text*/ undefined, /*documentation*/ undefined, kind);
}
function verifySymbolPresentWithWarning(symbol: string) {
return verifySymbolPresentWithKind(symbol, "warning");
}
for (const marker of ["type1", "type2"]) {
goTo.marker(marker);
verifySymbolPresentWithKind("Foo", "class");
verifySymbolPresentWithWarning("Namespace");
verifySymbolPresentWithWarning("SomeType");
verifySymbolPresentWithWarning("x");
verifySymbolPresentWithWarning("x1");
verifySymbolPresentWithWarning("method1");
verifySymbolPresentWithWarning("property1");
verifySymbolPresentWithWarning("method3");
verifySymbolPresentWithWarning("method4");
verifySymbolPresentWithWarning("foo");
}
goTo.marker("typeFooMember");
verifySymbolPresentWithWarning("Foo");
verifySymbolPresentWithKind("Namespace", "module");
verifySymbolPresentWithWarning("SomeType");
verifySymbolPresentWithWarning("x");
verifySymbolPresentWithWarning("x1");
verifySymbolPresentWithWarning("method1");
verifySymbolPresentWithWarning("property1");
verifySymbolPresentWithWarning("method3");
verifySymbolPresentWithWarning("method4");
verifySymbolPresentWithWarning("foo");
goTo.marker("NamespaceMember");
verifySymbolPresentWithWarning("Foo");
verifySymbolPresentWithWarning("Namespace");
verifySymbolPresentWithKind("SomeType", "type");
verifySymbolPresentWithWarning("x");
verifySymbolPresentWithWarning("x1");
verifySymbolPresentWithWarning("method1");
verifySymbolPresentWithWarning("property1");
verifySymbolPresentWithWarning("method3");
verifySymbolPresentWithWarning("method4");
verifySymbolPresentWithWarning("foo");
goTo.marker("globalValue");
verifySymbolPresentWithKind("Foo", "class");
verifySymbolPresentWithWarning("Namespace");
verifySymbolPresentWithWarning("SomeType");
verifySymbolPresentWithKind("x", "var");
verifySymbolPresentWithKind("x1", "var");
verifySymbolPresentWithWarning("method1");
verifySymbolPresentWithWarning("property1");
verifySymbolPresentWithWarning("method3");
verifySymbolPresentWithWarning("method4");
verifySymbolPresentWithWarning("foo");
goTo.marker("valueMemberOfSomeType");
verifySymbolPresentWithWarning("Foo");
verifySymbolPresentWithWarning("Namespace");
verifySymbolPresentWithWarning("SomeType");
verifySymbolPresentWithWarning("x");
verifySymbolPresentWithWarning("x1");
verifySymbolPresentWithWarning("method1");
verifySymbolPresentWithWarning("property1");
verifySymbolPresentWithWarning("method3");
verifySymbolPresentWithWarning("method4");
verifySymbolPresentWithWarning("foo");
goTo.marker("valueMemberOfFooInstance");
verifySymbolPresentWithWarning("Foo");
verifySymbolPresentWithWarning("Namespace");
verifySymbolPresentWithWarning("SomeType");
verifySymbolPresentWithWarning("x");
verifySymbolPresentWithWarning("x1");
verifySymbolPresentWithWarning("method1");
verifySymbolPresentWithKind("property1", "property");
verifySymbolPresentWithKind("method3", "method");
verifySymbolPresentWithKind("method4", "method");
verifySymbolPresentWithKind("foo", "warning");
goTo.marker("valueMemberOfFoo");
verifySymbolPresentWithWarning("Foo");
verifySymbolPresentWithWarning("Namespace");
verifySymbolPresentWithWarning("SomeType");
verifySymbolPresentWithWarning("x");
verifySymbolPresentWithWarning("x1");
verifySymbolPresentWithKind("method1", "method");
verifySymbolPresentWithWarning("property1");
verifySymbolPresentWithWarning("method3");
verifySymbolPresentWithWarning("method4");
verifySymbolPresentWithWarning("foo");

View file

@ -0,0 +1,140 @@
/// <reference path="fourslash.ts"/>
////interface I {
//// age: number;
////}
//// class Foo {
//// property1: string;
//// constructor(value: number) { this.property1 = "hello"; }
//// static method1() {}
//// method3(): number { return 3; }
//// /**
//// * @param {string} foo A value.
//// * @returns {number} Another value
//// * @mytag
//// */
//// method4(foo: string) { return 3; }
//// }
//// namespace Foo.Namespace { export interface SomeType { age2: number } }
//// /**
//// * @type { /*type1*/Foo./*typeFooMember*/Namespace./*NamespaceMember*/SomeType }
//// */
////var x;
/////*globalValue*/
////x./*valueMemberOfSomeType*/
////var x1: Foo;
////x1./*valueMemberOfFooInstance*/;
////Foo./*valueMemberOfFoo*/;
//// /**
//// * @type { {/*propertyName*/ageX: number} }
//// */
////var y;
function verifySymbolPresentWithKind(symbol: string, kind: string) {
return verify.completionListContains(symbol, /*text*/ undefined, /*documentation*/ undefined, kind);
}
function verifySymbolNotPresent(symbol: string) {
return verify.not.completionListContains(symbol);
}
goTo.marker("type1");
verifySymbolPresentWithKind("Foo", "class");
verifySymbolPresentWithKind("I", "interface");
verifySymbolNotPresent("Namespace");
verifySymbolNotPresent("SomeType");
verifySymbolNotPresent("x");
verifySymbolNotPresent("x1");
verifySymbolNotPresent("y");
verifySymbolNotPresent("method1");
verifySymbolNotPresent("property1");
verifySymbolNotPresent("method3");
verifySymbolNotPresent("method4");
verifySymbolNotPresent("foo");
goTo.marker("typeFooMember");
verifySymbolNotPresent("Foo");
verifySymbolNotPresent("I");
verifySymbolPresentWithKind("Namespace", "module");
verifySymbolNotPresent("SomeType");
verifySymbolNotPresent("x");
verifySymbolNotPresent("x1");
verifySymbolNotPresent("y");
verifySymbolNotPresent("method1");
verifySymbolNotPresent("property1");
verifySymbolNotPresent("method3");
verifySymbolNotPresent("method4");
verifySymbolNotPresent("foo");
goTo.marker("NamespaceMember");
verifySymbolNotPresent("Foo");
verifySymbolNotPresent("I");
verifySymbolNotPresent("Namespace");
verifySymbolPresentWithKind("SomeType", "interface");
verifySymbolNotPresent("x");
verifySymbolNotPresent("x1");
verifySymbolNotPresent("y");
verifySymbolNotPresent("method1");
verifySymbolNotPresent("property1");
verifySymbolNotPresent("method3");
verifySymbolNotPresent("method4");
verifySymbolNotPresent("foo");
goTo.marker("globalValue");
verifySymbolPresentWithKind("Foo", "class");
verifySymbolNotPresent("I");
verifySymbolNotPresent("Namespace");
verifySymbolNotPresent("SomeType");
verifySymbolPresentWithKind("x", "var");
verifySymbolPresentWithKind("x1", "var");
verifySymbolPresentWithKind("y", "var");
verifySymbolNotPresent("method1");
verifySymbolNotPresent("property1");
verifySymbolNotPresent("method3");
verifySymbolNotPresent("method4");
verifySymbolNotPresent("foo");
goTo.marker("valueMemberOfSomeType");
verifySymbolNotPresent("Foo");
verifySymbolNotPresent("I");
verifySymbolNotPresent("Namespace");
verifySymbolNotPresent("SomeType");
verifySymbolNotPresent("x");
verifySymbolNotPresent("x1");
verifySymbolNotPresent("y");
verifySymbolNotPresent("method1");
verifySymbolNotPresent("property1");
verifySymbolNotPresent("method3");
verifySymbolNotPresent("method4");
verifySymbolNotPresent("foo");
goTo.marker("valueMemberOfFooInstance");
verifySymbolNotPresent("Foo");
verifySymbolNotPresent("I");
verifySymbolNotPresent("Namespace");
verifySymbolNotPresent("SomeType");
verifySymbolNotPresent("x");
verifySymbolNotPresent("x1");
verifySymbolNotPresent("y");
verifySymbolNotPresent("method1");
verifySymbolPresentWithKind("property1", "property");
verifySymbolPresentWithKind("method3", "method");
verifySymbolPresentWithKind("method4", "method");
verifySymbolNotPresent("foo");
goTo.marker("valueMemberOfFoo");
verifySymbolNotPresent("Foo");
verifySymbolNotPresent("I");
verifySymbolNotPresent("Namespace");
verifySymbolNotPresent("SomeType");
verifySymbolNotPresent("x");
verifySymbolNotPresent("x1");
verifySymbolNotPresent("y");
verifySymbolPresentWithKind("method1", "method");
verifySymbolNotPresent("property1");
verifySymbolNotPresent("method3");
verifySymbolNotPresent("method4");
verifySymbolNotPresent("foo");
goTo.marker("propertyName");
verify.completionListIsEmpty();