diff --git a/internal/modulespecifiers/specifiers.go b/internal/modulespecifiers/specifiers.go index fa05bf937c..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 { @@ -645,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, @@ -657,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 new file mode 100644 index 0000000000..e74e35753c --- /dev/null +++ b/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.js @@ -0,0 +1,44 @@ +//// [tests/cases/compiler/destructuringReexportSymlinkImportType.ts] //// + +//// [package.json] +{ + "name": "package-b", + "main": "./index.js", + "types": "./index.d.ts" +} + +//// [index.d.ts] +export interface B { + value: string; +} + +//// [types.ts] +import type { B } from "package-b"; +export type { B }; + +//// [main.ts] +import type { B } from "./types"; + +export function useB(param: B): B { + return param; +} + + +//// [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; +} + + +//// [types.d.ts] +import type { B } from "package-b"; +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 new file mode 100644 index 0000000000..83284f81c5 --- /dev/null +++ b/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.symbols @@ -0,0 +1,31 @@ +//// [tests/cases/compiler/destructuringReexportSymlinkImportType.ts] //// + +=== /real-packages/package-b/index.d.ts === +export interface B { +>B : Symbol(B, Decl(index.d.ts, 0, 0)) + + value: string; +>value : Symbol(value, Decl(index.d.ts, 0, 20)) +} + +=== /project/src/types.ts === +import type { B } from "package-b"; +>B : Symbol(B, Decl(types.ts, 0, 13)) + +export type { B }; +>B : Symbol(B, Decl(types.ts, 1, 13)) + +=== /project/src/main.ts === +import type { B } from "./types"; +>B : Symbol(B, Decl(main.ts, 0, 13)) + +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)) + + 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 new file mode 100644 index 0000000000..e039f96db8 --- /dev/null +++ b/testdata/baselines/reference/compiler/destructuringReexportSymlinkImportType.types @@ -0,0 +1,27 @@ +//// [tests/cases/compiler/destructuringReexportSymlinkImportType.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 + +export type { B }; +>B : B + +=== /project/src/main.ts === +import type { B } from "./types"; +>B : B + +export function useB(param: B): B { +>useB : (param: B) => B +>param : B + + return param; +>param : 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..551c470855 100644 --- a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js +++ b/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js @@ -81,4 +81,4 @@ exports.a = pkg.invoke(); //// [index.d.ts] -export declare const a: import("../packageA/foo").Foo; +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 d5d42c3f80..0000000000 --- a/testdata/baselines/reference/submodule/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.js.diff +++ /dev/null @@ -1,8 +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("../packageA/foo").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..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("../packageA/foo").Foo +>create : () => import("package-a/cls").Foo export declare function invoke(): ReturnType; ->invoke : () => import("../packageA/foo").Foo ->create : () => import("../packageA/foo").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("../packageA/foo").Foo ->pkg.invoke() : import("../packageA/foo").Foo ->pkg.invoke : () => import("../packageA/foo").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("../packageA/foo").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 daf072ef1e..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("../packageA/foo").Foo ++>create : () => import("package-a/cls").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/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("../packageA/foo").Foo -+>pkg.invoke() : import("../packageA/foo").Foo -+>pkg.invoke : () => import("../packageA/foo").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("../packageA/foo").Foo ++>invoke : () => import("package-a/cls").Foo diff --git a/testdata/tests/cases/compiler/destructuringReexportSymlinkImportType.ts b/testdata/tests/cases/compiler/destructuringReexportSymlinkImportType.ts new file mode 100644 index 0000000000..05610a0677 --- /dev/null +++ b/testdata/tests/cases/compiler/destructuringReexportSymlinkImportType.ts @@ -0,0 +1,28 @@ +// 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", + "main": "./index.js", + "types": "./index.d.ts" +} + +// @Filename: /real-packages/package-b/index.d.ts +export interface B { + value: string; +} + +// @Filename: /project/src/types.ts +import type { B } from "package-b"; +export type { B }; + +// @Filename: /project/src/main.ts +import type { B } from "./types"; + +export function useB(param: B): B { + return param; +} + +// @link: /real-packages/package-b -> /project/node_modules/package-b \ No newline at end of file