Skip to content

Commit 6a3c9ea

Browse files
authored
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
1 parent 5435efb commit 6a3c9ea

10 files changed

+925
-42
lines changed

src/compiler/checker.ts

+5-35
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
canHaveIllegalDecorators,
5353
canHaveIllegalModifiers,
5454
canHaveModifiers,
55+
canUsePropertyAccess,
5556
cartesianProduct,
5657
CaseBlock,
5758
CaseClause,
@@ -102,7 +103,6 @@ import {
102103
createModeAwareCacheKey,
103104
createPrinter,
104105
createPropertyNameNodeForIdentifierOrLiteral,
105-
createScanner,
106106
createSymbolTable,
107107
createTextWriter,
108108
createUnderscoreEscapedMultiMap,
@@ -500,7 +500,6 @@ import {
500500
isGlobalScopeAugmentation,
501501
isHeritageClause,
502502
isIdentifier,
503-
isIdentifierStart,
504503
isIdentifierText,
505504
isIdentifierTypePredicate,
506505
isIdentifierTypeReference,
@@ -678,6 +677,7 @@ import {
678677
isTypeReferenceNode,
679678
isTypeReferenceType,
680679
isUMDExportSymbol,
680+
isValidBigIntString,
681681
isValidESSymbolDeclaration,
682682
isValidTypeOnlyAliasUseSite,
683683
isValueSignatureDeclaration,
@@ -827,6 +827,7 @@ import {
827827
parseIsolatedEntityName,
828828
parseNodeFactory,
829829
parsePseudoBigInt,
830+
parseValidBigInt,
830831
Path,
831832
pathIsRelative,
832833
PatternAmbientModule,
@@ -7691,10 +7692,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
76917692
if (isSingleOrDoubleQuote(firstChar) && some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) {
76927693
return factory.createStringLiteral(getSpecifierForModuleSymbol(symbol, context));
76937694
}
7694-
const canUsePropertyAccess = firstChar === CharacterCodes.hash ?
7695-
symbolName.length > 1 && isIdentifierStart(symbolName.charCodeAt(1), languageVersion) :
7696-
isIdentifierStart(firstChar, languageVersion);
7697-
if (index === 0 || canUsePropertyAccess) {
7695+
if (index === 0 || canUsePropertyAccess(symbolName, languageVersion)) {
76987696
const identifier = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping);
76997697
identifier.symbol = symbol;
77007698

@@ -23533,35 +23531,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2353323531
* @param text a valid bigint string excluding a trailing `n`, but including a possible prefix `-`. Use `isValidBigIntString(text, roundTripOnly)` before calling this function.
2353423532
*/
2353523533
function parseBigIntLiteralType(text: string) {
23536-
const negative = text.startsWith("-");
23537-
const base10Value = parsePseudoBigInt(`${negative ? text.slice(1) : text}n`);
23538-
return getBigIntLiteralType({ negative, base10Value });
23539-
}
23540-
23541-
/**
23542-
* Tests whether the provided string can be parsed as a bigint.
23543-
* @param s The string to test.
23544-
* @param roundTripOnly Indicates the resulting bigint matches the input when converted back to a string.
23545-
*/
23546-
function isValidBigIntString(s: string, roundTripOnly: boolean): boolean {
23547-
if (s === "") return false;
23548-
const scanner = createScanner(ScriptTarget.ESNext, /*skipTrivia*/ false);
23549-
let success = true;
23550-
scanner.setOnError(() => success = false);
23551-
scanner.setText(s + "n");
23552-
let result = scanner.scan();
23553-
const negative = result === SyntaxKind.MinusToken;
23554-
if (negative) {
23555-
result = scanner.scan();
23556-
}
23557-
const flags = scanner.getTokenFlags();
23558-
// validate that
23559-
// * scanning proceeded without error
23560-
// * a bigint can be scanned, and that when it is scanned, it is
23561-
// * the full length of the input string (so the scanner is one character beyond the augmented input length)
23562-
// * it does not contain a numeric seperator (the `BigInt` constructor does not accept a numeric seperator in its input)
23563-
return success && result === SyntaxKind.BigIntLiteral && scanner.getTextPos() === (s.length + 1) && !(flags & TokenFlags.ContainsSeparator)
23564-
&& (!roundTripOnly || s === pseudoBigIntToString({ negative, base10Value: parsePseudoBigInt(scanner.getTokenValue()) }));
23534+
return getBigIntLiteralType(parseValidBigInt(text));
2356523535
}
2356623536

2356723537
function isMemberOfStringMapping(source: Type, target: Type): boolean {

src/compiler/utilities.ts

+66-1
Original file line numberDiff line numberDiff line change
@@ -187,13 +187,14 @@ import {
187187
getResolutionMode,
188188
getResolutionName,
189189
getRootLength,
190+
getSnippetElement,
190191
getStringComparer,
191192
getSymbolId,
192193
getTrailingCommentRanges,
193194
HasExpressionInitializer,
194195
hasExtension,
195-
hasInitializer,
196196
HasInitializer,
197+
hasInitializer,
197198
HasJSDoc,
198199
hasJSDocNodes,
199200
HasModifiers,
@@ -257,6 +258,7 @@ import {
257258
isGetAccessorDeclaration,
258259
isHeritageClause,
259260
isIdentifier,
261+
isIdentifierStart,
260262
isIdentifierText,
261263
isImportTypeNode,
262264
isInterfaceDeclaration,
@@ -440,6 +442,7 @@ import {
440442
singleOrUndefined,
441443
skipOuterExpressions,
442444
skipTrivia,
445+
SnippetKind,
443446
some,
444447
sort,
445448
SortedArray,
@@ -8555,6 +8558,51 @@ export function pseudoBigIntToString({negative, base10Value}: PseudoBigInt): str
85558558
return (negative && base10Value !== "0" ? "-" : "") + base10Value;
85568559
}
85578560

8561+
/** @internal */
8562+
export function parseBigInt(text: string): PseudoBigInt | undefined {
8563+
if (!isValidBigIntString(text, /*roundTripOnly*/ false)) {
8564+
return undefined;
8565+
}
8566+
return parseValidBigInt(text);
8567+
}
8568+
8569+
/**
8570+
* @internal
8571+
* @param text a valid bigint string excluding a trailing `n`, but including a possible prefix `-`. Use `isValidBigIntString(text, roundTripOnly)` before calling this function.
8572+
*/
8573+
export function parseValidBigInt(text: string): PseudoBigInt {
8574+
const negative = text.startsWith("-");
8575+
const base10Value = parsePseudoBigInt(`${negative ? text.slice(1) : text}n`);
8576+
return { negative, base10Value };
8577+
}
8578+
8579+
/**
8580+
* @internal
8581+
* Tests whether the provided string can be parsed as a bigint.
8582+
* @param s The string to test.
8583+
* @param roundTripOnly Indicates the resulting bigint matches the input when converted back to a string.
8584+
*/
8585+
export function isValidBigIntString(s: string, roundTripOnly: boolean): boolean {
8586+
if (s === "") return false;
8587+
const scanner = createScanner(ScriptTarget.ESNext, /*skipTrivia*/ false);
8588+
let success = true;
8589+
scanner.setOnError(() => success = false);
8590+
scanner.setText(s + "n");
8591+
let result = scanner.scan();
8592+
const negative = result === SyntaxKind.MinusToken;
8593+
if (negative) {
8594+
result = scanner.scan();
8595+
}
8596+
const flags = scanner.getTokenFlags();
8597+
// validate that
8598+
// * scanning proceeded without error
8599+
// * a bigint can be scanned, and that when it is scanned, it is
8600+
// * the full length of the input string (so the scanner is one character beyond the augmented input length)
8601+
// * it does not contain a numeric seperator (the `BigInt` constructor does not accept a numeric seperator in its input)
8602+
return success && result === SyntaxKind.BigIntLiteral && scanner.getTextPos() === (s.length + 1) && !(flags & TokenFlags.ContainsSeparator)
8603+
&& (!roundTripOnly || s === pseudoBigIntToString({ negative, base10Value: parsePseudoBigInt(scanner.getTokenValue()) }));
8604+
}
8605+
85588606
/** @internal */
85598607
export function isValidTypeOnlyAliasUseSite(useSite: Node): boolean {
85608608
return !!(useSite.flags & NodeFlags.Ambient)
@@ -9062,4 +9110,21 @@ export function isOptionalJSDocPropertyLikeTag(node: Node): node is JSDocPropert
90629110
}
90639111
const { isBracketed, typeExpression } = node;
90649112
return isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType;
9113+
9114+
}
9115+
9116+
/** @internal */
9117+
export function canUsePropertyAccess(name: string, languageVersion: ScriptTarget): boolean {
9118+
if (name.length === 0) {
9119+
return false;
9120+
}
9121+
const firstChar = name.charCodeAt(0);
9122+
return firstChar === CharacterCodes.hash ?
9123+
name.length > 1 && isIdentifierStart(name.charCodeAt(1), languageVersion) :
9124+
isIdentifierStart(firstChar, languageVersion);
9125+
}
9126+
9127+
/** @internal */
9128+
export function hasTabstop(node: Node): boolean {
9129+
return getSnippetElement(node)?.kind === SnippetKind.TabStop;
90659130
}

0 commit comments

Comments
 (0)