Add support for completion in string literals

This commit is contained in:
Mohamed Hegazy 2016-05-02 16:30:50 -07:00
parent 0a277a1c60
commit 060f2a8563
4 changed files with 158 additions and 15 deletions

View File

@ -1032,10 +1032,10 @@ namespace ts {
getCancellationToken?(): HostCancellationToken;
getCurrentDirectory(): string;
getDefaultLibFileName(options: CompilerOptions): string;
log? (s: string): void;
trace? (s: string): void;
error? (s: string): void;
useCaseSensitiveFileNames? (): boolean;
log?(s: string): void;
trace?(s: string): void;
error?(s: string): void;
useCaseSensitiveFileNames?(): boolean;
/*
* LS host can optionally implement this method if it wants to be completely in charge of module name resolution.
@ -2416,7 +2416,7 @@ namespace ts {
}
// should be start of dependency list
if (token !== SyntaxKind.OpenBracketToken) {
if (token !== SyntaxKind.OpenBracketToken) {
return true;
}
@ -3912,10 +3912,15 @@ namespace ts {
}
}
function getCompletionsAtPosition(fileName: string, position: number): CompletionInfo {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
if (isInString(sourceFile, position)) {
return getStringLiteralCompletionEntries(sourceFile, position);
}
const completionData = getCompletionData(fileName, position);
if (!completionData) {
return undefined;
@ -3928,12 +3933,10 @@ namespace ts {
return { isMemberCompletion: false, isNewIdentifierLocation: false, entries: getAllJsDocCompletionEntries() };
}
const sourceFile = getValidSourceFile(fileName);
const entries: CompletionEntry[] = [];
if (isSourceFileJavaScript(sourceFile)) {
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries);
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ false);
addRange(entries, getJavaScriptCompletionEntries(sourceFile, location.pos, uniqueNames));
}
else {
@ -3957,7 +3960,7 @@ namespace ts {
}
}
getCompletionEntriesFromSymbols(symbols, entries);
getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true);
}
// Add keywords if this is not a member completion list
@ -4007,11 +4010,11 @@ namespace ts {
}));
}
function createCompletionEntry(symbol: Symbol, location: Node): CompletionEntry {
function createCompletionEntry(symbol: Symbol, location: Node, performCharacterChecks: boolean): 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
// not be accessed with a dot (a.1 <- invalid)
const displayName = getCompletionEntryDisplayNameForSymbol(symbol, program.getCompilerOptions().target, /*performCharacterChecks*/ true, location);
const displayName = getCompletionEntryDisplayNameForSymbol(symbol, program.getCompilerOptions().target, performCharacterChecks, location);
if (!displayName) {
return undefined;
}
@ -4032,12 +4035,12 @@ namespace ts {
};
}
function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[]): Map<string> {
function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[], location: Node, performCharacterChecks: boolean): Map<string> {
const start = new Date().getTime();
const uniqueNames: Map<string> = {};
if (symbols) {
for (const symbol of symbols) {
const entry = createCompletionEntry(symbol, location);
const entry = createCompletionEntry(symbol, location, performCharacterChecks);
if (entry) {
const id = escapeIdentifier(entry.name);
if (!lookUp(uniqueNames, id)) {
@ -4051,6 +4054,91 @@ namespace ts {
log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (new Date().getTime() - start));
return uniqueNames;
}
function getStringLiteralCompletionEntries(sourceFile: SourceFile, position: number) {
const node = findPrecedingToken(position, sourceFile);
if (!node || node.kind !== SyntaxKind.StringLiteral) {
return undefined;
}
isNameOfExternalModuleImportOrDeclaration
const argumentInfo = SignatureHelp.getContainingArgumentInfo(node, position, sourceFile);
if (argumentInfo) {
return getStringLiteralCompletionEntriesFromCallExpression(argumentInfo);
}
else if (isElementAccessExpression(node.parent) && node.parent.argumentExpression === node) {
return getStringLiteralCompletionEntriesFromElementAccess(node.parent);
}
else {
return getStringLiteralCompletionEntriesFromContextualType(<StringLiteral>node);
}
}
function getStringLiteralCompletionEntriesFromCallExpression(argumentInfo: SignatureHelp.ArgumentListInfo) {
const typeChecker = program.getTypeChecker();
const candidates: Signature[] = [];
const entries: CompletionEntry[] = [];
typeChecker.getResolvedSignature(argumentInfo.invocation, candidates);
for (const candidate of candidates) {
if (candidate.parameters.length > argumentInfo.argumentIndex) {
const parameter = candidate.parameters[argumentInfo.argumentIndex];
addStringLiteralCompletionsFromType(typeChecker.getTypeAtLocation(parameter.valueDeclaration), entries);
}
}
if (entries.length) {
return { isMemberCompletion: false, isNewIdentifierLocation: true, entries: entries };
}
return undefined;
}
function getStringLiteralCompletionEntriesFromElementAccess(node: ElementAccessExpression) {
const typeChecker = program.getTypeChecker();
const type = typeChecker.getTypeAtLocation(node.expression);
const entries: CompletionEntry[] = [];
if (type) {
getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, node, /*performCharacterChecks*/false);
if (entries.length) {
return { isMemberCompletion: true, isNewIdentifierLocation: true, entries: entries };
}
}
return undefined;
}
function getStringLiteralCompletionEntriesFromContextualType(node: StringLiteral) {
const typeChecker = program.getTypeChecker();
const type = typeChecker.getContextualType(node);
if (type) {
const entries: CompletionEntry[] = [];
addStringLiteralCompletionsFromType(type, entries);
if (entries.length) {
return { isMemberCompletion: false, isNewIdentifierLocation: false, entries: entries };
}
}
return undefined;
}
function addStringLiteralCompletionsFromType(type: Type, result: CompletionEntry[]): void {
if (!type) {
return;
}
if (type.flags & TypeFlags.Union) {
forEach((<UnionType>type).types, t => addStringLiteralCompletionsFromType(t, result));
}
else {
if (type.flags & TypeFlags.StringLiteral) {
result.push({
name: (<StringLiteralType>type).text,
kindModifiers: ScriptElementKindModifier.none,
kind: ScriptElementKind.variableElement,
sortText: "0"
});
}
}
}
}
function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails {
@ -4215,7 +4303,7 @@ namespace ts {
// try get the call/construct signature from the type if it matches
let callExpression: CallExpression;
if (location.kind === SyntaxKind.CallExpression || location.kind === SyntaxKind.NewExpression) {
callExpression = <CallExpression> location;
callExpression = <CallExpression>location;
}
else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) {
callExpression = <CallExpression>location.parent;

View File

@ -0,0 +1,15 @@
/// <reference path='fourslash.ts'/>
////type Options = "Option 1" | "Option 2" | "Option 3";
////var x: Options = "/*1*/Option 3";
////
////function f(a: Options) { };
////f("/*2*/
goTo.marker('1');
verify.completionListContains("Option 1");
verify.memberListCount(3);
goTo.marker('2');
verify.completionListContains("Option 2");
verify.memberListCount(3);

View File

@ -0,0 +1,20 @@
/// <reference path='fourslash.ts'/>
////var o = {
//// foo() { },
//// bar: 0,
//// "some other name": 1
////};
////
////o["/*1*/bar"];
////o["/*2*/
goTo.marker('1');
verify.completionListContains("foo");
verify.completionListAllowsNewIdentifier();
verify.memberListCount(3);
goTo.marker('2');
verify.completionListContains("some other name");
verify.completionListAllowsNewIdentifier();
verify.memberListCount(3);

View File

@ -0,0 +1,20 @@
/// <reference path='fourslash.ts'/>
////declare function f(a: "A", b: number): void;
////declare function f(a: "B", b: number): void;
////declare function f(a: "C", b: number): void;
////declare function f(a: string, b: number): void;
////
////f("/*1*/C", 2);
////
////f("/*2*/
goTo.marker('1');
verify.completionListContains("A");
verify.completionListAllowsNewIdentifier();
verify.memberListCount(3);
goTo.marker('2');
verify.completionListContains("A");
verify.completionListAllowsNewIdentifier();
verify.memberListCount(3);