Skip to content

Commit 009dc0f

Browse files
author
Andy
authored
For completion in string literal union, don't include strings already in the union (microsoft#26755)
1 parent ee7d0e2 commit 009dc0f

File tree

2 files changed

+26
-17
lines changed

2 files changed

+26
-17
lines changed

src/services/completions.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -390,29 +390,34 @@ namespace ts.Completions {
390390
}
391391
type StringLiteralCompletion = { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: ReadonlyArray<PathCompletions.PathCompletion> } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes;
392392
function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost): StringLiteralCompletion | undefined {
393-
switch (node.parent.kind) {
393+
const { parent } = node;
394+
switch (parent.kind) {
394395
case SyntaxKind.LiteralType:
395-
switch (node.parent.parent.kind) {
396+
switch (parent.parent.kind) {
396397
case SyntaxKind.TypeReference:
397-
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(node.parent as LiteralTypeNode)), isNewIdentifier: false };
398+
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(parent as LiteralTypeNode)), isNewIdentifier: false };
398399
case SyntaxKind.IndexedAccessType:
399400
// Get all apparent property names
400401
// i.e. interface Foo {
401402
// foo: string;
402403
// bar: string;
403404
// }
404405
// let x: Foo["/*completion position*/"]
405-
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode((node.parent.parent as IndexedAccessTypeNode).objectType));
406+
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode((parent.parent as IndexedAccessTypeNode).objectType));
406407
case SyntaxKind.ImportType:
407408
return { kind: StringLiteralCompletionKind.Paths, paths: PathCompletions.getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker) };
408-
case SyntaxKind.UnionType:
409-
return isTypeReferenceNode(node.parent.parent.parent) ? { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(node.parent.parent as UnionTypeNode)), isNewIdentifier: false } : undefined;
409+
case SyntaxKind.UnionType: {
410+
if (!isTypeReferenceNode(parent.parent.parent)) return undefined;
411+
const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(parent.parent as UnionTypeNode, parent as LiteralTypeNode);
412+
const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(parent.parent as UnionTypeNode)).filter(t => !contains(alreadyUsedTypes, t.value));
413+
return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier: false };
414+
}
410415
default:
411416
return undefined;
412417
}
413418

414419
case SyntaxKind.PropertyAssignment:
415-
if (isObjectLiteralExpression(node.parent.parent) && (<PropertyAssignment>node.parent).name === node) {
420+
if (isObjectLiteralExpression(parent.parent) && (<PropertyAssignment>parent).name === node) {
416421
// Get quoted name of properties of the object literal expression
417422
// i.e. interface ConfigFiles {
418423
// 'jspm:dev': string
@@ -425,12 +430,12 @@ namespace ts.Completions {
425430
// foo({
426431
// '/*completion position*/'
427432
// });
428-
return stringLiteralCompletionsFromProperties(typeChecker.getContextualType(node.parent.parent));
433+
return stringLiteralCompletionsFromProperties(typeChecker.getContextualType(parent.parent));
429434
}
430435
return fromContextualType();
431436

432437
case SyntaxKind.ElementAccessExpression: {
433-
const { expression, argumentExpression } = node.parent as ElementAccessExpression;
438+
const { expression, argumentExpression } = parent as ElementAccessExpression;
434439
if (node === argumentExpression) {
435440
// Get all names of properties on the expression
436441
// i.e. interface A {
@@ -445,7 +450,7 @@ namespace ts.Completions {
445450

446451
case SyntaxKind.CallExpression:
447452
case SyntaxKind.NewExpression:
448-
if (!isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false) && !isImportCall(node.parent)) {
453+
if (!isRequireCall(parent, /*checkArgumentIsStringLiteralLike*/ false) && !isImportCall(parent)) {
449454
const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(node, position, sourceFile);
450455
// Get string literal completions from specialized signatures of the target
451456
// i.e. declare function f(a: 'A');
@@ -476,6 +481,11 @@ namespace ts.Completions {
476481
}
477482
}
478483

484+
function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current: LiteralTypeNode): ReadonlyArray<string> {
485+
return mapDefined(union.types, type =>
486+
type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined);
487+
}
488+
479489
function getStringLiteralCompletionsFromSignature(argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes {
480490
let isNewIdentifier = false;
481491

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
/// <reference path='fourslash.ts' />
22

3-
//// type A = 'fooooo' | 'barrrrr';
3+
//// type A = 'foo' | 'bar' | 'baz';
44
//// type B<T extends A> = {};
5-
//// type C = B<'fooooo' | '/**/'>
5+
//// type C = B<'foo' | '/**/'>
66

7-
8-
goTo.marker();
9-
verify.completionListContains("fooooo");
10-
verify.completionListContains("barrrrr");
7+
verify.completions({ marker: "", exact: ["bar", "baz"] });
118
edit.insert("b");
12-
verify.completionListContains("barrrrr");
9+
verify.completions({ exact: ["bar", "baz"] });
10+
edit.insert("ar");
11+
verify.completions({ exact: ["bar", "baz"] });

0 commit comments

Comments
 (0)