Skip to content

Commit 800f7a3

Browse files
authored
Merge pull request #30414 from Microsoft/jsSyntaxCompletions
Filter ts only keywords from js file completion
2 parents fa97054 + 34a7b7b commit 800f7a3

File tree

4 files changed

+165
-4
lines changed

4 files changed

+165
-4
lines changed

src/harness/fourslash.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4552,9 +4552,47 @@ namespace FourSlashInterface {
45524552
];
45534553
}
45544554

4555+
function getInJsKeywords(keywords: ReadonlyArray<ExpectedCompletionEntryObject>): ReadonlyArray<ExpectedCompletionEntryObject> {
4556+
return keywords.filter(keyword => {
4557+
switch (keyword.name) {
4558+
case "enum":
4559+
case "interface":
4560+
case "implements":
4561+
case "private":
4562+
case "protected":
4563+
case "public":
4564+
case "abstract":
4565+
case "any":
4566+
case "boolean":
4567+
case "declare":
4568+
case "infer":
4569+
case "is":
4570+
case "keyof":
4571+
case "module":
4572+
case "namespace":
4573+
case "never":
4574+
case "readonly":
4575+
case "number":
4576+
case "object":
4577+
case "string":
4578+
case "symbol":
4579+
case "type":
4580+
case "unique":
4581+
case "unknown":
4582+
case "global":
4583+
case "bigint":
4584+
return false;
4585+
default:
4586+
return true;
4587+
}
4588+
});
4589+
}
4590+
45554591
export const classElementKeywords: ReadonlyArray<ExpectedCompletionEntryObject> =
45564592
["private", "protected", "public", "static", "abstract", "async", "constructor", "get", "readonly", "set"].map(keywordEntry);
45574593

4594+
export const classElementInJsKeywords = getInJsKeywords(classElementKeywords);
4595+
45584596
export const constructorParameterKeywords: ReadonlyArray<ExpectedCompletionEntryObject> =
45594597
["private", "protected", "public", "readonly"].map((name): ExpectedCompletionEntryObject => ({ name, kind: "keyword" }));
45604598

@@ -4692,6 +4730,8 @@ namespace FourSlashInterface {
46924730
}
46934731
});
46944732

