Skip to content

fix(javascript): ignore a require call that a local binding shadows - #2921

Closed
XiaoBinGan wants to merge 3 commits into
morluto:mainfrom
XiaoBinGan:fix/shadowed-require
Closed

XiaoBinGan wants to merge 3 commits into
morluto:mainfrom
XiaoBinGan:fix/shadowed-require

Conversation

@XiaoBinGan

Copy link
Copy Markdown
Contributor

Summary

Only treat a call named require as the CommonJS loader when the callee is an
unshadowed reference to it, so a locally bound require no longer invents an
imports edge.

Problem and expected behavior

analyze-javascript-application reports a resolved, high-confidence imports
relationship for a call to a local function parameter named require.
Lexical binding already establishes that the call is not the CommonJS loader, so
the graph must not carry that edge.

Minimal reproduction, as reported:

function run(require) {
  return require("./ghost.cjs");
}
console.log(run(() => "local"));

src/domain/javascript/javascriptStaticAnalysisCalls.ts decided this by name
alone:

if (name === "require" || name.endsWith(".require"))
  return { specifier: argumentValue(node.arguments[0]) };

Change and scope

requireCallSpecifier now receives whether the callee is an unshadowed loader
reference, computed with isUnmodifiedGlobalReference — the helper this file
already uses for worker constructors, and the same ownership rule the semantic
path applies in javascriptSemanticRequireOrigin.ts, whose contract is to
recover an unshadowed literal require origin.

Two lanes deliberately keep their behavior:

  • name.endsWith(".require") (member calls such as module.require) is a
    property access, not a lexical binding.
  • moduleRequireName is the module factory's own require parameter
    (javascriptStaticAnalysisBundler.ts), which is a real binding. A plain
    CommonJS module's ambient require has no binding, so ordinary calls are
    still detected.

Contract and boundary impact

The producing boundary keeps ownership of the decision. The semantic path
already refused a shadowed require, so both paths now agree instead of one of
them re-deriving the call from its name.

Evidence and regression coverage

src/domain/javascript/javascriptStaticAnalysis.shadowed_require.test.ts
reproduces the reported source and covers both directions:

  • Without the change the shadowed call is recorded (the ghost specifier is
    present in the references); with it, it is absent.
  • const loaded = require("./real.cjs") is still recorded, so no legitimate
    loader call is lost.

