From f6cb90a575f9a3934c635cf93871038be271dc45 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 17 Jul 2019 09:04:53 -0700 Subject: [PATCH] =?UTF-8?q?Revert=20"Proposal:=20If=20there=E2=80=99s=20a?= =?UTF-8?q?=20package.json,=20only=20auto-import=20things=20in=20it,=20mor?= =?UTF-8?q?e=20or=20less=20(#31893)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 60a1b1dc1a93ca792cf12bb0432cf7bc134c3ad1. --- src/compiler/utilities.ts | 2 +- src/harness/fourslash.ts | 2 +- src/services/codefixes/importFixes.ts | 124 +-------------- src/services/completions.ts | 150 ++++-------------- src/services/services.ts | 2 +- src/services/stringCompletions.ts | 49 ++++++ src/services/utilities.ts | 49 ------ ...rt_filteredByPackageJson_@typesImplicit.ts | 44 ----- ...Import_filteredByPackageJson_@typesOnly.ts | 44 ----- ...onsImport_filteredByPackageJson_ambient.ts | 30 ---- ...ionsImport_filteredByPackageJson_direct.ts | 46 ------ ...ionsImport_filteredByPackageJson_nested.ts | 66 -------- ...nsImport_filteredByPackageJson_reexport.ts | 58 ------- ...sImport_filteredByPackageJson_reexport2.ts | 58 ------- ...sImport_filteredByPackageJson_reexport3.ts | 48 ------ ...sImport_filteredByPackageJson_reexport4.ts | 57 ------- .../fourslash/completionsImport_ofAlias.ts | 17 +- .../importNameCodeFixNewImportNodeModules8.ts | 2 +- 18 files changed, 97 insertions(+), 751 deletions(-) delete mode 100644 tests/cases/fourslash/completionsImport_filteredByPackageJson_@typesImplicit.ts delete mode 100644 tests/cases/fourslash/completionsImport_filteredByPackageJson_@typesOnly.ts delete mode 100644 tests/cases/fourslash/completionsImport_filteredByPackageJson_ambient.ts delete mode 100644 tests/cases/fourslash/completionsImport_filteredByPackageJson_direct.ts delete mode 100644 tests/cases/fourslash/completionsImport_filteredByPackageJson_nested.ts delete mode 100644 tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport.ts delete mode 100644 tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport2.ts delete mode 100644 tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport3.ts delete mode 100644 tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport4.ts diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index fb5c69c637563..f3282d4372300 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7485,7 +7485,7 @@ namespace ts { export function getDirectoryPath(path: Path): Path; /** * Returns the path except for its basename. Semantics align with NodeJS's `path.dirname` - * except that we support URLs as well. + * except that we support URL's as well. * * ```ts * getDirectoryPath("/path/to/file.ext") === "/path/to" diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 919352e1391b7..aa764e74cdc44 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -798,7 +798,7 @@ namespace FourSlash { const name = typeof include === "string" ? include : include.name; const found = nameToEntries.get(name); if (!found) throw this.raiseError(`No completion ${name} found`); - assert(found.length === 1, `Must use 'exact' for multiple completions with same name: '${name}'`); + assert(found.length === 1); // Must use 'exact' for multiple completions with same name this.verifyCompletionEntry(ts.first(found), include); } } diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 2fcc8cf5ccbe6..8006d1a0cfd04 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -283,25 +283,13 @@ namespace ts.codefix { preferences: UserPreferences, ): ReadonlyArray { const isJs = isSourceFileJS(sourceFile); - const { allowsImporting } = createLazyPackageJsonDependencyReader(sourceFile, host); const choicesForEachExportingModule = flatMap(moduleSymbols, ({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }) => moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getCompilerOptions(), sourceFile, host, program.getSourceFiles(), preferences, program.redirectTargetsMap) .map((moduleSpecifier): FixAddNewImport | FixUseImportType => // `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types. exportedSymbolIsTypeOnly && isJs ? { kind: ImportFixKind.ImportType, moduleSpecifier, position: Debug.assertDefined(position) } : { kind: ImportFixKind.AddNew, moduleSpecifier, importKind })); - - // Sort by presence in package.json, then shortest paths first - return sort(choicesForEachExportingModule, (a, b) => { - const allowsImportingA = allowsImporting(a.moduleSpecifier); - const allowsImportingB = allowsImporting(b.moduleSpecifier); - if (allowsImportingA && !allowsImportingB) { - return -1; - } - if (allowsImportingB && !allowsImportingA) { - return 1; - } - return a.moduleSpecifier.length - b.moduleSpecifier.length; - }); + // Sort to keep the shortest paths first + return sort(choicesForEachExportingModule, (a, b) => a.moduleSpecifier.length - b.moduleSpecifier.length); } function getFixesForAddImport( @@ -392,8 +380,7 @@ namespace ts.codefix { // "default" is a keyword and not a legal identifier for the import, so we don't expect it here Debug.assert(symbolName !== InternalSymbolName.Default); - const exportInfos = getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, checker, program, preferences, host); - const fixes = arrayFrom(flatMapIterator(exportInfos.entries(), ([_, exportInfos]) => + const fixes = arrayFrom(flatMapIterator(getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, checker, program).entries(), ([_, exportInfos]) => getFixForImport(exportInfos, symbolName, symbolToken.getStart(sourceFile), program, sourceFile, host, preferences))); return { fixes, symbolName }; } @@ -406,8 +393,6 @@ namespace ts.codefix { sourceFile: SourceFile, checker: TypeChecker, program: Program, - preferences: UserPreferences, - host: LanguageServiceHost ): ReadonlyMap> { // For each original symbol, keep all re-exports of that symbol together so we can call `getCodeActionsForImport` on the whole group at once. // Maps symbol id to info for modules providing that symbol (original export + re-exports). @@ -415,7 +400,7 @@ namespace ts.codefix { function addSymbol(moduleSymbol: Symbol, exportedSymbol: Symbol, importKind: ImportKind): void { originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { moduleSymbol, importKind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exportedSymbol, checker) }); } - forEachExternalModuleToImportFrom(checker, host, preferences, program.redirectTargetsMap, sourceFile, program.getSourceFiles(), moduleSymbol => { + forEachExternalModuleToImportFrom(checker, sourceFile, program.getSourceFiles(), moduleSymbol => { cancellationToken.throwIfCancellationRequested(); const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, program.getCompilerOptions()); @@ -576,44 +561,12 @@ namespace ts.codefix { return some(declarations, decl => !!(getMeaningFromDeclaration(decl) & meaning)); } - export function forEachExternalModuleToImportFrom(checker: TypeChecker, host: LanguageServiceHost, preferences: UserPreferences, redirectTargetsMap: RedirectTargetsMap, from: SourceFile, allSourceFiles: ReadonlyArray, cb: (module: Symbol) => void) { - const { allowsImporting } = createLazyPackageJsonDependencyReader(from, host); - const compilerOptions = host.getCompilationSettings(); - const getCanonicalFileName = hostGetCanonicalFileName(host); + export function forEachExternalModuleToImportFrom(checker: TypeChecker, from: SourceFile, allSourceFiles: ReadonlyArray, cb: (module: Symbol) => void) { forEachExternalModule(checker, allSourceFiles, (module, sourceFile) => { - if (sourceFile === undefined && allowsImporting(stripQuotes(module.getName()))) { + if (sourceFile === undefined || sourceFile !== from && isImportablePath(from.fileName, sourceFile.fileName)) { cb(module); } - else if (sourceFile && sourceFile !== from && isImportablePath(from.fileName, sourceFile.fileName)) { - const moduleSpecifier = getNodeModulesPackageNameFromFileName(sourceFile.fileName); - if (!moduleSpecifier || allowsImporting(moduleSpecifier)) { - cb(module); - } - } }); - - function getNodeModulesPackageNameFromFileName(importedFileName: string): string | undefined { - const specifier = moduleSpecifiers.getModuleSpecifier( - compilerOptions, - from, - toPath(from.fileName, /*basePath*/ undefined, getCanonicalFileName), - importedFileName, - host, - allSourceFiles, - preferences, - redirectTargetsMap); - - // Paths here are not node_modules, so we don’t care about them; - // returning anything will trigger a lookup in package.json. - if (!pathIsRelative(specifier) && !isRootedDiskPath(specifier)) { - const components = getPathComponents(getPackageNameFromTypesPackageName(specifier)).slice(1); - // Scoped packages - if (startsWith(components[0], "@")) { - return `${components[0]}/${components[1]}`; - } - return components[0]; - } - } } function forEachExternalModule(checker: TypeChecker, allSourceFiles: ReadonlyArray, cb: (module: Symbol, sourceFile: SourceFile | undefined) => void) { @@ -667,69 +620,4 @@ namespace ts.codefix { // Need `|| "_"` to ensure result isn't empty. return !isStringANonContextualKeyword(res) ? res || "_" : `_${res}`; } - - function createLazyPackageJsonDependencyReader(fromFile: SourceFile, host: LanguageServiceHost) { - const packageJsonPaths = findPackageJsons(getDirectoryPath(fromFile.fileName), host); - const dependencyIterator = readPackageJsonDependencies(host, packageJsonPaths); - let seenDeps: Map | undefined; - let usesNodeCoreModules: boolean | undefined; - return { allowsImporting }; - - function containsDependency(dependency: string) { - if ((seenDeps || (seenDeps = createMap())).has(dependency)) { - return true; - } - let packageName: string | void; - while (packageName = dependencyIterator.next().value) { - seenDeps.set(packageName, true); - if (packageName === dependency) { - return true; - } - } - return false; - } - - function allowsImporting(moduleSpecifier: string): boolean { - if (!packageJsonPaths.length) { - return true; - } - - // If we’re in JavaScript, it can be difficult to tell whether the user wants to import - // from Node core modules or not. We can start by seeing if the user is actually using - // any node core modules, as opposed to simply having @types/node accidentally as a - // dependency of a dependency. - if (isSourceFileJS(fromFile) && JsTyping.nodeCoreModules.has(moduleSpecifier)) { - if (usesNodeCoreModules === undefined) { - usesNodeCoreModules = consumesNodeCoreModules(fromFile); - } - if (usesNodeCoreModules) { - return true; - } - } - - return containsDependency(moduleSpecifier) - || containsDependency(getTypesPackageName(moduleSpecifier)); - } - } - - function *readPackageJsonDependencies(host: LanguageServiceHost, packageJsonPaths: string[]) { - type PackageJson = Record | undefined>; - const dependencyKeys = ["dependencies", "devDependencies", "optionalDependencies"] as const; - for (const fileName of packageJsonPaths) { - const content = readJson(fileName, { readFile: host.readFile ? host.readFile.bind(host) : sys.readFile }) as PackageJson; - for (const key of dependencyKeys) { - const dependencies = content[key]; - if (!dependencies) { - continue; - } - for (const packageName in dependencies) { - yield packageName; - } - } - } - } - - function consumesNodeCoreModules(sourceFile: SourceFile): boolean { - return some(sourceFile.imports, ({ text }) => JsTyping.nodeCoreModules.has(text)); - } } diff --git a/src/services/completions.ts b/src/services/completions.ts index d7b40d14587c3..b5c412a788ae4 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -64,7 +64,7 @@ namespace ts.Completions { return getLabelCompletionAtPosition(contextToken.parent); } - const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, preferences, /*detailsEntryId*/ undefined, host); + const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, preferences, /*detailsEntryId*/ undefined); if (!completionData) { return undefined; } @@ -407,10 +407,10 @@ namespace ts.Completions { previousToken: Node | undefined; readonly isJsxInitializer: IsJsxInitializer; } - function getSymbolCompletionFromEntryId(program: Program, log: Log, sourceFile: SourceFile, position: number, entryId: CompletionEntryIdentifier, host: LanguageServiceHost + function getSymbolCompletionFromEntryId(program: Program, log: Log, sourceFile: SourceFile, position: number, entryId: CompletionEntryIdentifier, ): SymbolCompletion | { type: "request", request: Request } | { type: "literal", literal: string | number | PseudoBigInt } | { type: "none" } { const compilerOptions = program.getCompilerOptions(); - const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId, host); + const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId); if (!completionData) { return { type: "none" }; } @@ -472,7 +472,7 @@ namespace ts.Completions { } // Compute all the completion symbols again. - const symbolCompletion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host); + const symbolCompletion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId); switch (symbolCompletion.type) { case "request": { const { request } = symbolCompletion; @@ -557,8 +557,8 @@ namespace ts.Completions { return { sourceDisplay: [textPart(moduleSpecifier)], codeActions: [codeAction] }; } - export function getCompletionEntrySymbol(program: Program, log: Log, sourceFile: SourceFile, position: number, entryId: CompletionEntryIdentifier, host: LanguageServiceHost): Symbol | undefined { - const completion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host); + export function getCompletionEntrySymbol(program: Program, log: Log, sourceFile: SourceFile, position: number, entryId: CompletionEntryIdentifier): Symbol | undefined { + const completion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId); return completion.type === "symbol" ? completion.symbol : undefined; } @@ -657,7 +657,6 @@ namespace ts.Completions { position: number, preferences: Pick, detailsEntryId: CompletionEntryIdentifier | undefined, - host: LanguageServiceHost ): CompletionData | Request | undefined { const typeChecker = program.getTypeChecker(); @@ -1150,7 +1149,7 @@ namespace ts.Completions { } if (shouldOfferImportCompletions()) { - getSymbolsFromOtherSourceFileExports(symbols, previousToken && isIdentifier(previousToken) ? previousToken.text : "", program.getCompilerOptions().target!, host); + getSymbolsFromOtherSourceFileExports(symbols, previousToken && isIdentifier(previousToken) ? previousToken.text : "", program.getCompilerOptions().target!); } filterGlobalCompletion(symbols); } @@ -1268,64 +1267,12 @@ namespace ts.Completions { typeChecker.getExportsOfModule(sym).some(e => symbolCanBeReferencedAtTypeLocation(e, seenModules)); } - /** - * Gathers symbols that can be imported from other files, deduplicating along the way. Symbols can be “duplicates” - * if re-exported from another module, e.g. `export { foo } from "./a"`. That syntax creates a fresh symbol, but - * it’s just an alias to the first, and both have the same name, so we generally want to filter those aliases out, - * if and only if the the first can be imported (it may be excluded due to package.json filtering in - * `codefix.forEachExternalModuleToImportFrom`). - * - * Example. Imagine a chain of node_modules re-exporting one original symbol: - * - * ```js - * node_modules/x/index.js node_modules/y/index.js node_modules/z/index.js - * +-----------------------+ +--------------------------+ +--------------------------+ - * | | | | | | - * | export const foo = 0; | <--- | export { foo } from 'x'; | <--- | export { foo } from 'y'; | - * | | | | | | - * +-----------------------+ +--------------------------+ +--------------------------+ - * ``` - * - * Also imagine three buckets, which we’ll reference soon: - * - * ```md - * | | | | | | - * | **Bucket A** | | **Bucket B** | | **Bucket C** | - * | Symbols to | | Aliases to symbols | | Symbols to return | - * | definitely | | in Buckets A or C | | if nothing better | - * | return | | (don’t return these) | | comes along | - * |__________________| |______________________| |___________________| - * ``` - * - * We _probably_ want to show `foo` from 'x', but not from 'y' or 'z'. However, if 'x' is not in a package.json, it - * will not appear in a `forEachExternalModuleToImportFrom` iteration. Furthermore, the order of iterations is not - * guaranteed, as it is host-dependent. Therefore, when presented with the symbol `foo` from module 'y' alone, we - * may not be sure whether or not it should go in the list. So, we’ll take the following steps: - * - * 1. Resolve alias `foo` from 'y' to the export declaration in 'x', get the symbol there, and see if that symbol is - * already in Bucket A (symbols we already know will be returned). If it is, put `foo` from 'y' in Bucket B - * (symbols that are aliases to symbols in Bucket A). If it’s not, put it in Bucket C. - * 2. Next, imagine we see `foo` from module 'z'. Again, we resolve the alias to the nearest export, which is in 'y'. - * At this point, if that nearest export from 'y' is in _any_ of the three buckets, we know the symbol in 'z' - * should never be returned in the final list, so put it in Bucket B. - * 3. Next, imagine we see `foo` from module 'x', the original. Syntactically, it doesn’t look like a re-export, so - * we can just check Bucket C to see if we put any aliases to the original in there. If they exist, throw them out. - * Put this symbol in Bucket A. - * 4. After we’ve iterated through every symbol of every module, any symbol left in Bucket C means that step 3 didn’t - * occur for that symbol---that is, the original symbol is not in Bucket A, so we should include the alias. Move - * everything from Bucket C to Bucket A. - * - * Note: Bucket A is passed in as the parameter `symbols` and mutated. - */ - function getSymbolsFromOtherSourceFileExports(/** Bucket A */ symbols: Symbol[], tokenText: string, target: ScriptTarget, host: LanguageServiceHost): void { + function getSymbolsFromOtherSourceFileExports(symbols: Symbol[], tokenText: string, target: ScriptTarget): void { const tokenTextLowerCase = tokenText.toLowerCase(); + const seenResolvedModules = createMap(); - /** Bucket B */ - const aliasesToAlreadyIncludedSymbols = createMap(); - /** Bucket C */ - const aliasesToReturnIfOriginalsAreMissing = createMap<{ alias: Symbol, moduleSymbol: Symbol }>(); - codefix.forEachExternalModuleToImportFrom(typeChecker, host, preferences, program.redirectTargetsMap, sourceFile, program.getSourceFiles(), moduleSymbol => { + codefix.forEachExternalModuleToImportFrom(typeChecker, sourceFile, program.getSourceFiles(), moduleSymbol => { // Perf -- ignore other modules if this is a request for details if (detailsEntryId && detailsEntryId.source && stripQuotes(moduleSymbol.name) !== detailsEntryId.source) { return; @@ -1346,58 +1293,32 @@ namespace ts.Completions { symbolToOriginInfoMap[getSymbolId(resolvedModuleSymbol)] = { kind: SymbolOriginInfoKind.Export, moduleSymbol, isDefaultExport: false }; } - for (const symbol of typeChecker.getExportsOfModule(moduleSymbol)) { - // If this is `export { _break as break };` (a keyword) -- skip this and prefer the keyword completion. - if (some(symbol.declarations, d => isExportSpecifier(d) && !!d.propertyName && isIdentifierANonContextualKeyword(d.name))) { + for (let symbol of typeChecker.getExportsOfModule(moduleSymbol)) { + // Don't add a completion for a re-export, only for the original. + // The actual import fix might end up coming from a re-export -- we don't compute that until getting completion details. + // This is just to avoid adding duplicate completion entries. + // + // If `symbol.parent !== ...`, this is an `export * from "foo"` re-export. Those don't create new symbols. + if (typeChecker.getMergedSymbol(symbol.parent!) !== resolvedModuleSymbol + || some(symbol.declarations, d => + // If `!!d.name.originalKeywordKind`, this is `export { _break as break };` -- skip this and prefer the keyword completion. + // If `!!d.parent.parent.moduleSpecifier`, this is `export { foo } from "foo"` re-export, which creates a new symbol (thus isn't caught by the first check). + isExportSpecifier(d) && (d.propertyName ? isIdentifierANonContextualKeyword(d.name) : !!d.parent.parent.moduleSpecifier))) { continue; } - // If `symbol.parent !== moduleSymbol`, this is an `export * from "foo"` re-export. Those don't create new symbols. - const isExportStarFromReExport = typeChecker.getMergedSymbol(symbol.parent!) !== resolvedModuleSymbol; - // If `!!d.parent.parent.moduleSpecifier`, this is `export { foo } from "foo"` re-export, which creates a new symbol (thus isn't caught by the first check). - if (isExportStarFromReExport || some(symbol.declarations, d => isExportSpecifier(d) && !d.propertyName && !!d.parent.parent.moduleSpecifier)) { - // Walk the export chain back one module (step 1 or 2 in diagrammed example). - // Or, in the case of `export * from "foo"`, `symbol` already points to the original export, so just use that. - const nearestExportSymbolId = getSymbolId(isExportStarFromReExport ? symbol : Debug.assertDefined(getNearestExportSymbol(symbol))); - const symbolHasBeenSeen = !!symbolToOriginInfoMap[nearestExportSymbolId] || aliasesToAlreadyIncludedSymbols.has(nearestExportSymbolId.toString()); - if (!symbolHasBeenSeen) { - aliasesToReturnIfOriginalsAreMissing.set(nearestExportSymbolId.toString(), { alias: symbol, moduleSymbol }); - aliasesToAlreadyIncludedSymbols.set(getSymbolId(symbol).toString(), true); - } - else { - // Perf - we know this symbol is an alias to one that’s already covered in `symbols`, so store it here - // in case another symbol re-exports this one; that way we can short-circuit as soon as we see this symbol id. - addToSeen(aliasesToAlreadyIncludedSymbols, getSymbolId(symbol)); - } - } - else { - // This is not a re-export, so see if we have any aliases pending and remove them (step 3 in diagrammed example) - aliasesToReturnIfOriginalsAreMissing.delete(getSymbolId(symbol).toString()); - pushSymbol(symbol, moduleSymbol); - } - } - }); - // By this point, any potential duplicates that were actually duplicates have been - // removed, so the rest need to be added. (Step 4 in diagrammed example) - aliasesToReturnIfOriginalsAreMissing.forEach(({ alias, moduleSymbol }) => pushSymbol(alias, moduleSymbol)); + const isDefaultExport = symbol.escapedName === InternalSymbolName.Default; + if (isDefaultExport) { + symbol = getLocalSymbolForExportDefault(symbol) || symbol; + } - function pushSymbol(symbol: Symbol, moduleSymbol: Symbol) { - const isDefaultExport = symbol.escapedName === InternalSymbolName.Default; - if (isDefaultExport) { - symbol = getLocalSymbolForExportDefault(symbol) || symbol; - } - const origin: SymbolOriginInfoExport = { kind: SymbolOriginInfoKind.Export, moduleSymbol, isDefaultExport }; - if (detailsEntryId || stringContainsCharactersInOrder(getSymbolName(symbol, origin, target).toLowerCase(), tokenTextLowerCase)) { - symbols.push(symbol); - symbolToSortTextMap[getSymbolId(symbol)] = SortText.AutoImportSuggestions; - symbolToOriginInfoMap[getSymbolId(symbol)] = origin; + const origin: SymbolOriginInfoExport = { kind: SymbolOriginInfoKind.Export, moduleSymbol, isDefaultExport }; + if (detailsEntryId || stringContainsCharactersInOrder(getSymbolName(symbol, origin, target).toLowerCase(), tokenTextLowerCase)) { + symbols.push(symbol); + symbolToSortTextMap[getSymbolId(symbol)] = SortText.AutoImportSuggestions; + symbolToOriginInfoMap[getSymbolId(symbol)] = origin; + } } - } - } - - function getNearestExportSymbol(fromSymbol: Symbol) { - return findAlias(typeChecker, fromSymbol, alias => { - return some(alias.declarations, d => isExportSpecifier(d) || !!d.localSymbol); }); } @@ -2322,13 +2243,4 @@ namespace ts.Completions { function binaryExpressionMayBeOpenTag({ left }: BinaryExpression): boolean { return nodeIsMissing(left); } - - function findAlias(typeChecker: TypeChecker, symbol: Symbol, predicate: (symbol: Symbol) => boolean): Symbol | undefined { - let currentAlias: Symbol | undefined = symbol; - while (currentAlias.flags & SymbolFlags.Alias && (currentAlias = typeChecker.getImmediateAliasedSymbol(currentAlias))) { - if (predicate(currentAlias)) { - return currentAlias; - } - } - } } diff --git a/src/services/services.ts b/src/services/services.ts index 2f000bc0f2e95..fab6f88b779e0 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1453,7 +1453,7 @@ namespace ts { function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string): Symbol | undefined { synchronizeHostData(); - return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }, host); + return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }); } function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined { diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 58195b5cb3c50..b287ebdb406a9 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -627,6 +627,30 @@ namespace ts.Completions.StringCompletions { } } + function findPackageJsons(directory: string, host: LanguageServiceHost): string[] { + const paths: string[] = []; + forEachAncestorDirectory(directory, ancestor => { + const currentConfigPath = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json"); + if (!currentConfigPath) { + return true; // break out + } + paths.push(currentConfigPath); + }); + return paths; + } + + function findPackageJson(directory: string, host: LanguageServiceHost): string | undefined { + let packageJson: string | undefined; + forEachAncestorDirectory(directory, ancestor => { + if (ancestor === "node_modules") return true; + packageJson = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json"); + if (packageJson) { + return true; // break out + } + }); + return packageJson; + } + function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string): ReadonlyArray { if (!host.readFile || !host.fileExists) return emptyArray; @@ -682,6 +706,31 @@ namespace ts.Completions.StringCompletions { const nodeModulesDependencyKeys: ReadonlyArray = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]; + function tryGetDirectories(host: LanguageServiceHost, directoryName: string): string[] { + return tryIOAndConsumeErrors(host, host.getDirectories, directoryName) || []; + } + + function tryReadDirectory(host: LanguageServiceHost, path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray): ReadonlyArray { + return tryIOAndConsumeErrors(host, host.readDirectory, path, extensions, exclude, include) || emptyArray; + } + + function tryFileExists(host: LanguageServiceHost, path: string): boolean { + return tryIOAndConsumeErrors(host, host.fileExists, path); + } + + function tryDirectoryExists(host: LanguageServiceHost, path: string): boolean { + return tryAndIgnoreErrors(() => directoryProbablyExists(path, host)) || false; + } + + function tryIOAndConsumeErrors(host: LanguageServiceHost, toApply: ((...a: any[]) => T) | undefined, ...args: any[]) { + return tryAndIgnoreErrors(() => toApply && toApply.apply(host, args)); + } + + function tryAndIgnoreErrors(cb: () => T): T | undefined { + try { return cb(); } + catch { return undefined; } + } + function containsSlash(fragment: string) { return stringContains(fragment, directorySeparator); } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 98406c6879cae..852d22106a28f 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -2022,53 +2022,4 @@ namespace ts { // If even 2/5 places have a semicolon, the user probably wants semicolons return withSemicolon / withoutSemicolon > 1 / nStatementsToObserve; } - - export function tryGetDirectories(host: LanguageServiceHost, directoryName: string): string[] { - return tryIOAndConsumeErrors(host, host.getDirectories, directoryName) || []; - } - - export function tryReadDirectory(host: LanguageServiceHost, path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray): ReadonlyArray { - return tryIOAndConsumeErrors(host, host.readDirectory, path, extensions, exclude, include) || emptyArray; - } - - export function tryFileExists(host: LanguageServiceHost, path: string): boolean { - return tryIOAndConsumeErrors(host, host.fileExists, path); - } - - export function tryDirectoryExists(host: LanguageServiceHost, path: string): boolean { - return tryAndIgnoreErrors(() => directoryProbablyExists(path, host)) || false; - } - - export function tryAndIgnoreErrors(cb: () => T): T | undefined { - try { return cb(); } - catch { return undefined; } - } - - export function tryIOAndConsumeErrors(host: LanguageServiceHost, toApply: ((...a: any[]) => T) | undefined, ...args: any[]) { - return tryAndIgnoreErrors(() => toApply && toApply.apply(host, args)); - } - - export function findPackageJsons(directory: string, host: LanguageServiceHost): string[] { - const paths: string[] = []; - forEachAncestorDirectory(directory, ancestor => { - const currentConfigPath = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json"); - if (!currentConfigPath) { - return true; // break out - } - paths.push(currentConfigPath); - }); - return paths; - } - - export function findPackageJson(directory: string, host: LanguageServiceHost): string | undefined { - let packageJson: string | undefined; - forEachAncestorDirectory(directory, ancestor => { - if (ancestor === "node_modules") return true; - packageJson = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json"); - if (packageJson) { - return true; // break out - } - }); - return packageJson; - } } diff --git a/tests/cases/fourslash/completionsImport_filteredByPackageJson_@typesImplicit.ts b/tests/cases/fourslash/completionsImport_filteredByPackageJson_@typesImplicit.ts deleted file mode 100644 index 539a9cc8ff6c2..0000000000000 --- a/tests/cases/fourslash/completionsImport_filteredByPackageJson_@typesImplicit.ts +++ /dev/null @@ -1,44 +0,0 @@ -/// - -//@noEmit: true - -//@Filename: /package.json -////{ -//// "dependencies": { -//// "react": "*" -//// } -////} - -//@Filename: /node_modules/@types/react/index.d.ts -////export declare var React: any; - -//@Filename: /node_modules/@types/react/package.json -////{ -//// "name": "@types/react" -////} - -//@Filename: /node_modules/@types/fake-react/index.d.ts -////export declare var ReactFake: any; - -//@Filename: /node_modules/@types/fake-react/package.json -////{ -//// "name": "@types/fake-react" -////} - -//@Filename: /src/index.ts -////const x = Re/**/ - -verify.completions({ - marker: test.marker(""), - isNewIdentifierLocation: true, - includes: { - name: "React", - hasAction: true, - source: "/node_modules/@types/react/index", - sortText: completion.SortText.AutoImportSuggestions - }, - excludes: "ReactFake", - preferences: { - includeCompletionsForModuleExports: true - } -}); diff --git a/tests/cases/fourslash/completionsImport_filteredByPackageJson_@typesOnly.ts b/tests/cases/fourslash/completionsImport_filteredByPackageJson_@typesOnly.ts deleted file mode 100644 index b0d2c01e3dbe9..0000000000000 --- a/tests/cases/fourslash/completionsImport_filteredByPackageJson_@typesOnly.ts +++ /dev/null @@ -1,44 +0,0 @@ -/// - -//@noEmit: true - -//@Filename: /package.json -////{ -//// "devDependencies": { -//// "@types/react": "*" -//// } -////} - -//@Filename: /node_modules/@types/react/index.d.ts -////export declare var React: any; - -//@Filename: /node_modules/@types/react/package.json -////{ -//// "name": "@types/react" -////} - -//@Filename: /node_modules/@types/fake-react/index.d.ts -////export declare var ReactFake: any; - -//@Filename: /node_modules/@types/fake-react/package.json -////{ -//// "name": "@types/fake-react" -////} - -//@Filename: /src/index.ts -////const x = Re/**/ - -verify.completions({ - marker: test.marker(""), - isNewIdentifierLocation: true, - includes: { - name: "React", - hasAction: true, - source: "/node_modules/@types/react/index", - sortText: completion.SortText.AutoImportSuggestions - }, - excludes: "ReactFake", - preferences: { - includeCompletionsForModuleExports: true - } -}); diff --git a/tests/cases/fourslash/completionsImport_filteredByPackageJson_ambient.ts b/tests/cases/fourslash/completionsImport_filteredByPackageJson_ambient.ts deleted file mode 100644 index 3dcb9eb66904f..0000000000000 --- a/tests/cases/fourslash/completionsImport_filteredByPackageJson_ambient.ts +++ /dev/null @@ -1,30 +0,0 @@ -/// - -//@noEmit: true - -//@Filename: /package.json -////{ -//// "dependencies": { -//// } -////} - -//@Filename: /node_modules/@types/node/timers.d.ts -////declare module "timers" { -//// function setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timeout; -////} - -//@Filename: /node_modules/@types/node/package.json -////{ -//// "name": "@types/node", -////} - -//@Filename: /src/index.ts -////setTimeo/**/ - -verify.completions({ - marker: test.marker(""), - exact: completion.globals, - preferences: { - includeCompletionsForModuleExports: true - } -}); diff --git a/tests/cases/fourslash/completionsImport_filteredByPackageJson_direct.ts b/tests/cases/fourslash/completionsImport_filteredByPackageJson_direct.ts deleted file mode 100644 index aa7845daed3d1..0000000000000 --- a/tests/cases/fourslash/completionsImport_filteredByPackageJson_direct.ts +++ /dev/null @@ -1,46 +0,0 @@ -/// - -//@noEmit: true - -//@Filename: /package.json -////{ -//// "dependencies": { -//// "react": "*" -//// } -////} - -//@Filename: /node_modules/react/index.d.ts -////export declare var React: any; - -//@Filename: /node_modules/react/package.json -////{ -//// "name": "react", -//// "types": "./index.d.ts" -////} - -//@Filename: /node_modules/fake-react/index.d.ts -////export declare var ReactFake: any; - -//@Filename: /node_modules/fake-react/package.json -////{ -//// "name": "fake-react", -//// "types": "./index.d.ts" -////} - -//@Filename: /src/index.ts -////const x = Re/**/ - -verify.completions({ - marker: test.marker(""), - isNewIdentifierLocation: true, - includes: { - name: "React", - hasAction: true, - source: "/node_modules/react/index", - sortText: completion.SortText.AutoImportSuggestions - }, - excludes: "ReactFake", - preferences: { - includeCompletionsForModuleExports: true - } -}); diff --git a/tests/cases/fourslash/completionsImport_filteredByPackageJson_nested.ts b/tests/cases/fourslash/completionsImport_filteredByPackageJson_nested.ts deleted file mode 100644 index e940c43e32c6d..0000000000000 --- a/tests/cases/fourslash/completionsImport_filteredByPackageJson_nested.ts +++ /dev/null @@ -1,66 +0,0 @@ -/// - -//@noEmit: true - -//@Filename: /package.json -////{ -//// "dependencies": { -//// "react": "*" -//// } -////} - -//@Filename: /node_modules/react/index.d.ts -////export declare var React: any; - -//@Filename: /node_modules/react/package.json -////{ -//// "name": "react", -//// "types": "./index.d.ts" -////} - -//@Filename: /dir/package.json -////{ -//// "dependencies": { -//// "redux": "*" -//// } -////} - -//@Filename: /dir/node_modules/redux/package.json -////{ -//// "name": "redux", -//// "types": "./index.d.ts" -////} - -//@Filename: /dir/node_modules/redux/index.d.ts -////export declare var Redux: any; - -//@Filename: /dir/index.ts -////const x = Re/**/ - -verify.completions({ - marker: test.marker(""), - isNewIdentifierLocation: true, - includes: { - name: "React", - hasAction: true, - source: "/node_modules/react/index", - sortText: completion.SortText.AutoImportSuggestions - }, - preferences: { - includeCompletionsForModuleExports: true - } -}); - -verify.completions({ - marker: test.marker(""), - isNewIdentifierLocation: true, - includes: { - name: "Redux", - hasAction: true, - source: "/dir/node_modules/redux/index", - sortText: completion.SortText.AutoImportSuggestions - }, - preferences: { - includeCompletionsForModuleExports: true - } -}); diff --git a/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport.ts b/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport.ts deleted file mode 100644 index 8e17a3c3a449b..0000000000000 --- a/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport.ts +++ /dev/null @@ -1,58 +0,0 @@ -/// - -//@noEmit: true - -//@Filename: /package.json -////{ -//// "dependencies": { -//// "@emotion/core": "*" -//// } -////} - -//@Filename: /node_modules/@emotion/css/index.d.ts -////export declare const css: any; -////const css2: any; -////export { css2 }; - -//@Filename: /node_modules/@emotion/css/package.json -////{ -//// "name": "@emotion/css", -//// "types": "./index.d.ts" -////} - -//@Filename: /node_modules/@emotion/core/index.d.ts -////import { css2 } from "@emotion/css"; -////export { css } from "@emotion/css"; -////export { css2 }; - -//@Filename: /node_modules/@emotion/core/package.json -////{ -//// "name": "@emotion/core", -//// "types": "./index.d.ts" -////} - -//@Filename: /src/index.ts -////cs/**/ - -verify.completions({ - marker: test.marker(""), - includes: [ - completion.undefinedVarEntry, - { - name: "css", - source: "/node_modules/@emotion/core/index", - hasAction: true, - sortText: completion.SortText.AutoImportSuggestions - }, - { - name: "css2", - source: "/node_modules/@emotion/core/index", - hasAction: true, - sortText: completion.SortText.AutoImportSuggestions - }, - ...completion.statementKeywordsWithTypes - ], - preferences: { - includeCompletionsForModuleExports: true - } -}); diff --git a/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport2.ts b/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport2.ts deleted file mode 100644 index eb946ce17b42c..0000000000000 --- a/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport2.ts +++ /dev/null @@ -1,58 +0,0 @@ -/// - -//@noEmit: true - -//@Filename: /package.json -////{ -//// "dependencies": { -//// "b_": "*", -//// "_c": "*" -//// } -////} - -//@Filename: /node_modules/a/index.d.ts -////export const foo = 0; - -//@Filename: /node_modules/a/package.json -////{ -//// "name": "a", -//// "types": "./index.d.ts" -////} - -//@Filename: /node_modules/b_/index.d.ts -////export { foo } from "a"; - -//@Filename: /node_modules/b_/package.json -////{ -//// "name": "b_", -//// "types": "./index.d.ts" -////} - -//@Filename: /node_modules/_c/index.d.ts -////export { foo } from "b_"; - -//@Filename: /node_modules/_c/package.json -////{ -//// "name": "_c", -//// "types": "./index.d.ts" -////} - -//@Filename: /src/index.ts -////fo/**/ - -verify.completions({ - marker: test.marker(""), - includes: [ - completion.undefinedVarEntry, - { - name: "foo", - source: "/node_modules/b_/index", - hasAction: true, - sortText: completion.SortText.AutoImportSuggestions - }, - ...completion.statementKeywordsWithTypes - ], - preferences: { - includeCompletionsForModuleExports: true - } -}); diff --git a/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport3.ts b/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport3.ts deleted file mode 100644 index 8533461e0b805..0000000000000 --- a/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport3.ts +++ /dev/null @@ -1,48 +0,0 @@ -/// - -//@noEmit: true - -//@Filename: /package.json -////{ -//// "dependencies": { -//// "b": "*", -//// } -////} - -//@Filename: /node_modules/a/index.d.ts -////export const foo = 0; - -//@Filename: /node_modules/a/package.json -////{ -//// "name": "a", -//// "types": "./index.d.ts" -////} - -//@Filename: /node_modules/b/index.d.ts -////export * from "a"; - -//@Filename: /node_modules/b/package.json -////{ -//// "name": "b", -//// "types": "./index.d.ts" -////} - -//@Filename: /src/index.ts -////fo/**/ - -verify.completions({ - marker: test.marker(""), - includes: [ - completion.undefinedVarEntry, - { - name: "foo", - source: "/node_modules/b/index", - hasAction: true, - sortText: completion.SortText.AutoImportSuggestions - }, - ...completion.statementKeywordsWithTypes - ], - preferences: { - includeCompletionsForModuleExports: true - } -}); diff --git a/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport4.ts b/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport4.ts deleted file mode 100644 index 83ac6526b2586..0000000000000 --- a/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport4.ts +++ /dev/null @@ -1,57 +0,0 @@ -/// - -//@noEmit: true - -//@Filename: /package.json -////{ -//// "dependencies": { -//// "c": "*", -//// } -////} - -//@Filename: /node_modules/a/index.d.ts -////export const foo = 0; - -//@Filename: /node_modules/a/package.json -////{ -//// "name": "a", -//// "types": "./index.d.ts" -////} - -//@Filename: /node_modules/b/index.d.ts -////export * from "a"; - -//@Filename: /node_modules/b/package.json -////{ -//// "name": "b", -//// "types": "./index.d.ts" -////} - -//@Filename: /node_modules/c/index.d.ts -////export * from "a"; - -//@Filename: /node_modules/c/package.json -////{ -//// "name": "c", -//// "types": "./index.d.ts" -////} - -//@Filename: /src/index.ts -////fo/**/ - -verify.completions({ - marker: test.marker(""), - includes: [ - completion.undefinedVarEntry, - { - name: "foo", - source: "/node_modules/c/index", - hasAction: true, - sortText: completion.SortText.AutoImportSuggestions - }, - ...completion.statementKeywordsWithTypes - ], - preferences: { - includeCompletionsForModuleExports: true - } -}); diff --git a/tests/cases/fourslash/completionsImport_ofAlias.ts b/tests/cases/fourslash/completionsImport_ofAlias.ts index 1319391eb0bf1..9a9cb4a2b18c1 100644 --- a/tests/cases/fourslash/completionsImport_ofAlias.ts +++ b/tests/cases/fourslash/completionsImport_ofAlias.ts @@ -16,9 +16,6 @@ // @Filename: /a_reexport_2.ts ////export * from "./a"; -// @Filename: /a_reexport_3.ts -////export { foo } from "./a_reexport"; - // @Filename: /b.ts ////fo/**/ @@ -27,13 +24,13 @@ verify.completions({ includes: [ completion.undefinedVarEntry, { - name: "foo", - source: "/a", - sourceDisplay: "./a", - text: "(alias) const foo: 0\nexport foo", - kind: "alias", - hasAction: true, - sortText: completion.SortText.AutoImportSuggestions + name: "foo", + source: "/a", + sourceDisplay: "./a", + text: "(alias) const foo: 0\nexport foo", + kind: "alias", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions }, ...completion.statementKeywordsWithTypes, ], diff --git a/tests/cases/fourslash/importNameCodeFixNewImportNodeModules8.ts b/tests/cases/fourslash/importNameCodeFixNewImportNodeModules8.ts index acfddd587f7ae..f048f0d30d253 100644 --- a/tests/cases/fourslash/importNameCodeFixNewImportNodeModules8.ts +++ b/tests/cases/fourslash/importNameCodeFixNewImportNodeModules8.ts @@ -3,7 +3,7 @@ //// [|f1/*0*/('');|] // @Filename: package.json -//// { "dependencies": { "@scope/package-name": "latest" } } +//// { "dependencies": { "package-name": "latest" } } // @Filename: node_modules/@scope/package-name/bin/lib/index.d.ts //// export function f1(text: string): string;