diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index a36c21a414018..6e21b0678a9c1 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -107,6 +107,7 @@ import { removeTrailingDirectorySeparator, replaceFirstStar, ResolutionMode, + ResolvedModuleSpecifierInfo, resolveModuleName, resolvePath, ScriptKind, @@ -287,14 +288,15 @@ export function tryGetModuleSpecifiersFromCache( host: ModuleSpecifierResolutionHost, userPreferences: UserPreferences, options: ModuleSpecifierOptions = {}, -): readonly string[] | undefined { - return tryGetModuleSpecifiersFromCacheWorker( +): ModuleSpecifierResult | undefined { + const result = tryGetModuleSpecifiersFromCacheWorker( moduleSymbol, importingSourceFile, host, userPreferences, options, - )[0]; + ); + return result[1] && { kind: result[0], moduleSpecifiers: result[1], computedWithoutCache: false }; } function tryGetModuleSpecifiersFromCacheWorker( @@ -303,7 +305,7 @@ function tryGetModuleSpecifiersFromCacheWorker( host: ModuleSpecifierResolutionHost, userPreferences: UserPreferences, options: ModuleSpecifierOptions = {}, -): readonly [specifiers?: readonly string[], moduleFile?: SourceFile, modulePaths?: readonly ModulePath[], cache?: ModuleSpecifierCache] { +): readonly [kind?: ModuleSpecifierResult["kind"], specifiers?: readonly string[], moduleFile?: SourceFile, modulePaths?: readonly ModulePath[], cache?: ModuleSpecifierCache] { const moduleSourceFile = getSourceFileOfModule(moduleSymbol); if (!moduleSourceFile) { return emptyArray as []; @@ -311,7 +313,7 @@ function tryGetModuleSpecifiersFromCacheWorker( const cache = host.getModuleSpecifierCache?.(); const cached = cache?.get(importingSourceFile.path, moduleSourceFile.path, userPreferences, options); - return [cached?.moduleSpecifiers, moduleSourceFile, cached?.modulePaths, cache]; + return [cached?.kind, cached?.moduleSpecifiers, moduleSourceFile, cached?.modulePaths, cache]; } /** @@ -340,6 +342,13 @@ export function getModuleSpecifiers( ).moduleSpecifiers; } +/** @internal */ +export interface ModuleSpecifierResult { + kind: ResolvedModuleSpecifierInfo["kind"]; + moduleSpecifiers: readonly string[]; + computedWithoutCache: boolean; +} + /** @internal */ export function getModuleSpecifiersWithCacheInfo( moduleSymbol: Symbol, @@ -350,21 +359,21 @@ export function getModuleSpecifiersWithCacheInfo( userPreferences: UserPreferences, options: ModuleSpecifierOptions = {}, forAutoImport: boolean, -): { moduleSpecifiers: readonly string[]; computedWithoutCache: boolean; } { +): ModuleSpecifierResult { let computedWithoutCache = false; const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol, checker); - if (ambient) return { moduleSpecifiers: [ambient], computedWithoutCache }; + if (ambient) return { kind: "ambient", moduleSpecifiers: [ambient], computedWithoutCache }; // eslint-disable-next-line prefer-const - let [specifiers, moduleSourceFile, modulePaths, cache] = tryGetModuleSpecifiersFromCacheWorker( + let [kind, specifiers, moduleSourceFile, modulePaths, cache] = tryGetModuleSpecifiersFromCacheWorker( moduleSymbol, importingSourceFile, host, userPreferences, options, ); - if (specifiers) return { moduleSpecifiers: specifiers, computedWithoutCache }; - if (!moduleSourceFile) return { moduleSpecifiers: emptyArray, computedWithoutCache }; + if (specifiers) return { kind, moduleSpecifiers: specifiers, computedWithoutCache }; + if (!moduleSourceFile) return { kind: undefined, moduleSpecifiers: emptyArray, computedWithoutCache }; computedWithoutCache = true; modulePaths ||= getAllModulePathsWorker(getInfo(importingSourceFile.fileName, host), moduleSourceFile.originalFileName, host, compilerOptions, options); @@ -377,8 +386,8 @@ export function getModuleSpecifiersWithCacheInfo( options, forAutoImport, ); - cache?.set(importingSourceFile.path, moduleSourceFile.path, userPreferences, options, modulePaths, result); - return { moduleSpecifiers: result, computedWithoutCache }; + cache?.set(importingSourceFile.path, moduleSourceFile.path, userPreferences, options, result.kind, modulePaths, result.moduleSpecifiers); + return result; } /** @internal */ @@ -409,7 +418,7 @@ function computeModuleSpecifiers( userPreferences: UserPreferences, options: ModuleSpecifierOptions = {}, forAutoImport: boolean, -): readonly string[] { +): ModuleSpecifierResult { const info = getInfo(importingSourceFile.fileName, host); const preferences = getModuleSpecifierPreferences(userPreferences, host, compilerOptions, importingSourceFile); const existingSpecifier = isFullSourceFile(importingSourceFile) && forEach(modulePaths, modulePath => @@ -432,8 +441,7 @@ function computeModuleSpecifiers( }, )); if (existingSpecifier) { - const moduleSpecifiers = [existingSpecifier]; - return moduleSpecifiers; + return { kind: undefined, moduleSpecifiers: [existingSpecifier], computedWithoutCache: true }; } const importedFileIsInNodeModules = some(modulePaths, p => p.isInNodeModules); @@ -455,7 +463,7 @@ function computeModuleSpecifiers( if (specifier && modulePath.isRedirect) { // If we got a specifier for a redirect, it was a bare package specifier (e.g. "@foo/bar", // not "@foo/bar/path/to/file"). No other specifier will be this good, so stop looking. - return nodeModulesSpecifiers!; + return { kind: "node_modules", moduleSpecifiers: nodeModulesSpecifiers!, computedWithoutCache: true }; } if (!specifier) { @@ -501,10 +509,10 @@ function computeModuleSpecifiers( } } - return pathsSpecifiers?.length ? pathsSpecifiers : - redirectPathsSpecifiers?.length ? redirectPathsSpecifiers : - nodeModulesSpecifiers?.length ? nodeModulesSpecifiers : - Debug.checkDefined(relativeSpecifiers); + return pathsSpecifiers?.length ? { kind: "paths", moduleSpecifiers: pathsSpecifiers, computedWithoutCache: true } : + redirectPathsSpecifiers?.length ? { kind: "redirect", moduleSpecifiers: redirectPathsSpecifiers, computedWithoutCache: true } : + nodeModulesSpecifiers?.length ? { kind: "node_modules", moduleSpecifiers: nodeModulesSpecifiers, computedWithoutCache: true } : + { kind: "relative", moduleSpecifiers: Debug.checkDefined(relativeSpecifiers), computedWithoutCache: true }; } interface Info { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index aae7c5494a17d..c074a85e4ed8a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -9774,6 +9774,7 @@ export interface ModulePath { /** @internal */ export interface ResolvedModuleSpecifierInfo { + kind: "node_modules" | "paths" | "redirect" | "relative" | "ambient" | undefined; modulePaths: readonly ModulePath[] | undefined; moduleSpecifiers: readonly string[] | undefined; isBlockedByPackageJsonDependencies: boolean | undefined; @@ -9787,7 +9788,7 @@ export interface ModuleSpecifierOptions { /** @internal */ export interface ModuleSpecifierCache { get(fromFileName: Path, toFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions): Readonly | undefined; - set(fromFileName: Path, toFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions, modulePaths: readonly ModulePath[], moduleSpecifiers: readonly string[]): void; + set(fromFileName: Path, toFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions, kind: ResolvedModuleSpecifierInfo["kind"], modulePaths: readonly ModulePath[], moduleSpecifiers: readonly string[]): void; setBlockedByPackageJsonDependencies(fromFileName: Path, toFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions, isBlockedByPackageJsonDependencies: boolean): void; setModulePaths(fromFileName: Path, toFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions, modulePaths: readonly ModulePath[]): void; clear(): void; diff --git a/src/server/moduleSpecifierCache.ts b/src/server/moduleSpecifierCache.ts index b47add6dfe888..73e1168afd2f6 100644 --- a/src/server/moduleSpecifierCache.ts +++ b/src/server/moduleSpecifierCache.ts @@ -27,8 +27,8 @@ export function createModuleSpecifierCache(host: ModuleSpecifierResolutionCacheH if (!cache || currentKey !== key(fromFileName, preferences, options)) return undefined; return cache.get(toFileName); }, - set(fromFileName, toFileName, preferences, options, modulePaths, moduleSpecifiers) { - ensureCache(fromFileName, preferences, options).set(toFileName, createInfo(modulePaths, moduleSpecifiers, /*isBlockedByPackageJsonDependencies*/ false)); + set(fromFileName, toFileName, preferences, options, kind, modulePaths, moduleSpecifiers) { + ensureCache(fromFileName, preferences, options).set(toFileName, createInfo(kind, modulePaths, moduleSpecifiers, /*isBlockedByPackageJsonDependencies*/ false)); // If any module specifiers were generated based off paths in node_modules, // a package.json file in that package was read and is an input to the cached. @@ -58,7 +58,7 @@ export function createModuleSpecifierCache(host: ModuleSpecifierResolutionCacheH info.modulePaths = modulePaths; } else { - cache.set(toFileName, createInfo(modulePaths, /*moduleSpecifiers*/ undefined, /*isBlockedByPackageJsonDependencies*/ undefined)); + cache.set(toFileName, createInfo(/*kind*/ undefined, modulePaths, /*moduleSpecifiers*/ undefined, /*isBlockedByPackageJsonDependencies*/ undefined)); } }, setBlockedByPackageJsonDependencies(fromFileName, toFileName, preferences, options, isBlockedByPackageJsonDependencies) { @@ -68,7 +68,7 @@ export function createModuleSpecifierCache(host: ModuleSpecifierResolutionCacheH info.isBlockedByPackageJsonDependencies = isBlockedByPackageJsonDependencies; } else { - cache.set(toFileName, createInfo(/*modulePaths*/ undefined, /*moduleSpecifiers*/ undefined, isBlockedByPackageJsonDependencies)); + cache.set(toFileName, createInfo(/*kind*/ undefined, /*modulePaths*/ undefined, /*moduleSpecifiers*/ undefined, isBlockedByPackageJsonDependencies)); } }, clear() { @@ -100,10 +100,11 @@ export function createModuleSpecifierCache(host: ModuleSpecifierResolutionCacheH } function createInfo( + kind: ResolvedModuleSpecifierInfo["kind"] | undefined, modulePaths: readonly ModulePath[] | undefined, moduleSpecifiers: readonly string[] | undefined, isBlockedByPackageJsonDependencies: boolean | undefined, ): ResolvedModuleSpecifierInfo { - return { modulePaths, moduleSpecifiers, isBlockedByPackageJsonDependencies }; + return { kind, modulePaths, moduleSpecifiers, isBlockedByPackageJsonDependencies }; } } diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 1deadec75351b..c584de5b475af 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -340,6 +340,7 @@ function createImportAdderWorker(sourceFile: SourceFile | FutureSourceFile, prog ); const fix: FixAddNewImport = { kind: ImportFixKind.AddNew, + moduleSpecifierKind: "relative", moduleSpecifier, importKind, addAsTypeOnly, @@ -713,7 +714,7 @@ export function createImportSpecifierResolver(importingFile: SourceFile, program importMap, fromCacheOnly, ); - const result = getBestFix(fixes, importingFile, program, packageJsonImportFilter, host); + const result = getBestFix(fixes, importingFile, program, packageJsonImportFilter, host, preferences); return result && { ...result, computedWithoutCacheCount }; } } @@ -742,6 +743,7 @@ type ImportFixWithModuleSpecifier = FixUseNamespaceImport | FixAddJsdocTypeImpor interface ImportFixBase { readonly isReExport?: boolean; readonly exportInfo?: SymbolExportInfo | FutureSymbolExportInfo; + readonly moduleSpecifierKind: moduleSpecifiers.ModuleSpecifierResult["kind"]; readonly moduleSpecifier: string; } interface Qualification { @@ -850,7 +852,7 @@ export function getPromoteTypeOnlyCompletionAction(sourceFile: SourceFile, symbo function getImportFixForSymbol(sourceFile: SourceFile | FutureSourceFile, exportInfos: readonly SymbolExportInfo[], program: Program, position: number | undefined, isValidTypeOnlyUseSite: boolean, useRequire: boolean, host: LanguageServiceHost, preferences: UserPreferences) { const packageJsonImportFilter = createPackageJsonImportFilter(sourceFile, preferences, host); - return getBestFix(getImportFixes(exportInfos, position, isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences).fixes, sourceFile, program, packageJsonImportFilter, host); + return getBestFix(getImportFixes(exportInfos, position, isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences).fixes, sourceFile, program, packageJsonImportFilter, host, preferences); } function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction { @@ -947,7 +949,7 @@ function tryUseExistingNamespaceImport(existingImports: readonly FixAddToExistin const namespacePrefix = getNamespaceLikeImportText(declaration); const moduleSpecifier = namespacePrefix && tryGetModuleSpecifierFromDeclaration(declaration)?.text; if (moduleSpecifier) { - return { kind: ImportFixKind.UseNamespace, namespacePrefix, usagePosition: position, moduleSpecifier }; + return { kind: ImportFixKind.UseNamespace, namespacePrefix, usagePosition: position, moduleSpecifierKind: undefined, moduleSpecifier }; } }); } @@ -1017,7 +1019,7 @@ function tryAddToExistingImport(existingImports: readonly FixAddToExistingImport if (declaration.kind === SyntaxKind.VariableDeclaration) { return (importKind === ImportKind.Named || importKind === ImportKind.Default) && declaration.name.kind === SyntaxKind.ObjectBindingPattern - ? { kind: ImportFixKind.AddToExisting, importClauseOrBindingPattern: declaration.name, importKind, moduleSpecifier: declaration.initializer.arguments[0].text, addAsTypeOnly: AddAsTypeOnly.NotAllowed } + ? { kind: ImportFixKind.AddToExisting, importClauseOrBindingPattern: declaration.name, importKind, moduleSpecifierKind: undefined, moduleSpecifier: declaration.initializer.arguments[0].text, addAsTypeOnly: AddAsTypeOnly.NotAllowed } : undefined; } @@ -1056,6 +1058,7 @@ function tryAddToExistingImport(existingImports: readonly FixAddToExistingImport kind: ImportFixKind.AddToExisting, importClauseOrBindingPattern: importClause, importKind, + moduleSpecifierKind: undefined, moduleSpecifier: declaration.moduleSpecifier.text, addAsTypeOnly, }; @@ -1154,13 +1157,13 @@ function getNewImportFixes( const moduleResolution = getEmitModuleResolutionKind(compilerOptions); const rejectNodeModulesRelativePaths = moduleResolutionUsesNodeModules(moduleResolution); const getModuleSpecifiers = fromCacheOnly - ? (exportInfo: SymbolExportInfo | FutureSymbolExportInfo) => ({ moduleSpecifiers: moduleSpecifiers.tryGetModuleSpecifiersFromCache(exportInfo.moduleSymbol, sourceFile, moduleSpecifierResolutionHost, preferences), computedWithoutCache: false }) + ? (exportInfo: SymbolExportInfo | FutureSymbolExportInfo) => moduleSpecifiers.tryGetModuleSpecifiersFromCache(exportInfo.moduleSymbol, sourceFile, moduleSpecifierResolutionHost, preferences) : (exportInfo: SymbolExportInfo | FutureSymbolExportInfo, checker: TypeChecker) => moduleSpecifiers.getModuleSpecifiersWithCacheInfo(exportInfo.moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences, /*options*/ undefined, /*forAutoImport*/ true); let computedWithoutCacheCount = 0; const fixes = flatMap(exportInfo, (exportInfo, i) => { const checker = getChecker(exportInfo.isFromPackageJson); - const { computedWithoutCache, moduleSpecifiers } = getModuleSpecifiers(exportInfo, checker); + const { computedWithoutCache, moduleSpecifiers, kind: moduleSpecifierKind } = getModuleSpecifiers(exportInfo, checker) ?? {}; const importedSymbolHasValueMeaning = !!(exportInfo.targetFlags & SymbolFlags.Value); const addAsTypeOnly = getAddAsTypeOnly(isValidTypeOnlyUseSite, /*isForNewImportDeclaration*/ true, exportInfo.symbol, exportInfo.targetFlags, checker, compilerOptions); computedWithoutCacheCount += computedWithoutCache ? 1 : 0; @@ -1170,7 +1173,7 @@ function getNewImportFixes( } if (!importedSymbolHasValueMeaning && isJs && usagePosition !== undefined) { // `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types. - return { kind: ImportFixKind.JsdocTypeImport, moduleSpecifier, usagePosition, exportInfo, isReExport: i > 0 }; + return { kind: ImportFixKind.JsdocTypeImport, moduleSpecifierKind, moduleSpecifier, usagePosition, exportInfo, isReExport: i > 0 }; } const importKind = getImportKind(sourceFile, exportInfo.exportKind, program); let qualification: Qualification | undefined; @@ -1195,6 +1198,7 @@ function getNewImportFixes( } return { kind: ImportFixKind.AddNew, + moduleSpecifierKind, moduleSpecifier, importKind, useRequire, @@ -1237,7 +1241,7 @@ function newImportInfoFromExistingSpecifier( const addAsTypeOnly = useRequire ? AddAsTypeOnly.NotAllowed : getAddAsTypeOnly(isValidTypeOnlyUseSite, /*isForNewImportDeclaration*/ true, symbol, targetFlags, checker, compilerOptions); - return { kind: ImportFixKind.AddNew, moduleSpecifier, importKind, addAsTypeOnly, useRequire }; + return { kind: ImportFixKind.AddNew, moduleSpecifierKind: undefined, moduleSpecifier, importKind, addAsTypeOnly, useRequire }; } } @@ -1266,24 +1270,24 @@ function getFixInfos(context: CodeFixContextBase, errorCode: number, pos: number } const packageJsonImportFilter = createPackageJsonImportFilter(context.sourceFile, context.preferences, context.host); - return info && sortFixInfo(info, context.sourceFile, context.program, packageJsonImportFilter, context.host); + return info && sortFixInfo(info, context.sourceFile, context.program, packageJsonImportFilter, context.host, context.preferences); } -function sortFixInfo(fixes: readonly (FixInfo & { fix: ImportFixWithModuleSpecifier; })[], sourceFile: SourceFile, program: Program, packageJsonImportFilter: PackageJsonImportFilter, host: LanguageServiceHost): readonly (FixInfo & { fix: ImportFixWithModuleSpecifier; })[] { +function sortFixInfo(fixes: readonly (FixInfo & { fix: ImportFixWithModuleSpecifier; })[], sourceFile: SourceFile, program: Program, packageJsonImportFilter: PackageJsonImportFilter, host: LanguageServiceHost, preferences: UserPreferences): readonly (FixInfo & { fix: ImportFixWithModuleSpecifier; })[] { const _toPath = (fileName: string) => toPath(fileName, host.getCurrentDirectory(), hostGetCanonicalFileName(host)); return sort(fixes, (a, b) => compareBooleans(!!a.isJsxNamespaceFix, !!b.isJsxNamespaceFix) || compareValues(a.fix.kind, b.fix.kind) || - compareModuleSpecifiers(a.fix, b.fix, sourceFile, program, packageJsonImportFilter.allowsImportingSpecifier, _toPath)); + compareModuleSpecifiers(a.fix, b.fix, sourceFile, program, preferences, packageJsonImportFilter.allowsImportingSpecifier, _toPath)); } function getFixInfosWithoutDiagnostic(context: CodeFixContextBase, symbolToken: Identifier, useAutoImportProvider: boolean): readonly FixInfo[] | undefined { const info = getFixesInfoForNonUMDImport(context, symbolToken, useAutoImportProvider); const packageJsonImportFilter = createPackageJsonImportFilter(context.sourceFile, context.preferences, context.host); - return info && sortFixInfo(info, context.sourceFile, context.program, packageJsonImportFilter, context.host); + return info && sortFixInfo(info, context.sourceFile, context.program, packageJsonImportFilter, context.host, context.preferences); } -function getBestFix(fixes: readonly ImportFixWithModuleSpecifier[], sourceFile: SourceFile | FutureSourceFile, program: Program, packageJsonImportFilter: PackageJsonImportFilter, host: LanguageServiceHost): ImportFixWithModuleSpecifier | undefined { +function getBestFix(fixes: readonly ImportFixWithModuleSpecifier[], sourceFile: SourceFile | FutureSourceFile, program: Program, packageJsonImportFilter: PackageJsonImportFilter, host: LanguageServiceHost, preferences: UserPreferences): ImportFixWithModuleSpecifier | undefined { if (!some(fixes)) return; // These will always be placed first if available, and are better than other kinds if (fixes[0].kind === ImportFixKind.UseNamespace || fixes[0].kind === ImportFixKind.AddToExisting) { @@ -1297,6 +1301,7 @@ function getBestFix(fixes: readonly ImportFixWithModuleSpecifier[], sourceFile: best, sourceFile, program, + preferences, packageJsonImportFilter.allowsImportingSpecifier, fileName => toPath(fileName, host.getCurrentDirectory(), hostGetCanonicalFileName(host)), ) === Comparison.LessThan ? fix : best @@ -1309,11 +1314,16 @@ function compareModuleSpecifiers( b: ImportFixWithModuleSpecifier, importingFile: SourceFile | FutureSourceFile, program: Program, + preferences: UserPreferences, allowsImportingSpecifier: (specifier: string) => boolean, toPath: (fileName: string) => Path, ): Comparison { if (a.kind !== ImportFixKind.UseNamespace && b.kind !== ImportFixKind.UseNamespace) { - return compareBooleans(allowsImportingSpecifier(b.moduleSpecifier), allowsImportingSpecifier(a.moduleSpecifier)) + return compareBooleans( + b.moduleSpecifierKind !== "node_modules" || allowsImportingSpecifier(b.moduleSpecifier), + a.moduleSpecifierKind !== "node_modules" || allowsImportingSpecifier(a.moduleSpecifier), + ) + || compareModuleSpecifierRelativity(a, b, preferences) || compareNodeCoreModuleSpecifiers(a.moduleSpecifier, b.moduleSpecifier, importingFile, program) || compareBooleans( isFixPossiblyReExportingImportingFile(a, importingFile.path, toPath), @@ -1324,6 +1334,13 @@ function compareModuleSpecifiers( return Comparison.EqualTo; } +function compareModuleSpecifierRelativity(a: ImportFixWithModuleSpecifier, b: ImportFixWithModuleSpecifier, preferences: UserPreferences): Comparison { + if (preferences.importModuleSpecifierPreference === "non-relative" || preferences.importModuleSpecifierPreference === "project-relative") { + return compareBooleans(a.moduleSpecifierKind === "relative", b.moduleSpecifierKind === "relative"); + } + return Comparison.EqualTo; +} + // This is a simple heuristic to try to avoid creating an import cycle with a barrel re-export. // E.g., do not `import { Foo } from ".."` when you could `import { Foo } from "../Foo"`. // This can produce false positives or negatives if re-exports cross into sibling directories diff --git a/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap1.js b/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap1.js index 9ecb48c25507f..866447912e070 100644 --- a/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap1.js +++ b/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap1.js @@ -352,7 +352,7 @@ Info seq [hh:mm:ss:mss] response: "body": [ { "fixName": "import", - "description": "Add import from \"./env/browser.js\"", + "description": "Add import from \"#is-browser\"", "changes": [ { "fileName": "/src/a.ts", @@ -366,7 +366,7 @@ Info seq [hh:mm:ss:mss] response: "line": 1, "offset": 1 }, - "newText": "import { isBrowser } from \"./env/browser.js\";\r\n\r\n" + "newText": "import { isBrowser } from \"#is-browser\";\r\n\r\n" } ] } @@ -374,7 +374,7 @@ Info seq [hh:mm:ss:mss] response: }, { "fixName": "import", - "description": "Add import from \"#is-browser\"", + "description": "Add import from \"./env/browser.js\"", "changes": [ { "fileName": "/src/a.ts", @@ -388,7 +388,7 @@ Info seq [hh:mm:ss:mss] response: "line": 1, "offset": 1 }, - "newText": "import { isBrowser } from \"#is-browser\";\r\n\r\n" + "newText": "import { isBrowser } from \"./env/browser.js\";\r\n\r\n" } ] } diff --git a/tests/baselines/reference/tsserver/moduleSpecifierCache/caches-module-specifiers-within-a-file.js b/tests/baselines/reference/tsserver/moduleSpecifierCache/caches-module-specifiers-within-a-file.js index 25e23cd20eea7..8f5275900c7bd 100644 --- a/tests/baselines/reference/tsserver/moduleSpecifierCache/caches-module-specifiers-within-a-file.js +++ b/tests/baselines/reference/tsserver/moduleSpecifierCache/caches-module-specifiers-within-a-file.js @@ -1094,6 +1094,7 @@ Info seq [hh:mm:ss:mss] response: After request Info seq [hh:mm:ss:mss] mobxCache: { + "kind": "node_modules", "modulePaths": [ { "path": "/node_modules/mobx/index.d.ts", diff --git a/tests/cases/fourslash/completionsImport_packageJsonImportsPreference.ts b/tests/cases/fourslash/completionsImport_packageJsonImportsPreference.ts new file mode 100644 index 0000000000000..7e39f4ab22a6f --- /dev/null +++ b/tests/cases/fourslash/completionsImport_packageJsonImportsPreference.ts @@ -0,0 +1,57 @@ +/// + +// @module: preserve +// @allowImportingTsExtensions: true + +// @Filename: /project/package.json +//// { +//// "name": "project", +//// "version": "1.0.0", +//// "imports": { +//// "#internal/*": "./src/internal/*.ts" +//// } +//// } + +// @Filename: /project/src/internal/foo.ts +//// export const internalFoo = 0; + +// @Filename: /project/src/other.ts +//// export * from "./internal/foo.ts"; + +// @Filename: /project/src/main.ts +//// internalFoo/**/ + +goTo.marker(""); +verify.completions({ + includes: [ + { + name: "internalFoo", + source: "#internal/foo", + sourceDisplay: "#internal/foo", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions, + }, + ], + preferences: { + includeCompletionsForModuleExports: true, + allowIncompleteCompletions: true, + importModuleSpecifierPreference: "non-relative" + }, +}); + +verify.completions({ + includes: [ + { + name: "internalFoo", + source: "./other", + sourceDisplay: "./other", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions, + }, + ], + preferences: { + includeCompletionsForModuleExports: true, + allowIncompleteCompletions: true, + importModuleSpecifierPreference: "relative" + }, +}); diff --git a/tests/cases/fourslash/completionsImport_windowsPathsProjectRelative.ts b/tests/cases/fourslash/completionsImport_windowsPathsProjectRelative.ts new file mode 100644 index 0000000000000..12ed7049c80c7 --- /dev/null +++ b/tests/cases/fourslash/completionsImport_windowsPathsProjectRelative.ts @@ -0,0 +1,99 @@ +/// + +// @Filename: c:/project/tsconfig.json +//// { +//// "compilerOptions": { +//// "paths": { +//// "~/noIndex/*": ["./src/noIndex/*"], +//// "~/withIndex": ["./src/withIndex/index.ts"] +//// } +//// } +//// } + +// @Filename: c:/project/package.json +//// {} + +// @Filename: c:/project/src/noIndex/a.ts +//// export const myFunctionA = () => {}; + +// @Filename: c:/project/src/withIndex/b.ts +//// export const myFunctionB = () => {}; + +// @Filename: c:/project/src/withIndex/index.ts +//// export * from './b'; + +// @Filename: c:/project/src/reproduction/1.ts +//// myFunction/**/ + +goTo.marker(""); +verify.completions({ + includes: [ + { + name: "myFunctionA", + source: "~/noIndex/a", + sourceDisplay: "~/noIndex/a", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions, + }, + { + name: "myFunctionB", + source: "~/withIndex", + sourceDisplay: "~/withIndex", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions, + }, + ], + preferences: { + includeCompletionsForModuleExports: true, + allowIncompleteCompletions: true, + importModuleSpecifierPreference: "non-relative" + }, +}); + +verify.completions({ + includes: [ + { + name: "myFunctionA", + source: "../noIndex/a", + sourceDisplay: "../noIndex/a", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions, + }, + { + name: "myFunctionB", + source: "../withIndex", + sourceDisplay: "../withIndex", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions, + }, + ], + preferences: { + includeCompletionsForModuleExports: true, + allowIncompleteCompletions: true, + importModuleSpecifierPreference: "relative" + }, +}); + +verify.completions({ + includes: [ + { + name: "myFunctionA", + source: "../noIndex/a", + sourceDisplay: "../noIndex/a", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions, + }, + { + name: "myFunctionB", + source: "../withIndex", + sourceDisplay: "../withIndex", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions, + }, + ], + preferences: { + includeCompletionsForModuleExports: true, + allowIncompleteCompletions: true, + importModuleSpecifierPreference: "project-relative" + }, +}); \ No newline at end of file diff --git a/tests/cases/fourslash/server/autoImportProvider_importsMap1.ts b/tests/cases/fourslash/server/autoImportProvider_importsMap1.ts index 02f6072753d16..a4c0457180130 100644 --- a/tests/cases/fourslash/server/autoImportProvider_importsMap1.ts +++ b/tests/cases/fourslash/server/autoImportProvider_importsMap1.ts @@ -29,4 +29,4 @@ // @Filename: /src/a.ts //// isBrowser/**/ -verify.importFixModuleSpecifiers("", ["./env/browser.js", "#is-browser"]); \ No newline at end of file +verify.importFixModuleSpecifiers("", ["#is-browser", "./env/browser.js"]);