From ddab2dd3087a8ff962e3885d79509a72e1dbbadf Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 24 Jan 2018 15:06:21 -0800 Subject: [PATCH] Add KeywordCompletionFilters.TypeKeywords (#21364) (#21396) --- src/services/completions.ts | 86 ++++++++----------- src/services/utilities.ts | 4 + .../fourslash/completionsTypeKeywords.ts | 7 ++ 3 files changed, 46 insertions(+), 51 deletions(-) create mode 100644 tests/cases/fourslash/completionsTypeKeywords.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 80fdf6c280e..8b7f564ff08 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -20,7 +20,8 @@ namespace ts.Completions { None, ClassElementKeywords, // Keywords at class keyword ConstructorParameterKeywords, // Keywords at constructor parameter - FunctionLikeBodyKeywords // Keywords at function like body + FunctionLikeBodyKeywords, // Keywords at function like body + TypeKeywords, } export function getCompletionsAtPosition( @@ -565,7 +566,7 @@ namespace ts.Completions { } case "none": { // Didn't find a symbol with this name. See if we can find a keyword instead. - if (some(getKeywordCompletions(KeywordCompletionFilters.None), c => c.name === name)) { + if (allKeywordsCompletions().some(c => c.name === name)) { return { name, kind: ScriptElementKind.keyword, @@ -1163,6 +1164,9 @@ namespace ts.Completions { } function filterGlobalCompletion(symbols: Symbol[]): void { + const isTypeCompletion = insideJsDocTagTypeExpression || !isContextTokenValueLocation(contextToken) && (isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken)); + if (isTypeCompletion) keywordFilters = KeywordCompletionFilters.TypeKeywords; + filterMutate(symbols, symbol => { if (!isSourceFile(location)) { // export = /**/ here we want to get all meanings, so any symbol is ok @@ -1170,19 +1174,14 @@ namespace ts.Completions { return true; } - // This is an alias, follow what it aliases - if (symbol && symbol.flags & SymbolFlags.Alias) { - symbol = typeChecker.getAliasedSymbol(symbol); - } + symbol = skipAlias(symbol, typeChecker); // import m = /**/ <-- It can only access namespace (if typing import = x. this would get member symbols and not namespace) if (isInRightSideOfInternalImportEqualsDeclaration(location)) { return !!(symbol.flags & SymbolFlags.Namespace); } - if (insideJsDocTagTypeExpression || - (!isContextTokenValueLocation(contextToken) && - (isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken)))) { + if (isTypeCompletion) { // Its a type, but you can reach it by namespace.type as well return symbolCanBeReferencedAtTypeLocation(symbol); } @@ -1199,7 +1198,7 @@ namespace ts.Completions { contextToken.parent.kind === SyntaxKind.TypeQuery; } - function isContextTokenTypeLocation(contextToken: Node) { + function isContextTokenTypeLocation(contextToken: Node): boolean { if (contextToken) { const parentKind = contextToken.parent.kind; switch (contextToken.kind) { @@ -1217,6 +1216,7 @@ namespace ts.Completions { return parentKind === SyntaxKind.AsExpression; } } + return false; } function symbolCanBeReferencedAtTypeLocation(symbol: Symbol): boolean { @@ -2130,51 +2130,38 @@ namespace ts.Completions { } // A cache of completion entries for keywords, these do not change between sessions - const _keywordCompletions: CompletionEntry[][] = []; - function getKeywordCompletions(keywordFilter: KeywordCompletionFilters): CompletionEntry[] { - const completions = _keywordCompletions[keywordFilter]; - if (completions) { - return completions; + const _keywordCompletions: ReadonlyArray[] = []; + const allKeywordsCompletions: () => ReadonlyArray = ts.memoize(() => { + const res: CompletionEntry[] = []; + for (let i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) { + res.push({ + name: tokenToString(i), + kind: ScriptElementKind.keyword, + kindModifiers: ScriptElementKindModifier.none, + sortText: "0" + }); } - return _keywordCompletions[keywordFilter] = generateKeywordCompletions(keywordFilter); - - type FilterKeywordCompletions = (entryName: string) => boolean; - function generateKeywordCompletions(keywordFilter: KeywordCompletionFilters): CompletionEntry[] { + return res; + }); + function getKeywordCompletions(keywordFilter: KeywordCompletionFilters): ReadonlyArray { + return _keywordCompletions[keywordFilter] || (_keywordCompletions[keywordFilter] = allKeywordsCompletions().filter(entry => { + const kind = stringToToken(entry.name); switch (keywordFilter) { case KeywordCompletionFilters.None: - return getAllKeywordCompletions(); + // "undefined" is a global variable, so don't need a keyword completion for it. + return kind !== SyntaxKind.UndefinedKeyword; case KeywordCompletionFilters.ClassElementKeywords: - return getFilteredKeywordCompletions(isClassMemberCompletionKeywordText); + return isClassMemberCompletionKeyword(kind); case KeywordCompletionFilters.ConstructorParameterKeywords: - return getFilteredKeywordCompletions(isConstructorParameterCompletionKeywordText); + return isConstructorParameterCompletionKeyword(kind); case KeywordCompletionFilters.FunctionLikeBodyKeywords: - return getFilteredKeywordCompletions(isFunctionLikeBodyCompletionKeywordText); + return isFunctionLikeBodyCompletionKeyword(kind); + case KeywordCompletionFilters.TypeKeywords: + return isTypeKeyword(kind); default: - Debug.assertNever(keywordFilter); + return Debug.assertNever(keywordFilter); } - } - - function getAllKeywordCompletions() { - const allKeywordsCompletions: CompletionEntry[] = []; - for (let i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) { - // "undefined" is a global variable, so don't need a keyword completion for it. - if (i === SyntaxKind.UndefinedKeyword) continue; - allKeywordsCompletions.push({ - name: tokenToString(i), - kind: ScriptElementKind.keyword, - kindModifiers: ScriptElementKindModifier.none, - sortText: "0" - }); - } - return allKeywordsCompletions; - } - - function getFilteredKeywordCompletions(filterFn: FilterKeywordCompletions) { - return filter( - getKeywordCompletions(KeywordCompletionFilters.None), - entry => filterFn(entry.name) - ); - } + })); } function isClassMemberCompletionKeyword(kind: SyntaxKind) { @@ -2222,15 +2209,12 @@ namespace ts.Completions { case SyntaxKind.AbstractKeyword: case SyntaxKind.GetKeyword: case SyntaxKind.SetKeyword: + case SyntaxKind.UndefinedKeyword: return false; } return true; } - function isFunctionLikeBodyCompletionKeywordText(text: string) { - return isFunctionLikeBodyCompletionKeyword(stringToToken(text)); - } - function isEqualityOperatorKind(kind: ts.SyntaxKind): kind is EqualityOperator { switch (kind) { case ts.SyntaxKind.EqualsEqualsEqualsToken: diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 9bb35359523..c724fa5643d 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1070,12 +1070,16 @@ namespace ts { export const typeKeywords: ReadonlyArray = [ SyntaxKind.AnyKeyword, SyntaxKind.BooleanKeyword, + SyntaxKind.KeyOfKeyword, SyntaxKind.NeverKeyword, + SyntaxKind.NullKeyword, SyntaxKind.NumberKeyword, SyntaxKind.ObjectKeyword, SyntaxKind.StringKeyword, SyntaxKind.SymbolKeyword, SyntaxKind.VoidKeyword, + SyntaxKind.UndefinedKeyword, + SyntaxKind.UniqueKeyword, ]; export function isTypeKeyword(kind: SyntaxKind): boolean { diff --git a/tests/cases/fourslash/completionsTypeKeywords.ts b/tests/cases/fourslash/completionsTypeKeywords.ts new file mode 100644 index 00000000000..198d6dddab4 --- /dev/null +++ b/tests/cases/fourslash/completionsTypeKeywords.ts @@ -0,0 +1,7 @@ +/// + +////type T = /**/ + +goTo.marker(); +verify.completionListContains("undefined", "undefined", undefined, "keyword"); +verify.not.completionListContains("await");