diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 54be63be022fa..680a30171a551 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -69,7 +69,7 @@ namespace ts.moduleSpecifiers { const info = getInfo(importingSourceFileName, host); const modulePaths = getAllModulePaths(importingSourceFileName, nodeModulesFileName, host); return firstDefined(modulePaths, - moduleFileName => tryGetModuleNameAsNodeModule(moduleFileName, info, host, compilerOptions, /*packageNameOnly*/ true)); + modulePath => tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions, /*packageNameOnly*/ true)); } function getModuleSpecifierWorker( @@ -81,7 +81,7 @@ namespace ts.moduleSpecifiers { ): string { const info = getInfo(importingSourceFileName, host); const modulePaths = getAllModulePaths(importingSourceFileName, toFileName, host); - return firstDefined(modulePaths, moduleFileName => tryGetModuleNameAsNodeModule(moduleFileName, info, host, compilerOptions)) || + return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions)) || getLocalModuleSpecifier(toFileName, info, compilerOptions, preferences); } @@ -101,8 +101,48 @@ namespace ts.moduleSpecifiers { const modulePaths = getAllModulePaths(importingSourceFile.path, moduleSourceFile.originalFileName, host); const preferences = getPreferences(userPreferences, compilerOptions, importingSourceFile); - const global = mapDefined(modulePaths, moduleFileName => tryGetModuleNameAsNodeModule(moduleFileName, info, host, compilerOptions)); - return global.length ? global : modulePaths.map(moduleFileName => getLocalModuleSpecifier(moduleFileName, info, compilerOptions, preferences)); + const importedFileIsInNodeModules = some(modulePaths, p => p.isInNodeModules); + + // Module specifier priority: + // 1. "Bare package specifiers" (e.g. "@foo/bar") resulting from a path through node_modules to a package.json's "types" entry + // 2. Specifiers generated using "paths" from tsconfig + // 3. Non-relative specfiers resulting from a path through node_modules (e.g. "@foo/bar/path/to/file") + // 4. Relative paths + let nodeModulesSpecifiers: string[] | undefined; + let pathsSpecifiers: string[] | undefined; + let relativeSpecifiers: string[] | undefined; + for (const modulePath of modulePaths) { + const specifier = tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions); + nodeModulesSpecifiers = append(nodeModulesSpecifiers, specifier); + 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!; + } + + if (!specifier && !modulePath.isRedirect) { + const local = getLocalModuleSpecifier(modulePath.path, info, compilerOptions, preferences); + if (pathIsBareSpecifier(local)) { + pathsSpecifiers = append(pathsSpecifiers, local); + } + else if (!importedFileIsInNodeModules || modulePath.isInNodeModules) { + // Why this extra conditional, not just an `else`? If some path to the file contained + // 'node_modules', but we can't create a non-relative specifier (e.g. "@foo/bar/path/to/file"), + // that means we had to go through a *sibling's* node_modules, not one we can access directly. + // If some path to the file was in node_modules but another was not, this likely indicates that + // we have a monorepo structure with symlinks. In this case, the non-node_modules path is + // probably the realpath, e.g. "../bar/path/to/file", but a relative path to another package + // in a monorepo is probably not portable. So, the module specifier we actually go with will be + // the relative path through node_modules, so that the declaration emitter can produce a + // portability error. (See declarationEmitReexportedSymlinkReference3) + relativeSpecifiers = append(relativeSpecifiers, local); + } + } + } + + return pathsSpecifiers?.length ? pathsSpecifiers : + nodeModulesSpecifiers?.length ? nodeModulesSpecifiers : + Debug.checkDefined(relativeSpecifiers); } interface Info { @@ -161,10 +201,10 @@ namespace ts.moduleSpecifiers { return match ? match.length : 0; } - function comparePathsByNumberOfDirectorySeparators(a: string, b: string) { - return compareValues( - numberOfDirectorySeparators(a), - numberOfDirectorySeparators(b) + function comparePathsByRedirectAndNumberOfDirectorySeparators(a: ModulePath, b: ModulePath) { + return compareBooleans(b.isRedirect, a.isRedirect) || compareValues( + numberOfDirectorySeparators(a.path), + numberOfDirectorySeparators(b.path) ); } @@ -173,7 +213,7 @@ namespace ts.moduleSpecifiers { importedFileName: string, host: ModuleSpecifierResolutionHost, preferSymlinks: boolean, - cb: (fileName: string) => T | undefined + cb: (fileName: string, isRedirect: boolean) => T | undefined ): T | undefined { const getCanonicalFileName = hostGetCanonicalFileName(host); const cwd = host.getCurrentDirectory(); @@ -182,7 +222,7 @@ namespace ts.moduleSpecifiers { const importedFileNames = [...(referenceRedirect ? [referenceRedirect] : emptyArray), importedFileName, ...redirects]; const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd)); if (!preferSymlinks) { - const result = forEach(targets, cb); + const result = forEach(targets, p => cb(p, referenceRedirect === p)); if (result) return result; } const links = host.getSymlinkCache @@ -197,61 +237,68 @@ namespace ts.moduleSpecifiers { return undefined; // Don't want to a package to globally import from itself } - const target = find(targets, t => compareStrings(t.slice(0, resolved.real.length), resolved.real) === Comparison.EqualTo); - if (target === undefined) return undefined; + return forEach(targets, target => { + if (compareStrings(target.slice(0, resolved.real.length), resolved.real) !== Comparison.EqualTo) { + return; + } - const relative = getRelativePathFromDirectory(resolved.real, target, getCanonicalFileName); - const option = resolvePath(path, relative); - if (!host.fileExists || host.fileExists(option)) { - const result = cb(option); - if (result) return result; - } + const relative = getRelativePathFromDirectory(resolved.real, target, getCanonicalFileName); + const option = resolvePath(path, relative); + if (!host.fileExists || host.fileExists(option)) { + const result = cb(option, target === referenceRedirect); + if (result) return result; + } + }); }); return result || - (preferSymlinks ? forEach(targets, cb) : undefined); + (preferSymlinks ? forEach(targets, p => cb(p, p === referenceRedirect)) : undefined); + } + + interface ModulePath { + path: string; + isInNodeModules: boolean; + isRedirect: boolean; } /** * Looks for existing imports that use symlinks to this module. * Symlinks will be returned first so they are preferred over the real path. */ - function getAllModulePaths(importingFileName: string, importedFileName: string, host: ModuleSpecifierResolutionHost): readonly string[] { + function getAllModulePaths(importingFileName: string, importedFileName: string, host: ModuleSpecifierResolutionHost): readonly ModulePath[] { const cwd = host.getCurrentDirectory(); const getCanonicalFileName = hostGetCanonicalFileName(host); - const allFileNames = new Map(); + const allFileNames = new Map(); let importedFileFromNodeModules = false; forEachFileNameOfModule( importingFileName, importedFileName, host, /*preferSymlinks*/ true, - path => { + (path, isRedirect) => { + const isInNodeModules = pathContainsNodeModules(path); + allFileNames.set(path, { path: getCanonicalFileName(path), isRedirect, isInNodeModules }); + importedFileFromNodeModules = importedFileFromNodeModules || isInNodeModules; // dont return value, so we collect everything - allFileNames.set(path, getCanonicalFileName(path)); - importedFileFromNodeModules = importedFileFromNodeModules || pathContainsNodeModules(path); } ); // Sort by paths closest to importing file Name directory - const sortedPaths: string[] = []; + const sortedPaths: ModulePath[] = []; for ( let directory = getDirectoryPath(toPath(importingFileName, cwd, getCanonicalFileName)); allFileNames.size !== 0; ) { const directoryStart = ensureTrailingDirectorySeparator(directory); - let pathsInDirectory: string[] | undefined; - allFileNames.forEach((canonicalFileName, fileName) => { - if (startsWith(canonicalFileName, directoryStart)) { - // If the importedFile is from node modules, use only paths in node_modules folder as option - if (!importedFileFromNodeModules || pathContainsNodeModules(fileName)) { - (pathsInDirectory || (pathsInDirectory = [])).push(fileName); - } + let pathsInDirectory: ModulePath[] | undefined; + allFileNames.forEach(({ path, isRedirect, isInNodeModules }, fileName) => { + if (startsWith(path, directoryStart)) { + (pathsInDirectory ||= []).push({ path: fileName, isRedirect, isInNodeModules }); allFileNames.delete(fileName); } }); if (pathsInDirectory) { if (pathsInDirectory.length > 1) { - pathsInDirectory.sort(comparePathsByNumberOfDirectorySeparators); + pathsInDirectory.sort(comparePathsByRedirectAndNumberOfDirectorySeparators); } sortedPaths.push(...pathsInDirectory); } @@ -261,7 +308,7 @@ namespace ts.moduleSpecifiers { } if (allFileNames.size) { const remainingPaths = arrayFrom(allFileNames.values()); - if (remainingPaths.length > 1) remainingPaths.sort(comparePathsByNumberOfDirectorySeparators); + if (remainingPaths.length > 1) remainingPaths.sort(comparePathsByRedirectAndNumberOfDirectorySeparators); sortedPaths.push(...remainingPaths); } return sortedPaths; @@ -312,18 +359,19 @@ namespace ts.moduleSpecifiers { : removeFileExtension(relativePath); } - function tryGetModuleNameAsNodeModule(moduleFileName: string, { getCanonicalFileName, sourceDirectory }: Info, host: ModuleSpecifierResolutionHost, options: CompilerOptions, packageNameOnly?: boolean): string | undefined { + function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, host: ModuleSpecifierResolutionHost, options: CompilerOptions, packageNameOnly?: boolean): string | undefined { if (!host.fileExists || !host.readFile) { return undefined; } - const parts: NodeModulePathParts = getNodeModulePathParts(moduleFileName)!; + const parts: NodeModulePathParts = getNodeModulePathParts(path)!; if (!parts) { return undefined; } // Simplify the full file path to something that can be resolved by Node. - let moduleSpecifier = moduleFileName; + let moduleSpecifier = path; + let isPackageRootPath = false; if (!packageNameOnly) { let packageRootIndex = parts.packageRootIndex; let moduleFileNameForExtensionless: string | undefined; @@ -332,12 +380,13 @@ namespace ts.moduleSpecifiers { const { moduleFileToTry, packageRootPath } = tryDirectoryWithPackageJson(packageRootIndex); if (packageRootPath) { moduleSpecifier = packageRootPath; + isPackageRootPath = true; break; } if (!moduleFileNameForExtensionless) moduleFileNameForExtensionless = moduleFileToTry; // try with next level of directory - packageRootIndex = moduleFileName.indexOf(directorySeparator, packageRootIndex + 1); + packageRootIndex = path.indexOf(directorySeparator, packageRootIndex + 1); if (packageRootIndex === -1) { moduleSpecifier = getExtensionlessFileName(moduleFileNameForExtensionless); break; @@ -345,6 +394,10 @@ namespace ts.moduleSpecifiers { } } + if (isRedirect && !isPackageRootPath) { + return undefined; + } + const globalTypingsCacheLocation = host.getGlobalTypingsCacheLocation && host.getGlobalTypingsCacheLocation(); // Get a path that's relative to node_modules or the importing file's path // if node_modules folder is in this folder or any of its parent folders, no need to keep it. @@ -360,16 +413,16 @@ namespace ts.moduleSpecifiers { return getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs && packageName === nodeModulesDirectoryName ? undefined : packageName; function tryDirectoryWithPackageJson(packageRootIndex: number) { - const packageRootPath = moduleFileName.substring(0, packageRootIndex); + const packageRootPath = path.substring(0, packageRootIndex); const packageJsonPath = combinePaths(packageRootPath, "package.json"); - let moduleFileToTry = moduleFileName; + let moduleFileToTry = path; if (host.fileExists(packageJsonPath)) { const packageJsonContent = JSON.parse(host.readFile!(packageJsonPath)!); const versionPaths = packageJsonContent.typesVersions ? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions) : undefined; if (versionPaths) { - const subModuleName = moduleFileName.slice(packageRootPath.length + 1); + const subModuleName = path.slice(packageRootPath.length + 1); const fromPaths = tryGetModuleNameFromPaths( removeFileExtension(subModuleName), removeExtensionAndIndexPostFix(subModuleName, Ending.Minimal, options), diff --git a/src/compiler/path.ts b/src/compiler/path.ts index f377b411b244a..d7df5eefb2f4f 100644 --- a/src/compiler/path.ts +++ b/src/compiler/path.ts @@ -68,6 +68,14 @@ namespace ts { return /^\.\.?($|[\\/])/.test(path); } + /** + * Determines whether a path is neither relative nor absolute, e.g. "path/to/file". + * Also known misleadingly as "non-relative". + */ + export function pathIsBareSpecifier(path: string): boolean { + return !pathIsAbsolute(path) && !pathIsRelative(path); + } + export function hasExtension(fileName: string): boolean { return stringContains(getBaseFileName(fileName), "."); } diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 61a35dc1857ad..5ffd07547113c 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -2867,9 +2867,14 @@ namespace FourSlash { const change = ts.first(codeFix.changes); ts.Debug.assert(change.fileName === fileName); this.applyEdits(change.fileName, change.textChanges); - const text = range ? this.rangeText(range) : this.getFileContent(this.activeFile.fileName); + const text = range ? this.rangeText(range) : this.getFileContent(fileName); actualTextArray.push(text); - scriptInfo.updateContent(originalContent); + + // Undo changes to perform next fix + const span = change.textChanges[0].span; + const deletedText = originalContent.substr(span.start, change.textChanges[0].span.length); + const insertedText = change.textChanges[0].newText; + this.editScriptAndUpdateMarkers(fileName, span.start, span.start + insertedText.length, deletedText); } if (expectedTextArray.length !== actualTextArray.length) { this.raiseError(`Expected ${expectedTextArray.length} import fixes, got ${actualTextArray.length}`); diff --git a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts index 0fb2f8d883f9b..432cd81098128 100644 --- a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts +++ b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts @@ -149,7 +149,7 @@ fnErr(); { line: 4, offset: 5 }, { line: 4, offset: 10 }, Diagnostics.Module_0_has_no_exported_member_1, - [`"../decls/fns"`, "fnErr"], + [`"../dependency/fns"`, "fnErr"], "error", ) ], diff --git a/tests/cases/fourslash/server/autoImportCrossProject_paths_sharedOutDir.ts b/tests/cases/fourslash/server/autoImportCrossProject_paths_sharedOutDir.ts new file mode 100644 index 0000000000000..2c38808ea9af6 --- /dev/null +++ b/tests/cases/fourslash/server/autoImportCrossProject_paths_sharedOutDir.ts @@ -0,0 +1,43 @@ +/// + +// @Filename: /tsconfig.base.json +//// { +//// "compilerOptions": { +//// "module": "commonjs", +//// "baseUrl": ".", +//// "paths": { +//// "packages/*": ["./packages/*"] +//// } +//// } +//// } + +// @Filename: /packages/app/tsconfig.json +//// { +//// "extends": "../../tsconfig.base.json", +//// "compilerOptions": { "outDir": "../../dist/packages/app" }, +//// "references": [{ "path": "../dep" }] +//// } + +// @Filename: /packages/app/index.ts +//// dep/**/ + +// @Filename: /packages/app/utils.ts +//// import "packages/dep"; + + +// @Filename: /packages/dep/tsconfig.json +//// { +//// "extends": "../../tsconfig.base.json", +//// "compilerOptions": { "outDir": "../../dist/packages/dep" } +//// } + +// @Filename: /packages/dep/index.ts +//// import "./sub/folder"; + +// @Filename: /packages/dep/sub/folder/index.ts +//// export const dep = 0; + +goTo.marker(""); +verify.importFixAtPosition([`import { dep } from "packages/dep/sub/folder";\r +\r +dep`]); diff --git a/tests/cases/fourslash/server/autoImportCrossProject_paths_stripSrc.ts b/tests/cases/fourslash/server/autoImportCrossProject_paths_stripSrc.ts new file mode 100644 index 0000000000000..6eda86ffb69b6 --- /dev/null +++ b/tests/cases/fourslash/server/autoImportCrossProject_paths_stripSrc.ts @@ -0,0 +1,55 @@ +/// + +// @Filename: /packages/app/package.json +//// { "name": "app", "dependencies": { "dep": "*" } } + +// @Filename: /packages/app/tsconfig.json +//// { +//// "compilerOptions": { +//// "module": "commonjs", +//// "outDir": "dist", +//// "rootDir": "src", +//// "baseUrl": ".", +//// "paths": { +//// "dep": ["../dep/src/main"], +//// "dep/*": ["../dep/src/*"] +//// } +//// } +//// "references": [{ "path": "../dep" }] +//// } + +// @Filename: /packages/app/src/index.ts +//// dep1/*1*/; + +// @Filename: /packages/app/src/utils.ts +//// dep2/*2*/; + +// @Filename: /packages/app/src/a.ts +//// import "dep"; + + +// @Filename: /packages/dep/package.json +//// { "name": "dep", "main": "dist/main.js", "types": "dist/main.d.ts" } + +// @Filename: /packages/dep/tsconfig.json +//// { +//// "compilerOptions": { "outDir": "dist", "rootDir": "src", "module": "commonjs" } +//// } + +// @Filename: /packages/dep/src/main.ts +//// import "./sub/folder"; +//// export const dep1 = 0; + +// @Filename: /packages/dep/src/sub/folder/index.ts +//// export const dep2 = 0; + + +goTo.marker("1"); +verify.importFixAtPosition([`import { dep1 } from "dep";\r +\r +dep1;`]); + +goTo.marker("2"); +verify.importFixAtPosition([`import { dep2 } from "dep/sub/folder";\r +\r +dep2;`]); diff --git a/tests/cases/fourslash/server/autoImportCrossProject_paths_toDist.ts b/tests/cases/fourslash/server/autoImportCrossProject_paths_toDist.ts new file mode 100644 index 0000000000000..97bb9f1fefcc0 --- /dev/null +++ b/tests/cases/fourslash/server/autoImportCrossProject_paths_toDist.ts @@ -0,0 +1,55 @@ +/// + +// @Filename: /packages/app/package.json +//// { "name": "app", "dependencies": { "dep": "*" } } + +// @Filename: /packages/app/tsconfig.json +//// { +//// "compilerOptions": { +//// "module": "commonjs", +//// "outDir": "dist", +//// "rootDir": "src", +//// "baseUrl": ".", +//// "paths": { +//// "dep": ["../dep/src/main"], +//// "dep/dist/*": ["../dep/src/*"] +//// } +//// } +//// "references": [{ "path": "../dep" }] +//// } + +// @Filename: /packages/app/src/index.ts +//// dep1/*1*/; + +// @Filename: /packages/app/src/utils.ts +//// dep2/*2*/; + +// @Filename: /packages/app/src/a.ts +//// import "dep"; + + +// @Filename: /packages/dep/package.json +//// { "name": "dep", "main": "dist/main.js", "types": "dist/main.d.ts" } + +// @Filename: /packages/dep/tsconfig.json +//// { +//// "compilerOptions": { "outDir": "dist", "rootDir": "src", "module": "commonjs" } +//// } + +// @Filename: /packages/dep/src/main.ts +//// import "./sub/folder"; +//// export const dep1 = 0; + +// @Filename: /packages/dep/src/sub/folder/index.ts +//// export const dep2 = 0; + + +goTo.marker("1"); +verify.importFixAtPosition([`import { dep1 } from "dep";\r +\r +dep1;`]); + +goTo.marker("2"); +verify.importFixAtPosition([`import { dep2 } from "dep/dist/sub/folder";\r +\r +dep2;`]); diff --git a/tests/cases/fourslash/server/autoImportCrossProject_paths_toSrc.ts b/tests/cases/fourslash/server/autoImportCrossProject_paths_toSrc.ts new file mode 100644 index 0000000000000..7342b931383fc --- /dev/null +++ b/tests/cases/fourslash/server/autoImportCrossProject_paths_toSrc.ts @@ -0,0 +1,55 @@ +/// + +// @Filename: /packages/app/package.json +//// { "name": "app", "dependencies": { "dep": "*" } } + +// @Filename: /packages/app/tsconfig.json +//// { +//// "compilerOptions": { +//// "module": "commonjs", +//// "outDir": "dist", +//// "rootDir": "src", +//// "baseUrl": ".", +//// "paths": { +//// "dep": ["../dep/src/main"], +//// "dep/*": ["../dep/*"] +//// } +//// } +//// "references": [{ "path": "../dep" }] +//// } + +// @Filename: /packages/app/src/index.ts +//// dep1/*1*/; + +// @Filename: /packages/app/src/utils.ts +//// dep2/*2*/; + +// @Filename: /packages/app/src/a.ts +//// import "dep"; + + +// @Filename: /packages/dep/package.json +//// { "name": "dep", "main": "dist/main.js", "types": "dist/main.d.ts" } + +// @Filename: /packages/dep/tsconfig.json +//// { +//// "compilerOptions": { "outDir": "dist", "rootDir": "src", "module": "commonjs" } +//// } + +// @Filename: /packages/dep/src/main.ts +//// import "./sub/folder"; +//// export const dep1 = 0; + +// @Filename: /packages/dep/src/sub/folder/index.ts +//// export const dep2 = 0; + + +goTo.marker("1"); +verify.importFixAtPosition([`import { dep1 } from "dep";\r +\r +dep1;`]); + +goTo.marker("2"); +verify.importFixAtPosition([`import { dep2 } from "dep/src/sub/folder";\r +\r +dep2;`]); diff --git a/tests/cases/fourslash/server/autoImportCrossProject_symlinks_stripSrc.ts b/tests/cases/fourslash/server/autoImportCrossProject_symlinks_stripSrc.ts new file mode 100644 index 0000000000000..f18b998da4e41 --- /dev/null +++ b/tests/cases/fourslash/server/autoImportCrossProject_symlinks_stripSrc.ts @@ -0,0 +1,43 @@ +/// + +// @Filename: /packages/app/package.json +//// { "name": "app", "dependencies": { "dep": "*" } } + +// @Filename: /packages/app/tsconfig.json +//// { +//// "compilerOptions": { +//// "module": "commonjs", +//// "outDir": "dist", +//// "rootDir": "src", +//// "baseUrl": ".", +//// "paths": { +//// "dep/*": ["../dep/src/*"] +//// } +//// } +//// "references": [{ "path": "../dep" }] +//// } + +// @Filename: /packages/app/src/index.ts +//// dep/**/ + + +// @Filename: /packages/dep/package.json +//// { "name": "dep", "main": "dist/index.js", "types": "dist/index.d.ts" } + +// @Filename: /packages/dep/tsconfig.json +//// { +//// "compilerOptions": { "outDir": "dist", "rootDir": "src", "module": "commonjs" } +//// } + +// @Filename: /packages/dep/src/index.ts +//// import "./sub/folder"; + +// @Filename: /packages/dep/src/sub/folder/index.ts +//// export const dep = 0; + +// @link: /packages/dep -> /packages/app/node_modules/dep + +goTo.marker(""); +verify.importFixAtPosition([`import { dep } from "dep/sub/folder";\r +\r +dep`]); diff --git a/tests/cases/fourslash/server/autoImportCrossProject_symlinks_toDist.ts b/tests/cases/fourslash/server/autoImportCrossProject_symlinks_toDist.ts new file mode 100644 index 0000000000000..1b7d911227f18 --- /dev/null +++ b/tests/cases/fourslash/server/autoImportCrossProject_symlinks_toDist.ts @@ -0,0 +1,43 @@ +/// + +// @Filename: /packages/app/package.json +//// { "name": "app", "dependencies": { "dep": "*" } } + +// @Filename: /packages/app/tsconfig.json +//// { +//// "compilerOptions": { +//// "module": "commonjs", +//// "outDir": "dist", +//// "rootDir": "src", +//// "baseUrl": ".", +//// "paths": { +//// "dep/dist/*": ["../dep/src/*"] +//// } +//// } +//// "references": [{ "path": "../dep" }] +//// } + +// @Filename: /packages/app/src/index.ts +//// dep/**/ + + +// @Filename: /packages/dep/package.json +//// { "name": "dep", "main": "dist/index.js", "types": "dist/index.d.ts" } + +// @Filename: /packages/dep/tsconfig.json +//// { +//// "compilerOptions": { "outDir": "dist", "rootDir": "src", "module": "commonjs" } +//// } + +// @Filename: /packages/dep/src/index.ts +//// import "./sub/folder"; + +// @Filename: /packages/dep/src/sub/folder/index.ts +//// export const dep = 0; + +// @link: /packages/dep -> /packages/app/node_modules/dep + +goTo.marker(""); +verify.importFixAtPosition([`import { dep } from "dep/dist/sub/folder";\r +\r +dep`]); diff --git a/tests/cases/fourslash/server/autoImportCrossProject_symlinks_toSrc.ts b/tests/cases/fourslash/server/autoImportCrossProject_symlinks_toSrc.ts new file mode 100644 index 0000000000000..8c834854c572e --- /dev/null +++ b/tests/cases/fourslash/server/autoImportCrossProject_symlinks_toSrc.ts @@ -0,0 +1,40 @@ +/// + +// @Filename: /packages/app/package.json +//// { "name": "app", "dependencies": { "dep": "*" } } + +// @Filename: /packages/app/tsconfig.json +//// { +//// "compilerOptions": { +//// "module": "commonjs", +//// "outDir": "dist", +//// "rootDir": "src", +//// "baseUrl": "." +//// } +//// "references": [{ "path": "../dep" }] +//// } + +// @Filename: /packages/app/src/index.ts +//// dep/**/ + + +// @Filename: /packages/dep/package.json +//// { "name": "dep", "main": "dist/index.js", "types": "dist/index.d.ts" } + +// @Filename: /packages/dep/tsconfig.json +//// { +//// "compilerOptions": { "outDir": "dist", "rootDir": "src", "module": "commonjs" } +//// } + +// @Filename: /packages/dep/src/index.ts +//// import "./sub/folder"; + +// @Filename: /packages/dep/src/sub/folder/index.ts +//// export const dep = 0; + +// @link: /packages/dep -> /packages/app/node_modules/dep + +goTo.marker(""); +verify.importFixAtPosition([`import { dep } from "dep/src/sub/folder";\r +\r +dep`]);