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)
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.
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>
- bash: mark the bare-name `source lib.sh` sibling binding INFERRED (it
resolves via $PATH at runtime, so it's a heuristic, not EXTRACTED) (#2171)
- bash: un-join a comment accidentally merged onto the _BASH_SCRIPT_RUNNERS
line during #2172
- sql: gate the global routine-recovery raw-text scan on root.has_error so a
cleanly-parsing file can't fabricate routines from commented-out DDL,
EXECUTE-string bodies, or MySQL 'CREATE FUNCTION IF NOT EXISTS' (#2180)
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>
Continues the graphify/extract.py -> graphify/extractors/ split (MIGRATION.md,
upstream #1212), which already moved blade/zig/elixir/razor. Moves the
independent bespoke extractors whose closures are fully private (no shared
_extract_generic core, no shared mutable caches), verbatim, following the
documented invariants:
dart, rust, go, powershell (+psd1 manifest), fortran, sql,
dm (dm/dmm/dmi/dmf), bash, apex, terraform, sln,
pascal_forms (delphi .dfm + lazarus .lfm), json_config
Each language's private helper funcs and constants move with it; only the
`extract_<lang>` entry points (plus fortran's _cpp_preprocess, which has a
direct unit test) are re-exported from extract.py's facade block, so every
existing importer (__main__.py, watch.py, tests) is unchanged and object
identity is preserved. Registry (extractors/__init__.py) grows 4 -> 22 langs.
Verified: AST closure-privacy analysis (no symbol referenced from outside its
moved set except via the facade); byte-identity of every moved span;
extract._DISPATCH still resolves every extension; ruff clean; skillgen --check
OK. extract.py drops 17,054 -> 13,121 LOC. Full suite unchanged: 3036 passed,
29 skipped (excluding env-only openai tests).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>