Skip to content

fix(javascript): retain unbound require semantic module links - #1803

Closed
rekcilyssup wants to merge 1 commit into
morluto:mainfrom
rekcilyssup:fix/javascript-unbound-require-semantic-link
Closed

rekcilyssup wants to merge 1 commit into
morluto:mainfrom
rekcilyssup:fix/javascript-unbound-require-semantic-link

Conversation

@rekcilyssup

Copy link
Copy Markdown
Contributor

Summary

Fixes #1793.

Bare require calls established no binding, so collectSemanticModuleLink never visited the call itself. The application graph kept the static asset reference but omitted the semantic module-to-module edge and reported import_relationships: 0 for the importing module.

Problem and expected behavior

Repro from #1793 (main.cjs containing only require("./dependency.cjs");):

  • Before: zero module_link_kind: "require" edges from main.cjs; source-module observation reports import_relationships: 0.
  • After: one resolved semantic require edge with null imported/local/exported names; import_relationships: 1.

Change and scope

  • src/domain/javascript/javascriptSemanticProjection.ts: collectSemanticModuleLink now also visits CallExpression nodes via collectUnboundRequireLink. It walks up through member-expression bases and transparent TS/paren wrappers to find the owning root; declarator initializers and assignment right-hand sides stay owned by collectRequireLink/collectCommonJsExport (even when their patterns yield no binding, so no duplicate links). semanticRequireOrigin still enforces lexical shadowing and literal specifiers, and member-chain requires (non-empty imported path) are skipped.
  • Not included: package target selection after a link exists (covered by fix(javascript): honor package exports before sibling files #1762).

Contract and boundary impact

  • Semantic owner and earliest changed stage: collectSemanticModuleLink in the JavaScript semantic projection; bare require calls now emit require module links with null imported/local/exported names.
  • CLI and MCP/tool-catalog contract: none.
  • Provider, bridge, target-format, or platform compatibility: none; static JavaScript analysis only.
  • Evidence, artifact, provenance, or reconstruction contract: analyze-javascript-application now emits a semantic imports edge plus an incremented import_relationships count for unbound requires; module_format can flip from unknown to commonjs for files whose only require was unbound. No evidence shape changes; the new link uses the existing module-link transfer schema (all-nullable name fields already allowed).
  • Process execution, authorization, cleanup, or containment impact: none.
  • Generated metadata, package, or installation impact: none.

Evidence and regression coverage

  • Tests added or updated:

    • src/domain/javascript/javascriptSemanticAnalysis.structure.test.ts: bare require emits one null-named link; call/argument/return positions each emit one; bound/destructured/export-owned positions gain no duplicate; shadowed and dynamic calls stay link-free.
    • tests/boundary/filesystem/javascriptRequireLiteralPaths.test.ts: end-to-end CLI-shape boundary test reproducing Unbound CommonJS require calls lack semantic module edges and report zero import relationships #1793 (three-file fixture from the issue), asserting the resolved semantic edge, null names, import_relationships: 1, and complete coverage.
  • Base reproduction or other evidence: reproduced on 12c41600 with the issue's exact fixture (static asset edges present, zero semantic require edges); after the fix the same command yields one resolved semantic edge and import_relationships: 1.

  • User-visible CLI/MCP output (if applicable): one additional imports edge with module_link_kind: "require" for previously unbound requires.

  • Remaining proof gaps: MCP transport parity not run separately; the boundary test exercises the shared reconstruction workflow both transports use.

  • Observed, derived, and inferred claims remain distinguishable.

  • Artifact identity, source provenance, and failed attempts remain preserved.

  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • npx vitest run --project domain src/domain/javascript/ — 43 files, 825 tests passed
  • npm run check:fast — typecheck + lint passed
  • npx vitest run --project boundary tests/boundary/filesystem/javascriptRequireLiteralPaths.test.ts tests/boundary/filesystem/javascriptPackagePrecedence.test.ts tests/boundary/filesystem/javascriptHtmlResourcePaths.test.ts tests/boundary/filesystem/referenceSourceShadowedRequire.test.ts tests/boundary/cli/javascriptSelfImportRelationships.test.ts tests/boundary/javascript/javascriptApplicationCancellation.test.ts — 5 files, 54 tests passed
  • npx vitest run --project composition tests/composition/javascript-applications/javascriptModuleRelationships.test.ts — 3 tests passed
  • npx vitest run --project acceptance tests/acceptance/applications/packageJsonImports.test.ts tests/acceptance/applications/packageSubpathsCli.test.ts — 4 tests passed
  • npx vitest run --project services src/application/javascript/ — 18 files, 285 tests passed
  • npm run test:fast — 377 files, 4346 tests passed, 7 skipped
  • node scripts/rea.mjs analyze-javascript-application /tmp/repro1793 --artifact-format directory --json — before: 0 semantic require edges, import_relationships: 0; after: 1 resolved edge, import_relationships: 1

Compatibility, safety, and release

No breaking changes. Files whose only require was previously unbound gain one semantic edge and count; downstream consumers handle null-name require links (same shape as the existing side-effect ESM import link). No migration, package, platform, or security implications.

Bare require calls (expression statements, arguments, returns, nested
compositions) established no binding, so collectSemanticModuleLink never
visited the call itself and the application graph omitted the semantic
module-to-module edge plus its source-module import count.

Visit CallExpression require origins not owned by a declarator or
assignment right-hand side; emit one link with null imported/local/
exported names. Shadowing, dynamic specifiers, and owned member/export
positions keep their existing behavior.

Fixes morluto#1793
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 11, 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-11T11:35:18.792058Z 6227285 PR opened
ℹ️ 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: 622728582f

ℹ️ 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".

);
else if (t.isVariableDeclarator(node)) collectRequireLink(node, state);
else if (t.isAssignmentExpression(node)) collectCommonJsExport(node, state);
else if (t.isCallExpression(node)) collectUnboundRequireLink(node, 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 Handle optional unbound require calls

When the source uses the supported form require?.("./dependency.cjs"), Babel emits an OptionalCallExpression; the static pass already recognizes that node in javascriptStaticAnalysis.ts:204-205, but this new dispatch only accepts CallExpression, and semanticRequireOrigin rejects optional calls too. Such a literal, unshadowed require therefore still produces only the static asset reference, with no semantic imports edge and import_relationships: 0; route optional calls through the same collector and origin checks.

AGENTS.md reference: AGENTS.md:L17-L17

Useful? React with 👍 / 👎.

@rekcilyssup

Copy link
Copy Markdown
Contributor Author

Superseded by #1799 — upstream landed the same fix (commit 3a17882) while this PR was in CI, which is why the static-checks lane reports a duplicate declaration. The upstream approach (call-identity set via linkedRequireCalls) is more general than the parent-walk here since it also covers member-base requires. Closing this PR; no further action needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unbound CommonJS require calls lack semantic module edges and report zero import relationships

1 participant