npm run test:focused -- src/domain/javascript/*.test.ts passes: 57 files,
1283 tests. npm run check:fast passes.

Unavailable coverage

The end-to-end analyze-javascript-application edge was not re-run as a
boundary test; the evidence is at the reference boundary that produces it
(JavaScriptModuleRelationships.ts selects references whose kind is require).
No real-provider verification is involved.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T18:55:16.816713Z 9ed55f6 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1c10ce28aa

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// shadows the ambient loader, so only an unshadowed reference to it counts.
// The module's own factory require name is a real binding, so it keeps its
// own lane below.
if (name === "require" && isGlobalRequire)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore member-form require calls

For calls such as module.require("./dep.cjs"), calleeName returns "module.require", so this exact-name condition is false and neither later branch accepts the call. The previous name.endsWith(".require") lane was removed rather than preserved, causing these real loader calls to disappear from references and from downstream application import edges; keep a separate member-call branch while applying the shadow check only to bare require.

AGENTS.md reference: AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

node,
name,
moduleRequireName,
isUnmodifiedGlobalReference(node.callee, bindings, name),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve createRequire-backed loader calls

For ESM using the standard const require = createRequire(import.meta.url) pattern, the local binding makes this helper return false, so a real subsequent require("./dep.cjs") call is now omitted even though it was recorded before. The repository already treats this direct binding as a valid loader in directCreateRequireBinding in src/domain/referenceSourceRequireBindings.ts; distinguish known loader-producing bindings instead of rejecting every resolved require, otherwise ESM dependency edges disappear from application analysis.

AGENTS.md reference: AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 0/5

[Medium impact] Refines static analysis logic for require call detection.

The PR is not ready to merge because it still drops real loader dependencies and records calls that load nothing.

Findings

  1. P1 CommonJS loader dependencies disappear ▶
  2. P1 Namespace-created loaders lose dependencies ▶
  3. P1 Replaced loaders invent dependencies ▶
  4. P1 Let bindings become loaders ▶
  5. P1 Loader properties become imports ▶
Fix with agent prompt
### Issue 1
src/domain/javascript/javascriptStaticAnalysisCalls.ts:263-265
`isNodeCreateRequireFactory` rejects factories loaded through CommonJS. For example, `const { createRequire } = require("node:module"); function run() { const require = createRequire(__filename); return require("./real.cjs"); } run();` creates a real loader, but the factory binding has `runtimeImport: false` and no `directOrigins`. The new check therefore drops `./real.cjs` from `references` and the application graph.

Recover the factory's CommonJS origin instead of requiring an ESM import, and add a regression test.

### Issue 2
src/domain/javascript/javascriptStaticAnalysisCalls.ts:undefined-259
The identifier-only check rejects real loaders created through a namespace import. For example, `import * as mod from "node:module"; const require = mod.createRequire(import.meta.url); require("./real.cjs");` was recognized before this change. Now `isNodeCreateRequireFactory` rejects `mod.createRequire`, so `./real.cjs` disappears from the static `references`.

Check the member's module origin rather than rejecting every member call, and add a regression test.

### Issue 3
src/domain/javascript/javascriptStaticAnalysisCalls.ts:undefined-234
The new script shortcut ignores direct assignments to `require`. With `require = () => "local"; require("./ghost.cjs");`, no lexical binding is created, so this branch returns `true` even though the source replaced the loader. The previous `isUnmodifiedGlobalReference` check rejected this visible write. The analyzer now records a dependency for a call that loads nothing.

Keep the distinction between `globalThis.require` and the wrapper's `require`, but still reject direct writes to the latter.

### Issue 4
src/domain/javascript/javascriptStaticAnalysisCalls.ts:294-295
`isTopLevelCommonJsVarRequire` also accepts `let require;`. Both `var` and `let` declarations get the semantic kind `"variable"`, and a top-level declaration without a value has no initializer. Thus `let require; function run() { return require("./ghost.cjs"); }` passes this check even though `let` creates a separate binding, not a wrapper redeclaration. The analyzer records a false loader reference even when `run` is never called.

Check that the actual declaration uses `var`, and add a negative test for `let`.

### Issue 5
src/domain/javascript/javascriptStaticAnalysisCalls.ts:242-251
`isRequireLoaderReference` ignores the initializer's `projection`. With `import { createRequire } from 'node:module'; const { resolve: require } = createRequire(import.meta.url); require('./ghost.cjs');`, the local `require` calls the loader's `resolve` method, not the loader. The new check still records `./ghost.cjs` as a dependency even though the call never loads it.

Require an empty `projection` before treating the binding as the returned loader, and add a regression test.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR checks bindings before recording bare require calls and adds eight regression tests.

  • Static analysis ignores local calls named require.

No new security issue was established. Sources remain parsed as data rather than executed.

Supersession could not be checked because GitHub authentication was unavailable. The ancestry checks did not pass, so the full PR diff was reviewed as new changes.

Reviews (6) · Last reviewed commit: "fix(javascript): retain proven CommonJS ..." · Reviewed by Greptile

@XiaoBinGan

Copy link
Copy Markdown
Contributor Author

Both P1s are fixed in dc7bce7f.

Root cause. My first revision replaced if (name === "require" || name.endsWith(".require")) with a bare-name condition and dropped the member lane instead of preserving it: module.require("./dep.cjs") is a property access, not a lexical binding, and it needs its own branch. Separately, the check treated every lexical binding named require as a shadow, but const require = createRequire(import.meta.url) is the loader — the analyzer previously recorded that call and must keep doing so.

Structural fix.

  • The member lane is restored as its own branch, ahead of the factory lanes.
  • isRequireLoaderReference now separates the two binding kinds at the owner's own data instead of by name: an unresolved name is the ambient loader; a resolved binding is the loader only when its stable initializer (semanticStableInitializer) is a call to createRequire. A parameter or any other binding is still refused, which is the case the issue reports.

Validation. The regression test now covers all four lanes:

  • const loaded = require("./real.cjs") → recorded;
  • module.require("./member.cjs") → recorded;
  • const require = createRequire(import.meta.url); require("./created.cjs") → recorded;
  • function run(require) { require("./ghost.cjs") } → not recorded.

npm run test:focused -- src/domain/javascript/*.test.ts: 57 files, 1285 tests passed. npm run check:fast passes.

Remaining cleanup, not folded in here. semanticRequireOrigin in the semantic path refuses a createRequire binding exactly the way this path did before this change (resolveSemanticBindingState(...) !== undefined). Aligning that owner with this rule is a separate change against a different boundary, so I have left it out rather than widen this pull request.

@XiaoBinGan

Copy link
Copy Markdown
Contributor Author

These two reviews were computed against 1c10ce28 (both are timestamped 17:10, before dc7bce7f was pushed) and GitHub has re-anchored them to the newer line numbers. Both are fixed in dc7bce7f, which the branch already carries:

  • if (name.endsWith(".require")) is its own branch again (line 257), ahead of the factory lanes.
  • isRequireLoaderReference admits a binding whose stable initializer calls createRequire.

The regression test in that commit is the evidence rather than the file text: module.require("./member.cjs") is asserted to be recorded, and that assertion fails if the member branch is absent, while function run(require) is asserted not to be recorded. Reviewing dc7bce7f should clear both.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dc7bce7f79

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

expression !== null &&
expression !== undefined &&
t.isCallExpression(expression) &&
calleeName(expression.callee).endsWith("createRequire")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve aliased Node createRequire loaders

For the valid ESM case import { createRequire as makeRequire } from "node:module"; const require = makeRequire(import.meta.url); require("./dep.cjs"), calleeName(expression.callee) is makeRequire, so this spelling check returns false and removes the static reference and downstream application import edge. Fresh evidence after the earlier createRequire comment is that the added suffix-only fix still rejects aliases, even though directCreateRequireBinding already follows each ImportSpecifier.local in src/domain/referenceSourceRequireBindings.ts:127-145; resolve that parsed import provenance rather than matching the callee text.

AGENTS.md reference: AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

): boolean => {
const binding = resolveSemanticBindingState(state, callee, name);
if (binding === undefined)
return isUnmodifiedGlobalReference(callee, state, name);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Separate the CJS loader from globalThis.require

In a .cjs input such as globalThis.require = shim; require("./dep"), the assignment changes a global-object property but not Node's wrapper-local loader parameter. isUnmodifiedGlobalReference rejects the call because the ambient-write index covers ["global", "require"] (javascriptSemanticAmbientWrites.ts:280-293), so this change deletes a real static/application import edge even though no lexical require was shadowed; keep the CommonJS wrapper binding distinct from globalThis.require.

AGENTS.md reference: AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

Comment on lines +229 to +232
const binding = resolveSemanticBindingState(state, callee, name);
if (binding === undefined)
return isUnmodifiedGlobalReference(callee, state, name);
const initializer = semanticStableInitializer(binding, state)?.node;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve uninitialized top-level var require

For a .cjs input such as var require; require("./dep.cjs"), the semantic index resolves a program binding but it has no stable initializer, so this branch returns false and the static/application import edge is lost. An uninitialized top-level var only redeclares Node's wrapper parameter and does not replace its loader value; hasUninitializedTopLevelVarRequire in src/domain/referenceSourceRequireBindings.ts:39-40 and 91-111 already preserves this exact case, so keep that no-op wrapper redeclaration as a possible loader rather than treating it as absent.

AGENTS.md reference: AGENTS.md:L21-L21

Useful? React with 👍 / 👎.

@XiaoBinGan

Copy link
Copy Markdown
Contributor Author

All three findings are valid, and they share one root cause: this change re-derives "is this call the Node loader" by name inside the static path instead of using the owner's fact — the shape the repository's own rule forbids.

  • A local function createRequire() { return () => "local"; } makes my endsWith("createRequire") test admit a value that is not Node's loader.
  • import { createRequire as makeRequire } from "node:module" is a real loader that my test rejects, because the alias is not the name.
  • globalThis.require = shim is an ambient write, not a change to Node's wrapper-local loader, so refusing on isUnmodifiedGlobalReference drops a real dependency.

The correct boundary is directCreateRequireBinding in src/domain/referenceSourceRequireBindings.ts, which resolves the node:module import — aliases included — rather than matching a callee name, and which the reference-source path already uses for this exact question.

I am reworking the change to delegate to that owner rather than adding a fourth case to the heuristic. Until then I am not claiming these are fixed, and the original reported behaviour (a shadowing parameter must not produce an imports edge) stays the target.

callee: t.Node,
state: JavaScriptSemanticAnalysisState,
): boolean => {
if (!t.isIdentifier(callee)) return false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Namespace-created loaders lose dependencies

The identifier-only check rejects real loaders created through a namespace import. For example, import * as mod from "node:module"; const require = mod.createRequire(import.meta.url); require("./real.cjs"); was recognized before this change. Now isNodeCreateRequireFactory rejects mod.createRequire, so ./real.cjs disappears from the static references.

Check the member's module origin rather than rejecting every member call, and add a regression test.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/domain/javascript/javascriptStaticAnalysisCalls.ts
Line: 259

Comment:
**Namespace-created loaders lose dependencies**

The identifier-only check rejects real loaders created through a namespace import. For example, `import * as mod from "node:module"; const require = mod.createRequire(import.meta.url); require("./real.cjs");` was recognized before this change. Now `isNodeCreateRequireFactory` rejects `mod.createRequire`, so `./real.cjs` disappears from the static `references`.

Check the member's module origin rather than rejecting every member call, and add a regression test.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

// Node's CommonJS wrapper supplies a lexical `require` parameter. Writes
// to globalThis.require do not replace it; retain that possibility for
// script/CommonJS source where wrapper mode is possible.
if (name === "require" && isScriptProgram(callee, state)) return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Replaced loaders invent dependencies

The new script shortcut ignores direct assignments to require. With require = () => "local"; require("./ghost.cjs");, no lexical binding is created, so this branch returns true even though the source replaced the loader. The previous isUnmodifiedGlobalReference check rejected this visible write. The analyzer now records a dependency for a call that loads nothing.

Keep the distinction between globalThis.require and the wrapper's require, but still reject direct writes to the latter.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/domain/javascript/javascriptStaticAnalysisCalls.ts
Line: 234

Comment:
**Replaced loaders invent dependencies**

The new script shortcut ignores direct assignments to `require`. With `require = () => "local"; require("./ghost.cjs");`, no lexical binding is created, so this branch returns `true` even though the source replaced the loader. The previous `isUnmodifiedGlobalReference` check rejected this visible write. The analyzer now records a dependency for a call that loads nothing.

Keep the distinction between `globalThis.require` and the wrapper's `require`, but still reject direct writes to the latter.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +294 to +295
binding.definitions.every(({ kind }) => kind === "variable") &&
binding.initializers.length === 0 &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Let bindings become loaders

isTopLevelCommonJsVarRequire also accepts let require;. Both var and let declarations get the semantic kind "variable", and a top-level declaration without a value has no initializer. Thus let require; function run() { return require("./ghost.cjs"); } passes this check even though let creates a separate binding, not a wrapper redeclaration. The analyzer records a false loader reference even when run is never called.

Check that the actual declaration uses var, and add a negative test for let.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/domain/javascript/javascriptStaticAnalysisCalls.ts
Line: 294-295

Comment:
**Let bindings become loaders**

`isTopLevelCommonJsVarRequire` also accepts `let require;`. Both `var` and `let` declarations get the semantic kind `"variable"`, and a top-level declaration without a value has no initializer. Thus `let require; function run() { return require("./ghost.cjs"); }` passes this check even though `let` creates a separate binding, not a wrapper redeclaration. The analyzer records a false loader reference even when `run` is never called.

Check that the actual declaration uses `var`, and add a negative test for `let`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 75aafcf6cd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

callee: t.Node,
state: JavaScriptSemanticAnalysisState,
): boolean => {
if (!t.isIdentifier(callee)) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Accept namespace-imported createRequire factories

When ESM uses import * as Module from "node:module"; const require = Module.createRequire(import.meta.url); require("./dep.cjs"), the factory callee is a MemberExpression, so this guard returns false even though the namespace binding's directOrigins proves the same Node export; the resulting static reference and application imports edge disappear. Fresh evidence beyond the earlier aliased named-import report is this final identifier-only guard, so resolve the root import plus the createRequire member path as the neighboring worker-import logic does.

AGENTS.md reference: AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

isTopLevelCommonJsVarRequire(binding, callee, state)
)
return true;
const initializer = semanticStableInitializer(binding, state)?.node;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve loader calls before top-level var initializers

In a script/CommonJS file with require("./real.cjs"); var require = shim;, the top-level var shares Node's wrapper parameter and its initializer does not run until after the first call, so that call still uses the loader. This whole-binding initializer lookup sees shim for every reference and rejects the earlier call, removing the static reference and application import edge. Fresh evidence beyond the prior uninitialized-var case is execution ordering: a later initializer must not retroactively classify an earlier reference, so preserve the loader possibility until a write that can precede that call.

AGENTS.md reference: AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

node,
name,
moduleRequireName,
isRequireLoaderReference(node.callee, bindings, name),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate loader provenance checks to require calls

isRequireLoaderReference is evaluated eagerly for every call and new expression, although requireCallSpecifier reads the result only for the exact name require; ordinary local and global calls now traverse semantic scope chains, sometimes again through initializer import provenance. On large minified bundles where calls dominate nodes, this adds avoidable analysis CPU and time proportional to all call sites, so guard this lookup with name === "require" or make the argument lazy.

AGENTS.md reference: AGENTS.md:L49-L49

Useful? React with 👍 / 👎.

@morluto
morluto force-pushed the fix/shadowed-require branch from 75aafcf to f9898f6 Compare October 13, 2026 18:06
Comment on lines +242 to +251
const initializer = semanticStableInitializer(binding, state)?.node;
const expression =
initializer !== undefined && t.isVariableDeclarator(initializer)
? initializer.init
: initializer;
return (
expression !== null &&
expression !== undefined &&
t.isCallExpression(expression) &&
isNodeCreateRequireFactory(expression.callee, state)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Loader properties become imports

isRequireLoaderReference ignores the initializer's projection. With import { createRequire } from 'node:module'; const { resolve: require } = createRequire(import.meta.url); require('./ghost.cjs');, the local require calls the loader's resolve method, not the loader. The new check still records ./ghost.cjs as a dependency even though the call never loads it.

Require an empty projection before treating the binding as the returned loader, and add a regression test.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/domain/javascript/javascriptStaticAnalysisCalls.ts
Line: 242-251

Comment:
**Loader properties become imports**

`isRequireLoaderReference` ignores the initializer's `projection`. With `import { createRequire } from 'node:module'; const { resolve: require } = createRequire(import.meta.url); require('./ghost.cjs');`, the local `require` calls the loader's `resolve` method, not the loader. The new check still records `./ghost.cjs` as a dependency even though the call never loads it.

Require an empty `projection` before treating the binding as the returned loader, and add a regression test.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f9898f68a3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +250 to +251
t.isCallExpression(expression) &&
isNodeCreateRequireFactory(expression.callee, state)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Follow stable aliases of the CommonJS loader

In a CommonJS module using const loader = require; { const require = loader; require("./dep.cjs"); }, semanticStableInitializer returns the loader identifier, but this call-expression-only gate returns false, so the static reference and the imports edge created from it in src/application/javascript/JavaScriptArtifactGraphFindings.ts:82-162 disappear. Fresh evidence beyond the createRequire threads is that this binding is a direct alias of the ambient wrapper loader rather than another factory-import spelling; follow stable identifier aliases (or retain the candidate) before rejecting the binding.

AGENTS.md reference: AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

@morluto
morluto force-pushed the fix/shadowed-require branch from f9898f6 to 45ecff9 Compare October 13, 2026 18:24

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 45ecff9622

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return (
expression !== null &&
expression !== undefined &&
t.isCallExpression(expression) &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Unwrap type-only createRequire initializers

When TypeScript writes a loader as const require = createRequire(import.meta.url) as NodeRequire, semanticStableInitializer returns the TSAsExpression, so this strict CallExpression check is false even though the assertion is erased at runtime. A subsequent require("./dep.cjs") is therefore omitted from references and from the application imports edge produced in JavaScriptArtifactGraphFindings.ts; this is fresh syntax evidence beyond the earlier bare-createRequire case, because that bare form now works while the parser's supported type-only wrappers still need unwrapJavaScriptExpression before classification.

AGENTS.md reference: AGENTS.md:L23-L23

Useful? React with 👍 / 👎.

吴佳浩 and others added 3 commits October 14, 2026 02:47
`requireCallSpecifier` treated any call named `require` as the CommonJS loader,
so a local function parameter named `require` produced a loader reference and
the application graph invented an `imports` edge for it.

Take whether the callee is an unshadowed loader reference, computed with
`isUnmodifiedGlobalReference`, which matches the ownership rule the semantic
path already applies in `javascriptSemanticRequireOrigin.ts`. Member calls and
the module factory's own require parameter keep their lanes.

Validation: the new test records the shadowed call without this change and not
with it, while an unshadowed require is still recorded;
`src/domain/javascript/*.test.ts` passes 57 files / 1283 tests; `check:fast`
passes.
The shadow check dropped two lanes the static path recorded before, which the
review found:

- `module.require("./dep.cjs")` has the callee name `module.require`, which no
  remaining branch accepts, so member loader calls disappeared.
- `const require = createRequire(import.meta.url)` is a real loader, but its
  local binding made the check refuse the call.

Restore the member lane, and recognize a binding whose stable initializer calls
`createRequire`. A parameter or any other binding only shares the name with the
loader and is still refused.

Validation: the regression test covers all four lanes: an unshadowed require, a
member require, a createRequire binding, and a shadowing parameter that must not
be recorded.
@morluto
morluto force-pushed the fix/shadowed-require branch from 45ecff9 to 9ed55f6 Compare October 13, 2026 18:47
Comment on lines +263 to +265
binding?.runtimeImport === true &&
binding.definitions.length === 1 &&
binding.directOrigins.length === 1 &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 CommonJS loader dependencies disappear

isNodeCreateRequireFactory rejects factories loaded through CommonJS. For example, const { createRequire } = require("node:module"); function run() { const require = createRequire(__filename); return require("./real.cjs"); } run(); creates a real loader, but the factory binding has runtimeImport: false and no directOrigins. The new check therefore drops ./real.cjs from references and the application graph.

Recover the factory's CommonJS origin instead of requiring an ESM import, and add a regression test.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/domain/javascript/javascriptStaticAnalysisCalls.ts
Line: 263-265

Comment:
**CommonJS loader dependencies disappear**

`isNodeCreateRequireFactory` rejects factories loaded through CommonJS. For example, `const { createRequire } = require("node:module"); function run() { const require = createRequire(__filename); return require("./real.cjs"); } run();` creates a real loader, but the factory binding has `runtimeImport: false` and no `directOrigins`. The new check therefore drops `./real.cjs` from `references` and the application graph.

Recover the factory's CommonJS origin instead of requiring an ESM import, and add a regression test.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9ed55f69cd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +250 to +251
t.isCallExpression(expression) &&
isNodeCreateRequireFactory(expression.callee, state)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve loader parameters in AMD factories

For RequireJS/AMD sources using the simplified CommonJS form define(function (require) { return require("./dep"); }), that parameter is the real module loader, not a shadowing local. It has no stable initializer, so this createRequire-only predicate now returns false; the moduleRequireName escape hatch is not populated because javascriptStaticAnalysisBundler.ts:42-49 only accepts webpack/rspack chunk-array registrations. The static reference and downstream imports edge that the previous name-based scanner emitted therefore disappear for every such factory, so recognize the define factory's loader parameter before rejecting resolved bindings.

AGENTS.md reference: AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

@morluto

morluto commented Oct 13, 2026

Copy link
Copy Markdown
Owner

Closing as superseded by #2989, which remains open for the shadowed-require fix. It covers the loader-versus-local-binding distinction here and carries broader producer and public CLI/MCP coverage, including bundle-factory loader ownership, createRequire bindings, hoisted declarations, and reassignment/loop ordering. I compared both current diffs. This closes the duplicate implementation; the underlying fix is still pending review and merge in #2989. Thank you for the contribution.

@morluto morluto closed this Oct 13, 2026
@morluto morluto added bug Something isn't working area: domain Domain logic, errors, and pure functions labels Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: domain Domain logic, errors, and pure functions bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants