[GH-ISSUE #2058] [Bug] TypeScript import-equals declarations lose their module dependency during analysis #682

Closed
opened 2026-10-15 18:27:21 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @xu91102 on GitHub (Oct 12, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/2058

Before submitting

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

Area

Evidence, artifacts, or reconstruction — JavaScript/TypeScript source relationships.

Observed behavior

import value = require("./dep.cjs") in main.cts parses with complete coverage, but contributes no import relationship and reports module_format unknown. Actual TypeScript NodeNext emission contains require("./dep.cjs") and executes successfully, printing 42. A companion normal CJS require is already represented.

Reproduction

From the repository root at the revision below, install dependencies and run npm run build:cached. This self-contained probe creates and removes its owned temporary fixture and compiler output:

python3 - <<'PY'
import json, os, shutil, subprocess, tempfile
from pathlib import Path
repo = Path.cwd()
env = dict(os.environ, REA_LOG_LEVEL="silent")
with tempfile.TemporaryDirectory(prefix="rea-repro-") as temp:
    fixture = Path(temp) / "source"
    fixture.mkdir()
    files = {'package.json': '{"type":"commonjs"}',
     'main.cts': 'import value = require("./dep.cjs"); console.log(value);',
     'control.cjs': 'const value = require("./dep.cjs"); console.log(value);',
     'dep.cjs': 'module.exports = 42;'}
    for name, text in files.items():
        path = fixture / name
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(text + "\n")
    emitted = Path(temp) / "emitted"
    emitted.mkdir()
    args = ["node", str(repo / "node_modules/typescript/bin/tsc"), "--module", "NodeNext",
            "--moduleResolution", "NodeNext", "--target", "ES2022", "--skipLibCheck",
            "--allowJs", "--outDir", str(emitted), "main.cts", "dep.cjs"]
    subprocess.run(args, cwd=fixture, check=True)
    (emitted / "package.json").write_text('{"type":"commonjs"}\n')
    if (fixture / "node_modules").exists():
        shutil.copytree(fixture / "node_modules", emitted / "node_modules")
    result = subprocess.run(["node", "main.cjs"], cwd=emitted, capture_output=True, text=True)
    print("Node emitted main.cjs", "exit", result.returncode, "stdout", result.stdout.strip())
    result = subprocess.run(["node", str(repo / "scripts/rea.mjs"),
                             "analyze-javascript-application", str(fixture), "--format", "json"],
                            cwd=repo, env=env, capture_output=True, text=True, check=True)
    graph = json.loads(result.stdout)["normalized_result"]["graph"]
    links = [e["properties"] for e in graph["edges"] if e["relation"] == "imports"
             and "module_link_kind" in e["properties"]
             and e["evidence"]["location"].get("value", {}).get("source") == "main.cts"]
    formats = [o["properties"]["module_format"] for n in graph["nodes"] for o in n["observations"]
               if o.get("properties", {}).get("semantic_role") == "source-module"
               and o["properties"].get("path") == "main.cts"]
    print("REA coverage", graph["coverage"]["status"], "format", formats)
    print("REA semantic imports", json.dumps(links))
PY

Expected behavior

Retain the value import-equals dependency and immutable local alias with require semantics and the original declaration range and source SHA. Type-only declarations and internal namespace aliases must not invent runtime external dependencies. An MTS source must remain ESM when its import-equals compiles through createRequire.

Impact and scope

The graph silently omits a dependency loaded by the emitted program and understates source module relationships. The target is an owned local JavaScript/TypeScript directory; no target execution or native provider is needed by REA.

REA version

Source revision 3bdc8730bd98e073522bcb8ba86527be402c0cbe (package version 6.4.0).

Runtime and platform

Node.js 24.18.0, npm 11.16.0, TypeScript 7.0.2; macOS 27.0.1 arm64.

Target and artifact context

Local source directory fixture. No browser, Hopper, Ghidra or credentials.

Interface

Compiled CLI analyze-javascript-application; regression coverage also uses real stdio MCP analyze_javascript_application.

Earliest failure stage

Transport or result projection — static source dependency relationships.

Relevant output or evidence

NodeNext compiler exit 0; Node emitted main.cjs exit 0, stdout 42.
REA main.cts: zero import relationships, module_format unknown.
REA companion control.cjs: ./dep.cjs resolved.
CLI result reports complete coverage; repeated twice on the revision below.

Analysis or suspected cause

Source-derived: TSImportEqualsDeclaration parses but is not classified by the binding, semantic module-link or legacy static-reference producers.

Originally created by @xu91102 on GitHub (Oct 12, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2058 ### Before submitting - [x] I searched open and closed issues and PRs for a related report. - [x] I removed credentials, private paths and sensitive target data. ### Area Evidence, artifacts, or reconstruction — JavaScript/TypeScript source relationships. ### Observed behavior import value = require("./dep.cjs") in main.cts parses with complete coverage, but contributes no import relationship and reports module_format unknown. Actual TypeScript NodeNext emission contains require("./dep.cjs") and executes successfully, printing 42. A companion normal CJS require is already represented. ### Reproduction From the repository root at the revision below, install dependencies and run `npm run build:cached`. This self-contained probe creates and removes its owned temporary fixture and compiler output: ```sh python3 - <<'PY' import json, os, shutil, subprocess, tempfile from pathlib import Path repo = Path.cwd() env = dict(os.environ, REA_LOG_LEVEL="silent") with tempfile.TemporaryDirectory(prefix="rea-repro-") as temp: fixture = Path(temp) / "source" fixture.mkdir() files = {'package.json': '{"type":"commonjs"}', 'main.cts': 'import value = require("./dep.cjs"); console.log(value);', 'control.cjs': 'const value = require("./dep.cjs"); console.log(value);', 'dep.cjs': 'module.exports = 42;'} for name, text in files.items(): path = fixture / name path.parent.mkdir(parents=True, exist_ok=True) path.write_text(text + "\n") emitted = Path(temp) / "emitted" emitted.mkdir() args = ["node", str(repo / "node_modules/typescript/bin/tsc"), "--module", "NodeNext", "--moduleResolution", "NodeNext", "--target", "ES2022", "--skipLibCheck", "--allowJs", "--outDir", str(emitted), "main.cts", "dep.cjs"] subprocess.run(args, cwd=fixture, check=True) (emitted / "package.json").write_text('{"type":"commonjs"}\n') if (fixture / "node_modules").exists(): shutil.copytree(fixture / "node_modules", emitted / "node_modules") result = subprocess.run(["node", "main.cjs"], cwd=emitted, capture_output=True, text=True) print("Node emitted main.cjs", "exit", result.returncode, "stdout", result.stdout.strip()) result = subprocess.run(["node", str(repo / "scripts/rea.mjs"), "analyze-javascript-application", str(fixture), "--format", "json"], cwd=repo, env=env, capture_output=True, text=True, check=True) graph = json.loads(result.stdout)["normalized_result"]["graph"] links = [e["properties"] for e in graph["edges"] if e["relation"] == "imports" and "module_link_kind" in e["properties"] and e["evidence"]["location"].get("value", {}).get("source") == "main.cts"] formats = [o["properties"]["module_format"] for n in graph["nodes"] for o in n["observations"] if o.get("properties", {}).get("semantic_role") == "source-module" and o["properties"].get("path") == "main.cts"] print("REA coverage", graph["coverage"]["status"], "format", formats) print("REA semantic imports", json.dumps(links)) PY ``` ### Expected behavior Retain the value import-equals dependency and immutable local alias with require semantics and the original declaration range and source SHA. Type-only declarations and internal namespace aliases must not invent runtime external dependencies. An MTS source must remain ESM when its import-equals compiles through createRequire. ### Impact and scope The graph silently omits a dependency loaded by the emitted program and understates source module relationships. The target is an owned local JavaScript/TypeScript directory; no target execution or native provider is needed by REA. ### REA version Source revision `3bdc8730bd98e073522bcb8ba86527be402c0cbe` (package version 6.4.0). ### Runtime and platform Node.js 24.18.0, npm 11.16.0, TypeScript 7.0.2; macOS 27.0.1 arm64. ### Target and artifact context Local source directory fixture. No browser, Hopper, Ghidra or credentials. ### Interface Compiled CLI `analyze-javascript-application`; regression coverage also uses real stdio MCP `analyze_javascript_application`. ### Earliest failure stage Transport or result projection — static source dependency relationships. ### Relevant output or evidence ```text NodeNext compiler exit 0; Node emitted main.cjs exit 0, stdout 42. REA main.cts: zero import relationships, module_format unknown. REA companion control.cjs: ./dep.cjs resolved. CLI result reports complete coverage; repeated twice on the revision below. ``` ### Analysis or suspected cause Source-derived: TSImportEqualsDeclaration parses but is not classified by the binding, semantic module-link or legacy static-reference producers.
bsakaguchi 2026-10-15 18:27:21 +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#682
No description provided.