diff --git a/tsc/internal/modulespecifiers/specifiers.go b/tsc/internal/modulespecifiers/specifiers.go index 3e50d575e985f..76fdbce1eef99 100644 --- a/tsc/internal/modulespecifiers/specifiers.go +++ b/tsc/internal/modulespecifiers/specifiers.go @@ -760,11 +760,21 @@ func tryGetModuleNameAsNodeModule( return "" } + caseSensitive := host.UseCaseSensitiveFileNames() + globalTypingsCacheLocation := host.GetGlobalTypingsCacheLocation() + // Check reachability before consulting package.json exports, which can return + // a bare specifier directly. An exported path is only usable if the importing + // file can reach the package through node_modules. + pathToTopLevelNodeModules := pathObj.FileName[0:parts.TopLevelNodeModulesIndex] + + if !stringutil.HasPrefix(info.SourceDirectory, pathToTopLevelNodeModules, caseSensitive) || len(globalTypingsCacheLocation) > 0 && stringutil.HasPrefix(globalTypingsCacheLocation, pathToTopLevelNodeModules, caseSensitive) { + return "" + } + // Simplify the full file path to something that can be resolved by Node. preferences := getModuleSpecifierPreferences(userPreferences, host, options, importingSourceFile, "") allowedEndings := preferences.getAllowedEndingsInPreferredOrder(core.ResolutionModeNone) - caseSensitive := host.UseCaseSensitiveFileNames() moduleSpecifier := pathObj.FileName isPackageRootPath := false if !packageNameOnly { @@ -794,7 +804,6 @@ func tryGetModuleNameAsNodeModule( if verbatimFromExports { return moduleFileToTry } - //} if len(packageRootPath) > 0 { moduleSpecifier = packageRootPath isPackageRootPath = true @@ -816,15 +825,6 @@ func tryGetModuleNameAsNodeModule( return "" } - globalTypingsCacheLocation := host.GetGlobalTypingsCacheLocation() - // Get a path that's relative to node_modules or the importing file's path - // if node_modules folder is in this folder or any of its parent folders, no need to keep it. - pathToTopLevelNodeModules := moduleSpecifier[0:parts.TopLevelNodeModulesIndex] - - if !stringutil.HasPrefix(info.SourceDirectory, pathToTopLevelNodeModules, caseSensitive) || len(globalTypingsCacheLocation) > 0 && stringutil.HasPrefix(globalTypingsCacheLocation, pathToTopLevelNodeModules, caseSensitive) { - return "" - } - // If the module was found in @types, get the actual Node package name nodeModulesDirectoryName := moduleSpecifier[parts.TopLevelPackageNameIndex+1:] return module.GetPackageNameFromTypesPackageName(nodeModulesDirectoryName) diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitUnreachablePackageExports(module=nodenext).errors.txt b/tsc/testdata/baselines/reference/compiler/declarationEmitUnreachablePackageExports(module=nodenext).errors.txt new file mode 100644 index 0000000000000..ac3f906c87b93 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitUnreachablePackageExports(module=nodenext).errors.txt @@ -0,0 +1,50 @@ +/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. +/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. +/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. + + +==== /node_modules/.store/c-root/node_modules/c-root/package.json (0 errors) ==== + { "name": "c-root", "version": "1.0.0", "type": "module", "exports": { ".": "./dist/Thing.d.ts" } } +==== /node_modules/.store/c-root/node_modules/c-root/dist/Thing.d.ts (0 errors) ==== + export interface Thing { readonly x: number; } + export declare const create: () => Thing; + +==== /node_modules/.store/c-subpath/node_modules/c-subpath/package.json (0 errors) ==== + { "name": "c-subpath", "version": "1.0.0", "type": "module", "exports": { "./Thing": { "types": "./dist/Thing.d.ts", "default": "./dist/Thing.js" } } } +==== /node_modules/.store/c-subpath/node_modules/c-subpath/dist/Thing.d.ts (0 errors) ==== + export interface Thing { readonly x: number; } + export declare const create: () => Thing; + +==== /node_modules/.store/c-pattern/node_modules/c-pattern/package.json (0 errors) ==== + { "name": "c-pattern", "version": "1.0.0", "type": "module", "exports": { "./*": "./dist/*.d.ts" } } +==== /node_modules/.store/c-pattern/node_modules/c-pattern/dist/Thing.d.ts (0 errors) ==== + export interface Thing { readonly x: number; } + export declare const create: () => Thing; + +==== /packages/s/package.json (0 errors) ==== + { "name": "s", "type": "module", "exports": "./index.d.ts" } +==== /packages/s/index.d.ts (0 errors) ==== + import { create as createRoot } from "c-root"; + import { create as createSubpath } from "c-subpath/Thing"; + import { create as createPattern } from "c-pattern/Thing"; + export declare const root: ReturnType; + export declare const subpath: ReturnType; + export declare const pattern: ReturnType; + +==== /packages/a/package.json (0 errors) ==== + { "name": "a", "type": "module" } +==== /packages/a/index.ts (3 errors) ==== + import { root, subpath, pattern } from "s"; + export const thing = { root, subpath, pattern }; + ~~~~~ +!!! 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. + ~~~~~ +!!! 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. + ~~~~~ +!!! 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. + +==== /packages/s/reachable.ts (0 errors) ==== + import { root, subpath, pattern } from "./index.js"; + export const thing = { root, subpath, pattern }; + + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitUnreachablePackageExports(module=nodenext).js b/tsc/testdata/baselines/reference/compiler/declarationEmitUnreachablePackageExports(module=nodenext).js new file mode 100644 index 0000000000000..273ddc4bac134 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitUnreachablePackageExports(module=nodenext).js @@ -0,0 +1,50 @@ +//// [tests/cases/compiler/declarationEmitUnreachablePackageExports.ts] //// + +//// [package.json] +{ "name": "c-root", "version": "1.0.0", "type": "module", "exports": { ".": "./dist/Thing.d.ts" } } +//// [Thing.d.ts] +export interface Thing { readonly x: number; } +export declare const create: () => Thing; + +//// [package.json] +{ "name": "c-subpath", "version": "1.0.0", "type": "module", "exports": { "./Thing": { "types": "./dist/Thing.d.ts", "default": "./dist/Thing.js" } } } +//// [Thing.d.ts] +export interface Thing { readonly x: number; } +export declare const create: () => Thing; + +//// [package.json] +{ "name": "c-pattern", "version": "1.0.0", "type": "module", "exports": { "./*": "./dist/*.d.ts" } } +//// [Thing.d.ts] +export interface Thing { readonly x: number; } +export declare const create: () => Thing; + +//// [package.json] +{ "name": "s", "type": "module", "exports": "./index.d.ts" } +//// [index.d.ts] +import { create as createRoot } from "c-root"; +import { create as createSubpath } from "c-subpath/Thing"; +import { create as createPattern } from "c-pattern/Thing"; +export declare const root: ReturnType; +export declare const subpath: ReturnType; +export declare const pattern: ReturnType; + +//// [package.json] +{ "name": "a", "type": "module" } +//// [index.ts] +import { root, subpath, pattern } from "s"; +export const thing = { root, subpath, pattern }; + +//// [reachable.ts] +import { root, subpath, pattern } from "./index.js"; +export const thing = { root, subpath, pattern }; + + + + + +//// [reachable.d.ts] +export declare const thing: { + root: import("c-root").Thing; + subpath: import("c-subpath/Thing").Thing; + pattern: import("c-pattern/Thing").Thing; +}; diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitUnreachablePackageExports(module=preserve).errors.txt b/tsc/testdata/baselines/reference/compiler/declarationEmitUnreachablePackageExports(module=preserve).errors.txt new file mode 100644 index 0000000000000..2320aa9439238 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitUnreachablePackageExports(module=preserve).errors.txt @@ -0,0 +1,50 @@ +/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. +/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. +/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. + + +==== /node_modules/.store/c-root/node_modules/c-root/package.json (0 errors) ==== + { "name": "c-root", "version": "1.0.0", "type": "module", "exports": { ".": "./dist/Thing.d.ts" } } +==== /node_modules/.store/c-root/node_modules/c-root/dist/Thing.d.ts (0 errors) ==== + export interface Thing { readonly x: number; } + export declare const create: () => Thing; + +==== /node_modules/.store/c-subpath/node_modules/c-subpath/package.json (0 errors) ==== + { "name": "c-subpath", "version": "1.0.0", "type": "module", "exports": { "./Thing": { "types": "./dist/Thing.d.ts", "default": "./dist/Thing.js" } } } +==== /node_modules/.store/c-subpath/node_modules/c-subpath/dist/Thing.d.ts (0 errors) ==== + export interface Thing { readonly x: number; } + export declare const create: () => Thing; + +==== /node_modules/.store/c-pattern/node_modules/c-pattern/package.json (0 errors) ==== + { "name": "c-pattern", "version": "1.0.0", "type": "module", "exports": { "./*": "./dist/*.d.ts" } } +==== /node_modules/.store/c-pattern/node_modules/c-pattern/dist/Thing.d.ts (0 errors) ==== + export interface Thing { readonly x: number; } + export declare const create: () => Thing; + +==== /packages/s/package.json (0 errors) ==== + { "name": "s", "type": "module", "exports": "./index.d.ts" } +==== /packages/s/index.d.ts (0 errors) ==== + import { create as createRoot } from "c-root"; + import { create as createSubpath } from "c-subpath/Thing"; + import { create as createPattern } from "c-pattern/Thing"; + export declare const root: ReturnType; + export declare const subpath: ReturnType; + export declare const pattern: ReturnType; + +==== /packages/a/package.json (0 errors) ==== + { "name": "a", "type": "module" } +==== /packages/a/index.ts (3 errors) ==== + import { root, subpath, pattern } from "s"; + export const thing = { root, subpath, pattern }; + ~~~~~ +!!! 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. + ~~~~~ +!!! 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. + ~~~~~ +!!! 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. + +==== /packages/s/reachable.ts (0 errors) ==== + import { root, subpath, pattern } from "./index.js"; + export const thing = { root, subpath, pattern }; + + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitUnreachablePackageExports(module=preserve).js b/tsc/testdata/baselines/reference/compiler/declarationEmitUnreachablePackageExports(module=preserve).js new file mode 100644 index 0000000000000..273ddc4bac134 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitUnreachablePackageExports(module=preserve).js @@ -0,0 +1,50 @@ +//// [tests/cases/compiler/declarationEmitUnreachablePackageExports.ts] //// + +//// [package.json] +{ "name": "c-root", "version": "1.0.0", "type": "module", "exports": { ".": "./dist/Thing.d.ts" } } +//// [Thing.d.ts] +export interface Thing { readonly x: number; } +export declare const create: () => Thing; + +//// [package.json] +{ "name": "c-subpath", "version": "1.0.0", "type": "module", "exports": { "./Thing": { "types": "./dist/Thing.d.ts", "default": "./dist/Thing.js" } } } +//// [Thing.d.ts] +export interface Thing { readonly x: number; } +export declare const create: () => Thing; + +//// [package.json] +{ "name": "c-pattern", "version": "1.0.0", "type": "module", "exports": { "./*": "./dist/*.d.ts" } } +//// [Thing.d.ts] +export interface Thing { readonly x: number; } +export declare const create: () => Thing; + +//// [package.json] +{ "name": "s", "type": "module", "exports": "./index.d.ts" } +//// [index.d.ts] +import { create as createRoot } from "c-root"; +import { create as createSubpath } from "c-subpath/Thing"; +import { create as createPattern } from "c-pattern/Thing"; +export declare const root: ReturnType; +export declare const subpath: ReturnType; +export declare const pattern: ReturnType; + +//// [package.json] +{ "name": "a", "type": "module" } +//// [index.ts] +import { root, subpath, pattern } from "s"; +export const thing = { root, subpath, pattern }; + +//// [reachable.ts] +import { root, subpath, pattern } from "./index.js"; +export const thing = { root, subpath, pattern }; + + + + + +//// [reachable.d.ts] +export declare const thing: { + root: import("c-root").Thing; + subpath: import("c-subpath/Thing").Thing; + pattern: import("c-pattern/Thing").Thing; +}; diff --git a/tsc/testdata/tests/cases/compiler/declarationEmitUnreachablePackageExports.ts b/tsc/testdata/tests/cases/compiler/declarationEmitUnreachablePackageExports.ts new file mode 100644 index 0000000000000..d83f82ab06a5e --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/declarationEmitUnreachablePackageExports.ts @@ -0,0 +1,48 @@ +// @declaration: true +// @emitDeclarationOnly: true +// @strict: true +// @module: nodenext, preserve +// @noTypesAndSymbols: true + +// @filename: /node_modules/.store/c-root/node_modules/c-root/package.json +{ "name": "c-root", "version": "1.0.0", "type": "module", "exports": { ".": "./dist/Thing.d.ts" } } +// @filename: /node_modules/.store/c-root/node_modules/c-root/dist/Thing.d.ts +export interface Thing { readonly x: number; } +export declare const create: () => Thing; + +// @filename: /node_modules/.store/c-subpath/node_modules/c-subpath/package.json +{ "name": "c-subpath", "version": "1.0.0", "type": "module", "exports": { "./Thing": { "types": "./dist/Thing.d.ts", "default": "./dist/Thing.js" } } } +// @filename: /node_modules/.store/c-subpath/node_modules/c-subpath/dist/Thing.d.ts +export interface Thing { readonly x: number; } +export declare const create: () => Thing; + +// @filename: /node_modules/.store/c-pattern/node_modules/c-pattern/package.json +{ "name": "c-pattern", "version": "1.0.0", "type": "module", "exports": { "./*": "./dist/*.d.ts" } } +// @filename: /node_modules/.store/c-pattern/node_modules/c-pattern/dist/Thing.d.ts +export interface Thing { readonly x: number; } +export declare const create: () => Thing; + +// @filename: /packages/s/package.json +{ "name": "s", "type": "module", "exports": "./index.d.ts" } +// @filename: /packages/s/index.d.ts +import { create as createRoot } from "c-root"; +import { create as createSubpath } from "c-subpath/Thing"; +import { create as createPattern } from "c-pattern/Thing"; +export declare const root: ReturnType; +export declare const subpath: ReturnType; +export declare const pattern: ReturnType; + +// @filename: /packages/a/package.json +{ "name": "a", "type": "module" } +// @filename: /packages/a/index.ts +import { root, subpath, pattern } from "s"; +export const thing = { root, subpath, pattern }; + +// @filename: /packages/s/reachable.ts +import { root, subpath, pattern } from "./index.js"; +export const thing = { root, subpath, pattern }; + +// @link: /node_modules/.store/c-root/node_modules/c-root -> /packages/s/node_modules/c-root +// @link: /node_modules/.store/c-subpath/node_modules/c-subpath -> /packages/s/node_modules/c-subpath +// @link: /node_modules/.store/c-pattern/node_modules/c-pattern -> /packages/s/node_modules/c-pattern +// @link: /packages/s -> /packages/a/node_modules/s