Skip to content

Commit e543d8b

Browse files
authored
Fix type keyword completions (#32474)
* Fix type keyword completions 1. In functions, type keywords were omitted. 2. In All context, no keywords were omitted. (1) fixes #28737 (2) removes 17 keywords that should not be suggested, even at the toplevel of a typescript file: * private * protected * public * static * abstract * as * constructor * get * infer * is * namespace * require * set * type * from * global * of I don't know whether we have a bug tracking this or not. * Change keyword filter in filterGlobalCompletion Instead of changing FunctionLikeBodyKeywords * Add more tests cases * Make type-only completions after < more common Because isPossiblyTypeArgumentPosition doesn't give false positives now that it uses type information.
1 parent 90afd6d commit e543d8b

8 files changed

+64
-75
lines changed

src/harness/fourslash.ts

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ namespace FourSlash {
797797
for (const include of toArray(options.includes)) {
798798
const name = typeof include === "string" ? include : include.name;
799799
const found = nameToEntries.get(name);
800-
if (!found) throw this.raiseError(`No completion ${name} found`);
800+
if (!found) throw this.raiseError(`Includes: completion '${name}' not found.`);
801801
assert(found.length === 1); // Must use 'exact' for multiple completions with same name
802802
this.verifyCompletionEntry(ts.first(found), include);
803803
}
@@ -806,7 +806,7 @@ namespace FourSlash {
806806
for (const exclude of toArray(options.excludes)) {
807807
assert(typeof exclude === "string");
808808
if (nameToEntries.has(exclude)) {
809-
this.raiseError(`Did not expect to get a completion named ${exclude}`);
809+
this.raiseError(`Excludes: unexpected completion '${exclude}' found.`);
810810
}
811811
}
812812
}
@@ -4827,40 +4827,23 @@ namespace FourSlashInterface {
48274827
"interface",
48284828
"let",
48294829
"package",
4830-
"private",
4831-
"protected",
4832-
"public",
4833-
"static",
48344830
"yield",
4835-
"abstract",
4836-
"as",
48374831
"any",
48384832
"async",
48394833
"await",
48404834
"boolean",
4841-
"constructor",
48424835
"declare",
4843-
"get",
4844-
"infer",
4845-
"is",
48464836
"keyof",
48474837
"module",
4848-
"namespace",
48494838
"never",
48504839
"readonly",
4851-
"require",
48524840
"number",
48534841
"object",
4854-
"set",
48554842
"string",
48564843
"symbol",
4857-
"type",
48584844
"unique",
48594845
"unknown",
4860-
"from",
4861-
"global",
48624846
"bigint",
4863-
"of",
48644847
].map(keywordEntry);
48654848

48664849
export const statementKeywords: ReadonlyArray<ExpectedCompletionEntryObject> = statementKeywordsWithTypes.filter(k => {
@@ -5041,40 +5024,23 @@ namespace FourSlashInterface {
50415024
"interface",
50425025
"let",
50435026
"package",
5044-
"private",
5045-
"protected",
5046-
"public",
5047-
"static",
50485027
"yield",
5049-
"abstract",
5050-
"as",
50515028
"any",
50525029
"async",
50535030
"await",
50545031
"boolean",
5055-
"constructor",
50565032
"declare",
5057-
"get",
5058-
"infer",
5059-
"is",
50605033
"keyof",
50615034
"module",
5062-
"namespace",
50635035
"never",
50645036
"readonly",
5065-
"require",
50665037
"number",
50675038
"object",
5068-
"set",
50695039
"string",
50705040
"symbol",
5071-
"type",
50725041
"unique",
50735042
"unknown",
5074-
"from",
5075-
"global",
50765043
"bigint",
5077-
"of",
50785044
].map(keywordEntry);
50795045

50805046
export const globalInJsKeywords = getInJsKeywords(globalKeywords);
@@ -5127,11 +5093,6 @@ namespace FourSlashInterface {
51275093

51285094
export const insideMethodInJsKeywords = getInJsKeywords(insideMethodKeywords);
51295095

5130-
export const globalKeywordsPlusUndefined: ReadonlyArray<ExpectedCompletionEntryObject> = (() => {
5131-
const i = ts.findIndex(globalKeywords, x => x.name === "unique");
5132-
return [...globalKeywords.slice(0, i), keywordEntry("undefined"), ...globalKeywords.slice(i)];
5133-
})();
5134-
51355096
export const globals: ReadonlyArray<ExpectedCompletionEntryObject> = [
51365097
globalThisEntry,
51375098
...globalsVars,

src/services/completions.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -947,11 +947,13 @@ namespace ts.Completions {
947947
// Right of dot member completion list
948948
completionKind = CompletionKind.PropertyAccess;
949949

950-
// Since this is qualified name check its a type node location
950+
// Since this is qualified name check it's a type node location
951951
const isImportType = isLiteralImportTypeNode(node);
952-
const isTypeLocation = insideJsDocTagTypeExpression || (isImportType && !(node as ImportTypeNode).isTypeOf) || isPartOfTypeNode(node.parent);
952+
const isTypeLocation = insideJsDocTagTypeExpression
953+
|| (isImportType && !(node as ImportTypeNode).isTypeOf)
954+
|| isPartOfTypeNode(node.parent)
955+
|| isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker);
953956
const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node);
954-
const allowTypeOrValue = isRhsOfImportDeclaration || (!isTypeLocation && isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker));
955957
if (isEntityName(node) || isImportType) {
956958
const isNamespaceName = isModuleDeclaration(node.parent);
957959
if (isNamespaceName) isNewIdentifierLocation = true;
@@ -968,7 +970,7 @@ namespace ts.Completions {
968970
isNamespaceName
969971
// At `namespace N.M/**/`, if this is the only declaration of `M`, don't include `M` as a completion.
970972
? symbol => !!(symbol.flags & SymbolFlags.Namespace) && !symbol.declarations.every(d => d.parent === node.parent)
971-
: allowTypeOrValue ?
973+
: isRhsOfImportDeclaration ?
972974
// Any kind is allowed when dotting off namespace in internal import equals declaration
973975
symbol => isValidTypeAccess(symbol) || isValidValueAccess(symbol) :
974976
isTypeLocation ? isValidTypeAccess : isValidValueAccess;
@@ -1181,7 +1183,6 @@ namespace ts.Completions {
11811183

11821184
function filterGlobalCompletion(symbols: Symbol[]): void {
11831185
const isTypeOnly = isTypeOnlyCompletion();
1184-
const allowTypes = isTypeOnly || !isContextTokenValueLocation(contextToken) && isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker);
11851186
if (isTypeOnly) {
11861187
keywordFilters = isTypeAssertion()
11871188
? KeywordCompletionFilters.TypeAssertionKeywords
@@ -1202,12 +1203,9 @@ namespace ts.Completions {
12021203
return !!(symbol.flags & SymbolFlags.Namespace);
12031204
}
12041205

1205-
if (allowTypes) {
1206-
// Its a type, but you can reach it by namespace.type as well
1207-
const symbolAllowedAsType = symbolCanBeReferencedAtTypeLocation(symbol);
1208-
if (symbolAllowedAsType || isTypeOnly) {
1209-
return symbolAllowedAsType;
1210-
}
1206+
if (isTypeOnly) {
1207+
// It's a type, but you can reach it by namespace.type as well
1208+
return symbolCanBeReferencedAtTypeLocation(symbol);
12111209
}
12121210
}
12131211

@@ -1221,7 +1219,11 @@ namespace ts.Completions {
12211219
}
12221220

12231221
function isTypeOnlyCompletion(): boolean {
1224-
return insideJsDocTagTypeExpression || !isContextTokenValueLocation(contextToken) && (isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken));
1222+
return insideJsDocTagTypeExpression
1223+
|| !isContextTokenValueLocation(contextToken) &&
1224+
(isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker)
1225+
|| isPartOfTypeNode(location)
1226+
|| isContextTokenTypeLocation(contextToken));
12251227
}
12261228

12271229
function isContextTokenValueLocation(contextToken: Node) {
@@ -2060,16 +2062,18 @@ namespace ts.Completions {
20602062
case KeywordCompletionFilters.None:
20612063
return false;
20622064
case KeywordCompletionFilters.All:
2063-
return kind === SyntaxKind.AsyncKeyword || SyntaxKind.AwaitKeyword || !isContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind) || kind === SyntaxKind.DeclareKeyword || kind === SyntaxKind.ModuleKeyword
2065+
return isFunctionLikeBodyKeyword(kind)
2066+
|| kind === SyntaxKind.DeclareKeyword
2067+
|| kind === SyntaxKind.ModuleKeyword
20642068
|| isTypeKeyword(kind) && kind !== SyntaxKind.UndefinedKeyword;
2069+
case KeywordCompletionFilters.FunctionLikeBodyKeywords:
2070+
return isFunctionLikeBodyKeyword(kind);
20652071
case KeywordCompletionFilters.ClassElementKeywords:
20662072
return isClassMemberCompletionKeyword(kind);
20672073
case KeywordCompletionFilters.InterfaceElementKeywords:
20682074
return isInterfaceOrTypeLiteralCompletionKeyword(kind);
20692075
case KeywordCompletionFilters.ConstructorParameterKeywords:
20702076
return isParameterPropertyModifier(kind);
2071-
case KeywordCompletionFilters.FunctionLikeBodyKeywords:
2072-
return isFunctionLikeBodyKeyword(kind);
20732077
case KeywordCompletionFilters.TypeAssertionKeywords:
20742078
return isTypeKeyword(kind) || kind === SyntaxKind.ConstKeyword;
20752079
case KeywordCompletionFilters.TypeKeywords:
@@ -2132,7 +2136,9 @@ namespace ts.Completions {
21322136
}
21332137

21342138
function isFunctionLikeBodyKeyword(kind: SyntaxKind) {
2135-
return kind === SyntaxKind.AsyncKeyword || kind === SyntaxKind.AwaitKeyword || !isContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind);
2139+
return kind === SyntaxKind.AsyncKeyword
2140+
|| kind === SyntaxKind.AwaitKeyword
2141+
|| !isContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind);
21362142
}
21372143

21382144
function keywordForNode(node: Node): SyntaxKind {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
//// class Foo<T> { }
4+
//// class Bar { }
5+
//// function includesTypes() {
6+
//// new Foo</*1*/
7+
//// }
8+
//// function excludesTypes1() {
9+
//// new Bar</*2*/
10+
//// }
11+
//// function excludesTypes2() {
12+
//// 1</*3*/
13+
//// }
14+
15+
verify.completions(
16+
{
17+
marker: ["1"],
18+
includes: [
19+
{ name: "string", sortText: completion.SortText.GlobalsOrKeywords },
20+
{ name: "String", sortText: completion.SortText.GlobalsOrKeywords },
21+
],
22+
},
23+
{
24+
marker: ["2", "3"],
25+
excludes: ["string"]
26+
}
27+
);

tests/cases/fourslash/completionListInUnclosedTypeArguments.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
////f</*1b*/T/*2b*/y/*3b*/;
99
////f</*1c*/T/*2c*/y/*3c*/>
1010
////f</*1d*/T/*2d*/y/*3d*/>
11-
////f</*1eTypeOnly*/T/*2eTypeOnly*/y/*3eTypeOnly*/>();
11+
////f</*1e*/T/*2e*/y/*3e*/>();
1212
////
1313
////f2</*1k*/T/*2k*/y/*3k*/,
1414
////f2</*1l*/T/*2l*/y/*3l*/,{| "newId": true |}T{| "newId": true |}y{| "newId": true |}
1515
////f2</*1m*/T/*2m*/y/*3m*/,{| "newId": true |}T{| "newId": true |}y{| "newId": true |};
1616
////f2</*1n*/T/*2n*/y/*3n*/,{| "newId": true |}T{| "newId": true |}y{| "newId": true |}>
1717
////f2</*1o*/T/*2o*/y/*3o*/,{| "newId": true |}T{| "newId": true |}y{| "newId": true |}>
18-
////f2</*1pTypeOnly*/T/*2pTypeOnly*/y/*3pTypeOnly*/,{| "newId": true, "typeOnly": true |}T{| "newId": true, "typeOnly": true |}y{| "newId": true, "typeOnly": true |}>();
18+
////f2</*1p*/T/*2p*/y/*3p*/,{| "newId": true, "typeOnly": true |}T{| "newId": true, "typeOnly": true |}y{| "newId": true, "typeOnly": true |}>();
1919
////
2020
////f2<typeof /*1uValueOnly*/x, {| "newId": true |}T{| "newId": true |}y{| "newId": true |}
2121
////
@@ -25,12 +25,11 @@
2525

2626
goTo.eachMarker(marker => {
2727
const markerName = test.markerName(marker) || "";
28-
const typeOnly = markerName.endsWith("TypeOnly") || marker.data && marker.data.typeOnly;
2928
const valueOnly = markerName.endsWith("ValueOnly");
3029
verify.completions({
3130
marker,
32-
includes: typeOnly ? "Type" : valueOnly ? "x" : ["Type", "x"],
33-
excludes: typeOnly ? "x" : valueOnly ? "Type" : [],
31+
includes: valueOnly ? "x" : "Type",
32+
excludes: valueOnly ? "Type" : "x",
3433
isNewIdentifierLocation: marker.data && marker.data.newId || false,
3534
});
3635
});

tests/cases/fourslash/completionListIsGlobalCompletion.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,5 @@ verify.completions(
4848
{ marker: "13", exact: globals, isGlobalCompletion: false },
4949
{ marker: "15", exact: globals, isGlobalCompletion: true, isNewIdentifierLocation: true },
5050
{ marker: "16", exact: [...x, completion.globalThisEntry, ...completion.globalsVars, completion.undefinedVarEntry], isGlobalCompletion: false },
51-
{ marker: "17", exact: completion.globalKeywordsPlusUndefined, isGlobalCompletion: false },
51+
{ marker: "17", exact: completion.globalKeywords, isGlobalCompletion: false },
5252
);

tests/cases/fourslash/completionsIsPossiblyTypeArgumentPosition.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,22 @@
99
////x + {| "valueOnly": true |}
1010
////x < {| "valueOnly": true |}
1111
////f < {| "valueOnly": true |}
12-
////g < {| "valueOnly": false |}
13-
////const something: C<{| "typeOnly": true |};
14-
////const something2: C<C<{| "typeOnly": true |};
15-
////new C<{| "valueOnly": false |};
16-
////new C<C<{| "valueOnly": false |};
12+
////g < /*g*/
13+
////const something: C</*something*/;
14+
////const something2: C<C</*something2*/;
15+
////new C</*C*/;
16+
////new C<C</*CC*/;
1717
////
1818
////declare const callAndConstruct: { new<T>(): callAndConstruct<T>; <T>(): string; };
1919
////interface callAndConstruct<T> {}
2020
////new callAndConstruct<callAndConstruct</*callAndConstruct*/
2121

2222
for (const marker of test.markers()) {
23-
if (marker.data && marker.data.typeOnly) {
24-
verify.completions({ marker, includes: "T", excludes: "x" });
25-
}
26-
else if (marker.data && marker.data.valueOnly) {
23+
if (marker.data && marker.data.valueOnly) {
2724
verify.completions({ marker, includes: "x", excludes: "T" });
2825
}
2926
else {
30-
verify.completions({ marker, includes: ["x", "T"] });
27+
verify.completions({ marker, includes: "T", excludes: "x" });
3128
}
3229
}
3330

tests/cases/fourslash/fourslash.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,6 @@ declare namespace completion {
685685
export const globalInJsKeywords: ReadonlyArray<Entry>;
686686
export const insideMethodKeywords: ReadonlyArray<Entry>;
687687
export const insideMethodInJsKeywords: ReadonlyArray<Entry>;
688-
export const globalKeywordsPlusUndefined: ReadonlyArray<Entry>;
689688
export const globalsVars: ReadonlyArray<Entry>;
690689
export function globalsInsideFunction(plus: ReadonlyArray<Entry>): ReadonlyArray<Entry>;
691690
export function globalsInJsInsideFunction(plus: ReadonlyArray<Entry>): ReadonlyArray<Entry>;

tests/cases/user/prettier/prettier

Submodule prettier updated 174 files

0 commit comments

Comments
 (0)