Commit Graph
27 Commits
Author SHA1 Message Date
rajatnagda45 e444d5ac37 fix(sql): link a trigger to the function it executes
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)
2026-10-05 19:40:03 +01:00
Yyunozor 4babf7b173 fix(rust): keep a forward reference unresolved when its type id collides with another item (#3782)
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)
2026-10-02 17:10:07 +01:00
Yyunozor 5c8df1566b fix(rust): resolve a forward-referenced type to its local declaration (#3782)
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)
2026-10-02 17:10:07 +01:00
rajatnagda45 b8ee397721 fix(rust): emit a node per enum variant with a case_of edge
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)
2026-09-30 20:10:34 +01:00
berce 452e7b7da2 fix(sql): walk block nodes so DDL before a DO block is extracted
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)
2026-09-29 13:49:39 +01:00
rajatnagda45 d7e463e476 fix(sql): extract triggers and link them to their table
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)
2026-09-28 09:13:29 +01:00
L4XB ab80fa7413 fix(rust): extract module-level static and const declarations
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)
2026-09-10 22:44:38 +01:00
L4XB d2abe4ed0c fix(sql): emit index nodes for CREATE [UNIQUE] INDEX statements
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)
2026-09-10 14:51:54 +01:00
safishamsi 6e420a0840 test(rust): cover trait method declarations and decl/impl id distinctness (#3366)
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.
2026-09-06 16:31:40 +01:00
egarcia74 8b3f1911b2 fix(sql): recover T-SQL bracket-named, CREATE OR ALTER, and PROC routines (#3164)
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.
2026-08-29 19:32:44 +01:00
akshitj11 6a06b652cc fix(sql): extract CREATE TABLE inside BEGIN/COMMIT blocks (#2953)
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.
2026-08-24 14:06:55 +01:00
ousamabenyounes 037970159d fix(extract): stop a built-in base class from inheriting across languages (#2812)
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.
2026-08-21 18:26:00 +01:00
safishamsiandClaude Opus 4.8 10ad921b42 fix: JS/Kotlin/Swift/SQL extractor correctness batch; bump to 0.9.38 (#2568, #2565, #2561, #2577, #2575)
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>
2026-08-09 23:19:08 +01:00
safishamsiandClaude Opus 4.8 c8163d8338 fix(sql): sourceless stubs + identifier normalization for cross-file table references (#2324)
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>
2026-07-30 18:38:58 +01:00
Souptik Chakraborty ffa2a2471a fix: recover every declared SQL routine from unparseable PL/pgSQL (#2180)
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).
2026-07-26 11:08:10 +01:00
safishamsiandClaude Opus 4.8 49df466385 fix(sql): recover CREATE FUNCTION/PROCEDURE from tree-sitter ERROR nodes (#1910)
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>
2026-07-15 10:58:57 +01:00
Synvoya 7eb847bcf7 fix(rust): emit field type references for tuple structs
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.
2026-07-01 13:45:53 +01:00
SynvoyaandClaude Opus 4.8 674184d462 fix(rust): emit references edges for enum variant 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>
2026-07-01 11:58:39 +01:00
TheFedaikin 32aa053e6c feat: semantic type-reference edges for Swift, Kotlin, PHP, Rust, and Go (#1015) 2026-05-28 14:46:03 +01:00
Danil Tarasov e44e6e986c feat: add v8 affected and import-resolution support 2026-05-22 13:24:54 +01:00
Safi f7160c81c5 fix Rust cross-crate spurious INFERRED edges: skip scoped_identifier and trait-method blocklist from raw_calls (#908) 2026-05-17 13:12:49 +01:00
zuyeyang 5d0392524a fix(extract): add ALTER TABLE FK extraction + schema-qualified name support for SQL (#779)
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
2026-05-08 20:31:25 +01:00
SafiandClaude Sonnet 4.6 28047b502f merge PR #573: cross-language edge context filters in MCP query tool
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 16:49:22 +01:00
SafiandClaude Sonnet 4.6 7d604e8141 v6: SQL AST extractor + xlsx structural extraction utility (fixes #349)
- 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>
2026-05-01 10:15:06 +01:00
Danil Tarasov 3ff7188fbf feat: add cross-language edge contexts and context-aware queries 2026-04-24 02:59:04 +03:00
SafiandClaude Sonnet 4.6 ca842472e5 Fix AST call edges confidence: INFERRED/0.8 -> EXTRACTED/1.0 (#127)
Tree-sitter resolves call targets directly from source — marking them
INFERRED was incorrect. Cross-file class-level uses edges remain INFERRED.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 09:22:12 +01:00
Safi e7a03a0539 feat: cache, multi-language extraction, MCP, memory feedback
call-graph INFERRED edges, multi-language semantic extraction, SHA256 cache,
MCP stdio server with shortest_path, Q&A memory feedback loop
2026-04-04 18:56:38 +01:00