Merge base

This commit is contained in:
Chris Tate
2026-10-19 22:00:59 -05:00
parent 4f7539103c
commit d07188d250
31 changed files with 860 additions and 24 deletions
@@ -111,6 +111,98 @@ console.log(require_named().name);
expect(chunk).toContain("var __defProp = Object.defineProperty;");
});
test("a helper travels with a chunk whose nested scopes declare its name", () => {
const answer = expectSameBehavior(`${keyedHelper}function label(n) {
return "label " + n;
}
function wrap(n) {
function inner() {
var label = "inner";
return label;
}
return label(n) + " " + inner();
}
var require_e = __commonJS({
"e.js"(exports2, module2) {
function nested() {
var label = "nested";
return label;
}
var seen = [];
for (let label of ["loop"]) seen.push(label);
try {
throw "caught";
} catch (label) {
seen.push(label);
}
{
const label = "block";
seen.push(label);
}
module2.exports = { outer: label(1), indirect: (0, label)(3), wrapped: wrap(2), nested: nested(), seen };
}
});
var e = require_e();
console.log(e.outer, e.indirect, e.wrapped, e.nested, e.seen.join(","));
`);
const chunk = answer.chunks[0]!.source;
expect(chunk).toContain("function label(n)");
expect(chunk).toContain("function wrap(n)");
});
test("a named function expression that only calls itself travels with a chunk", () => {
const answer = expectSameBehavior(`${keyedHelper}var fact = function self(n) {
return n <= 1 ? 1 : n * self(n - 1);
};
var kind = function self(self) {
return typeof self;
};
var outer = function self() {
var self = 2;
return self;
};
var require_g = __commonJS({
"g.js"(exports2, module2) {
module2.exports = [fact(5), kind(1), outer()];
}
});
console.log(require_g().join(","));
`);
const chunk = answer.chunks[0]!.source;
expect(chunk).toContain("var fact = function self(n)");
});
test.each([
["exports it", `function tally() {}`, `module2.exports = { tally };`],
["reads its properties", `function tally() {}`, `exports2.calls = tally.calls;`],
["constructs it", `function tally() {}`, `exports2.made = new tally();`],
["stores an arrow helper", `var tally = () => 1;`, `exports2.fn = tally;`],
[
"calls a function that reads its own name as a value",
`var tally = function self() { self.n = (self.n || 0) + 1; return self.n; };`,
`exports2.count = tally();`,
],
[
"calls a function whose parameter default reads its own name",
`var tally = function self(box = self) { box.n = (box.n || 0) + 1; return box.n; };`,
`exports2.count = tally();`,
],
])("a chunk that %s keeps the package refused", (_, declaration, statement) => {
const result = split(`${keyedHelper}${declaration}
var require_f = __commonJS({
"f.js"(exports2, module2) {
${statement}
}
});
require_f();
`);
expect(result).toEqual({
degrade: expect.stringContaining(
"uses the bundle's top-level function 'tally' as a value",
) as string,
});
});
test.each([
["reads its factory's receiver", `console.log(this);`],
["reads its factory's arguments object", `console.log(arguments.length);`],
@@ -105,6 +105,128 @@ export function declaredNames(root: ts.Node): Set<string> {
return names;
}
/** Adds the identifiers a binding name (an identifier or a destructuring
* pattern) declares. */
function addBindingNames(name: ts.BindingName, out: Set<string>): void {
if (ts.isIdentifier(name)) {
out.add(name.text);
return;
}
for (const element of name.elements.nodes) {
if (!ts.isOmittedExpression(element) && element.name !== undefined)
addBindingNames(element.name, out);
}
}
function isFunctionScope(n: ts.Node): n is ts.SignatureDeclaration {
return (
ts.isFunctionDeclaration(n) ||
ts.isFunctionExpression(n) ||
ts.isArrowFunction(n) ||
ts.isMethodDeclaration(n) ||
ts.isGetAccessorDeclaration(n) ||
ts.isSetAccessorDeclaration(n) ||
ts.isConstructorDeclaration(n)
);
}
/** The `var` names a function body declares, without entering nested
* functions. Function declarations in nested blocks count as block-scoped
* here; treating one as free only adds an unused helper copy. */
function varScopedNames(body: ts.Node): Set<string> {
const out = new Set<string>();
ts.walkPreorder(body, (n) => {
if (n !== body && (isFunctionScope(n) || ts.isClassDeclaration(n) || ts.isClassExpression(n)))
return "skip";
if (ts.isVariableDeclarationList(n) && (n.flags & ts.NodeFlags.BlockScoped) === 0) {
for (const d of n.declarations.nodes) addBindingNames(d.name, out);
}
return undefined;
});
return out;
}
/** The names one scope node binds for the references inside it. */
function scopeNames(scope: ts.Node): Set<string> {
const out = new Set<string>();
if (isFunctionScope(scope)) {
for (const param of scope.parameters.nodes) addBindingNames(param.name, out);
if (ts.isFunctionExpression(scope) && scope.name !== undefined) out.add(scope.name.text);
if (scope.body !== undefined) for (const name of varScopedNames(scope.body)) out.add(name);
} else if (ts.isBlock(scope) || ts.isSourceFile(scope) || ts.isCaseBlock(scope)) {
const statements = ts.isCaseBlock(scope)
? scope.clauses.nodes.flatMap((clause) => clause.statements.nodes)
: scope.statements.nodes;
for (const stmt of statements) {
if ((ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) && stmt.name)
out.add(stmt.name.text);
else if (
ts.isVariableStatement(stmt) &&
(stmt.declarationList.flags & ts.NodeFlags.BlockScoped) !== 0
) {
for (const d of stmt.declarationList.declarations.nodes) addBindingNames(d.name, out);
}
}
} else if (ts.isCatchClause(scope) && scope.variableDeclaration !== undefined) {
addBindingNames(scope.variableDeclaration.name, out);
} else if (
(ts.isForStatement(scope) || ts.isForInStatement(scope) || ts.isForOfStatement(scope)) &&
scope.initializer !== undefined &&
ts.isVariableDeclarationList(scope.initializer) &&
(scope.initializer.flags & ts.NodeFlags.BlockScoped) !== 0
) {
for (const d of scope.initializer.declarations.nodes) addBindingNames(d.name, out);
} else if (
(ts.isClassDeclaration(scope) || ts.isClassExpression(scope)) &&
scope.name !== undefined
) {
out.add(scope.name.text);
}
return out;
}
/** Resolves references against one bundle's scopes, keeping each scope's
* names for the bundle's split. */
class BundleScopes {
private readonly names = new Map<ts.Node, Set<string>>();
private of(scope: ts.Node): Set<string> {
let names = this.names.get(scope);
if (names === undefined) {
names = scopeNames(scope);
this.names.set(scope, names);
}
return names;
}
/** True when a scope between the reference and `root` (inclusive)
* binds its name. */
boundWithin(id: ts.Identifier, root: ts.Node): boolean {
for (let n = id.parent; n !== undefined; n = n.parent) {
if (this.of(n).has(id.text)) return true;
if (n === root) return false;
}
return false;
}
/** True when a scope inside `root` (exclusive) binds the reference's
* name. */
boundInside(id: ts.Identifier, root: ts.Node): boolean {
for (let n = id.parent; n !== undefined && n !== root; n = n.parent) {
if (this.of(n).has(id.text)) return true;
}
return false;
}
/** True when the reference resolves to a top-level binding of `sf`. */
topLevel(id: ts.Identifier, sf: ts.SourceFile): boolean {
for (let n = id.parent; n !== undefined && n !== sf; n = n.parent) {
if (this.of(n).has(id.text)) return false;
}
return true;
}
}
/** Every binding reference inside a subtree. */
export function references(root: ts.Node): ts.Identifier[] {
const out: ts.Identifier[] = [];
@@ -230,6 +352,52 @@ function copyableInitializer(init: ts.Expression | undefined): boolean {
return depth > 0 && ts.isIdentifier(walk) && walk.text === "Object";
}
/** A top-level declaration whose value is a function. */
function isFunctionValued(node: ts.VariableDeclaration | ts.FunctionDeclaration): boolean {
if (ts.isFunctionDeclaration(node)) return true;
const init = node.initializer && unparen(node.initializer);
return init !== undefined && (ts.isArrowFunction(init) || ts.isFunctionExpression(init));
}
/** True when a named function expression initializer (`var f = function
* self() {}`) uses its own name only as a callee: `self` is the copied
* function, so it is as observable as the top-level binding. */
function selfCalledOnly(
node: ts.VariableDeclaration | ts.FunctionDeclaration,
scopes: BundleScopes,
): boolean {
if (!ts.isVariableDeclaration(node) || node.initializer === undefined) return true;
const fn = unparen(node.initializer);
if (!ts.isFunctionExpression(fn) || fn.name === undefined || fn.body === undefined) return true;
const self = fn.name.text;
// A parameter or `var` of the same name hides the function's own name.
const own = varScopedNames(fn.body);
for (const param of fn.parameters.nodes) addBindingNames(param.name, own);
if (own.has(self)) return true;
// Its parameter initializers see the name too.
return references(fn).every((r) => r.text !== self || scopes.boundInside(r, fn) || isCallee(r));
}
/** True when a reference is the function a call invokes: `f(...)`, or the
* `(0, f)(...)` form that drops the receiver. */
function isCallee(id: ts.Identifier): boolean {
let callee: ts.Node = id;
const p = id.parent;
if (
p !== undefined &&
ts.isBinaryExpression(p) &&
p.operatorToken.kind === ts.SyntaxKind.CommaToken &&
p.right === id &&
p.parent !== undefined &&
ts.isParenthesizedExpression(p.parent)
)
callee = p.parent;
while (callee.parent !== undefined && ts.isParenthesizedExpression(callee.parent))
callee = callee.parent;
const call = callee.parent;
return call !== undefined && ts.isCallExpression(call) && call.expression === callee;
}
/** One recognized chunk declaration. */
interface Chunk {
name: string;
@@ -302,6 +470,12 @@ export function splitBundledChunks(
if (declaredAnywhere.has("Object") || written.has("Object"))
return { degrade: CHUNK_SHAPE_DEGRADE };
const stable = (name: string): boolean => !duplicate.has(name) && !written.has(name);
// Names whose every reference in the bundle is a callee: `f(...)` or the
// `(0, f)(...)` form.
const scopes = new BundleScopes();
const valueUsed = new Set<string>();
for (const r of allRefs) if (!isCallee(r) && scopes.topLevel(r, sf)) valueUsed.add(r.text);
const calledOnly = (name: string): boolean => !valueUsed.has(name);
const ownNames = (name: string): boolean => {
const t = top.get(name);
if (t === undefined || !stable(name) || !ts.isVariableDeclaration(t.node)) return false;
@@ -422,7 +596,17 @@ export function splitBundledChunks(
const out: BundledChunkSplit["chunks"] = [];
const bundleEdits: Edit[] = [...callEdits];
for (const chunk of chunks.values()) {
const result = chunkSource(sf, chunk, chunks, top, stable, callEdits, bundleStrict);
const result = chunkSource(
sf,
chunk,
chunks,
top,
stable,
calledOnly,
scopes,
callEdits,
bundleStrict,
);
if (typeof result !== "string") return result;
out.push({ path: chunk.path, source: result });
bundleEdits.push({ start: chunk.stmt.getStart(sf), end: chunk.stmt.getEnd(), text: "" });
@@ -524,6 +708,8 @@ function chunkSource(
chunks: ReadonlyMap<string, Chunk>,
top: ReadonlyMap<string, TopLevel>,
stable: (name: string) => boolean,
calledOnly: (name: string) => boolean,
scopes: BundleScopes,
callEdits: readonly Edit[],
bundleStrict: boolean,
): string | { degrade: string } {
@@ -570,7 +756,8 @@ function chunkSource(
}
continue;
}
if (local.has(name)) continue;
// The factory function itself binds the parameters and `var`s.
if (scopes.boundWithin(r, body.parent ?? body)) continue;
if (name === "exports" || name === "module")
return why(`reads the bundle's own '${name}' binding`);
if (name === "__filename") return why("reads __filename, which names the bundle file");
@@ -594,9 +781,14 @@ function chunkSource(
(ts.isVariableDeclaration(node) && copyableInitializer(node.initializer)));
if (!pure || chunks.has(name))
return why(`reads the bundle's top-level binding '${name}', which it cannot carry along`);
const own = declaredNames(node);
// Each chunk gets its own copy, so a function's identity and its
// properties must stay unobservable: the bundle only ever calls it.
if (isFunctionValued(node) && (!calledOnly(name) || !selfCalledOnly(node, scopes)))
return why(
`uses the bundle's top-level function '${name}' as a value, which its chunks cannot share`,
);
for (const r of references(node)) {
if (own.has(r.text) || !top.has(r.text) || copied.has(r.text)) continue;
if (scopes.boundWithin(r, node) || !top.has(r.text) || copied.has(r.text)) continue;
copied.add(r.text);
copyQueue.push(r.text);
}
+93 -17
View File
@@ -3199,29 +3199,105 @@ function cjsCycleRefusal7(
return null;
}
/** True for a plain read `binding.name` (not a call, not a write) of the
* cycle member `dep` whose export `name` is a top-level function
* declaration nothing reassigns. Every member settles its exports before
* entering the cycle, so Node reads that same function whenever the read
* runs, and the compiled read resolves to the declaration itself. */
/** True when `use` initializes a top-level const whose every other
* reference is a value of an export statement (`module.exports = { … }`
* or `exports.n = …`). */
function publishedAlias7(use: ts.Node, decl: ts.Node | undefined): boolean {
if (
decl === undefined ||
!ts.isVariableDeclaration(decl) ||
decl.initializer !== use ||
!ts.isIdentifier(decl.name)
)
return false;
const list = decl.parent;
const stmt = list?.parent;
const sf = stmt?.parent;
if (
list === undefined ||
(list.flags & ts.NodeFlags.Const) === 0 ||
stmt === undefined ||
!ts.isVariableStatement(stmt) ||
sf === undefined ||
!ts.isSourceFile(sf)
)
return false;
const alias = decl.name;
const published = new Set<ts.Node>();
for (const st of sf.statements.nodes) {
const cjs = cjsExportAssignmentOf(st);
if (cjs === null) continue;
if (cjs.kind === "member") published.add(cjs.value);
else if (cjs.obj !== null) {
for (const p of cjs.obj.properties.nodes) {
if (ts.isPropertyAssignment(p) && !ts.isComputedPropertyName(p.name))
published.add(p.initializer);
else if (ts.isShorthandPropertyAssignment(p)) published.add(p.name);
}
}
}
let kept = false;
ts.walkPreorder(sf, (n) => {
if (kept) return "stop";
if (!ts.isIdentifier(n) || n === alias || n.text !== alias.text) return undefined;
const p = n.parent;
if (
p !== undefined &&
((ts.isPropertyAccessExpression(p) && p.name === n) ||
(ts.isPropertyAssignment(p) && p.name === n))
)
return undefined;
if (!published.has(n)) kept = true;
return undefined;
});
return !kept;
}
/** True for a plain read `binding.name` (not a call, not a write, not a
* value that is stored or passed on) of the cycle member `dep` whose
* export `name` is a top-level function declaration nothing reassigns.
* Every member settles its exports before entering the cycle, so Node
* reads that same function whenever the read runs, and the compiled read
* resolves to the declaration itself. */
function functionExportRead7(id: ts.Identifier, dep: ts.SourceFile): boolean {
const access = id.parent;
if (!ts.isPropertyAccessExpression(access) || access.expression !== id) return false;
// The value the read yields: through parentheses and the right operand
// of a comma, so `(0, binding.name)(...)` is the call it spells.
let use: ts.Node = access;
while (ts.isParenthesizedExpression(use.parent)) use = use.parent;
for (;;) {
const p = use.parent;
if (ts.isParenthesizedExpression(p)) use = p;
else if (
ts.isBinaryExpression(p) &&
p.operatorToken.kind === ts.SyntaxKind.CommaToken &&
p.right === use
)
use = p;
else break;
}
const parent = use.parent;
if (
(ts.isCallExpression(parent) && parent.expression === use) ||
ts.isDeleteExpression(parent) ||
((ts.isPrefixUnaryExpression(parent) || ts.isPostfixUnaryExpression(parent)) &&
(parent.operator === ts.SyntaxKind.PlusPlusToken ||
parent.operator === ts.SyntaxKind.MinusMinusToken)) ||
// Only a use that neither runs the function nor keeps it is a read: a
// stored alias (`const f = binding.name`, `{ f: binding.name }`) or an
// argument can be called while the cycle initializes. A top-level const
// that only publishes it (`module.exports = { name: f }`, the static
// rewrite's re-export spelling) runs nothing: a module that reads the
// export back checks that read itself.
const observes =
publishedAlias7(use, parent) ||
ts.isExpressionStatement(parent) ||
ts.isTypeOfExpression(parent) ||
ts.isVoidExpression(parent) ||
(ts.isPrefixUnaryExpression(parent) && parent.operator === ts.SyntaxKind.ExclamationToken) ||
(ts.isBinaryExpression(parent) &&
parent.left === use &&
parent.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
parent.operatorToken.kind <= ts.SyntaxKind.LastAssignment)
)
return false;
(parent.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken ||
parent.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken ||
parent.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken ||
parent.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsToken)) ||
((ts.isIfStatement(parent) || ts.isWhileStatement(parent) || ts.isDoStatement(parent)) &&
parent.expression === use) ||
(ts.isConditionalExpression(parent) && parent.condition === use);
if (!observes) return false;
const name = access.name.text;
// The exported value's identifier: a table entry or a member export.
const values: ts.Expression[] = [];
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts"
],
"diags": []
}
@@ -0,0 +1,7 @@
// A bundled CommonJS chunk that calls the bundle's top-level `label` while
// a nested function of the chunk declares its own `label`: the chunk
// carries the top-level function along, and each reference keeps its own
// binding.
import { inner, outer } from "gtchunkshadow";
console.log(outer(), inner());
@@ -0,0 +1,7 @@
// Two bundled CommonJS chunks export the bundle's top-level `tally` and
// count calls on a property of it. Node prints "1 2 true": both chunks
// share one function. A per-chunk copy would split its identity and its
// properties, so the package stays refused.
import { counts, same } from "gtchunkshared";
console.log(counts(), same());
@@ -0,0 +1,7 @@
// A require cycle whose partner calls an export of index.js with esbuild's
// `(0, import_index.peek)()` spelling while index is still initializing:
// peek reads a const in its temporal dead zone, so Node throws a
// ReferenceError, and scriptc refuses the cycle.
import { show } from "gtcyclecall";
console.log(show());
@@ -0,0 +1,8 @@
// Require cycles whose partner stores an export of index.js (in a const,
// or in an object literal) and calls it while index is still
// initializing: the export reads a const in its temporal dead zone, so
// Node throws a ReferenceError, and scriptc refuses both cycles.
import { show } from "gtcyclealias";
import { show as showBox } from "gtcyclebox";
console.log(show(), showBox());
+2
View File
@@ -0,0 +1,2 @@
export declare function outer(): string;
export declare function inner(): string;
+52
View File
@@ -0,0 +1,52 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
function label(n) {
return "label " + n;
}
// chunk/vendor/shadow.js
var require_shadow = __commonJS({
"chunk/vendor/shadow.js"(exports2, module2) {
function nested() {
var label = "nested";
return label;
}
module2.exports = { outer: label(1), inner: nested() };
}
});
// chunk/src/index.ts
var index_exports = {};
__export(index_exports, {
inner: () => inner,
outer: () => outer
});
module.exports = __toCommonJS(index_exports);
function outer() {
return require_shadow().outer;
}
function inner() {
return require_shadow().inner;
}
0 && (module.exports = {
inner,
outer
});
+7
View File
@@ -0,0 +1,7 @@
{
"name": "gtchunkshadow",
"version": "1.0.0",
"description": "esbuild's CommonJS bundle shape: a chunk calls a top-level function whose name a nested scope of the chunk also declares",
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
}
+2
View File
@@ -0,0 +1,2 @@
export declare function same(): boolean;
export declare function counts(): string;
+56
View File
@@ -0,0 +1,56 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
function tally() {
tally.calls = (tally.calls || 0) + 1;
return tally.calls;
}
// chunk/vendor/a.js
var require_a = __commonJS({
"chunk/vendor/a.js"(exports2, module2) {
module2.exports = { tally, first: tally() };
}
});
// chunk/vendor/b.js
var require_b = __commonJS({
"chunk/vendor/b.js"(exports2, module2) {
module2.exports = { tally, second: tally() };
}
});
// chunk/src/index.ts
var index_exports = {};
__export(index_exports, {
counts: () => counts,
same: () => same
});
module.exports = __toCommonJS(index_exports);
function same() {
return require_a().tally === require_b().tally;
}
function counts() {
return require_a().first + " " + require_b().second;
}
0 && (module.exports = {
counts,
same
});
+7
View File
@@ -0,0 +1,7 @@
{
"name": "gtchunkshared",
"version": "1.0.0",
"description": "esbuild's CommonJS bundle shape: two chunks export one top-level function and count calls on a property of it",
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
}
+3
View File
@@ -0,0 +1,3 @@
export declare const LIMIT = 3;
export declare function show(): string;
export declare function peek(): number;
+37
View File
@@ -0,0 +1,37 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var index_exports = {};
__export(index_exports, {
LIMIT: () => LIMIT,
peek: () => peek,
show: () => show
});
module.exports = __toCommonJS(index_exports);
var import_reader = require("./reader");
const LIMIT = 3;
function peek() {
return LIMIT;
}
function show() {
return (0, import_reader.describe)();
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
LIMIT,
show
});
+34
View File
@@ -0,0 +1,34 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var reader_exports = {};
__export(reader_exports, {
captured: () => captured,
describe: () => describe
});
module.exports = __toCommonJS(reader_exports);
var import_index = require("./index");
const read = import_index.peek;
const captured = read();
function describe() {
return "limit " + captured;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
captured,
describe
});
+7
View File
@@ -0,0 +1,7 @@
{
"name": "gtcyclealias",
"version": "1.0.0",
"description": "esbuild 0.25.10 per-file CommonJS output shape of a cycle whose partner stores an exported function in a const and calls it while that function's const binding is uninitialized",
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
}
+3
View File
@@ -0,0 +1,3 @@
export declare const LIMIT = 3;
export declare function show(): string;
export declare function peek(): number;
+37
View File
@@ -0,0 +1,37 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var index_exports = {};
__export(index_exports, {
LIMIT: () => LIMIT,
peek: () => peek,
show: () => show
});
module.exports = __toCommonJS(index_exports);
var import_reader = require("./reader");
const LIMIT = 3;
function peek() {
return LIMIT;
}
function show() {
return (0, import_reader.describe)();
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
LIMIT,
show
});
+34
View File
@@ -0,0 +1,34 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var reader_exports = {};
__export(reader_exports, {
captured: () => captured,
describe: () => describe
});
module.exports = __toCommonJS(reader_exports);
var import_index = require("./index");
const box = { read: import_index.peek };
const captured = box.read();
function describe() {
return "limit " + captured;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
captured,
describe
});
+7
View File
@@ -0,0 +1,7 @@
{
"name": "gtcyclebox",
"version": "1.0.0",
"description": "esbuild 0.25.10 per-file CommonJS output shape of a cycle whose partner stores an exported function in an object literal and calls it while that function's const binding is uninitialized",
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
}
+3
View File
@@ -0,0 +1,3 @@
export declare const LIMIT = 3;
export declare function show(): string;
export declare function peek(): number;
+37
View File
@@ -0,0 +1,37 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var index_exports = {};
__export(index_exports, {
LIMIT: () => LIMIT,
peek: () => peek,
show: () => show
});
module.exports = __toCommonJS(index_exports);
var import_reader = require("./reader");
const LIMIT = 3;
function peek() {
return LIMIT;
}
function show() {
return (0, import_reader.describe)();
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
LIMIT,
show
});
+33
View File
@@ -0,0 +1,33 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var reader_exports = {};
__export(reader_exports, {
captured: () => captured,
describe: () => describe
});
module.exports = __toCommonJS(reader_exports);
var import_index = require("./index");
const captured = (0, import_index.peek)();
function describe() {
return "limit " + captured;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
captured,
describe
});
+7
View File
@@ -0,0 +1,7 @@
{
"name": "gtcyclecall",
"version": "1.0.0",
"description": "esbuild 0.25.10 per-file CommonJS output shape of a cycle whose partner calls an exported function through (0, binding.fn)() while that function's const binding is uninitialized",
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
}
+2 -2
View File
@@ -19,12 +19,12 @@ export function npmCases(fixturesRoot: string): NpmCase[] {
return [
...globSync(join(fixturesRoot, "npm/cases/*/main.ts"))
.sort()
// 2465-2469, 2556-2563 and 2567 are the --npm-static bundler-emitted-CJS
// 2465-2469, 2556-2567 and 2571 are the --npm-static bundler-emitted-CJS
// cases (npm-static.test.ts drives them with the opt-in): their
// consumers exercise the static rewrite's surface (lexer-visible
// names the shipped .d.ts never declares, the __toESM interop
// family), so they stay out of the flagless island lane by design.
.filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-37])-[^/]+\/main\.ts$/.test(entry))
.filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-7]|2571)-[^/]+\/main\.ts$/.test(entry))
.map((entry) => ({
name: entry.split("/").at(-2)!,
entry,
+56 -1
View File
@@ -76,7 +76,7 @@ async function buildStatic(entry: string, npmStatic: string[] | "auto"): Promise
...globSync(
join(fixturesRoot, "npm/node_modules/cryptozoo/**/*.{js,mjs,cjs,json,d.ts}"),
).sort(),
// the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2563, 2567)
// the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2567, 2571)
...globSync(join(fixturesRoot, "npm/node_modules/gt*/**/*.{js,cjs,json}")).sort(),
];
for (const f of inputs) hash.update(f).update(readFileSync(f));
@@ -1502,6 +1502,61 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => {
]);
}, 180_000);
// 2566: the same early read through esbuild's `(0, binding.fn)()` call
// spelling also keeps SC1016.
test("a CommonJS cycle that calls an export during initialization stays refused", () => {
const entry = join(fixturesRoot, "npm/cases/2566-cjs-cycle-indirect-call/main.ts");
const { coverage } = analyze(entry, { npmStatic: ["gtcyclecall"] });
expect(coverage.npmStatic).toEqual([
{
package: "gtcyclecall",
status: "refused",
detail: expect.stringMatching(
/circular imports .*can use its binding 'import_index' of a module of the cycle/,
) as string,
},
]);
}, 120_000);
// 2571: the partner stores the export (in a const, or in an object
// literal) and calls it while the cycle initializes; both keep SC1016.
test("a CommonJS cycle that stores an export and calls it stays refused", () => {
const entry = join(fixturesRoot, "npm/cases/2571-cjs-cycle-stored-function/main.ts");
const { coverage } = analyze(entry, { npmStatic: ["gtcyclealias", "gtcyclebox"] });
for (const pkg of ["gtcyclealias", "gtcyclebox"])
expect(coverage.npmStatic).toContainEqual({
package: pkg,
status: "refused",
detail: expect.stringMatching(
/circular imports .*can use its binding 'import_index' of a module of the cycle/,
) as string,
});
}, 120_000);
// 2564: a chunk reads a top-level function whose name a nested scope of
// the chunk also declares; the function still travels with the chunk.
test("a chunk carries a top-level function its nested scopes shadow", async () => {
await expectNodeParity(join(fixturesRoot, "npm/cases/2564-chunk-helper-shadowing/main.ts"), [
"gtchunkshadow",
]);
}, 180_000);
// 2565: Node shares one function between the chunks that export it; a
// per-chunk copy would not, so the package stays refused.
test("chunks that export a shared top-level function stay refused", () => {
const entry = join(fixturesRoot, "npm/cases/2565-chunk-shared-function/main.ts");
const { coverage } = analyze(entry, { npmStatic: ["gtchunkshared"] });
expect(coverage.npmStatic).toEqual([
{
package: "gtchunkshared",
status: "refused",
detail: expect.stringMatching(
/uses the bundle's top-level function 'tally' as a value, which its chunks cannot share/,
) as string,
},
]);
}, 120_000);
test("published type-only declarations coexist with a static runtime bundle", async () => {
const entry = join(fixturesRoot, "npm/cases/2469-bundle-offender/main.ts");
const { coverage } = analyze(entry, { npmStatic: ["gtghost"] });