-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Don't include already-covered cases in switch completions #51790
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a65957c
43bf85a
333979d
f95a169
bd14233
ede4377
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -127,6 +127,7 @@ import { | |
isCallExpression, | ||
isCaseBlock, | ||
isCaseClause, | ||
isCaseKeyword, | ||
isCheckJsEnabledForFile, | ||
isClassElement, | ||
isClassLike, | ||
|
@@ -191,6 +192,7 @@ import { | |
isNamedImports, | ||
isNamedImportsOrExports, | ||
isNamespaceImport, | ||
isNodeDescendantOf, | ||
isObjectBindingPattern, | ||
isObjectLiteralExpression, | ||
isObjectTypeDeclaration, | ||
|
@@ -429,6 +431,7 @@ export const enum SymbolOriginInfoKind { | |
ResolvedExport = 1 << 5, | ||
TypeOnlyAlias = 1 << 6, | ||
ObjectLiteralMethod = 1 << 7, | ||
Ignore = 1 << 8, | ||
|
||
SymbolMemberNoExport = SymbolMember, | ||
SymbolMemberExport = SymbolMember | Export, | ||
|
@@ -507,6 +510,10 @@ function originIsObjectLiteralMethod(origin: SymbolOriginInfo | undefined): orig | |
return !!(origin && origin.kind & SymbolOriginInfoKind.ObjectLiteralMethod); | ||
} | ||
|
||
function originIsIgnore(origin: SymbolOriginInfo | undefined): boolean { | ||
return !!(origin && origin.kind & SymbolOriginInfoKind.Ignore); | ||
} | ||
|
||
/** @internal */ | ||
export interface UniqueNameSet { | ||
add(name: string): void; | ||
|
@@ -859,7 +866,6 @@ function completionInfoFromData( | |
location, | ||
propertyAccessToConvert, | ||
keywordFilters, | ||
literals, | ||
symbolToOriginInfoMap, | ||
recommendedCompletion, | ||
isJsxInitializer, | ||
|
@@ -868,9 +874,12 @@ function completionInfoFromData( | |
isRightOfOpenTag, | ||
importStatementCompletion, | ||
insideJsDocTagTypeExpression, | ||
symbolToSortTextMap: symbolToSortTextMap, | ||
symbolToSortTextMap, | ||
hasUnresolvedAutoImports, | ||
} = completionData; | ||
let literals = completionData.literals; | ||
|
||
const checker = program.getTypeChecker(); | ||
|
||
// Verify if the file is JSX language variant | ||
if (getLanguageVariant(sourceFile.scriptKind) === LanguageVariant.JSX) { | ||
|
@@ -880,6 +889,25 @@ function completionInfoFromData( | |
} | ||
} | ||
|
||
// When the completion is for the expression of a case clause (e.g. `case |`), | ||
// filter literals & enum symbols whose values are already present in existing case clauses. | ||
const caseClause = findAncestor(contextToken, isCaseClause); | ||
if (caseClause && (isCaseKeyword(contextToken!) || isNodeDescendantOf(contextToken!, caseClause.expression))) { | ||
const tracker = newCaseClauseTracker(checker, caseClause.parent.clauses); | ||
literals = literals.filter(literal => !tracker.hasValue(literal)); | ||
// The `symbols` array cannot be filtered directly, because to each symbol at position i in `symbols`, | ||
// there might be a corresponding origin at position i in `symbolToOriginInfoMap`. | ||
// So instead of filtering the `symbols` array, we mark symbols to be ignored. | ||
symbols.forEach((symbol, i) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't filter the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are overdue to make a nice abstraction over a similar data structure to what we have so it’s easier to work with without sacrificing (and probably improving on) the reasonably efficient allocations we have going. This seems fine for now. |
||
if (symbol.valueDeclaration && isEnumMember(symbol.valueDeclaration)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that the filtering won't work if someone is using e.g. an object literal as an enum, because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like something that can be expanded to |
||
const value = checker.getConstantValue(symbol.valueDeclaration); | ||
if (value !== undefined && tracker.hasValue(value)) { | ||
symbolToOriginInfoMap[i] = { kind: SymbolOriginInfoKind.Ignore }; | ||
} | ||
} | ||
}); | ||
} | ||
|
||
const entries = createSortedArray<CompletionEntry>(); | ||
const isChecked = isCheckedFile(sourceFile, compilerOptions); | ||
if (isChecked && !isNewIdentifierLocation && (!symbols || symbols.length === 0) && keywordFilters === KeywordCompletionFilters.None) { | ||
|
@@ -4509,6 +4537,9 @@ function getCompletionEntryDisplayNameForSymbol( | |
kind: CompletionKind, | ||
jsxIdentifierExpected: boolean, | ||
): CompletionEntryDisplayNameForSymbol | undefined { | ||
if (originIsIgnore(origin)) { | ||
return undefined; | ||
} | ||
const name = originIncludesSymbolName(origin) ? origin.symbolName : symbol.name; | ||
if (name === undefined | ||
// If the symbol is external module, don't show it in the completion list | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/// <reference path="fourslash.ts" /> | ||
|
||
//// enum E { A, B } | ||
//// declare const e: E; | ||
//// switch (e) { | ||
//// case E.A: | ||
//// return 0; | ||
//// case E./*1*/ | ||
//// } | ||
//// declare const f: 1 | 2 | 3; | ||
//// switch (f) { | ||
//// case 1: | ||
//// return 1; | ||
//// case /*2*/ | ||
//// } | ||
|
||
verify.completions( | ||
{ | ||
marker: "1", | ||
isNewIdentifierLocation: false, | ||
includes: ["B"], | ||
excludes: "A", | ||
}, | ||
{ | ||
marker: "2", | ||
isNewIdentifierLocation: false, | ||
excludes: "1", | ||
includes: ["2", "3"], | ||
} | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very cool that this abstraction came in handy in an additional place 🎉