A function name used as a VALUE — passed as an argument
(signal(SIGINT, handler), qsort(..., compare)), assigned to a function
pointer or field (ops->recv_cb = my_cb, OnClick := Handler), or placed in
a struct initializer / handler table ({ .recv_cb = my_cb },
{ "get", getCommand }) — produced no edge in ANY of the 19 tree-sitter
languages, so registered callbacks looked dead and their registration
sites were invisible to callers/impact.
This adds table-driven function-as-value capture across all 19 languages
(plus the wrapper forms: &fn, &Cls::method, Java Class::m, Kotlin ::f,
Swift #selector, ObjC @selector, Ruby method(:sym), Scala eta, Pascal
@Handler), gated at extraction (same-file definitions + imported
bindings; C-family file-scope initializers are constant-expression
contexts and skip the gate, which is how redis-style cross-file command
tables resolve), and resolved by a dedicated strategy: function/method
targets only, same-file first, unique-or-drop cross-file, no fuzzy
fallback ever. Edges persist as kind 'references' with metadata.fnRef,
so getCallers/getImpactRadius surface them with zero graph-layer
changes; MCP callers/callees label them "via callback registration".
Precision rules bought by real-repo false positives (full A/B record in
docs/design/function-ref-capture.md): C++ is &-explicit outside
file-scope tables (fmt's begin/out/size collisions; out-of-line member
defs are function-kind); TS/JS/Python bare ids resolve to functions only
(TS class fields extract as method-kind — pre-existing quirk); Swift
refuses same-file method overload-families; param-forward shapes
(this.x = x, value: value) and destructuring are skipped; minified
bundles (*.min.js) produce no candidates.
Validated on 17 public OSS repos (redis, excalidraw, gin, bytes, okhttp,
okio, Alamofire, flask, sinatra, Newtonsoft.Json, scopt, provider,
busted, Fusion, AFNetworking, PascalCoin, fmt): node counts identical,
zero calls edges lost or gained, references strictly additive
(+3,200 registration edges total), precision spot-checked by reading
sampled source lines (redis 30/30, flask 8/8). Deliberately NOT covered:
indirect-dispatch resolution (o->cb(x) → impl) — that needs data-flow
through struct fields, and a wrong edge is worse than none.
EXTRACTION_VERSION 18 → 19 (re-index to benefit).
Closes #756
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
25 lines
1.3 KiB
TypeScript
25 lines
1.3 KiB
TypeScript
/**
|
|
* Extraction version
|
|
*
|
|
* A monotonically-increasing integer that identifies the *shape and depth* of
|
|
* what the extractor writes into the graph. Unlike `CURRENT_SCHEMA_VERSION`
|
|
* (which tracks the SQLite table layout and is migrated in place), this tracks
|
|
* the EXTRACTED CONTENT — node kinds, edges, synthesizers, resolver coverage.
|
|
*
|
|
* When an index was built by an older engine whose `EXTRACTION_VERSION` is
|
|
* below the running engine's, the data on disk is structurally fine but
|
|
* *stale*: it's missing whatever a newer extractor would now produce. A schema
|
|
* migration can't backfill that — only a re-index can. So this is the signal
|
|
* `codegraph status` uses to recommend a re-index, and the reason `codegraph
|
|
* upgrade` reminds users to refresh their projects.
|
|
*
|
|
* BUMP THIS when a release changes extraction output enough that existing
|
|
* indexes should be rebuilt to benefit — e.g. a new language/framework
|
|
* extractor, a new dynamic-dispatch synthesizer, a new node/edge kind, or a
|
|
* resolver fix that materially changes which edges exist. Do NOT bump for
|
|
* pure bug fixes, CLI/UX changes, or schema-only migrations. Over-bumping
|
|
* turns the re-index hint into noise — keep it honest (see CLAUDE.md, "Honesty
|
|
* in the product is load-bearing").
|
|
*/
|
|
export const EXTRACTION_VERSION = 19;
|