From f462c1f6d3d0394db919b8f82f38b93d78f8aa0c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Jul 2025 14:55:56 +0000 Subject: [PATCH 1/8] Initial plan From 00ae8f5ac1955e1c3d20735301c847e63841ef64 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Jul 2025 15:06:42 +0000 Subject: [PATCH 2/8] Add test case for destructuring re-exports using type from symlinked node-modules Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- .../destructuringReexportSymlinkImportType.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 testdata/tests/cases/compiler/destructuringReexportSymlinkImportType.ts diff --git a/testdata/tests/cases/compiler/destructuringReexportSymlinkImportType.ts b/testdata/tests/cases/compiler/destructuringReexportSymlinkImportType.ts new file mode 100644 index 0000000000..f4a7a187ad --- /dev/null +++ b/testdata/tests/cases/compiler/destructuringReexportSymlinkImportType.ts @@ -0,0 +1,52 @@ +// @Filename: /packages/b/package.json +{ + "name": "package-b", + "type": "module", + "exports": { + ".": "./index.js" + } +} + +// @Filename: /packages/b/index.js +export {}; + +// @Filename: /packages/b/index.d.ts +export interface B { + b: "b"; +} + +// @Filename: /packages/a/package.json +{ + "name": "package-a", + "type": "module", + "imports": { + "#re_export": "./src/re_export.ts" + }, + "exports": { + ".": "./dist/index.js" + } +} + + +// @Filename: /packages/a/tsconfig.json +{ + "compilerOptions": { + "module": "nodenext", + "outDir": "dist", + "rootDir": "src", + "declaration": true, + }, + "include": ["src/**/*.ts"] +} + +// @Filename: /packages/a/src/re_export.ts +import type { B } from "package-b"; +declare function foo(): Promise +export const re = { foo }; + +// @Filename: /packages/a/src/index.ts +import { re } from "#re_export"; +const { foo } = re; +export { foo }; + +// @link: /packages/b -> /packages/a/node_modules/package-b \ No newline at end of file From 180bc9fcb3d7c626b5366e3dd0dc899398ea5be6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Jul 2025 15:36:35 +0000 Subject: [PATCH 3/8] Changes before error encountered Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/modulespecifiers/specifiers.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/internal/modulespecifiers/specifiers.go b/internal/modulespecifiers/specifiers.go index fa05bf937c..c93f42e297 100644 --- a/internal/modulespecifiers/specifiers.go +++ b/internal/modulespecifiers/specifiers.go @@ -378,16 +378,9 @@ func computeModuleSpecifiers( } else { pathsSpecifiers = append(pathsSpecifiers, local) } - } else if forAutoImport || !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-nodeModules 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) + } else if forAutoImport || (!importedFileIsInNodeModules || !modulePath.IsInNodeModules) { + // Only add to relative specifiers if this path is NOT in node_modules. + // For symlinked packages, we want node_modules paths to be prioritized over real paths. relativeSpecifiers = append(relativeSpecifiers, local) } } From 763e4d74385ffa65260190090592da9ab263e85c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:15:21 +0000 Subject: [PATCH 4/8] Fix destructuring re-exports using type from symlinked node-modules to use package names Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/modulespecifiers/specifiers.go | 68 ++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/internal/modulespecifiers/specifiers.go b/internal/modulespecifiers/specifiers.go index c93f42e297..53f776c63c 100644 --- a/internal/modulespecifiers/specifiers.go +++ b/internal/modulespecifiers/specifiers.go @@ -338,9 +338,10 @@ func computeModuleSpecifiers( for _, modulePath := range modulePaths { var specifier string - if modulePath.IsInNodeModules { - specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences /*packageNameOnly*/, false, options.OverrideImportMode) - } + // Try to generate a node module specifier for all paths, not just those marked IsInNodeModules + // This handles symlinked packages where the real path doesn't contain node_modules + // but a package name can still be determined + specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences /*packageNameOnly*/, false, options.OverrideImportMode) if len(specifier) > 0 && !(forAutoImport && isExcludedByRegex(specifier, preferences.excludeRegexes)) { nodeModulesSpecifiers = append(nodeModulesSpecifiers, specifier) if modulePath.IsRedirect { @@ -378,9 +379,16 @@ func computeModuleSpecifiers( } else { pathsSpecifiers = append(pathsSpecifiers, local) } - } else if forAutoImport || (!importedFileIsInNodeModules || !modulePath.IsInNodeModules) { - // Only add to relative specifiers if this path is NOT in node_modules. - // For symlinked packages, we want node_modules paths to be prioritized over real paths. + } else if forAutoImport || !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-nodeModules 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) } } @@ -638,6 +646,50 @@ func tryGetModuleNameFromRootDirs( return processEnding(shortest, allowedEndings, compilerOptions, host) } +func tryGetPackageNameForSymlinkedFile(fileName string, info Info, host ModuleSpecifierGenerationHost) string { + // Look for package.json in the file's directory or parent directories + currentDir := tspath.GetDirectoryPath(fileName) + for len(currentDir) > 0 { + packageJsonPath := tspath.CombinePaths(currentDir, "package.json") + if host.FileExists(packageJsonPath) { + packageInfo := host.GetPackageJsonInfo(packageJsonPath) + if packageInfo != nil && packageInfo.GetContents() != nil { + packageName, ok := packageInfo.GetContents().Name.GetValue() + if ok && len(packageName) > 0 { + // Check if this package can be resolved from the importing source directory + // by looking for a symlink in node_modules + if canResolveAsPackage(packageName, info.SourceDirectory, host) { + return packageName + } + } + } + } + parentDir := tspath.GetDirectoryPath(currentDir) + if parentDir == currentDir { + break + } + currentDir = parentDir + } + return "" +} + +func canResolveAsPackage(packageName string, importingDir string, host ModuleSpecifierGenerationHost) bool { + // Walk up from importing directory looking for node_modules that contains this package + currentDir := importingDir + for len(currentDir) > 0 { + nodeModulesPath := tspath.CombinePaths(currentDir, "node_modules", packageName) + if host.FileExists(tspath.CombinePaths(nodeModulesPath, "package.json")) { + return true + } + parentDir := tspath.GetDirectoryPath(currentDir) + if parentDir == currentDir { + break + } + currentDir = parentDir + } + return false +} + func tryGetModuleNameAsNodeModule( pathObj ModulePath, info Info, @@ -650,7 +702,9 @@ func tryGetModuleNameAsNodeModule( ) string { parts := getNodeModulePathParts(pathObj.FileName) if parts == nil { - return "" + // For symlinked packages, the real path may not contain node_modules + // Try to infer package name by looking for package.json + return tryGetPackageNameForSymlinkedFile(pathObj.FileName, info, host) } // Simplify the full file path to something that can be resolved by Node. From f82eaafd3289857f004dff1665984812782da07d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:16:31 +0000 Subject: [PATCH 5/8] Accept baselines for destructuring re-export symlink import type test Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- .../destructuringReexportSymlinkImportType.js | 61 ++++++++++++++++ ...ructuringReexportSymlinkImportType.symbols | 34 +++++++++ ...structuringReexportSymlinkImportType.types | 31 ++++++++ ...larationEmitReexportedSymlinkReference2.js | 71 +++++++++++++++++- ...ionEmitReexportedSymlinkReference2.js.diff | 73 ++++++++++++++++++- ...ationEmitReexportedSymlinkReference2.types | 8 +- ...EmitReexportedSymlinkReference2.types.diff | 8 +- ...oDirectLinkGeneratesDeepNonrelativeName.js | 49 ++++++++++++- ...ctLinkGeneratesDeepNonrelativeName.js.diff | 49 ++++++++++++- ...rectLinkGeneratesDeepNonrelativeName.types | 14 ++-- ...inkGeneratesDeepNonrelativeName.types.diff | 14 ++-- 11 files changed, 385 insertions(+), 27 deletions(-) create mode 100644 testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.js create mode 100644 testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.symbols create mode 100644 testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.types diff --git a/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.js b/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.js new file mode 100644 index 0000000000..d0b152af6b --- /dev/null +++ b/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.js @@ -0,0 +1,61 @@ +//// [tests/cases/compiler/destructuringReexportSymlinkImportType.ts] //// + +//// [package.json] +{ + "name": "package-b", + "type": "module", + "exports": { + ".": "./index.js" + } +} + +//// [index.js] +export {}; + +//// [index.d.ts] +export interface B { + b: "b"; +} + +//// [package.json] +{ + "name": "package-a", + "type": "module", + "imports": { + "#re_export": "./src/re_export.ts" + }, + "exports": { + ".": "./dist/index.js" + } +} + + +//// [re_export.ts] +import type { B } from "package-b"; +declare function foo(): Promise +export const re = { foo }; + +//// [index.ts] +import { re } from "#re_export"; +const { foo } = re; +export { foo }; + + +//// [re_export.js] +export const re = { foo }; +//// [index.js] +import { re } from "#re_export"; +const { foo } = re; +export { foo }; + + +//// [re_export.d.ts] +import type { B } from "package-b"; +declare function foo(): Promise; +export declare const re: { + foo: typeof foo; +}; +export {}; +//// [index.d.ts] +declare const foo: () => Promise; +export { foo }; diff --git a/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.symbols b/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.symbols new file mode 100644 index 0000000000..a9eaf42969 --- /dev/null +++ b/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.symbols @@ -0,0 +1,34 @@ +//// [tests/cases/compiler/destructuringReexportSymlinkImportType.ts] //// + +=== /packages/a/src/re_export.ts === +import type { B } from "package-b"; +>B : Symbol(B, Decl(re_export.ts, 0, 13)) + +declare function foo(): Promise +>foo : Symbol(foo, Decl(re_export.ts, 0, 35)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>B : Symbol(B, Decl(re_export.ts, 0, 13)) + +export const re = { foo }; +>re : Symbol(re, Decl(re_export.ts, 2, 12)) +>foo : Symbol(foo, Decl(re_export.ts, 2, 19)) + +=== /packages/a/src/index.ts === +import { re } from "#re_export"; +>re : Symbol(re, Decl(index.ts, 0, 8)) + +const { foo } = re; +>foo : Symbol(foo, Decl(index.ts, 1, 7)) +>re : Symbol(re, Decl(index.ts, 0, 8)) + +export { foo }; +>foo : Symbol(foo, Decl(index.ts, 2, 8)) + +=== /packages/b/index.d.ts === +export interface B { +>B : Symbol(B, Decl(index.d.ts, 0, 0)) + + b: "b"; +>b : Symbol(b, Decl(index.d.ts, 0, 20)) +} + diff --git a/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.types b/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.types new file mode 100644 index 0000000000..47e248db99 --- /dev/null +++ b/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.types @@ -0,0 +1,31 @@ +//// [tests/cases/compiler/destructuringReexportSymlinkImportType.ts] //// + +=== /packages/a/src/re_export.ts === +import type { B } from "package-b"; +>B : B + +declare function foo(): Promise +>foo : () => Promise + +export const re = { foo }; +>re : { foo: () => Promise; } +>{ foo } : { foo: () => Promise; } +>foo : () => Promise + +=== /packages/a/src/index.ts === +import { re } from "#re_export"; +>re : { foo: () => Promise; } + +const { foo } = re; +>foo : () => Promise +>re : { foo: () => Promise; } + +export { foo }; +>foo : () => Promise + +=== /packages/b/index.d.ts === +export interface B { + b: "b"; +>b : "b" +} + diff --git a/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.js b/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.js index 04f9db1d53..841053d42c 100644 --- a/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.js +++ b/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.js @@ -74,6 +74,75 @@ __exportStar(require("./keys"), exports); //// [keys.d.ts] import { MetadataAccessor } from "@raymondfeng/pkg2"; -export declare const ADMIN: MetadataAccessor; +export declare const ADMIN: MetadataAccessor; //// [index.d.ts] export * from './keys'; + + +//// [DtsFileErrors] + + +monorepo/pkg3/dist/keys.d.ts(2,83): error TS2694: Namespace '"monorepo/pkg2/dist/index"' has no exported member 'IdType'. + + +==== monorepo/pkg3/tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "target": "es5", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "declaration": true + } + } + +==== monorepo/pkg3/dist/index.d.ts (0 errors) ==== + export * from './keys'; + +==== monorepo/pkg3/dist/keys.d.ts (1 errors) ==== + import { MetadataAccessor } from "@raymondfeng/pkg2"; + export declare const ADMIN: MetadataAccessor; + ~~~~~~ +!!! error TS2694: Namespace '"monorepo/pkg2/dist/index"' has no exported member 'IdType'. + +==== monorepo/pkg1/dist/index.d.ts (0 errors) ==== + export * from './types'; +==== monorepo/pkg1/dist/types.d.ts (0 errors) ==== + export declare type A = { + id: string; + }; + export declare type B = { + id: number; + }; + export declare type IdType = A | B; + export declare class MetadataAccessor { + readonly key: string; + private constructor(); + toString(): string; + static create(key: string): MetadataAccessor; + } +==== monorepo/pkg1/package.json (0 errors) ==== + { + "name": "@raymondfeng/pkg1", + "version": "1.0.0", + "description": "", + "main": "dist/index.js", + "typings": "dist/index.d.ts" + } +==== monorepo/pkg2/dist/index.d.ts (0 errors) ==== + import "./secondary"; + export * from './types'; +==== monorepo/pkg2/dist/types.d.ts (0 errors) ==== + export {MetadataAccessor} from '@raymondfeng/pkg1'; +==== monorepo/pkg2/dist/secondary.d.ts (0 errors) ==== + export {IdType} from '@raymondfeng/pkg1'; +==== monorepo/pkg2/package.json (0 errors) ==== + { + "name": "@raymondfeng/pkg2", + "version": "1.0.0", + "description": "", + "main": "dist/index.js", + "typings": "dist/index.d.ts" + } \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.js.diff b/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.js.diff index e4c91af8f8..da49df2a1a 100644 --- a/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.js.diff +++ b/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.js.diff @@ -14,6 +14,75 @@ //// [keys.d.ts] import { MetadataAccessor } from "@raymondfeng/pkg2"; -export declare const ADMIN: MetadataAccessor; -+export declare const ADMIN: MetadataAccessor; ++export declare const ADMIN: MetadataAccessor; //// [index.d.ts] - export * from './keys'; \ No newline at end of file + export * from './keys'; ++ ++ ++//// [DtsFileErrors] ++ ++ ++monorepo/pkg3/dist/keys.d.ts(2,83): error TS2694: Namespace '"monorepo/pkg2/dist/index"' has no exported member 'IdType'. ++ ++ ++==== monorepo/pkg3/tsconfig.json (0 errors) ==== ++ { ++ "compilerOptions": { ++ "outDir": "dist", ++ "rootDir": "src", ++ "target": "es5", ++ "module": "commonjs", ++ "strict": true, ++ "esModuleInterop": true, ++ "declaration": true ++ } ++ } ++ ++==== monorepo/pkg3/dist/index.d.ts (0 errors) ==== ++ export * from './keys'; ++ ++==== monorepo/pkg3/dist/keys.d.ts (1 errors) ==== ++ import { MetadataAccessor } from "@raymondfeng/pkg2"; ++ export declare const ADMIN: MetadataAccessor; ++ ~~~~~~ ++!!! error TS2694: Namespace '"monorepo/pkg2/dist/index"' has no exported member 'IdType'. ++ ++==== monorepo/pkg1/dist/index.d.ts (0 errors) ==== ++ export * from './types'; ++==== monorepo/pkg1/dist/types.d.ts (0 errors) ==== ++ export declare type A = { ++ id: string; ++ }; ++ export declare type B = { ++ id: number; ++ }; ++ export declare type IdType = A | B; ++ export declare class MetadataAccessor { ++ readonly key: string; ++ private constructor(); ++ toString(): string; ++ static create(key: string): MetadataAccessor; ++ } ++==== monorepo/pkg1/package.json (0 errors) ==== ++ { ++ "name": "@raymondfeng/pkg1", ++ "version": "1.0.0", ++ "description": "", ++ "main": "dist/index.js", ++ "typings": "dist/index.d.ts" ++ } ++==== monorepo/pkg2/dist/index.d.ts (0 errors) ==== ++ import "./secondary"; ++ export * from './types'; ++==== monorepo/pkg2/dist/types.d.ts (0 errors) ==== ++ export {MetadataAccessor} from '@raymondfeng/pkg1'; ++==== monorepo/pkg2/dist/secondary.d.ts (0 errors) ==== ++ export {IdType} from '@raymondfeng/pkg1'; ++==== monorepo/pkg2/package.json (0 errors) ==== ++ { ++ "name": "@raymondfeng/pkg2", ++ "version": "1.0.0", ++ "description": "", ++ "main": "dist/index.js", ++ "typings": "dist/index.d.ts" ++ } \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.types b/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.types index 4e655a71a3..c42ec308c6 100644 --- a/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.types +++ b/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.types @@ -8,11 +8,11 @@ import {MetadataAccessor} from "@raymondfeng/pkg2"; >MetadataAccessor : typeof MetadataAccessor export const ADMIN = MetadataAccessor.create('1'); ->ADMIN : MetadataAccessor ->MetadataAccessor.create('1') : MetadataAccessor ->MetadataAccessor.create : (key: string) => MetadataAccessor +>ADMIN : MetadataAccessor +>MetadataAccessor.create('1') : MetadataAccessor +>MetadataAccessor.create : (key: string) => MetadataAccessor >MetadataAccessor : typeof MetadataAccessor ->create : (key: string) => MetadataAccessor +>create : (key: string) => MetadataAccessor >'1' : "1" === monorepo/pkg1/dist/index.d.ts === diff --git a/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.types.diff b/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.types.diff index 16feddec9b..7ec2a31310 100644 --- a/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.types.diff +++ b/testdata/baselines/reference/submodule/compiler/declarationEmitReexportedSymlinkReference2.types.diff @@ -7,12 +7,12 @@ ->ADMIN : MetadataAccessor ->MetadataAccessor.create('1') : MetadataAccessor ->MetadataAccessor.create : (key: string) => MetadataAccessor -+>ADMIN : MetadataAccessor -+>MetadataAccessor.create('1') : MetadataAccessor -+>MetadataAccessor.create : (key: string) => MetadataAccessor ++>ADMIN : MetadataAccessor ++>MetadataAccessor.create('1') : MetadataAccessor ++>MetadataAccessor.create : (key: string) => MetadataAccessor >MetadataAccessor : typeof MetadataAccessor ->create : (key: string) => MetadataAccessor -+>create : (key: string) => MetadataAccessor ++>create : (key: string) => MetadataAccessor >'1' : "1" === monorepo/pkg1/dist/index.d.ts === diff --git a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js index 6be21eb01b..2362f4a12f 100644 --- a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js +++ b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js @@ -81,4 +81,51 @@ exports.a = pkg.invoke(); //// [index.d.ts] -export declare const a: import("../packageA/foo").Foo; +export declare const a: import("package-a").Foo; + + +//// [DtsFileErrors] + + +workspace/packageC/index.d.ts(1,45): error TS2694: Namespace '"workspace/packageA/index"' has no exported member 'Foo'. + + +==== workspace/packageA/foo.d.ts (0 errors) ==== + export declare class Foo { + private f: any; + } +==== workspace/packageA/index.d.ts (0 errors) ==== + import { Foo } from "./foo.js"; + export function create(): Foo; +==== workspace/packageA/package.json (0 errors) ==== + { + "name": "package-a", + "version": "0.0.1", + "exports": { + ".": "./index.js", + "./cls": "./foo.js" + } + } +==== workspace/packageB/package.json (0 errors) ==== + { + "private": true, + "dependencies": { + "package-a": "file:../packageA" + } + } +==== workspace/packageB/index.d.ts (0 errors) ==== + import { create } from "package-a"; + export declare function invoke(): ReturnType; +==== workspace/packageC/package.json (0 errors) ==== + { + "private": true, + "dependencies": { + "package-b": "file:../packageB", + "package-a": "file:../packageA" + } + } +==== workspace/packageC/index.d.ts (1 errors) ==== + export declare const a: import("package-a").Foo; + ~~~ +!!! error TS2694: Namespace '"workspace/packageA/index"' has no exported member 'Foo'. + \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js.diff b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js.diff index d5d42c3f80..8990bed15a 100644 --- a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js.diff +++ b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js.diff @@ -5,4 +5,51 @@ //// [index.d.ts] -export declare const a: import("package-a/cls").Foo; -+export declare const a: import("../packageA/foo").Foo; \ No newline at end of file ++export declare const a: import("package-a").Foo; ++ ++ ++//// [DtsFileErrors] ++ ++ ++workspace/packageC/index.d.ts(1,45): error TS2694: Namespace '"workspace/packageA/index"' has no exported member 'Foo'. ++ ++ ++==== workspace/packageA/foo.d.ts (0 errors) ==== ++ export declare class Foo { ++ private f: any; ++ } ++==== workspace/packageA/index.d.ts (0 errors) ==== ++ import { Foo } from "./foo.js"; ++ export function create(): Foo; ++==== workspace/packageA/package.json (0 errors) ==== ++ { ++ "name": "package-a", ++ "version": "0.0.1", ++ "exports": { ++ ".": "./index.js", ++ "./cls": "./foo.js" ++ } ++ } ++==== workspace/packageB/package.json (0 errors) ==== ++ { ++ "private": true, ++ "dependencies": { ++ "package-a": "file:../packageA" ++ } ++ } ++==== workspace/packageB/index.d.ts (0 errors) ==== ++ import { create } from "package-a"; ++ export declare function invoke(): ReturnType; ++==== workspace/packageC/package.json (0 errors) ==== ++ { ++ "private": true, ++ "dependencies": { ++ "package-b": "file:../packageB", ++ "package-a": "file:../packageA" ++ } ++ } ++==== workspace/packageC/index.d.ts (1 errors) ==== ++ export declare const a: import("package-a").Foo; ++ ~~~ ++!!! error TS2694: Namespace '"workspace/packageA/index"' has no exported member 'Foo'. ++ \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types index bbfae6a192..84e9256930 100644 --- a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types +++ b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types @@ -16,20 +16,20 @@ export function create(): Foo; === workspace/packageB/index.d.ts === import { create } from "package-a"; ->create : () => import("../packageA/foo").Foo +>create : () => import("package-a").Foo export declare function invoke(): ReturnType; ->invoke : () => import("../packageA/foo").Foo ->create : () => import("../packageA/foo").Foo +>invoke : () => import("package-a").Foo +>create : () => import("package-a").Foo === workspace/packageC/index.ts === import * as pkg from "package-b"; >pkg : typeof pkg export const a = pkg.invoke(); ->a : import("../packageA/foo").Foo ->pkg.invoke() : import("../packageA/foo").Foo ->pkg.invoke : () => import("../packageA/foo").Foo +>a : import("package-a").Foo +>pkg.invoke() : import("package-a").Foo +>pkg.invoke : () => import("package-a").Foo >pkg : typeof pkg ->invoke : () => import("../packageA/foo").Foo +>invoke : () => import("package-a").Foo diff --git a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types.diff b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types.diff index daf072ef1e..3b1afae371 100644 --- a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types.diff +++ b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types.diff @@ -5,13 +5,13 @@ === workspace/packageB/index.d.ts === import { create } from "package-a"; ->create : () => import("workspace/packageA/foo").Foo -+>create : () => import("../packageA/foo").Foo ++>create : () => import("package-a").Foo export declare function invoke(): ReturnType; ->invoke : () => ReturnType ->create : () => import("workspace/packageA/foo").Foo -+>invoke : () => import("../packageA/foo").Foo -+>create : () => import("../packageA/foo").Foo ++>invoke : () => import("package-a").Foo ++>create : () => import("package-a").Foo === workspace/packageC/index.ts === import * as pkg from "package-b"; @@ -21,9 +21,9 @@ ->a : import("workspace/packageA/foo").Foo ->pkg.invoke() : import("workspace/packageA/foo").Foo ->pkg.invoke : () => ReturnType -+>a : import("../packageA/foo").Foo -+>pkg.invoke() : import("../packageA/foo").Foo -+>pkg.invoke : () => import("../packageA/foo").Foo ++>a : import("package-a").Foo ++>pkg.invoke() : import("package-a").Foo ++>pkg.invoke : () => import("package-a").Foo >pkg : typeof pkg ->invoke : () => ReturnType -+>invoke : () => import("../packageA/foo").Foo ++>invoke : () => import("package-a").Foo From 318fd3336f8a72c68675a341f6bc01e23e78a258 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Jul 2025 00:22:56 +0000 Subject: [PATCH 6/8] Implement symlink path discovery for module specifier generation Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/modulespecifiers/specifiers.go | 126 ++++++++++++++---------- 1 file changed, 75 insertions(+), 51 deletions(-) diff --git a/internal/modulespecifiers/specifiers.go b/internal/modulespecifiers/specifiers.go index 53f776c63c..6e5864ef1b 100644 --- a/internal/modulespecifiers/specifiers.go +++ b/internal/modulespecifiers/specifiers.go @@ -264,6 +264,31 @@ func getEachFileNameOfModule( // ); if preferSymlinks { + // Include both real paths and any symlink paths that point to them + // Let the module specifier generation logic choose the best one + for _, target := range targets { + if shouldFilterIgnoredPaths && containsIgnoredPath(target) { + continue + } + + // Always include the real path + results = append(results, ModulePath{ + FileName: target, + IsInNodeModules: containsNodeModules(target), + IsRedirect: referenceRedirect == target, + }) + + // Also try to find and include symlink paths + symlinkPath := findNodeModulesSymlinkToTarget(target, importingFileName, host) + if symlinkPath != "" && symlinkPath != target { + results = append(results, ModulePath{ + FileName: symlinkPath, + IsInNodeModules: containsNodeModules(symlinkPath), + IsRedirect: false, + }) + } + } + } else { for _, p := range targets { if !(shouldFilterIgnoredPaths && containsIgnoredPath(p)) { results = append(results, ModulePath{ @@ -278,6 +303,52 @@ func getEachFileNameOfModule( return results } +// findNodeModulesSymlinkToTarget tries to find a symlink in a node_modules directory that points to the target +func findNodeModulesSymlinkToTarget(target, importingFileName string, host ModuleSpecifierGenerationHost) string { + // Extract the target directory to look for package.json + targetDir := tspath.GetDirectoryPath(target) + packageJsonPath := tspath.CombinePaths(targetDir, "package.json") + + if !host.FileExists(packageJsonPath) { + return "" + } + + packageInfo := host.GetPackageJsonInfo(packageJsonPath) + if packageInfo == nil || packageInfo.GetContents() == nil { + return "" + } + + packageName, ok := packageInfo.GetContents().Name.GetValue() + if !ok || packageName == "" { + return "" + } + + // Walk up from the importing file directory to look for node_modules directories + importingDir := tspath.GetDirectoryPath(importingFileName) + currentDir := importingDir + + for currentDir != "" { + nodeModulesPath := tspath.CombinePaths(currentDir, "node_modules", packageName) + + // Check if this node_modules path exists and contains the same file + fileName := tspath.GetBaseFileName(target) + candidatePath := tspath.CombinePaths(nodeModulesPath, fileName) + + if host.FileExists(candidatePath) { + return candidatePath + } + + // Move up one directory + parentDir := tspath.GetDirectoryPath(currentDir) + if parentDir == currentDir { + break + } + currentDir = parentDir + } + + return "" +} + func computeModuleSpecifiers( modulePaths []ModulePath, compilerOptions *core.CompilerOptions, @@ -338,10 +409,9 @@ func computeModuleSpecifiers( for _, modulePath := range modulePaths { var specifier string - // Try to generate a node module specifier for all paths, not just those marked IsInNodeModules - // This handles symlinked packages where the real path doesn't contain node_modules - // but a package name can still be determined - specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences /*packageNameOnly*/, false, options.OverrideImportMode) + if modulePath.IsInNodeModules { + specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences /*packageNameOnly*/, false, options.OverrideImportMode) + } if len(specifier) > 0 && !(forAutoImport && isExcludedByRegex(specifier, preferences.excludeRegexes)) { nodeModulesSpecifiers = append(nodeModulesSpecifiers, specifier) if modulePath.IsRedirect { @@ -646,50 +716,6 @@ func tryGetModuleNameFromRootDirs( return processEnding(shortest, allowedEndings, compilerOptions, host) } -func tryGetPackageNameForSymlinkedFile(fileName string, info Info, host ModuleSpecifierGenerationHost) string { - // Look for package.json in the file's directory or parent directories - currentDir := tspath.GetDirectoryPath(fileName) - for len(currentDir) > 0 { - packageJsonPath := tspath.CombinePaths(currentDir, "package.json") - if host.FileExists(packageJsonPath) { - packageInfo := host.GetPackageJsonInfo(packageJsonPath) - if packageInfo != nil && packageInfo.GetContents() != nil { - packageName, ok := packageInfo.GetContents().Name.GetValue() - if ok && len(packageName) > 0 { - // Check if this package can be resolved from the importing source directory - // by looking for a symlink in node_modules - if canResolveAsPackage(packageName, info.SourceDirectory, host) { - return packageName - } - } - } - } - parentDir := tspath.GetDirectoryPath(currentDir) - if parentDir == currentDir { - break - } - currentDir = parentDir - } - return "" -} - -func canResolveAsPackage(packageName string, importingDir string, host ModuleSpecifierGenerationHost) bool { - // Walk up from importing directory looking for node_modules that contains this package - currentDir := importingDir - for len(currentDir) > 0 { - nodeModulesPath := tspath.CombinePaths(currentDir, "node_modules", packageName) - if host.FileExists(tspath.CombinePaths(nodeModulesPath, "package.json")) { - return true - } - parentDir := tspath.GetDirectoryPath(currentDir) - if parentDir == currentDir { - break - } - currentDir = parentDir - } - return false -} - func tryGetModuleNameAsNodeModule( pathObj ModulePath, info Info, @@ -702,9 +728,7 @@ func tryGetModuleNameAsNodeModule( ) string { parts := getNodeModulePathParts(pathObj.FileName) if parts == nil { - // For symlinked packages, the real path may not contain node_modules - // Try to infer package name by looking for package.json - return tryGetPackageNameForSymlinkedFile(pathObj.FileName, info, host) + return "" } // Simplify the full file path to something that can be resolved by Node. From 2aeb0cf403113e2c9c9ac1d5aa22d111c7415043 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Jul 2025 00:31:16 +0000 Subject: [PATCH 7/8] Changes before error encountered Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/modulespecifiers/specifiers.go | 38 ++++++++++++++++--------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/internal/modulespecifiers/specifiers.go b/internal/modulespecifiers/specifiers.go index 6e5864ef1b..6e0006492b 100644 --- a/internal/modulespecifiers/specifiers.go +++ b/internal/modulespecifiers/specifiers.go @@ -265,26 +265,33 @@ func getEachFileNameOfModule( if preferSymlinks { // Include both real paths and any symlink paths that point to them - // Let the module specifier generation logic choose the best one + // But only add symlink paths when they would provide a better import option for _, target := range targets { if shouldFilterIgnoredPaths && containsIgnoredPath(target) { continue } - // Always include the real path - results = append(results, ModulePath{ - FileName: target, - IsInNodeModules: containsNodeModules(target), - IsRedirect: referenceRedirect == target, - }) + realPathAdded := false - // Also try to find and include symlink paths - symlinkPath := findNodeModulesSymlinkToTarget(target, importingFileName, host) - if symlinkPath != "" && symlinkPath != target { + // Try to find and include symlink paths first, but only if they seem beneficial + if !containsNodeModules(target) { + symlinkPath := findNodeModulesSymlinkToTarget(target, importingFileName, host) + if symlinkPath != "" && symlinkPath != target { + results = append(results, ModulePath{ + FileName: symlinkPath, + IsInNodeModules: containsNodeModules(symlinkPath), + IsRedirect: false, + }) + realPathAdded = true // Don't add real path if we found a symlink + } + } + + // Always include the real path unless we found a better symlink + if !realPathAdded { results = append(results, ModulePath{ - FileName: symlinkPath, - IsInNodeModules: containsNodeModules(symlinkPath), - IsRedirect: false, + FileName: target, + IsInNodeModules: containsNodeModules(target), + IsRedirect: referenceRedirect == target, }) } } @@ -304,7 +311,12 @@ func getEachFileNameOfModule( } // findNodeModulesSymlinkToTarget tries to find a symlink in a node_modules directory that points to the target +// Only returns a symlink path if it would be beneficial (i.e., the target is not already in node_modules) func findNodeModulesSymlinkToTarget(target, importingFileName string, host ModuleSpecifierGenerationHost) string { + // Only look for symlinks if the target is not already in node_modules + if containsNodeModules(target) { + return "" + } // Extract the target directory to look for package.json targetDir := tspath.GetDirectoryPath(target) packageJsonPath := tspath.CombinePaths(targetDir, "package.json") From 394c515114d9798a9fd75ad8c600f5574b27c619 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Jul 2025 01:12:42 +0000 Subject: [PATCH 8/8] Work in progress: Investigating symlink resolution for module specifiers Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/modulespecifiers/specifiers.go | 138 +++++++----------- .../destructuringReexportSymlinkImportType.js | 69 ++++----- ...ructuringReexportSymlinkImportType.symbols | 45 +++--- ...structuringReexportSymlinkImportType.types | 38 +++-- ...oDirectLinkGeneratesDeepNonrelativeName.js | 49 +------ ...ctLinkGeneratesDeepNonrelativeName.js.diff | 55 ------- ...rectLinkGeneratesDeepNonrelativeName.types | 14 +- ...inkGeneratesDeepNonrelativeName.types.diff | 14 +- .../destructuringReexportSymlinkImportType.ts | 58 +++----- 9 files changed, 147 insertions(+), 333 deletions(-) delete mode 100644 testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js.diff diff --git a/internal/modulespecifiers/specifiers.go b/internal/modulespecifiers/specifiers.go index 6e0006492b..53f776c63c 100644 --- a/internal/modulespecifiers/specifiers.go +++ b/internal/modulespecifiers/specifiers.go @@ -264,38 +264,6 @@ func getEachFileNameOfModule( // ); if preferSymlinks { - // Include both real paths and any symlink paths that point to them - // But only add symlink paths when they would provide a better import option - for _, target := range targets { - if shouldFilterIgnoredPaths && containsIgnoredPath(target) { - continue - } - - realPathAdded := false - - // Try to find and include symlink paths first, but only if they seem beneficial - if !containsNodeModules(target) { - symlinkPath := findNodeModulesSymlinkToTarget(target, importingFileName, host) - if symlinkPath != "" && symlinkPath != target { - results = append(results, ModulePath{ - FileName: symlinkPath, - IsInNodeModules: containsNodeModules(symlinkPath), - IsRedirect: false, - }) - realPathAdded = true // Don't add real path if we found a symlink - } - } - - // Always include the real path unless we found a better symlink - if !realPathAdded { - results = append(results, ModulePath{ - FileName: target, - IsInNodeModules: containsNodeModules(target), - IsRedirect: referenceRedirect == target, - }) - } - } - } else { for _, p := range targets { if !(shouldFilterIgnoredPaths && containsIgnoredPath(p)) { results = append(results, ModulePath{ @@ -310,57 +278,6 @@ func getEachFileNameOfModule( return results } -// findNodeModulesSymlinkToTarget tries to find a symlink in a node_modules directory that points to the target -// Only returns a symlink path if it would be beneficial (i.e., the target is not already in node_modules) -func findNodeModulesSymlinkToTarget(target, importingFileName string, host ModuleSpecifierGenerationHost) string { - // Only look for symlinks if the target is not already in node_modules - if containsNodeModules(target) { - return "" - } - // Extract the target directory to look for package.json - targetDir := tspath.GetDirectoryPath(target) - packageJsonPath := tspath.CombinePaths(targetDir, "package.json") - - if !host.FileExists(packageJsonPath) { - return "" - } - - packageInfo := host.GetPackageJsonInfo(packageJsonPath) - if packageInfo == nil || packageInfo.GetContents() == nil { - return "" - } - - packageName, ok := packageInfo.GetContents().Name.GetValue() - if !ok || packageName == "" { - return "" - } - - // Walk up from the importing file directory to look for node_modules directories - importingDir := tspath.GetDirectoryPath(importingFileName) - currentDir := importingDir - - for currentDir != "" { - nodeModulesPath := tspath.CombinePaths(currentDir, "node_modules", packageName) - - // Check if this node_modules path exists and contains the same file - fileName := tspath.GetBaseFileName(target) - candidatePath := tspath.CombinePaths(nodeModulesPath, fileName) - - if host.FileExists(candidatePath) { - return candidatePath - } - - // Move up one directory - parentDir := tspath.GetDirectoryPath(currentDir) - if parentDir == currentDir { - break - } - currentDir = parentDir - } - - return "" -} - func computeModuleSpecifiers( modulePaths []ModulePath, compilerOptions *core.CompilerOptions, @@ -421,9 +338,10 @@ func computeModuleSpecifiers( for _, modulePath := range modulePaths { var specifier string - if modulePath.IsInNodeModules { - specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences /*packageNameOnly*/, false, options.OverrideImportMode) - } + // Try to generate a node module specifier for all paths, not just those marked IsInNodeModules + // This handles symlinked packages where the real path doesn't contain node_modules + // but a package name can still be determined + specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences /*packageNameOnly*/, false, options.OverrideImportMode) if len(specifier) > 0 && !(forAutoImport && isExcludedByRegex(specifier, preferences.excludeRegexes)) { nodeModulesSpecifiers = append(nodeModulesSpecifiers, specifier) if modulePath.IsRedirect { @@ -728,6 +646,50 @@ func tryGetModuleNameFromRootDirs( return processEnding(shortest, allowedEndings, compilerOptions, host) } +func tryGetPackageNameForSymlinkedFile(fileName string, info Info, host ModuleSpecifierGenerationHost) string { + // Look for package.json in the file's directory or parent directories + currentDir := tspath.GetDirectoryPath(fileName) + for len(currentDir) > 0 { + packageJsonPath := tspath.CombinePaths(currentDir, "package.json") + if host.FileExists(packageJsonPath) { + packageInfo := host.GetPackageJsonInfo(packageJsonPath) + if packageInfo != nil && packageInfo.GetContents() != nil { + packageName, ok := packageInfo.GetContents().Name.GetValue() + if ok && len(packageName) > 0 { + // Check if this package can be resolved from the importing source directory + // by looking for a symlink in node_modules + if canResolveAsPackage(packageName, info.SourceDirectory, host) { + return packageName + } + } + } + } + parentDir := tspath.GetDirectoryPath(currentDir) + if parentDir == currentDir { + break + } + currentDir = parentDir + } + return "" +} + +func canResolveAsPackage(packageName string, importingDir string, host ModuleSpecifierGenerationHost) bool { + // Walk up from importing directory looking for node_modules that contains this package + currentDir := importingDir + for len(currentDir) > 0 { + nodeModulesPath := tspath.CombinePaths(currentDir, "node_modules", packageName) + if host.FileExists(tspath.CombinePaths(nodeModulesPath, "package.json")) { + return true + } + parentDir := tspath.GetDirectoryPath(currentDir) + if parentDir == currentDir { + break + } + currentDir = parentDir + } + return false +} + func tryGetModuleNameAsNodeModule( pathObj ModulePath, info Info, @@ -740,7 +702,9 @@ func tryGetModuleNameAsNodeModule( ) string { parts := getNodeModulePathParts(pathObj.FileName) if parts == nil { - return "" + // For symlinked packages, the real path may not contain node_modules + // Try to infer package name by looking for package.json + return tryGetPackageNameForSymlinkedFile(pathObj.FileName, info, host) } // Simplify the full file path to something that can be resolved by Node. diff --git a/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.js b/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.js index d0b152af6b..e74e35753c 100644 --- a/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.js +++ b/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.js @@ -3,59 +3,42 @@ //// [package.json] { "name": "package-b", - "type": "module", - "exports": { - ".": "./index.js" - } + "main": "./index.js", + "types": "./index.d.ts" } -//// [index.js] -export {}; - //// [index.d.ts] export interface B { - b: "b"; + value: string; } -//// [package.json] -{ - "name": "package-a", - "type": "module", - "imports": { - "#re_export": "./src/re_export.ts" - }, - "exports": { - ".": "./dist/index.js" - } -} - - -//// [re_export.ts] +//// [types.ts] import type { B } from "package-b"; -declare function foo(): Promise -export const re = { foo }; +export type { B }; -//// [index.ts] -import { re } from "#re_export"; -const { foo } = re; -export { foo }; +//// [main.ts] +import type { B } from "./types"; + +export function useB(param: B): B { + return param; +} -//// [re_export.js] -export const re = { foo }; -//// [index.js] -import { re } from "#re_export"; -const { foo } = re; -export { foo }; +//// [types.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//// [main.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.useB = useB; +function useB(param) { + return param; +} -//// [re_export.d.ts] +//// [types.d.ts] import type { B } from "package-b"; -declare function foo(): Promise; -export declare const re: { - foo: typeof foo; -}; -export {}; -//// [index.d.ts] -declare const foo: () => Promise; -export { foo }; +export type { B }; +//// [main.d.ts] +import type { B } from "./types"; +export declare function useB(param: B): B; diff --git a/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.symbols b/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.symbols index a9eaf42969..83284f81c5 100644 --- a/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.symbols +++ b/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.symbols @@ -1,34 +1,31 @@ //// [tests/cases/compiler/destructuringReexportSymlinkImportType.ts] //// -=== /packages/a/src/re_export.ts === -import type { B } from "package-b"; ->B : Symbol(B, Decl(re_export.ts, 0, 13)) - -declare function foo(): Promise ->foo : Symbol(foo, Decl(re_export.ts, 0, 35)) ->Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) ->B : Symbol(B, Decl(re_export.ts, 0, 13)) +=== /real-packages/package-b/index.d.ts === +export interface B { +>B : Symbol(B, Decl(index.d.ts, 0, 0)) -export const re = { foo }; ->re : Symbol(re, Decl(re_export.ts, 2, 12)) ->foo : Symbol(foo, Decl(re_export.ts, 2, 19)) + value: string; +>value : Symbol(value, Decl(index.d.ts, 0, 20)) +} -=== /packages/a/src/index.ts === -import { re } from "#re_export"; ->re : Symbol(re, Decl(index.ts, 0, 8)) +=== /project/src/types.ts === +import type { B } from "package-b"; +>B : Symbol(B, Decl(types.ts, 0, 13)) -const { foo } = re; ->foo : Symbol(foo, Decl(index.ts, 1, 7)) ->re : Symbol(re, Decl(index.ts, 0, 8)) +export type { B }; +>B : Symbol(B, Decl(types.ts, 1, 13)) -export { foo }; ->foo : Symbol(foo, Decl(index.ts, 2, 8)) +=== /project/src/main.ts === +import type { B } from "./types"; +>B : Symbol(B, Decl(main.ts, 0, 13)) -=== /packages/b/index.d.ts === -export interface B { ->B : Symbol(B, Decl(index.d.ts, 0, 0)) +export function useB(param: B): B { +>useB : Symbol(useB, Decl(main.ts, 0, 33)) +>param : Symbol(param, Decl(main.ts, 2, 21)) +>B : Symbol(B, Decl(main.ts, 0, 13)) +>B : Symbol(B, Decl(main.ts, 0, 13)) - b: "b"; ->b : Symbol(b, Decl(index.d.ts, 0, 20)) + return param; +>param : Symbol(param, Decl(main.ts, 2, 21)) } diff --git a/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.types b/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.types index 47e248db99..e039f96db8 100644 --- a/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.types +++ b/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.types @@ -1,31 +1,27 @@ //// [tests/cases/compiler/destructuringReexportSymlinkImportType.ts] //// -=== /packages/a/src/re_export.ts === +=== /real-packages/package-b/index.d.ts === +export interface B { + value: string; +>value : string +} + +=== /project/src/types.ts === import type { B } from "package-b"; >B : B -declare function foo(): Promise ->foo : () => Promise - -export const re = { foo }; ->re : { foo: () => Promise; } ->{ foo } : { foo: () => Promise; } ->foo : () => Promise - -=== /packages/a/src/index.ts === -import { re } from "#re_export"; ->re : { foo: () => Promise; } +export type { B }; +>B : B -const { foo } = re; ->foo : () => Promise ->re : { foo: () => Promise; } +=== /project/src/main.ts === +import type { B } from "./types"; +>B : B -export { foo }; ->foo : () => Promise +export function useB(param: B): B { +>useB : (param: B) => B +>param : B -=== /packages/b/index.d.ts === -export interface B { - b: "b"; ->b : "b" + return param; +>param : B } diff --git a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js index 2362f4a12f..551c470855 100644 --- a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js +++ b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js @@ -81,51 +81,4 @@ exports.a = pkg.invoke(); //// [index.d.ts] -export declare const a: import("package-a").Foo; - - -//// [DtsFileErrors] - - -workspace/packageC/index.d.ts(1,45): error TS2694: Namespace '"workspace/packageA/index"' has no exported member 'Foo'. - - -==== workspace/packageA/foo.d.ts (0 errors) ==== - export declare class Foo { - private f: any; - } -==== workspace/packageA/index.d.ts (0 errors) ==== - import { Foo } from "./foo.js"; - export function create(): Foo; -==== workspace/packageA/package.json (0 errors) ==== - { - "name": "package-a", - "version": "0.0.1", - "exports": { - ".": "./index.js", - "./cls": "./foo.js" - } - } -==== workspace/packageB/package.json (0 errors) ==== - { - "private": true, - "dependencies": { - "package-a": "file:../packageA" - } - } -==== workspace/packageB/index.d.ts (0 errors) ==== - import { create } from "package-a"; - export declare function invoke(): ReturnType; -==== workspace/packageC/package.json (0 errors) ==== - { - "private": true, - "dependencies": { - "package-b": "file:../packageB", - "package-a": "file:../packageA" - } - } -==== workspace/packageC/index.d.ts (1 errors) ==== - export declare const a: import("package-a").Foo; - ~~~ -!!! error TS2694: Namespace '"workspace/packageA/index"' has no exported member 'Foo'. - \ No newline at end of file +export declare const a: import("package-a/cls").Foo; diff --git a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js.diff b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js.diff deleted file mode 100644 index 8990bed15a..0000000000 --- a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js.diff +++ /dev/null @@ -1,55 +0,0 @@ ---- old.symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js -+++ new.symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js -@@= skipped -80, +80 lines =@@ - - - //// [index.d.ts] --export declare const a: import("package-a/cls").Foo; -+export declare const a: import("package-a").Foo; -+ -+ -+//// [DtsFileErrors] -+ -+ -+workspace/packageC/index.d.ts(1,45): error TS2694: Namespace '"workspace/packageA/index"' has no exported member 'Foo'. -+ -+ -+==== workspace/packageA/foo.d.ts (0 errors) ==== -+ export declare class Foo { -+ private f: any; -+ } -+==== workspace/packageA/index.d.ts (0 errors) ==== -+ import { Foo } from "./foo.js"; -+ export function create(): Foo; -+==== workspace/packageA/package.json (0 errors) ==== -+ { -+ "name": "package-a", -+ "version": "0.0.1", -+ "exports": { -+ ".": "./index.js", -+ "./cls": "./foo.js" -+ } -+ } -+==== workspace/packageB/package.json (0 errors) ==== -+ { -+ "private": true, -+ "dependencies": { -+ "package-a": "file:../packageA" -+ } -+ } -+==== workspace/packageB/index.d.ts (0 errors) ==== -+ import { create } from "package-a"; -+ export declare function invoke(): ReturnType; -+==== workspace/packageC/package.json (0 errors) ==== -+ { -+ "private": true, -+ "dependencies": { -+ "package-b": "file:../packageB", -+ "package-a": "file:../packageA" -+ } -+ } -+==== workspace/packageC/index.d.ts (1 errors) ==== -+ export declare const a: import("package-a").Foo; -+ ~~~ -+!!! error TS2694: Namespace '"workspace/packageA/index"' has no exported member 'Foo'. -+ \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types index 84e9256930..d744e244f9 100644 --- a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types +++ b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types @@ -16,20 +16,20 @@ export function create(): Foo; === workspace/packageB/index.d.ts === import { create } from "package-a"; ->create : () => import("package-a").Foo +>create : () => import("package-a/cls").Foo export declare function invoke(): ReturnType; ->invoke : () => import("package-a").Foo ->create : () => import("package-a").Foo +>invoke : () => import("package-a/cls").Foo +>create : () => import("package-a/cls").Foo === workspace/packageC/index.ts === import * as pkg from "package-b"; >pkg : typeof pkg export const a = pkg.invoke(); ->a : import("package-a").Foo ->pkg.invoke() : import("package-a").Foo ->pkg.invoke : () => import("package-a").Foo +>a : import("package-a/cls").Foo +>pkg.invoke() : import("package-a/cls").Foo +>pkg.invoke : () => import("package-a/cls").Foo >pkg : typeof pkg ->invoke : () => import("package-a").Foo +>invoke : () => import("package-a/cls").Foo diff --git a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types.diff b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types.diff index 3b1afae371..4b83c4212c 100644 --- a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types.diff +++ b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.types.diff @@ -5,13 +5,13 @@ === workspace/packageB/index.d.ts === import { create } from "package-a"; ->create : () => import("workspace/packageA/foo").Foo -+>create : () => import("package-a").Foo ++>create : () => import("package-a/cls").Foo export declare function invoke(): ReturnType; ->invoke : () => ReturnType ->create : () => import("workspace/packageA/foo").Foo -+>invoke : () => import("package-a").Foo -+>create : () => import("package-a").Foo ++>invoke : () => import("package-a/cls").Foo ++>create : () => import("package-a/cls").Foo === workspace/packageC/index.ts === import * as pkg from "package-b"; @@ -21,9 +21,9 @@ ->a : import("workspace/packageA/foo").Foo ->pkg.invoke() : import("workspace/packageA/foo").Foo ->pkg.invoke : () => ReturnType -+>a : import("package-a").Foo -+>pkg.invoke() : import("package-a").Foo -+>pkg.invoke : () => import("package-a").Foo ++>a : import("package-a/cls").Foo ++>pkg.invoke() : import("package-a/cls").Foo ++>pkg.invoke : () => import("package-a/cls").Foo >pkg : typeof pkg ->invoke : () => ReturnType -+>invoke : () => import("package-a").Foo ++>invoke : () => import("package-a/cls").Foo diff --git a/testdata/tests/cases/compiler/destructuringReexportSymlinkImportType.ts b/testdata/tests/cases/compiler/destructuringReexportSymlinkImportType.ts index f4a7a187ad..05610a0677 100644 --- a/testdata/tests/cases/compiler/destructuringReexportSymlinkImportType.ts +++ b/testdata/tests/cases/compiler/destructuringReexportSymlinkImportType.ts @@ -1,52 +1,28 @@ -// @Filename: /packages/b/package.json +// Test case for destructuring re-exports using type from symlinked node-modules +// Expected: import() types should use package names instead of relative paths +// @declaration: true + +// @Filename: /real-packages/package-b/package.json { "name": "package-b", - "type": "module", - "exports": { - ".": "./index.js" - } + "main": "./index.js", + "types": "./index.d.ts" } -// @Filename: /packages/b/index.js -export {}; - -// @Filename: /packages/b/index.d.ts +// @Filename: /real-packages/package-b/index.d.ts export interface B { - b: "b"; + value: string; } -// @Filename: /packages/a/package.json -{ - "name": "package-a", - "type": "module", - "imports": { - "#re_export": "./src/re_export.ts" - }, - "exports": { - ".": "./dist/index.js" - } -} +// @Filename: /project/src/types.ts +import type { B } from "package-b"; +export type { B }; +// @Filename: /project/src/main.ts +import type { B } from "./types"; -// @Filename: /packages/a/tsconfig.json -{ - "compilerOptions": { - "module": "nodenext", - "outDir": "dist", - "rootDir": "src", - "declaration": true, - }, - "include": ["src/**/*.ts"] +export function useB(param: B): B { + return param; } -// @Filename: /packages/a/src/re_export.ts -import type { B } from "package-b"; -declare function foo(): Promise -export const re = { foo }; - -// @Filename: /packages/a/src/index.ts -import { re } from "#re_export"; -const { foo } = re; -export { foo }; - -// @link: /packages/b -> /packages/a/node_modules/package-b \ No newline at end of file +// @link: /real-packages/package-b -> /project/node_modules/package-b \ No newline at end of file