mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-02 07:43:42 +08:00
Adds the report-only Node island policy, configuration and detector of the scope-B plan (milestone NI-2). Coverage now lists the npm packages a build would run in a Node island and why, gap islands first, without changing code generation. - project-config.ts reads the `scriptc` key of the entry's package.json with a schema; an unknown value in `scriptc.packages` or `scriptc.nodeIslands` is SC8004 instead of being ignored. Package shims and the WebAssembly scanner read through it. - node-islands/: origin (user, npm, shim, builtin; package instances), rules R1.1-R2.5 and the four classes, a syntactic pre-scan for R1/R2 markers, classification of refusals and runtime fences (reason ids at the refusal sites that need them), the policy (auto, explicit, off; forced node and native packages; marked user modules) and the report with island closure. - coverage: a "Node islands" section, `--explain-island=<pkg>`, a `nodeIslands` field in the diagnostics envelope, and `--fail-on` `node-islands`, `eager-node-islands` and `island-gaps`. - CLI: `--node-islands`, `--node-island`, `--native`, `--node` (validated, report-only) and `--strict-user-js` (SC8101-SC8105 for R2 constructs in user JavaScript). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
132 lines
4.5 KiB
TypeScript
132 lines
4.5 KiB
TypeScript
/* Diagnostic → Node island class (DESIGN.md §1.3). A refusal in a module
|
|
* of an npm package is a reason for that package to run in a Node island;
|
|
* its class says why: the runtime itself (R1), a mutable object model (R2),
|
|
* a shipping problem (packaging), or a construct inside the native subset
|
|
* that scriptc cannot compile yet (gap). The reason id on the diagnostic
|
|
* (ScrDiagnostic.reason) decides first; the code and message decide for
|
|
* refusal sites that do not carry one. */
|
|
import type { ScrDiagnostic } from "../diagnostics/diagnostic.js";
|
|
import type { NodeIslandMarker } from "./detect.js";
|
|
import { classOfRule, ruleInfo, ruleOfReason } from "./rules.js";
|
|
import type { RefusalRecord } from "./scan.js";
|
|
import type { NodeIslandReason, NodeIslandReasonSource } from "./summary.js";
|
|
|
|
/** Message shapes of refusal sites that predate reason ids. */
|
|
const MESSAGE_REASONS: { code: string; pattern: RegExp; reason: string }[] = [
|
|
{ code: "SC2010", pattern: /./, reason: "code-from-string" },
|
|
{ code: "SC1010", pattern: /'(?:node:)?vm'/, reason: "vm" },
|
|
{ code: "SC1090", pattern: /^dynamic import\(\) of computed/, reason: "dynamic-import" },
|
|
{ code: "SC1090", pattern: /require\(\) with a computed specifier/, reason: "dynamic-require" },
|
|
{ code: "SC1090", pattern: /'with' statement/, reason: "with" },
|
|
{ code: "SC1090", pattern: /sloppy-mode parameter aliases/, reason: "sloppy-arguments" },
|
|
{ code: "SC2020", pattern: /^'(?:eval|Function)'/, reason: "code-from-string" },
|
|
{
|
|
code: "SC2020",
|
|
pattern: /createRequire's require with this argument shape/,
|
|
reason: "dynamic-require",
|
|
},
|
|
{ code: "SC2020", pattern: /native addon/, reason: "native-addon" },
|
|
{
|
|
code: "SC2020",
|
|
pattern: /through require\.cache|require\.extensions|Module\._|module\.register/,
|
|
reason: "loader-hook",
|
|
},
|
|
{
|
|
code: "SC2020",
|
|
pattern: /process\.binding|_linkedBinding|internalBinding|setFlagsFromString/,
|
|
reason: "engine-internal",
|
|
},
|
|
{ code: "SC2020", pattern: /\bWeakRef\b|\bFinalizationRegistry\b/, reason: "weak-ref" },
|
|
{ code: "SC1100", pattern: /\bWeakRef\b|\bFinalizationRegistry\b/, reason: "weak-ref" },
|
|
];
|
|
|
|
function reasonIdOf(d: ScrDiagnostic): string | null {
|
|
if (d.reason !== undefined) return d.reason;
|
|
for (const entry of MESSAGE_REASONS) {
|
|
if (entry.code === d.code && entry.pattern.test(d.message)) return entry.reason;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Codes that are not reasons of the package they appear in: a cascade
|
|
* marker repeats a root cause, an import of another refused package is
|
|
* that package's island boundary, and divergences compile. */
|
|
export function isIslandReasonCode(code: string): boolean {
|
|
if (code === "SC2004" || code === "SC2013") return false;
|
|
if (/^SC6\d{3}$/.test(code)) return false;
|
|
return true;
|
|
}
|
|
|
|
export function classifyDiagnostic(
|
|
d: ScrDiagnostic,
|
|
source: NodeIslandReasonSource,
|
|
): NodeIslandReason {
|
|
const id = reasonIdOf(d);
|
|
const rule = id === null ? null : ruleOfReason(id);
|
|
if (rule !== null && id !== null) {
|
|
return {
|
|
class: classOfRule(rule),
|
|
rule,
|
|
reason: id,
|
|
code: d.code,
|
|
message: d.message,
|
|
loc: d.loc,
|
|
source,
|
|
};
|
|
}
|
|
return {
|
|
class: "gap",
|
|
rule: null,
|
|
reason: "gap",
|
|
code: d.code,
|
|
message: d.message,
|
|
loc: d.loc,
|
|
source,
|
|
};
|
|
}
|
|
|
|
export function classifyMarker(m: NodeIslandMarker): NodeIslandReason {
|
|
return {
|
|
class: classOfRule(m.rule),
|
|
rule: m.rule,
|
|
reason: m.reason,
|
|
code: null,
|
|
message: `${ruleInfo(m.rule).title}: ${m.construct}`,
|
|
loc: m.loc,
|
|
source: "pre-scan",
|
|
};
|
|
}
|
|
|
|
/** A refused package's status (pipeline.ts): the drop-and-reload record
|
|
* when there is one, else the declaration and shipped-JS eligibility bar,
|
|
* which is a packaging reason. */
|
|
export function classifyRefusal(
|
|
detail: string,
|
|
record: RefusalRecord | null,
|
|
): NodeIslandReason | null {
|
|
if (record !== null && record.kind === "external") return null;
|
|
if (record !== null && record.kind === "preflight" && record.diag !== null) {
|
|
return classifyDiagnostic(record.diag, "package");
|
|
}
|
|
if (record !== null && record.kind === "offender" && /require|createRequire/.test(detail)) {
|
|
return {
|
|
class: "runtime",
|
|
rule: "R1.2",
|
|
reason: "dynamic-require",
|
|
code: "SC2013",
|
|
message: detail,
|
|
loc: null,
|
|
source: "package",
|
|
};
|
|
}
|
|
return {
|
|
class: "packaging",
|
|
rule: null,
|
|
reason: "packaging",
|
|
code: "SC2013",
|
|
message: detail,
|
|
loc: null,
|
|
source: "package",
|
|
};
|
|
}
|