Skip to content

Commit 664671c

Browse files
authored
Merge pull request #32377 from minajevs/fix29666
Fix completion lists for 'readonly' and 'const' keywords
2 parents 1de76cd + 84cdc63 commit 664671c

File tree

6 files changed

+45
-15
lines changed

6 files changed

+45
-15
lines changed

src/harness/fourslash.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,7 @@ namespace FourSlash {
865865
ts.zipWith(actual, expected, (completion, expectedCompletion, index) => {
866866
const name = typeof expectedCompletion === "string" ? expectedCompletion : expectedCompletion.name;
867867
if (completion.name !== name) {
868-
this.raiseError(`${marker ? JSON.stringify(marker) : "" } Expected completion at index ${index} to be ${name}, got ${completion.name}`);
868+
this.raiseError(`${marker ? JSON.stringify(marker) : ""} Expected completion at index ${index} to be ${name}, got ${completion.name}`);
869869
}
870870
this.verifyCompletionEntry(completion, expectedCompletion);
871871
});
@@ -3759,7 +3759,7 @@ namespace FourSlashInterface {
37593759
}
37603760

37613761
export class Plugins {
3762-
constructor (private state: FourSlash.TestState) {
3762+
constructor(private state: FourSlash.TestState) {
37633763
}
37643764

37653765
public configurePlugin(pluginName: string, configuration: any): void {
@@ -4582,7 +4582,7 @@ namespace FourSlashInterface {
45824582
export const keywords: ReadonlyArray<ExpectedCompletionEntryObject> = keywordsWithUndefined.filter(k => k.name !== "undefined");
45834583

45844584
export const typeKeywords: ReadonlyArray<ExpectedCompletionEntryObject> =
4585-
["false", "null", "true", "void", "any", "boolean", "keyof", "never", "number", "object", "string", "symbol", "undefined", "unique", "unknown", "bigint"].map(keywordEntry);
4585+
["false", "null", "true", "void", "any", "boolean", "keyof", "never", "readonly", "number", "object", "string", "symbol", "undefined", "unique", "unknown", "bigint"].map(keywordEntry);
45864586

45874587
const globalTypeDecls: ReadonlyArray<ExpectedCompletionEntryObject> = [
45884588
interfaceEntry("Symbol"),
@@ -4698,6 +4698,9 @@ namespace FourSlashInterface {
46984698
];
46994699
}
47004700

4701+
export const typeAssertionKeywords: ReadonlyArray<ExpectedCompletionEntry> =
4702+
globalTypesPlus([keywordEntry("const")]);
4703+
47014704
function getInJsKeywords(keywords: ReadonlyArray<ExpectedCompletionEntryObject>): ReadonlyArray<ExpectedCompletionEntryObject> {
47024705
return keywords.filter(keyword => {
47034706
switch (keyword.name) {

src/services/completions.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ namespace ts.Completions {
3838
InterfaceElementKeywords, // Keywords inside interface body
3939
ConstructorParameterKeywords, // Keywords at constructor parameter
4040
FunctionLikeBodyKeywords, // Keywords at function like body
41+
TypeAssertionKeywords,
4142
TypeKeywords,
4243
Last = TypeKeywords
4344
}
@@ -441,7 +442,7 @@ namespace ts.Completions {
441442
(symbol.escapedName === InternalSymbolName.ExportEquals))
442443
// Name of "export default foo;" is "foo". Name of "export default 0" is the filename converted to camelCase.
443444
? firstDefined(symbol.declarations, d => isExportAssignment(d) && isIdentifier(d.expression) ? d.expression.text : undefined)
444-
|| codefix.moduleSymbolToValidIdentifier(origin.moduleSymbol, target)
445+
|| codefix.moduleSymbolToValidIdentifier(origin.moduleSymbol, target)
445446
: symbol.name;
446447
}
447448

@@ -632,9 +633,9 @@ namespace ts.Completions {
632633
// At `,`, treat this as the next argument after the comma.
633634
? checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex + (previousToken.kind === SyntaxKind.CommaToken ? 1 : 0))
634635
: isEqualityOperatorKind(previousToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind)
635-
// completion at `x ===/**/` should be for the right side
636-
? checker.getTypeAtLocation(parent.left)
637-
: checker.getContextualType(previousToken as Expression);
636+
// completion at `x ===/**/` should be for the right side
637+
? checker.getTypeAtLocation(parent.left)
638+
: checker.getContextualType(previousToken as Expression);
638639
}
639640
}
640641

@@ -1182,7 +1183,11 @@ namespace ts.Completions {
11821183
function filterGlobalCompletion(symbols: Symbol[]): void {
11831184
const isTypeOnly = isTypeOnlyCompletion();
11841185
const allowTypes = isTypeOnly || !isContextTokenValueLocation(contextToken) && isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker);
1185-
if (isTypeOnly) keywordFilters = KeywordCompletionFilters.TypeKeywords;
1186+
if (isTypeOnly) {
1187+
keywordFilters = isTypeAssertion()
1188+
? KeywordCompletionFilters.TypeAssertionKeywords
1189+
: KeywordCompletionFilters.TypeKeywords;
1190+
}
11861191

11871192
filterMutate(symbols, symbol => {
11881193
if (!isSourceFile(location)) {
@@ -1212,6 +1217,10 @@ namespace ts.Completions {
12121217
});
12131218
}
12141219

1220+
function isTypeAssertion(): boolean {
1221+
return isAssertionExpression(contextToken.parent);
1222+
}
1223+
12151224
function isTypeOnlyCompletion(): boolean {
12161225
return insideJsDocTagTypeExpression || !isContextTokenValueLocation(contextToken) && (isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken));
12171226
}
@@ -1240,7 +1249,8 @@ namespace ts.Completions {
12401249
return parentKind === SyntaxKind.AsExpression;
12411250

12421251
case SyntaxKind.LessThanToken:
1243-
return parentKind === SyntaxKind.TypeReference;
1252+
return parentKind === SyntaxKind.TypeReference ||
1253+
parentKind === SyntaxKind.TypeAssertionExpression;
12441254

12451255
case SyntaxKind.ExtendsKeyword:
12461256
return parentKind === SyntaxKind.TypeParameter;
@@ -1516,7 +1526,7 @@ namespace ts.Completions {
15161526
// 3. at the end of a regular expression (due to trailing flags like '/foo/g').
15171527
return (isRegularExpressionLiteral(contextToken) || isStringTextContainingNode(contextToken)) && (
15181528
rangeContainsPositionExclusive(createTextRangeFromSpan(createTextSpanFromNode(contextToken)), position) ||
1519-
position === contextToken.end && (!!contextToken.isUnterminated || isRegularExpressionLiteral(contextToken)));
1529+
position === contextToken.end && (!!contextToken.isUnterminated || isRegularExpressionLiteral(contextToken)));
15201530
}
15211531

15221532
/**
@@ -2026,8 +2036,8 @@ namespace ts.Completions {
20262036

20272037
return baseSymbols.filter(propertySymbol =>
20282038
!existingMemberNames.has(propertySymbol.escapedName) &&
2029-
!!propertySymbol.declarations &&
2030-
!(getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private));
2039+
!!propertySymbol.declarations &&
2040+
!(getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private));
20312041
}
20322042

20332043
/**
@@ -2139,6 +2149,8 @@ namespace ts.Completions {
21392149
return isParameterPropertyModifier(kind);
21402150
case KeywordCompletionFilters.FunctionLikeBodyKeywords:
21412151
return isFunctionLikeBodyKeyword(kind);
2152+
case KeywordCompletionFilters.TypeAssertionKeywords:
2153+
return isTypeKeyword(kind) || kind === SyntaxKind.ConstKeyword;
21422154
case KeywordCompletionFilters.TypeKeywords:
21432155
return isTypeKeyword(kind);
21442156
default:

src/services/utilities.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,7 @@ namespace ts {
12241224
SyntaxKind.NullKeyword,
12251225
SyntaxKind.NumberKeyword,
12261226
SyntaxKind.ObjectKeyword,
1227+
SyntaxKind.ReadonlyKeyword,
12271228
SyntaxKind.StringKeyword,
12281229
SyntaxKind.SymbolKeyword,
12291230
SyntaxKind.TrueKeyword,
@@ -1734,8 +1735,8 @@ namespace ts {
17341735

17351736
function getSynthesizedDeepCloneWorker<T extends Node>(node: T, renameMap?: Map<Identifier>, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any): T {
17361737
const visited = (renameMap || checker || callback) ?
1737-
visitEachChild(node, wrapper, nullTransformationContext) :
1738-
visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext);
1738+
visitEachChild(node, wrapper, nullTransformationContext) :
1739+
visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext);
17391740

17401741
if (visited === node) {
17411742
// This only happens for leaf nodes - internal nodes always see their children change.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////const a = {
4+
//// b: 42 as /*0*/
5+
////};
6+
////
7+
////1 as /*1*/
8+
////
9+
////const b = 42 as /*2*/
10+
////
11+
////var c = </*3*/>42
12+
13+
verify.completions({ marker: test.markers(), exact: completion.typeAssertionKeywords });

tests/cases/fourslash/documentHighlightsInvalidModifierLocations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
////function f([|readonly|] p) {}
77

88
for (const r of test.ranges()) {
9-
verify.documentHighlightsOf(r, [r]);
9+
verify.documentHighlightsOf(r, test.ranges());
1010
}

tests/cases/fourslash/fourslash.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,7 @@ declare namespace completion {
696696
export const typeKeywords: ReadonlyArray<Entry>;
697697
export const globalTypes: ReadonlyArray<Entry>;
698698
export function globalTypesPlus(plus: ReadonlyArray<FourSlashInterface.ExpectedCompletionEntry>): ReadonlyArray<Entry>;
699+
export const typeAssertionKeywords: ReadonlyArray<Entry>;
699700
export const classElementKeywords: ReadonlyArray<Entry>;
700701
export const classElementInJsKeywords: ReadonlyArray<Entry>;
701702
export const constructorParameterKeywords: ReadonlyArray<Entry>;

0 commit comments

Comments
 (0)