mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-12-12 03:20:56 -06:00
Exhaustive case completion for switch statements (#50996)
* fix services' type's isLiteral * update literal completions tests * initial prototype * use symbol to expression. TODO: filter existing, replace import nodes * WIP * WIP * remove booleans from literals * trigger at case keyword positions * clean up tests * fix element access expression case * refactor dealing with existing values into a tracker * fix merge errors * cleanup and more tests * fix lint errors * more merge conflict fixes and cleanup * use appropriate quotes * small indentation fix * refactor case clause tracker * experiment: support tabstops after each case clause * address small CR comments * fix completion entry details; add test case * fix lint errors * remove space before tab stops; refactor
This commit is contained in:
parent
5435efbf37
commit
6a3c9ea125
@ -52,6 +52,7 @@ import {
|
||||
canHaveIllegalDecorators,
|
||||
canHaveIllegalModifiers,
|
||||
canHaveModifiers,
|
||||
canUsePropertyAccess,
|
||||
cartesianProduct,
|
||||
CaseBlock,
|
||||
CaseClause,
|
||||
@ -102,7 +103,6 @@ import {
|
||||
createModeAwareCacheKey,
|
||||
createPrinter,
|
||||
createPropertyNameNodeForIdentifierOrLiteral,
|
||||
createScanner,
|
||||
createSymbolTable,
|
||||
createTextWriter,
|
||||
createUnderscoreEscapedMultiMap,
|
||||
@ -500,7 +500,6 @@ import {
|
||||
isGlobalScopeAugmentation,
|
||||
isHeritageClause,
|
||||
isIdentifier,
|
||||
isIdentifierStart,
|
||||
isIdentifierText,
|
||||
isIdentifierTypePredicate,
|
||||
isIdentifierTypeReference,
|
||||
@ -678,6 +677,7 @@ import {
|
||||
isTypeReferenceNode,
|
||||
isTypeReferenceType,
|
||||
isUMDExportSymbol,
|
||||
isValidBigIntString,
|
||||
isValidESSymbolDeclaration,
|
||||
isValidTypeOnlyAliasUseSite,
|
||||
isValueSignatureDeclaration,
|
||||
@ -827,6 +827,7 @@ import {
|
||||
parseIsolatedEntityName,
|
||||
parseNodeFactory,
|
||||
parsePseudoBigInt,
|
||||
parseValidBigInt,
|
||||
Path,
|
||||
pathIsRelative,
|
||||
PatternAmbientModule,
|
||||
@ -7691,10 +7692,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
if (isSingleOrDoubleQuote(firstChar) && some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) {
|
||||
return factory.createStringLiteral(getSpecifierForModuleSymbol(symbol, context));
|
||||
}
|
||||
const canUsePropertyAccess = firstChar === CharacterCodes.hash ?
|
||||
symbolName.length > 1 && isIdentifierStart(symbolName.charCodeAt(1), languageVersion) :
|
||||
isIdentifierStart(firstChar, languageVersion);
|
||||
if (index === 0 || canUsePropertyAccess) {
|
||||
if (index === 0 || canUsePropertyAccess(symbolName, languageVersion)) {
|
||||
const identifier = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping);
|
||||
identifier.symbol = symbol;
|
||||
|
||||
@ -23533,35 +23531,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
* @param text a valid bigint string excluding a trailing `n`, but including a possible prefix `-`. Use `isValidBigIntString(text, roundTripOnly)` before calling this function.
|
||||
*/
|
||||
function parseBigIntLiteralType(text: string) {
|
||||
const negative = text.startsWith("-");
|
||||
const base10Value = parsePseudoBigInt(`${negative ? text.slice(1) : text}n`);
|
||||
return getBigIntLiteralType({ negative, base10Value });
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the provided string can be parsed as a bigint.
|
||||
* @param s The string to test.
|
||||
* @param roundTripOnly Indicates the resulting bigint matches the input when converted back to a string.
|
||||
*/
|
||||
function isValidBigIntString(s: string, roundTripOnly: boolean): boolean {
|
||||
if (s === "") return false;
|
||||
const scanner = createScanner(ScriptTarget.ESNext, /*skipTrivia*/ false);
|
||||
let success = true;
|
||||
scanner.setOnError(() => success = false);
|
||||
scanner.setText(s + "n");
|
||||
let result = scanner.scan();
|
||||
const negative = result === SyntaxKind.MinusToken;
|
||||
if (negative) {
|
||||
result = scanner.scan();
|
||||
}
|
||||
const flags = scanner.getTokenFlags();
|
||||
// validate that
|
||||
// * scanning proceeded without error
|
||||
// * a bigint can be scanned, and that when it is scanned, it is
|
||||
// * the full length of the input string (so the scanner is one character beyond the augmented input length)
|
||||
// * it does not contain a numeric seperator (the `BigInt` constructor does not accept a numeric seperator in its input)
|
||||
return success && result === SyntaxKind.BigIntLiteral && scanner.getTextPos() === (s.length + 1) && !(flags & TokenFlags.ContainsSeparator)
|
||||
&& (!roundTripOnly || s === pseudoBigIntToString({ negative, base10Value: parsePseudoBigInt(scanner.getTokenValue()) }));
|
||||
return getBigIntLiteralType(parseValidBigInt(text));
|
||||
}
|
||||
|
||||
function isMemberOfStringMapping(source: Type, target: Type): boolean {
|
||||
|
||||
@ -187,13 +187,14 @@ import {
|
||||
getResolutionMode,
|
||||
getResolutionName,
|
||||
getRootLength,
|
||||
getSnippetElement,
|
||||
getStringComparer,
|
||||
getSymbolId,
|
||||
getTrailingCommentRanges,
|
||||
HasExpressionInitializer,
|
||||
hasExtension,
|
||||
hasInitializer,
|
||||
HasInitializer,
|
||||
hasInitializer,
|
||||
HasJSDoc,
|
||||
hasJSDocNodes,
|
||||
HasModifiers,
|
||||
@ -257,6 +258,7 @@ import {
|
||||
isGetAccessorDeclaration,
|
||||
isHeritageClause,
|
||||
isIdentifier,
|
||||
isIdentifierStart,
|
||||
isIdentifierText,
|
||||
isImportTypeNode,
|
||||
isInterfaceDeclaration,
|
||||
@ -440,6 +442,7 @@ import {
|
||||
singleOrUndefined,
|
||||
skipOuterExpressions,
|
||||
skipTrivia,
|
||||
SnippetKind,
|
||||
some,
|
||||
sort,
|
||||
SortedArray,
|
||||
@ -8555,6 +8558,51 @@ export function pseudoBigIntToString({negative, base10Value}: PseudoBigInt): str
|
||||
return (negative && base10Value !== "0" ? "-" : "") + base10Value;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function parseBigInt(text: string): PseudoBigInt | undefined {
|
||||
if (!isValidBigIntString(text, /*roundTripOnly*/ false)) {
|
||||
return undefined;
|
||||
}
|
||||
return parseValidBigInt(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param text a valid bigint string excluding a trailing `n`, but including a possible prefix `-`. Use `isValidBigIntString(text, roundTripOnly)` before calling this function.
|
||||
*/
|
||||
export function parseValidBigInt(text: string): PseudoBigInt {
|
||||
const negative = text.startsWith("-");
|
||||
const base10Value = parsePseudoBigInt(`${negative ? text.slice(1) : text}n`);
|
||||
return { negative, base10Value };
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Tests whether the provided string can be parsed as a bigint.
|
||||
* @param s The string to test.
|
||||
* @param roundTripOnly Indicates the resulting bigint matches the input when converted back to a string.
|
||||
*/
|
||||
export function isValidBigIntString(s: string, roundTripOnly: boolean): boolean {
|
||||
if (s === "") return false;
|
||||
const scanner = createScanner(ScriptTarget.ESNext, /*skipTrivia*/ false);
|
||||
let success = true;
|
||||
scanner.setOnError(() => success = false);
|
||||
scanner.setText(s + "n");
|
||||
let result = scanner.scan();
|
||||
const negative = result === SyntaxKind.MinusToken;
|
||||
if (negative) {
|
||||
result = scanner.scan();
|
||||
}
|
||||
const flags = scanner.getTokenFlags();
|
||||
// validate that
|
||||
// * scanning proceeded without error
|
||||
// * a bigint can be scanned, and that when it is scanned, it is
|
||||
// * the full length of the input string (so the scanner is one character beyond the augmented input length)
|
||||
// * it does not contain a numeric seperator (the `BigInt` constructor does not accept a numeric seperator in its input)
|
||||
return success && result === SyntaxKind.BigIntLiteral && scanner.getTextPos() === (s.length + 1) && !(flags & TokenFlags.ContainsSeparator)
|
||||
&& (!roundTripOnly || s === pseudoBigIntToString({ negative, base10Value: parsePseudoBigInt(scanner.getTokenValue()) }));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isValidTypeOnlyAliasUseSite(useSite: Node): boolean {
|
||||
return !!(useSite.flags & NodeFlags.Ambient)
|
||||
@ -9062,4 +9110,21 @@ export function isOptionalJSDocPropertyLikeTag(node: Node): node is JSDocPropert
|
||||
}
|
||||
const { isBracketed, typeExpression } = node;
|
||||
return isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType;
|
||||
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function canUsePropertyAccess(name: string, languageVersion: ScriptTarget): boolean {
|
||||
if (name.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const firstChar = name.charCodeAt(0);
|
||||
return firstChar === CharacterCodes.hash ?
|
||||
name.length > 1 && isIdentifierStart(name.charCodeAt(1), languageVersion) :
|
||||
isIdentifierStart(firstChar, languageVersion);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function hasTabstop(node: Node): boolean {
|
||||
return getSnippetElement(node)?.kind === SnippetKind.TabStop;
|
||||
}
|
||||
|
||||
@ -5,6 +5,9 @@ import {
|
||||
BinaryExpression,
|
||||
BreakOrContinueStatement,
|
||||
CancellationToken,
|
||||
canUsePropertyAccess,
|
||||
CaseBlock,
|
||||
CaseClause,
|
||||
cast,
|
||||
CharacterCodes,
|
||||
ClassElement,
|
||||
@ -40,11 +43,15 @@ import {
|
||||
createTextSpanFromRange,
|
||||
Debug,
|
||||
Declaration,
|
||||
DefaultClause,
|
||||
Diagnostics,
|
||||
diagnosticToString,
|
||||
displayPart,
|
||||
EmitHint,
|
||||
EmitTextWriter,
|
||||
endsWith,
|
||||
EntityName,
|
||||
EnumMember,
|
||||
escapeSnippetText,
|
||||
every,
|
||||
ExportKind,
|
||||
@ -106,6 +113,7 @@ import {
|
||||
ImportSpecifier,
|
||||
ImportTypeNode,
|
||||
IncompleteCompletionsCache,
|
||||
IndexedAccessTypeNode,
|
||||
insertSorted,
|
||||
InternalSymbolName,
|
||||
isAbstractConstructorSymbol,
|
||||
@ -117,6 +125,7 @@ import {
|
||||
isBindingPattern,
|
||||
isBreakOrContinueStatement,
|
||||
isCallExpression,
|
||||
isCaseBlock,
|
||||
isCaseClause,
|
||||
isCheckJsEnabledForFile,
|
||||
isClassElement,
|
||||
@ -128,8 +137,10 @@ import {
|
||||
isConstructorDeclaration,
|
||||
isContextualKeyword,
|
||||
isDeclarationName,
|
||||
isDefaultClause,
|
||||
isDeprecatedDeclaration,
|
||||
isEntityName,
|
||||
isEnumMember,
|
||||
isEqualityOperatorKind,
|
||||
isExportAssignment,
|
||||
isExportDeclaration,
|
||||
@ -169,6 +180,7 @@ import {
|
||||
isKeyword,
|
||||
isKnownSymbol,
|
||||
isLabeledStatement,
|
||||
isLiteralExpression,
|
||||
isLiteralImportTypeNode,
|
||||
isMemberName,
|
||||
isMethodDeclaration,
|
||||
@ -238,6 +250,9 @@ import {
|
||||
lastOrUndefined,
|
||||
length,
|
||||
ListFormat,
|
||||
LiteralType,
|
||||
LiteralTypeNode,
|
||||
map,
|
||||
mapDefined,
|
||||
maybeBind,
|
||||
MemberOverrideStatus,
|
||||
@ -257,11 +272,14 @@ import {
|
||||
NodeBuilderFlags,
|
||||
NodeFlags,
|
||||
nodeIsMissing,
|
||||
NumericLiteral,
|
||||
ObjectBindingPattern,
|
||||
ObjectLiteralExpression,
|
||||
ObjectType,
|
||||
ObjectTypeDeclaration,
|
||||
or,
|
||||
ParenthesizedTypeNode,
|
||||
parseBigInt,
|
||||
positionBelongsToNode,
|
||||
positionIsASICandidate,
|
||||
positionsAreOnSameLine,
|
||||
@ -324,7 +342,10 @@ import {
|
||||
TypeFlags,
|
||||
typeHasCallOrConstructSignatures,
|
||||
TypeLiteralNode,
|
||||
TypeNode,
|
||||
TypeOnlyAliasDeclaration,
|
||||
TypeQueryNode,
|
||||
TypeReferenceNode,
|
||||
unescapeLeadingUnderscores,
|
||||
UnionReduction,
|
||||
UnionType,
|
||||
@ -394,6 +415,8 @@ export enum CompletionSource {
|
||||
TypeOnlyAlias = "TypeOnlyAlias/",
|
||||
/** Auto-import that comes attached to an object literal method snippet */
|
||||
ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/",
|
||||
/** Case completions for switch statements */
|
||||
SwitchCases = "SwitchCases/",
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@ -915,6 +938,16 @@ function completionInfoFromData(
|
||||
getJSCompletionEntries(sourceFile, location.pos, uniqueNames, getEmitScriptTarget(compilerOptions), entries);
|
||||
}
|
||||
|
||||
let caseBlock: CaseBlock | undefined;
|
||||
if (preferences.includeCompletionsWithInsertText
|
||||
&& contextToken
|
||||
&& (caseBlock = findAncestor(contextToken, isCaseBlock))) {
|
||||
const cases = getExhaustiveCaseSnippets(caseBlock, sourceFile, preferences, compilerOptions, host, program, formatContext);
|
||||
if (cases) {
|
||||
entries.push(cases.entry);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
flags: completionData.flags,
|
||||
isGlobalCompletion: isInSnippetScope,
|
||||
@ -930,6 +963,224 @@ function isCheckedFile(sourceFile: SourceFile, compilerOptions: CompilerOptions)
|
||||
return !isSourceFileJS(sourceFile) || !!isCheckJsEnabledForFile(sourceFile, compilerOptions);
|
||||
}
|
||||
|
||||
function getExhaustiveCaseSnippets(
|
||||
caseBlock: CaseBlock,
|
||||
sourceFile: SourceFile,
|
||||
preferences: UserPreferences,
|
||||
options: CompilerOptions,
|
||||
host: LanguageServiceHost,
|
||||
program: Program,
|
||||
formatContext: formatting.FormatContext | undefined): { entry: CompletionEntry, importAdder: codefix.ImportAdder } | undefined {
|
||||
|
||||
const clauses = caseBlock.clauses;
|
||||
const checker = program.getTypeChecker();
|
||||
const switchType = checker.getTypeAtLocation(caseBlock.parent.expression);
|
||||
if (switchType && switchType.isUnion() && every(switchType.types, type => type.isLiteral())) {
|
||||
// Collect constant values in existing clauses.
|
||||
const tracker = newCaseClauseTracker(checker, clauses);
|
||||
|
||||
const target = getEmitScriptTarget(options);
|
||||
const quotePreference = getQuotePreference(sourceFile, preferences);
|
||||
const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host);
|
||||
const elements: Expression[] = [];
|
||||
for (const type of switchType.types as LiteralType[]) {
|
||||
// Enums
|
||||
if (type.flags & TypeFlags.EnumLiteral) {
|
||||
Debug.assert(type.symbol, "An enum member type should have a symbol");
|
||||
Debug.assert(type.symbol.parent, "An enum member type should have a parent symbol (the enum symbol)");
|
||||
// Filter existing enums by their values
|
||||
const enumValue = type.symbol.valueDeclaration && checker.getConstantValue(type.symbol.valueDeclaration as EnumMember);
|
||||
if (enumValue !== undefined) {
|
||||
if (tracker.hasValue(enumValue)) {
|
||||
continue;
|
||||
}
|
||||
tracker.addValue(enumValue);
|
||||
}
|
||||
const typeNode = codefix.typeToAutoImportableTypeNode(checker, importAdder, type, caseBlock, target);
|
||||
if (!typeNode) {
|
||||
return undefined;
|
||||
}
|
||||
const expr = typeNodeToExpression(typeNode, target, quotePreference);
|
||||
if (!expr) {
|
||||
return undefined;
|
||||
}
|
||||
elements.push(expr);
|
||||
}
|
||||
// Literals
|
||||
else if (!tracker.hasValue(type.value)) {
|
||||
switch (typeof type.value) {
|
||||
case "object":
|
||||
elements.push(factory.createBigIntLiteral(type.value));
|
||||
break;
|
||||
case "number":
|
||||
elements.push(factory.createNumericLiteral(type.value));
|
||||
break;
|
||||
case "string":
|
||||
elements.push(factory.createStringLiteral(type.value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (elements.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const newClauses = map(elements, element => factory.createCaseClause(element, []));
|
||||
const newLineChar = getNewLineCharacter(options, maybeBind(host, host.getNewLine));
|
||||
const printer = createSnippetPrinter({
|
||||
removeComments: true,
|
||||
module: options.module,
|
||||
target: options.target,
|
||||
newLine: getNewLineKind(newLineChar),
|
||||
});
|
||||
const printNode = formatContext
|
||||
? (node: Node) => printer.printAndFormatNode(EmitHint.Unspecified, node, sourceFile, formatContext)
|
||||
: (node: Node) => printer.printNode(EmitHint.Unspecified, node, sourceFile);
|
||||
const insertText = map(newClauses, (clause, i) => {
|
||||
if (preferences.includeCompletionsWithSnippetText) {
|
||||
return `${printNode(clause)}$${i+1}`;
|
||||
}
|
||||
return `${printNode(clause)}`;
|
||||
}).join(newLineChar);
|
||||
|
||||
const firstClause = printer.printNode(EmitHint.Unspecified, newClauses[0], sourceFile);
|
||||
return {
|
||||
entry: {
|
||||
name: `${firstClause} ...`,
|
||||
kind: ScriptElementKind.unknown,
|
||||
sortText: SortText.GlobalsOrKeywords,
|
||||
insertText,
|
||||
hasAction: importAdder.hasFixes() || undefined,
|
||||
source: CompletionSource.SwitchCases,
|
||||
isSnippet: preferences.includeCompletionsWithSnippetText ? true : undefined,
|
||||
},
|
||||
importAdder,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
interface CaseClauseTracker {
|
||||
addValue(value: string | number): void;
|
||||
hasValue(value: string | number | PseudoBigInt): boolean;
|
||||
}
|
||||
|
||||
function newCaseClauseTracker(checker: TypeChecker, clauses: readonly (CaseClause | DefaultClause)[]): CaseClauseTracker {
|
||||
const existingStrings = new Set<string>();
|
||||
const existingNumbers = new Set<number>();
|
||||
const existingBigInts = new Set<string>();
|
||||
|
||||
for (const clause of clauses) {
|
||||
if (!isDefaultClause(clause)) {
|
||||
if (isLiteralExpression(clause.expression)) {
|
||||
const expression = clause.expression;
|
||||
switch (expression.kind) {
|
||||
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
||||
case SyntaxKind.StringLiteral:
|
||||
existingStrings.add(expression.text);
|
||||
break;
|
||||
case SyntaxKind.NumericLiteral:
|
||||
existingNumbers.add(parseInt(expression.text));
|
||||
break;
|
||||
case SyntaxKind.BigIntLiteral:
|
||||
const parsedBigInt = parseBigInt(endsWith(expression.text, "n") ? expression.text.slice(0, -1) : expression.text);
|
||||
if (parsedBigInt) {
|
||||
existingBigInts.add(pseudoBigIntToString(parsedBigInt));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const symbol = checker.getSymbolAtLocation(clause.expression);
|
||||
if (symbol && symbol.valueDeclaration && isEnumMember(symbol.valueDeclaration)) {
|
||||
const enumValue = checker.getConstantValue(symbol.valueDeclaration);
|
||||
if (enumValue !== undefined) {
|
||||
addValue(enumValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
addValue,
|
||||
hasValue,
|
||||
};
|
||||
|
||||
function addValue(value: string | number) {
|
||||
switch (typeof value) {
|
||||
case "string":
|
||||
existingStrings.add(value);
|
||||
break;
|
||||
case "number":
|
||||
existingNumbers.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
function hasValue(value: string | number | PseudoBigInt): boolean {
|
||||
switch (typeof value) {
|
||||
case "string":
|
||||
return existingStrings.has(value);
|
||||
case "number":
|
||||
return existingNumbers.has(value);
|
||||
case "object":
|
||||
return existingBigInts.has(pseudoBigIntToString(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function typeNodeToExpression(typeNode: TypeNode, languageVersion: ScriptTarget, quotePreference: QuotePreference): Expression | undefined {
|
||||
switch (typeNode.kind) {
|
||||
case SyntaxKind.TypeReference:
|
||||
const typeName = (typeNode as TypeReferenceNode).typeName;
|
||||
return entityNameToExpression(typeName, languageVersion, quotePreference);
|
||||
case SyntaxKind.IndexedAccessType:
|
||||
const objectExpression =
|
||||
typeNodeToExpression((typeNode as IndexedAccessTypeNode).objectType, languageVersion, quotePreference);
|
||||
const indexExpression =
|
||||
typeNodeToExpression((typeNode as IndexedAccessTypeNode).indexType, languageVersion, quotePreference);
|
||||
return objectExpression
|
||||
&& indexExpression
|
||||
&& factory.createElementAccessExpression(objectExpression, indexExpression);
|
||||
case SyntaxKind.LiteralType:
|
||||
const literal = (typeNode as LiteralTypeNode).literal;
|
||||
switch (literal.kind) {
|
||||
case SyntaxKind.StringLiteral:
|
||||
return factory.createStringLiteral(literal.text, quotePreference === QuotePreference.Single);
|
||||
case SyntaxKind.NumericLiteral:
|
||||
return factory.createNumericLiteral(literal.text, (literal as NumericLiteral).numericLiteralFlags);
|
||||
}
|
||||
return undefined;
|
||||
case SyntaxKind.ParenthesizedType:
|
||||
const exp = typeNodeToExpression((typeNode as ParenthesizedTypeNode).type, languageVersion, quotePreference);
|
||||
return exp && (isIdentifier(exp) ? exp : factory.createParenthesizedExpression(exp));
|
||||
case SyntaxKind.TypeQuery:
|
||||
return entityNameToExpression((typeNode as TypeQueryNode).exprName, languageVersion, quotePreference);
|
||||
case SyntaxKind.ImportType:
|
||||
Debug.fail(`We should not get an import type after calling 'codefix.typeToAutoImportableTypeNode'.`);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function entityNameToExpression(entityName: EntityName, languageVersion: ScriptTarget, quotePreference: QuotePreference): Expression {
|
||||
if (isIdentifier(entityName)) {
|
||||
return entityName;
|
||||
}
|
||||
const unescapedName = unescapeLeadingUnderscores(entityName.right.escapedText);
|
||||
if (canUsePropertyAccess(unescapedName, languageVersion)) {
|
||||
return factory.createPropertyAccessExpression(
|
||||
entityNameToExpression(entityName.left, languageVersion, quotePreference),
|
||||
unescapedName);
|
||||
}
|
||||
else {
|
||||
return factory.createElementAccessExpression(
|
||||
entityNameToExpression(entityName.left, languageVersion, quotePreference),
|
||||
factory.createStringLiteral(unescapedName, quotePreference === QuotePreference.Single));
|
||||
}
|
||||
}
|
||||
|
||||
function isMemberCompletionKind(kind: CompletionKind): boolean {
|
||||
switch (kind) {
|
||||
case CompletionKind.ObjectPropertyDeclaration:
|
||||
@ -1563,6 +1814,8 @@ function createSnippetPrinter(
|
||||
return {
|
||||
printSnippetList,
|
||||
printAndFormatSnippetList,
|
||||
printNode,
|
||||
printAndFormatNode,
|
||||
};
|
||||
|
||||
// The formatter/scanner will have issues with snippet-escaped text,
|
||||
@ -1582,7 +1835,7 @@ function createSnippetPrinter(
|
||||
}
|
||||
}
|
||||
|
||||
/* Snippet-escaping version of `printer.printList`. */
|
||||
/** Snippet-escaping version of `printer.printList`. */
|
||||
function printSnippetList(
|
||||
format: ListFormat,
|
||||
list: NodeArray<Node>,
|
||||
@ -1636,6 +1889,50 @@ function createSnippetPrinter(
|
||||
: changes;
|
||||
return textChanges.applyChanges(syntheticFile.text, allChanges);
|
||||
}
|
||||
|
||||
/** Snippet-escaping version of `printer.printNode`. */
|
||||
function printNode(hint: EmitHint, node: Node, sourceFile: SourceFile): string {
|
||||
const unescaped = printUnescapedNode(hint, node, sourceFile);
|
||||
return escapes ? textChanges.applyChanges(unescaped, escapes) : unescaped;
|
||||
}
|
||||
|
||||
function printUnescapedNode(hint: EmitHint, node: Node, sourceFile: SourceFile): string {
|
||||
escapes = undefined;
|
||||
writer.clear();
|
||||
printer.writeNode(hint, node, sourceFile, writer);
|
||||
return writer.getText();
|
||||
}
|
||||
|
||||
function printAndFormatNode(
|
||||
hint: EmitHint,
|
||||
node: Node,
|
||||
sourceFile: SourceFile,
|
||||
formatContext: formatting.FormatContext): string {
|
||||
const syntheticFile = {
|
||||
text: printUnescapedNode(
|
||||
hint,
|
||||
node,
|
||||
sourceFile),
|
||||
getLineAndCharacterOfPosition(pos: number) {
|
||||
return getLineAndCharacterOfPosition(this, pos);
|
||||
},
|
||||
};
|
||||
|
||||
const formatOptions = getFormatCodeSettingsForWriting(formatContext, sourceFile);
|
||||
const nodeWithPos = textChanges.assignPositionsToNode(node);
|
||||
const changes = formatting.formatNodeGivenIndentation(
|
||||
nodeWithPos,
|
||||
syntheticFile,
|
||||
sourceFile.languageVariant,
|
||||
/* indentation */ 0,
|
||||
/* delta */ 0,
|
||||
{ ...formatContext, options: formatOptions });
|
||||
|
||||
const allChanges = escapes
|
||||
? stableSort(concatenate(changes, escapes), (a, b) => compareTextSpans(a.span, b.span))
|
||||
: changes;
|
||||
return textChanges.applyChanges(syntheticFile.text, allChanges);
|
||||
}
|
||||
}
|
||||
|
||||
function originToCompletionEntryData(origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport): CompletionEntryData | undefined {
|
||||
@ -1928,7 +2225,10 @@ function getSymbolCompletionFromEntryId(
|
||||
entryId: CompletionEntryIdentifier,
|
||||
host: LanguageServiceHost,
|
||||
preferences: UserPreferences,
|
||||
): SymbolCompletion | { type: "request", request: Request } | { type: "literal", literal: string | number | PseudoBigInt } | { type: "none" } {
|
||||
): SymbolCompletion | { type: "request", request: Request } | { type: "literal", literal: string | number | PseudoBigInt } | { type: "cases" } | { type: "none" } {
|
||||
if (entryId.source === CompletionSource.SwitchCases) {
|
||||
return { type: "cases" };
|
||||
}
|
||||
if (entryId.data) {
|
||||
const autoImport = getAutoImportSymbolFromCompletionEntryData(entryId.name, entryId.data, program, host);
|
||||
if (autoImport) {
|
||||
@ -1999,9 +2299,9 @@ export function getCompletionEntryDetails(
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const { name, source, data } = entryId;
|
||||
|
||||
const contextToken = findPrecedingToken(position, sourceFile);
|
||||
if (isInString(sourceFile, position, contextToken)) {
|
||||
return StringCompletions.getStringLiteralCompletionDetails(name, sourceFile, position, contextToken, typeChecker, compilerOptions, host, cancellationToken, preferences);
|
||||
const { previousToken, contextToken } = getRelevantTokens(position, sourceFile);
|
||||
if (isInString(sourceFile, position, previousToken)) {
|
||||
return StringCompletions.getStringLiteralCompletionDetails(name, sourceFile, position, previousToken, typeChecker, compilerOptions, host, cancellationToken, preferences);
|
||||
}
|
||||
|
||||
// Compute all the completion symbols again.
|
||||
@ -2031,6 +2331,39 @@ export function getCompletionEntryDetails(
|
||||
const { literal } = symbolCompletion;
|
||||
return createSimpleDetails(completionNameForLiteral(sourceFile, preferences, literal), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral);
|
||||
}
|
||||
case "cases": {
|
||||
const { entry, importAdder } = getExhaustiveCaseSnippets(
|
||||
contextToken!.parent as CaseBlock,
|
||||
sourceFile,
|
||||
preferences,
|
||||
program.getCompilerOptions(),
|
||||
host,
|
||||
program,
|
||||
/*formatContext*/ undefined)!;
|
||||
if (importAdder.hasFixes()) {
|
||||
const changes = textChanges.ChangeTracker.with(
|
||||
{ host, formatContext, preferences },
|
||||
importAdder.writeFixes);
|
||||
return {
|
||||
name: entry.name,
|
||||
kind: ScriptElementKind.unknown,
|
||||
kindModifiers: "",
|
||||
displayParts: [],
|
||||
sourceDisplay: undefined,
|
||||
codeActions: [{
|
||||
changes,
|
||||
description: diagnosticToString([Diagnostics.Includes_imports_of_types_referenced_by_0, name]),
|
||||
}],
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: entry.name,
|
||||
kind: ScriptElementKind.unknown,
|
||||
kindModifiers: "",
|
||||
displayParts: [],
|
||||
sourceDisplay: undefined,
|
||||
};
|
||||
}
|
||||
case "none":
|
||||
// Didn't find a symbol with this name. See if we can find a keyword instead.
|
||||
return allKeywordsCompletions().some(c => c.name === name) ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined;
|
||||
|
||||
@ -122,6 +122,7 @@ import {
|
||||
hasProperty,
|
||||
hasStaticModifier,
|
||||
hasSyntacticModifier,
|
||||
hasTabstop,
|
||||
HighlightSpanKind,
|
||||
HostCancellationToken,
|
||||
hostGetCanonicalFileName,
|
||||
@ -182,8 +183,8 @@ import {
|
||||
isTagName,
|
||||
isTextWhiteSpaceLike,
|
||||
isThisTypeParameter,
|
||||
JsDoc,
|
||||
JSDoc,
|
||||
JsDoc,
|
||||
JSDocContainer,
|
||||
JSDocTagInfo,
|
||||
JsonSourceFile,
|
||||
@ -497,6 +498,9 @@ function addSyntheticNodes(nodes: Push<Node>, pos: number, end: number, parent:
|
||||
const textPos = scanner.getTextPos();
|
||||
if (textPos <= end) {
|
||||
if (token === SyntaxKind.Identifier) {
|
||||
if (hasTabstop(parent)) {
|
||||
continue;
|
||||
}
|
||||
Debug.fail(`Did not expect ${Debug.formatSyntaxKind(parent.kind)} to have an Identifier in its trivia`);
|
||||
}
|
||||
nodes.push(createNode(token, pos, textPos, parent));
|
||||
|
||||
107
tests/cases/fourslash/exhaustiveCaseCompletions1.ts
Normal file
107
tests/cases/fourslash/exhaustiveCaseCompletions1.ts
Normal file
@ -0,0 +1,107 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// Basic tests
|
||||
|
||||
// @newline: LF
|
||||
//// enum E {
|
||||
//// A = 0,
|
||||
//// B = "B",
|
||||
//// C = "C",
|
||||
//// }
|
||||
//// // Mixed union
|
||||
//// declare const u: E.A | E.B | 1;
|
||||
//// switch (u) {
|
||||
//// case/*1*/
|
||||
//// }
|
||||
//// // Union enum
|
||||
//// declare const e: E;
|
||||
//// switch (e) {
|
||||
//// case/*2*/
|
||||
//// }
|
||||
//// enum F {
|
||||
//// D = 1 << 0,
|
||||
//// E = 1 << 1,
|
||||
//// F = 1 << 2,
|
||||
//// }
|
||||
////
|
||||
//// declare const f: F;
|
||||
//// switch (f) {
|
||||
//// case/*3*/
|
||||
//// }
|
||||
|
||||
verify.completions(
|
||||
{
|
||||
marker: "1",
|
||||
isNewIdentifierLocation: false,
|
||||
includes: [
|
||||
{
|
||||
name: "case E.A: ...",
|
||||
source: completion.CompletionSource.SwitchCases,
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
insertText:
|
||||
`case E.A:
|
||||
case E.B:
|
||||
case 1:`,
|
||||
},
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
marker: "2",
|
||||
isNewIdentifierLocation: false,
|
||||
includes: [
|
||||
{
|
||||
name: "case E.A: ...",
|
||||
source: completion.CompletionSource.SwitchCases,
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
insertText:
|
||||
`case E.A:
|
||||
case E.B:
|
||||
case E.C:`,
|
||||
},
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
marker: "3",
|
||||
isNewIdentifierLocation: false,
|
||||
includes: [
|
||||
{
|
||||
name: "case F.D: ...",
|
||||
source: completion.CompletionSource.SwitchCases,
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
insertText:
|
||||
`case F.D:
|
||||
case F.E:
|
||||
case F.F:`,
|
||||
},
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
marker: "3",
|
||||
isNewIdentifierLocation: false,
|
||||
includes: [
|
||||
{
|
||||
name: "case F.D: ...",
|
||||
source: completion.CompletionSource.SwitchCases,
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
isSnippet: true,
|
||||
insertText:
|
||||
`case F.D:$1
|
||||
case F.E:$2
|
||||
case F.F:$3`,
|
||||
},
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
77
tests/cases/fourslash/exhaustiveCaseCompletions2.ts
Normal file
77
tests/cases/fourslash/exhaustiveCaseCompletions2.ts
Normal file
@ -0,0 +1,77 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// Import-related cases
|
||||
|
||||
// @newline: LF
|
||||
// @Filename: /dep.ts
|
||||
//// export enum E {
|
||||
//// A = 0,
|
||||
//// B = "B",
|
||||
//// C = "C",
|
||||
//// }
|
||||
//// declare const u: E.A | E.B | 1;
|
||||
//// export { u };
|
||||
|
||||
// @Filename: /main.ts
|
||||
//// import { u } from "./dep";
|
||||
//// switch (u) {
|
||||
//// case/*1*/
|
||||
//// }
|
||||
|
||||
// @Filename: /other.ts
|
||||
//// import * as d from "./dep";
|
||||
//// declare const u: d.E;
|
||||
//// switch (u) {
|
||||
//// case/*2*/
|
||||
//// }
|
||||
|
||||
verify.completions(
|
||||
{
|
||||
marker: "1",
|
||||
isNewIdentifierLocation: false,
|
||||
includes: [
|
||||
{
|
||||
name: "case E.A: ...",
|
||||
source: completion.CompletionSource.SwitchCases,
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
insertText:
|
||||
`case E.A:
|
||||
case E.B:
|
||||
case 1:`,
|
||||
hasAction: true,
|
||||
},
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
marker: "2",
|
||||
isNewIdentifierLocation: false,
|
||||
includes: [
|
||||
{
|
||||
name: "case d.E.A: ...",
|
||||
source: completion.CompletionSource.SwitchCases,
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
insertText:
|
||||
`case d.E.A:
|
||||
case d.E.B:
|
||||
case d.E.C:`,
|
||||
},
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
verify.applyCodeActionFromCompletion("1", {
|
||||
name: "case E.A: ...",
|
||||
source: "SwitchCases/",
|
||||
description: "Includes imports of types referenced by 'case E.A: ...'",
|
||||
newFileContent:
|
||||
`import { E, u } from "./dep";
|
||||
switch (u) {
|
||||
case
|
||||
}`,
|
||||
});
|
||||
112
tests/cases/fourslash/exhaustiveCaseCompletions3.ts
Normal file
112
tests/cases/fourslash/exhaustiveCaseCompletions3.ts
Normal file
@ -0,0 +1,112 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// Where the exhaustive case completion appears or not.
|
||||
|
||||
// @newline: LF
|
||||
// @Filename: /main.ts
|
||||
//// enum E {
|
||||
//// A = 0,
|
||||
//// B = "B",
|
||||
//// C = "C",
|
||||
//// }
|
||||
//// declare const u: E;
|
||||
//// switch (u) {
|
||||
//// case/*1*/
|
||||
//// }
|
||||
//// switch (u) {
|
||||
//// /*2*/
|
||||
//// }
|
||||
//// switch (u) {
|
||||
//// case 1:
|
||||
//// /*3*/
|
||||
//// }
|
||||
//// switch (u) {
|
||||
//// c/*4*/
|
||||
//// }
|
||||
//// switch (u) {
|
||||
//// case /*5*/
|
||||
//// }
|
||||
//// /*6*/
|
||||
//// switch (u) {
|
||||
//// /*7*/
|
||||
////
|
||||
|
||||
const exhaustiveCaseCompletion = {
|
||||
name: "case E.A: ...",
|
||||
source: completion.CompletionSource.SwitchCases,
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
insertText:
|
||||
`case E.A:
|
||||
case E.B:
|
||||
case E.C:`,
|
||||
};
|
||||
|
||||
verify.completions(
|
||||
{
|
||||
marker: "1",
|
||||
isNewIdentifierLocation: false,
|
||||
includes: [
|
||||
exhaustiveCaseCompletion,
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
marker: "2",
|
||||
includes: [
|
||||
exhaustiveCaseCompletion,
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
marker: "3",
|
||||
includes: [
|
||||
exhaustiveCaseCompletion,
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
marker: "4",
|
||||
includes: [
|
||||
exhaustiveCaseCompletion,
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
marker: "5",
|
||||
includes: [
|
||||
exhaustiveCaseCompletion,
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
marker: "6",
|
||||
exact: [
|
||||
"E",
|
||||
"u",
|
||||
...completion.globals,
|
||||
exhaustiveCaseCompletion,
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
marker: "7",
|
||||
includes: [
|
||||
exhaustiveCaseCompletion,
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
}
|
||||
},
|
||||
);
|
||||
179
tests/cases/fourslash/exhaustiveCaseCompletions4.ts
Normal file
179
tests/cases/fourslash/exhaustiveCaseCompletions4.ts
Normal file
@ -0,0 +1,179 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// Filter existing values.
|
||||
|
||||
// @newline: LF
|
||||
//// enum E {
|
||||
//// A = 0,
|
||||
//// B = "B",
|
||||
//// C = "C",
|
||||
//// }
|
||||
//// // Filtering existing literals
|
||||
//// declare const u: E.A | E.B | 1 | 1n | "1";
|
||||
//// switch (u) {
|
||||
//// case E.A:
|
||||
//// case 1:
|
||||
//// case 1n:
|
||||
//// case 0x1n:
|
||||
//// case "1":
|
||||
//// case `1`:
|
||||
//// case `1${u}`:
|
||||
//// case/*1*/
|
||||
//// }
|
||||
//// declare const v: E.A | "1" | "2";
|
||||
//// switch (v) {
|
||||
//// case 0:
|
||||
//// case `1`:
|
||||
//// /*2*/
|
||||
//// }
|
||||
//// // Filtering repreated enum members
|
||||
//// enum F {
|
||||
//// A = "A",
|
||||
//// B = "B",
|
||||
//// C = A,
|
||||
//// }
|
||||
//// declare const x: F;
|
||||
//// switch (x) {
|
||||
//// /*3*/
|
||||
//// }
|
||||
//// // Enum with computed elements
|
||||
//// enum G {
|
||||
//// C = 0,
|
||||
//// D = 1 << 1,
|
||||
//// E = 1 << 2,
|
||||
//// OtherD = D,
|
||||
//// DorE = D | E,
|
||||
//// }
|
||||
//// declare const y: G;
|
||||
//// switch (y) {
|
||||
//// /*4*/
|
||||
//// }
|
||||
//// switch (y) {
|
||||
//// case 0: // same as G.C
|
||||
//// case 1: // same as G.D, but we don't know it
|
||||
//// case 3: // same as G.DorE, but we don't know
|
||||
//// /*5*/
|
||||
//// }
|
||||
////
|
||||
//// // Already exhaustive switch
|
||||
//// enum H {
|
||||
//// A = "A",
|
||||
//// B = "B",
|
||||
//// C = "C",
|
||||
//// }
|
||||
//// declare const z: H;
|
||||
//// switch (z) {
|
||||
//// case H.A:
|
||||
//// case H.B:
|
||||
//// case H.C:
|
||||
//// /*6*/
|
||||
//// }
|
||||
|
||||
verify.completions(
|
||||
{
|
||||
marker: "1",
|
||||
isNewIdentifierLocation: false,
|
||||
includes: [
|
||||
{
|
||||
name: "case E.B: ...",
|
||||
source: completion.CompletionSource.SwitchCases,
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
insertText:
|
||||
`case E.B:`,
|
||||
},
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
marker: "2",
|
||||
isNewIdentifierLocation: false,
|
||||
includes: [
|
||||
{
|
||||
name: `case "2": ...`,
|
||||
source: completion.CompletionSource.SwitchCases,
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
insertText:
|
||||
`case "2":`,
|
||||
},
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
marker: "3",
|
||||
isNewIdentifierLocation: false,
|
||||
includes: [
|
||||
{
|
||||
name: "case F.A: ...",
|
||||
source: completion.CompletionSource.SwitchCases,
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
insertText:
|
||||
`case F.A:
|
||||
case F.B:`, // no C because C's value is the same as A's
|
||||
},
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
marker: "4",
|
||||
isNewIdentifierLocation: false,
|
||||
includes: [
|
||||
{
|
||||
name: "case G.C: ...",
|
||||
source: completion.CompletionSource.SwitchCases,
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
insertText:
|
||||
`case G.C:
|
||||
case G.D:
|
||||
case G.E:
|
||||
case G.DorE:`,
|
||||
},
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
marker: "5",
|
||||
isNewIdentifierLocation: false,
|
||||
includes: [
|
||||
{
|
||||
name: "case G.D: ...",
|
||||
source: completion.CompletionSource.SwitchCases,
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
insertText:
|
||||
`case G.D:
|
||||
case G.E:
|
||||
case G.DorE:`,
|
||||
},
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
marker: "6",
|
||||
isNewIdentifierLocation: false,
|
||||
// No exhaustive case completion offered here because the switch is already exhaustive
|
||||
exact: [
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"u",
|
||||
"v",
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
...completion.globals,
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
35
tests/cases/fourslash/exhaustiveCaseCompletions5.ts
Normal file
35
tests/cases/fourslash/exhaustiveCaseCompletions5.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// Filter existing values.
|
||||
|
||||
// @newline: LF
|
||||
//// enum P {
|
||||
//// " Space",
|
||||
//// Bar,
|
||||
//// }
|
||||
////
|
||||
//// declare const p: P;
|
||||
////
|
||||
//// switch (p) {
|
||||
//// /*1*/
|
||||
//// }
|
||||
|
||||
verify.completions(
|
||||
{
|
||||
marker: "1",
|
||||
isNewIdentifierLocation: false,
|
||||
includes: [
|
||||
{
|
||||
name: `case P[" Space"]: ...`,
|
||||
source: completion.CompletionSource.SwitchCases,
|
||||
sortText: completion.SortText.GlobalsOrKeywords,
|
||||
insertText:
|
||||
`case P[" Space"]:
|
||||
case P.Bar:`,
|
||||
},
|
||||
],
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
@ -884,6 +884,7 @@ declare namespace completion {
|
||||
ClassMemberSnippet = "ClassMemberSnippet/",
|
||||
TypeOnlyAlias = "TypeOnlyAlias/",
|
||||
ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/",
|
||||
SwitchCases = "SwitchCases/",
|
||||
}
|
||||
export const globalThisEntry: Entry;
|
||||
export const undefinedVarEntry: Entry;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user