Skip to content

Commit 6b43f82

Browse files
authored
Filter out existing cases from string completions (#52633)
1 parent 7d8f6ee commit 6b43f82

File tree

4 files changed

+101
-79
lines changed

4 files changed

+101
-79
lines changed

src/services/completions.ts

+1-75
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
CancellationToken,
88
canUsePropertyAccess,
99
CaseBlock,
10-
CaseClause,
1110
cast,
1211
CharacterCodes,
1312
ClassElement,
@@ -43,13 +42,11 @@ import {
4342
createTextSpanFromRange,
4443
Debug,
4544
Declaration,
46-
DefaultClause,
4745
Diagnostics,
4846
diagnosticToString,
4947
displayPart,
5048
EmitHint,
5149
EmitTextWriter,
52-
endsWith,
5350
EntityName,
5451
EnumMember,
5552
escapeSnippetText,
@@ -140,7 +137,6 @@ import {
140137
isConstructorDeclaration,
141138
isContextualKeyword,
142139
isDeclarationName,
143-
isDefaultClause,
144140
isDeprecatedDeclaration,
145141
isEntityName,
146142
isEnumMember,
@@ -185,7 +181,6 @@ import {
185181
isKeyword,
186182
isKnownSymbol,
187183
isLabeledStatement,
188-
isLiteralExpression,
189184
isLiteralImportTypeNode,
190185
isMemberName,
191186
isMethodDeclaration,
@@ -276,6 +271,7 @@ import {
276271
ModuleReference,
277272
moduleResolutionSupportsPackageJsonExportsAndImports,
278273
NamedImportBindings,
274+
newCaseClauseTracker,
279275
Node,
280276
NodeArray,
281277
NodeBuilderFlags,
@@ -288,7 +284,6 @@ import {
288284
ObjectTypeDeclaration,
289285
or,
290286
ParenthesizedTypeNode,
291-
parseBigInt,
292287
positionBelongsToNode,
293288
positionIsASICandidate,
294289
positionsAreOnSameLine,
@@ -1105,75 +1100,6 @@ function getExhaustiveCaseSnippets(
11051100
return undefined;
11061101
}
11071102

1108-
interface CaseClauseTracker {
1109-
addValue(value: string | number): void;
1110-
hasValue(value: string | number | PseudoBigInt): boolean;
1111-
}
1112-
1113-
function newCaseClauseTracker(checker: TypeChecker, clauses: readonly (CaseClause | DefaultClause)[]): CaseClauseTracker {
1114-
const existingStrings = new Set<string>();
1115-
const existingNumbers = new Set<number>();
1116-
const existingBigInts = new Set<string>();
1117-
1118-
for (const clause of clauses) {
1119-
if (!isDefaultClause(clause)) {
1120-
if (isLiteralExpression(clause.expression)) {
1121-
const expression = clause.expression;
1122-
switch (expression.kind) {
1123-
case SyntaxKind.NoSubstitutionTemplateLiteral:
1124-
case SyntaxKind.StringLiteral:
1125-
existingStrings.add(expression.text);
1126-
break;
1127-
case SyntaxKind.NumericLiteral:
1128-
existingNumbers.add(parseInt(expression.text));
1129-
break;
1130-
case SyntaxKind.BigIntLiteral:
1131-
const parsedBigInt = parseBigInt(endsWith(expression.text, "n") ? expression.text.slice(0, -1) : expression.text);
1132-
if (parsedBigInt) {
1133-
existingBigInts.add(pseudoBigIntToString(parsedBigInt));
1134-
}
1135-
break;
1136-
}
1137-
}
1138-
else {
1139-
const symbol = checker.getSymbolAtLocation(clause.expression);
1140-
if (symbol && symbol.valueDeclaration && isEnumMember(symbol.valueDeclaration)) {
1141-
const enumValue = checker.getConstantValue(symbol.valueDeclaration);
1142-
if (enumValue !== undefined) {
1143-
addValue(enumValue);
1144-
}
1145-
}
1146-
}
1147-
}
1148-
}
1149-
1150-
return {
1151-
addValue,
1152-
hasValue,
1153-
};
1154-
1155-
function addValue(value: string | number) {
1156-
switch (typeof value) {
1157-
case "string":
1158-
existingStrings.add(value);
1159-
break;
1160-
case "number":
1161-
existingNumbers.add(value);
1162-
}
1163-
}
1164-
1165-
function hasValue(value: string | number | PseudoBigInt): boolean {
1166-
switch (typeof value) {
1167-
case "string":
1168-
return existingStrings.has(value);
1169-
case "number":
1170-
return existingNumbers.has(value);
1171-
case "object":
1172-
return existingBigInts.has(pseudoBigIntToString(value));
1173-
}
1174-
}
1175-
}
1176-
11771103
function typeNodeToExpression(typeNode: TypeNode, languageVersion: ScriptTarget, quotePreference: QuotePreference): Expression | undefined {
11781104
switch (typeNode.kind) {
11791105
case SyntaxKind.TypeReference:

src/services/stringCompletions.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
arrayFrom,
55
CallLikeExpression,
66
CancellationToken,
7+
CaseClause,
78
changeExtension,
89
CharacterCodes,
910
combinePaths,
@@ -94,6 +95,7 @@ import {
9495
moduleResolutionUsesNodeModules,
9596
ModuleSpecifierEnding,
9697
moduleSpecifiers,
98+
newCaseClauseTracker,
9799
Node,
98100
normalizePath,
99101
normalizeSlashes,
@@ -275,7 +277,7 @@ function stringLiteralCompletionDetails(name: string, location: Node, completion
275277
return match && createCompletionDetailsForSymbol(match, checker, sourceFile, location, cancellationToken);
276278
}
277279
case StringLiteralCompletionKind.Types:
278-
return find(completion.types, t => t.value === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.typeElement, [textPart(name)]) : undefined;
280+
return find(completion.types, t => t.value === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.string, [textPart(name)]) : undefined;
279281
default:
280282
return Debug.assertNever(completion);
281283
}
@@ -415,12 +417,15 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL
415417
// var y = require("/*completion position*/");
416418
// export * from "/*completion position*/";
417419
return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) };
418-
420+
case SyntaxKind.CaseClause:
421+
const tracker = newCaseClauseTracker(typeChecker, (node.parent as CaseClause).parent.clauses);
422+
const literals = fromContextualType().types.filter(literal => !tracker.hasValue(literal.value));
423+
return { kind: StringLiteralCompletionKind.Types, types: literals, isNewIdentifier: false };
419424
default:
420425
return fromContextualType();
421426
}
422427

423-
function fromContextualType(): StringLiteralCompletion {
428+
function fromContextualType(): StringLiteralCompletionsFromTypes {
424429
// Get completion for string literal from string literal type
425430
// i.e. var x: "hi" | "hello" = "/*completion position*/"
426431
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker, ContextFlags.Completions)), isNewIdentifier: false };

src/services/utilities.ts

+79
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
Debug,
3939
Declaration,
4040
Decorator,
41+
DefaultClause,
4142
defaultMaximumTruncationLength,
4243
DeleteExpression,
4344
Diagnostic,
@@ -53,6 +54,7 @@ import {
5354
EmitHint,
5455
emptyArray,
5556
EndOfFileToken,
57+
endsWith,
5658
ensureScriptKind,
5759
EqualityOperator,
5860
escapeString,
@@ -138,10 +140,12 @@ import {
138140
isDeclaration,
139141
isDeclarationName,
140142
isDecorator,
143+
isDefaultClause,
141144
isDeleteExpression,
142145
isElementAccessExpression,
143146
isEntityName,
144147
isEnumDeclaration,
148+
isEnumMember,
145149
isExportAssignment,
146150
isExportDeclaration,
147151
isExportSpecifier,
@@ -188,6 +192,7 @@ import {
188192
isKeyword,
189193
isLabeledStatement,
190194
isLet,
195+
isLiteralExpression,
191196
isLiteralTypeNode,
192197
isMappedTypeNode,
193198
isModifier,
@@ -281,13 +286,16 @@ import {
281286
or,
282287
OrganizeImports,
283288
PackageJsonDependencyGroup,
289+
parseBigInt,
284290
pathIsRelative,
285291
PrefixUnaryExpression,
286292
Program,
287293
ProjectPackageJsonInfo,
288294
PropertyAccessExpression,
289295
PropertyAssignment,
290296
PropertyName,
297+
PseudoBigInt,
298+
pseudoBigIntToString,
291299
QualifiedName,
292300
RefactorContext,
293301
Scanner,
@@ -4071,3 +4079,74 @@ export function jsxModeNeedsExplicitImport(jsx: JsxEmit | undefined) {
40714079
export function isSourceFileFromLibrary(program: Program, node: SourceFile) {
40724080
return program.isSourceFileFromExternalLibrary(node) || program.isSourceFileDefaultLibrary(node);
40734081
}
4082+
4083+
/** @internal */
4084+
export interface CaseClauseTracker {
4085+
addValue(value: string | number): void;
4086+
hasValue(value: string | number | PseudoBigInt): boolean;
4087+
}
4088+
4089+
/** @internal */
4090+
export function newCaseClauseTracker(checker: TypeChecker, clauses: readonly (CaseClause | DefaultClause)[]): CaseClauseTracker {
4091+
const existingStrings = new Set<string>();
4092+
const existingNumbers = new Set<number>();
4093+
const existingBigInts = new Set<string>();
4094+
4095+
for (const clause of clauses) {
4096+
if (!isDefaultClause(clause)) {
4097+
if (isLiteralExpression(clause.expression)) {
4098+
const expression = clause.expression;
4099+
switch (expression.kind) {
4100+
case SyntaxKind.NoSubstitutionTemplateLiteral:
4101+
case SyntaxKind.StringLiteral:
4102+
existingStrings.add(expression.text);
4103+
break;
4104+
case SyntaxKind.NumericLiteral:
4105+
existingNumbers.add(parseInt(expression.text));
4106+
break;
4107+
case SyntaxKind.BigIntLiteral:
4108+
const parsedBigInt = parseBigInt(endsWith(expression.text, "n") ? expression.text.slice(0, -1) : expression.text);
4109+
if (parsedBigInt) {
4110+
existingBigInts.add(pseudoBigIntToString(parsedBigInt));
4111+
}
4112+
break;
4113+
}
4114+
}
4115+
else {
4116+
const symbol = checker.getSymbolAtLocation(clause.expression);
4117+
if (symbol && symbol.valueDeclaration && isEnumMember(symbol.valueDeclaration)) {
4118+
const enumValue = checker.getConstantValue(symbol.valueDeclaration);
4119+
if (enumValue !== undefined) {
4120+
addValue(enumValue);
4121+
}
4122+
}
4123+
}
4124+
}
4125+
}
4126+
4127+
return {
4128+
addValue,
4129+
hasValue,
4130+
};
4131+
4132+
function addValue(value: string | number) {
4133+
switch (typeof value) {
4134+
case "string":
4135+
existingStrings.add(value);
4136+
break;
4137+
case "number":
4138+
existingNumbers.add(value);
4139+
}
4140+
}
4141+
4142+
function hasValue(value: string | number | PseudoBigInt): boolean {
4143+
switch (typeof value) {
4144+
case "string":
4145+
return existingStrings.has(value);
4146+
case "number":
4147+
return existingNumbers.has(value);
4148+
case "object":
4149+
return existingBigInts.has(pseudoBigIntToString(value));
4150+
}
4151+
}
4152+
}

tests/cases/fourslash/switchCompletions.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
//// return 1;
1414
//// case /*2*/
1515
//// }
16+
//// declare const f2: 'foo' | 'bar' | 'baz';
17+
//// switch (f2) {
18+
//// case 'bar':
19+
//// return 1;
20+
//// case '/*3*/'
21+
//// }
1622

1723
verify.completions(
1824
{
@@ -26,5 +32,11 @@ verify.completions(
2632
isNewIdentifierLocation: false,
2733
excludes: "1",
2834
includes: ["2", "3"],
35+
},
36+
{
37+
marker: "3",
38+
isNewIdentifierLocation: false,
39+
includes: ["foo", "baz"],
40+
excludes: "bar",
2941
}
30-
);
42+
);

0 commit comments

Comments
 (0)