Description
Test case
// @Filename: /node_modules/direct-dep/index.d.ts
/// <reference types="transitive-dep" />
// @Filename: /node_modules/direct-dep/node_modules/transitive-dep/index.d.ts
declare module "ambient-module" {
export class C {
private x;
}
}
// @Filename: /index.ts
import { C } from "ambient-module";
export const c = new C();
// @Filename: /tsconfig.json
{
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"module": "nodenext",
"types": ["direct-dep"]
}
}
Expected behavior
Declaration files should be error-free if running tsc --declaration
did not produce errors. Specifically, either /index.d.ts
should contain a triple-slash reference to "direct-dep"
, or compiling should produce an error saying that a portable reference to /node_modules/direct-dep/node_modules/transitive-dep/index.d.ts
cannot be emitted.
Actual behavior
/index.d.ts
contains
/// <reference types="transitive-dep" />
which does not resolve. No errors are produced while compiling.
Discussion
The erroneous type reference directive is emitted because the program stores information that the target file, /node_modules/direct-dep/node_modules/transitive-dep/index.d.ts
, had previously been resolved by a type reference directive of "transitive-dep"
somewhere in the program’s input files. The declaration emitter mistakenly assumes that any type reference directive that resolved anywhere in the program will be resolvable from the declaration file it’s emitting, but this is a bad assumption because type reference resolution is dependent on the location of the file that’s making the resolution. In the test case above, the location that originally had the reference to "transitive-dep"
has a node_modules
folder in scope for its lookup that is not in scope for /index.d.ts
.
This cannot reliably be fixed with a simple change to the declaration emitter, because the Program itself discards the relevant information about the location from which type reference directives were resolved. Its cache is keyed only by name and resolution mode.
I discovered this while attempting to make a package that uses conditional package.json "exports"
to conditionally load ts-expose-internals. It fails because tsc writes invalid triple-slash references to "ts-expose-internals"
instead of to my proxy package.
cc @sheetalkamat @weswigham for input