Skip to content

Commit 060f2a8

Browse files
committed
Add support for completion in string literals
1 parent 0a277a1 commit 060f2a8

4 files changed

Lines changed: 158 additions & 15 deletions

File tree

src/services/services.ts

Lines changed: 103 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,10 +1032,10 @@ namespace ts {
10321032
getCancellationToken?(): HostCancellationToken;
10331033
getCurrentDirectory(): string;
10341034
getDefaultLibFileName(options: CompilerOptions): string;
1035-
log? (s: string): void;
1036-
trace? (s: string): void;
1037-
error? (s: string): void;
1038-
useCaseSensitiveFileNames? (): boolean;
1035+
log?(s: string): void;
1036+
trace?(s: string): void;
1037+
error?(s: string): void;
1038+
useCaseSensitiveFileNames?(): boolean;
10391039

10401040
/*
10411041
* 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 {
24162416
}
24172417

24182418
// should be start of dependency list
2419-
if (token !== SyntaxKind.OpenBracketToken) {
2419+
if (token !== SyntaxKind.OpenBracketToken) {
24202420
return true;
24212421
}
24222422

@@ -3912,10 +3912,15 @@ namespace ts {
39123912
}
39133913
}
39143914

3915-
39163915
function getCompletionsAtPosition(fileName: string, position: number): CompletionInfo {
39173916
synchronizeHostData();
39183917

3918+
const sourceFile = getValidSourceFile(fileName);
3919+
3920+
if (isInString(sourceFile, position)) {
3921+
return getStringLiteralCompletionEntries(sourceFile, position);
3922+
}
3923+
39193924
const completionData = getCompletionData(fileName, position);
39203925
if (!completionData) {
39213926
return undefined;
@@ -3928,12 +3933,10 @@ namespace ts {
39283933
return { isMemberCompletion: false, isNewIdentifierLocation: false, entries: getAllJsDocCompletionEntries() };
39293934
}
39303935

3931-
const sourceFile = getValidSourceFile(fileName);
3932-
39333936
const entries: CompletionEntry[] = [];
39343937

39353938
if (isSourceFileJavaScript(sourceFile)) {
3936-
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries);
3939+
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ false);
39373940
addRange(entries, getJavaScriptCompletionEntries(sourceFile, location.pos, uniqueNames));
39383941
}
39393942
else {
@@ -3957,7 +3960,7 @@ namespace ts {
39573960
}
39583961
}
39593962

3960-
getCompletionEntriesFromSymbols(symbols, entries);
3963+
getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true);
39613964
}
39623965

39633966
// Add keywords if this is not a member completion list
@@ -4007,11 +4010,11 @@ namespace ts {
40074010
}));
40084011
}
40094012

4010-
function createCompletionEntry(symbol: Symbol, location: Node): CompletionEntry {
4013+
function createCompletionEntry(symbol: Symbol, location: Node, performCharacterChecks: boolean): CompletionEntry {
40114014
// Try to get a valid display name for this symbol, if we could not find one, then ignore it.
40124015
// We would like to only show things that can be added after a dot, so for instance numeric properties can
40134016
// not be accessed with a dot (a.1 <- invalid)
4014-
const displayName = getCompletionEntryDisplayNameForSymbol(symbol, program.getCompilerOptions().target, /*performCharacterChecks*/ true, location);
4017+
const displayName = getCompletionEntryDisplayNameForSymbol(symbol, program.getCompilerOptions().target, performCharacterChecks, location);
40154018
if (!displayName) {
40164019
return undefined;
40174020
}
@@ -4032,12 +4035,12 @@ namespace ts {
40324035
};
40334036
}
40344037

