mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Compile more npm and CommonJS JavaScript dependencies statically (#2122)
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8214-unshimmed-package-version/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8216-module-namespace-live-values/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8217-intl-segmenter-word-construct/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+9
@@ -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": []
|
||||
}
|
||||
+9
@@ -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": []
|
||||
}
|
||||
+9
@@ -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": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8221-js-recursive-generator-returns/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -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"],
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"));
|
||||
+1
@@ -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" } }
|
||||
+3
@@ -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;
|
||||
+16
@@ -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;
|
||||
+1
@@ -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());
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
export let count = 0;
|
||||
export var legacy = "legacy";
|
||||
export const label = "counter";
|
||||
export function bump() {
|
||||
count++;
|
||||
legacy = `legacy ${count}`;
|
||||
}
|
||||
Generated
Vendored
+5
@@ -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;
|
||||
+40
@@ -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);
|
||||
}
|
||||
Generated
Vendored
+1
@@ -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}"));
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
export declare function columns(text: string): string;
|
||||
export declare function describe(): string;
|
||||
export declare function plainColumns(text: string): number;
|
||||
+16
@@ -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}`;
|
||||
}
|
||||
Generated
Vendored
+1
@@ -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 )"));
|
||||
Generated
Vendored
+4
@@ -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>;
|
||||
Generated
Vendored
+131
@@ -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 }
|
||||
Generated
Vendored
+1
@@ -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");
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
export declare function splitWords(text: string): string;
|
||||
+9
@@ -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
@@ -0,0 +1 @@
|
||||
{ "name": "word-segments", "version": "1.0.0", "type": "module", "main": "index.js", "types": "index.d.ts" }
|
||||
@@ -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 }
|
||||
@@ -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.
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user