Skip to content

[WIP] Destructuring re-exports using type from symlinked node-modules results in relative paths used in import() type #1348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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))
}

Original file line number Diff line number Diff line change
@@ -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
}

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
Original file line number Diff line number Diff line change
Expand Up @@ -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;

This file was deleted.

Loading