Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
WIP
  • Loading branch information
gabritto committed Sep 28, 2022
commit d46c0d254d6093e3375bfee8d1a80aecafd1d23a
30 changes: 1 addition & 29 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22449,35 +22449,7 @@ namespace ts {
* @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 {
Expand Down
42 changes: 42 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7313,6 +7313,48 @@ namespace ts {
return (negative && base10Value !== "0" ? "-" : "") + base10Value;
}

export function parseBigInt(text: string): PseudoBigInt | undefined {
if (!isValidBigIntString(text, /*roundTripOnly*/ false)) {
return undefined;
}
return parseValidBigInt(text);
}

/**
* @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 };
}

/**
* 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()) }));
}

export function isValidTypeOnlyAliasUseSite(useSite: Node): boolean {
return !!(useSite.flags & NodeFlags.Ambient)
|| isPartOfTypeQuery(useSite)
Expand Down
94 changes: 50 additions & 44 deletions src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ namespace ts.Completions {
getJSCompletionEntries(sourceFile, location.pos, uniqueNames, getEmitScriptTarget(compilerOptions), entries);
}

// >> TODO: prototype remaining cases here
// >> TODO: trigger on semi-completed case-keyword
if (contextToken && isCaseKeyword(contextToken) && preferences.includeCompletionsWithInsertText) {
const cases = getExhaustiveCaseSnippets(contextToken, sourceFile, preferences, compilerOptions, host, program, formatContext);
if (cases) {
Expand Down Expand Up @@ -624,7 +624,11 @@ namespace ts.Completions {
// const existingSymbols = new Map<SymbolId, true>();
// const existingLiterals: (number | string | PseudoBigInt)[] = [];
// existingLiterals;
const existingValues: (string | number | PseudoBigInt)[] = [];
// const existingValues: (string | number | PseudoBigInt)[] = [];
const existingStrings = new Set<string>();
const existingNumbers = new Set<number>();
const existingBigInts = new Set<string>();
const existingBools = new Set<boolean>();

for (const clause of clauses) {
if (isDefaultClause(clause)) {
Expand All @@ -633,91 +637,93 @@ namespace ts.Completions {
if (isLiteralExpression(clause.expression)) {
const expression = clause.expression;
switch (expression.kind) {
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.StringLiteral:
existingValues.push(expression.text);
continue;
existingStrings.add(expression.text);
break;
case SyntaxKind.NumericLiteral:
existingValues.push(parseInt(expression.text)); // ??
continue;
existingNumbers.add(parseInt(expression.text)); // >> do we need to parse it??
break;
case SyntaxKind.BigIntLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
continue;
default:
return undefined;
const parsedBigInt = parseBigInt(endsWith(expression.text, "n") ? expression.text.slice(0, -1) : expression.text);
if (parsedBigInt) {
existingBigInts.add(pseudoBigIntToString(parsedBigInt)); // >> does it work? answer: no
}
break;
case SyntaxKind.TrueKeyword:
existingBools.add(true);
break;
case SyntaxKind.FalseKeyword:
existingBools.add(false);
break;
}
}
else {
else if (checker.getSymbolAtLocation(clause.expression)) {
const symbol = checker.getSymbolAtLocation(clause.expression);
if (symbol && symbol.valueDeclaration && isEnumMember(symbol.valueDeclaration)) {
// TODO: check that it's an enum member symbol
const enumValue = checker.getConstantValue(symbol.valueDeclaration);
if (enumValue) {
existingValues.push(enumValue);
}
else {
return undefined
if (enumValue !== undefined) {
switch (typeof enumValue) {
case "string":
existingStrings.add(enumValue);
break;
case "number":
existingNumbers.add(enumValue);
}
}
}
}
return undefined; // We couldn't recognize all existing case clauses, so fail.
}


const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host);
const elements: Expression[] = [];
for (const type of switchType.types as LiteralType[]) {
if (type.flags & TypeFlags.EnumLiteral) {
Debug.assert(type.symbol, "TODO: should this hold always?");
Debug.assert(type.symbol.parent, "TODO: should this hold always too?");
// if (existingSymbols.has(getSymbolId(type.symbol))) {
// continue;
// }
// >> TODO: see if we need to filter enum by their constant vals
const target = getEmitScriptTarget(options);
// >> TODO: figure out if need an import action
// >> TODO: fix issue when qualified import
const typeNode = codefix.typeToAutoImportableTypeNode(checker, importAdder, type, caseClause.parent, target);
if (typeNode) {
const typeNodeText = printer.printSnippetList(
ListFormat.None,
factory.createNodeArray([typeNode]),
sourceFile);
typeNodeText;

const expr = foo(typeNode, target);
if (expr) {
elements.push(expr);
}
if (!typeNode) {
return undefined;
}
const typeNodeText = printer.printSnippetList(
ListFormat.None,
factory.createNodeArray([typeNode]),
sourceFile);
typeNodeText;

const expr = foo(typeNode, target);
if (!expr) {
return undefined;
}
elements.push(expr);
// >> TODO: what if expression has import node?
// const expr = checker.symbolToExpression(type.symbol, SymbolFlags.EnumMember, caseClause.parent, /*flags*/ undefined);
// if (expr) {
// return expr;
// }
// }
}
} // >> TODO: else if boolean???
else {
// const text = completionNameForLiteral(sourceFile, preferences, type.value);
// >> TODO: filter by existing
const literal: Expression = typeof type.value === "object"
? factory.createBigIntLiteral(type.value)
: typeof type.value === "number"
? factory.createNumericLiteral(type.value)
: factory.createStringLiteral(type.value);
elements.push(literal);
}
return undefined;
}

if (elements.length === 0) {
return undefined;
}

// const existingClauses = new Set(mapDefined(caseClause.parent.clauses, clause => {
// if (isDefaultClause(clause)) {
// return undefined;
// }
// return clause.expression.getText();
// }));

// elements = filter(elements, element => !existingClauses.has(element[0]));
const newClauses = mapDefined(elements, element => {
return factory.createCaseClause(element, []);
});
Expand All @@ -736,9 +742,9 @@ namespace ts.Completions {
return {
entry: {
name: `${firstClause} ...`, // >> TODO: what should this be?
isRecommended: true, // >> I assume that is ok because if there's another recommended, it will be sorted after this one
// isRecommended: true, // >> I assume that is ok because if there's another recommended, it will be sorted after this one
kind: ScriptElementKind.unknown, // >> TODO: what should this be?
sortText: SortText.LocalDeclarationPriority,
sortText: SortText.LocalDeclarationPriority, // >> TODO: sort *right after* case keyword
insertText,
replacementSpan: getReplacementSpanForContextToken(contextToken),
hasAction: importAdder.hasFixes() || undefined,
Expand Down
2 changes: 2 additions & 0 deletions tests/cases/fourslash/fullCaseCompletions3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//// case E.A:
//// case 1:
//// case 1n:
//// case 0x1n:
//// case "1":
//// case `1`:
//// case `1${u}`:
Expand Down Expand Up @@ -41,6 +42,7 @@ verify.completions(
},
);


// verify.applyCodeActionFromCompletion("1", {
// name: "case E.A: ...",
// source: "SwitchCases/",
Expand Down