-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Support completions for enums in switch cases #16864
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
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,7 +24,7 @@ namespace ts.Completions { | |
return undefined; | ||
} | ||
|
||
const { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, request, keywordFilters } = completionData; | ||
const { symbols, useFullSymbolNames, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, request, keywordFilters } = completionData; | ||
|
||
if (sourceFile.languageVariant === LanguageVariant.JSX && | ||
location && location.parent && location.parent.kind === SyntaxKind.JsxClosingElement) { | ||
|
@@ -56,15 +56,15 @@ namespace ts.Completions { | |
const entries: CompletionEntry[] = []; | ||
|
||
if (isSourceFileJavaScript(sourceFile)) { | ||
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log); | ||
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log, useFullSymbolNames); | ||
addRange(entries, getJavaScriptCompletionEntries(sourceFile, location.pos, uniqueNames, compilerOptions.target)); | ||
} | ||
else { | ||
if ((!symbols || symbols.length === 0) && keywordFilters === KeywordCompletionFilters.None) { | ||
return undefined; | ||
} | ||
|
||
getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log); | ||
getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log, useFullSymbolNames); | ||
} | ||
|
||
// TODO add filter for keyword based on type/value/namespace and also location | ||
|
@@ -107,11 +107,11 @@ namespace ts.Completions { | |
return entries; | ||
} | ||
|
||
function createCompletionEntry(symbol: Symbol, location: Node, performCharacterChecks: boolean, typeChecker: TypeChecker, target: ScriptTarget): CompletionEntry { | ||
function createCompletionEntry(symbol: Symbol, location: Node, performCharacterChecks: boolean, typeChecker: TypeChecker, target: ScriptTarget, useFullSymbolNames: 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(typeChecker, symbol, target, performCharacterChecks, location); | ||
const displayName = getCompletionEntryDisplayNameForSymbol(typeChecker, symbol, target, performCharacterChecks, location, useFullSymbolNames); | ||
if (!displayName) { | ||
return undefined; | ||
} | ||
|
@@ -132,12 +132,12 @@ namespace ts.Completions { | |
}; | ||
} | ||
|
||
function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: Push<CompletionEntry>, location: Node, performCharacterChecks: boolean, typeChecker: TypeChecker, target: ScriptTarget, log: Log): Map<string> { | ||
function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: Push<CompletionEntry>, location: Node, performCharacterChecks: boolean, typeChecker: TypeChecker, target: ScriptTarget, log: Log, useFullSymbolNames?: boolean): Map<string> { | ||
const start = timestamp(); | ||
const uniqueNames = createMap<string>(); | ||
if (symbols) { | ||
for (const symbol of symbols) { | ||
const entry = createCompletionEntry(symbol, location, performCharacterChecks, typeChecker, target); | ||
const entry = createCompletionEntry(symbol, location, performCharacterChecks, typeChecker, target, useFullSymbolNames); | ||
if (entry) { | ||
const id = escapeIdentifier(entry.name); | ||
if (!uniqueNames.get(id)) { | ||
|
@@ -300,13 +300,13 @@ namespace ts.Completions { | |
// Compute all the completion symbols again. | ||
const completionData = getCompletionData(typeChecker, log, sourceFile, position); | ||
if (completionData) { | ||
const { symbols, location } = completionData; | ||
const { symbols, useFullSymbolNames, location } = completionData; | ||
|
||
// Find the symbol with the matching entry name. | ||
// We don't need to perform character checks here because we're only comparing the | ||
// name against 'entryName' (which is known to be good), not building a new | ||
// completion entry. | ||
const symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location) === entryName ? s : undefined); | ||
const symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location, useFullSymbolNames) === entryName ? s : undefined); | ||
|
||
if (symbol) { | ||
const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All); | ||
|
@@ -344,20 +344,21 @@ namespace ts.Completions { | |
// Compute all the completion symbols again. | ||
const completionData = getCompletionData(typeChecker, log, sourceFile, position); | ||
if (completionData) { | ||
const { symbols, location } = completionData; | ||
const { symbols, useFullSymbolNames, location } = completionData; | ||
|
||
// Find the symbol with the matching entry name. | ||
// We don't need to perform character checks here because we're only comparing the | ||
// name against 'entryName' (which is known to be good), not building a new | ||
// completion entry. | ||
return forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location) === entryName ? s : undefined); | ||
return forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location, useFullSymbolNames) === entryName ? s : undefined); | ||
} | ||
|
||
return undefined; | ||
} | ||
|
||
interface CompletionData { | ||
symbols: Symbol[]; | ||
useFullSymbolNames: boolean; | ||
isGlobalCompletion: boolean; | ||
isMemberCompletion: boolean; | ||
isNewIdentifierLocation: boolean; | ||
|
@@ -440,7 +441,7 @@ namespace ts.Completions { | |
} | ||
|
||
if (request) { | ||
return { symbols: undefined, isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, location: undefined, isRightOfDot: false, request, keywordFilters: KeywordCompletionFilters.None }; | ||
return { symbols: undefined, useFullSymbolNames: false, isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, location: undefined, isRightOfDot: false, request, keywordFilters: KeywordCompletionFilters.None }; | ||
} | ||
|
||
if (!insideJsDocTagTypeExpression) { | ||
|
@@ -474,6 +475,7 @@ namespace ts.Completions { | |
let isRightOfDot = false; | ||
let isRightOfOpenTag = false; | ||
let isStartingCloseTag = false; | ||
let isRightOfCase = false; | ||
|
||
let location = getTouchingPropertyName(sourceFile, position, insideJsDocTagTypeExpression); // TODO: GH#15853 | ||
if (contextToken) { | ||
|
@@ -499,6 +501,10 @@ namespace ts.Completions { | |
return undefined; | ||
} | ||
} | ||
else if (contextToken.kind === SyntaxKind.CaseKeyword) { | ||
node = contextToken.parent as CaseClause; | ||
isRightOfCase = true; | ||
} | ||
else if (sourceFile.languageVariant === LanguageVariant.JSX) { | ||
// <UI.Test /* completion position */ /> | ||
// If the tagname is a property access expression, we will then walk up to the top most of property access expression. | ||
|
@@ -540,11 +546,15 @@ namespace ts.Completions { | |
let isMemberCompletion: boolean; | ||
let isNewIdentifierLocation: boolean; | ||
let keywordFilters = KeywordCompletionFilters.None; | ||
const useFullSymbolNames = isRightOfCase; | ||
let symbols: Symbol[] = []; | ||
|
||
if (isRightOfDot) { | ||
getTypeScriptMemberSymbols(); | ||
} | ||
else if (isRightOfCase) { | ||
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. Wouldn't this restrict completions to only enum completions and prevent completions for locals? That is too restrictive. |
||
getSwitchCaseSymbols(node as CaseClause); | ||
} | ||
else if (isRightOfOpenTag) { | ||
const tagSymbols = typeChecker.getJsxIntrinsicTagNames(); | ||
if (tryGetGlobalSymbols()) { | ||
|
@@ -577,7 +587,7 @@ namespace ts.Completions { | |
|
||
log("getCompletionData: Semantic work: " + (timestamp() - semanticStart)); | ||
|
||
return { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), request, keywordFilters }; | ||
return { symbols, useFullSymbolNames, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), request, keywordFilters }; | ||
|
||
type JSDocTagWithTypeExpression = JSDocAugmentsTag | JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; | ||
|
||
|
@@ -633,6 +643,20 @@ namespace ts.Completions { | |
} | ||
} | ||
|
||
function getSwitchCaseSymbols(clause: CaseClause): void { | ||
isGlobalCompletion = false; | ||
isMemberCompletion = true; // Set to true to avoid getting keyword completions. TODO: This really needs a refactor. | ||
isNewIdentifierLocation = false; | ||
|
||
const switchStatement = clause.parent.parent; | ||
forEach(typeChecker.getRemainingSwitchCaseTypes(switchStatement), type => { | ||
// TODO: GH#16863: Support regular number and string types, although those have no symbols. | ||
if (type.flags & TypeFlags.EnumLiteral) { | ||
symbols.push(type.symbol); | ||
} | ||
}); | ||
} | ||
|
||
function addTypeProperties(type: Type) { | ||
if (type) { | ||
// Filter private properties | ||
|
@@ -1598,7 +1622,9 @@ namespace ts.Completions { | |
* | ||
* @return undefined if the name is of external module otherwise a name with striped of any quote | ||
*/ | ||
function getCompletionEntryDisplayNameForSymbol(typeChecker: TypeChecker, symbol: Symbol, target: ScriptTarget, performCharacterChecks: boolean, location: Node): string { | ||
function getCompletionEntryDisplayNameForSymbol(typeChecker: TypeChecker, symbol: Symbol, target: ScriptTarget, performCharacterChecks: boolean, location: Node, useFullSymbolNames: boolean): string { | ||
if (useFullSymbolNames) return typeChecker.symbolToString(symbol, location); | ||
|
||
const displayName: string = getDeclaredName(typeChecker, symbol, location); | ||
|
||
if (displayName) { | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/// <reference path='fourslash.ts'/> | ||
|
||
////enum E { A, B } | ||
////declare const e: E; | ||
////switch (e) { | ||
//// case /*1*/ | ||
////} | ||
////switch (e) { | ||
//// case E.B: | ||
//// case /*2*/: | ||
//// default: | ||
//// case nonsense: | ||
////} | ||
|
||
verify.completionsAt("1", ["E.A", "E.B"]); | ||
verify.completionsAt("2", ["E.A"]); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
i would move this to services, and use
getTypeAtLocation
for switch labels.