[GH-ISSUE #2954] [Bug] Browser source-map dependency extraction ignores original filename parser modes #1039

Closed
opened 2026-10-15 18:30:34 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @dakjdakd on GitHub (Oct 13, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/2954

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

Browser source-map analysis marks some valid original sources as incomplete because dependency extraction does not use their filenames when selecting the parser mode. A source map containing an ordinary TypeScript file with an angle-bracket type assertion is accepted and its original text and mappings are retained, but its original dependency list is empty and the item is reported as partial.

The smallest example is a .ts file containing an import followed by const n = <number>1;. That assertion is valid TypeScript in a plain .ts file. The existing filename-aware parser accepts the same text when given main.ts; the source-map fetcher omits that argument and the parse fails instead. This is not a malformed mapping, an unavailable original source, or a failed map download.

The same loss occurs for .cts originals with an angle-bracket assertion. A related manifestation of the same missing filename is a .cjs original containing a top-level return: REA retains its require edge but incorrectly reports the map as incomplete. These belong to one issue because the fetcher drops the input that the shared parser already uses to distinguish both cases.

The affected path is browser bundle analysis with source-map fetching enabled, exposed by analyze-web-bundle and its shared analyze_web_bundle operation. The reduced reproductions exercise fetchWebSourceMaps, the actual production boundary used by that provider. They do not establish that every browser or every public interface has been independently exercised.

Reproduction

Serve the following JSON as a successful source-map response at https://example.test/app.js.map:

{
  "version": 3,
  "names": [],
  "sources": ["main.ts"],
  "sourcesContent": ["import './dep.ts'; const n = <number>1;"],
  "mappings": "AAAA"
}
  1. Request that map through fetchWebSourceMaps, with https://example.test in allowed_origins and fetch_source_maps: true. A synthetic successful HTTP response is sufficient for the smallest reproduction; the new regression also serves the inputs from a real loopback HTTP listener.
  2. Inspect the returned item's status, limitation, and original_module_edges.
  3. Compare parsing the retained original text with parseJavaScriptSource(text, "main.ts"). The existing parser accepts it when the filename is supplied.

For a CommonJS counterpart, replace the source declaration and content with:

{
  "version": 3,
  "names": [],
  "sources": ["main.cjs"],
  "sourcesContent": ["const value = require('./dep.cjs'); return value;"],
  "mappings": "AAAA"
}

The CommonJS case keeps the ./dep.cjs dependency but also reports partial. Node's wrapper permits that return, and REA's existing parser already handles it correctly for .cjs input.

An indexed map also reproduces the TypeScript problem when the first JSON object is placed inside a section's map field. Section offsets do not change the original source's language. Sources such as main.cts?revision=2#view.tsx reproduce the missing-path problem too: parser mode should come from the URL pathname, while the query and fragment must remain part of the recorded source identity.

Expected behavior

The TypeScript map should have status: "included", no item limitation, and this dependency:

{
  "from_source": "https://example.test/main.ts",
  "kind": "static_import",
  "specifier": "./dep.ts",
  "resolved_source": "https://example.test/dep.ts"
}

The original text, map artifact, and decoded mapping should remain intact. The CommonJS example should likewise be included with its existing require dependency. Filename information should improve the completeness decision rather than change the recorded source URL or substitute a different original artifact.

The expected behavior is not to relax parsing globally. A .tsx original still needs JSX support. An .mts original must retain its prohibition on ambiguous angle-bracket assertions, and an .mjs original must not gain CommonJS top-level-return semantics. A misleading suffix in a query or fragment should not select a different mode from the actual pathname.

Impact and scope

For affected TypeScript originals, an agent or analyst loses a real import edge even though the map supplies the source text needed to derive it. That can make an original module appear disconnected or hide the dependency worth inspecting next. The partial marker makes the uncertainty visible, but it still leaves callers with a reduced dependency graph for valid input.

For the CommonJS example, the graph edge survives, so the observed problem is false incompleteness rather than total dependency loss. The TypeScript dependency loss is tied to syntax whose interpretation depends on the filename; ordinary typed declarations without ambiguous angle syntax can still parse under the current fallback mode.

This affects derived module facts and completeness reporting. The reduced examples retain their source text and mapping evidence; there is no observed corruption of fetched bytes or generated coordinates. There is also no new target execution, mutation, or cleanup behavior involved. The fix can stay inside the existing inert source-map analysis boundary.

REA version

Source revision ebaaa79ca02bde4a73013a2a9a781d0b07a57ee4.

Runtime and platform

Node.js 24.18.0, npm 11.16.0, Windows x64. Real HTTP fetch/parser boundary coverage was run on this host; other platforms were not independently qualified for this report.

Target and artifact context

Source-owned regular and indexed source maps with embedded TypeScript, TSX, and CommonJS original text. No third-party target or private artifact is required.

Interface

Other — production browser source-map fetch/parser boundary.

Earliest failure stage

Native analysis or reconstruction — specifically inert original-source dependency extraction after the source map has been decoded.

Relevant output or evidence

The TypeScript reproduction returns status: "partial", original_module_edges: [], and a limitation containing original sources could not be parsed in full. The same source passed to the existing parser with its filename produces a valid AST without parser errors.

Typed outcome or recovery expected

A successfully parsed original should yield the normal included source-map item and its derived edges. Truly invalid syntax should continue yielding a partial item with an explicit limitation; it should not silently become complete.

Analysis or suspected cause

This is source-derived and supported by the reduced reproduction. In src/browser/WebSourceMapFetcher.ts, originalModuleEdges calls parseJavaScriptSource(source.artifact.text) even though source.source is already available. In src/domain/javascript/javascriptSourceParser.ts, parserPluginsForPath and the runtime source-type selection already own the required filename semantics. With no path, TypeScript and JSX are both enabled; a plain TypeScript assertion is consequently interpreted through the wrong grammar.

Passing the resolved source URL's pathname to that existing parser restores the missing information without duplicating its mode rules. The complete URL can still be used unchanged for edge identity and resolution. No changes to type-only dependency handling or template-literal specifier recognition are needed.

Additional context

I have a focused fix ready, with a loopback HTTP regression covering regular and indexed maps, query/fragment identity, a webpack source URL, and the relevant parser-mode controls. It verifies the recovered edge and completeness result while keeping explicitly invalid module syntax partial. This issue is scoped to that missing filename input.

The fix is available in PR #2955, from the independent fix/source-map-parser-modes branch. That PR addresses this issue only.

Originally created by @dakjdakd on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2954 ## Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Area Evidence, artifacts, or reconstruction ## Observed behavior Browser source-map analysis marks some valid original sources as incomplete because dependency extraction does not use their filenames when selecting the parser mode. A source map containing an ordinary TypeScript file with an angle-bracket type assertion is accepted and its original text and mappings are retained, but its original dependency list is empty and the item is reported as `partial`. The smallest example is a `.ts` file containing an import followed by `const n = <number>1;`. That assertion is valid TypeScript in a plain `.ts` file. The existing filename-aware parser accepts the same text when given `main.ts`; the source-map fetcher omits that argument and the parse fails instead. This is not a malformed mapping, an unavailable original source, or a failed map download. The same loss occurs for `.cts` originals with an angle-bracket assertion. A related manifestation of the same missing filename is a `.cjs` original containing a top-level `return`: REA retains its `require` edge but incorrectly reports the map as incomplete. These belong to one issue because the fetcher drops the input that the shared parser already uses to distinguish both cases. The affected path is browser bundle analysis with source-map fetching enabled, exposed by `analyze-web-bundle` and its shared `analyze_web_bundle` operation. The reduced reproductions exercise `fetchWebSourceMaps`, the actual production boundary used by that provider. They do not establish that every browser or every public interface has been independently exercised. ## Reproduction Serve the following JSON as a successful source-map response at `https://example.test/app.js.map`: ```json { "version": 3, "names": [], "sources": ["main.ts"], "sourcesContent": ["import './dep.ts'; const n = <number>1;"], "mappings": "AAAA" } ``` 1. Request that map through `fetchWebSourceMaps`, with `https://example.test` in `allowed_origins` and `fetch_source_maps: true`. A synthetic successful HTTP response is sufficient for the smallest reproduction; the new regression also serves the inputs from a real loopback HTTP listener. 2. Inspect the returned item's `status`, `limitation`, and `original_module_edges`. 3. Compare parsing the retained original text with `parseJavaScriptSource(text, "main.ts")`. The existing parser accepts it when the filename is supplied. For a CommonJS counterpart, replace the source declaration and content with: ```json { "version": 3, "names": [], "sources": ["main.cjs"], "sourcesContent": ["const value = require('./dep.cjs'); return value;"], "mappings": "AAAA" } ``` The CommonJS case keeps the `./dep.cjs` dependency but also reports `partial`. Node's wrapper permits that return, and REA's existing parser already handles it correctly for `.cjs` input. An indexed map also reproduces the TypeScript problem when the first JSON object is placed inside a section's `map` field. Section offsets do not change the original source's language. Sources such as `main.cts?revision=2#view.tsx` reproduce the missing-path problem too: parser mode should come from the URL pathname, while the query and fragment must remain part of the recorded source identity. ## Expected behavior The TypeScript map should have `status: "included"`, no item limitation, and this dependency: ```json { "from_source": "https://example.test/main.ts", "kind": "static_import", "specifier": "./dep.ts", "resolved_source": "https://example.test/dep.ts" } ``` The original text, map artifact, and decoded mapping should remain intact. The CommonJS example should likewise be included with its existing `require` dependency. Filename information should improve the completeness decision rather than change the recorded source URL or substitute a different original artifact. The expected behavior is not to relax parsing globally. A `.tsx` original still needs JSX support. An `.mts` original must retain its prohibition on ambiguous angle-bracket assertions, and an `.mjs` original must not gain CommonJS top-level-return semantics. A misleading suffix in a query or fragment should not select a different mode from the actual pathname. ## Impact and scope For affected TypeScript originals, an agent or analyst loses a real import edge even though the map supplies the source text needed to derive it. That can make an original module appear disconnected or hide the dependency worth inspecting next. The `partial` marker makes the uncertainty visible, but it still leaves callers with a reduced dependency graph for valid input. For the CommonJS example, the graph edge survives, so the observed problem is false incompleteness rather than total dependency loss. The TypeScript dependency loss is tied to syntax whose interpretation depends on the filename; ordinary typed declarations without ambiguous angle syntax can still parse under the current fallback mode. This affects derived module facts and completeness reporting. The reduced examples retain their source text and mapping evidence; there is no observed corruption of fetched bytes or generated coordinates. There is also no new target execution, mutation, or cleanup behavior involved. The fix can stay inside the existing inert source-map analysis boundary. ## REA version Source revision `ebaaa79ca02bde4a73013a2a9a781d0b07a57ee4`. ## Runtime and platform Node.js 24.18.0, npm 11.16.0, Windows x64. Real HTTP fetch/parser boundary coverage was run on this host; other platforms were not independently qualified for this report. ## Target and artifact context Source-owned regular and indexed source maps with embedded TypeScript, TSX, and CommonJS original text. No third-party target or private artifact is required. ## Interface Other — production browser source-map fetch/parser boundary. ## Earliest failure stage Native analysis or reconstruction — specifically inert original-source dependency extraction after the source map has been decoded. ## Relevant output or evidence The TypeScript reproduction returns `status: "partial"`, `original_module_edges: []`, and a limitation containing `original sources could not be parsed in full`. The same source passed to the existing parser with its filename produces a valid AST without parser errors. ## Typed outcome or recovery expected A successfully parsed original should yield the normal included source-map item and its derived edges. Truly invalid syntax should continue yielding a partial item with an explicit limitation; it should not silently become complete. ## Analysis or suspected cause This is source-derived and supported by the reduced reproduction. In `src/browser/WebSourceMapFetcher.ts`, `originalModuleEdges` calls `parseJavaScriptSource(source.artifact.text)` even though `source.source` is already available. In `src/domain/javascript/javascriptSourceParser.ts`, `parserPluginsForPath` and the runtime source-type selection already own the required filename semantics. With no path, TypeScript and JSX are both enabled; a plain TypeScript assertion is consequently interpreted through the wrong grammar. Passing the resolved source URL's pathname to that existing parser restores the missing information without duplicating its mode rules. The complete URL can still be used unchanged for edge identity and resolution. No changes to type-only dependency handling or template-literal specifier recognition are needed. ## Additional context I have a focused fix ready, with a loopback HTTP regression covering regular and indexed maps, query/fragment identity, a webpack source URL, and the relevant parser-mode controls. It verifies the recovered edge and completeness result while keeping explicitly invalid module syntax partial. This issue is scoped to that missing filename input. The fix is available in [PR #2955](https://github.com/morluto/rea/pull/2955), from the independent `fix/source-map-parser-modes` branch. That PR addresses this issue only.
bsakaguchi 2026-10-15 18:30:34 +00:00
Author
Owner

@morluto commented on GitHub (Oct 14, 2026):

Verified the reported defect is addressed on main by f6905719c3.

Root cause and resolution: Original source text was parsed without its declared source filename, selecting the wrong parser mode for .ts/.cts and CommonJS sources.

Validation: tests/boundary/browser/webSourceMapFetcher.test.ts — selects parser mode from the original source URL pathname: a .ts original with <number>1 and query/hash is included, limitation is null, and ./dep.ts edge resolves.

Evidence: CI run 500e: exact owner files unchanged since that passed run; focused assertion passed.

Verification scope: Covers the reported TypeScript angle assertion; no direct .cts/.cjs case is asserted in this test.

Current main CI has separate failures under repair; this closure does not claim the complete main workflow is green.

<!-- gh-comment-id:6162501175 --> @morluto commented on GitHub (Oct 14, 2026): Verified the reported defect is addressed on main by f6905719c3c80d829037e509d6e46129b131d893. Root cause and resolution: Original source text was parsed without its declared source filename, selecting the wrong parser mode for .ts/.cts and CommonJS sources. Validation: `tests/boundary/browser/webSourceMapFetcher.test.ts` — `selects parser mode from the original source URL pathname`: a `.ts` original with `<number>1` and query/hash is included, limitation is null, and `./dep.ts` edge resolves. Evidence: CI run 500e: exact owner files unchanged since that passed run; focused assertion passed. Verification scope: Covers the reported TypeScript angle assertion; no direct `.cts`/`.cjs` case is asserted in this test. Current main CI has separate failures under repair; this closure does not claim the complete main workflow is green.
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#1039
No description provided.