[GH-ISSUE #4371] [Bug] TypeScript parameter-property declarations are emitted as identifier reads #1728

Closed
opened 2026-10-16 21:09:32 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 16, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4371

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

Source-boundary reproduction: the semantic reference collector emits an x:read reference at the parameter declaration in valid TypeScript class C { constructor(public x: number) {} }, whose constructor body is empty. public x = 1 behaves the same way. An ordinary constructor parameter produces no declaration read. This report is source-only with respect to the public CLI/MCP workflows.

Reproduction

Use a source checkout at either revision in the version field, with the pinned Node toolchain and lockfile dependencies (npm ci for a fresh checkout). Save this complete script as repro.mts at the checkout root:

import assert from "node:assert/strict";
import { resolve } from "node:path";
import { pathToFileURL } from "node:url";
const src = (file) => pathToFileURL(resolve(process.env.REA_CHECKOUT ?? ".", "src/domain/javascript", file)).href;
const { analyzeJavaScriptSemantics } = await import(src("javascriptSemanticAnalysis.ts"));
const { parseJavaScriptSource } = await import(src("javascriptSourceParser.ts"));
for (const [source, count] of [
  ["class C { constructor(public x: number) {} }", 1],
  ["class C { constructor(public x = 1) {} }", 1],
  ["class C { constructor(x: number) {} }", 0],
  ["class C { constructor(x: number) { console.log(x); } }", 1],
  ["const seed = 1; class C { constructor(public x = seed) {} }", 1],
]) {
  assert.equal(parseJavaScriptSource(source, "fixture.ts").errors.length, 0);
  const ir = analyzeJavaScriptSemantics(source);
  const uses = ir.references.filter((r) => r.name === "x");
  assert.equal(uses.length, count);
  assert.ok(uses.every((r) => r.role === "read"));
  if (source.startsWith("const seed")) {
    assert.equal(ir.references.filter((r) => r.name === "seed" && r.role === "read").length, 1);
  }
  console.log(JSON.stringify({ source, references: ir.references.filter((r) => r.name === "x" || r.name === "seed").map((r) => r.name + ":" + r.role) }));
}

Run REA_CHECKOUT="$PWD" node_modules/.bin/tsx repro.mts from the checkout root. The assertions intentionally describe the observed counts, including the false reads. This script was executed once per tested source tree with exit 0. It parses inert fixture text and runs the semantic analyzer; it does not execute the fixture code.

Expected behavior

A parameter declaration should remain a definition rather than a source identifier use. The ordinary-parameter control and declaration exclusions in isDeclarationIdentifier establish this distinction. Actual reads in an initializer or constructor body must remain references. The implicit instance-field initialization of a TypeScript parameter property does not make its declaration token an authored lexical use.

Impact and scope

Valid TypeScript parameter properties gain a spurious source reference, affecting semantic reference counts and facts available to graph consumers. The executed checks cover semantic IR, not full CLI/MCP projection, retained Evidence, traces, or larger application graphs. No native provider is involved.

REA version

Current main source 8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd; latest published rea-agents 6.5.0 source 3b79af28e80785afce9f5998d8f03289402a1847. Both reproduce at the semantic-analysis source boundary.

Runtime and platform

Node v24.18.0; macOS arm64; lockfile dependencies and TSX.

Target and artifact context

Inert synthetic TypeScript constructor declarations; no external target.

Interface

Other

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Both source trees produce the following results, filtered to x and seed:

constructor(public x: number) {}       -> ["x:read"]
constructor(public x = 1) {}           -> ["x:read"]
constructor(x: number) {}              -> []
constructor(x: number) { console.log(x); } -> ["x:read"]
constructor(public x = seed) {}       -> ["x:read","seed:read"]

All five inputs parse with zero diagnostics. The ordinary declaration control has no x reference; the body-read and initializer-read controls retain actual reads.

Reference collection and classification collects lexical references and sends identifier roles through semanticIdentifierRole; its declaration-parent recognition at lines 696–717 omits the parameter-property wrapper. Parameter binding collection already unwraps that node to record the parameter definition.

Typed outcome or recovery expected

Remove the reference for the x declaration in the parameter-property fixtures. Keep the one actual x body read and one actual seed initializer read in the controls; the ordinary declaration must still produce no reference.

Analysis or suspected cause

Source-derived: semanticReferenceRole defaults to read after declaration recognition misses the parameter-property wrapper. This does not establish the appropriate implementation change.

Application analysis calls the same parsed semantic-analysis steps for complete or partial JavaScript/TypeScript parse results. This establishes source reachability, not an executed CLI/MCP result.

Additional context

No last-known-good revision was established. Related #3310 and its open PR #3638 concern noncomputed class member keys, not parameter declarations; the PR diff changes member-key filtering and does not cover this trigger. Closed PR #775 concerns visibility in default-parameter environments. Closed PR #537 concerns assignment-target roles. Closed PR #2062 concerns import-equals declarations. Their contracts and restoration checks differ from this report. Only valid identifier parameter properties are used here.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Valid TypeScript parameter declarations gain false source reads in semantic IR; the demonstrated consequence is bounded to affected syntax and reference consumers, with full CLI/MCP projection untested.
  • Workaround status: unverified
  • Workaround basis: No product option preserving the authored source while removing only these false references was verified; removing the modifier changes the input.
Originally created by @coygeek on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4371 ### 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 Source-boundary reproduction: the semantic reference collector emits an `x:read` reference at the parameter declaration in valid TypeScript `class C { constructor(public x: number) {} }`, whose constructor body is empty. `public x = 1` behaves the same way. An ordinary constructor parameter produces no declaration read. This report is source-only with respect to the public CLI/MCP workflows. ### Reproduction Use a source checkout at either revision in the version field, with the pinned Node toolchain and lockfile dependencies (`npm ci` for a fresh checkout). Save this complete script as `repro.mts` at the checkout root: ```ts import assert from "node:assert/strict"; import { resolve } from "node:path"; import { pathToFileURL } from "node:url"; const src = (file) => pathToFileURL(resolve(process.env.REA_CHECKOUT ?? ".", "src/domain/javascript", file)).href; const { analyzeJavaScriptSemantics } = await import(src("javascriptSemanticAnalysis.ts")); const { parseJavaScriptSource } = await import(src("javascriptSourceParser.ts")); for (const [source, count] of [ ["class C { constructor(public x: number) {} }", 1], ["class C { constructor(public x = 1) {} }", 1], ["class C { constructor(x: number) {} }", 0], ["class C { constructor(x: number) { console.log(x); } }", 1], ["const seed = 1; class C { constructor(public x = seed) {} }", 1], ]) { assert.equal(parseJavaScriptSource(source, "fixture.ts").errors.length, 0); const ir = analyzeJavaScriptSemantics(source); const uses = ir.references.filter((r) => r.name === "x"); assert.equal(uses.length, count); assert.ok(uses.every((r) => r.role === "read")); if (source.startsWith("const seed")) { assert.equal(ir.references.filter((r) => r.name === "seed" && r.role === "read").length, 1); } console.log(JSON.stringify({ source, references: ir.references.filter((r) => r.name === "x" || r.name === "seed").map((r) => r.name + ":" + r.role) })); } ``` Run `REA_CHECKOUT="$PWD" node_modules/.bin/tsx repro.mts` from the checkout root. The assertions intentionally describe the observed counts, including the false reads. This script was executed once per tested source tree with exit 0. It parses inert fixture text and runs the semantic analyzer; it does not execute the fixture code. ### Expected behavior A parameter declaration should remain a definition rather than a source identifier use. The ordinary-parameter control and declaration exclusions in `isDeclarationIdentifier` establish this distinction. Actual reads in an initializer or constructor body must remain references. The implicit instance-field initialization of a TypeScript parameter property does not make its declaration token an authored lexical use. ### Impact and scope Valid TypeScript parameter properties gain a spurious source reference, affecting semantic reference counts and facts available to graph consumers. The executed checks cover semantic IR, not full CLI/MCP projection, retained Evidence, traces, or larger application graphs. No native provider is involved. ### REA version Current main source `8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd`; latest published rea-agents 6.5.0 source `3b79af28e80785afce9f5998d8f03289402a1847`. Both reproduce at the semantic-analysis source boundary. ### Runtime and platform Node v24.18.0; macOS arm64; lockfile dependencies and TSX. ### Target and artifact context Inert synthetic TypeScript constructor declarations; no external target. ### Interface Other ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Both source trees produce the following results, filtered to `x` and `seed`: ```text constructor(public x: number) {} -> ["x:read"] constructor(public x = 1) {} -> ["x:read"] constructor(x: number) {} -> [] constructor(x: number) { console.log(x); } -> ["x:read"] constructor(public x = seed) {} -> ["x:read","seed:read"] ``` All five inputs parse with zero diagnostics. The ordinary declaration control has no `x` reference; the body-read and initializer-read controls retain actual reads. [Reference collection and classification](https://github.com/morluto/rea/blob/8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd/src/domain/javascript/javascriptSemanticProjection.ts#L176-L220) collects lexical references and sends identifier roles through `semanticIdentifierRole`; its declaration-parent recognition at lines 696–717 omits the parameter-property wrapper. [Parameter binding collection](https://github.com/morluto/rea/blob/8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd/src/domain/javascript/javascriptSemanticAnalysis.ts#L1521-L1560) already unwraps that node to record the parameter definition. ### Typed outcome or recovery expected Remove the reference for the `x` declaration in the parameter-property fixtures. Keep the one actual `x` body read and one actual `seed` initializer read in the controls; the ordinary declaration must still produce no reference. ### Analysis or suspected cause Source-derived: `semanticReferenceRole` defaults to `read` after declaration recognition misses the parameter-property wrapper. This does not establish the appropriate implementation change. [Application analysis](https://github.com/morluto/rea/blob/8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd/src/application/javascript/JavaScriptArtifactAnalysis.ts#L413-L430) calls the same parsed semantic-analysis steps for complete or partial JavaScript/TypeScript parse results. This establishes source reachability, not an executed CLI/MCP result. ### Additional context No last-known-good revision was established. Related [#3310](https://github.com/morluto/rea/issues/3310) and its open [PR #3638](https://github.com/morluto/rea/pull/3638) concern noncomputed class member keys, not parameter declarations; the PR diff changes member-key filtering and does not cover this trigger. Closed [PR #775](https://github.com/morluto/rea/pull/775) concerns visibility in default-parameter environments. Closed [PR #537](https://github.com/morluto/rea/pull/537) concerns assignment-target roles. Closed [PR #2062](https://github.com/morluto/rea/pull/2062) concerns import-equals declarations. Their contracts and restoration checks differ from this report. Only valid identifier parameter properties are used here. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Valid TypeScript parameter declarations gain false source reads in semantic IR; the demonstrated consequence is bounded to affected syntax and reference consumers, with full CLI/MCP projection untested. - Workaround status: unverified - Workaround basis: No product option preserving the authored source while removing only these false references was verified; removing the modifier changes the input.
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#1728
No description provided.