Skip to content

Commit 71956e9

Browse files
authored
Check package reachability before using exports in declarations (#64599)
1 parent 5b3a367 commit 71956e9

6 files changed

Lines changed: 259 additions & 9 deletions

‎tsc/internal/modulespecifiers/specifiers.go‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -757,11 +757,21 @@ func tryGetModuleNameAsNodeModule(
757757
return ""
758758
}
759759

760+
caseSensitivity := host.CaseSensitivity()
761+
globalTypingsCacheLocation := host.GetGlobalTypingsCacheLocation()
762+
// Check reachability before consulting package.json exports, which can return
763+
// a bare specifier directly. An exported path is only usable if the importing
764+
// file can reach the package through node_modules, and packages under the
765+
// global typings cache are excluded.
766+
if !caseSensitivity.ContainsPath(parts.TopLevelNodeModulesSearchRoot, info.SourceDirectory.AsPath()) ||
767+
globalTypingsCacheLocation != "" && caseSensitivity.ContainsPath(parts.TopLevelNodeModulesSearchRoot, globalTypingsCacheLocation.AsPath()) {
768+
return ""
769+
}
770+
760771
// Simplify the full file path to something that can be resolved by Node.
761772
preferences := getModuleSpecifierPreferences(userPreferences, host, options, importingSourceFile, "")
762773
allowedEndings := preferences.getAllowedEndingsInPreferredOrder(core.ResolutionModeNone)
763774

764-
caseSensitivity := host.CaseSensitivity()
765775
moduleSpecifier := pathObj.FileName.AsString()
766776
isPackageRootPath := false
767777
if !packageNameOnly {
@@ -819,14 +829,6 @@ func tryGetModuleNameAsNodeModule(
819829
return ""
820830
}
821831

822-
globalTypingsCacheLocation := host.GetGlobalTypingsCacheLocation()
823-
// Get a path that's relative to node_modules or the importing file's path
824-
// if node_modules folder is in this folder or any of its parent folders, no need to keep it.
825-
if !caseSensitivity.ContainsPath(parts.TopLevelNodeModulesSearchRoot, info.SourceDirectory.AsPath()) ||
826-
globalTypingsCacheLocation != "" && caseSensitivity.ContainsPath(parts.TopLevelNodeModulesSearchRoot, globalTypingsCacheLocation.AsPath()) {
827-
return ""
828-
}
829-
830832
// If the module was found in @types, get the actual Node package name
831833
nodeModulesPrefix := tspath.EnsureTrailingDirectorySeparator(parts.TopLevelNodeModulesDirectory.AsString())
832834
if !strings.HasPrefix(moduleSpecifier, nodeModulesPrefix) {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/packages/a/index.ts(2,14): error TS2883: The inferred type of 'thing' cannot be named without a reference to 'Thing' from '../s/node_modules/c-pattern/dist/Thing.js'. This is likely not portable. A type annotation is necessary.
2+
/packages/a/index.ts(2,14): error TS2883: The inferred type of 'thing' cannot be named without a reference to 'Thing' from '../s/node_modules/c-root/dist/Thing.js'. This is likely not portable. A type annotation is necessary.
3+
/packages/a/index.ts(2,14): error TS2883: The inferred type of 'thing' cannot be named without a reference to 'Thing' from '../s/node_modules/c-subpath/dist/Thing.js'. This is likely not portable. A type annotation is necessary.
4+
5+
6+
==== /node_modules/.store/c-root/node_modules/c-root/package.json (0 errors) ====
7+
{ "name": "c-root", "version": "1.0.0", "type": "module", "exports": { ".": "./dist/Thing.d.ts" } }
8+
==== /node_modules/.store/c-root/node_modules/c-root/dist/Thing.d.ts (0 errors) ====
9+
export interface Thing { readonly x: number; }
10+
export declare const create: () => Thing;
11+
12+
==== /node_modules/.store/c-subpath/node_modules/c-subpath/package.json (0 errors) ====
13+
{ "name": "c-subpath", "version": "1.0.0", "type": "module", "exports": { "./Thing": { "types": "./dist/Thing.d.ts", "default": "./dist/Thing.js" } } }
14+
==== /node_modules/.store/c-subpath/node_modules/c-subpath/dist/Thing.d.ts (0 errors) ====
15+
export interface Thing { readonly x: number; }
16+
export declare const create: () => Thing;
17+
18+
==== /node_modules/.store/c-pattern/node_modules/c-pattern/package.json (0 errors) ====
19+
{ "name": "c-pattern", "version": "1.0.0", "type": "module", "exports": { "./*": "./dist/*.d.ts" } }
20+
==== /node_modules/.store/c-pattern/node_modules/c-pattern/dist/Thing.d.ts (0 errors) ====
21+
export interface Thing { readonly x: number; }
22+
export declare const create: () => Thing;
23+
24+
==== /packages/s/package.json (0 errors) ====
25+
{ "name": "s", "type": "module", "exports": "./index.d.ts" }
26+
==== /packages/s/index.d.ts (0 errors) ====
27+
import { create as createRoot } from "c-root";
28+
import { create as createSubpath } from "c-subpath/Thing";
29+
import { create as createPattern } from "c-pattern/Thing";
30+
export declare const root: ReturnType<typeof createRoot>;
31+
export declare const subpath: ReturnType<typeof createSubpath>;
32+
export declare const pattern: ReturnType<typeof createPattern>;
33+
34+
==== /packages/a/package.json (0 errors) ====
35+
{ "name": "a", "type": "module" }
36+
==== /packages/a/index.ts (3 errors) ====
37+
import { root, subpath, pattern } from "s";
38+
export const thing = { root, subpath, pattern };
39+
~~~~~
40+
!!! error TS2883: The inferred type of 'thing' cannot be named without a reference to 'Thing' from '../s/node_modules/c-pattern/dist/Thing.js'. This is likely not portable. A type annotation is necessary.
41+
~~~~~
42+
!!! error TS2883: The inferred type of 'thing' cannot be named without a reference to 'Thing' from '../s/node_modules/c-root/dist/Thing.js'. This is likely not portable. A type annotation is necessary.
43+
~~~~~
44+
!!! error TS2883: The inferred type of 'thing' cannot be named without a reference to 'Thing' from '../s/node_modules/c-subpath/dist/Thing.js'. This is likely not portable. A type annotation is necessary.
45+
46+
==== /packages/s/reachable.ts (0 errors) ====
47+
import { root, subpath, pattern } from "./index.js";
48+
export const thing = { root, subpath, pattern };
49+
50+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//// [tests/cases/compiler/declarationEmitUnreachablePackageExports.ts] ////
2+
3+
//// [package.json]
4+
{ "name": "c-root", "version": "1.0.0", "type": "module", "exports": { ".": "./dist/Thing.d.ts" } }
5+
//// [Thing.d.ts]
6+
export interface Thing { readonly x: number; }
7+
export declare const create: () => Thing;
8+
9+
//// [package.json]
10+
{ "name": "c-subpath", "version": "1.0.0", "type": "module", "exports": { "./Thing": { "types": "./dist/Thing.d.ts", "default": "./dist/Thing.js" } } }
11+
//// [Thing.d.ts]
12+
export interface Thing { readonly x: number; }
13+
export declare const create: () => Thing;
14+
15+
//// [package.json]
16+
{ "name": "c-pattern", "version": "1.0.0", "type": "module", "exports": { "./*": "./dist/*.d.ts" } }
17+
//// [Thing.d.ts]
18+
export interface Thing { readonly x: number; }
19+
export declare const create: () => Thing;
20+
21+
//// [package.json]
22+
{ "name": "s", "type": "module", "exports": "./index.d.ts" }
23+
//// [index.d.ts]
24+
import { create as createRoot } from "c-root";
25+
import { create as createSubpath } from "c-subpath/Thing";
26+
import { create as createPattern } from "c-pattern/Thing";
27+
export declare const root: ReturnType<typeof createRoot>;
28+
export declare const subpath: ReturnType<typeof createSubpath>;
29+
export declare const pattern: ReturnType<typeof createPattern>;
30+
31+
//// [package.json]
32+
{ "name": "a", "type": "module" }
33+
//// [index.ts]
34+
import { root, subpath, pattern } from "s";
35+
export const thing = { root, subpath, pattern };
36+
37+
//// [reachable.ts]
38+
import { root, subpath, pattern } from "./index.js";
39+
export const thing = { root, subpath, pattern };
40+
41+
42+
43+
44+
45+
//// [reachable.d.ts]
46+
export declare const thing: {
47+
root: import("c-root").Thing;
48+
subpath: import("c-subpath/Thing").Thing;
49+
pattern: import("c-pattern/Thing").Thing;
50+
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/packages/a/index.ts(2,14): error TS2883: The inferred type of 'thing' cannot be named without a reference to 'Thing' from '../s/node_modules/c-pattern/dist/Thing'. This is likely not portable. A type annotation is necessary.
2+
/packages/a/index.ts(2,14): error TS2883: The inferred type of 'thing' cannot be named without a reference to 'Thing' from '../s/node_modules/c-root/dist/Thing'. This is likely not portable. A type annotation is necessary.
3+
/packages/a/index.ts(2,14): error TS2883: The inferred type of 'thing' cannot be named without a reference to 'Thing' from '../s/node_modules/c-subpath/dist/Thing'. This is likely not portable. A type annotation is necessary.
4+
5+
6+
==== /node_modules/.store/c-root/node_modules/c-root/package.json (0 errors) ====
7+
{ "name": "c-root", "version": "1.0.0", "type": "module", "exports": { ".": "./dist/Thing.d.ts" } }
8+
==== /node_modules/.store/c-root/node_modules/c-root/dist/Thing.d.ts (0 errors) ====
9+
export interface Thing { readonly x: number; }
10+
export declare const create: () => Thing;
11+
12+
==== /node_modules/.store/c-subpath/node_modules/c-subpath/package.json (0 errors) ====
13+
{ "name": "c-subpath", "version": "1.0.0", "type": "module", "exports": { "./Thing": { "types": "./dist/Thing.d.ts", "default": "./dist/Thing.js" } } }
14+
==== /node_modules/.store/c-subpath/node_modules/c-subpath/dist/Thing.d.ts (0 errors) ====
15+
export interface Thing { readonly x: number; }
16+
export declare const create: () => Thing;
17+
18+
==== /node_modules/.store/c-pattern/node_modules/c-pattern/package.json (0 errors) ====
19+
{ "name": "c-pattern", "version": "1.0.0", "type": "module", "exports": { "./*": "./dist/*.d.ts" } }
20+
==== /node_modules/.store/c-pattern/node_modules/c-pattern/dist/Thing.d.ts (0 errors) ====
21+
export interface Thing { readonly x: number; }
22+
export declare const create: () => Thing;
23+
24+
==== /packages/s/package.json (0 errors) ====
25+
{ "name": "s", "type": "module", "exports": "./index.d.ts" }
26+
==== /packages/s/index.d.ts (0 errors) ====
27+
import { create as createRoot } from "c-root";
28+
import { create as createSubpath } from "c-subpath/Thing";
29+
import { create as createPattern } from "c-pattern/Thing";
30+
export declare const root: ReturnType<typeof createRoot>;
31+
export declare const subpath: ReturnType<typeof createSubpath>;
32+
export declare const pattern: ReturnType<typeof createPattern>;
33+
34+
==== /packages/a/package.json (0 errors) ====
35+
{ "name": "a", "type": "module" }
36+
==== /packages/a/index.ts (3 errors) ====
37+
import { root, subpath, pattern } from "s";
38+
export const thing = { root, subpath, pattern };
39+
~~~~~
40+
!!! error TS2883: The inferred type of 'thing' cannot be named without a reference to 'Thing' from '../s/node_modules/c-pattern/dist/Thing'. This is likely not portable. A type annotation is necessary.
41+
~~~~~
42+
!!! error TS2883: The inferred type of 'thing' cannot be named without a reference to 'Thing' from '../s/node_modules/c-root/dist/Thing'. This is likely not portable. A type annotation is necessary.
43+
~~~~~
44+
!!! error TS2883: The inferred type of 'thing' cannot be named without a reference to 'Thing' from '../s/node_modules/c-subpath/dist/Thing'. This is likely not portable. A type annotation is necessary.
45+
46+
==== /packages/s/reachable.ts (0 errors) ====
47+
import { root, subpath, pattern } from "./index.js";
48+
export const thing = { root, subpath, pattern };
49+
50+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//// [tests/cases/compiler/declarationEmitUnreachablePackageExports.ts] ////
2+
3+
//// [package.json]
4+
{ "name": "c-root", "version": "1.0.0", "type": "module", "exports": { ".": "./dist/Thing.d.ts" } }
5+
//// [Thing.d.ts]
6+
export interface Thing { readonly x: number; }
7+
export declare const create: () => Thing;
8+
9+
//// [package.json]
10+
{ "name": "c-subpath", "version": "1.0.0", "type": "module", "exports": { "./Thing": { "types": "./dist/Thing.d.ts", "default": "./dist/Thing.js" } } }
11+
//// [Thing.d.ts]
12+
export interface Thing { readonly x: number; }
13+
export declare const create: () => Thing;
14+
15+
//// [package.json]
16+
{ "name": "c-pattern", "version": "1.0.0", "type": "module", "exports": { "./*": "./dist/*.d.ts" } }
17+
//// [Thing.d.ts]
18+
export interface Thing { readonly x: number; }
19+
export declare const create: () => Thing;
20+
21+
//// [package.json]
22+
{ "name": "s", "type": "module", "exports": "./index.d.ts" }
23+
//// [index.d.ts]
24+
import { create as createRoot } from "c-root";
25+
import { create as createSubpath } from "c-subpath/Thing";
26+
import { create as createPattern } from "c-pattern/Thing";
27+
export declare const root: ReturnType<typeof createRoot>;
28+
export declare const subpath: ReturnType<typeof createSubpath>;
29+
export declare const pattern: ReturnType<typeof createPattern>;
30+
31+
//// [package.json]
32+
{ "name": "a", "type": "module" }
33+
//// [index.ts]
34+
import { root, subpath, pattern } from "s";
35+
export const thing = { root, subpath, pattern };
36+
37+
//// [reachable.ts]
38+
import { root, subpath, pattern } from "./index.js";
39+
export const thing = { root, subpath, pattern };
40+
41+
42+
43+
44+
45+
//// [reachable.d.ts]
46+
export declare const thing: {
47+
root: import("c-root").Thing;
48+
subpath: import("c-subpath/Thing").Thing;
49+
pattern: import("c-pattern/Thing").Thing;
50+
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// @declaration: true
2+
// @emitDeclarationOnly: true
3+
// @strict: true
4+
// @module: nodenext, preserve
5+
// @noTypesAndSymbols: true
6+
7+
// @filename: /node_modules/.store/c-root/node_modules/c-root/package.json
8+
{ "name": "c-root", "version": "1.0.0", "type": "module", "exports": { ".": "./dist/Thing.d.ts" } }
9+
// @filename: /node_modules/.store/c-root/node_modules/c-root/dist/Thing.d.ts
10+
export interface Thing { readonly x: number; }
11+
export declare const create: () => Thing;
12+
13+
// @filename: /node_modules/.store/c-subpath/node_modules/c-subpath/package.json
14+
{ "name": "c-subpath", "version": "1.0.0", "type": "module", "exports": { "./Thing": { "types": "./dist/Thing.d.ts", "default": "./dist/Thing.js" } } }
15+
// @filename: /node_modules/.store/c-subpath/node_modules/c-subpath/dist/Thing.d.ts
16+
export interface Thing { readonly x: number; }
17+
export declare const create: () => Thing;
18+
19+
// @filename: /node_modules/.store/c-pattern/node_modules/c-pattern/package.json
20+
{ "name": "c-pattern", "version": "1.0.0", "type": "module", "exports": { "./*": "./dist/*.d.ts" } }
21+
// @filename: /node_modules/.store/c-pattern/node_modules/c-pattern/dist/Thing.d.ts
22+
export interface Thing { readonly x: number; }
23+
export declare const create: () => Thing;
24+
25+
// @filename: /packages/s/package.json
26+
{ "name": "s", "type": "module", "exports": "./index.d.ts" }
27+
// @filename: /packages/s/index.d.ts
28+
import { create as createRoot } from "c-root";
29+
import { create as createSubpath } from "c-subpath/Thing";
30+
import { create as createPattern } from "c-pattern/Thing";
31+
export declare const root: ReturnType<typeof createRoot>;
32+
export declare const subpath: ReturnType<typeof createSubpath>;
33+
export declare const pattern: ReturnType<typeof createPattern>;
34+
35+
// @filename: /packages/a/package.json
36+
{ "name": "a", "type": "module" }
37+
// @filename: /packages/a/index.ts
38+
import { root, subpath, pattern } from "s";
39+
export const thing = { root, subpath, pattern };
40+
41+
// @filename: /packages/s/reachable.ts
42+
import { root, subpath, pattern } from "./index.js";
43+
export const thing = { root, subpath, pattern };
44+
45+
// @link: /node_modules/.store/c-root/node_modules/c-root -> /packages/s/node_modules/c-root
46+
// @link: /node_modules/.store/c-subpath/node_modules/c-subpath -> /packages/s/node_modules/c-subpath
47+
// @link: /node_modules/.store/c-pattern/node_modules/c-pattern -> /packages/s/node_modules/c-pattern
48+
// @link: /packages/s -> /packages/a/node_modules/s

0 commit comments

Comments
 (0)