You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Two common ways of declaring a method are never materialized as callables, so they are absent from the symbol table and every call to them is unresolvable — edges are gated to allSignatures (src/semantic_analysis/callGraph.ts:50-55), which is built from the symbol table.
Minimal repro, one fixture per idiom, analyzed at -a 1:
src/klass.js class Dao → method getById, save, constructor ✓ captured
src/klass_ts.ts class Dao2 → method getById, save, constructor ✓ captured
src/ctorfn.js fn Dao → this.getById / this.save ✗ missing
src/ctorfn_ts.ts fn Dao → this.getById / this.save ✗ missing
src/objlit.js module_var api ✗ missing
src/objlit_ts.ts module_var api2 ✗ missing
this.<name> = fn inside a constructor function — lands in the enclosing callable's local_variables, never in inner_callables. Confirmed on app/data/user-dao.js: the module reports 1 function (UserDAO) and 2 inner callables (comparePassword, validateUserDoc, both const x = () => {}), while the six this.getUserById = …-style methods sit in local_variables.
Object-literal methods — the object is emitted as a module variable; getById(){} and save: function(){} inside it are not callables.
This is not a JavaScript problem. Both idioms are missed identically in TypeScript. ES6 classes are captured correctly in .js.
Scope boundary
Fixes symbol-table materialization for the two idioms above. This issue does not:
change any schema shape — these become ordinary inner_callables entries in the existing v1 model;
attempt general escape analysis or aliasing — only the syntactic forms this.<name> = <function|arrow> within a function body, and method/function-valued properties of an object literal;
change the call-graph providers. Edges follow automatically once the targets exist in allSignatures.
Goals
this.<name> = function(){} / this.<name> = () => {} inside a function body become inner_callables of that function
Object-literal method shorthand { foo(){} } and function-valued properties { foo: function(){} } become callables homed on the owning variable
Signatures follow the existing scheme and stay stable (<module>.<owner>.<name>)
Fixtures for both idioms, in both.js and .ts, since the gap is language-neutral
Existing fixture output unchanged (no signature churn for code that already worked)
Caveats and known risks
Recall impact is large, so the diff is not cosmetic. On NodeGoat only 11% of call sites resolve, against 81% for idiomatic TypeScript (sample-app). Expect the emitted call graph to grow substantially once these targets exist, which will change edge counts in any test asserting totals.
Signature collisions. Two constructor functions in one module can each declare this.save; the owner segment must disambiguate. Existing signatures must not change — only new ones added.
this is not always the enclosing constructor. In an arrow function this is lexical; in a plain function it is dynamic. Restricting to the syntactic form inside a function body is deliberately an over-approximation, and will occasionally home a method on a function that is never used as a constructor. That is preferable to missing it, but should be stated in the code rather than discovered later.
Interaction with jelly. Jelly already synthesizes anonymous callables for some of these (88 on NodeGoat). Once tsc materializes them too, the union merge must not double-count — the (source, target) key should coalesce them, but this needs an explicit assertion.
Scope is the v1 (0.x) line, where the supported releases are. Forward-porting to main is a separate PR.
Definition of done
The six-case probe above yields callables for all six rows, not two.
On app/data/user-dao.js specifically, the seven this.* methods appear as callables with stable signatures, and getUserById — which has 6 unresolved call sites today — resolves.
Call-site resolution on NodeGoat rises from its current 30/262; the new figure is recorded in the PR rather than asserted as a threshold.
Fixtures exist for both idioms in .jsand.ts, each failing on the parent commit.
sample-app output is byte-identical, proving no signature churn for code that already worked.
Plan
codellm-devkit/.github→docs/design/roadmap.md— candidate 10 of the 2026-08-05 planning pass.Problem
Two common ways of declaring a method are never materialized as callables, so they are absent from the symbol table and every call to them is unresolvable — edges are gated to
allSignatures(src/semantic_analysis/callGraph.ts:50-55), which is built from the symbol table.Minimal repro, one fixture per idiom, analyzed at
-a 1:this.<name> = fninside a constructor function — lands in the enclosing callable'slocal_variables, never ininner_callables. Confirmed onapp/data/user-dao.js: the module reports 1 function (UserDAO) and 2 inner callables (comparePassword,validateUserDoc, bothconst x = () => {}), while the sixthis.getUserById = …-style methods sit inlocal_variables.variable;getById(){}andsave: function(){}inside it are not callables.This is not a JavaScript problem. Both idioms are missed identically in TypeScript. ES6 classes are captured correctly in
.js.Scope boundary
Fixes symbol-table materialization for the two idioms above. This issue does not:
inner_callablesentries in the existing v1 model;require/module.exportsmodule-level modeling, or HTML;this.<name> = <function|arrow>within a function body, and method/function-valued properties of an object literal;allSignatures.Goals
this.<name> = function(){}/this.<name> = () => {}inside a function body becomeinner_callablesof that function{ foo(){} }and function-valued properties{ foo: function(){} }become callables homed on the owning variable<module>.<owner>.<name>).jsand.ts, since the gap is language-neutralCaveats and known risks
sample-app). Expect the emitted call graph to grow substantially once these targets exist, which will change edge counts in any test asserting totals.this.save; the owner segment must disambiguate. Existing signatures must not change — only new ones added.thisis not always the enclosing constructor. In an arrow functionthisis lexical; in a plainfunctionit is dynamic. Restricting to the syntactic form inside a function body is deliberately an over-approximation, and will occasionally home a method on a function that is never used as a constructor. That is preferable to missing it, but should be stated in the code rather than discovered later.(source, target)key should coalesce them, but this needs an explicit assertion.mainis a separate PR.Definition of done
app/data/user-dao.jsspecifically, the seventhis.*methods appear as callables with stable signatures, andgetUserById— which has 6 unresolved call sites today — resolves..jsand.ts, each failing on the parent commit.sample-appoutput is byte-identical, proving no signature churn for code that already worked.