mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-02 07:43:42 +08:00
A top-level require binding that earlier code could reach was refused
(SC1013). The reachability scan now follows Node's evaluation order:
hoisted functions and classes run only when reachable code names them, a
class statement runs only its heritage, computed keys, decorators and
static initializers, timer and promise callbacks run after the module
body, and publishing a value through module.exports, exports.n,
Object.defineProperty(exports, ...) or F.prototype.m runs nothing until
reachable code reads it back. This admits the export-first layouts of
semver, glob@7, fs.realpath, on-finished, readable-stream and
tsc-emitted CommonJS.
A const or let binding that code can still reach before its require now
compiles with an initialization flag set right after the require and a
module.tdzCheck before the outermost expression of every reference that
can run earlier, so an early read throws Node's ReferenceError before
any argument evaluates. A reference whose lowering leaves no expression
to check, a var binding (Node reads undefined there) and createRequire
bindings keep SC1013.
require() of a JSON document now answers one parsed object per document,
created in the requiring module's %init prelude unless another module
already created it, so repeated requires return the same object and
mutations are visible to every requirer. Require bindings of JSON
documents are ordinary storage, so destructuring (nested patterns,
defaults, missing keys) copies at the require as in Node.
Nested destructuring of a builtin require binds the members of the
sub-module the key names (const { types: { isPromise } } =
require('util')).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
46 lines
1.9 KiB
TypeScript
46 lines
1.9 KiB
TypeScript
import type {
|
|
ExportDeclaration,
|
|
Identifier,
|
|
SourceFile,
|
|
VariableDeclaration,
|
|
} from "./ts7/ast-types.js";
|
|
import type { ModuleSourceCandidates } from "./module-source-candidates.js";
|
|
import type { CycleEarlyBinding } from "./program.js";
|
|
|
|
/** Early bindings computed for one module order of a program. */
|
|
export interface EarlyBindingsMemo {
|
|
key: string;
|
|
bindings: CycleEarlyBinding[];
|
|
}
|
|
|
|
/** Facts owned by one program snapshot. Strong collections are bounded by
|
|
* the program lifecycle, so neither GC finalizers nor native weak handles
|
|
* are needed, and newer snapshots cannot inherit stale package decisions. */
|
|
export class ProgramAnalysis {
|
|
readonly createRequireReasons = new Map<VariableDeclaration, string | null>();
|
|
readonly nodeEsmFiles = new Map<SourceFile, boolean>();
|
|
readonly prunedNpmReexports = new Set<ExportDeclaration>();
|
|
readonly moduleSourceCandidates = new Map<SourceFile, ModuleSourceCandidates>();
|
|
/** The modules of every import cycle the exact initialization rule admitted. */
|
|
readonly exactCycleMembers = new Set<SourceFile>();
|
|
cycleEarlyBindings: EarlyBindingsMemo | null = null;
|
|
moduleEarlyBindings: EarlyBindingsMemo | null = null;
|
|
/** Top-level require declarations: the first reference to their bindings
|
|
* that code running before the require can reach (requireEarlyReference). */
|
|
readonly requireEarlyReferences = new Map<VariableDeclaration, Identifier | null>();
|
|
/** Per file: the first top-level statement that can run user code. */
|
|
readonly requireFirstRunnable = new Map<SourceFile, number>();
|
|
|
|
clear(): void {
|
|
this.createRequireReasons.clear();
|
|
this.nodeEsmFiles.clear();
|
|
this.prunedNpmReexports.clear();
|
|
this.moduleSourceCandidates.clear();
|
|
this.exactCycleMembers.clear();
|
|
this.cycleEarlyBindings = null;
|
|
this.moduleEarlyBindings = null;
|
|
this.requireEarlyReferences.clear();
|
|
this.requireFirstRunnable.clear();
|
|
}
|
|
}
|