mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Keep declaration files out of the module order
A specifier that resolves ONLY to a declaration file has no runtime module to link: Node throws ERR_MODULE_NOT_FOUND at it. Preflight already refused the import form (SC1010), but the re-export arm and both require arms took the declaration file as a module-order edge. Once a declaration file is a program module, its own imports are lowered as code — which is how a package's own `.d.mts` files emit SC2013 for every specifier inside them (the pi/typebox shape: 133 errors, all at import statements inside `node_modules/typebox/build/**/*.d.mts`). Refuse the re-export form with the same SC1010 the import form uses, and skip declaration files as edges in both require arms. `splitFiles` gains the matching invariant: a declaration file is never lowered as a runtime module even if one reaches the order by another path. Test: tests/diagnostics/declaration-only-module (SC1010 snapshot) and the recorded preflight/order baselines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 534ec68eb13f15812376aa5126f013da693a48d6)
This commit is contained in:
@@ -328,7 +328,15 @@ function appendEntryModules(
|
||||
* (tests, coverage on broken files) may arrive with an empty order — fall
|
||||
* back to the entry alone. */
|
||||
export function splitFiles(lowerer: Lowerer): FileParts[] {
|
||||
const files = lowerer.moduleOrder.length > 0 ? lowerer.moduleOrder : [lowerer.entry];
|
||||
// A declaration file is never a runtime module: it carries no
|
||||
// implementation, so it has no %init to lower and its imports never load
|
||||
// anything. Preflight keeps declarations out of the module order (a
|
||||
// specifier that resolves only to declarations is refused at its own
|
||||
// statement); this filter is the invariant that keeps a stray declaration
|
||||
// from being lowered as code.
|
||||
const files = (lowerer.moduleOrder.length > 0 ? lowerer.moduleOrder : [lowerer.entry]).filter(
|
||||
(sf) => !sf.isDeclarationFile,
|
||||
);
|
||||
return files.map((sf) => {
|
||||
const fp: FileParts = { sf, fnDecls: [], classDecls: [], topStmts: [] };
|
||||
const rebindable = collectRebindableFunctions(lowerer, sf);
|
||||
|
||||
@@ -6060,6 +6060,21 @@ function preflight7(load: LoadResult): {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// A re-export whose specifier resolves ONLY to a declaration file
|
||||
// has no runtime module to link (Node throws
|
||||
// ERR_MODULE_NOT_FOUND), and a declaration file is not a program
|
||||
// module: an edge to it would lower the declarations' own imports
|
||||
// as code.
|
||||
if (reDep !== null && reDep.isDeclarationFile) {
|
||||
diags.push(
|
||||
unsupportedDiag(
|
||||
"SC1010",
|
||||
locOf7(stmt),
|
||||
`the '${fromSpec}' re-export (it resolves only to type declarations — there is no runtime implementation to compile)`,
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (reDep && !reDep.fileName.endsWith(".json")) {
|
||||
deps.push({ dep: reDep, stmt });
|
||||
depPositions.get(sf)!.push({ dep: reDep, pos: ts.tokenStartOfNode(stmt) });
|
||||
@@ -6250,6 +6265,22 @@ function preflight7(load: LoadResult): {
|
||||
const clause = stmt.importClause;
|
||||
const dep = isRelative ? resolveImport7(program, sf, spec) : (npmStaticDep ?? projDep);
|
||||
const isJson = dep !== null && dep.fileName.endsWith(".json");
|
||||
// A specifier that resolves ONLY to a declaration file has no runtime
|
||||
// module to link: Node throws ERR_MODULE_NOT_FOUND at this import.
|
||||
// Declarations are not program modules — taking one as a module-order
|
||||
// edge would lower its imports as if it were code (SC2013 for every
|
||||
// specifier inside a package's `.d.ts`). Type-only imports were
|
||||
// already erased above; this is the value form.
|
||||
if (dep !== null && dep.isDeclarationFile && !isJson) {
|
||||
diags.push(
|
||||
unsupportedDiag(
|
||||
"SC1010",
|
||||
locOf7(stmt),
|
||||
`the '${spec}' import (it resolves only to type declarations — there is no runtime implementation to compile)`,
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// SELF-imports: the bindings alias this module's OWN declarations,
|
||||
// so a TOP-LEVEL read lexically above the aliased declaration is
|
||||
// Node's TDZ ReferenceError at runtime (exactly what tsc's TS2448
|
||||
@@ -6532,6 +6563,10 @@ function preflight7(load: LoadResult): {
|
||||
}
|
||||
}
|
||||
if (earlyRequireFence(req, bindingKind, loc)) continue;
|
||||
// A declaration file is never a runtime module (see the import
|
||||
// arm): the require throws MODULE_NOT_FOUND in Node, so it is not
|
||||
// a module-order edge.
|
||||
if (dep !== null && dep.isDeclarationFile) continue;
|
||||
if (dep) deps.push({ dep });
|
||||
}
|
||||
}
|
||||
@@ -6610,7 +6645,8 @@ function preflight7(load: LoadResult): {
|
||||
lazy ? { dep, lazy: true, call } : { dep, call };
|
||||
const projectDep = resolveImport7(program, sf, spec, "require");
|
||||
if (projectDep !== null) {
|
||||
if (!projectDep.fileName.endsWith(".json")) deps.push(edgeTo(projectDep));
|
||||
if (!projectDep.fileName.endsWith(".json") && !projectDep.isDeclarationFile)
|
||||
deps.push(edgeTo(projectDep));
|
||||
continue;
|
||||
}
|
||||
if (isRelativeSpecifier(spec) || spec.startsWith("#")) continue;
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/declaration-only-module/main.ts"
|
||||
],
|
||||
"diags": [
|
||||
{
|
||||
"code": "SC1010",
|
||||
"message": "the './types.js' import (it resolves only to type declarations — there is no runtime implementation to compile) is not supported yet",
|
||||
"loc": {
|
||||
"file": "<repo>/tests/diagnostics/declaration-only-module/main.ts",
|
||||
"start": 246,
|
||||
"end": 281
|
||||
},
|
||||
"milestone": "M4",
|
||||
"hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// A value import whose specifier resolves ONLY to a declaration file has no
|
||||
// runtime module to link — Node throws ERR_MODULE_NOT_FOUND here, and a
|
||||
// declaration file is not a program module (its own imports must never be
|
||||
// lowered as code).
|
||||
import { value } from "./types.js";
|
||||
|
||||
console.log(value);
|
||||
@@ -0,0 +1,6 @@
|
||||
// A module that exists only as declarations: it has no runtime
|
||||
// implementation for either side to link.
|
||||
export interface Shape {
|
||||
kind: string;
|
||||
}
|
||||
export declare const value: number;
|
||||
@@ -0,0 +1,8 @@
|
||||
declaration-only-module/main.ts:5:1 - error SC1010: the './types.js' import (it resolves only to type declarations — there is no runtime implementation to compile) is not supported yet
|
||||
|
||||
4 | // lowered as code).
|
||||
5 | import { value } from "./types.js";
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
6 |
|
||||
|
||||
hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported
|
||||
Reference in New Issue
Block a user