Skip to content
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

Exhaustive case completion for switch statements #50996

Merged
merged 27 commits into from
Dec 2, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f6c3566
fix services' type's isLiteral
gabritto Sep 23, 2022
4467330
update literal completions tests
gabritto Sep 23, 2022
a206fe1
initial prototype
gabritto Sep 7, 2022
73c1eea
use symbol to expression. TODO: filter existing, replace import nodes
gabritto Sep 16, 2022
5648cba
WIP
gabritto Sep 23, 2022
d46c0d2
WIP
gabritto Sep 28, 2022
297f892
remove booleans from literals
gabritto Sep 28, 2022
fd1d6ed
Merge branch 'gabritto/servicesIsLiteral' into gabritto/switchsnippet
gabritto Sep 28, 2022
4c528b3
trigger at case keyword positions
gabritto Sep 29, 2022
1a5cd05
clean up tests
gabritto Nov 8, 2022
ee42732
fix element access expression case
gabritto Nov 9, 2022
1819d0b
refactor dealing with existing values into a tracker
gabritto Nov 9, 2022
b19543e
Merge branch 'main' into gabritto/switchsnippet
gabritto Nov 10, 2022
bd5b817
fix merge errors
gabritto Nov 10, 2022
f02122b
cleanup and more tests
gabritto Nov 10, 2022
a35bc4a
fix lint errors
gabritto Nov 10, 2022
83b88f7
more merge conflict fixes and cleanup
gabritto Nov 11, 2022
599fb30
use appropriate quotes
gabritto Nov 11, 2022
97dcf69
small indentation fix
gabritto Nov 11, 2022
3b92638
refactor case clause tracker
gabritto Nov 14, 2022
89f6f6b
Merge branch 'main' into gabritto/switchsnippet
gabritto Nov 14, 2022
1894d2e
experiment: support tabstops after each case clause
gabritto Nov 22, 2022
90767fc
address small CR comments
gabritto Nov 23, 2022
d1c8968
fix completion entry details; add test case
gabritto Nov 30, 2022
fb15ba1
Merge branch 'main' into gabritto/switchsnippet
gabritto Nov 30, 2022
3980b93
fix lint errors
gabritto Dec 1, 2022
8823108
remove space before tab stops; refactor
gabritto Dec 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 5 additions & 36 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
createDiagnosticForFileFromMessageChain, createDiagnosticForNode, createDiagnosticForNodeArray,
createDiagnosticForNodeFromMessageChain, createDiagnosticMessageChainFromDiagnostic, createEmptyExports,
createFileDiagnostic, createGetCanonicalFileName, createGetSymbolWalker, createPrinter,
createPropertyNameNodeForIdentifierOrLiteral, createScanner, createSymbolTable, createTextWriter,
createPropertyNameNodeForIdentifierOrLiteral, createSymbolTable, createTextWriter,
createUnderscoreEscapedMultiMap, Debug, Declaration, DeclarationName, declarationNameToString, DeclarationStatement,
DeclarationWithTypeParameterChildren, DeclarationWithTypeParameters, Decorator, deduplicate, DefaultClause,
defaultMaximumTruncationLength, DeferredTypeReference, DeleteExpression, Diagnostic, DiagnosticCategory,
Expand Down Expand Up @@ -101,7 +101,7 @@ import {
isFunctionExpressionOrArrowFunction, isFunctionLike, isFunctionLikeDeclaration,
isFunctionLikeOrClassStaticBlockDeclaration, isFunctionOrModuleBlock, isFunctionTypeNode, isGeneratedIdentifier,
isGetAccessor, isGetAccessorDeclaration, isGetOrSetAccessorDeclaration, isGlobalScopeAugmentation, isHeritageClause,
isIdentifier, isIdentifierStart, isIdentifierText, isIdentifierTypePredicate, isIdentifierTypeReference,
isIdentifier, isIdentifierText, isIdentifierTypePredicate, isIdentifierTypeReference,
isIfStatement, isImportCall, isImportClause, isImportDeclaration, isImportEqualsDeclaration, isImportKeyword,
isImportOrExportSpecifier, isImportSpecifier, isImportTypeNode, isIndexedAccessTypeNode, isInExpressionContext,
isInfinityOrNaNString, isInJSDoc, isInJSFile, isInJsonFile, isInterfaceDeclaration,
Expand Down Expand Up @@ -195,7 +195,7 @@ import {
usingSingleLineStringWriter, VariableDeclaration, VariableDeclarationList, VariableLikeDeclaration,
VariableStatement, VarianceFlags, visitEachChild, visitNode, visitNodes, Visitor, VisitResult, VoidExpression,
walkUpBindingElementsAndPatterns, walkUpParenthesizedExpressions, walkUpParenthesizedTypes,
walkUpParenthesizedTypesAndGetParentAndChild, WhileStatement, WideningContext, WithStatement, YieldExpression,
walkUpParenthesizedTypesAndGetParentAndChild, WhileStatement, WideningContext, WithStatement, YieldExpression, canUsePropertyAccess, parseValidBigInt, isValidBigIntString,
} from "./_namespaces/ts";
import * as performance from "./_namespaces/ts.performance";
import * as moduleSpecifiers from "./_namespaces/ts.moduleSpecifiers";
Expand Down Expand Up @@ -6881,10 +6881,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 ?
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also moved this into a new function in utilities.

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;

Expand Down Expand Up @@ -22698,35 +22695,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("-");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this into a new function in utilities.

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 {
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function isDotDotDotToken(node: Node): node is DotDotDotToken {
return node.kind === SyntaxKind.DotDotDotToken;
}

/** @internal */
/** @internal*/
export function isCommaToken(node: Node): node is Token<SyntaxKind.CommaToken> {
return node.kind === SyntaxKind.CommaToken;
}
Expand Down
13 changes: 7 additions & 6 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1331,6 +1331,7 @@ export interface KeywordToken<TKind extends KeywordSyntaxKind> extends Token<TKi
export type AssertsKeyword = KeywordToken<SyntaxKind.AssertsKeyword>;
export type AssertKeyword = KeywordToken<SyntaxKind.AssertKeyword>;
export type AwaitKeyword = KeywordToken<SyntaxKind.AwaitKeyword>;
export type CaseKeyword = KeywordToken<SyntaxKind.CaseKeyword>;

/** @deprecated Use `AwaitKeyword` instead. */
export type AwaitKeywordToken = AwaitKeyword;
Expand Down Expand Up @@ -7853,12 +7854,12 @@ export interface NodeFactory {
// Signature elements
//

createTypeParameterDeclaration(modifiers: readonly Modifier[] | undefined, name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode): TypeParameterDeclaration;
updateTypeParameterDeclaration(node: TypeParameterDeclaration, modifiers: readonly Modifier[] | undefined, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined): TypeParameterDeclaration;
createParameterDeclaration(modifiers: readonly ModifierLike[] | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression): ParameterDeclaration;
updateParameterDeclaration(node: ParameterDeclaration, modifiers: readonly ModifierLike[] | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): ParameterDeclaration;
createDecorator(expression: Expression): Decorator;
updateDecorator(node: Decorator, expression: Expression): Decorator;
createTypeParameterDeclaration(modifiers: readonly Modifier[] | undefined, name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode): TypeParameterDeclaration;
updateTypeParameterDeclaration(node: TypeParameterDeclaration, modifiers: readonly Modifier[] | undefined, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined): TypeParameterDeclaration;
createParameterDeclaration(modifiers: readonly ModifierLike[] | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression): ParameterDeclaration;
updateParameterDeclaration(node: ParameterDeclaration, modifiers: readonly ModifierLike[] | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): ParameterDeclaration;
createDecorator(expression: Expression): Decorator;
updateDecorator(node: Decorator, expression: Expression): Decorator;

//
// Type Elements
Expand Down
56 changes: 55 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ import {
TypePredicate, TypePredicateKind, TypeReferenceNode, unescapeLeadingUnderscores, UnionOrIntersectionTypeNode,
ValidImportTypeNode, VariableDeclaration, VariableDeclarationInitializedTo, VariableDeclarationList,
VariableLikeDeclaration, VariableStatement, version, WhileStatement, WithStatement, WriteFileCallback,
WriteFileCallbackData, YieldExpression,
WriteFileCallbackData, YieldExpression, isIdentifierStart,
} from "./_namespaces/ts";

/** @internal */
Expand Down Expand Up @@ -8129,6 +8129,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;
gabritto marked this conversation as resolved.
Show resolved Hide resolved
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)
Expand Down Expand Up @@ -8636,4 +8681,13 @@ 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 {
const firstChar = name.charCodeAt(0);
gabritto marked this conversation as resolved.
Show resolved Hide resolved
return firstChar === CharacterCodes.hash ?
name.length > 1 && isIdentifierStart(name.charCodeAt(1), languageVersion) :
isIdentifierStart(firstChar, languageVersion);
}
Loading