[GH-ISSUE #6380] [Bug] TypeScript member keys, enum members, meta properties, and import attribute keys create false resolved lexical reads #3831

Closed
opened 2026-10-31 00:44:27 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @kshitizj03 on GitHub (Oct 22, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6380

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

JavaScript semantic analysis records a resolved lexical read of an outer binding when the same name appears in a position that is only a name, not an identifier reference:

Position Example Outer binding read
TS interface property key interface Shape { marker: string } marker
TS interface method signature interface Shape { marker(): void } marker
TS type-literal property key type Shape = { marker: string } marker
TS enum member name enum Kind { marker } marker
import.meta import.meta.url meta
new.target function F() { return new.target; } target
Import attribute key import data from "./data.json" with { type: "json" } type

Each case returns a reference with role: "read" and resolution: "resolved" pointing at the unrelated outer declaration. The equivalent object-literal key (const o = { marker: 1 }) correctly produces no reference.

This is the same class of defect as #3310 (noncomputed class member names, fixed by #3638), in positions that fix does not cover. const type = ... next to a JSON import attribute is a realistic source shape.

The public semantic graph shows it too: built with the tests/fixtures/javascriptSemanticGraph.ts fixture (graphForJavaScript), each of interface Shape { marker: string }, enum Kind { marker }, import.meta.url (binding meta) and the JSON import attribute (binding type) produces one reads relation from the outer binding.

Reproduction

From a checkout at 86655eac (main, 2026-10-22) with locked dependencies installed, save this as src/domain/javascript/nonReferenceNames.probe.test.ts and run npx vitest run src/domain/javascript/nonReferenceNames.probe.test.ts:

import { expect, it } from "vitest";
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";

const cases: [string, string][] = [
  ["marker", 'const marker = "seed";\ninterface Shape { marker: string }'],
  ["marker", 'const marker = "seed";\ninterface Shape { marker(): void }'],
  ["marker", 'const marker = "seed";\ntype Shape = { marker: string };'],
  ["marker", 'const marker = "seed";\nenum Kind { marker }'],
  ["meta", 'const meta = "seed";\nconsole.log(import.meta.url);'],
  ["target", 'const target = "seed";\nfunction F() { return new.target; }'],
  ["type", 'import data from "./data.json" with { type: "json" };\nconst type = "seed";'],
];

it.each(cases)("%s is not read through a non-reference name", (name, source) => {
  const references = analyzeJavaScriptSemantics(source).references.filter(
    (reference) => reference.name === name,
  );
  expect(references).toEqual([]);
});

Result: Tests 7 failed (7). Each failure lists one unexpected reference to the outer binding with role: "read" and resolution: "resolved". Reproduced on every run.

Expected behavior

None of these positions evaluates the outer binding, so none should create a lexical reference. Computed TS member keys (interface Shape { [marker]: string }) and enum initializers (enum Kind { first = marker }) do evaluate their expressions and must keep their reads. Both currently do (one resolved read each on 86655eac).

docs/javascript-application-workflows.md describes lexical reads, and resolved relationships as uniquely identified static targets.

Impact and scope

False resolved reads relations in retained semantic Evidence, which forward and backward semantic traces follow as data dependencies (the same impact as #3310). Demonstrated scope: the seven positions above, in TypeScript sources and in module code that uses import.meta, new.target or import attributes. Frequency in real applications: unknown.

REA version

Source revision 86655eac (main, 2026-10-22)

Runtime and platform

Node.js 24.14.0, Windows 11 x64

Target and artifact context

Inline JavaScript/TypeScript source text (no application artifact).

Interface

Other (module-level analyzeJavaScriptSemantics and the semantic-graph projection; CLI analysis is blocked on Windows source checkouts by #3501)

Earliest failure stage

Artifact or evidence registration

Relevant output or evidence

The reproduction above (module-level vitest run, executed) and the semantic-graph check in Observed behavior.

Analysis or suspected cause

Source-derived: isNonReferenceKey in src/domain/javascript/javascriptSemanticProjection.ts decides whether an identifier occurrence is a reference, for collectSemanticReferencesSteps, semanticReferenceRole and closure-capture detection. It has no case for a noncomputed TSPropertySignature/TSMethodSignature key, a TSEnumMember id, a MetaProperty parent, or an ImportAttribute key. Babel's t.isReferenced lists these as non-references, but it cannot replace the predicate wholesale: it also treats assignment targets as non-references, while REA records them as write.

I have a fix that adds these cases next to the class member keys from #3638, with regression tests, and will open a PR linked to this issue.

Additional context

Related: #3310 and #3638 (class member keys, same predicate, merged).

Not covered here, found while probing the same predicate: TypeScript declaration names (interface marker {}, type marker = ..., enum marker {}, namespace marker {}) and type references (let v: marker) also produce a resolved read of an outer value binding named marker. Those are different: enum and namespace create runtime bindings, and typeof marker in a type position is a real reference, so they need a decision about how TypeScript type-space names relate to value bindings. I kept this report to names that can never be references.

Originally created by @kshitizj03 on GitHub (Oct 22, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6380 ### 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 JavaScript semantic analysis records a resolved lexical `read` of an outer binding when the same name appears in a position that is only a name, not an identifier reference: | Position | Example | Outer binding read | |---|---|---| | TS interface property key | `interface Shape { marker: string }` | `marker` | | TS interface method signature | `interface Shape { marker(): void }` | `marker` | | TS type-literal property key | `type Shape = { marker: string }` | `marker` | | TS enum member name | `enum Kind { marker }` | `marker` | | `import.meta` | `import.meta.url` | `meta` | | `new.target` | `function F() { return new.target; }` | `target` | | Import attribute key | `import data from "./data.json" with { type: "json" }` | `type` | Each case returns a reference with `role: "read"` and `resolution: "resolved"` pointing at the unrelated outer declaration. The equivalent object-literal key (`const o = { marker: 1 }`) correctly produces no reference. This is the same class of defect as #3310 (noncomputed class member names, fixed by #3638), in positions that fix does not cover. `const type = ...` next to a JSON import attribute is a realistic source shape. The public semantic graph shows it too: built with the `tests/fixtures/javascriptSemanticGraph.ts` fixture (`graphForJavaScript`), each of `interface Shape { marker: string }`, `enum Kind { marker }`, `import.meta.url` (binding `meta`) and the JSON import attribute (binding `type`) produces one `reads` relation from the outer binding. ### Reproduction From a checkout at `86655eac` (main, 2026-10-22) with locked dependencies installed, save this as `src/domain/javascript/nonReferenceNames.probe.test.ts` and run `npx vitest run src/domain/javascript/nonReferenceNames.probe.test.ts`: ```ts import { expect, it } from "vitest"; import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js"; const cases: [string, string][] = [ ["marker", 'const marker = "seed";\ninterface Shape { marker: string }'], ["marker", 'const marker = "seed";\ninterface Shape { marker(): void }'], ["marker", 'const marker = "seed";\ntype Shape = { marker: string };'], ["marker", 'const marker = "seed";\nenum Kind { marker }'], ["meta", 'const meta = "seed";\nconsole.log(import.meta.url);'], ["target", 'const target = "seed";\nfunction F() { return new.target; }'], ["type", 'import data from "./data.json" with { type: "json" };\nconst type = "seed";'], ]; it.each(cases)("%s is not read through a non-reference name", (name, source) => { const references = analyzeJavaScriptSemantics(source).references.filter( (reference) => reference.name === name, ); expect(references).toEqual([]); }); ``` Result: `Tests 7 failed (7)`. Each failure lists one unexpected reference to the outer binding with `role: "read"` and `resolution: "resolved"`. Reproduced on every run. ### Expected behavior None of these positions evaluates the outer binding, so none should create a lexical reference. Computed TS member keys (`interface Shape { [marker]: string }`) and enum initializers (`enum Kind { first = marker }`) do evaluate their expressions and must keep their reads. Both currently do (one resolved read each on `86655eac`). `docs/javascript-application-workflows.md` describes lexical reads, and resolved relationships as uniquely identified static targets. ### Impact and scope False resolved `reads` relations in retained semantic Evidence, which forward and backward semantic traces follow as data dependencies (the same impact as #3310). Demonstrated scope: the seven positions above, in TypeScript sources and in module code that uses `import.meta`, `new.target` or import attributes. Frequency in real applications: unknown. ### REA version Source revision `86655eac` (main, 2026-10-22) ### Runtime and platform Node.js 24.14.0, Windows 11 x64 ### Target and artifact context Inline JavaScript/TypeScript source text (no application artifact). ### Interface Other (module-level `analyzeJavaScriptSemantics` and the semantic-graph projection; CLI analysis is blocked on Windows source checkouts by #3501) ### Earliest failure stage Artifact or evidence registration ### Relevant output or evidence The reproduction above (module-level vitest run, executed) and the semantic-graph check in Observed behavior. ### Analysis or suspected cause Source-derived: `isNonReferenceKey` in `src/domain/javascript/javascriptSemanticProjection.ts` decides whether an identifier occurrence is a reference, for `collectSemanticReferencesSteps`, `semanticReferenceRole` and closure-capture detection. It has no case for a noncomputed `TSPropertySignature`/`TSMethodSignature` key, a `TSEnumMember` id, a `MetaProperty` parent, or an `ImportAttribute` key. Babel's `t.isReferenced` lists these as non-references, but it cannot replace the predicate wholesale: it also treats assignment targets as non-references, while REA records them as `write`. I have a fix that adds these cases next to the class member keys from #3638, with regression tests, and will open a PR linked to this issue. ### Additional context Related: #3310 and #3638 (class member keys, same predicate, merged). Not covered here, found while probing the same predicate: TypeScript declaration names (`interface marker {}`, `type marker = ...`, `enum marker {}`, `namespace marker {}`) and type references (`let v: marker`) also produce a resolved read of an outer value binding named `marker`. Those are different: `enum` and `namespace` create runtime bindings, and `typeof marker` in a type position is a real reference, so they need a decision about how TypeScript type-space names relate to value bindings. I kept this report to names that can never be references.
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#3831
No description provided.