Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 35 additions & 51 deletions src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1163,26 +1164,24 @@ 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
if (isExportAssignment(location.parent)) {
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);
}
Expand All @@ -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) {
Expand All @@ -1217,6 +1216,7 @@ namespace ts.Completions {
return parentKind === SyntaxKind.AsExpression;
}
}
return false;
}

function symbolCanBeReferencedAtTypeLocation(symbol: Symbol): boolean {
Expand Down Expand Up @@ -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<CompletionEntry>[] = [];
const allKeywordsCompletions: () => ReadonlyArray<CompletionEntry> = 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<CompletionEntry> {
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) {
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions src/services/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1070,12 +1070,16 @@ namespace ts {
export const typeKeywords: ReadonlyArray<SyntaxKind> = [
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 {
Expand Down
7 changes: 7 additions & 0 deletions tests/cases/fourslash/completionsTypeKeywords.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// <reference path="fourslash.ts" />

////type T = /**/

goTo.marker();
verify.completionListContains("undefined", "undefined", undefined, "keyword");
verify.not.completionListContains("await");