From afcced68393179e2b5d454e6a75b6bed967f3c42 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 12 Apr 2018 11:55:21 -0700 Subject: [PATCH] Simplify tryGetImportOrExportClauseCompletionSymbols (#22961) * Simplify tryGetImportOrExportClauseCompletionSymbols * Handle undefined key in arrayToSet --- src/compiler/core.ts | 14 ++++--- src/compiler/utilities.ts | 5 +++ src/services/completions.ts | 73 +++++-------------------------------- 3 files changed, 22 insertions(+), 70 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index c4885e9d683..a14e92c3e5b 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1314,12 +1314,13 @@ namespace ts { * the same key with the given 'makeKey' function, then the element with the higher * index in the array will be the one associated with the produced key. */ - export function arrayToMap(array: ReadonlyArray, makeKey: (value: T) => string): Map; - export function arrayToMap(array: ReadonlyArray, makeKey: (value: T) => string, makeValue: (value: T) => U): Map; - export function arrayToMap(array: ReadonlyArray, makeKey: (value: T) => string, makeValue: (value: T) => T | U = identity): Map { + export function arrayToMap(array: ReadonlyArray, makeKey: (value: T) => string | undefined): Map; + export function arrayToMap(array: ReadonlyArray, makeKey: (value: T) => string | undefined, makeValue: (value: T) => U): Map; + export function arrayToMap(array: ReadonlyArray, makeKey: (value: T) => string | undefined, makeValue: (value: T) => T | U = identity): Map { const result = createMap(); for (const value of array) { - result.set(makeKey(value), makeValue(value)); + const key = makeKey(value); + if (key !== undefined) result.set(key, makeValue(value)); } return result; } @@ -1340,8 +1341,9 @@ namespace ts { * @param array the array of input elements. */ export function arrayToSet(array: ReadonlyArray): Map; - export function arrayToSet(array: ReadonlyArray, makeKey: (value: T) => string): Map; - export function arrayToSet(array: ReadonlyArray, makeKey?: (value: any) => string): Map { + export function arrayToSet(array: ReadonlyArray, makeKey: (value: T) => string | undefined): Map; + export function arrayToSet(array: ReadonlyArray, makeKey: (value: T) => __String | undefined): UnderscoreEscapedMap; + export function arrayToSet(array: ReadonlyArray, makeKey?: (value: any) => string | __String | undefined): Map | UnderscoreEscapedMap { return arrayToMap(array, makeKey || (s => s), () => true); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index aceb447eff5..850d26f8473 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6325,4 +6325,9 @@ namespace ts { export function isStringLiteralLike(node: Node): node is StringLiteralLike { return node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NoSubstitutionTemplateLiteral; } + + /** @internal */ + export function isNamedImportsOrExports(node: Node): node is NamedImportsOrExports { + return node.kind === SyntaxKind.NamedImports || node.kind === SyntaxKind.NamedExports; + } } diff --git a/src/services/completions.ts b/src/services/completions.ts index 52ae6803ba3..6e77d71840e 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1555,32 +1555,22 @@ namespace ts.Completions { * @returns true if 'symbols' was successfully populated; false otherwise. */ function tryGetImportOrExportClauseCompletionSymbols(): GlobalsSearch { - const namedImportsOrExports = tryGetNamedImportsOrExportsForCompletion(contextToken); - if (!namedImportsOrExports) return undefined; + // `import { |` or `import { a as 0, | }` + const namedImportsOrExports = contextToken && (contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken) + ? tryCast(contextToken.parent, isNamedImportsOrExports) : undefined; + if (!namedImportsOrExports) return GlobalsSearch.Continue; // cursor is in an import clause // try to show exported member for imported module - const declarationKind = namedImportsOrExports.kind === SyntaxKind.NamedImports ? - SyntaxKind.ImportDeclaration : - SyntaxKind.ExportDeclaration; - const importOrExportDeclaration = getAncestor(namedImportsOrExports, declarationKind); - const moduleSpecifier = importOrExportDeclaration.moduleSpecifier; - - if (!moduleSpecifier) { - return GlobalsSearch.Fail; - } + const { moduleSpecifier } = namedImportsOrExports.kind === SyntaxKind.NamedImports ? namedImportsOrExports.parent.parent : namedImportsOrExports.parent; + const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(moduleSpecifier); + if (!moduleSpecifierSymbol) return GlobalsSearch.Fail; completionKind = CompletionKind.MemberLike; isNewIdentifierLocation = false; - - const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(moduleSpecifier); - if (!moduleSpecifierSymbol) { - symbols = emptyArray; - return GlobalsSearch.Fail; - } - const exports = typeChecker.getExportsAndPropertiesOfModule(moduleSpecifierSymbol); - symbols = filterNamedImportOrExportCompletionItems(exports, namedImportsOrExports.elements); + const existing = arrayToSet(namedImportsOrExports.elements, n => isCurrentlyEditingNode(n) ? undefined : (n.propertyName || n.name).escapedText); + symbols = exports.filter(e => e.escapedName !== InternalSymbolName.Default && !existing.get(e.escapedName)); return GlobalsSearch.Success; } @@ -1648,26 +1638,6 @@ namespace ts.Completions { return undefined; } - /** - * Returns the containing list of named imports or exports of a context token, - * on the condition that one exists and that the context implies completion should be given. - */ - function tryGetNamedImportsOrExportsForCompletion(contextToken: Node): NamedImportsOrExports { - if (contextToken) { - switch (contextToken.kind) { - case SyntaxKind.OpenBraceToken: // import { | - case SyntaxKind.CommaToken: // import { a as 0, | - switch (contextToken.parent.kind) { - case SyntaxKind.NamedImports: - case SyntaxKind.NamedExports: - return contextToken.parent; - } - } - } - - return undefined; - } - function isConstructorParameterCompletion(node: Node): boolean { return !!node.parent && isParameter(node.parent) && isConstructorDeclaration(node.parent.parent) && (isParameterPropertyModifier(node.kind) || isDeclarationName(node)); @@ -1911,31 +1881,6 @@ namespace ts.Completions { return false; } - /** - * Filters out completion suggestions for named imports or exports. - * - * @param exportsOfModule The list of symbols which a module exposes. - * @param namedImportsOrExports The list of existing import/export specifiers in the import/export clause. - * - * @returns Symbols to be suggested at an import/export clause, barring those whose named imports/exports - * do not occur at the current position and have not otherwise been typed. - */ - function filterNamedImportOrExportCompletionItems(exportsOfModule: Symbol[], namedImportsOrExports: ReadonlyArray): Symbol[] { - const existingImportsOrExports = createUnderscoreEscapedMap(); - - for (const element of namedImportsOrExports) { - // If this is the current item we are editing right now, do not filter it out - if (isCurrentlyEditingNode(element)) { - continue; - } - - const name = element.propertyName || element.name; - existingImportsOrExports.set(name.escapedText, true); - } - - return exportsOfModule.filter(e => e.escapedName !== InternalSymbolName.Default && !existingImportsOrExports.get(e.escapedName)); - } - /** * Filters out completion suggestions for named imports or exports. *