[PR #1820] [CLOSED] fix(javascript): resolve NodeNext emitted extensions in application analysis #4838

Closed
opened 2026-10-31 00:55:41 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/1820
Author: @BaaRai
Created: 10/11/2026
Status: ❌ Closed

Base: main ← Head: fix/analyze-nodenext-emitted-extensions


📝 Commits (4)

  • 11a31e0 fix(javascript): resolve NodeNext emitted extensions in application analysis
  • e0bee7b fix(javascript): preserve directories when substituting emitted dotfile names
  • d382550 test(javascript): expect emitted-name rewrites to join self-import disclosure
  • 59d3152 fix(javascript): restrict emitted-source substitution to relative module specifiers

📊 Changes

5 files changed (+252 additions, -23 deletions)

View changed files

📝 src/application/ReferenceSourceImportEntries.ts (+2 -15)
➕ src/application/TypeScriptEmittedSourceCandidates.ts (+31 -0)
📝 src/application/javascript/JavaScriptArtifactPathResolution.test.ts (+182 -0)
📝 src/application/javascript/JavaScriptArtifactPathResolution.ts (+18 -1)
📝 tests/boundary/cli/javascriptSelfImportRelationships.test.ts (+19 -7)

📄 Description

Summary

analyze-javascript-application reported ./dep.js as not-found whenever only dep.ts existed on disk, so TypeScript NodeNext source trees lost almost all internal import edges and feature traces stopped at the first import. The reference-source importer gained exactly this substitution through #755/#815; the application resolver (resolveCandidate) did not. This PR applies the same rule there, extracted into one shared helper used by both resolvers.

Problem and expected behavior

In a TypeScript ESM/NodeNext source tree, relative imports name the emitted file: import { dep } from "./dep.js" with only dep.ts present resolves under tsc --module NodeNext --moduleResolution NodeNext, as do ./x.mjs -> x.mts, ./x.cjs -> x.cts, ./B.js -> B.tsx, and the declaration counterparts (.d.ts, .d.mts, .d.cts). Application analysis reported every one as not-found with no unknowns entry, silently. This is #1811; the resolver gap was also flagged during the #1130 merge review.

Expected: these specifiers resolve to their TypeScript source counterparts, and every previously working shape keeps resolving.

Change and scope

  • New src/application/TypeScriptEmittedSourceCandidates.ts: for TypeScript-family callers (.ts, .tsx, .mts, .cts, including declaration callers, matching the tsc-verified behavior retained in #755), a candidate ending in an emitted extension substitutes its source counterparts (.js -> .ts/.tsx/.d.ts, .mjs -> .mts/.d.mts, .cjs -> .cts/.d.cts, .jsx -> .tsx/.d.ts). Explicit filenames only: no directory or index resolution. JavaScript callers get no fallback.
  • resolveCandidate appends these after the existing runtime candidates, so a present runtime file always wins and every other candidate order is unchanged.
  • ReferenceSourceImportEntries.resolveInternalSpecifier now calls the same helper instead of its inline copy. The logic is token-identical to the merged #815 rule (same caller gate, same suffix lists, same stem computation); its eight behavior tests are unchanged and pass.
  • Bare specifiers, package exports/entrypoint resolution, and HTML/URL contexts are untouched: the fallback lives only in the relative-candidate file lookup.

Contract and boundary impact

not applicable: resolver candidate list extension only; no schema, contract, or provider change. Module edges that previously carried resolution_status: "not-found" now carry resolved paths, which is the issue's requested outcome.

Evidence and regression coverage

  • Red-first unit coverage in JavaScriptArtifactPathResolution.test.ts: five emitted-name cases (.js->.ts, ./ui/Button.js->.tsx, .mjs->.mts, .cjs->.cts, .js->.tsx), three declaration-counterpart cases, plus four controls that already passed and must keep passing (runtime file wins, JavaScript-caller negative, no index substitution, extensionless resolution). 78/78 pass.
  • The #815 regression suite (referenceSourceImport) is untouched by behavior: its failures on Windows are the known symlink-EPERM environment artifacts, identical count on a clean main worktree.
  • End-to-end with the exact #1811 reproduction tree (Linux, node 24.19, compiled CLI): all five module_link_kind: "import" edges resolve - ./dep.js -> src/dep.ts, ./ui/Button.js -> src/ui/Button.tsx, ./cfg.mjs -> src/cfg.mts, ./legacy.cjs -> src/legacy.cts, ./util -> src/util.ts - and the graph reports zero unknown nodes.
  • check:fast passes; jscpd stays under its budget (the shared helper replaces the inline copy rather than adding duplication).

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/1820 **Author:** [@BaaRai](https://github.com/BaaRai) **Created:** 10/11/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/analyze-nodenext-emitted-extensions` --- ### 📝 Commits (4) - [`11a31e0`](https://github.com/morluto/rea/commit/11a31e0ec7bc0ff4088cf9ad66370e7724184754) fix(javascript): resolve NodeNext emitted extensions in application analysis - [`e0bee7b`](https://github.com/morluto/rea/commit/e0bee7b632eb84e30a56d3a0d33388c984b58689) fix(javascript): preserve directories when substituting emitted dotfile names - [`d382550`](https://github.com/morluto/rea/commit/d382550a3130f50ecc9e3e09ebebf88b7b2db597) test(javascript): expect emitted-name rewrites to join self-import disclosure - [`59d3152`](https://github.com/morluto/rea/commit/59d315273667a87f3153064db4fb4c5921cfdec4) fix(javascript): restrict emitted-source substitution to relative module specifiers ### 📊 Changes **5 files changed** (+252 additions, -23 deletions) <details> <summary>View changed files</summary> 📝 `src/application/ReferenceSourceImportEntries.ts` (+2 -15) ➕ `src/application/TypeScriptEmittedSourceCandidates.ts` (+31 -0) 📝 `src/application/javascript/JavaScriptArtifactPathResolution.test.ts` (+182 -0) 📝 `src/application/javascript/JavaScriptArtifactPathResolution.ts` (+18 -1) 📝 `tests/boundary/cli/javascriptSelfImportRelationships.test.ts` (+19 -7) </details> ### 📄 Description ## Summary `analyze-javascript-application` reported `./dep.js` as `not-found` whenever only `dep.ts` existed on disk, so TypeScript NodeNext source trees lost almost all internal import edges and feature traces stopped at the first import. The reference-source importer gained exactly this substitution through #755/#815; the application resolver (`resolveCandidate`) did not. This PR applies the same rule there, extracted into one shared helper used by both resolvers. ## Problem and expected behavior In a TypeScript ESM/NodeNext source tree, relative imports name the emitted file: `import { dep } from "./dep.js"` with only `dep.ts` present resolves under `tsc --module NodeNext --moduleResolution NodeNext`, as do `./x.mjs` -> `x.mts`, `./x.cjs` -> `x.cts`, `./B.js` -> `B.tsx`, and the declaration counterparts (`.d.ts`, `.d.mts`, `.d.cts`). Application analysis reported every one as `not-found` with no `unknowns` entry, silently. This is #1811; the resolver gap was also flagged during the #1130 merge review. Expected: these specifiers resolve to their TypeScript source counterparts, and every previously working shape keeps resolving. ## Change and scope - New `src/application/TypeScriptEmittedSourceCandidates.ts`: for TypeScript-family callers (`.ts`, `.tsx`, `.mts`, `.cts`, including declaration callers, matching the tsc-verified behavior retained in #755), a candidate ending in an emitted extension substitutes its source counterparts (`.js` -> `.ts`/`.tsx`/`.d.ts`, `.mjs` -> `.mts`/`.d.mts`, `.cjs` -> `.cts`/`.d.cts`, `.jsx` -> `.tsx`/`.d.ts`). Explicit filenames only: no directory or index resolution. JavaScript callers get no fallback. - `resolveCandidate` appends these after the existing runtime candidates, so a present runtime file always wins and every other candidate order is unchanged. - `ReferenceSourceImportEntries.resolveInternalSpecifier` now calls the same helper instead of its inline copy. The logic is token-identical to the merged #815 rule (same caller gate, same suffix lists, same stem computation); its eight behavior tests are unchanged and pass. - Bare specifiers, package exports/entrypoint resolution, and HTML/URL contexts are untouched: the fallback lives only in the relative-candidate file lookup. ## Contract and boundary impact not applicable: resolver candidate list extension only; no schema, contract, or provider change. Module edges that previously carried `resolution_status: "not-found"` now carry resolved paths, which is the issue's requested outcome. ## Evidence and regression coverage - Red-first unit coverage in `JavaScriptArtifactPathResolution.test.ts`: five emitted-name cases (`.js`->`.ts`, `./ui/Button.js`->`.tsx`, `.mjs`->`.mts`, `.cjs`->`.cts`, `.js`->`.tsx`), three declaration-counterpart cases, plus four controls that already passed and must keep passing (runtime file wins, JavaScript-caller negative, no index substitution, extensionless resolution). 78/78 pass. - The #815 regression suite (`referenceSourceImport`) is untouched by behavior: its failures on Windows are the known symlink-EPERM environment artifacts, identical count on a clean main worktree. - End-to-end with the exact #1811 reproduction tree (Linux, node 24.19, compiled CLI): all five `module_link_kind: "import"` edges resolve - `./dep.js` -> `src/dep.ts`, `./ui/Button.js` -> `src/ui/Button.tsx`, `./cfg.mjs` -> `src/cfg.mts`, `./legacy.cjs` -> `src/legacy.cts`, `./util` -> `src/util.ts` - and the graph reports zero unknown nodes. - `check:fast` passes; jscpd stays under its budget (the shared helper replaces the inline copy rather than adding duplication). --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 00:55:41 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#4838
No description provided.