4035-
function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[]): Map<string> {
4038+
function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[], location: Node, performCharacterChecks: boolean): Map<string> {
40364039
const start = new Date().getTime();
40374040
const uniqueNames: Map<string> = {};
40384041
if (symbols) {
40394042
for (const symbol of symbols) {
4040-
const entry = createCompletionEntry(symbol, location);
4043+
const entry = createCompletionEntry(symbol, location, performCharacterChecks);
40414044
if (entry) {
40424045
const id = escapeIdentifier(entry.name);
40434046
if (!lookUp(uniqueNames, id)) {
@@ -4051,6 +4054,91 @@ namespace ts {
40514054
log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (new Date().getTime() - start));
40524055
return uniqueNames;
40534056
}
4057+
4058+
function getStringLiteralCompletionEntries(sourceFile: SourceFile, position: number) {
4059+
const node = findPrecedingToken(position, sourceFile);
4060+
if (!node || node.kind !== SyntaxKind.StringLiteral) {
4061+
return undefined;
4062+
}
4063+
4064+
isNameOfExternalModuleImportOrDeclaration
4065+
const argumentInfo = SignatureHelp.getContainingArgumentInfo(node, position, sourceFile);
4066+
if (argumentInfo) {
4067+
return getStringLiteralCompletionEntriesFromCallExpression(argumentInfo);
4068+
}
4069+
else if (isElementAccessExpression(node.parent) && node.parent.argumentExpression === node) {
4070+
return getStringLiteralCompletionEntriesFromElementAccess(node.parent);
4071+
}
4072+
else {
4073+
return getStringLiteralCompletionEntriesFromContextualType(<StringLiteral>node);
4074+
}
4075+
}
4076+
4077+
function getStringLiteralCompletionEntriesFromCallExpression(argumentInfo: SignatureHelp.ArgumentListInfo) {
4078+
const typeChecker = program.getTypeChecker();
4079+
const candidates: Signature[] = [];
4080+
const entries: CompletionEntry[] = [];
4081+
4082+
typeChecker.getResolvedSignature(argumentInfo.invocation, candidates);
4083+
4084+
for (const candidate of candidates) {
4085+
if (candidate.parameters.length > argumentInfo.argumentIndex) {
4086+
const parameter = candidate.parameters[argumentInfo.argumentIndex];
4087+
addStringLiteralCompletionsFromType(typeChecker.getTypeAtLocation(parameter.valueDeclaration), entries);
4088+
}
4089+
}
4090+
4091+
if (entries.length) {
4092+
return { isMemberCompletion: false, isNewIdentifierLocation: true, entries: entries };
4093+
}
4094+
4095+
return undefined;
4096+
}
4097+
4098+
function getStringLiteralCompletionEntriesFromElementAccess(node: ElementAccessExpression) {
4099+
const typeChecker = program.getTypeChecker();
4100+
const type = typeChecker.getTypeAtLocation(node.expression);
4101+
const entries: CompletionEntry[] = [];
4102+
if (type) {
4103+
getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, node, /*performCharacterChecks*/false);
4104+
if (entries.length) {
4105+
return { isMemberCompletion: true, isNewIdentifierLocation: true, entries: entries };
4106+
}
4107+
}
4108+
return undefined;
4109+
}
4110+
4111+
function getStringLiteralCompletionEntriesFromContextualType(node: StringLiteral) {
4112+
const typeChecker = program.getTypeChecker();
4113+
const type = typeChecker.getContextualType(node);
4114+
if (type) {
4115+
const entries: CompletionEntry[] = [];
4116+
addStringLiteralCompletionsFromType(type, entries);
4117+
if (entries.length) {
4118+
return { isMemberCompletion: false, isNewIdentifierLocation: false, entries: entries };
4119+
}
4120+
}
4121+
return undefined;
4122+
}
4123+
4124+
function addStringLiteralCompletionsFromType(type: Type, result: CompletionEntry[]): void {
4125+
if (!type) {
4126+
return;
4127+
}
4128+
if (type.flags & TypeFlags.Union) {
4129+
forEach((<UnionType>type).types, t => addStringLiteralCompletionsFromType(t, result));
4130+
}
4131+
else {
4132+
if (type.flags & TypeFlags.StringLiteral) {
4133+
result.push({
4134+
name: (<StringLiteralType>type).text,
4135+
kindModifiers: ScriptElementKindModifier.none,
4136+
kind: ScriptElementKind.variableElement,
4137+
sortText: "0"
4138+
});
4139+
}
4140+
}
4141+
}
40544142
}
40554143

40564144
function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails {
@@ -4215,7 +4303,7 @@ namespace ts {
42154303
// try get the call/construct signature from the type if it matches
42164304
let callExpression: CallExpression;
42174305
if (location.kind === SyntaxKind.CallExpression || location.kind === SyntaxKind.NewExpression) {
4218-
callExpression = <CallExpression> location;
4306+
callExpression = <CallExpression>location;
42194307
}
42204308
else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) {
42214309
callExpression = <CallExpression>location.parent;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////type Options = "Option 1" | "Option 2" | "Option 3";
4+
////var x: Options = "/*1*/Option 3";
5+
////
6+
////function f(a: Options) { };
7+
////f("/*2*/
8+
9+
goTo.marker('1');
10+
verify.completionListContains("Option 1");
11+
verify.memberListCount(3);
12+
13+
goTo.marker('2');
14+
verify.completionListContains("Option 2");
15+
verify.memberListCount(3);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////var o = {
4+
//// foo() { },
5+
//// bar: 0,
6+
//// "some other name": 1
7+
////};
8+
////
9+
////o["/*1*/bar"];
10+
////o["/*2*/
11+
12+
goTo.marker('1');
13+
verify.completionListContains("foo");
14+
verify.completionListAllowsNewIdentifier();
15+
verify.memberListCount(3);
16+
17+
goTo.marker('2');
18+
verify.completionListContains("some other name");
19+
verify.completionListAllowsNewIdentifier();
20+
verify.memberListCount(3);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////declare function f(a: "A", b: number): void;
4+
////declare function f(a: "B", b: number): void;
5+
////declare function f(a: "C", b: number): void;
6+
////declare function f(a: string, b: number): void;
7+
////
8+
////f("/*1*/C", 2);
9+
////
10+
////f("/*2*/
11+
12+
goTo.marker('1');
13+
verify.completionListContains("A");
14+
verify.completionListAllowsNewIdentifier();
15+
verify.memberListCount(3);
16+
17+
goTo.marker('2');
18+
verify.completionListContains("A");
19+
verify.completionListAllowsNewIdentifier();
20+
verify.memberListCount(3);

0 commit comments

Comments
 (0)