[PR #2366] [MERGED] fix(javascript): keep paths after __dirname inside the source directory #5191

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

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/2366
Author: @Chaoqi31
Created: 10/12/2026
Status: ✅ Merged
Merged: 10/13/2026
Merged by: @morluto

Base: main ← Head: fix/javascript-join-leading-slash


📝 Commits (1)

  • 9554762 fix(javascript): keep paths after __dirname inside the source directory

📊 Changes

2 files changed (+60 additions, -17 deletions)

View changed files

📝 src/domain/javascript/javascriptStaticAnalysisHelpers.ts (+44 -16)
📝 src/domain/javascript/javascriptStaticPathResolution.test.ts (+16 -1)

📄 Description

Summary

Electron apps often point a window or utility process at a file next to the main script with path.join(__dirname, '/preload.js'), __dirname + '/preload.js', or path.dirname(__filename) + '/preload.js'. Node resolves all three to <dir>/preload.js. REA's static path projection turned the first and third into the root path /preload.js and left the second unknown. In an app whose main script lives in src/, the preload resolved to an unrelated preload.js at the artifact root or was reported missing, and utilityProcess.fork targets written the same way went unresolved. All three now project to a path inside the source directory.

No issue is linked. I found this while comparing analyze-javascript-application with Node's path.join on synthetic Electron apps.

Problem and expected behavior

Smallest trigger, in src/main.js:

new BrowserWindow({ webPreferences: { preload: path.join(__dirname, '/join.js') } });
new BrowserWindow({ webPreferences: { preload: __dirname + '/concat.js' } });
new BrowserWindow({ webPreferences: { preload: path.dirname(__filename) + '/dirname.js' } });
utilityProcess.fork(path.join(__dirname, '/worker.js'));

path.join never restarts at an absolute segment, and string concatenation after __dirname appends to the directory. In src/domain/javascript/javascriptStaticAnalysisHelpers.ts, staticCallPath skipped a directory argument to join, so posix.join("/join.js") returned /join.js. It also projected path.dirname(__filename) to the empty string, so "" + "/dirname.js" became /dirname.js. The + branch had no case for __dirname, so __dirname + '/concat.js' stayed unknown. path.resolve has the opposite rule, and REA already models it in staticResolvedPath.

Expected: each of these projects to a path relative to the source file's directory, the same as path.join(__dirname, 'preload.js') does today.

Change and scope

  • A source directory now contributes . to a join, so /join.js after it stays inside it. As a consequence path.join(__dirname) projects to ., like path.resolve(__dirname) already does; its test moves from the unresolved list to the join table.
  • A + chain that starts with __dirname or the source file's dirname keeps text that begins with a separator inside the directory. Other text, as in __dirname + 'x', extends the directory's own name and stays unknown. The chain is read left to right without recursion, so __dirname + '/' + 'preload.js' works.
  • The empty-string projection of path.dirname(__filename) is removed; it only produced the wrong concatenation. A bare path.dirname(__filename) is now unknown, like a bare __dirname.
  • new URL(..., import.meta.url) is not a concatenation anchor, because its string form is a URL; its existing projection is unchanged.

Not changed: an anchored result nested inside another call or after other text still loses its anchor, for example 'p' + path.join(__dirname, 'y') or path.resolve('/x', path.join(__dirname, 'y')). That already happens on main for join and resolve results and needs staticPath to carry the anchor through nesting. Template literals with expressions, such as `${__dirname}/preload.js`, stay unknown.

Contribution scope: not applicable (ordinary fix within existing boundaries).

Contract and boundary impact

  • Semantic owner and earliest changed stage: staticPath and staticCallPath in src/domain/javascript/javascriptStaticAnalysisHelpers.ts, the shared static path projection used by preload, BrowserWindow, and utilityProcess.fork recovery.
  • Contribution scope fit and linked maintainer decision: ordinary fix; not applicable.
  • CLI and MCP/tool-catalog contract: no schema change. Projected paths change for join calls whose later segments start with / after a source directory, for + chains that start with a source directory, and for path.join(__dirname) and bare path.dirname(__filename).
  • Provider, bridge, target-format, or platform compatibility: JavaScript and Electron static analysis; POSIX join semantics as before.
  • Evidence, artifact, provenance, or reconstruction contract: declared_path now records the path Node would load; resolution status follows from it.
  • Target selection, authority, upstream behavior, and cleanup details (if applicable): not applicable.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact: none.

Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated: src/domain/javascript/javascriptStaticPathResolution.test.ts. "projects the source anchor" adds __dirname + "/preload.js", __dirname + "/" + "preload.js" and path.dirname(__filename) + "/lib" + "/preload.js". "preserves join behavior" adds path.join(__dirname, "/preload.js"), path.join(dirname(fileURLToPath(import.meta.url)), "/lib", "/preload.js") and path.join(__dirname). "keeps unsupported %s unknown" adds __dirname + "preload.js" and path.dirname(__filename) + "preload.js". Each projection case checks staticPath, the resolution context, the generic preload role path, the BrowserWindow preload, and the utilityProcess.fork module path; each unknown case checks that those stay null.

  • Base reproduction or other evidence: with javascriptStaticAnalysisHelpers.ts restored from main (fd958808), 7 of these cases fail. __dirname + "preload.js" passes on both, as it should.

  • User-visible CLI/MCP output (if applicable): rea analyze-javascript-application app --format json for the app above, with each preload in src/ and a decoy file of the same name at the app root. Rows are the preload loads edges and the utility process.

    Before (main):

    preload {"declared":"/join.js","resolved":"join.js","status":"resolved"}
    preload {"declared":"/dirname.js","resolved":"dirname.js","status":"resolved"}
    utility {"module_path":"/worker.js","resolved_utility_entrypoints":0}
    

    After:

    preload {"declared":"join.js","resolved":"src/join.js","status":"resolved"}
    preload {"declared":"concat.js","resolved":"src/concat.js","status":"resolved"}
    preload {"declared":"dirname.js","resolved":"src/dirname.js","status":"resolved"}
    utility {"module_path":"worker.js","resolved_utility_entrypoints":1}
    

    On main the first and third preloads resolve to the root decoys, and the second has no edge.

  • Remaining proof gaps: the nested and template-literal cases listed above. I did not run a packaged Electron app.

For evidence-bearing changes:

  • 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

  • On the branch rebased onto main fd958808, with dependencies installed by npm ci: npm run check:fast passes.
  • npm run test:focused over all 109 test files in src/domain/javascript/, src/application/javascript/, tests/boundary/filesystem/javascript-applications/ and tests/acceptance/applications/. All 1717 tests pass.
  • The before/after CLI run above, with npm run build:cached on each checkout.

Compatibility, safety, and release

  • Breaking changes or migration steps: none.
  • Real Hopper/Ghidra, browser, or OS coverage: not applicable; static analysis of a synthetic app, with Node's path module as the oracle.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: none; the projected path is still checked by the existing artifact resolution.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 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/2366 **Author:** [@Chaoqi31](https://github.com/Chaoqi31) **Created:** 10/12/2026 **Status:** ✅ Merged **Merged:** 10/13/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `fix/javascript-join-leading-slash` --- ### 📝 Commits (1) - [`9554762`](https://github.com/morluto/rea/commit/95547621a99e5e940c7e4c9e3af478942d8519bf) fix(javascript): keep paths after __dirname inside the source directory ### 📊 Changes **2 files changed** (+60 additions, -17 deletions) <details> <summary>View changed files</summary> 📝 `src/domain/javascript/javascriptStaticAnalysisHelpers.ts` (+44 -16) 📝 `src/domain/javascript/javascriptStaticPathResolution.test.ts` (+16 -1) </details> ### 📄 Description ## Summary Electron apps often point a window or utility process at a file next to the main script with `path.join(__dirname, '/preload.js')`, `__dirname + '/preload.js'`, or `path.dirname(__filename) + '/preload.js'`. Node resolves all three to `<dir>/preload.js`. REA's static path projection turned the first and third into the root path `/preload.js` and left the second unknown. In an app whose main script lives in `src/`, the preload resolved to an unrelated `preload.js` at the artifact root or was reported missing, and `utilityProcess.fork` targets written the same way went unresolved. All three now project to a path inside the source directory. No issue is linked. I found this while comparing `analyze-javascript-application` with Node's `path.join` on synthetic Electron apps. ## Problem and expected behavior Smallest trigger, in `src/main.js`: ```js new BrowserWindow({ webPreferences: { preload: path.join(__dirname, '/join.js') } }); new BrowserWindow({ webPreferences: { preload: __dirname + '/concat.js' } }); new BrowserWindow({ webPreferences: { preload: path.dirname(__filename) + '/dirname.js' } }); utilityProcess.fork(path.join(__dirname, '/worker.js')); ``` `path.join` never restarts at an absolute segment, and string concatenation after `__dirname` appends to the directory. In `src/domain/javascript/javascriptStaticAnalysisHelpers.ts`, `staticCallPath` skipped a directory argument to `join`, so `posix.join("/join.js")` returned `/join.js`. It also projected `path.dirname(__filename)` to the empty string, so `"" + "/dirname.js"` became `/dirname.js`. The `+` branch had no case for `__dirname`, so `__dirname + '/concat.js'` stayed unknown. `path.resolve` has the opposite rule, and REA already models it in `staticResolvedPath`. Expected: each of these projects to a path relative to the source file's directory, the same as `path.join(__dirname, 'preload.js')` does today. ## Change and scope - A source directory now contributes `.` to a `join`, so `/join.js` after it stays inside it. As a consequence `path.join(__dirname)` projects to `.`, like `path.resolve(__dirname)` already does; its test moves from the unresolved list to the join table. - A `+` chain that starts with `__dirname` or the source file's dirname keeps text that begins with a separator inside the directory. Other text, as in `__dirname + 'x'`, extends the directory's own name and stays unknown. The chain is read left to right without recursion, so `__dirname + '/' + 'preload.js'` works. - The empty-string projection of `path.dirname(__filename)` is removed; it only produced the wrong concatenation. A bare `path.dirname(__filename)` is now unknown, like a bare `__dirname`. - `new URL(..., import.meta.url)` is not a concatenation anchor, because its string form is a URL; its existing projection is unchanged. Not changed: an anchored result nested inside another call or after other text still loses its anchor, for example `'p' + path.join(__dirname, 'y')` or `path.resolve('/x', path.join(__dirname, 'y'))`. That already happens on `main` for `join` and `resolve` results and needs `staticPath` to carry the anchor through nesting. Template literals with expressions, such as `` `${__dirname}/preload.js` ``, stay unknown. Contribution scope: not applicable (ordinary fix within existing boundaries). ## Contract and boundary impact - Semantic owner and earliest changed stage: `staticPath` and `staticCallPath` in `src/domain/javascript/javascriptStaticAnalysisHelpers.ts`, the shared static path projection used by preload, `BrowserWindow`, and `utilityProcess.fork` recovery. - Contribution scope fit and linked maintainer decision: ordinary fix; not applicable. - CLI and MCP/tool-catalog contract: no schema change. Projected paths change for `join` calls whose later segments start with `/` after a source directory, for `+` chains that start with a source directory, and for `path.join(__dirname)` and bare `path.dirname(__filename)`. - Provider, bridge, target-format, or platform compatibility: JavaScript and Electron static analysis; POSIX `join` semantics as before. - Evidence, artifact, provenance, or reconstruction contract: `declared_path` now records the path Node would load; resolution status follows from it. - Target selection, authority, upstream behavior, and cleanup details (if applicable): not applicable. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact: none. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: `src/domain/javascript/javascriptStaticPathResolution.test.ts`. "projects the source anchor" adds `__dirname + "/preload.js"`, `__dirname + "/" + "preload.js"` and `path.dirname(__filename) + "/lib" + "/preload.js"`. "preserves join behavior" adds `path.join(__dirname, "/preload.js")`, `path.join(dirname(fileURLToPath(import.meta.url)), "/lib", "/preload.js")` and `path.join(__dirname)`. "keeps unsupported %s unknown" adds `__dirname + "preload.js"` and `path.dirname(__filename) + "preload.js"`. Each projection case checks `staticPath`, the resolution context, the generic `preload` role path, the `BrowserWindow` preload, and the `utilityProcess.fork` module path; each unknown case checks that those stay null. - Base reproduction or other evidence: with `javascriptStaticAnalysisHelpers.ts` restored from `main` (fd958808), 7 of these cases fail. `__dirname + "preload.js"` passes on both, as it should. - User-visible CLI/MCP output (if applicable): `rea analyze-javascript-application app --format json` for the app above, with each preload in `src/` and a decoy file of the same name at the app root. Rows are the preload `loads` edges and the utility process. Before (`main`): ``` preload {"declared":"/join.js","resolved":"join.js","status":"resolved"} preload {"declared":"/dirname.js","resolved":"dirname.js","status":"resolved"} utility {"module_path":"/worker.js","resolved_utility_entrypoints":0} ``` After: ``` preload {"declared":"join.js","resolved":"src/join.js","status":"resolved"} preload {"declared":"concat.js","resolved":"src/concat.js","status":"resolved"} preload {"declared":"dirname.js","resolved":"src/dirname.js","status":"resolved"} utility {"module_path":"worker.js","resolved_utility_entrypoints":1} ``` On `main` the first and third preloads resolve to the root decoys, and the second has no edge. - Remaining proof gaps: the nested and template-literal cases listed above. I did not run a packaged Electron app. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - On the branch rebased onto `main` fd958808, with dependencies installed by `npm ci`: `npm run check:fast` passes. - `npm run test:focused` over all 109 test files in `src/domain/javascript/`, `src/application/javascript/`, `tests/boundary/filesystem/javascript-applications/` and `tests/acceptance/applications/`. All 1717 tests pass. - The before/after CLI run above, with `npm run build:cached` on each checkout. ## Compatibility, safety, and release - Breaking changes or migration steps: none. - Real Hopper/Ghidra, browser, or OS coverage: not applicable; static analysis of a synthetic app, with Node's `path` module as the oracle. - Package or release metadata impact: none. - Security, privacy, process, or containment review: none; the projected path is still checked by the existing artifact resolution. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 01:04: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#5191
No description provided.