Skip to content
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 51 additions & 4 deletions internal/modulespecifiers/specifiers.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package modulespecifiers

import (
"maps"

Check failure on line 4 in internal/modulespecifiers/specifiers.go

View workflow job for this annotation

GitHub Actions / copilot

"fmt" imported and not used
"slices"
"strings"

Expand Down Expand Up @@ -338,9 +338,10 @@

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 {
Expand Down Expand Up @@ -645,6 +646,50 @@
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 failure on line 658 in internal/modulespecifiers/specifiers.go

View workflow job for this annotation

GitHub Actions / copilot

undefined: fmt
// 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) {

Check failure on line 661 in internal/modulespecifiers/specifiers.go

View workflow job for this annotation

GitHub Actions / copilot

undefined: fmt
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,
Expand All @@ -657,7 +702,9 @@
) 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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<B>
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<B>;
export declare const re: {
foo: typeof foo;
};
export {};
//// [index.d.ts]
declare const foo: () => Promise<import("package-b").B>;
export { foo };
Original file line number Diff line number Diff line change
@@ -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<B>
>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))
}

Original file line number Diff line number Diff line change
@@ -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<B>
>foo : () => Promise<B>

export const re = { foo };
>re : { foo: () => Promise<B>; }
>{ foo } : { foo: () => Promise<B>; }
>foo : () => Promise<B>

=== /packages/a/src/index.ts ===
import { re } from "#re_export";
>re : { foo: () => Promise<import("package-b").B>; }

const { foo } = re;
>foo : () => Promise<import("package-b").B>
>re : { foo: () => Promise<import("package-b").B>; }

export { foo };
>foo : () => Promise<import("package-b").B>

=== /packages/b/index.d.ts ===
export interface B {
b: "b";
>b : "b"
}

Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,75 @@ __exportStar(require("./keys"), exports);

//// [keys.d.ts]
import { MetadataAccessor } from "@raymondfeng/pkg2";
export declare const ADMIN: MetadataAccessor<boolean, import("../../pkg1/dist").IdType>;
export declare const ADMIN: MetadataAccessor<boolean, import("@raymondfeng/pkg2").IdType>;
//// [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<boolean, import("@raymondfeng/pkg2").IdType>;
~~~~~~
!!! 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<T, D extends IdType = IdType> {
readonly key: string;
private constructor();
toString(): string;
static create<T, D extends IdType = IdType>(key: string): MetadataAccessor<T, D>;
}
==== 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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,75 @@
//// [keys.d.ts]
import { MetadataAccessor } from "@raymondfeng/pkg2";
-export declare const ADMIN: MetadataAccessor<boolean, import("@raymondfeng/pkg2/dist/secondary").IdType>;
+export declare const ADMIN: MetadataAccessor<boolean, import("../../pkg1/dist").IdType>;
+export declare const ADMIN: MetadataAccessor<boolean, import("@raymondfeng/pkg2").IdType>;
//// [index.d.ts]
export * from './keys';
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<boolean, import("@raymondfeng/pkg2").IdType>;
+ ~~~~~~
+!!! 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<T, D extends IdType = IdType> {
+ readonly key: string;
+ private constructor();
+ toString(): string;
+ static create<T, D extends IdType = IdType>(key: string): MetadataAccessor<T, D>;
+ }
+==== 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"
+ }
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import {MetadataAccessor} from "@raymondfeng/pkg2";
>MetadataAccessor : typeof MetadataAccessor

export const ADMIN = MetadataAccessor.create<boolean>('1');
>ADMIN : MetadataAccessor<boolean, import("../../pkg1/dist").IdType>
>MetadataAccessor.create<boolean>('1') : MetadataAccessor<boolean, import("../../pkg1/dist").IdType>
>MetadataAccessor.create : <T, D extends import("../../pkg1/dist").IdType = import("../../pkg1/dist").IdType>(key: string) => MetadataAccessor<T, D>
>ADMIN : MetadataAccessor<boolean, import("@raymondfeng/pkg2").IdType>
>MetadataAccessor.create<boolean>('1') : MetadataAccessor<boolean, import("@raymondfeng/pkg2").IdType>
>MetadataAccessor.create : <T, D extends import("@raymondfeng/pkg2").IdType = import("@raymondfeng/pkg2").IdType>(key: string) => MetadataAccessor<T, D>
>MetadataAccessor : typeof MetadataAccessor
>create : <T, D extends import("../../pkg1/dist").IdType = import("../../pkg1/dist").IdType>(key: string) => MetadataAccessor<T, D>
>create : <T, D extends import("@raymondfeng/pkg2").IdType = import("@raymondfeng/pkg2").IdType>(key: string) => MetadataAccessor<T, D>
>'1' : "1"

=== monorepo/pkg1/dist/index.d.ts ===
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
->ADMIN : MetadataAccessor<boolean, import("monorepo/pkg1/dist/types").IdType>
->MetadataAccessor.create<boolean>('1') : MetadataAccessor<boolean, import("monorepo/pkg1/dist/types").IdType>
->MetadataAccessor.create : <T, D extends import("monorepo/pkg1/dist/types").IdType = import("monorepo/pkg1/dist/types").IdType>(key: string) => MetadataAccessor<T, D>
+>ADMIN : MetadataAccessor<boolean, import("../../pkg1/dist").IdType>
+>MetadataAccessor.create<boolean>('1') : MetadataAccessor<boolean, import("../../pkg1/dist").IdType>
+>MetadataAccessor.create : <T, D extends import("../../pkg1/dist").IdType = import("../../pkg1/dist").IdType>(key: string) => MetadataAccessor<T, D>
+>ADMIN : MetadataAccessor<boolean, import("@raymondfeng/pkg2").IdType>
+>MetadataAccessor.create<boolean>('1') : MetadataAccessor<boolean, import("@raymondfeng/pkg2").IdType>
+>MetadataAccessor.create : <T, D extends import("@raymondfeng/pkg2").IdType = import("@raymondfeng/pkg2").IdType>(key: string) => MetadataAccessor<T, D>
>MetadataAccessor : typeof MetadataAccessor
->create : <T, D extends import("monorepo/pkg1/dist/types").IdType = import("monorepo/pkg1/dist/types").IdType>(key: string) => MetadataAccessor<T, D>
+>create : <T, D extends import("../../pkg1/dist").IdType = import("../../pkg1/dist").IdType>(key: string) => MetadataAccessor<T, D>
+>create : <T, D extends import("@raymondfeng/pkg2").IdType = import("@raymondfeng/pkg2").IdType>(key: string) => MetadataAccessor<T, D>
>'1' : "1"

=== monorepo/pkg1/dist/index.d.ts ===
Expand Down
Loading
Loading