From f829f958a2ba7aee116eff169d3be18a57b8179a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 14 Mar 2019 13:58:57 -0700 Subject: [PATCH] Filter ts only keywords from js file completion Fixes #29212 --- src/harness/fourslash.ts | 221 ++++++++++++++++++ src/services/completions.ts | 70 +++++- .../fourslash/completionEntryInJsFile.ts | 20 ++ tests/cases/fourslash/fourslash.ts | 7 + 4 files changed, 314 insertions(+), 4 deletions(-) create mode 100644 tests/cases/fourslash/completionEntryInJsFile.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 16f1a37ae8..1ba8b03e42 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -4555,6 +4555,9 @@ namespace FourSlashInterface { export const classElementKeywords: ReadonlyArray = ["private", "protected", "public", "static", "abstract", "async", "constructor", "get", "readonly", "set"].map(keywordEntry); + export const classElementInJsKeywords: ReadonlyArray = + ["async", "constructor", "get", "set"].map(keywordEntry); + export const constructorParameterKeywords: ReadonlyArray = ["private", "protected", "public", "readonly"].map((name): ExpectedCompletionEntryObject => ({ name, kind: "keyword" })); @@ -4692,6 +4695,59 @@ namespace FourSlashInterface { } }); + export const statementInJsKeywords: ReadonlyArray = [ + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "enum", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + "implements", + "interface", + "let", + "package", + "yield", + "as", + "async", + "await", + "constructor", + "get", + "require", + "set", + "from", + "of", + ].map(keywordEntry); + export const globalsVars: ReadonlyArray = [ functionEntry("eval"), functionEntry("parseInt"), @@ -4793,6 +4849,60 @@ namespace FourSlashInterface { ...globalKeywordsInsideFunction, ]; + const globalInJsKeywordsInsideFunction: ReadonlyArray = [ + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + "implements", + "let", + "package", + "yield", + "async", + "await", + ].map(keywordEntry); + + // TODO: many of these are inappropriate to always provide + export const globalsInJsInsideFunction = (plus: ReadonlyArray): ReadonlyArray => [ + { name: "arguments", kind: "local var" }, + { name: "globalThis", kind: "module" }, + ...globalsVars, + ...plus, + { name: "undefined", kind: "var" }, + ...globalInJsKeywordsInsideFunction, + ]; + // TODO: many of these are inappropriate to always provide export const globalKeywords: ReadonlyArray = [ "break", @@ -4871,6 +4981,57 @@ namespace FourSlashInterface { "of", ].map(keywordEntry); + export const globalInJsKeywords: ReadonlyArray = [ + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + "implements", + "let", + "package", + "yield", + "as", + "async", + "await", + "constructor", + "get", + "require", + "set", + "from", + "of", + ].map(keywordEntry); + export const insideMethodKeywords: ReadonlyArray = [ "break", "case", @@ -4917,6 +5078,50 @@ namespace FourSlashInterface { "await", ].map(keywordEntry); + export const insideMethodInJsKeywords: ReadonlyArray = [ + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + "implements", + "let", + "package", + "yield", + "async", + "await", + ].map(keywordEntry); + export const globalKeywordsPlusUndefined: ReadonlyArray = (() => { const i = ts.findIndex(globalKeywords, x => x.name === "unique"); return [...globalKeywords.slice(0, i), keywordEntry("undefined"), ...globalKeywords.slice(i)]; @@ -4929,6 +5134,13 @@ namespace FourSlashInterface { ...globalKeywords ]; + export const globalsInJs: ReadonlyArray = [ + { name: "globalThis", kind: "module" }, + ...globalsVars, + { name: "undefined", kind: "var" }, + ...globalInJsKeywords + ]; + export function globalsPlus(plus: ReadonlyArray): ReadonlyArray { return [ { name: "globalThis", kind: "module" }, @@ -4937,6 +5149,15 @@ namespace FourSlashInterface { { name: "undefined", kind: "var" }, ...globalKeywords]; } + + export function globalsInJsPlus(plus: ReadonlyArray): ReadonlyArray { + return [ + { name: "globalThis", kind: "module" }, + ...globalsVars, + ...plus, + { name: "undefined", kind: "var" }, + ...globalInJsKeywords]; + } } export interface ReferenceGroup { diff --git a/src/services/completions.ts b/src/services/completions.ts index 5cdaca020c..e1be024463 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -30,6 +30,7 @@ namespace ts.Completions { ConstructorParameterKeywords, // Keywords at constructor parameter FunctionLikeBodyKeywords, // Keywords at function like body TypeKeywords, + Last = TypeKeywords } const enum GlobalsSearch { Continue, Success, Fail } @@ -77,7 +78,7 @@ namespace ts.Completions { } function completionInfoFromData(sourceFile: SourceFile, typeChecker: TypeChecker, compilerOptions: CompilerOptions, log: Log, completionData: CompletionData, preferences: UserPreferences): CompletionInfo | undefined { - const { symbols, completionKind, isInSnippetScope, isNewIdentifierLocation, location, propertyAccessToConvert, keywordFilters, literals, symbolToOriginInfoMap, recommendedCompletion, isJsxInitializer } = completionData; + const { symbols, completionKind, isInSnippetScope, isNewIdentifierLocation, location, propertyAccessToConvert, keywordFilters, literals, symbolToOriginInfoMap, recommendedCompletion, isJsxInitializer, insideJsDocTagTypeExpression } = completionData; if (location && location.parent && isJsxClosingElement(location.parent)) { // 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 { if (keywordFilters !== KeywordCompletionFilters.None) { const entryNames = arrayToSet(entries, e => e.name); - for (const keywordEntry of getKeywordCompletions(keywordFilters)) { + for (const keywordEntry of getKeywordCompletions(keywordFilters, !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile))) { if (!entryNames.has(keywordEntry.name)) { entries.push(keywordEntry); } @@ -510,6 +511,7 @@ namespace ts.Completions { readonly recommendedCompletion: Symbol | undefined; readonly previousToken: Node | undefined; readonly isJsxInitializer: IsJsxInitializer; + readonly insideJsDocTagTypeExpression: boolean; } type Request = { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag }; @@ -837,7 +839,22 @@ namespace ts.Completions { const literals = mapDefined(contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), t => t.isLiteral() ? t.value : undefined); const recommendedCompletion = previousToken && contextualType && getRecommendedCompletion(previousToken, contextualType, typeChecker); - return { kind: CompletionDataKind.Data, symbols, completionKind, isInSnippetScope, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, literals, symbolToOriginInfoMap, recommendedCompletion, previousToken, isJsxInitializer }; + return { + kind: CompletionDataKind.Data, + symbols, + completionKind, + isInSnippetScope, + propertyAccessToConvert, + isNewIdentifierLocation, + location, + keywordFilters, + literals, + symbolToOriginInfoMap, + recommendedCompletion, + previousToken, + isJsxInitializer, + insideJsDocTagTypeExpression + }; type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; @@ -1927,7 +1944,18 @@ namespace ts.Completions { } return res; }); - function getKeywordCompletions(keywordFilter: KeywordCompletionFilters): ReadonlyArray { + + function getKeywordCompletions(keywordFilter: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean): ReadonlyArray { + if (!filterOutTsOnlyKeywords) return getTypescriptKeywordCompletions(keywordFilter); + + const index = keywordFilter + KeywordCompletionFilters.Last + 1; + return _keywordCompletions[index] || + (_keywordCompletions[index] = getTypescriptKeywordCompletions(keywordFilter) + .filter(entry => !isTypeScriptOnlyKeyword(stringToToken(entry.name)!)) + ); + } + + function getTypescriptKeywordCompletions(keywordFilter: KeywordCompletionFilters): ReadonlyArray { return _keywordCompletions[keywordFilter] || (_keywordCompletions[keywordFilter] = allKeywordsCompletions().filter(entry => { const kind = stringToToken(entry.name)!; switch (keywordFilter) { @@ -1952,6 +1980,40 @@ namespace ts.Completions { })); } + function isTypeScriptOnlyKeyword(kind: SyntaxKind) { + switch (kind) { + case SyntaxKind.AbstractKeyword: + case SyntaxKind.AnyKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.EnumKeyword: + case SyntaxKind.GlobalKeyword: + case SyntaxKind.InferKeyword: + case SyntaxKind.InterfaceKeyword: + case SyntaxKind.IsKeyword: + case SyntaxKind.KeyOfKeyword: + case SyntaxKind.ModuleKeyword: + case SyntaxKind.NamespaceKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.ObjectKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.PublicKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.StaticKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.TypeKeyword: + case SyntaxKind.UniqueKeyword: + case SyntaxKind.UnknownKeyword: + return true; + default: + return false; + } + } + function isInterfaceOrTypeLiteralCompletionKeyword(kind: SyntaxKind): boolean { return kind === SyntaxKind.ReadonlyKeyword; } diff --git a/tests/cases/fourslash/completionEntryInJsFile.ts b/tests/cases/fourslash/completionEntryInJsFile.ts new file mode 100644 index 0000000000..a3492d9de3 --- /dev/null +++ b/tests/cases/fourslash/completionEntryInJsFile.ts @@ -0,0 +1,20 @@ +/// + +// @allowJs: true +// @Filename: /Foo.js +//// /*global*/ +////class classA { +//// /*class*/ +////} +////class Test7 { +//// constructor(/*constructorParameter*/){} +////} +////function foo() { +/////*insideFunction*/ +////} +verify.completions( + { marker: "global", exact: completion.globalsInJsPlus(["foo", "classA", "Test7"]) }, + { marker: "class", isNewIdentifierLocation: true, exact: ["classA", "Test7", "foo", ...completion.classElementInJsKeywords] }, + { marker: "constructorParameter", isNewIdentifierLocation: true, exact: ["classA", "Test7", "foo"] }, + { marker: "insideFunction", exact: completion.globalsInJsInsideFunction(["foo", "classA", "Test7"]) }, +); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index db4f7be009..6488748b72 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -650,22 +650,29 @@ declare var classification: typeof FourSlashInterface.classification; declare namespace completion { type Entry = FourSlashInterface.ExpectedCompletionEntryObject; export const globals: ReadonlyArray; + export const globalsInJs: ReadonlyArray; export const globalKeywords: ReadonlyArray; + export const globalInJsKeywords: ReadonlyArray; export const insideMethodKeywords: ReadonlyArray; + export const insideMethodInJsKeywords: ReadonlyArray; export const globalKeywordsPlusUndefined: ReadonlyArray; export const globalsVars: ReadonlyArray; export function globalsInsideFunction(plus: ReadonlyArray): ReadonlyArray; + export function globalsInJsInsideFunction(plus: ReadonlyArray): ReadonlyArray; export function globalsPlus(plus: ReadonlyArray): ReadonlyArray; + export function globalsInJsPlus(plus: ReadonlyArray): ReadonlyArray; export const keywordsWithUndefined: ReadonlyArray; export const keywords: ReadonlyArray; export const typeKeywords: ReadonlyArray; export const globalTypes: ReadonlyArray; export function globalTypesPlus(plus: ReadonlyArray): ReadonlyArray; export const classElementKeywords: ReadonlyArray; + export const classElementInJsKeywords: ReadonlyArray; export const constructorParameterKeywords: ReadonlyArray; export const functionMembers: ReadonlyArray; export const stringMembers: ReadonlyArray; export const functionMembersWithPrototype: ReadonlyArray; export const statementKeywordsWithTypes: ReadonlyArray; export const statementKeywords: ReadonlyArray; + export const statementInJsKeywords: ReadonlyArray; }