A trigger exists to run a routine, but the create_trigger branch only linked
the trigger to its ON table and ignored the EXECUTE FUNCTION/PROCEDURE clause
entirely. The routine a trigger fires — its whole reason to exist — was never
represented in the graph.
Register function/procedure node ids in a routine_nids map (mirroring
table_nids) and, in create_trigger, read the object_reference after EXECUTE and
emit an `executes` edge to that routine. Resolution is name-based against
routines defined in the same file (a trigger's function is declared before it);
when the routine is not in this file the edge is skipped rather than guessing a
cross-file target or minting a bare-name stub that could never rewire onto a
`name()` function node.
Add regression tests for the resolved case and the fail-closed case.
(cherry picked from commit ea39929459)
Ids are casefolded, so `fn handle`, `const HANDLE`, `struct HANDLE` or an
`impl` for `HANDLE` share the id of `struct Handle`, and walk() keeps
whichever item comes first. The pre-scan registered the type id
regardless, so a reference placed above the declarations was bound to
that shared id and could end on the function or on the other item, where
v8's extractor left a sourceless stub. For the same reason a `HANDLE`
reference (a `type HANDLE` alias, or an imported name) placed above a
local `struct Handle` was bound to the struct.
Only pre-register a type when every file-level item behind its id is
that same type (`#[cfg]` twins count once), and match it by its exact
name. An `impl` block counts as the type it names, since walk() gives it
the node of its type text; methods keep impl-qualified ids and are not
counted. Otherwise the forward reference keeps its stub. A reference
placed after an item that already owns the id still resolves through
seen_ids, unchanged from v8.
Adds regression cases for a type colliding with a function (also in an
`extern` block), a const, a static, another type, an `impl` for another
type and a type alias, and controls where the id is not shared: another
function name, `#[cfg]` twins, an `impl` for the type itself, a method
named like the type, and a generic type only an `impl` names.
Assisted-by: assayer harness on Claude Code (https://github.com/Yyunozor/assayer-memory-mcp)
(cherry picked from commit 93fa872e2c)
ensure_named_node only resolved names already in seen_ids, so a struct,
enum or trait used above its declaration in the same file got a
sourceless stub:
fn before(s: &Sink) {}
struct Sink { n: u32 }
The corpus-level rewire folds that stub back onto the real struct only
when the name is unique across the corpus. Once another file declares a
`Sink` as well, the reference stays on the stub, and `affected` or
`explain` on the local struct miss that user.
Pre-scan the struct/enum/trait names the file declares, as the Verilog,
SQL and Bash extractors already pre-scan their definitions, and let
ensure_named_node resolve against them, so a use above a declaration
resolves exactly as a use below it already does. Only these three items
are pre-registered: walk() never makes a node of a `type` alias or
`union`, so registering one would leave its references to be dropped by
the final dangling-edge filter. The scan never descends further than
walk(), so every registered id ends up as a node.
Adds regression coverage for a struct, enum and trait used above their
declarations, a type declared in an inline module, the two-file
ambiguous case (cold and warm cache), and the items that must not
resolve a forward reference (type alias, union, function body, const
and static initializers).
Assisted-by: assayer harness on Claude Code (https://github.com/Yyunozor/assayer-memory-mcp)
(cherry picked from commit d73c4adf59)
The enum_item handler walked each variant only to collect its payload type
references (`Click(Logger)` -> Logger), but never emitted a node for the
variant itself. So an `enum Shape { Circle, Square, Empty }` produced a node
for Shape with no members at all — the enum was a leaf and its variants were
absent from the graph.
Emit a node plus a `case_of` edge per enum_variant, keyed on the variant's
`identifier` child, alongside the existing payload-type walk. This brings
Rust to parity with every other language that has enums: Java's enum_constant
(#1719), Kotlin's enum_entry (#1738), Swift, and Scala.
(cherry picked from commit 13903a8ac3)
With a bare `BEGIN;` transaction, a later `DO $$ ... END $$;` makes
tree-sitter-sql read everything from BEGIN up to the DO body's END as a
single block node. The top-level dispatch had no branch for `block`, so
every CREATE TABLE inside it was silently dropped. Walk block nodes the
same way transaction nodes are walked.
Fixes#3886
(cherry picked from commit 8f47001d5b)
Two defects left CREATE TRIGGER under-extracted:
- The create_trigger AST branch read the subject table off keyword_for, but the
table follows ON ('... AFTER INSERT ON users'); FOR EACH ROW carries no table.
So even a cleanly-parsed trigger got a node with no link to the table it fires
on. Read the table after keyword_on instead.
- A trigger with a procedural body ('FOR EACH ROW BEGIN ... END') has no grammar
production, so the statement lands in an ERROR node and only the whole-file
routine-recovery scan can save it — but that scan's pattern matched only
FUNCTION/PROCEDURE, so the trigger and its table were dropped entirely. Add a
dedicated trigger-recovery pattern that mints the trigger node (label without
(), since a trigger is not callable) and a triggers edge to the table named by
the first ON after the trigger name. A trigger already recovered via the AST
path is skipped to avoid a duplicate edge.
Added regression tests for both the clean EXECUTE FUNCTION form and the
procedural BEGIN...END form.
(cherry picked from commit a28de74a52)
The Rust extractor gated every non-function item on struct_item,
enum_item and trait_item, so tree-sitter-rust's static_item and
const_item never produced a node: a constant existed in the graph only
through the files that referenced it, never from the Rust that defines
it (0 of 783 names in a jdx/mise snapshot).
Add a branch for both node types: a module-level declaration gets a
node with a file-level contains edge, an associated const inside an
impl is attributed to the impl like a method, and the declared type is
referenced the way a struct field type is.
Fixes#3471
(cherry picked from commit 70ff464064)
Adds an AST branch for create_index in the SQL extractor, emitting an index
node with an 'indexes' edge to its table (cross-file stub when the table is
defined elsewhere), mirroring the existing trigger handling. (#3470)
Follow-up to #3367: guards that signature-only trait methods are captured and
that a trait-declared method stays a distinct node from its impl definition.
T-SQL routine idioms (CREATE OR ALTER, the PROC shorthand, bracket-delimited names like
[dbo].[Get Widgets]) parse to tree-sitter ERROR nodes, so the routine names were lost. Hoist
a single routine-recovery pattern covering all three forms and run it over a
comment/string-masked copy of the file so commented-out or dynamic-SQL DDL cannot fabricate
nodes; labels keep the raw bracketed text and plain CREATE PROCEDURE/FUNCTION is unchanged.
DDL wrapped in a transaction parses under a `transaction` node the statement walker never
descended into, so a CREATE TABLE inside BEGIN/COMMIT was never extracted while a top-level
one was. Recurse into transaction blocks; nested statements get the same handling, no
double-emit.
A class extending a built-in base (PHP `extends \Exception`) emits a sourceless supertype
stub, and the corpus rewire bound it to the unique same-labelled real definition anywhere
in the corpus — so in a PHP+TS monorepo a PHP class inherited from the TypeScript
Exception, a phantom cross-language edge / god node. Gate the supertype-target rewire with
a per-language curated builtin-base set: refuse the rewire only when the referring file's
language names the base as a builtin and the resolved target is in a different language
family. Same-language inheritance and user classes named like a builtin still link.
These changes are interleaved across engine.py/extract.py by function, so
they land as one batch.
#2568 (thanks @imagineers-tyler): the 0.9.37 #2552 callback-body fix
unioned sibling closures' local names under the shared declaration, so a
local in one callback suppressed a real indirect_call in a sibling. Locals
are now scoped per body (keyed by body id, via walk_calls' extra_locals).
Restore-only, never fabricates; #2552 capture preserved.
#2565 (thanks @kskchaitanya1993): Kotlin property initializers — class,
top-level, companion, and `by lazy {}` — now seed call extraction, so
`val repo = createRepo()` produces a calls edge; literal initializers
produce none; FQ calls compose with the #2550 resolver.
#2561 (thanks @fakewaffle): Swift receiver typing now handles
`@Environment(Store.self)` (whitelisted; @Query/keypath/dotted skipped to
avoid a wrong edge) and in-corpus factory bindings via a marked concrete
return type; opaque/array/out-of-corpus returns stay unresolved.
#2577 (thanks @wilyan09007, PR #2579): the SQL extractor no longer emits a
reads_from edge to a CTE name. WITH names are scoped per query (a subquery
CTE no longer suppresses an outer real table of the same name), so a CTE
no longer mints a bare stub that binds to an unrelated same-named symbol.
#2575 (thanks @phudayyy, PR #2574): a dynamic `import('…')` inside a nested
function or at module scope now produces an edge, dynamic_import is
included in affected, and calls inside nested named functions are
collected; a dynamic import already captured as a deferred imports_from is
not double-counted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SQL references to a table defined in another file (e.g. prisma m2 -> m1) were
minted as node-less absolute-stem ids under the referencing file, leaking the
scan path and losing the FK. They now mint a sourceless bare-name stub (Go
#1402 pattern) that _rewire_unique_stub_nodes collapses onto the real table.
Also normalize identifiers (strip quotes, lowercase, bare-name alias) on both
table_nids writes and reference lookups, so a quoted definition ("public".
"users") resolves an unquoted reference (public.users) in-file instead of
stubbing — which also fixes pg_introspect's reconstructed-DDL views/FKs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tree-sitter-sql cannot parse PL/pgSQL-only statements, and #1910's ERROR-node
name recovery only covered one of the shapes that produces. Two others dropped
the routine silently -- no node, no warning, exit code 0:
1. The statement is shredded into loose top-level tokens (keyword_create,
keyword_function, object_reference, ..., keyword_begin) and the ERROR node
holds only the offending body line, e.g. `PERFORM other_fn();` or `x := 1;`.
No ERROR node contains any CREATE text, so scanning ERROR nodes finds
nothing. This is what still dropped PERFORM and := after #1910.
2. The routine name is a quoted identifier -- CREATE OR REPLACE FUNCTION
"public"."fn"(...) -- which the recovery's bare [\w$.]+ pattern cannot match,
because it stops dead at the leading quote. Generated schema dumps quote
every identifier, so whole files recovered nothing.
Verified on the reported repro: the same body that drops under a quoted name is
recovered fine under an unquoted one, which is why the drop looked like it
depended only on the body statement.
Fix mirrors the global REFERENCES fallback already in this extractor: after the
tree walk, scan the raw source for every CREATE [OR REPLACE] FUNCTION/PROCEDURE
and emit any routine the walk missed. Name parts accept bare or double-quoted
identifiers. _add_node dedupes by node id, so routines already recovered from
the tree are not emitted twice.
Adds tests/fixtures/sample_plpgsql_quoted.sql -- generated-style quoted DDL whose
bodies use RAISE, RAISE NOTICE, PERFORM, :=, IF..THEN and bare NULL; -- plus
tests that every routine is recovered and that the file stays clean (tables
before and after still extract, no duplicate ids or labels, no empty/ERROR
labels, and every routine keeps its contains edge from the file node).
tree-sitter-sql parses PL/pgSQL CREATE FUNCTION statements (OUT/INOUT
params, tagged dollar quotes, PERFORM/:= body statements) as ERROR
nodes, and the dispatch loop had no branch for them, so the functions
were silently dropped from the graph.
Handle ERROR nodes inside walk() (they can nest inside a merged
create_function during multi-statement error recovery) and dispatch
top-level ERROR statements to it. The branch regex-scans the raw node
text for every CREATE [OR REPLACE] FUNCTION/PROCEDURE, mirroring the
existing fb_proc_or_trigger and has_error fallbacks, with a name class
that keeps schema-qualified names (exposed.important_function) whole.
The PL/pgSQL body is not scanned for FROM/JOIN references to avoid
junk reads_from targets, and node ids match the clean create_function
branch so seen_ids dedups consistently.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
extract_rust() only traversed field_declaration_list (named-struct
bodies), so tuple structs -- whose positional fields nest under
ordered_field_declaration_list -- had every field type reference
silently dropped from the graph.
This is the same node shape the enum handler already accounts for
(tuple variants nest their types under ordered_field_declaration_list);
the struct path was simply left behind. Add an additive branch that,
for each type node in a tuple struct's ordered_field_declaration_list,
collects type refs via _rust_collect_type_refs and emits references
edges with the appropriate field / generic_arg context. The
named-struct path is untouched.
For `struct Wrapper(Logger, Config);` with Logger/Config defined
in-file, no field edges were produced before; both are now emitted.
Adds test_rust_tuple_struct_field_references and a tuple struct to the
shared Rust fixture covering plain and generic positional field types.
Enum variant payload types were silently dropped — `struct_item` and
`trait_item` had type-reference handlers but `enum_item` had none, so the
variant field types were never traversed.
Add an `enum_item` branch that walks
`enum_variant_list -> enum_variant -> ordered_field_declaration_list`
(tuple variants, `Click(Logger)`) and `field_declaration_list` (struct
variants, `Resize { size: Dim }`), emitting a `references` edge from the enum
to each field type. Reuses the same type collection as the struct path. Adds
an enum to the fixture and a regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The SQL parser (`extract_sql`) previously only extracted foreign key
relationships defined inline within CREATE TABLE column definitions.
FK constraints added via ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY
... REFERENCES were silently ignored.
Additionally, `_obj_name()` only read the first identifier child of
object_reference nodes, so schema-qualified names like `Sales.Customer`
were truncated to just `Sales`.
Changes:
- Add `alter_table` handler to `walk()` that extracts FK edges from
ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY ... REFERENCES
- Fix `_obj_name()` to read the full object_reference text, preserving
schema-qualified names (e.g. `Sales.Customer`)
- Fix inline FK resolution in create_table and _walk_from_refs to use
full object_reference text instead of first identifier only
- extract_sql(): deterministic tree-sitter extraction of tables, views,
functions, foreign key references, and FROM/JOIN reads_from edges
- .sql added to CODE_EXTENSIONS and dispatch table
- tree-sitter-sql added as optional dep under [sql] extra
- xlsx_extract_structure(): extracts sheet/table/column nodes from .xlsx
(utility — pipeline wiring in follow-up)
- 6 new SQL tests, 447 total passing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>