Compile more npm and CommonJS JavaScript dependencies statically (#2122)

This commit is contained in:
Chris Tate
2026-10-24 18:30:56 -05:00
committed by GitHub
102 changed files with 1810 additions and 124 deletions
@@ -1,2 +1,3 @@
- **Intl.Segmenter:** Default Unicode grapheme segmentation, iteration, and `containing()` compile.
Locale negotiation, word and sentence segmentation, `resolvedOptions()`, and detached methods are unsupported.
- **Intl.Segmenter:** Unicode grapheme segmentation, iteration, and `containing()` compile, with the default locale or one language tag of the form language[-script][-region] written as a string literal (grapheme clusters do not depend on the locale).
In JavaScript dependencies, word and sentence segmenters construct, and their `segment()` throws an SC2020 error.
Locale negotiation over several locales, word and sentence segmentation, `resolvedOptions()`, and detached methods are unsupported.
@@ -19,6 +19,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures {
...(features.wasm ? { wasm: true } : {}),
...(features.warningReplace ? { warningReplace: true } : {}),
...(features.stdioExtended ? { stdioExtended: true } : {}),
...(features.moduleNamespace ? { moduleNamespace: true } : {}),
zlib: features.zlib,
...(features.brotli ? { brotli: true } : {}),
assert: features.assert,
@@ -41,6 +41,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"insp.jsonDyn": "scr_util_format_j",
"http.reqH2StreamOrThrow": "scr_http_req_h2_stream_or_throw",
"intl.segmenterNew": "scr_intl_segmenter_new",
"intl.segmenterRefusing": "scr_intl_segmenter_refusing",
"weakMap.is": "scr_weak_map_is",
"weakSet.is": "scr_weak_set_is",
"weakMap.new": "scr_weak_map_new",
@@ -256,6 +257,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"global.native": "scr_global_native",
"process.builtinModule": "scr_process_builtin_module",
"process.builtinUnsupported": "scr_process_builtin_unsupported",
"module.namespaceValue": "scr_module_namespace_value",
"fs.callbackValue": "scr_fs_callback_value",
"fs.callbackCall": "scr_fs_callback_call",
"process.hrtimeValue": "scr_process_hrtime_value",
@@ -995,6 +997,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"dyn.keySetComputed": "scr_dyn_key_set_computed",
"dyn.keyDelete": "scr_dyn_key_delete",
"dyn.keyDeleteComputed": "scr_dyn_key_delete_computed",
"dyn.reflectDeleteProperty": "scr_dyn_reflect_delete_property",
"dyn.hasKeyComputed": "scr_dyn_has_key_computed",
"dyn.hasOwnComputed": "scr_dyn_has_own_computed",
"dyn.propertyIsEnumerableComputed": "scr_dyn_property_is_enumerable_computed",
@@ -34,6 +34,9 @@ export interface NativeLinkFeatures {
/** The program uses process.stdin.setEncoding, string 'data' listeners
* or stdio 'resize' events: links the SCR_STDIO_EXTENDED events unit. */
stdioExtended?: boolean;
/** Module namespace objects as checked-dynamic values link
* scr_module_namespace.c; absent (false) otherwise. */
moduleNamespace?: boolean;
zlib: boolean;
/** node:zlib's Brotli forms: scr_brotli.c and the vendored brotli archive. */
brotli?: boolean;
@@ -147,6 +147,9 @@ export interface CcOptions {
warningReplace?: boolean;
/** SCR_STDIO_EXTENDED: stdin encoding, string data listeners, resize. */
stdioExtended?: boolean;
/** Module namespace objects as checked-dynamic values: compiles
* scr_module_namespace.c. */
moduleNamespace?: boolean;
/** The program uses the process-events surface (signal/exit listeners,
* stdin events, for-await over stdin — moduleUsesProcessEvents on the
* IR): compiles scr_events.c into the binary. Event-free binaries keep
@@ -195,6 +195,8 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
unit((u) => u.opts.dynTier, "scr_js_value.c", "scr_js_object.c"),
unit((u) => u.opts.eventTarget || u.nativeFetch, "scr_event_target.c"),
unit((u) => u.opts.wasm, "scr_wasm.c"),
// Module namespace objects as checked-dynamic values.
unit((u) => u.opts.moduleNamespace === true, "scr_module_namespace.c"),
unit(
(u) =>
u.opts.dynAsync ||
@@ -94,6 +94,7 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures {
warningReplace: boolean;
stdioExtended: boolean;
brotli: boolean;
moduleNamespace: boolean;
nativeFetch: boolean;
netEffective: boolean;
httpEffective: boolean;
@@ -293,6 +294,7 @@ export function effectiveRuntimeFeatures(
warningReplace: features.warningReplace === true,
stdioExtended: features.stdioExtended === true,
brotli: features.brotli === true,
moduleNamespace: features.moduleNamespace === true,
nativeFetch,
netEffective,
httpEffective,
@@ -62,6 +62,7 @@ export interface EarlyExecutableNativeFeatures {
wasm?: boolean;
warningReplace?: boolean;
stdioExtended?: boolean;
moduleNamespace?: boolean;
zlib: boolean;
brotli?: boolean;
assert: boolean;
@@ -19,6 +19,18 @@ export function bindingNeverReassigned(
): boolean {
const cached = lowerer.neverReassignedCache.get(sym);
if (cached !== undefined) return cached;
const written = fileWritesBinding(sym, ts.getSourceFileOfNode(decl), resolve);
lowerer.neverReassignedCache.set(sym, !written);
return !written;
}
/** True when something in `file` writes `sym` (the writes
* bindingNeverReassigned counts), resolving identifiers with `resolve`. */
export function fileWritesBinding(
sym: ts.Symbol,
file: ts.SourceFile,
resolve: (ident: ts.Identifier) => ts.Symbol | null | undefined,
): boolean {
let written = false;
// Text pre-check keeps the file walk cheap: only same-named
// identifiers pay a symbol resolution.
@@ -62,9 +74,8 @@ export function bindingNeverReassigned(
}
ts.forEachChild(n, visit);
};
ts.forEachChild(ts.getSourceFileOfNode(decl), visit);
lowerer.neverReassignedCache.set(sym, !written);
return !written;
ts.forEachChild(file, visit);
return written;
}
/** Strips the value-preserving wrappers off an expression: parens,
@@ -3,7 +3,8 @@ import * as ts from "../../ts7/adapter.js";
import { type Lowerer } from "../lowerer.js";
import { locOf } from "../../program.js";
import { builtinModuleFnOf } from "../surfaces.js";
import { BOOL, DYN, type IrExpr, STRING, arrayOf } from "../../../ir/ir.js";
import { hasRuntimeStatics } from "../class-runtime-statics.js";
import { BOOL, DYN, type IrExpr, STRING, arrayOf, isUnitType } from "../../../ir/ir.js";
/** Reflect.ownKeys uses native computed-key reflection. Reflect.apply(target,
* thisArg, argsList) where the TARGET is a builtin
@@ -63,6 +64,37 @@ export function lowerReflectCall(
loc,
};
}
// Reflect.deleteProperty over a value with a live property table: a
// checked-dynamic value, or a JavaScript class whose statics are runtime
// properties of its constructor
// (`Reflect.deleteProperty(Response, "getResponseState")`).
if (
reflectMember === "deleteProperty" &&
call.arguments.nodes.length === 2 &&
!call.arguments.nodes.some(ts.isSpreadElement)
) {
const loc = locOf(call);
let target = lowerer.lowerExpr(call.arguments.nodes[0]!);
const info =
target.type.kind === "classval" ? lowerer.classes.get(target.type.className) : undefined;
// A primitive target throws the TypeError, so it boxes as is.
const primitive =
isUnitType(target.type) ||
["bool", "f64", "string", "bigint", "symbol"].includes(target.type.kind);
if (
primitive ||
(info !== undefined && hasRuntimeStatics(info) && lowerer.dynConvertible(target.type))
)
target = lowerer.coerceToExpected(target, DYN);
if (target.type.kind !== "dyn") return null;
return {
kind: "libCall",
fn: "dyn.reflectDeleteProperty",
args: [target, lowerer.lowerExprExpecting(call.arguments.nodes[1]!, DYN)],
type: BOOL,
loc,
};
}
if (reflectMember === "get" || reflectMember === "set") {
const required = reflectMember === "get" ? 2 : 3;
if (
@@ -263,6 +263,7 @@ const KEY_CALLS = new Map<string, "object" | "any" | "value">([
["dyn.propertyIsEnumerableComputed", "any"],
["dyn.hasOwnComputed", "value"],
["dyn.keyDeleteComputed", "value"],
["dyn.reflectDeleteProperty", "object"],
["dyn.keySetComputed", "value"],
]);
@@ -58,6 +58,7 @@ import {
contextualUndefReadType,
fenceEarlyAliasUse,
isTemporalGlobalRoot,
cjsNamespaceImportDep,
lowerNsIdentifierValue,
nsUndefRead,
} from "../lower-namespaces.js";
@@ -969,6 +970,37 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc:
// representation (ambient namespaces compile to Node's
// ReferenceError instead — the object never exists at runtime).
{
// `ns.name` through a namespace import of a CommonJS module, where Node's
// namespace has no `name` property (its lexer does not detect it), or
// `ns.default` (module.exports itself).
const cjsNamespace = cjsNamespaceImportDep(lowerer, expr);
if (
cjsNamespace !== null &&
ts.isPropertyAccessExpression(expr.parent) &&
expr.parent.expression === expr &&
ts.isIdentifier(expr.parent.name)
) {
const name = expr.parent.name.text;
const { names, assignedLater } = lowerer.cjsNamespaceOf(cjsNamespace);
if (name === "default")
lowerer.unsupported(
"SC1090",
expr.parent,
`reads of 'default' through namespace imports of CommonJS modules (module.exports itself: read it through a default import)`,
);
if (!names.has(name))
lowerer.unsupported(
"SC1090",
expr.parent,
`reads of '${name}' through namespace imports of CommonJS modules (Node's lexer does not detect '${name}' among the module's exports, so Node reads undefined)`,
);
if (assignedLater === "all" || assignedLater.has(name))
lowerer.unsupported(
"SC1090",
expr.parent,
`reads of '${name}' through namespace imports of CommonJS modules whose functions assign exports (Node's namespace keeps the values from the end of the module's evaluation)`,
);
}
const moduleNs = lowerer.mapTypeOf(lowerer.typeOf(expr));
if (moduleNs?.kind === "moduleNs") {
return { kind: "moduleNsRef", moduleId: moduleNs.moduleId, type: moduleNs, loc };
@@ -175,6 +175,23 @@ export function declaredReturnType(
// `() => void`), and throw-only callbacks are ordinary code
// (`.action(() => { throw ... })`). `never` VALUES stay unmapped.
if (retTsType.flags & ts.TypeFlags.Never) return VOID;
// A JS generator whose result the checker cannot infer (mutually
// recursive methods returning `yield*` of each other, as in yaml's
// Parser) has the return type `any`: a generator over untyped values.
if (
isJsSourceFile(ts.getSourceFileOfNode(decl)) &&
decl.asteriskToken &&
retTsType.flags & ts.TypeFlags.Any
)
return {
kind: "generator",
...(decl.modifiers?.nodes.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)
? { async: true as const }
: {}),
yieldT: DYN,
retT: DYN,
nextT: DYN,
};
let mappedReturn = lowerer.mapTypeOf(retTsType);
// A generator declared to return Iterable<T> returns a generator over T.
if (mappedReturn && decl.asteriskToken)
@@ -8478,7 +8478,17 @@ function lowerObjectStaticCall(
...(fnName !== null ? { fnName } : {}),
};
}
if (target && isUnitType(target.type)) {
// A class value whose statics are runtime properties of its constructor
// boxes to that constructor function, as for Object.defineProperty above.
const runtimeStaticClass =
target?.type.kind === "classval" ? lowerer.classes.get(target.type.className) : undefined;
if (
target &&
(isUnitType(target.type) ||
(runtimeStaticClass !== undefined &&
hasRuntimeStatics(runtimeStaticClass) &&
lowerer.dynConvertible(target.type)))
) {
target = { kind: "dynFrom", value: target, type: DYN, loc: locOf(call.arguments.nodes[0]!) };
}
if (target?.type.kind === "dyn") {
@@ -11,7 +11,7 @@ import { lowerDateStaticCall } from "./builtins/dates.js";
import { keyedHelperName } from "./stable-names.js";
import { lowerValueReturningConstructorNew } from "./js-dynamic-objects.js";
import { adaptPromiseExecutor } from "./promise-executor-adapter.js";
import { dynUndefinedExpr, nodeThrowExpr, varRef } from "../../ir/build.js";
import { dynUndefinedExpr, nodeThrowExpr, strLit, varRef } from "../../ir/build.js";
import {
constructedFromValue,
constructedValue,
@@ -128,6 +128,7 @@ import {
lowerWorkerNew,
} from "./builtins/workers.js";
import {
noLoweringDiag,
unsupportedPackageValueDiag,
unsupportedDiag,
type ScrDiagnostic,
@@ -5695,15 +5696,22 @@ export function lowerStaticFieldRead(
): IrExpr | null {
if (expr.name.text === "prototype") return null;
if (expr.questionDotToken) return null;
const info = staticReceiverClass(lowerer, expr.expression);
if (!info) return null;
return lowerClassStaticRead(lowerer, info, expr.name.text, expr.expression, expr);
}
/** The class a static member read through `receiver` reads exactly: the
* class a name binds (directly, through a require binding, or through a
* const alias), or null. */
export function staticReceiverClass(lowerer: Lowerer, receiver: ts.Expression): ClassInfo | null {
if (
!ts.isIdentifier(expr.expression) &&
expr.expression.kind !== ts.SyntaxKind.ThisKeyword &&
exactClassOfReceiver(lowerer, expr.expression) === null
!ts.isIdentifier(receiver) &&
receiver.kind !== ts.SyntaxKind.ThisKeyword &&
exactClassOfReceiver(lowerer, receiver) === null
)
return null;
const symbol = ts.isIdentifier(expr.expression)
? lowerer.resolveValueSymbol(expr.expression)
: null;
const symbol = ts.isIdentifier(receiver) ? lowerer.resolveValueSymbol(receiver) : null;
const info =
(symbol ? lowerer.classBySymbol.get(symbol) : undefined) ??
// A require binding over `module.exports = class {…}` (the alias
@@ -5711,7 +5719,7 @@ export function lowerStaticFieldRead(
// or `C.name` below would fall through to paths that answer for
// stdlib globals instead of this class.
propertyAssignedClassInfoOf(lowerer, symbol) ??
exactClassOfReceiver(lowerer, expr.expression) ??
exactClassOfReceiver(lowerer, receiver) ??
undefined;
if (!info) return null;
// A decorated name that can REBIND (a replacing decorator): the
@@ -5720,22 +5728,36 @@ export function lowerStaticFieldRead(
// whose devirtualization and .name rules answer for every legal
// runtime value.
if (info.classDecorators?.valueGlobalId !== undefined) return null;
const loc = locOf(expr);
const found = findStaticOn(lowerer, info, expr.name.text);
return info;
}
/** The static member `name` of `info` read through `receiver` (an exact
* reference to the class, see staticReceiverClass): `C.name` for the
* property access `site`, or a destructuring element over `C` whose
* initializer is `site`. Null when the class has no such member. */
export function lowerClassStaticRead(
lowerer: Lowerer,
info: ClassInfo,
name: string,
receiver: ts.Expression,
site: ts.Expression,
): IrExpr | null {
const loc = locOf(site);
const found = findStaticOn(lowerer, info, name);
// A Date static read as a value through a class declared `extends Date`:
// the statics lower only as calls.
if (
!found &&
dateRootOf(info) &&
["now", "parse", "UTC"].includes(expr.name.text) &&
!(ts.isCallExpression(expr.parent) && expr.parent.expression === expr)
["now", "parse", "UTC"].includes(name) &&
!(ts.isCallExpression(site.parent) && site.parent.expression === site)
)
lowerer.noLowering(`Date.${expr.name.text} read through a class that extends Date`, expr);
lowerer.noLowering(`Date.${name} read through a class that extends Date`, site);
if (hasRuntimeStatics(info) && !found?.field)
return {
kind: "dynKeyGet",
value: lowerer.coerceToExpected(classValueRef(lowerer, info, expr.expression), DYN),
key: { kind: "strLit", value: expr.name.text, type: STRING, loc },
value: lowerer.coerceToExpected(classValueRef(lowerer, info, receiver), DYN),
key: { kind: "strLit", value: name, type: STRING, loc },
type: DYN,
loc,
};
@@ -5743,11 +5765,11 @@ export function lowerStaticFieldRead(
// name: in JS the brand lives on that one constructor object, so
// `D.#s` (a subclass receiver) throws Node's TypeError instead of
// reaching up the chain — reported as SC1090 instead of resolved.
if (found && expr.name.text.startsWith("#") && found.declarer !== info) {
if (found && name.startsWith("#") && found.declarer !== info) {
lowerer.unsupported(
"SC1090",
expr,
`reading the private static '${expr.name.text}' through the subclass '${info.def.name.replace(/^%|^%m\d+\./, "")}' (JS brands the declaring class object alone — Node throws a TypeError here; spell the declaring class's name)`,
site,
`reading the private static '${name}' through the subclass '${info.def.name.replace(/^%|^%m\d+\./, "")}' (JS brands the declaring class object alone — Node throws a TypeError here; spell the declaring class's name)`,
);
}
if (found?.field !== undefined) {
@@ -5757,29 +5779,31 @@ export function lowerStaticFieldRead(
type: found.field.type,
loc,
};
return found.field.runtimeOptional ? value : lowerer.maybeNarrow(value, expr);
return found.field.runtimeOptional || !ts.isPropertyAccessExpression(site)
? value
: lowerer.maybeNarrow(value, site);
}
if (found) {
return staticMethodValue(lowerer, found.declarer, expr.name.text, found.method, expr, loc);
return staticMethodValue(lowerer, found.declarer, name, found.method, site, loc);
}
// A GENERIC static method as a VALUE: the pinned-value rule verbatim
// (lowerGenericFnValue) — a slot spelling one concrete signature names
// an instance, an unpinned reference fences by name.
{
const gfound = findGenericStaticOn(lowerer, info, expr.name.text);
if (gfound) return lowerer.lowerGenericFnValue(expr, gfound.info);
const gfound = findGenericStaticOn(lowerer, info, name);
if (gfound) return lowerer.lowerGenericFnValue(site, gfound.info);
}
// `C.name` — the JS-observable class name, a compile-time constant on
// the direct spelling (tsc rejects user statics named `name`, so the
// chain above can never shadow it in TypeScript sources). A class of an
// import cycle reads it from its class object instead, whose reference
// checks the class's initialization (checkCycleBindingAccesses).
if (expr.name.text === "name" && info.def.jsName !== undefined) {
if (name === "name" && info.def.jsName !== undefined) {
if (lowerer.classInitFlags.get(info.def.name)?.cycle === true)
return {
kind: "libCall",
fn: "class.name",
args: [classValueRef(lowerer, info, expr.expression)],
args: [classValueRef(lowerer, info, receiver)],
type: STRING,
loc,
};
@@ -5788,26 +5812,26 @@ export function lowerStaticFieldRead(
if (info.callableBase)
return {
kind: "dynKeyGet",
value: lowerer.coerceToExpected(classValueRef(lowerer, info, expr.expression), DYN),
key: { kind: "strLit", value: expr.name.text, type: STRING, loc },
value: lowerer.coerceToExpected(classValueRef(lowerer, info, receiver), DYN),
key: { kind: "strLit", value: name, type: STRING, loc },
type: DYN,
loc,
};
const data = classStaticDataFor(lowerer, info, expr.name.text, loc);
const data = classStaticDataFor(lowerer, info, name, loc);
if (data) {
const value: IrExpr = {
kind: "dynKeyGet",
value: data,
key: { kind: "strLit", value: expr.name.text, type: STRING, loc },
key: { kind: "strLit", value: name, type: STRING, loc },
type: DYN,
loc,
};
// JavaScript often assigns public statics after the class declaration.
// Their storage stays mutable and checked, while reads retain the
// checker's concrete type instead of losing the method receiver.
const type = isJsSourceFile(ts.getSourceFileOfNode(expr))
const type = isJsSourceFile(ts.getSourceFileOfNode(site))
? null
: lowerer.mapTypeOf(lowerer.typeOf(expr));
: lowerer.mapTypeOf(lowerer.typeOf(site));
return type ? lowerer.coerceToExpected(value, type) : value;
}
return null;
@@ -6633,7 +6657,7 @@ function staticCallOn(
kind: "dynInvoke",
recv: lowerer.lowerExprExpecting(access.expression, DYN),
method: access.name.text,
calleeName: access.name.text,
calleeName: ts.getTextOfNode(access),
args: call.arguments.nodes.map((argument) => lowerer.lowerExprExpecting(argument, DYN)),
type: DYN,
loc,
@@ -9910,34 +9934,54 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
const args = expr.arguments?.nodes ?? [];
const undefinedArg = (arg: ts.Expression): boolean =>
(lowerer.typeOf(arg).flags & ts.TypeFlags.Undefined) !== 0;
if (args.length > 2 || (args[0] && !undefinedArg(args[0]))) {
// A literal language tag: Unicode grapheme clusters have no locale
// tailoring. Only tags of the form language[-script][-region] are
// accepted; each is well-formed, so Node constructs the segmenter
// without the RangeError a malformed tag throws.
const literalLocale = (arg: ts.Expression): boolean =>
ts.isStringLiteralLike(arg) &&
/^[A-Za-z]{2,3}(-[A-Za-z]{4})?(-([A-Za-z]{2}|[0-9]{3}))?$/.test(arg.text);
if (args.length > 2 || (args[0] && !undefinedArg(args[0]) && !literalLocale(args[0]))) {
lowerer.noLowering(
"Intl.Segmenter locale negotiation",
expr,
"omit the locale or pass undefined for default Unicode grapheme segmentation",
"omit the locale, pass undefined, or pass one language tag as a string literal",
);
}
// JavaScript dependencies may construct word and sentence segmenters
// that they use only on some paths (pi-tui's shared word segmenter):
// the construction compiles and their segment() throws SC2020.
// TypeScript sources keep the build-time refusal.
const granularities = isJsSourceFile(ts.getSourceFileOfNode(expr))
? ["grapheme", "word", "sentence"]
: ["grapheme"];
const options = args[1];
if (
options &&
!undefinedArg(options) &&
!(
ts.isObjectLiteralExpression(options) &&
options.properties.nodes.every(
(property) =>
ts.isPropertyAssignment(property) &&
(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) &&
property.name.text === "granularity" &&
ts.isStringLiteral(property.initializer) &&
property.initializer.text === "grapheme",
)
)
) {
lowerer.noLowering(
"Intl.Segmenter options",
options,
'the native segmenter supports default options and { granularity: "grapheme" }; word and sentence segmentation remain unsupported',
);
let granularity = "grapheme";
if (options && !undefinedArg(options)) {
// An object literal of `granularity` entries (none: the default; a
// repeated key: the last one, as in JavaScript).
if (!ts.isObjectLiteralExpression(options))
lowerer.noLowering(
"Intl.Segmenter options",
options,
'the native segmenter supports default options and { granularity: "grapheme" }; word and sentence segmentation remain unsupported',
);
for (const property of options.properties.nodes) {
if (
!ts.isPropertyAssignment(property) ||
!(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) ||
property.name.text !== "granularity" ||
!ts.isStringLiteral(property.initializer) ||
!granularities.includes(property.initializer.text)
) {
lowerer.noLowering(
"Intl.Segmenter options",
options,
'the native segmenter supports default options and { granularity: "grapheme" }; word and sentence segmentation remain unsupported',
);
}
granularity = property.initializer.text;
}
}
// Explicit undefined expressions can carry effects; retain argument order.
const stmts: IrStmt[] = args.map((arg) => {
@@ -9948,7 +9992,28 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
loc: locOf(arg),
};
});
const result: IrExpr = { kind: "libCall", fn: "intl.segmenterNew", args: [], type: DYN, loc };
let result: IrExpr = { kind: "libCall", fn: "intl.segmenterNew", args: [], type: DYN, loc };
// A word or sentence segmenter carries its segment() refusal, which
// coverage lists as deferred to runtime.
if (granularity !== "grapheme") {
const surface = `Intl.Segmenter segmentation with granularity '${granularity}'`;
lowerer.runtimeFences.push(
noLoweringDiag(
surface,
loc,
"only grapheme segmenters segment; word and sentence segmenters construct",
),
);
const line = ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(expr), loc.start).line;
const refusal = `${surface} is not supported yet [SC2020 at ${loc.file}:${line + 1}]`;
result = {
kind: "libCall",
fn: "intl.segmenterRefusing",
args: [strLit(refusal, loc)],
type: DYN,
loc,
};
}
return stmts.length ? { kind: "seqExpr", stmts, result, type: DYN, loc } : result;
}
// A package declaration without a compiled constructor cannot provide
@@ -11310,13 +11375,18 @@ function genericFieldFnNodeOf(
}
/** Whether a JavaScript class's own binding is the target of
* `Object.defineProperty`/`defineProperties` in its file
* (`Object.defineProperty(Definition, "name", { value })`): its constructor
* `Object.defineProperty`/`defineProperties` or `Reflect.deleteProperty` in
* its file (`Object.defineProperty(Definition, "name", { value })`,
* `Reflect.deleteProperty(Response, "getResponseState")`): its constructor
* then needs a live own-property table. */
function definedThroughReflection(lowerer: Lowerer, decl: ts.ClassLikeDeclaration): boolean {
const name = decl.name;
const sf = ts.getSourceFileOfNode(decl);
if (name === undefined || !sf.text.includes("definePropert")) return false;
if (
name === undefined ||
(!sf.text.includes("definePropert") && !sf.text.includes("deleteProperty"))
)
return false;
const symbol = lowerer.checker.getSymbolAtLocation(name);
if (symbol === undefined) return false;
let found = false;
@@ -11325,9 +11395,11 @@ function definedThroughReflection(lowerer: Lowerer, decl: ts.ClassLikeDeclaratio
if (
ts.isCallExpression(node) &&
ts.isPropertyAccessExpression(node.expression) &&
(node.expression.name.text === "defineProperty" ||
(((node.expression.name.text === "defineProperty" ||
node.expression.name.text === "defineProperties") &&
lowerer.stdlibGlobalMember(node.expression, "Object") !== null
lowerer.stdlibGlobalMember(node.expression, "Object") !== null) ||
(node.expression.name.text === "deleteProperty" &&
lowerer.stdlibGlobalMember(node.expression, "Reflect") !== null))
) {
const target = node.arguments.nodes[0];
if (
@@ -42,7 +42,7 @@ import { isPackageShimFile, isShimIntrinsicName, packageShimFor } from "../packa
import type { Lowerer } from "./lowerer.js";
import { boundIdentifiersOf } from "./lowerer.js";
import type { FileParts } from "./lower-modules.js";
import { locOf, resolveImport } from "../program.js";
import { isCjsJsFile, isJsSourceFile, locOf, resolveImport } from "../program.js";
import { F64, type IrExpr, type IrStmt, type IrType, STRING } from "../../ir/ir.js";
import { TEMPORAL_GLOBAL } from "../builtin-modules.js";
@@ -311,6 +311,30 @@ function moduleNsSourceFileOf(lowerer: Lowerer, e: ts.Expression): ts.SourceFile
return null;
}
/** The CommonJS module a JavaScript namespace import binding names
* (`import * as ns from "./m.cjs"`, `ns`), or null. */
export function cjsNamespaceImportDep(lowerer: Lowerer, e: ts.Expression): ts.SourceFile | null {
if (!ts.isIdentifier(e)) return null;
const decl = lowerer.checker.getSymbolAtLocation(e)?.declarations.find(ts.isNamespaceImport);
const stmt = decl?.parent?.parent;
if (
decl === undefined ||
stmt === undefined ||
!ts.isImportDeclaration(stmt) ||
!ts.isStringLiteral(stmt.moduleSpecifier)
)
return null;
const from = ts.getSourceFileOfNode(decl);
if (!isJsSourceFile(from)) return null;
const dep = resolveImport(lowerer.program, from, stmt.moduleSpecifier.text);
return dep !== null &&
!dep.isDeclarationFile &&
lowerer.fileTag.has(dep) &&
isCjsJsFile(dep, lowerer.program)
? dep
: null;
}
/** The member identifier of a qualified namespace reference, when `access`
* reads a member declared in a lowered (or type-only) namespace block —
* or an EXPORT of a program module read through its namespace-import
@@ -330,6 +354,21 @@ export function nsMemberIdentOf(
if (!ts.isIdentifier(access.name)) return null;
const memberSym = lowerer.checker.getSymbolAtLocation(access.name);
if (!memberSym) return null;
// A CommonJS module's namespace has the names Node's lexer detects, with
// the values they had when the module finished evaluating; the member
// then reads like the named import `import { name }`. `default` is
// module.exports itself.
const cjs = cjsNamespaceImportDep(lowerer, access.expression);
if (cjs !== null) {
const { names, assignedLater } = lowerer.cjsNamespaceOf(cjs);
const name = access.name.text;
return name !== "default" &&
names.has(name) &&
assignedLater !== "all" &&
!assignedLater.has(name)
? access.name
: null;
}
if (nsBlockKindOfSymbol(lowerer, memberSym) !== null) return access.name;
if (moduleNsSourceFileOf(lowerer, access.expression) !== null) return access.name;
return null;
@@ -857,6 +896,15 @@ export function nsWritableTarget(
const nsMember = nsMemberIdentOf(lowerer, access);
if (!nsMember) return null;
const memberSym = lowerer.checker.getSymbolAtLocation(nsMember);
// An export read through a module namespace object (`import * as ns`):
// the object is read-only, and Node's assignment throws a TypeError.
if (memberSym && nsBlockKindOfSymbol(lowerer, memberSym) === null) {
lowerer.unsupported(
"SC1090",
access,
`assignment to the module namespace member '${nsMember.text}' (module namespace objects are read-only: Node throws a TypeError)`,
);
}
if (memberSym) fenceEarlyNsMemberRef(lowerer, access, memberSym);
const resolved = lowerer.resolveValueSymbol(nsMember);
const g = resolved ? lowerer.globalsBySymbol.get(resolved) : undefined;
@@ -168,6 +168,7 @@ import {
import {
bindingNeverReassigned,
deadUnmappableBinding,
fileWritesBinding,
nullishExprUnitOf,
nullishGenericBindingUnitOf,
} from "./binding-analysis.js";
@@ -181,7 +182,12 @@ import { classStaticDataFor } from "./class-static-data.js";
import { objectFactorySignature } from "./object-factory-new.js";
import { isClassCallback, lowerClassCallbackAssign } from "./class-callbacks.js";
import { classForInHasKey, classPropertiesHelper } from "./class-dynamic-dispatch.js";
import { genericIfaceBindingKeepsClass, superConditionLead } from "./lower-classes.js";
import {
genericIfaceBindingKeepsClass,
lowerClassStaticRead,
staticReceiverClass,
superConditionLead,
} from "./lower-classes.js";
import {
lowerStreamUnderscoreAssign,
streamClassAliasDecl,
@@ -3085,6 +3091,18 @@ export function lowerBindingPattern(
);
return;
}
// A class in a JavaScript file, destructured through its own name
// (`const { ANY, parse } = Comparator`): each element is the static
// read `Comparator.ANY` — the static getter, field or method as Node
// reads it, with the element's default applied when the read answers
// undefined. Other class-valued sources keep the refusal below.
if (
srcType.kind === "classval" &&
ts.isObjectBindingPattern(pattern) &&
isJsSourceFile(ts.getSourceFileOfNode(pattern)) &&
lowerClassValuePattern(lowerer, pattern, isLet, out)
)
return;
if (srcType.kind === "dyn") {
// A runtime computed first key evaluates after RequireObjectCoercible:
// a nullish source throws before the key's side effects run. Later
@@ -4396,6 +4414,60 @@ function stdlibGlobalTokenDestructure(
return out;
}
/** `const { ANY, parse } = Comparator` in a JavaScript file, where the
* declaration's initializer names the class exactly (staticReceiverClass):
* one static read per element, left to right. False, with nothing
* emitted, for other sources (a parameter or nested pattern over a class
* value), which keep the class-value refusal. */
function lowerClassValuePattern(
lowerer: Lowerer,
pattern: ts.ObjectBindingPattern,
isLet: boolean,
out: IrStmt[],
): boolean {
const decl = pattern.parent;
if (!ts.isVariableDeclaration(decl) || decl.initializer === undefined) return false;
let receiver: ts.Expression = decl.initializer;
while (ts.isParenthesizedExpression(receiver)) receiver = receiver.expression;
const info = staticReceiverClass(lowerer, receiver);
if (info === null) return false;
for (const el of pattern.elements.nodes) {
if (el.name === undefined) continue;
if (el.dotDotDotToken)
lowerer.unsupported(
"SC1031",
el,
"rest bindings over class values (the rest object copies the class's own enumerable statics)",
);
const prop = el.propertyName ?? el.name;
const name =
ts.isIdentifier(prop) ||
ts.isComputedPropertyName(prop) ||
ts.isStringLiteralLike(prop) ||
ts.isNumericLiteral(prop)
? patternKeyNameOf(lowerer, prop as ts.PropertyName)
: null;
if (name === null)
lowerer.unsupported(
"SC1031",
el,
"destructuring with computed keys that do not fold to one property name",
);
if (name === "prototype" || name.startsWith("#"))
lowerer.unsupported("SC1031", el, `destructuring '${name}' from a class value`);
let value = lowerClassStaticRead(lowerer, info, name, receiver, receiver);
if (value === null)
lowerer.unsupported(
"SC1031",
el,
`destructuring the property '${name}' the class '${info.def.jsName ?? info.def.name}' does not declare as a static`,
);
if (el.initializer) value = applyBindingDefault(lowerer, el, value);
lowerer.bindPatternTarget(el.name, value, isLet, out);
}
return true;
}
/** `x = dflt` on a pattern position whose source value always EXISTS
* (tuple positions, record fields): JS evaluates the default exactly
* when the value is undefined — the undefined-arm ternary, lazy, so
@@ -7299,6 +7371,66 @@ function lowerDeleteStatement(lowerer: Lowerer, expr: ts.DeleteExpression): IrSt
);
}
/** True when the `var`/`let` binding `nameNode` names cannot change after
* the top-level CommonJS export statement `exportNode` that exports it by
* reference, so importers read the value Node copies at the export: its
* module's top level declares it once, with an initializer that runs
* before the export, and nothing assigns it. The initializer runs earlier
* in the same module, or in a module the exporting module required before
* the export (`var hljs = require('./core'); module.exports = hljs;` over
* highlight.js's `var highlight = HLJS({}); module.exports = highlight;`). */
function unchangedAfterExport(
lowerer: Lowerer,
nameNode: ts.Identifier,
exportNode: ts.Node,
): boolean {
const topLevelDeclaration = (symbol: ts.Symbol): ts.VariableDeclaration | null => {
const decl = symbol.declarations.length === 1 ? symbol.declarations[0]! : undefined;
return decl !== undefined &&
ts.isVariableDeclaration(decl) &&
ts.isIdentifier(decl.name) &&
decl.initializer !== undefined &&
ts.isVariableDeclarationList(decl.parent) &&
ts.isVariableStatement(decl.parent.parent) &&
ts.isSourceFile(decl.parent.parent.parent)
? decl
: null;
};
const exportFile = ts.getSourceFileOfNode(exportNode);
let exportStatement: ts.Node = exportNode;
for (let parent = exportNode.parent; parent !== undefined && !ts.isSourceFile(parent);) {
if (ts.isFunctionLike(parent) || ts.isClassDeclaration(parent) || ts.isClassExpression(parent))
return false;
exportStatement = parent;
parent = parent.parent;
}
const runsBeforeExport = (decl: ts.VariableDeclaration): boolean => {
const statement = decl.parent?.parent;
return statement?.parent === exportFile && statement.end <= exportStatement.pos;
};
const sym = lowerer.resolveValueSymbol(nameNode);
const decl = sym ? topLevelDeclaration(sym) : null;
if (!sym || !decl || !bindingNeverReassigned(lowerer, sym, decl)) return false;
if (ts.getSourceFileOfNode(decl) === exportFile) return runsBeforeExport(decl);
// A binding of another module: the exporting module's own binding
// `nameNode` names holds that module's require, evaluated before the
// export, and this file does not assign the binding either.
const local = lowerer.checker.getSymbolAtLocation(nameNode);
const localDecl = local ? topLevelDeclaration(local) : null;
if (!localDecl || !runsBeforeExport(localDecl)) return false;
let init: ts.Expression = localDecl.initializer!;
while (ts.isParenthesizedExpression(init)) init = init.expression;
if (
!ts.isCallExpression(init) ||
!ts.isIdentifier(init.expression) ||
init.expression.text !== "require" ||
init.arguments.nodes.length !== 1 ||
!ts.isStringLiteralLike(init.arguments.nodes[0]!)
)
return false;
return !fileWritesBinding(sym, exportFile, (ident) => lowerer.resolveValueSymbol(ident));
}
/** A top-level CommonJS export statement (see cjsExportAssignmentOf):
* `exports.f = <expr>` and each expression-valued property of
* `module.exports = { ... }` assign their pre-registered export globals
@@ -7482,7 +7614,7 @@ function lowerCjsExportStatement(
const fenceMutable = (nameNode: ts.Identifier): void => {
const sym = lowerer.resolveValueSymbol(nameNode);
const g = sym ? lowerer.globalsBySymbol.get(sym) : undefined;
if (g?.mutable) {
if (g?.mutable && !unchangedAfterExport(lowerer, nameNode, cjs.expr)) {
lowerer.unsupported(
"SC1090",
nameNode,
@@ -7642,7 +7774,7 @@ function lowerCjsSingleValueExport(
}
const g = lowerer.globalsBySymbol.get(sym);
if (g) {
if (g.mutable) {
if (g.mutable && !unchangedAfterExport(lowerer, rhs, expr)) {
lowerer.unsupported(
"SC1090",
rhs,
@@ -1,6 +1,6 @@
import { isDeclBoundaryFile } from "../decl-projection.js";
import { relieveHeapPressure } from "../../heap-pressure.js";
import { moduleNamespaceObject } from "./module-namespace-elements.js";
import { moduleNamespaceObject, moduleNamespaceValue } from "./module-namespace-elements.js";
import { keyedHelperName } from "./stable-names.js";
import { positionTag, StructuralPositions } from "./structural-positions.js";
import { onlyForwarded, stripTypeWrappers } from "./typed-arguments.js";
@@ -270,6 +270,9 @@ import {
type StartupCrash,
cjsExportAssignmentOf,
cjsExportDiscardReason,
cjsExportsAssignedInFunctions,
cjsReexportedModules,
cjsNamespaceExportNames,
fallbackDtsPath,
isCheckedJsSourceFile,
isCjsExportTableLiteral,
@@ -4021,6 +4024,42 @@ export class Lowerer {
return null;
}
/** For each CommonJS module read through a JavaScript namespace import:
* the property names of Node's namespace object
* (cjsNamespaceExportNames), and the exports the module assigns inside
* functions, which the namespace does not see
* (cjsExportsAssignedInFunctions). */
private readonly cjsNamespaces = new Map<
ts.SourceFile,
{ names: ReadonlySet<string>; assignedLater: ReadonlySet<string> | "all" }
>();
cjsNamespaceOf(dep: ts.SourceFile): {
names: ReadonlySet<string>;
assignedLater: ReadonlySet<string> | "all";
} {
let namespace = this.cjsNamespaces.get(dep);
if (namespace === undefined) {
// A re-exported name reads the module that assigns it, so the writes
// after evaluation of every re-exported module count too.
let assignedLater: Set<string> | "all" = new Set();
for (const mod of cjsReexportedModules(this.program, dep)) {
const later = cjsExportsAssignedInFunctions(mod);
if (later === "all") {
assignedLater = "all";
break;
}
for (const name of later) assignedLater.add(name);
}
namespace = {
names: cjsNamespaceExportNames(this.program, dep),
assignedLater,
};
this.cjsNamespaces.set(dep, namespace);
}
return namespace;
}
sourceFileOfModuleNamespace(type: IrType): ts.SourceFile | null {
if (type.kind !== "moduleNs" || !type.moduleId.startsWith("file:")) return null;
const path = type.moduleId.slice("file:".length);
@@ -9819,10 +9858,8 @@ export class Lowerer {
// so its module binding is too (`ReleaseEmitter = EventEmitter` over
// `const EventEmitter = require("events")`).
if (expr.type.kind === "moduleNs" && isJsSourceFileName(expr.loc.file)) {
const object = moduleNamespaceObject(
this,
expr as IrExpr & { type: IrType & { kind: "moduleNs" } },
);
const ns = expr as IrExpr & { type: IrType & { kind: "moduleNs" } };
const object = moduleNamespaceObject(this, ns) ?? moduleNamespaceValue(this, ns);
if (object) return object;
}
if (
@@ -1,4 +1,5 @@
import { dynUndefinedExpr, strLit, varRef } from "../../ir/build.js";
import { arrayOf, funcOf } from "../../ir/ir.js";
import * as ts from "../ts7/adapter.js";
import {
DYN,
@@ -9,7 +10,7 @@ import {
type IrType,
} from "../../ir/ir.js";
import { locOf } from "../program.js";
import { newFnCtx, type Lowerer } from "./lowerer.js";
import { newFnCtx, PoisonError, type Lowerer } from "./lowerer.js";
/** Computed namespace reads select live exports instead of copying a
* namespace into a registry snapshot. Constructor registries may contain
@@ -171,3 +172,124 @@ export function moduleNamespaceObject(
loc,
};
}
/** A module namespace taken as an untyped value whose exports a copy
* cannot represent: a binding that can change after initialization (`let`,
* `var`), or an export the copy cannot name. The value is the runtime's
* namespace object (scr_module_namespace_value): one per module, whose
* property reads call a compiled getter for the export's current value and
* whose writes throw Node's TypeError. An export whose value has no
* checked-dynamic form throws its fence when it is read, and listing the
* object's keys or inspecting it throws the "not supported yet" error. */
export function moduleNamespaceValue(
lowerer: Lowerer,
ns: IrExpr & { type: IrType & { kind: "moduleNs" } },
): IrExpr | null {
const source = lowerer.sourceFileOfModuleNamespace(ns.type);
if (!source) return null;
const symbol = lowerer.checker.getSymbolAtLocation(source);
if (!symbol) return null;
const exports: { key: string; declaration: ts.Node | undefined }[] = [];
for (const exported of lowerer.checker.getExportsOfModule(symbol)) {
const target =
exported.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(exported) : exported;
if (!(target.flags & ts.SymbolFlags.Value)) continue;
exports.push({ key: ts.symbolName(exported), declaration: target.valueDeclaration });
}
exports.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
const name = `%module.namespace:${ns.type.moduleId}`;
const loc = ns.loc;
if (!lowerer.liftedFns.some((fn) => fn.name === name)) {
const context = newFnCtx(false, null, null, DYN);
const previousClass = lowerer.currentClass;
lowerer.currentClass = null;
lowerer.fnStack.push(context);
try {
const body: IrStmt[] = [];
for (const { key, declaration } of exports) {
const node = declaration ?? source;
const diagsBefore = lowerer.diags.length;
let read: IrStmt[];
try {
const identifier = declaration?.name as ts.Node | undefined;
if (!identifier || !ts.isIdentifier(identifier)) {
lowerer.unsupported(
"SC1090",
node,
`reading the unnamed export '${key}' through a module namespace object`,
);
}
const value = lowerer.lowerExprExpecting(identifier, DYN);
if (value.type.kind !== "dyn") {
lowerer.unsupported(
"SC1101",
identifier,
`reading the export '${key}' ('${lowerer.fmt(value.type)}') through a module namespace object`,
);
}
read = [{ kind: "return", value, loc }];
} catch (err) {
if (!(err instanceof PoisonError)) throw err;
const fence = lowerer.deferToRuntimeFence(diagsBefore, node, { kind: "statement" });
if (!fence) throw err;
read = [fence];
}
body.push({
kind: "if",
cond: {
kind: "strEq",
negated: false,
left: varRef("key", STRING, loc),
right: strLit(key, loc),
type: { kind: "bool" },
loc,
},
then: read,
else_: null,
loc,
});
}
body.push({ kind: "return", value: dynUndefinedExpr(loc), loc });
lowerer.liftedFns.push({
name,
params: [{ localId: "key", name: "key", type: STRING }],
returnType: DYN,
locals: [{ id: "key", name: "key", type: STRING, mutable: false }, ...context.locals],
body,
loc,
});
} finally {
lowerer.fnStack.pop();
lowerer.currentClass = previousClass;
}
}
const value: IrExpr = {
kind: "libCall",
fn: "module.namespaceValue",
args: [
strLit(ns.type.moduleId, loc),
{
kind: "arrayLit",
elems: exports.map(({ key }) => strLit(key, loc)),
type: arrayOf(STRING),
loc,
},
{
kind: "dynFrom",
value: { kind: "closure", fnName: name, captures: [], type: funcOf([STRING], DYN), loc },
type: DYN,
loc,
},
],
type: DYN,
loc,
};
if (ns.kind === "moduleNsRef") return value;
return {
kind: "seqExpr",
stmts: [{ kind: "exprStmt", expr: ns, loc }],
result: value,
type: DYN,
loc,
};
}
+36 -11
View File
@@ -55,6 +55,10 @@ interface ImplementationShim {
/** Supported major versions and their entry modules, relative to the
* shim root. */
versions: ReadonlyMap<number, string>;
/** Major versions without an entry module that `--npm-static` compiles
* from the package's own published JavaScript. An installed version that
* is in neither list stays out of the program. */
sourceVersions?: ReadonlySet<number>;
/** The entry has a default export (a CommonJS package's whole export
* object, which a default import reads). */
defaultExport?: boolean;
@@ -98,6 +102,7 @@ const IMPLEMENTATION_SHIMS: ReadonlyMap<string, ImplementationShim> = new Map<
mainFile: "source/index.js",
typesFile: "source/index.d.ts",
versions: new Map([[5, "chalk/v5.ts"]]),
sourceVersions: new Set([6]),
},
],
[
@@ -233,10 +238,17 @@ export function configurePackageShims(entryPath: string): void {
unloadedPackages = packages;
}
/** True for a package name the registry implements or the project marks
* never loaded: such a package never compiles its published JavaScript. */
export function isPackageShimName(name: string): boolean {
return IMPLEMENTATION_SHIMS.has(name) || unloadedPackages.has(name);
/** True when `--npm-static` leaves the package `name` out of its package
* list: the project marks it never loaded, or the registry implements it
* and the version installed for `entryPath` has an entry module or is not
* one of the entry's `sourceVersions`. */
export function npmStaticOmitsPackage(entryPath: string, name: string): boolean {
if (unloadedPackages.has(name)) return true;
const shim = IMPLEMENTATION_SHIMS.get(name);
if (shim === undefined) return false;
const dir = installedPackageDir(entryPath, name);
const major = dir === null ? null : installedMajorVersion(dir, name);
return major === null || shim.versions.has(major) || !shim.sourceVersions?.has(major);
}
/** True when `specifier` names a package the project marks never loaded. */
@@ -272,9 +284,9 @@ function installedPackageDir(fromFile: string, name: string): string | null {
return found;
}
/** The shim entry for an installed package directory, or null when the
* directory is not a registered package at a supported major version. */
function shimEntryForPackageDir(pkgDir: string, expectedName: string | null): string | null {
/** The installed package's name and major version from its package.json,
* or null when either is missing. */
function installedPackageVersion(pkgDir: string): { name: string; major: number } | null {
const text = trackedReadFile(join(pkgDir, "package.json"));
if (text === null) return null;
let pkg: { name?: unknown; version?: unknown };
@@ -284,11 +296,24 @@ function shimEntryForPackageDir(pkgDir: string, expectedName: string | null): st
return null;
}
if (typeof pkg.name !== "string" || typeof pkg.version !== "string") return null;
if (expectedName !== null && pkg.name !== expectedName) return null;
const shim = IMPLEMENTATION_SHIMS.get(pkg.name);
return { name: pkg.name, major: Number(/^(\d+)\./.exec(pkg.version)?.[1] ?? NaN) };
}
/** The major version of the package `name` installed at `pkgDir`, or null. */
function installedMajorVersion(pkgDir: string, name: string): number | null {
const installed = installedPackageVersion(pkgDir);
return installed === null || installed.name !== name ? null : installed.major;
}
/** The shim entry for an installed package directory, or null when the
* directory is not a registered package at a supported major version. */
function shimEntryForPackageDir(pkgDir: string, expectedName: string | null): string | null {
const installed = installedPackageVersion(pkgDir);
if (installed === null) return null;
if (expectedName !== null && installed.name !== expectedName) return null;
const shim = IMPLEMENTATION_SHIMS.get(installed.name);
if (shim === undefined) return null;
const major = Number(/^(\d+)\./.exec(pkg.version)?.[1] ?? NaN);
const entry = shim.versions.get(major);
const entry = shim.versions.get(installed.major);
if (entry === undefined) return null;
return join(packageShimRoot(), shim.variant?.(pkgDir) ?? entry);
}
+123 -25
View File
@@ -96,8 +96,8 @@ import type {
import {
configurePackageShims,
isPackageShimFile,
isPackageShimName,
isShimmedImport,
npmStaticOmitsPackage,
} from "./package-shims.js";
import { provenanceEntryFor, provenancePaths } from "./provenance-registry.js";
import { cjsLexedExportsOfFile, cjsVisibleNames } from "./cjs-syntax.js";
@@ -1063,12 +1063,13 @@ export function loadProgram(
}
externalTypes.set(specifier, declarationPath);
}
// Packages scriptc implements itself or the project marks never loaded
// (package-shims.ts) never compile their published JavaScript, even when
// named.
// Packages scriptc implements itself, and packages the project marks
// never loaded (package-shims.ts), never compile their published
// JavaScript, even when named. A registry entry's `sourceVersions`
// (chalk 6) compile from their own source.
configurePackageShims(entryPath);
const npmStaticPackages = [...new Set(opts?.npmStatic ?? [])].filter(
(name) => !isPackageShimName(name),
(name) => !npmStaticOmitsPackage(entryPath, name),
);
// Resolve authored declaration entries before enabling the JS-only
// package resolver. Safe overload metadata binds through the runtime
@@ -5946,9 +5947,15 @@ function preflight7(load: LoadResult): {
// to Node's no-op, the module still evaluates through its
// ordinary init edge, and no member is ever read through the
// lexer facade (self-imports of a CJS module included — the
// partially-built exports are never observed). Any OTHER use
// keeps the fence.
if (!nsBindingUsesAreBareStatements7(program, sf, clause.namedBindings.name)) {
// partially-built exports are never observed). In JavaScript
// sources every use passes here: member reads lower through the
// names Node's lexer detects, and the other reads refuse at
// their site (lower-namespaces.ts, cjsNamespaceImportDep). Any
// OTHER use in TypeScript keeps the fence.
if (
!isJsSourceFileName(sf.fileName) &&
!nsBindingUsesAreBareStatements7(program, sf, clause.namedBindings.name)
) {
diags.push(
unsupportedDiag(
"SC1013",
@@ -6377,6 +6384,108 @@ function preflight7(load: LoadResult): {
* Node refuses the program). This walk asks Node's question at compile
* time, in Node's exact ORDER, and answers with Node's exact message. */
/** The CommonJS program file a re-export (`module.exports = require(spec)`)
* of `from` resolves to, or null: reexport targets union in only when they
* resolve to CommonJS program files (Node's cjsPreparseModuleExports rule). */
function cjsReexportDep7(program: ts.Program) {
return (from: ts.SourceFile, spec: string): ts.SourceFile | null => {
const dep =
resolveImport7(program, from, spec, "require") ??
npmStaticDepSf7(program, from, spec, "require");
return dep !== null && isCjsJsFile7(dep, program) ? dep : null;
};
}
/** Node's lexer analysis of a CommonJS file. A bundler rewrite spells some
* lexer-visible names as accessors. */
function lexWithAccessorNames(sf: ts.SourceFile): ReturnType<typeof cjsLexedExportsOfFile> {
const lexed = cjsLexedExportsOfFile(sf);
for (const name of npmStaticLexerVisibleNames(sf.fileName)) lexed.exports.add(name);
return lexed;
}
/** The property names of Node's namespace object for the CommonJS module
* `dep`: the names its lexer detects, CommonJS re-exports included (the
* rule of the named-import link check), plus `default` and
* `module.exports`. */
/** `dep` and every CommonJS module it re-exports, transitively (the modules
* whose exports its namespace object shows). */
export function cjsReexportedModules(program: ts.Program, dep: ts.SourceFile): ts.SourceFile[] {
const resolveDep = cjsReexportDep7(program);
const seen = new Set<ts.SourceFile>([dep]);
const order = [dep];
for (let i = 0; i < order.length; i++) {
const mod = order[i]!;
for (const spec of lexWithAccessorNames(mod).reexports) {
const next = resolveDep(mod, spec);
if (next !== null && !seen.has(next)) {
seen.add(next);
order.push(next);
}
}
}
return order;
}
export function cjsNamespaceExportNames(program: ts.Program, dep: ts.SourceFile): Set<string> {
const names = cjsVisibleNames(dep, lexWithAccessorNames, cjsReexportDep7(program), new Map());
return new Set([...names, "default", "module.exports"]);
}
/** The export names a CommonJS module assigns inside functions
* (`exports.current = value` in a function body), or "all" when such code
* writes a computed name, replaces module.exports or redefines exports
* through Object.defineProperty. Node's namespace object of the module
* holds the values the exports had when the module finished evaluating,
* so it does not see those writes. */
export function cjsExportsAssignedInFunctions(sf: ts.SourceFile): ReadonlySet<string> | "all" {
const names = new Set<string>();
let all = false;
const exportsObject = (e: ts.Expression): boolean =>
(ts.isIdentifier(e) && e.text === "exports") || isModuleExportsAccess(e);
const target = (e: ts.Expression): void => {
while (ts.isParenthesizedExpression(e)) e = e.expression;
if (isModuleExportsAccess(e)) all = true;
else if (ts.isPropertyAccessExpression(e) && exportsObject(e.expression))
names.add(e.name.text);
else if (ts.isElementAccessExpression(e) && exportsObject(e.expression)) {
if (ts.isStringLiteralLike(e.argumentExpression)) names.add(e.argumentExpression.text);
else all = true;
}
};
const visit = (node: ts.Node, inFunction: boolean): void => {
if (all) return;
const nested = inFunction || ts.isFunctionLike(node);
if (nested) {
if (
ts.isBinaryExpression(node) &&
node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
node.operatorToken.kind <= ts.SyntaxKind.LastAssignment
)
target(node.left);
else if (
(ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
(node.operator === ts.SyntaxKind.PlusPlusToken ||
node.operator === ts.SyntaxKind.MinusMinusToken)
)
target(node.operand);
else if (ts.isDeleteExpression(node)) target(node.expression);
else if (
ts.isCallExpression(node) &&
ts.isPropertyAccessExpression(node.expression) &&
ts.isIdentifier(node.expression.expression) &&
node.expression.expression.text === "Object" &&
node.arguments.nodes[0] !== undefined &&
exportsObject(node.arguments.nodes[0])
)
all = true;
}
ts.forEachChild(node, (child) => visit(child, nested));
};
visit(sf, false);
return all ? "all" : names;
}
interface BadCjsImport {
/** The export name requested from the CJS module. */
exportName: string;
@@ -6417,25 +6526,14 @@ function cjsNamedImportLinkCheck(
const npmStatic = npmStaticDepSf7(program, from, spec);
return npmStatic;
};
// Reexport targets union in only when they resolve to CommonJS program
// files (Node's cjsPreparseModuleExports rule).
const resolveCjsDep = (from: ts.SourceFile, spec: string): ts.SourceFile | null => {
const dep =
resolveImport7(program, from, spec, "require") ??
npmStaticDepSf7(program, from, spec, "require");
return dep !== null && isCjsJsFile7(dep, program) ? dep : null;
};
// A bundler rewrite spells some lexer-visible names as accessors.
const lexWithAccessorNames = (sf: ts.SourceFile): ReturnType<typeof cjsLexedExportsOfFile> => {
const lexed = cjsLexedExportsOfFile(sf);
for (const name of npmStaticLexerVisibleNames(sf.fileName)) lexed.exports.add(name);
return lexed;
};
const resolveCjsDep = cjsReexportDep7(program);
const visible = (dep: ts.SourceFile, name: string): boolean =>
// `default` is the module.exports binding itself — always provided.
// The program already owns native source ASTs; reuse them without
// starting another syntax session or reparsing their source text.
// `default` and `module.exports` are the module.exports binding itself
// — always provided. The program already owns native source ASTs;
// reuse them without starting another syntax session or reparsing
// their source text.
name === "default" ||
name === "module.exports" ||
cjsVisibleNames(dep, lexWithAccessorNames, resolveCjsDep, lexMemo).has(name);
/** The statement's resolved LOCAL CommonJS dependency, when it is an
+1
View File
@@ -596,6 +596,7 @@ async function compileExecutableNative(
...(features.wasm ? { wasm: true } : {}),
...(features.warningReplace ? { warningReplace: true } : {}),
...(features.stdioExtended ? { stdioExtended: true } : {}),
...(features.moduleNamespace ? { moduleNamespace: true } : {}),
zlib: features.zlib,
...(features.brotli ? { brotli: true } : {}),
assert: features.assert,
@@ -685,6 +685,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
"fs.callbackCall",
"dyn.keyDelete",
"dyn.keyDeleteComputed",
"dyn.reflectDeleteProperty",
"dyn.hasKeyComputed",
"dyn.hasOwnComputed",
"dyn.propertyIsEnumerableComputed",
@@ -196,6 +196,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
* arguments; other receiver representations retain a runtime refusal. */
"dyn.keyDelete": { argTypes: [DYN, STRING, BOOL], result: VOID },
"dyn.keyDeleteComputed": { argTypes: [DYN, DYN, BOOL], result: VOID },
"dyn.reflectDeleteProperty": { argTypes: [DYN, DYN], result: BOOL },
"dyn.hasKeyComputed": { argTypes: [DYN, DYN], result: BOOL },
"dyn.hasOwnComputed": { argTypes: [DYN, DYN], result: BOOL },
"dyn.propertyIsEnumerableComputed": { argTypes: [DYN, DYN], result: BOOL },
@@ -3431,6 +3432,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
"process.builtinId": { argTypes: [DYN, arrayOf(STRING)], result: STRING },
"process.builtinModule": { argTypes: [STRING, DYN], result: DYN },
"process.builtinUnsupported": { argTypes: [STRING, STRING], result: DYN },
/** A module namespace object as a checked-dynamic value
* (scr_module_namespace_value): the module id, its sorted export names
* and the compiled getter. One object per module. Never throws. */
"module.namespaceValue": { argTypes: [STRING, arrayOf(STRING), DYN], result: DYN },
"fs.callbackValue": { argTypes: [STRING], result: DYN },
"fs.callbackCall": { argTypes: [STRING, DYN], result: DYN },
"process.hrtimeValue": { argTypes: [], result: DYN },
@@ -3716,6 +3721,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
"ffi.argument": { argTypes: [DYN, STRING], result: DYN },
"ffi.memoryModule": { argTypes: [DYN], result: DYN },
"intl.segmenterNew": { argTypes: [], result: DYN },
"intl.segmenterRefusing": { argTypes: [STRING], result: DYN },
"arrayBuffer.is": { argTypes: [DYN], result: BOOL },
"arrayBuffer.isView": { argTypes: [DYN], result: BOOL },
"arrayBuffer.byteLengthGetter": { argTypes: [], result: F64 },
@@ -1128,6 +1128,9 @@ const RUNTIME_FEATURE_RULES = {
utilPromisify: rule({ fns: ["util.promisifyDyn"] }),
/** process.emitWarning is assigned (process.setEmitWarning). */
warningReplace: rule({ fns: ["process.setEmitWarning"] }),
/** Module namespace objects as checked-dynamic values
* (scr_module_namespace.c). */
moduleNamespace: rule({ fns: ["module.namespaceValue"] }),
/** The WebAssembly API (scr_wasm.c). */
wasm: rule({ prefixes: ["wasm."] }),
/** crypto.subtle importKey/sign/verify/digest and CryptoKey:
@@ -1192,6 +1195,7 @@ function noRuntimeFeatures(): RuntimeFeatures {
searchParams: false,
urlIdna: false,
stdioExtended: false,
moduleNamespace: false,
stream: false,
streamDyn: false,
handleProps: false,
@@ -88,6 +88,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")(
warningReplace: false,
stdioExtended: false,
brotli: false,
moduleNamespace: false,
regex: true,
copying: false,
textDecoderLegacy: false,
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/8214-unshimmed-package-version/main.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/8216-module-namespace-live-values/main.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/8217-intl-segmenter-word-construct/main.ts"
],
"diags": []
}
@@ -0,0 +1,9 @@
{
"order": [
"<repo>/tests/corpus/8218-cjs-namespace-imports/table.cjs",
"<repo>/tests/corpus/8218-cjs-namespace-imports/client/transport.js",
"<repo>/tests/corpus/8218-cjs-namespace-imports/client/index.mjs",
"<repo>/tests/corpus/8218-cjs-namespace-imports/main.mjs"
],
"diags": []
}
@@ -0,0 +1,9 @@
{
"order": [
"<repo>/tests/corpus/8219-cjs-stable-var-exports/lib/core.js",
"<repo>/tests/corpus/8219-cjs-stable-var-exports/lib/index.js",
"<repo>/tests/corpus/8219-cjs-stable-var-exports/lib/options.js",
"<repo>/tests/corpus/8219-cjs-stable-var-exports/main.js"
],
"diags": []
}
@@ -0,0 +1,9 @@
{
"order": [
"<repo>/tests/corpus/8220-js-class-static-destructuring/classes/comparator.js",
"<repo>/tests/corpus/8220-js-class-static-destructuring/ranges/outside.js",
"<repo>/tests/corpus/8220-js-class-static-destructuring/formdata.js",
"<repo>/tests/corpus/8220-js-class-static-destructuring/main.js"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/8221-js-recursive-generator-returns/main.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/8222-js-class-static-reflection.js"
],
"diags": []
}
@@ -81,6 +81,8 @@ export const OPTIONAL_RUNTIME_SOURCES = [
["scr_perf.c", "dynAsync"],
// perf_hooks event-loop metrics, only for programs that read them.
["scr_perf_loop.c", "perfLoop"],
// Module namespace objects as checked-dynamic values.
["scr_module_namespace.c", "moduleNamespace"],
// Blob and File box their reads as dyn promises through scr_async_dyn.c.
["scr_blob.c", "dynAsync"],
["scr_zlib.c", "zlib"],
+10 -2
View File
@@ -1,5 +1,6 @@
/* scriptc runtime — Blob and File (scr_blob.c). Included by scr_runtime.h,
* which says where new declarations go. */
/* scriptc runtime — Blob and File (scr_blob.c), and module namespace
* objects as checked-dynamic values (scr_module_namespace.c). Included by
* scr_runtime.h, which says where new declarations go. */
#ifndef SCR_BLOB_H
#define SCR_BLOB_H
@@ -23,9 +24,16 @@ ScrPromise *scr_blob_array_buffer(ScrDyn *self);
bool scr_blob_is(const ScrDyn *value, double kind);
ScrDyn *scr_intl_segmenter_new(void);
/* A word or sentence Intl.Segmenter whose segment() throws `refusal`
* (borrowed), the compiler's SC2020 message. */
ScrDyn *scr_intl_segmenter_refusing(ScrStr *refusal);
ScrStr *scr_process_builtin_id(ScrDyn *id, ScrArr *known);
ScrDyn *scr_process_builtin_module(ScrStr *id, ScrDyn *getter);
ScrDyn *scr_process_builtin_unsupported(ScrStr *id, ScrStr *member);
/* A module namespace object as a checked-dynamic value (scr_module_namespace.c):
* one handle per module `id`, whose reads call `getter` with the export
* name when `keys` lists it. Borrows all three; +1 handle. */
ScrDyn *scr_module_namespace_value(ScrStr *id, ScrArr *keys, ScrDyn *getter);
ScrDyn *scr_global_native(ScrArr *known);
ScrDyn *scr_global_native_init(ScrArr *known, ScrDyn *(*console_get)(void));
ScrDyn *scr_console_native(void);
+3
View File
@@ -186,6 +186,7 @@ typedef enum {
X(MATCH_SOURCE, 55) /* a match result's pending metadata (scr_regex.c) */ \
X(MESSAGE_EVENT, 50) /* ScrEvent with data — BroadcastChannel's 'message' */ \
X(MESSAGE_PORT, 36) \
X(MODULE_NAMESPACE, 78) /* module namespace object read through its exports (scr_module_namespace.c) */ \
X(NET_BLOCK_LIST, 48) /* net.BlockList (scr_net.c) */ \
X(NET_SERVER, 3) /* ScrNetServer — net.Server (http.Server rides the same handle) */ \
X(NET_SOCKET, 2) /* ScrNetSocket — net.Socket */ \
@@ -195,6 +196,7 @@ typedef enum {
X(REGEXP, 26) /* native RegExp, shared compiled pattern */ \
X(SEARCH_PARAMS, 33) /* live WHATWG URLSearchParams */ \
X(SEGMENTER, 19) /* default Unicode grapheme segmentation */ \
X(SEGMENTER_REFUSING, 80) /* word or sentence Segmenter whose segment() throws (scr_grapheme.c) */ \
X(SEGMENTS, 20) /* iterable immutable segmentation result */ \
X(SET, 25) /* native Set<unknown>, shared backing map */ \
X(SHARED_ARRAY_BUFFER, 37) \
@@ -876,6 +878,7 @@ bool scr_dyn_has_key_computed(const ScrDyn *value, const ScrDyn *key);
bool scr_dyn_has_own_computed(const ScrDyn *value, const ScrDyn *key);
bool scr_dyn_property_is_enumerable_computed(const ScrDyn *value, const ScrDyn *key);
void scr_dyn_key_delete_computed(ScrDyn *value, const ScrDyn *key, bool strict);
bool scr_dyn_reflect_delete_property(ScrDyn *target, const ScrDyn *key);
ScrStr *scr_dyn_string_constructor(const ScrDyn *value);
ScrDyn *scr_dyn_property_key_value(const ScrDyn *value);
ScrDyn *scr_dyn_to_numeric(const ScrDyn *value);
+55
View File
@@ -201,3 +201,58 @@ ScrDyn *scr_intl_segmenter_new(void) {
scr_dyn_handle_install(SCR_DYNH_SEGMENTS, &segments);
return scr_graphemes_box(NULL, SCR_DYNH_SEGMENTER);
}
/* A word or sentence Segmenter, which JavaScript dependencies may build at
* module load and use on some paths only: it constructs, and its segment()
* throws the compiler's SC2020 message. */
typedef struct {
size_t rc;
ScrStr *refusal; /* retained */
} ScrRefusingSegmenter;
static void *scr_refusing_segmenter_retain(void *ptr) {
ScrRefusingSegmenter *value = ptr;
value->rc++;
return value;
}
static void scr_refusing_segmenter_release(void *ptr) {
ScrRefusingSegmenter *value = ptr;
if (--value->rc) return;
scr_str_release(value->refusal);
free(value);
}
static ScrDyn *scr_refusing_segmenter_invoke(void *ptr, ScrDyn *self, const char *method,
ScrDyn *const *args, size_t argc, const char *what) {
ScrRefusingSegmenter *value = ptr;
if (strcmp(method, "segment") == 0) {
ScrStr *input = scr_dyn_string_coerce_js(argc ? args[0] : scr_dyn_undefined());
if (!input) return NULL;
scr_str_release(input);
scr_throw_error_msg_code(SCR_ERR_ERROR, value->refusal->data, value->refusal->len, "SC2020");
return NULL;
}
return scr_graphemes_invoke(&(ScrGraphemes){ .rc = 1 }, self, method, args, argc, what);
}
static ScrDyn *scr_refusing_segmenter_get(void *ptr, const char *key, size_t len) {
(void)ptr;
return scr_graphemes_get(&(ScrGraphemes){ .rc = 1 }, key, len);
}
ScrDyn *scr_intl_segmenter_refusing(ScrStr *refusal) {
static const ScrDynHandleOps segmenter = {
.cls = "Segmenter", .retain = scr_refusing_segmenter_retain,
.release = scr_refusing_segmenter_release, .invoke = scr_refusing_segmenter_invoke,
.get = scr_refusing_segmenter_get, .set = scr_graphemes_set,
};
scr_dyn_handle_install(SCR_DYNH_SEGMENTER_REFUSING, &segmenter);
ScrRefusingSegmenter *value = calloc(1, sizeof *value);
if (!value) scr_trap("scriptc: out of memory\n");
value->rc = 1;
value->refusal = scr_str_retain(refusal);
ScrDyn *boxed = scr_dyn_new_handle(value, SCR_DYNH_SEGMENTER_REFUSING);
scr_refusing_segmenter_release(value);
return boxed;
}
+39
View File
@@ -8174,6 +8174,45 @@ void scr_dyn_key_delete_computed(ScrDyn *value, const ScrDyn *raw_key, bool stri
scr_dyn_release(key);
}
/* Reflect.deleteProperty(target, key): a TypeError for a primitive target,
* then the key's ToPropertyKey and a sloppy-mode delete. Answers whether
* the property is absent afterwards (false when it is non-configurable).
* A Proxy would observe the own-property check through its traps, and a
* compiled object's native fields keep their values after a delete through
* its checked-dynamic view, so both refuse. */
bool scr_dyn_reflect_delete_property(ScrDyn *target, const ScrDyn *raw_key) {
switch (target->kind) {
case SCR_DYN_NULL:
case SCR_DYN_UNDEF:
case SCR_DYN_BOOL:
case SCR_DYN_NUM:
case SCR_DYN_STR:
case SCR_DYN_BIGINT:
case SCR_DYN_SYMBOL: {
static const char message[] = "Reflect.deleteProperty called on non-object";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
return false;
}
default:
break;
}
if (target->kind == SCR_DYN_PROXY) {
scr_dyn_proxy_unsupported("Reflect.deleteProperty");
return false;
}
if (target->kind == SCR_DYN_TYPED_REF) {
static const char message[] = "Reflect.deleteProperty on a compiled object is not supported yet";
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
return false;
}
ScrDyn *key = scr_dyn_property_key_value(raw_key);
if (!key) return false;
scr_dyn_key_delete_computed(target, key, false);
bool deleted = !scr_exc_pending() && !scr_dyn_has_own_computed(target, key);
scr_dyn_release(key);
return deleted;
}
/* Native call/apply/bind lowering still owns these prototype operations.
* Refuse overrides until every typed call site observes the property table. */
static bool scr_dyn_fn_property_fence(const ScrStr *key) {
+121
View File
@@ -0,0 +1,121 @@
/* Module namespace objects as checked-dynamic values
* (module-namespace-elements.ts lowers `import * as ns` values here). */
#include "scr_runtime.h"
#include <stdlib.h>
#include <string.h>
/* A module namespace object taken as a checked-dynamic value
* (module-namespace-elements.ts). The compiled getter answers an export's
* current value by name, so reads see live bindings; `keys` lists the
* namespace's export names. One object per module keeps its identity.
* Writes throw Node's strict-mode TypeError for a frozen namespace. */
typedef struct ScrModuleNamespace {
size_t rc;
ScrStr *id;
ScrDyn *getter;
ScrArr *keys;
struct ScrModuleNamespace *next;
} ScrModuleNamespace;
static SCR_TL ScrModuleNamespace *scr_module_namespaces;
static SCR_TL bool scr_module_namespace_cleanup_registered;
static void *scr_module_namespace_retain(void *handle) {
ScrModuleNamespace *ns = handle;
ns->rc++;
return ns;
}
static void scr_module_namespace_release(void *handle) {
ScrModuleNamespace *ns = handle;
if (--ns->rc) return;
scr_str_release(ns->id);
scr_dyn_release(ns->getter);
scr_arr_release(ns->keys);
free(ns);
}
static void scr_module_namespace_cleanup(void) {
while (scr_module_namespaces) {
ScrModuleNamespace *ns = scr_module_namespaces;
scr_module_namespaces = ns->next;
scr_module_namespace_release(ns);
}
}
static bool scr_module_namespace_has(const ScrModuleNamespace *ns, const char *key, size_t length) {
for (size_t i = 0; i < ns->keys->len; i++) {
ScrStr *entry = (ScrStr *)scr_arr_get_ref(ns->keys, (double)i);
bool same = entry->len == length && memcmp(entry->data, key, length) == 0;
scr_str_release(entry);
if (same) return true;
}
return false;
}
static ScrDyn *scr_module_namespace_get(void *handle, const char *key, size_t length) {
ScrModuleNamespace *ns = handle;
if (!scr_module_namespace_has(ns, key, length)) return NULL;
ScrStr *name = scr_str_new(key, length);
ScrDyn *arg = scr_dyn_new_str(name);
scr_str_release(name);
ScrDyn *result = scr_dyn_call(ns->getter, &arg, 1, "module namespace export");
scr_dyn_release(arg);
return result;
}
static bool scr_module_namespace_set(void *handle, const char *key, size_t length, const ScrDyn *value) {
(void)value;
ScrModuleNamespace *ns = handle;
ScrJsonBuf text;
scr_jb_init(&text);
if (scr_module_namespace_has(ns, key, length)) {
scr_jb_puts(&text, "Cannot assign to read only property '");
scr_jb_write(&text, key, length);
scr_jb_puts(&text, "' of object '[object Module]'");
} else {
scr_jb_puts(&text, "Cannot add property ");
scr_jb_write(&text, key, length);
scr_jb_puts(&text, ", object is not extensible");
}
ScrStr *message = scr_jb_finish(&text);
scr_throw_error_msg(SCR_ERR_TYPE, message->data, message->len);
scr_str_release(message);
return true;
}
static ScrDyn *scr_module_namespace_invoke(void *handle, ScrDyn *self, const char *key,
ScrDyn *const *args, size_t argc, const char *what) {
ScrDyn *fn = scr_module_namespace_get(handle, key, strlen(key));
if (fn == NULL) {
if (scr_exc_pending()) return NULL;
fn = scr_dyn_retain(scr_dyn_undefined());
}
scr_dyn_this_push_dyn(self);
ScrDyn *result = scr_dyn_call(fn, args, argc, what);
scr_dyn_this_pop();
scr_dyn_release(fn);
return result;
}
ScrDyn *scr_module_namespace_value(ScrStr *id, ScrArr *keys, ScrDyn *getter) {
static const ScrDynHandleOps ops = {
.cls = "Module", .retain = scr_module_namespace_retain, .release = scr_module_namespace_release,
.get = scr_module_namespace_get, .set = scr_module_namespace_set, .invoke = scr_module_namespace_invoke,
};
scr_dyn_handle_install(SCR_DYNH_MODULE_NAMESPACE, &ops);
for (ScrModuleNamespace *ns = scr_module_namespaces; ns; ns = ns->next) {
if (ns->id->len == id->len && memcmp(ns->id->data, id->data, id->len) == 0)
return scr_dyn_new_handle(ns, SCR_DYNH_MODULE_NAMESPACE);
}
if (!scr_module_namespace_cleanup_registered) {
scr_atexit(scr_module_namespace_cleanup);
scr_module_namespace_cleanup_registered = true;
}
ScrModuleNamespace *ns = malloc(sizeof(*ns));
if (!ns) scr_trap("scriptc: out of memory\n");
*ns = (ScrModuleNamespace){ 1, scr_str_retain(id), scr_dyn_retain(getter), scr_arr_retain(keys), scr_module_namespaces };
scr_module_namespaces = ns;
return scr_dyn_new_handle(ns, SCR_DYNH_MODULE_NAMESPACE);
}
@@ -0,0 +1,5 @@
// A package that scriptc implements at some major versions (chalk 5)
// compiles from its own source at a version without an implementation.
import chalk, { level } from "chalk";
console.log(chalk.version, level, chalk.red("red"), chalk.bold("bold"));
@@ -0,0 +1 @@
{ "name": "chalk", "version": "6.0.0", "type": "module", "exports": { "types": "./source/index.d.ts", "default": "./source/index.js" }, "imports": { "#styles": "./source/styles.js" } }
@@ -0,0 +1,3 @@
export declare const level: number;
declare const chalk: { version: number; red(text: string): string; bold(text: string): string };
export default chalk;
@@ -0,0 +1,16 @@
import { styles } from "#styles";
export const level = 0;
function paint(name, text) {
const [open, close] = styles[name];
return level > 0 ? `\u001B[${open}m${text}\u001B[${close}m` : `${text}`;
}
const chalk = {
version: 6,
red: (text) => paint("red", text),
bold: (text) => paint("bold", text),
};
export default chalk;
@@ -0,0 +1 @@
export const styles = { red: [31, 39], bold: [1, 22] };
@@ -0,0 +1,11 @@
// A module namespace object stored as an untyped value, here in a registry
// keyed by module name (pi-coding-agent's VIRTUAL_MODULES): reads see the
// exports' current values, both registry entries are the same object, and
// writes throw Node's TypeError.
import { addThrough, bumpThrough, describe, same, writeThrough } from "ns-registry";
console.log(describe());
bumpThrough();
bumpThrough();
console.log(describe());
console.log(same(), writeThrough(), addThrough());
@@ -0,0 +1,7 @@
export let count = 0;
export var legacy = "legacy";
export const label = "counter";
export function bump() {
count++;
legacy = `legacy ${count}`;
}
@@ -0,0 +1,5 @@
export declare function describe(): string;
export declare function bumpThrough(): void;
export declare function same(): boolean;
export declare function writeThrough(): string;
export declare function addThrough(): string;
@@ -0,0 +1,40 @@
import * as counter from "./counter.js";
export const registry = {
counter: counter,
"alias/counter": counter,
};
export function describe() {
const ns = registry["counter"];
return `${ns.count} ${ns.legacy} ${ns.label} ${typeof ns.bump} ${typeof ns}`;
}
export function bumpThrough() {
registry.counter.bump();
}
export function same() {
return registry.counter === registry["alias/counter"];
}
function assign(target, key, value) {
try {
target[key] = value;
} catch (error) {
return `${error.name}: ${error.message}`;
}
return "no error";
}
function lookup(name) {
return registry[name];
}
export function writeThrough() {
return assign(lookup("counter"), "count", 5);
}
export function addThrough() {
return assign(lookup("alias/counter"), "extra", 5);
}
@@ -0,0 +1 @@
{ "name": "ns-registry", "version": "1.0.0", "type": "module", "main": "index.js", "types": "index.d.ts" }
@@ -0,0 +1,10 @@
// A package builds its segmenters at module load (pi-tui, grok-mermaid):
// a grapheme segmenter with a locale tag, one with empty options, and a
// word segmenter that only some paths use. Grapheme segmentation matches
// Node; the word segmenter constructs, and this program never segments
// with it.
import { columns, describe, plainColumns } from "text-width";
console.log(describe());
console.log(columns("éa👍🏽🇫🇷"), columns("abc"));
console.log(plainColumns("a\u0301b\u{1F44D}\u{1F3FD}"));
@@ -0,0 +1,3 @@
export declare function columns(text: string): string;
export declare function describe(): string;
export declare function plainColumns(text: string): number;
@@ -0,0 +1,16 @@
const graphemes = new Intl.Segmenter("en", { granularity: "grapheme" });
const words = new Intl.Segmenter(undefined, { granularity: "word" });
const sentences = new Intl.Segmenter(undefined, { granularity: "sentence" });
const plain = new Intl.Segmenter(undefined, {});
export function columns(text) {
return [...graphemes.segment(text)].map((part) => part.segment).join("|");
}
export function plainColumns(text) {
return [...plain.segment(text)].length;
}
export function describe() {
return `${typeof words} ${typeof sentences}`;
}
@@ -0,0 +1 @@
{ "name": "text-width", "version": "1.0.0", "type": "module", "main": "index.js", "types": "index.d.ts" }
@@ -0,0 +1,12 @@
import * as transport from './transport.js'
export function connect (origin) {
const pool = new transport.Pool(origin, transport.defaults)
console.log(transport.setGlobalDispatcher(pool), pool.connections, transport.defaults.keepAlive)
return pool
}
export function describe (pool) {
const format = transport.format
return format(pool.request('/status'))
}
@@ -0,0 +1 @@
{ "name": "ns-client", "version": "1.0.0", "main": "index.mjs" }
@@ -0,0 +1,26 @@
'use strict'
// A CommonJS module of the package: member exports, a class, and a table
// whose getter stops Node's lexer before the names after it.
class Pool {
constructor (origin, options) {
this.origin = origin
this.connections = options.connections
this.requests = 0
}
request (path) {
this.requests += 1
return `${this.origin}${path} #${this.requests}`
}
}
function setGlobalDispatcher (dispatcher) {
exports.current = dispatcher
return dispatcher.origin
}
exports.Pool = Pool
exports.setGlobalDispatcher = setGlobalDispatcher
exports.current = null
exports.defaults = { connections: 4, keepAlive: true }
exports.format = function format (value) { return `[${value}]` }
@@ -0,0 +1,13 @@
// Namespace imports of CommonJS modules in JavaScript: member reads, calls,
// constructions and a method read as a value, through a package's ES
// module and directly. Each name is one Node's lexer detects.
import * as table from './table.cjs'
import { connect, describe } from './client/index.mjs'
console.log(table.add(20, 22), table.scale)
const add = table.add
console.log(add(1, 2))
const pool = connect('http://localhost')
console.log(describe(pool))
console.log(describe(pool))
@@ -0,0 +1,10 @@
'use strict'
const add = (a, b) => a + b
const scale = 10
let calls = 0
module.exports = {
add,
scale,
get calls () { return calls },
bump () { calls += 1 }
}
@@ -0,0 +1,31 @@
// highlight.js's lib/core.js shape: the module builds one instance and
// exports the var that holds it.
'use strict';
const HLJS = function (options) {
const languages = new Map();
const prefix = options.classPrefix || 'hljs-';
function registerLanguage(name, keywords) {
languages.set(name, keywords);
}
function listLanguages() {
return Array.from(languages.keys());
}
function highlight(code, name) {
const keywords = languages.get(name);
if (keywords === undefined) throw new Error(`Unknown language: "${name}"`);
return code
.split(' ')
.map((word) => (keywords.includes(word) ? `<span class="${prefix}keyword">${word}</span>` : word))
.join(' ');
}
return { registerLanguage, listLanguages, highlight, versionString: '10.7.3' };
};
var highlight = HLJS({});
module.exports = highlight;
@@ -0,0 +1,8 @@
// highlight.js's lib/index.js shape: require the core instance, register
// languages on it and export the same var.
var hljs = require('./core');
hljs.registerLanguage('javascript', ['const', 'let', 'function', 'return']);
hljs.registerLanguage('python', ['def', 'return', 'lambda']);
module.exports = hljs;
@@ -0,0 +1,12 @@
// A let that is never reassigned, exported in a table.
'use strict';
let tabReplace = ' ';
let calls = [];
function render(text) {
calls.push(text);
return text.split('\t').join(tabReplace);
}
module.exports = { tabReplace, calls, render };
@@ -0,0 +1,21 @@
// A var and a let exported by reference, where neither changes after the
// export: importers see the same instance and value as Node.
'use strict';
const hljs = require('./lib');
const core = require('./lib/core');
const options = require('./lib/options');
console.log(hljs === core, hljs.versionString);
console.log(hljs.listLanguages());
console.log(hljs.highlight('const x = function', 'javascript'));
hljs.registerLanguage('ruby', ['def', 'end']);
console.log(core.listLanguages().length, core.highlight('def run end', 'ruby'));
try {
hljs.highlight('x', 'cobol');
} catch (error) {
console.log(error.message);
}
console.log(JSON.stringify(options.tabReplace), options.render('a\tb'), options.render('\tc'));
console.log(options.calls);
@@ -0,0 +1,3 @@
{
"type": "commonjs"
}
@@ -0,0 +1,22 @@
'use strict'
// semver's Comparator: a static getter returning a module symbol.
const ANY = Symbol('SemVer ANY')
class Comparator {
static get ANY () {
return ANY
}
static loose = false
static parse (text) {
return new Comparator(text)
}
constructor (text) {
this.semver = text === '' ? ANY : text
this.value = text === '' ? '' : `>=${text}`
}
}
module.exports = Comparator
@@ -0,0 +1,26 @@
'use strict'
// undici's FormData: statics that read private instance state, taken off
// the class before the class is exposed.
class FormData {
#state = []
append (name, value) {
this.#state.push({ name, value })
}
static getFormDataState (formData) {
return formData.#state
}
static setFormDataState (formData, state) {
formData.#state = state
}
static get boundary () {
return undefined
}
}
const { getFormDataState, setFormDataState, boundary = '----formdata-default' } = FormData
module.exports = { FormData, getFormDataState, setFormDataState, boundary }
@@ -0,0 +1,39 @@
'use strict'
// Object patterns over a class value read its static getters, fields and
// methods as Node does, defaults included.
const Comparator = require('./classes/comparator')
const { outside, ANY, any, isLoose, name } = require('./ranges/outside')
const { FormData, getFormDataState, setFormDataState, boundary } = require('./formdata')
console.log(typeof ANY, ANY === Comparator.ANY, any === ANY, isLoose, name)
console.log(outside(''), outside('1.2.3'))
console.log(Comparator.parse('').semver === ANY, Comparator.parse('2.0.0').semver === ANY)
const form = new FormData()
form.append('a', 1)
form.append('b', 'two')
console.log(getFormDataState(form))
setFormDataState(form, [{ name: 'c', value: true }])
console.log(getFormDataState(form), boundary)
class Defaults {
static count = 0
static missing = undefined
static nested = { inner: 'value' }
}
const { count = 5, missing = 'fallback', nested: { inner, other = 'other' } } = Defaults
console.log(count, missing, inner, other)
// Inherited statics, and a static getter reading the class it is read
// through.
class Base {
static tag = 'base'
static get label () { return `label:${this.tag}` }
static make () { return this.tag }
}
class Sub extends Base {
static tag = 'sub'
}
const { tag, label, make, name: subName } = Sub
const { label: baseLabel } = Base
console.log(tag, label, typeof make, subName, baseLabel)
@@ -0,0 +1,3 @@
{
"type": "commonjs"
}
@@ -0,0 +1,13 @@
'use strict'
// semver's ranges/outside.js and ranges/subset.js destructure the class's
// statics at module load.
const Comparator = require('../classes/comparator')
const { ANY } = Comparator
const { ANY: any, loose: isLoose = true, parse, name } = Comparator
const outside = (text) => {
const comparator = parse(text)
return comparator.semver === ANY ? 'any' : comparator.value
}
module.exports = { outside, ANY, any, isLoose, name }
@@ -0,0 +1,10 @@
// A JavaScript package's parser of mutually recursive generator methods that
// return `yield*` of each other (yaml 2.9.0's parse/parser.js): the checker
// cannot infer their results. Tokens, values passed to next() and return
// values reach each generator in the same order as in Node.
import { parseTokens, drive, spreadTypes, walkAll } from "token-parser";
for (const token of parseTokens("a { b [ c d ] e } f")) console.log(token);
console.log(drive("{ x } y ! z"));
console.log(spreadTypes("[ 1 2 3 ]"));
console.log(await walkAll("a ( b c ) d ( e )"));
@@ -0,0 +1,4 @@
export declare function parseTokens(source: string): string[];
export declare function drive(source: string): string;
export declare function spreadTypes(source: string): string;
export declare function walkAll(source: string): Promise<string>;
@@ -0,0 +1,131 @@
'use strict'
class Parser {
constructor (source) {
this.tokens = source.split(' ')
this.index = 0
this.stack = []
}
* parse () {
while (this.index < this.tokens.length) {
const done = yield * this.step()
if (done) return 'stopped'
}
return yield * this.end()
}
* step () {
const token = this.tokens[this.index++]
if (token === '{') return yield * this.blockMap(token)
if (token === '[') return yield * this.flowSeq(token)
if (token === '!') return true
return yield * this.scalar(token)
}
* blockMap (open) {
this.stack.push(open)
yield { type: 'map-start', depth: this.stack.length }
while (this.index < this.tokens.length && this.tokens[this.index] !== '}') {
const stop = yield * this.step()
if (stop) return stop
}
this.index++
this.stack.pop()
const reply = yield { type: 'map-end', depth: this.stack.length + 1 }
if (reply !== undefined) yield { type: 'reply', value: reply }
return false
}
* flowSeq (open) {
this.stack.push(open)
let count = 0
while (this.index < this.tokens.length && this.tokens[this.index] !== ']') {
yield * this.step()
count++
}
this.index++
this.stack.pop()
yield { type: 'seq', count }
return false
}
* scalar (token) {
const value = yield { type: 'scalar', source: token }
if (value !== undefined) return yield * this.scalar(String(value))
return false
}
* end () {
yield { type: 'end', depth: this.stack.length }
return this.index
}
}
function parseTokens (source) {
const tokens = []
for (const token of new Parser(source).parse()) tokens.push(JSON.stringify(token))
return tokens
}
// next(value) reaches the innermost delegated generator, and return values
// pass back through each yield*.
function drive (source) {
const parser = new Parser(source).parse()
const log = []
let result = parser.next()
while (!result.done) {
const value = result.value
log.push(JSON.stringify(value))
result = parser.next(value.type === 'map-end' ? 'ack' : value.source === 'x' ? 'x2' : undefined)
}
log.push('return ' + result.value)
return log.join('\n')
}
function spreadTypes (source) {
const tokens = [...new Parser(source).parse()]
return tokens.length + ' ' + tokens.map((token) => token.type).join(',')
}
// Async generator methods with the same mutual recursion.
class AsyncWalker {
constructor (source) {
this.tokens = source.split(' ')
this.index = 0
}
async * walk () {
while (this.index < this.tokens.length) yield * this.next()
return this.index
}
async * next () {
const token = this.tokens[this.index++]
await null
if (token === '(') return yield * this.group()
yield token
return token
}
async * group () {
const inner = []
while (this.tokens[this.index] !== ')') inner.push(yield * this.next())
this.index++
yield 'group:' + inner.join('+')
return inner.length
}
}
async function walkAll (source) {
const seen = []
const walker = new AsyncWalker(source).walk()
let result = await walker.next()
while (!result.done) {
seen.push(result.value)
result = await walker.next()
}
return seen.join(' ') + ' -> ' + result.value
}
module.exports = { parseTokens, drive, spreadTypes, walkAll }
@@ -0,0 +1 @@
{ "name": "token-parser", "version": "1.0.0", "main": "dist/index.js", "types": "dist/index.d.ts" }
@@ -0,0 +1,94 @@
'use strict'
// A class's statics extracted, then removed with Reflect.deleteProperty, and
// redefined with Object.defineProperties (undici's FormData and Response).
const kEnumerableProperty = { __proto__: null, enumerable: true }
class FormData {
#state = []
append (name, value) { this.#state.push({ name, value }) }
static getFormDataState (formData) { return formData.#state }
static setFormDataState (formData, state) { formData.#state = state }
}
const getFormDataState = FormData.getFormDataState
const setFormDataState = FormData.setFormDataState
console.log(Reflect.deleteProperty(FormData, 'getFormDataState'))
console.log(Reflect.deleteProperty(FormData, 'setFormDataState'))
console.log(Reflect.deleteProperty(FormData, 'missing'))
console.log(typeof FormData.getFormDataState, typeof FormData.setFormDataState)
const form = new FormData()
form.append('a', 1)
form.append('b', 'two')
console.log(getFormDataState(form))
setFormDataState(form, [{ name: 'c', value: true }])
console.log(getFormDataState(form).length, getFormDataState(form)[0].name)
try {
FormData.getFormDataState(form)
} catch (error) {
console.log(error.constructor.name, error.message)
}
class Response {
#status
constructor (status) { this.#status = status }
get status () { return this.#status }
static json () { return new Response(200) }
static error () { return new Response(0) }
static getResponseState (response) { return response.#status }
}
const getResponseState = Response.getResponseState
Reflect.deleteProperty(Response, 'getResponseState')
Object.defineProperties(Response, {
json: kEnumerableProperty,
error: kEnumerableProperty
})
const response = Response.json()
console.log(response.status, Response.error().status, getResponseState(response))
for (const key of ['json', 'error', 'getResponseState']) {
const descriptor = Object.getOwnPropertyDescriptor(Response, key)
console.log(key, descriptor === undefined ? 'absent' : [descriptor.enumerable, descriptor.writable, descriptor.configurable, typeof descriptor.value])
}
// Statics a subclass inherits, a static field and a static accessor.
class Base {
static helper () { return 'helper' }
static count = 3
static get label () { return 'label' }
}
class Child extends Base {}
console.log(Child.helper(), Child.count, Child.label)
console.log(Reflect.deleteProperty(Base, 'helper'), Reflect.deleteProperty(Base, 'count'))
console.log(typeof Child.helper, Child.count, typeof Base.helper, Base.count)
console.log(Reflect.deleteProperty(Base, 'label'), Base.label, Child.label)
Base.helper = () => 'replaced'
console.log(Base.helper(), Child.helper())
Base.count = 4
const countKey = { toString () { return 'count' } }
console.log(Reflect.deleteProperty(Base, countKey), Base.count)
class Key {
constructor (name) { this.name = name }
toString () { return this.name }
}
Base.count = 5
console.log(Reflect.deleteProperty(Base, new Key('count')), Base.count)
// Checked-dynamic objects and primitive targets.
const parsed = JSON.parse('{"a":1,"b":2}')
console.log(Reflect.deleteProperty(parsed, 'a'), parsed)
const frozen = Object.freeze(JSON.parse('{"a":1}'))
console.log(Reflect.deleteProperty(frozen, 'a'), frozen)
const attempt = (run) => {
try {
run()
} catch (error) {
console.log(error.constructor.name, error.message)
}
}
attempt(() => Reflect.deleteProperty(1, 'a'))
attempt(() => Reflect.deleteProperty('text', 'a'))
attempt(() => Reflect.deleteProperty(null, 'a'))
attempt(() => Reflect.deleteProperty(undefined, 'a'))
attempt(() => Reflect.deleteProperty(true, 'a'))
@@ -0,0 +1,5 @@
const reassigned = require('./reassigned');
const written = require('./written');
const redeclared = require('./redeclared');
const stable = require('./stable');
console.log(reassigned.n, written.tab, redeclared.twice, stable.n);
@@ -0,0 +1 @@
{ "type": "commonjs" }
@@ -0,0 +1,4 @@
// Reassigned after the export: Node's importers keep the first object.
var counter = { n: 1 };
module.exports = counter;
counter = { n: 2 };
@@ -0,0 +1,4 @@
// Declared twice: the second declaration assigns the binding again.
var twice = 1;
module.exports = { twice };
var twice = 2;
@@ -0,0 +1,3 @@
// Initialized before the export and never assigned.
var stable = { n: 3 };
module.exports = stable;
@@ -0,0 +1,4 @@
// Written by a function: the table holds the value at the export.
let tab = ' ';
function setTab(next) { tab = next; }
module.exports = { tab, setTab };
@@ -0,0 +1,6 @@
"use strict";
exports.version = 1;
exports.current = null;
exports.set = (value) => {
exports.current = value;
};
@@ -0,0 +1,7 @@
// A namespace import of a CommonJS wrapper that re-exports another module:
// Node's namespace keeps impl.cjs's values from the end of its evaluation,
// so `ns.current` reads null after `ns.set(5)`.
import * as ns from "./wrapper.cjs";
ns.set(5);
console.log(ns.current, ns.version);
@@ -0,0 +1 @@
{ "type": "commonjs" }
@@ -0,0 +1,2 @@
"use strict";
module.exports = require("./impl.cjs");
+2 -2
View File
@@ -1,5 +1,5 @@
// The native implementation covers default Unicode grapheme segmentation.
new Intl.Segmenter("en-US");
// The native implementation covers Unicode grapheme segmentation.
new Intl.Segmenter(["en-US", "de"]);
new Intl.Segmenter(undefined, { granularity: "word" });
new Intl.Segmenter(undefined, { granularity: "sentence" });
// End of the diagnostic fixture.
+1
View File
@@ -0,0 +1 @@
export declare function splitWords(text: string): string;
+9
View File
@@ -0,0 +1,9 @@
const words = new Intl.Segmenter(undefined, { granularity: "word" });
export function splitWords(text) {
try {
return [...words.segment(text)].map((part) => part.segment).join("|");
} catch (error) {
return `${error.code}: ${error.message}`;
}
}
+1
View File
@@ -0,0 +1 @@
{ "name": "word-segments", "version": "1.0.0", "type": "module", "main": "index.js", "types": "index.d.ts" }
+3
View File
@@ -0,0 +1,3 @@
import { splitWords } from "word-segments";
console.log(splitWords("two words"));
@@ -1,15 +1,15 @@
intl-segmenter.ts:2:1 - error SC2020: 'Intl.Segmenter locale negotiation' is part of the standard library types but has no scriptc lowering yet
1 | // The native implementation covers default Unicode grapheme segmentation.
2 | new Intl.Segmenter("en-US");
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
1 | // The native implementation covers Unicode grapheme segmentation.
2 | new Intl.Segmenter(["en-US", "de"]);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 | new Intl.Segmenter(undefined, { granularity: "word" });
hint: omit the locale or pass undefined for default Unicode grapheme segmentation
hint: omit the locale, pass undefined, or pass one language tag as a string literal
intl-segmenter.ts:3:31 - error SC2020: 'Intl.Segmenter options' is part of the standard library types but has no scriptc lowering yet
2 | new Intl.Segmenter("en-US");
2 | new Intl.Segmenter(["en-US", "de"]);
3 | new Intl.Segmenter(undefined, { granularity: "word" });
| ^~~~~~~~~~~~~~~~~~~~~~~
4 | new Intl.Segmenter(undefined, { granularity: "sentence" });
@@ -0,0 +1 @@
{ "plain": 2500, "san": 3500 }
@@ -0,0 +1 @@
{ "plain": 2500, "san": 3500 }
@@ -0,0 +1 @@
{ "plain": 2500, "san": 3500 }
@@ -0,0 +1 @@
{ "plain": 7000, "san": 7200 }
@@ -0,0 +1 @@
{ "plain": 8300, "san": 7400 }
@@ -0,0 +1 @@
{ "plain": 10100, "san": 9400 }
@@ -0,0 +1 @@
{ "plain": 6600, "san": 6000 }
@@ -0,0 +1 @@
{ "plain": 4500, "san": 4100 }
+26
View File
@@ -543,6 +543,32 @@ test("settled generic rest-order fences count each source statement once", () =>
expect(coverage.stats.statementsFailed).toBe(2);
});
test("CommonJS var and let exports that can change after the export keep their refusal", () => {
// JavaScript refusals defer to run time: they are the runtime fences.
const { coverage } = analyze(fixture("cjs-mutable-exports/main.js"));
const refused = [...coverage.diagnostics, ...(coverage.runtimeFences ?? [])]
.map((d) => /exporting the mutable 'let' binding '(\w+)'/.exec(d.message)?.[1])
.filter((name) => name !== undefined)
.sort();
expect(refused).toEqual(["counter", "tab", "twice"]);
});
test("CommonJS namespace reads of exports a re-exported module assigns later keep their refusal", () => {
// wrapper.cjs re-exports impl.cjs, whose set() assigns `current` after
// evaluation; `version` never changes.
const { coverage } = analyze(fixture("cjs-namespace-reexports/main.mjs"));
const refused = [...coverage.diagnostics, ...(coverage.runtimeFences ?? [])]
.map(
(d) =>
/reads of '(\w+)' through namespace imports of CommonJS modules whose functions assign exports/.exec(
d.message,
)?.[1],
)
.filter((name) => name !== undefined)
.sort();
expect(refused).toEqual(["current"]);
});
test("inferred JavaScript compiles natively", async () => {
// The js-gap fixture turns noImplicitAny off. Its inferred parameter
// uses checked native values alongside the JSDoc-typed neighbor.
+22
View File
@@ -523,6 +523,28 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => {
}
});
test("a word segmenter in a package constructs, and its segment() throws SC2020", async () => {
const entry = join(pilotRoot, "word-segments-cli.ts");
const { coverage } = analyze(entry, { npmStatic: "auto" });
expect(coverage.npmStatic).toEqual([{ package: "word-segments", status: "static" }]);
expect(coverage.diagnostics).toHaveLength(0);
const outDir = join(cacheDir, "word-segments", sanitize ? "san" : "plain");
const result = await compile(entry, {
backend: "llvm",
npmStatic: "auto",
sanitize,
outDir,
outPath: join(outDir, "program"),
});
if (!result.ok)
throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"));
const nativeRes = await runBinary(result.binaryPath, []);
expect(nativeRes.stdout.toString()).toMatch(
/^SC2020: Intl\.Segmenter segmentation with granularity 'word' is not supported yet \[SC2020 at .*index\.js:1\]\n$/,
);
expect(nativeRes.exitCode).toBe(0);
});
test.each([
'import { Console as Logger } from "node:console"; new Logger({ stdout: process.stdout }).dir(1);',
'import * as output from "console"; new output.Console(process.stdout).table([1, 2]);',

Some files were not shown because too many files have changed in this diff Show More