4733+
export const statementInJsKeywords = getInJsKeywords(statementKeywords);
4734+
46954735
export const globalsVars: ReadonlyArray<ExpectedCompletionEntryObject> = [
46964736
functionEntry("eval"),
46974737
functionEntry("parseInt"),
@@ -4793,6 +4833,18 @@ namespace FourSlashInterface {
47934833
...globalKeywordsInsideFunction,
47944834
];
47954835

4836+
const globalInJsKeywordsInsideFunction = getInJsKeywords(globalKeywordsInsideFunction);
4837+
4838+
// TODO: many of these are inappropriate to always provide
4839+
export const globalsInJsInsideFunction = (plus: ReadonlyArray<ExpectedCompletionEntry>): ReadonlyArray<ExpectedCompletionEntry> => [
4840+
{ name: "arguments", kind: "local var" },
4841+
{ name: "globalThis", kind: "module" },
4842+
...globalsVars,
4843+
...plus,
4844+
{ name: "undefined", kind: "var" },
4845+
...globalInJsKeywordsInsideFunction,
4846+
];
4847+
47964848
// TODO: many of these are inappropriate to always provide
47974849
export const globalKeywords: ReadonlyArray<ExpectedCompletionEntryObject> = [
47984850
"break",
@@ -4871,6 +4923,8 @@ namespace FourSlashInterface {
48714923
"of",
48724924
].map(keywordEntry);
48734925

4926+
export const globalInJsKeywords = getInJsKeywords(globalKeywords);
4927+
48744928
export const insideMethodKeywords: ReadonlyArray<ExpectedCompletionEntryObject> = [
48754929
"break",
48764930
"case",
@@ -4917,6 +4971,8 @@ namespace FourSlashInterface {
49174971
"await",
49184972
].map(keywordEntry);
49194973

4974+
export const insideMethodInJsKeywords = getInJsKeywords(insideMethodKeywords);
4975+
49204976
export const globalKeywordsPlusUndefined: ReadonlyArray<ExpectedCompletionEntryObject> = (() => {
49214977
const i = ts.findIndex(globalKeywords, x => x.name === "unique");
49224978
return [...globalKeywords.slice(0, i), keywordEntry("undefined"), ...globalKeywords.slice(i)];
@@ -4929,6 +4985,13 @@ namespace FourSlashInterface {
49294985
...globalKeywords
49304986
];
49314987

4988+
export const globalsInJs: ReadonlyArray<ExpectedCompletionEntryObject> = [
4989+
{ name: "globalThis", kind: "module" },
4990+
...globalsVars,
4991+
{ name: "undefined", kind: "var" },
4992+
...globalInJsKeywords
4993+
];
4994+
49324995
export function globalsPlus(plus: ReadonlyArray<ExpectedCompletionEntry>): ReadonlyArray<ExpectedCompletionEntry> {
49334996
return [
49344997
{ name: "globalThis", kind: "module" },
@@ -4937,6 +5000,15 @@ namespace FourSlashInterface {
49375000
{ name: "undefined", kind: "var" },
49385001
...globalKeywords];
49395002
}
5003+
5004+
export function globalsInJsPlus(plus: ReadonlyArray<ExpectedCompletionEntry>): ReadonlyArray<ExpectedCompletionEntry> {
5005+
return [
5006+
{ name: "globalThis", kind: "module" },
5007+
...globalsVars,
5008+
...plus,
5009+
{ name: "undefined", kind: "var" },
5010+
...globalInJsKeywords];
5011+
}
49405012
}
49415013

49425014
export interface ReferenceGroup {

src/services/completions.ts

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ namespace ts.Completions {
3030
ConstructorParameterKeywords, // Keywords at constructor parameter
3131
FunctionLikeBodyKeywords, // Keywords at function like body
3232
TypeKeywords,
33+
Last = TypeKeywords
3334
}
3435

3536
const enum GlobalsSearch { Continue, Success, Fail }
@@ -77,7 +78,7 @@ namespace ts.Completions {
7778
}
7879

7980
function completionInfoFromData(sourceFile: SourceFile, typeChecker: TypeChecker, compilerOptions: CompilerOptions, log: Log, completionData: CompletionData, preferences: UserPreferences): CompletionInfo | undefined {
80-
const { symbols, completionKind, isInSnippetScope, isNewIdentifierLocation, location, propertyAccessToConvert, keywordFilters, literals, symbolToOriginInfoMap, recommendedCompletion, isJsxInitializer } = completionData;
81+
const { symbols, completionKind, isInSnippetScope, isNewIdentifierLocation, location, propertyAccessToConvert, keywordFilters, literals, symbolToOriginInfoMap, recommendedCompletion, isJsxInitializer, insideJsDocTagTypeExpression } = completionData;
8182

8283
if (location && location.parent && isJsxClosingElement(location.parent)) {
8384
// In the TypeScript JSX element, if such element is not defined. When users query for completion at closing tag,
@@ -113,7 +114,7 @@ namespace ts.Completions {
113114

114115
if (keywordFilters !== KeywordCompletionFilters.None) {
115116
const entryNames = arrayToSet(entries, e => e.name);
116-
for (const keywordEntry of getKeywordCompletions(keywordFilters)) {
117+
for (const keywordEntry of getKeywordCompletions(keywordFilters, !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile))) {
117118
if (!entryNames.has(keywordEntry.name)) {
118119
entries.push(keywordEntry);
119120
}
@@ -510,6 +511,7 @@ namespace ts.Completions {
510511
readonly recommendedCompletion: Symbol | undefined;
511512
readonly previousToken: Node | undefined;
512513
readonly isJsxInitializer: IsJsxInitializer;
514+
readonly insideJsDocTagTypeExpression: boolean;
513515
}
514516
type Request = { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag };
515517

@@ -837,7 +839,22 @@ namespace ts.Completions {
837839
const literals = mapDefined(contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), t => t.isLiteral() ? t.value : undefined);
838840

839841
const recommendedCompletion = previousToken && contextualType && getRecommendedCompletion(previousToken, contextualType, typeChecker);
840-
return { kind: CompletionDataKind.Data, symbols, completionKind, isInSnippetScope, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, literals, symbolToOriginInfoMap, recommendedCompletion, previousToken, isJsxInitializer };
842+
return {
843+
kind: CompletionDataKind.Data,
844+
symbols,
845+
completionKind,
846+
isInSnippetScope,
847+
propertyAccessToConvert,
848+
isNewIdentifierLocation,
849+
location,
850+
keywordFilters,
851+
literals,
852+
symbolToOriginInfoMap,
853+
recommendedCompletion,
854+
previousToken,
855+
isJsxInitializer,
856+
insideJsDocTagTypeExpression
857+
};
841858

842859
type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag;
843860

@@ -1929,7 +1946,18 @@ namespace ts.Completions {
19291946
}
19301947
return res;
19311948
});
1932-
function getKeywordCompletions(keywordFilter: KeywordCompletionFilters): ReadonlyArray<CompletionEntry> {
1949+
1950+
function getKeywordCompletions(keywordFilter: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean): ReadonlyArray<CompletionEntry> {
1951+
if (!filterOutTsOnlyKeywords) return getTypescriptKeywordCompletions(keywordFilter);
1952+
1953+
const index = keywordFilter + KeywordCompletionFilters.Last + 1;
1954+
return _keywordCompletions[index] ||
1955+
(_keywordCompletions[index] = getTypescriptKeywordCompletions(keywordFilter)
1956+
.filter(entry => !isTypeScriptOnlyKeyword(stringToToken(entry.name)!))
1957+
);
1958+
}
1959+
1960+
function getTypescriptKeywordCompletions(keywordFilter: KeywordCompletionFilters): ReadonlyArray<CompletionEntry> {
19331961
return _keywordCompletions[keywordFilter] || (_keywordCompletions[keywordFilter] = allKeywordsCompletions().filter(entry => {
19341962
const kind = stringToToken(entry.name)!;
19351963
switch (keywordFilter) {
@@ -1954,6 +1982,40 @@ namespace ts.Completions {
19541982
}));
19551983
}
19561984

1985+
function isTypeScriptOnlyKeyword(kind: SyntaxKind) {
1986+
switch (kind) {
1987+
case SyntaxKind.AbstractKeyword:
1988+
case SyntaxKind.AnyKeyword:
1989+
case SyntaxKind.BigIntKeyword:
1990+
case SyntaxKind.BooleanKeyword:
1991+
case SyntaxKind.DeclareKeyword:
1992+
case SyntaxKind.EnumKeyword:
1993+
case SyntaxKind.GlobalKeyword:
1994+
case SyntaxKind.ImplementsKeyword:
1995+
case SyntaxKind.InferKeyword:
1996+
case SyntaxKind.InterfaceKeyword:
1997+
case SyntaxKind.IsKeyword:
1998+
case SyntaxKind.KeyOfKeyword:
1999+
case SyntaxKind.ModuleKeyword:
2000+
case SyntaxKind.NamespaceKeyword:
2001+
case SyntaxKind.NeverKeyword:
2002+
case SyntaxKind.NumberKeyword:
2003+
case SyntaxKind.ObjectKeyword:
2004+
case SyntaxKind.PrivateKeyword:
2005+
case SyntaxKind.ProtectedKeyword:
2006+
case SyntaxKind.PublicKeyword:
2007+
case SyntaxKind.ReadonlyKeyword:
2008+
case SyntaxKind.StringKeyword:
2009+
case SyntaxKind.SymbolKeyword:
2010+
case SyntaxKind.TypeKeyword:
2011+
case SyntaxKind.UniqueKeyword:
2012+
case SyntaxKind.UnknownKeyword:
2013+
return true;
2014+
default:
2015+
return false;
2016+
}
2017+
}
2018+
19572019
function isInterfaceOrTypeLiteralCompletionKeyword(kind: SyntaxKind): boolean {
19582020
return kind === SyntaxKind.ReadonlyKeyword;
19592021
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
///<reference path="fourslash.ts" />
2+
3+
// @allowJs: true
4+
// @Filename: /Foo.js
5+
//// /*global*/
6+
////class classA {
7+
//// /*class*/
8+
////}
9+
////class Test7 {
10+
//// constructor(/*constructorParameter*/){}
11+
////}
12+
////function foo() {
13+
/////*insideFunction*/
14+
////}
15+
verify.completions(
16+
{ marker: "global", exact: completion.globalsInJsPlus(["foo", "classA", "Test7"]) },
17+
{ marker: "class", isNewIdentifierLocation: true, exact: ["classA", "Test7", "foo", ...completion.classElementInJsKeywords] },
18+
{ marker: "constructorParameter", isNewIdentifierLocation: true, exact: ["classA", "Test7", "foo"] },
19+
{ marker: "insideFunction", exact: completion.globalsInJsInsideFunction(["foo", "classA", "Test7"]) },
20+
);

tests/cases/fourslash/fourslash.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,22 +650,29 @@ declare var classification: typeof FourSlashInterface.classification;
650650
declare namespace completion {
651651
type Entry = FourSlashInterface.ExpectedCompletionEntryObject;
652652
export const globals: ReadonlyArray<Entry>;
653+
export const globalsInJs: ReadonlyArray<Entry>;
653654
export const globalKeywords: ReadonlyArray<Entry>;
655+
export const globalInJsKeywords: ReadonlyArray<Entry>;
654656
export const insideMethodKeywords: ReadonlyArray<Entry>;
657+
export const insideMethodInJsKeywords: ReadonlyArray<Entry>;
655658
export const globalKeywordsPlusUndefined: ReadonlyArray<Entry>;
656659
export const globalsVars: ReadonlyArray<Entry>;
657660
export function globalsInsideFunction(plus: ReadonlyArray<Entry>): ReadonlyArray<Entry>;
661+
export function globalsInJsInsideFunction(plus: ReadonlyArray<Entry>): ReadonlyArray<Entry>;
658662
export function globalsPlus(plus: ReadonlyArray<FourSlashInterface.ExpectedCompletionEntry>): ReadonlyArray<Entry>;
663+
export function globalsInJsPlus(plus: ReadonlyArray<FourSlashInterface.ExpectedCompletionEntry>): ReadonlyArray<Entry>;
659664
export const keywordsWithUndefined: ReadonlyArray<Entry>;
660665
export const keywords: ReadonlyArray<Entry>;
661666
export const typeKeywords: ReadonlyArray<Entry>;
662667
export const globalTypes: ReadonlyArray<Entry>;
663668
export function globalTypesPlus(plus: ReadonlyArray<FourSlashInterface.ExpectedCompletionEntry>): ReadonlyArray<Entry>;
664669
export const classElementKeywords: ReadonlyArray<Entry>;
670+
export const classElementInJsKeywords: ReadonlyArray<Entry>;
665671
export const constructorParameterKeywords: ReadonlyArray<Entry>;
666672
export const functionMembers: ReadonlyArray<Entry>;
667673
export const stringMembers: ReadonlyArray<Entry>;
668674
export const functionMembersWithPrototype: ReadonlyArray<Entry>;
669675
export const statementKeywordsWithTypes: ReadonlyArray<Entry>;
670676
export const statementKeywords: ReadonlyArray<Entry>;
677+
export const statementInJsKeywords: ReadonlyArray<Entry>;
671678
}

0 commit comments

Comments
 (0)