diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 42fa6116e..149584d00 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -1563,6 +1563,14 @@ function splitRequireOfStatement7( return null; } +/** The top-level statement of a split require binding's assignment, from + * its require call (`m = require("./x")` or `m = (require("./x"))`). */ +function splitRequireStatementOf7(call: ts.CallExpression): ts.Node | undefined { + let node: ts.Node = call; + while (node.parent !== undefined && ts.isParenthesizedExpression(node.parent)) node = node.parent; + return node.parent?.parent; +} + /** The require call of a split require binding's declaration, or null. */ function splitRequireCallOf7( program: ts.Program, @@ -1877,7 +1885,7 @@ function requireEarlyReference7( const sf = ts.getSourceFileOfNode(decl); // A split require binding is bound at its assignment statement. const splitCall = splitRequireCallOf7(program, decl); - const stmt = splitCall !== null ? splitCall.parent?.parent : decl.parent?.parent; + const stmt = splitCall !== null ? splitRequireStatementOf7(splitCall) : decl.parent?.parent; if ( stmt !== undefined && (ts.isVariableStatement(stmt) || splitCall !== null) && @@ -6184,15 +6192,12 @@ function preflight7(load: LoadResult): { // counts, and targets outside the compiled graph add nothing here: // their use sites keep their existing lowering fences. if (jsRequireFile && !isNodeEsmFile7(sf, program)) { + // A split require binding's assignment is a statement-level require + // edge (requiresOf7 above), parenthesized or not. + const splitCalls = new Set(splitRequireBindings7(program, sf).values()); for (const call of inlineRequiresOf7(sf)) { if (requireCallBindingKind7(program, call) !== "ambient") continue; - // A split require binding's assignment is a statement-level - // require edge (requiresOf7 above). - if ( - call.parent?.parent !== undefined && - splitRequireOfStatement7(program, call.parent.parent as ts.Statement) !== null - ) - continue; + if (splitCalls.has(call)) continue; const spec = requireSpecOf7(call)!; if (load.externalTypes.has(spec) || canonicalBuiltinModule(spec) !== null) continue; const lazy = inDeferredPosition7(call); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/split-cjs-require-early/main.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/split-cjs-require-early/main.js.json index a0a094bf5..acb338494 100644 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/split-cjs-require-early/main.js.json +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/split-cjs-require-early/main.js.json @@ -12,6 +12,16 @@ "end": 415 }, "milestone": "later" + }, + { + "code": "SC1013", + "message": "require() binding 'wrapped' assigned below code that can reach it (Node reads undefined before the assignment runs, which is not modeled — move the assignment above the code that can run first) is not supported yet", + "loc": { + "file": "/tests/diagnostics/split-cjs-require-early/main.js", + "start": 530, + "end": 549 + }, + "milestone": "later" } ] } diff --git a/tests/corpus/split-cjs-require-cycle/main.cjs b/tests/corpus/split-cjs-require-cycle/main.cjs index c4788ddbe..9ec7997db 100644 --- a/tests/corpus/split-cjs-require-cycle/main.cjs +++ b/tests/corpus/split-cjs-require-cycle/main.cjs @@ -1,7 +1,8 @@ // Split require bindings (`var m; m = require("./x.cjs")`) that close a // CommonJS cycle: each module publishes its exports before its require, and // reads the other module's binding only in functions that run after both -// modules finish, the cycle shape the const form compiles too. +// modules finish, the cycle shape the const form compiles too. One +// assignment parenthesizes its require. console.log("main: start"); var even, odd; even = require("./even.cjs"); diff --git a/tests/corpus/split-cjs-require-cycle/odd.cjs b/tests/corpus/split-cjs-require-cycle/odd.cjs index c066a1e33..d449c28e8 100644 --- a/tests/corpus/split-cjs-require-cycle/odd.cjs +++ b/tests/corpus/split-cjs-require-cycle/odd.cjs @@ -8,5 +8,5 @@ exports.isOdd = function (n) { exports.calls = function () { return calls; }; -even = require("./even.cjs"); +even = (require("./even.cjs")); console.log("odd: loaded"); diff --git a/tests/diagnostics/split-cjs-require-early/main.js b/tests/diagnostics/split-cjs-require-early/main.js index 3e0e43aa3..ae1d8d770 100644 --- a/tests/diagnostics/split-cjs-require-early/main.js +++ b/tests/diagnostics/split-cjs-require-early/main.js @@ -9,6 +9,11 @@ let lib; console.log('boot', helper()); lib = require('./lib.js'); +// The same with the require parenthesized. +let wrapped; +console.log('peek', wrapped === undefined); +wrapped = (require('./lib.js')); + function helper() { return lib === undefined ? 'not loaded' : lib.greet('late'); } diff --git a/tests/harness/__snapshots__/split-cjs-require-early/main.js.txt b/tests/harness/__snapshots__/split-cjs-require-early/main.js.txt index 2269f906e..0496cd125 100644 --- a/tests/harness/__snapshots__/split-cjs-require-early/main.js.txt +++ b/tests/harness/__snapshots__/split-cjs-require-early/main.js.txt @@ -3,4 +3,11 @@ split-cjs-require-early/main.js:10:7 - error SC1013: require() binding 'lib' ass 9 | console.log('boot', helper()); 10 | lib = require('./lib.js'); | ^~~~~~~~~~~~~~~~~~~ - 11 | \ No newline at end of file + 11 | + +split-cjs-require-early/main.js:15:12 - error SC1013: require() binding 'wrapped' assigned below code that can reach it (Node reads undefined before the assignment runs, which is not modeled — move the assignment above the code that can run first) is not supported yet + + 14 | console.log('peek', wrapped === undefined); + 15 | wrapped = (require('./lib.js')); + | ^~~~~~~~~~~~~~~~~~~ + 16 | \ No newline at end of file