A named class expression, like register(class Widget extends Base {})
or module.exports = class Router {}, was not indexed as a class, as
issue #2499 found. Its methods became free functions, its fields and
supertypes were dropped, and calls in it had no class to start from,
so its this-calls, super calls and own static calls went unresolved or
to a by-name guess, and calls elsewhere guessed into its methods.
Both engines now index a named class expression as a class, with its
methods, properties, extends and implements. An anonymous one is still
passed over. Its head (class Widget extends Base) is its signature,
which marks it as an expression, and it is never exported by itself.
JavaScript binds the name only inside the expression, and the resolver
now scopes it the same way. A reference written outside every class
expression resolves in a view of the graph that hides class
expressions and their members, so new Widget() elsewhere links
nothing, and a same-named class, function or binding keeps its links.
A name only class expressions carry is unknown there. A reference
inside one sees the whole graph, unless it names another class
expression. A class a CommonJS module exports, module.exports = class
Router {} or exports.Store = class Store {}, is the module's value,
which require hands to every importer, so it is not scoped: it reads
as a declaration does, and a require of it reaches its static methods
and its instances' methods. The reader of CommonJS named exports takes
exports.X = class Y {} too. Super calls in a class expression now
resolve through the super-call matcher.
Validation: the new test fails 11 of 13 on main d5fc1fbb and passes 13
of 13 on the fix, and a mutation of each of 7 parts (the view, the
inside check, the name rule, the expression marker, the class node,
the CommonJS export rule, the named-export reader) fails a test.
A/B against main d5fc1fbb on 22 repos (20 TS/JS, flask and gin): 193
classes are added in 10 repos with their members, and 801 edge sites
change in 7 repos, each bucketed and sampled by hand. 563 are jest's
calls to its global test(), 0.4 guesses that move from one wrong
namesake to another: the old one, a class expression's test() in an
e2e fixture, is now a method. Of the rest, 85 are new extends,
implements and type references of class expressions; 74 calls, news
and references move to the member they are written in; 65 calls and
news gain or change a target, inside class expressions (this-calls,
own static calls) and through require (static calls, typed instance
calls, new); and 14 guesses are removed, among them 3 super calls
whose parent the index can't name. 12 repos' dumps, the controls among
them, are byte-identical. Kernel parity holds on 22 repos. Three sync
scenarios match a fresh index. Index time, median of 5 interleaved
runs: lit +0.8%, webpack +2.7%, jest +0.6%; insomnia +12.8%, though it
indexes byte-identically, holds no class expression, and its resolver
time is the same on both arms, so that gap is other jobs' load.
Fixes#2499
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The override-dispatch pass (interface-impl) linked a base class's
method to every subclass method of the same name, so a static method
counted as overriding an instance one, or the reverse, though a call to
one never runs the other (issue #2761). TypeScript nodes recorded
static already, so there the pass alone was wrong. JavaScript nodes
never did: the extractor had no isStatic hook and the kernel answered
nothing for the JS variants, so a JS class's static s() and s() were
both stored as instance methods.
JavaScript members now record their own static keyword with the hook
TypeScript uses (hasStaticModifier, which also keeps a method named
static() an instance one), and the kernel gives the JS variants the
same answer, so both engines agree. The pass skips a JS/TS base and
override whose static flags differ; a static method still overrides a
static one, and an instance method an instance one.
Validation: the new test fails 5 of 10 on main 08478eea (JS and JSX
static flags in both engines, and the override pairs) and passes 10 of
10 on the fix. A/B against main on 20 repos: webpack's lib loses 15
edges, every one a cross-kind pair, such as Module's instance
deserialize(context) to the static deserialize(context) factories of
NormalModule and six other modules, or Module's static
getSourceBasicTypes(module) to RuntimeModule's instance
getSourceBasicTypes(); the same-kind pairs stay and no edge is added.
No other repo's edges change. Nodes change only in is_static, 0 to 1,
on 315 JavaScript static members, none in TypeScript or another
language; the flask and gin control dumps are byte-identical. Kernel
parity over the 20 TS/JS repos: 16,612 of 16,873 files byte-parity,
none with diffs, the rest deferred to wasm as before. Index time,
median of 3 interleaved runs on vite, nuxt, webdriverio, insomnia and
webpack: -1.1% total, within noise.
Fixes#2761
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tree-sitter kinds generator_function_declaration / generator_function were
missing from both the wasm and native kernel function-type lists, so
function* / async function* (and const g = function* () {}) produced no
nodes. Add the kinds on both paths and cover TS+JS declaration/expression
forms in extraction tests.
Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
Every TS `public_field_definition` / JS `field_definition` extracted as a
method-kind node, so a plain field (`public fonts: Fonts;`) was reported
as callable: class shape was misrepresented, kind-based filtering was
defeated, and bare-name call resolution landed on data fields — typeorm's
boolean `ColumnMetadata::isArray` field was soaking up Array.isArray(...)
call edges (685 such wrong edges on typeorm alone).
Classification now follows the VALUE (classifyMethodNode hook, mirroring
resolveBody's callable detection): arrow-function / function-expression
fields and HOF-wrapped ones (`onScroll = throttle(() => {…})`) stay
methods with their bodies walked; everything else becomes a property that
keeps its type-annotation references edge, visibility, static-ness, and
decorators. Field initializers are now walked too (`history =
createHistory()` attributes the call to the property — previously
invisible), and JS class fields — whose name lives in the grammar's
`property` field, so they never extracted a symbol at all — now appear in
the graph (resolveName on the JS extractor).
With fields correctly kinded, `this.X` callback registration is re-enabled
for TS/JS (removed in #807 because field pseudo-methods made it mostly
wrong): `this.<member>` candidates resolve CLASS-SCOPED
(resolveThisMemberFnRef) — the target must be a function/method sharing
the from-symbol's qualified-name class prefix, same file, no fallback —
so `addEventListener("online", this.onOfflineStatusToggle)` and API-object
wiring (`{ mutateElement: this.mutateElement }`) produce registration
edges to the enclosing class's own method, while `this.fonts` (a
property) and inherited/unknown members yield no edge.
A/B (baseline = #807 main): excalidraw / typeorm / express — node counts
identical on all three; kinds shift method→property only (typeorm: exactly
7,406 swapped; excalidraw also corrects 5 anonymous-class mock fields that
were function-kind); every one of the 736 dropped call edges targeted a
node that is now a property (calls into data fields — verified 100%);
gains are retargets to real callables, initializer-call attributions, and
+74/+7 class-scoped this.X registration edges (sampled: addEventListener/
removeEventListener wiring, imperative-API method maps). Full suite green
(1386).
EXTRACTION_VERSION 19 → 20 (re-index to benefit).
Closes#808
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses arrow function class fields like `field = () => { ... }` where the function body is nested inside field_definition nodes. Adds resolveBody method to traverse field_definition → arrow_function/function_expression → body and handles HOF wrapper patterns like `field = throttle(() => { ... })` by searching call_expression arguments. Enables proper function body extraction for class field functions in both JavaScript and TypeScript.
Splits the monolithic tree-sitter.ts (3,358 lines) into modular files:
- 14 language config files under src/extraction/languages/
- 3 standalone extractors (Liquid, Svelte, DFM)
- Shared helpers and types modules to avoid circular imports
Also fixes a bug where Java's extractImport hook incorrectly set
handledRefs: true, preventing unresolved reference creation and
degrading codegraph_explore results for Java codebases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>