mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-25 20:12:11 +08:00
c46523079f6b00aae0fbbbaebecf519d3d768603
78
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bf6f1fa67e |
fix(dart): a typedef references the types it aliases (#2763)
In
class Report { final int score; const Report(this.score); }
typedef ReportCallback = void Function(Report r);
`codegraph callers Report` did not list ReportCallback. Both extractors
minted the typedef's node and nothing else. tree-sitter-dart gives
`type_alias` no fields, so extractTypeAlias found no `value` to read and
had no Dart branch, and the kernel's extract_type_alias mirrored that.
Impact on a model type missed every callback or collection type built
on it. The legacy form with a return type, `typedef Report
LegacyMaker(int n);`, was also named after that return type: with no
`name` field the name fell back to the first type name in the
declaration, so a `type_alias Report` (or `Widget`, `Future`, `bool`)
stood beside the real type and took references meant for it.
Both extractors now name a typedef by the last type name before its
`=`, its type parameters or its parameter list, and add a `references`
ref from the typedef, on each type's name, for every type its aliased
type names: a function type's return and parameter types, generic
arguments, record fields, a type parameter's bound, and the legacy
form's return and parameter types. Annotations are not part of it, an
import prefix names no type, and a type parameter (the typedef's own,
or one a generic function type or an old-style generic function-typed
parameter declares) is skipped unless written through a prefix. Kernel
and wasm output are identical; a new parity fixture,
TortureTypedefs.dart, pins every form in LF and CRLF.
Validation, kernel path, arms built from
|
||
|
|
51be28d15b |
fix(extraction): a function in an initializer is referenced once, by what the initializer declares (#2743)
PRs #693 and #1802 walk a declaration's initializer under the symbol it declares, so `const routes = { home }` references `home` from `routes`. The dispatcher's declaration-wide function-as-value scan then took the same values again for the enclosing file (or class). Flush-time dedup keys on the source, so both stayed: a second reference to the same function from the same spot. Each initializer walk now records what it walked, and the scan skips it. PR #2738 added that mechanism for component wrappers, and C#, Scala and Dart already had their own. The newly covered paths: - TS/JS module-level constants and variables, owned object literals included (#2300), and TS/JS class properties; - Python assignments, Go var/const specs, Rust const/static items; - Java fields, and Kotlin properties (walkInitializer takes the property's id, so their walk is unchanged). Mirrored in the kernel: tsjs, python, go, rustlang, java and kotlin. References no initializer walk takes are unchanged: a module-level call argument `register(fn)`, C-family tables, Rust's `static CB: fn() = handler` (the declaration's own value, which the scan still reads). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ae0fc631e0 |
fix(js,ts): a callee that names nothing records no reference, and a wrapped callee is the plain call (#2742)
A TS/JS call whose callee was no name and no member access (an IIFE, a curried `connect(mapState)(View)`, `handlers[type]()`, `(0, fn)()`, a tagged template on a call) stored the callee's whole source text as the reference's name. Nothing can answer such a name, so each one parked as a failed reference, an IIFE's entire body among them. extractCall now reads the callee past the wrappers that keep it the same callee (parentheses, `!`, `as` / `satisfies`, `await`, type arguments) and records nothing when that is not a name or a dotted chain; the calls inside are visited on their own. Mirrored in the kernel (tsjs/extractors.rs callee_name_of). The reference of a wrapped callee sits at its name, where a plain call's does, and the resolver's bare-call check reads `fn!(`, `(fn)(`, `(fn as F)(` and `(fn satisfies F)(` as it reads `fn(`: SigNoz's `(callback as FrameRequestCallback)(0)` and `(callback)(0)` name the arrow's own parameter, and from the `(` they bound a generated interface's `callback` property. tree-sitter-typescript parses a prefix operator over a generic call as it parses `await f<T>()` (#2586): `!settings.get<boolean>('X')` and `void apiRequest<T>()` are calls whose callee is `!settings.get` / `void apiRequest`. The callee peel that handled `await` now peels a prefix operator too (peelTsPrefixedCallee; the kernel's peel_await_callee), so the call is to `settings.get`, and `void apiRequest<T>().then()` encodes as `apiRequest().then`, not `voidapiRequest().then`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2c4a16d091 |
fix(react): a wrapper component renders the component it wraps, and owns the calls in its styles (#2738)
A component declared through a wrapper with no render function of its own
(`styled.div`…``, `styled(ActionButton).attrs(…)<Props>`…``, MUI's
`styled(Box)(({ theme }) => …)`, `memo(Button)`) was a bodyless component
node: nothing walked its initializer, so the calls in its styles went
nowhere, and nothing linked it to the component it wraps, so a render flow
stopped at it. On outline, explore printed no Flow for
"OverflowMenuButton NudeButton ActionButton performAction".
Extraction (TS and the native kernel) now walks the initializer with the
component on the stack: the template's interpolations, the .attrs() /
.withConfig() / options arguments, a style function and a memo comparator
are the component's code. The wrapper's own chain (styled, styled.div,
.attrs, memo) names the library and is not recorded; a typed tag's type
operands are not walked; and the wrapped component is no function-as-value
reference, from the component or from the file (the declaration-wide scan
skips the walked initializer). A component is also a value-ref reader, as
a function is.
The jsx-render synthesizer links a wrapper to the component it wraps
(`wrappedComponentName` in frameworks/react.ts, beside `wrappedName`),
bound as a tag of that name in the file would be, but only where the file
binds the name by a declaration or an import of a project module, so a
package's styled(Link), a Meteor /imports/… path and a destructured name
render nothing rather than a namesake. Files that declare such wrappers are
synthesis inputs, so a sync redraws the link.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
f66d064279 |
fix(cpp): a function is named by its declarator, never by its noexcept, throw or requires clause (#2727)
Both C++ extractors name a function by the first qualified identifier found breadth-first in its declarator, which is how an out-of-line `Foo::bar` is named. The walk skipped only the parameter list and the trailing return type, so a qualified name in the clauses after them became the name of a function whose own name is plain. nlohmann/json's constructor `basic_json(CompatibleType&& val) noexcept(noexcept( JSONSerializer<U>::to_json(…)))` was indexed as a method `JSONSerializer::to_json` that doesn't exist, with no node for the constructor; `throw(std::bad_alloc)` named a method `std::bad_alloc`, and a trailing `requires std::integral<T>` a function `std::integral<T>`. The walk now also skips `noexcept`, `throw_specifier` and `requires_clause`, in the TS extractor and the kernel alike; the parity fixture covers all three. Measured on main |
||
|
|
bd62d9c061 |
fix(rust): a turbofish call reaches the function the turbofish wraps (#2720)
A Rust call written with a turbofish (`s.get::<u32>()`, `helper::<u8>()`, `Pool::<u8>::new()`) kept its type arguments in the callee's name (`s.get::<u32>`), which no resolution strategy reads, so every such call was left unresolved. Both engines now name the function the turbofish wraps and drop a path's type arguments (`Pool::<u8>::new` is `Pool::new`), so the call resolves as it would without the turbofish: on a typed receiver, on `self` or a field of `self`, and through an associated-function chain. The kernel and wasm extractors emit the same bytes; the parity fixture covers nested type arguments and a `->` inside them. A/B against main with fresh indexes, sites matched by position: clap 197 calls linked, dioxus 251 linked and 1 removed, axum 69, tokio 143, serde 48, ripgrep 3; gin and express are byte-identical. About 680 of the new links are right: typed method calls (clap's `matches.get_one::<String>()` to `ArgMatches::get_one`, dioxus' `self.query.new_query::<T>()` to `QueryEngine::new_query`) and generic free functions (tokio's `channel::<T>()`, dioxus' `consume_context::<T>()`, serde's `assert::<T>()`). About 22 are weak guesses at confidence 0.5 or below, where an instance chain's method becomes a bare name, as it is for the same call without a turbofish (dioxus' `.collect::<Vec<_>>()` to `ServerFunction::collect`; main already makes 23 of those for `.collect()`). The one removed link was wrong on main: `serde_json::from_str::<T>(...).context(...)` is no longer guessed as `DiffState::context`. The 54 sites reported as retargeted are positions a chain's inner and outer calls share, where an edge was added beside the existing one. Full suite on the tested tree: 8,037 passed, 0 failed. Follow-up 1 of the Rust receiver series (PR 2719's open list). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
642c7bb3a5 |
fix(python): attribute calls follow the type the class body, a factory or a base gives the attribute (#1926)
Python calls through an attribute reach the method the code gives that attribute: `self.sink.stop()` follows `self.sink = Sink()`, a `sink: Sink` annotation, a typed `__init__` parameter, a factory's return type or what every `return` in it builds, and a base class's `__init__` in another file. Calls straight on a factory's result (`Client.from_env().send()`) resolve the same way, and a sync re-opens these answers when the factory or base they were read from changes. The native Python extractor now keeps a call's receiver text as the wasm one does, so `self.data.append(1)` is never a bare `append` that matches an unrelated project function. This includes all of PR 1704. Reworked from evidence-or-silence, which dropped about 3,000 correct Django links (`self.client.get()`, `self.engine.render_to_string()`). The reader now has three answers. A project class that has the method: that method. Evidence that rules project methods out: no edge, for a builtin, a class from outside the project built in a method or named by an annotation, an attribute only an outside base can give (Django's `TestCase` in an app), or a class without the method whose bases are all known. Nothing typed: the call is guessed by the name of its last link, read as written, as any receiver is, but only for an owner whose name holds the link's head noun (`self.session` for `SessionBase`, not `database.group_shopping_lists` for `GroupRepositoryAIProvider`) and never as the one method of a name; the guess records the files the reader looked at, so a sync re-opens it too. A class-body field an outside class builds (`author = models.ForeignKey(Author)`) may be a descriptor, so it is no evidence. A chain through an imported module (`os.path.exists()`) is the module's. In the shared scoring, a receiver that spells an owner's whole name (`client` for `Client`) outscores one that only shares its word (`RedisCacheClient`). A call through an imported value's attribute chain (`app.jinja_env.filters.keys()`) no longer links to the value's `jinja_env`. A/B against main with fresh indexes, every changed edge triaged, sites matched by position since the extractor now names these calls by their whole chain. Django: 3,317 calls linked that were not (`self.client.force_login()` to `ClientMixin`, `User.objects.create_user()` to `UserManager`, `self.stdout.write()` to `OutputWrapper`), 127 moved, nearly all to a more precise target (`self.site.register()` to `AdminSite.register` rather than the `register` decorator), and 134 removed: main's bare-name links on builtin values (`value.lower()` to the template filter, `.encode()` to a test password hasher) and calls on expressions such as `(q1 | q2).order_by()`, which now go unresolved as they do in the wasm engine. flask: 32 linked, 4 moved to `JSONProvider`. fastapi: 58 linked. mealie: 740 linked. Guesses still wrong, as main makes them for a plain receiver of the same name: fastapi's `request.headers.get()` to a telemetry `_HeadersGetter`, and about 20 of mealie's repository attributes to a controller. Go, C#, Ruby, Swift, Kotlin, Java and TS repositories are byte-identical, except excalidraw's 5 `props.onChange()` calls, which move between two unrelated `Props` interfaces. Indexing CPU: Django +7%, mealie +5%, fastapi flat; wall time unchanged within noise. Full suite on the merged tree: 7,999 passed, 0 failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b5553e238a |
fix(rust): an impl's associated type or const no longer takes a bare name or a module path (#2707)
A `type` or `const` written inside an `impl` belongs to the type: Rust
reaches it only through a path like `Self::Err`, `<T as FromStr>::Err` or
`T::Err`, never by its name alone, and no module path or `use` names it.
Both extractors put it at file scope under its bare name, so the resolver
could not tell it from a module's own alias:
- a same-file `Err(…)` call linked to a `FromStr` impl's `type Err`, and
serde's `Ok(…)` calls to a serializer's `type Ok`;
- axum's routing tests' `Response` linked to the `type Response` of
`routing/mod.rs`'s `impl Service`, a private item the modules inside
it can see;
- `io::Error` and `std::error::Error` linked to the `type Error` of an
impl in the same file, and serde's `use crate::de::Error` to one in
`de/mod.rs`.
Fix:
- Both extractors (rustImplItemOwner, and the kernel's impl_item_owner)
qualify an item written directly in an impl by the impl's type, as they
already qualify its methods: `ValueHint::Err`, `Limits::MAX`. A type no
single name stands for keeps the impl's spelling, whitespace collapsed:
`(A, B)::First`, `[u8]::Out`, `u64::WIDTH`.
- isRustAssociatedItem: a Rust type alias or const with a qualified name.
Nothing else of those kinds gets one: an inline module adds no prefix,
a function body's items aren't indexed, and a trait's default `type`
is already its trait's.
- isRustNameInScope: such an item never matches a bare name, and matches a
path only as `Self::X` in its own impl or as `<…>::X` (unchanged).
- resolveRustPathReference never picks one for a `crate::m::X` path.
Tests: rust-associated-item-scope.test.ts, 6 tests, on both engines; 5
fail on origin/main. The kernel parity torture fixture gains associated
items on a tuple, a slice reference and a primitive.
A/B on fresh indexes, origin/main
|
||
|
|
3308cff071 |
fix(swift): a call through a prefix operator, try or await keeps its callee and receiver (#2686)
tree-sitter-swift binds a prefix operator to the leftmost operand: `!isReady()` parses as `(!isReady)()`, and `!store.contains(id)` as `(!store).contains(id)`. `try` and `await` before a chain bind to the part before its last call: `try! Decoder().decode(d)` parses as `(try! Decoder()).decode(d)`. Both engines read the callee or receiver node as written, so the call was named `!isReady`, which never resolves, or it lost its receiver and name-matched whichever `contains` or `decode` came first; a type path behind a `!` lost its path too. Both walkers now strip these wrappers (a prefix operator other than an implicit member's `.`, `try` and `await`) off a call's callee and receiver. They read a receiver chain's text from its innermost wrapped operand on, for the chained-call encoding and the type-path receiver, and look through a prefix operator for the type a member read references: `!Config.shared.isOn` references `Config`. An operator applied to a parenthesized value, `!(x)` or `-(a + b)`, parses as a call of the operator and now emits no call. swift-wrapped-call-receivers.test.ts checks the edges, and TortureWrappedCalls.swift pins both engines' refs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c58f0417c6 |
fix(swift): a call written after try! links what it calls on Windows (#2683)
The Windows kernels read the `!` of every `try!` as a logical not, so `try! Message2(data: d)` became a call of `! Message2` that never resolves, and a call through a receiver lost it: `try! Settings.Defaults.load()` was a bare `load`. The cause is the grammar's C, not the walker. tree-sitter-swift 0.7.3's external scanner steps aside for the `!` of `try!` through a 64-bit mask whose entry is built as `1UL << FAKE_TRY_BANG`, bit 32. `unsigned long` is 32 bits on Windows and MSVC folds the overflowing shift to 0 (warning C4293), so the scanner never stepped aside: `try` parsed alone, then a prefix `!` on the callee. macOS and Linux have 64-bit longs, and clang's wasm32 build folds the shift to bit 31 (`_directive_endif`, valid at every expression start), so the wasm and the other kernels have always read `try!` right. The kernel now compiles the crate tarball's src/ itself, from codegraph-kernel/grammars/swift, with upstream 0.7.4's one-line fix (`1ULL`), as it already does for the Kotlin, Lua, Scala and Dart grammars. The patch and its provenance are in docs/grammars/tree-sitter-swift.patch and .md. parser.c is unchanged, so the node-kind and field tables still match the wasm, which is untouched. TortureTry.swift pins calls after try!, try? and try on both engines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
22e9f7b337 |
fix(go): variables declared in a var ( … ) block are indexed (#2682)
tree-sitter-go wraps the specs of a var group in a var_spec_list node, unlike a const group's, which sit directly in the declaration. Both extractors read only the specs directly inside a declaration, so every variable declared in a var ( ... ) block was missing from the graph: no node, no call or construction recorded from its initializer, and nothing for code that reads it, or for a call made through it, to point at. Each member of a var group is now a variable, as one declared on its own already was, with its own doc comment or the group's, and its initializer walked under it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6e68a11beb |
fix(go): a constant in a const ( … ) group keeps its own doc comment (#2680)
Both extractors read every member's docstring from the comment above `const (`, which documents the group. A constant documented inside the group, like gin's ErrorTypeBind under "ErrorTypeBind is used when Context.Bind() fails.", stored no docstring, or the group's comment instead of its own. A member's own doc comment, read with Go's doc comment rules, is now its docstring. A member without one keeps the group's comment, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f43432c86a |
fix(go): a comment directive like //go:embed is not part of a declaration's doc (#2679)
go doc leaves comment directives out of a doc comment's text: a // comment with no space after the slashes that is //line, //extern or //export followed by text, or that opens with a lowercase word, a colon and a lowercase letter or digit, like //go:embed static or //nolint:errcheck (go/ast's isDirective). Both extractors kept them, so prometheus's testsFs had the docstring "go:embed testdata", etcd's store.restore "nolint:unparam", and a doc comment with a directive under it ended in that directive. Go now drops directives from the docstring's text through a Go-only extractor option (docstringSkipsDirectives in TS; the kernel's Go docstring helper becomes preceding_go_doc_comment, which applies all three Go rules). A directive still counts as a comment when deciding where the doc comment starts, as in go/parser, so a declaration whose only comment is a directive has no docstring. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
23f728fac2 |
fix(go): a comment a blank line separates from a declaration is not its doc (#2678)
Go's doc comment, the one go doc shows (go/parser's lead comment), is the comment group that ends on the line right above a declaration: comments on consecutive lines form one group, and a blank line ends it. Both extractors joined every comment above a Go declaration instead, so a section banner, a TODO or commented-out code above a blank line became part of the declaration's docstring, or its whole docstring when the declaration had no doc of its own. Go now keeps only that group, through a Go-only extractor option beside the trailing-comment one (docstringSkipsDetachedComments in TS, preceding_docstring_skipping_trailing_and_detached in the kernel). A comment that ends on the declaration's own line documents nothing, as in go doc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
383c2cd9e3 |
fix(go): an interface's methods link the types their signatures name (#2659)
A Go interface's methods are `method` nodes, but their signatures named
no types: etcd's `KV.Put(ctx context.Context, key, val string, opts
...OpOption) (*PutResponse, error)` had no edge to `OpOption` or
`PutResponse`. A call through the interface reaches that method, so
impact on those types missed the interface method and the callers that go
through it.
TS extractor and Rust kernel, mirrored:
- extractGoInterfaceMethods / extract_go_interface_methods give each
method a `references` ref per type name in its parameters and results
(goInterfaceMethodTypeNames / interface_method_type_names, on #2655's
collectGoTypeNames).
- Skipped: the predeclared types and, written bare, the interface's own
type parameters (goTypeParameterNames / type_parameter_names, split out
of goDeclaredNames). The interface's own name is a type its method names
(`Children() []Node`), so it is kept.
A/B on main
|
||
|
|
a944ec3579 |
fix(go): a struct links the types its fields are declared with (#2655)
A Go struct referenced nothing through its fields: etcd's `type
PutResponse struct { Header *ResponseHeader }` or gin's `type Context
struct { handlers HandlersChain }` had no `references` edge to those
types, so impact on `ResponseHeader` or `HandlersChain` missed every
struct holding one, and their users. Main had 0 such edges on gin, etcd,
prometheus and kubernetes; only embedded fields were read (`extends`).
TS extractor and Rust kernel, mirrored:
- goStructFieldTypeNames / struct_field_type_names walk each named
field's type and push a `references` ref per type name from the
struct, on the name. Fields mint no nodes: impact never climbs
`contains` upward (#536), so a field node would stop impact short of
the struct; Dart attributes field types to the class too (#2327).
- #2441's alias walk is factored into collectGoTypeNames /
collect_type_names + goDeclaredNames / declared_names, so struct
fields skip what aliases skip: predeclared types and, written bare,
the declaration's own type parameters and its own name (no self-edge,
no failed row for `next *node`).
- An embedded field stays `extends`; an embedded generic's type
arguments are references (client-go's
`listers.ResourceIndexer[*v1.Pod]`: 651 such fields in kubernetes).
Resolver: gateTargetKind's unknown-qualifier rule (#2417) now covers
every Go type declaration (GO_TYPE_KINDS). Without it, a field
`op clientv3.Op` under an unaliased go.etcd.io/etcd/client/v3 import
bound to the same file's own `Op`.
Explore: a `references` edge whose ends are both type declarations no
longer makes either end connected to an entry. With the new edges alone
the #1500 CG-10 gate failed: payroll's 8-line `RunResult` (holding a
`[]Payslip`) became connected and displaced the 51-line
runPayrollCycleAll body. The rule alone changes no rendered symbol on
gin, etcd, prometheus, kubernetes, bloc, bulletproof or refine-examples.
Validation (main
|
||
|
|
3be906fcf4 |
test: stop index-daemon-rebuild leaving a suspended daemon on Windows (#2629)
A full suite run on Windows left a `serve --mcp --path %TEMP%\cg-rebuild-…` process behind. Its parent was gone, and its one thread was suspended with 0 CPU used. It would never exit, and it held the checkout's dist. libuv creates every detached child suspended on Windows and resumes it only at the end of `uv_spawn`. A launcher killed inside that call never resumes its daemon. The daemon never runs, so it writes no daemon.pid, and nothing can find or stop it. The rebuild test's teardown killed its launcher at once. The test stops the daemon on purpose, so by then the launcher is serving calls in-process with a retry timer running. When the retry comes due during the last call, the launcher spawns its daemon as soon as that call answers, just as the teardown begins. Under load the two coincide. The teardown now: - closes each launcher's stdin and waits for it to exit. A graceful exit can't land inside the synchronous spawn. - records the daemons each launcher spawns (#1773's recorder) and stops them until all have exited. One spawned as its launcher exited may still be starting, and stopDaemonAt leaves a daemon running until it answers. The recorder's comment now says that a launcher killed mid-spawn records nothing. CODEGRAPH_DAEMON_RETRY_MS=2500 puts the retry inside the last call on a quiet box. Under it, main's test left a daemon in 6 of 12 runs: 2 suspended ones, plus 4 live ones whose fixture then failed to delete. This test left nothing in 12 runs. Of the mutants, killing launchers again, a single stopDaemonAt and no spawn recorder each leaked. The other daemon test files left nothing in 2 runs each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b635dd467f |
fix(go): a type declared on its own keeps its doc comment (#2438)
tree-sitter-go puts the comment above `type Foo struct{}` before the
`type_declaration`, but the node is made from the spec inside it, so both
extractors found no docstring for an ungrouped struct, interface, defined
type or alias. A type inside a `type ( ... )` group kept its own comment,
which sits beside it in the parentheses.
Go's docstring is now read from the declaration when the declaration
holds one spec and nothing precedes that spec inside it. The leading
comment of a group with several members stays the group's.
Reading from the declaration exposed comments that trail the line above:
`const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto
package` would have become `type MetricType int32`'s doc. For every Go
declaration, a comment that begins after code on its line, and one that
begins on the line such a comment ends, is now left out, as go/parser
reads them. Seven function docstrings on etcd and prometheus lose one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
235387ec42 |
fix(c,cpp): a struct, class, union or enum defined in a variable declaration is indexed (#2446)
`struct Foo { … } foo;`, `class Bar { … } bar;` and `static struct { … } SPT;`
outside any function or class body went to the variable extractor, which
skipped the declaration's children, so the type, its methods and its
enumerators were never indexed in either extraction engine.
Both engines now walk such a type before the variables: in C always, and in
C++ when the tree has no errors or the file is walked in brace scopes. This
converges with #2426's walk for erroring C++ files; a C++ file whose braces
don't balance is still left alone, since error recovery can run a class past
its own `}` there. The declaration's function-as-value scan skips the walked
type, which captured its own. An unnamed struct, union or enum takes the name
of the first variable its declaration declares (also inside function bodies,
where it was `<anonymous>`), as `typedef struct { … } Name;` already takes the
typedef name, and the comment above the declaration is the type's docstring.
cDeclaratorIdentifier moves to languages/c-cpp.ts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
51000e3a92 |
fix(go): a defined type links the types it is defined from (#2441)
A Go defined type (`type WatchChan <-chan WatchResponse`, gin's `type HandlerFunc func(*Context)` and `type HandlersChain []HandlerFunc`) is a `type_alias` node, and it referenced nothing: only an `=` alias (#2417) named the types on its right-hand side. Impact on `WatchResponse` or gin's `Context` missed the declarations built from them, and everything that uses those. Fix (TS extractor and the Rust kernel, mirrored): goAliasTypeNames / alias_type_names walk the `type` field of every type_alias node, a defined type's as well as an alias's, so each type it names becomes a `references` ref on that name, where resolution reads the package qualifier back. Still skipped: the declaration's own type parameters, Go's predeclared types, and now its own name written bare, which in a recursive type (prometheus' `type stateFn func(*Lexer) stateFn`) is the declaration itself: no self-edge, and no failed row that would make dead code treat every namesake as referenced. A qualified name is never skipped (`type PutResponse pb.PutResponse` names pb's). Resolution is unchanged. A defined type is a new type: goAliasTarget still follows `=` aliases only, so a method called on a defined type is not looked up on its underlying type. The gateTargetKind rule that leaves a type_alias's reference written through a package the index doesn't know unresolved now covers defined types too; without it, every such name bound to the same-file declaration of its name (`type Op clientv3.Op` linked itself). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ed199e60d8 |
fix(go): a type alias type A = B is indexed and links to the type it names (#2417)
tree-sitter-go parses `type Event = mvccpb.Event` as a `type_alias`, beside
the `type_spec` of a defined type, and both extractors read `type_spec`
only. etcd's client/v3 `Event` had no node; a use through its package
(`clientv3.Event`, prometheus's `storage.AOptions{…}`) linked to nothing,
and a use inside it linked by name to any type so named, another package's
included.
- Extraction (TS and kernel): an alias is a `type_alias` node referencing
each type its right-hand side names, at the name so resolution reads the
package back; an alias of a struct or interface literal is a struct or
interface. The alias's own type parameters and Go's predeclared types are
skipped. A generic alias, which tree-sitter-go 0.23 parses as a
`type_spec` around an error, reads as an alias on wasm; the kernel defers
its file.
- A method called on an alias-typed value is the aliased type's: an alias
of a project type is followed in its own package, through pointers and
further aliases; one of an outside type resolves by name as before.
- An alias's target written through a package that is none of the file's
imports as indexed stays unresolved, as an embedded type's does, so the
alias never links to itself or a namesake.
- With Go defined types as implementers (#2419), an alias that owns
methods written with it as the receiver implements what they satisfy,
in place of the type it names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
24501fa66c |
fix(go): an interface links the interfaces it embeds (#2397)
extractInheritance's Go interface-embedding branch matched a `constraint_elem`, a node type neither the vendored tree-sitter-go wasm nor the kernel's tree-sitter-go 0.23 crate has: both parse an embedded interface as a `type_elem` holding one type. So no Go interface ever extended anything, in either engine; gin's IRouter never reached IRoutes. Read an embedded type the same way in an interface and in a struct: `T`, `*T`, `pkg.T` and `T[X]` all embed `T`, recorded at the name so resolution reads the package back from the source (#2322). A type_elem holding a union, a `~T` term or a lone predeclared type is a type-set constraint and records nothing, and predeclared names (`error`, `any`) are skipped in structs too. Qualified and generic struct embeddings, skipped until now, link the same way. Resolution keeps an embedded type in its package. The Go framework resolver's naming heuristics no longer take extends/implements refs: its PascalCase-model pattern saw structs only, so an embedded interface went to another package's same-named struct (promql/parser's Node, etcd's Lease), as #1827 did for Spring. And a Go supertype written through a package that is none of the file's imports as indexed (`yaml` under an unaliased go.yaml.in/yaml/v3, registered as `v3`) stays unresolved instead of binding by its bare name. Validated on gin, prometheus and etcd (before/after edge diffs, every added supertype edge checked against its package; kernel/wasm parity sweeps unchanged). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
83f54a1853 |
fix(dart): a library links the files its part directives name (#2391)
A Dart `part 'x.g.dart';` now mints an `import` node (name = the URI,
signature = the directive) and an `imports` ref from the library's file
node, in both extractors. The ref resolves through `dartDirectiveFile` by
the rules an import or export follows, so the library's file links the
part's file. A library and its parts are one library, so a change to a
part, generated or hand-written, now reaches the library and what imports
it in `codegraph affected` and the files listed as using the part. Before,
neither `part` nor `part of` left a trace, and the two files were joined
only by whatever symbol edges happened to cross between them: riverpod's
framework.dart, which holds nothing but `part` lines, by none.
`part of` still records nothing: the library's edge already joins the two
files, and a part -> library edge would list every pair on both import
rails of both files and make each a file 2-cycle.
The kernel's directive arm now goes on into the directive's children, as
the TS ladder does. Its `import_or_export` arm used to return first, so an
annotated directive's arguments (`@Tag(f) import 'a.dart';`) were fn-ref
candidates on the wasm path only.
Validation on rrousselGit/riverpod @4ba1be2 and felangel/bloc @b9be1e2
with the kernel loaded, base
|
||
|
|
d2fb323e51 |
fix(dart): an annotation links the constant or constructor it names, even behind a comment (#2390)
A Dart annotation is a constant expression: a const variable (`@riverpod`, `@meta.immutable`, a type's static constant or enum value) or a call of a const constructor (`@Riverpod(keepAlive: true)`, `@Foo.named(…)`). It was ranked like a Python decorator, which favours a function or method of that name over a class and scores a constant not at all, so riverpod's 506 `@riverpod` annotations went to riverpod_analyzer_utils' extension getter `riverpod` (0.4) instead of riverpod_annotation's `const riverpod = Riverpod();`. matchDartAnnotation (name-matcher.ts) now takes every Dart `decorates` ref before any other strategy, and nothing falls through. It reads the annotation as written from its `@` and links a constant or (called) a class the file's library can see, through the import prefix it is written with (isDartImportPrefix, dart-libraries.ts); a type's own static constant, enum value or (called) named constructor (isDartConstructor); or, on a member, a static constant of the type around it. Never a method, getter or function. The decorator scan also stopped at the first sibling that was not an annotation, so a comment between an annotation and its declaration (`@override` `// ignore: must_call_super` `void f()`, riverpod_lint's `@riverpod` `// expect_lint: …` fixtures) hid the annotation. Dart's decoratorStepOverTypes lists its two comment kinds, which the scan now steps over, and the kernel's extract_decorators_for steps over the same. Validated on rrousselGit/riverpod @4ba1be2 and felangel/bloc @b9be1e2 with the kernel loaded: riverpod's 506 `@riverpod` edges move to `const riverpod` and 16 annotations behind a comment add an edge each, with nothing removed; bloc gains 7 `@override` refs and no edge changes. Kernel and wasm full-index dumps are byte-identical, and the parity sweeps show 0 diffs. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9924684899 |
fix(dart): a member keeps the dartdoc written above its annotations (#2387)
A Dart member's annotations are siblings that stand between it and the
dartdoc written above them:
/// Builds the widget.
@override
Widget build(BuildContext context) { ... }
Both extractors read a docstring from the comments directly before the
declaration (or its `declaration` wrapper) and stopped at the first node
that was not a comment, so the annotation ended the run before the `///`
was reached. Members behind `@override`, `@protected`, `@mustCallSuper`,
`@pragma(...)`, `@internal` and the like lost their documentation, and
searching for words in it didn't find them.
Extraction (TS + kernel, at parity):
- getPrecedingDocstring takes an optional list of sibling types to step
over, and Dart sets `docstringStepOverTypes: ['annotation']`. Stacked,
multi-line and inline annotations are passed, and comments on either
side of one still join (`/// a` `@x` `// b` gives "a" and "b" on two
lines), as adjacent comments already did. Any other node still ends
the walk: the previous member's body, a field, a top-level variable,
an import or another declaration.
- The kernel's shared docstring.rs gains preceding_docstring_stepping_over.
preceding_docstring passes an empty list, so every other language is
unchanged; only Dart's docstring_of passes ["annotation"].
- Class-like declarations needed nothing: a class, mixin, extension,
extension type, enum or typedef node starts at its first annotation,
so the dartdoc above it is already the node's previous sibling.
Validated on felangel/bloc @b9be1e2 and rrousselGit/riverpod @4ba1be2 at
main
|
||
|
|
249d9a860e |
fix(dart): a comment inside a member chain no longer loses a call or a read (#2385)
tree-sitter-dart keeps a comment as a named node wherever it is written, so in a member chain broken over lines (`tester //`, then `.state(…)` on the next line: the empty `//` keeps dart format from joining the lines) the comment, not `tester`, is the sibling right before the `.state` selector. Both extractors (the TS walker and the native kernel) read that sibling as the receiver or the callee: - A call with a comment between the member and its arguments (`box.grow /* by */ (3)`) was lost, and recorded as a read of `box.grow`. - A getter read (`box //` + `.area`) or a static access (`Config //` + `.instance`) written after a comment was lost. - A call after a comment kept only its bare name. Since #2376 the resolver reads such a chain link back past comments, so those calls already linked correctly; they now carry the receiver in the name. Every sibling step along a member chain now skips `comment` and `documentation_comment` nodes: the callee before an argument part, dartReceiverOf / receiver_of, dartCalleeOfArgPart, the type in front of a constructor's type arguments, and the member read's receiver and next selector. The misparsed-generic-call gate keeps reading raw siblings, and now rejects a comment against the `<` (`ref.read /* c */<Repo>(p)`), so that shape stays a comparison instead of losing its read. Validated with the kernel loaded on felangel/bloc @b9be1e2, rrousselGit/riverpod @4ba1be2 and flutter/samples @63411527. No edge is added or removed: the 14 comment-split chains there are all in riverpod, and its 5 calls among them gain their receiver and stay unlinked. Kernel/wasm parity has 0 diffs on all three. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1b752de2c0 |
fix(dart): a member with no body keeps its dartdoc and annotations (#2382)
A Dart member written without a body - a constructor like `Foo._();` or
`Point.origin() : x = 0;`, a `const` constructor or redirecting factory
such as flutter_bloc's `const BlocProvider.value(...)`, an abstract or
`external` method - parses as a `declaration` wrapping its signature,
and its `///` dartdoc and `@annotation`s are siblings of that wrapper.
Both extractors looked for them before the signature, inside the
wrapper, and found nothing: the docstring was lost, and
`@visibleForTesting Foo._();` emitted no `decorates` reference.
Extraction (TS + kernel, at parity):
- A Dart-only `getDeclarationWrapper` hook returns the `declaration`
when the signature is its first named child. `docstringFor` and
extractDecoratorsFor's preceding-sibling scan start from it; the
kernel's `declaration_wrapper` does the same for every docstring
lookup and for the annotation scan.
- The shared DOCSTRING_WRAPPER_TYPES / `is_wrapper` list is untouched:
C/C++ share it, and their `declaration` wraps declarators. All 35
non-Dart parity fixtures extract byte-identically before and after.
Validated on felangel/bloc @b9be1e2 and rrousselGit/riverpod @4ba1be2
at main
|
||
|
|
3f96f80fcb |
fix(dart): link generic calls the parser reads as two comparisons (#2375)
tree-sitter-dart parses a generic call with arguments as two comparisons about as often as it parses it as a call, depending on the code around it: `ref.read<Repo>(p)` comes out as `relational_expression(relational_expression(ref.read < Repo) > (p))`. Generic constructors (`Provider<int>((ref) => 0)`), named arguments (which then parse as a `record_literal`) and calls after `await` or a prefix operator are hit too. Neither extractor recorded a call for that shape, only a member read of `ref.read` that linked nothing, so providers, repository reads and bloc lookups written with a type argument had no callers. Across felangel/bloc, rrousselGit/riverpod and flutter/samples there are 2,853 such calls, against 2,836 generic calls with arguments that parse as calls. - Both extractors (the TS walker and the native kernel) recover the call at its `<` when the type argument names a type and the code is laid out as a call: `<` against the callee and `(` against the `>`. A comparison such as `a < b` is left alone. The recovered call gets what a parsed one does: the call at the `<`, a reference to the type argument, and no member read of the callee or of a prefixed type argument. - A call chained on a recovered call keeps its bare name. The `BlocProvider.of().increment` encoding a parsed chain gets resolves through what `of` returns, which for a generic factory is its type parameter, so it would drop the edge the bare name finds. - When the grammar ends an arrow function in front of a generic call (`(ref) => ref.watch<int>(p)` as `((ref) => ref).watch<int>(p)`), the receiver is read from the end of the arrow's body, so the call is `ref.watch` rather than a bare `watch`. Validated with the kernel loaded: bloc +85, riverpod +1,580 and flutter/samples +54 edges, none lost; kernel/wasm parity has 0 diffs on all three repos. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
652c36b72a |
fix(dart): index const constructors and redirecting factories; a call through a type stays on that type (#2380)
* fix(dart): index const constructors and redirecting factories; a call through a type stays on that type Both Dart extractors (the TS walker and the native kernel) skipped `const` constructors and redirecting factories (`const Foo.bar(...)`, `const factory Foo.bar() = _Bar;`), which parse as signature kinds of their own. A call such as flutter_bloc's `BlocProvider.value(...)` then found no method on BlocProvider, and the resolver guessed another type's `value`: on felangel/bloc, 89 calls linked to `RepositoryProvider::value`. Extraction (TS + kernel, at parity): - Named const constructors and redirecting factories are methods named by the constructor, returning their class, with a parameters-only signature; the unnamed ones stay unindexed. The name is read only from before the parameter list, so a redirect target (`= _Impl.named`) never names it. - A redirecting factory references the class it redirects to, not the target constructor's name or an import prefix. - A constructor called with type arguments keeps its type: `BlocProvider<CounterCubit>.value(...)` is `BlocProvider.value`, as a statement and as an expression (`constructor_invocation`, which produced no call at all before). Resolver: - A Dart call through a type's name reaches only that type's own constructor, static member or static constant, or nothing: no guess by name, no inherited lookup, no same-file look-alike, no instance member of the same name. SDK calls like `Uri.parse` no longer land on a project `parse` past an `extension on Uri`. - A named constructor is never reached by a bare name, the last link of a chain, a function value or an annotation (riverpod's 210 `@internal` annotations). Validated on felangel/bloc @b9be1e2 and rrousselGit/riverpod @4ba1be2 with the kernel loaded: bloc 18,338 -> 18,495 edges, riverpod 85,606 -> 85,854, every removed edge triaged. Afterwards every edge into a named constructor is written `Owner.ctor`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(dart): read a type's static members one way for calls and chains - A call through a type's name now finds its member with dartStaticMember, the lookup a chain's static link uses since #2376, so both pick the same member: the call site's own file first, then the nearest copy. - isDartStaticMember's constructor test is isDartConstructor, anchored at the declaration: an instance getter declared just above a same-named factory (`Object? get error` over `const factory AsyncValue.error(...)`) read the factory from the next lines and counted as static. - clearNameMatcherMemos drops DART_CONSTRUCTORS with the other source-derived Dart memos. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6421644ef2 |
fix(dart): link calls in top-level and field initializers (#2367)
Both Dart extractors (the TS walker and the native kernel) skipped the code in initializers. A `final`/`const` declaration minted its constant and stopped, and a field's or `var`'s initializer only had its types recorded (#2360). In Riverpod apps, where most wiring lives in provider closures (`final repoProvider = Provider((ref) => Repository(ref.watch(dioProvider)));`), what a provider builds with had no callers, and impact and explore flows stopped at the provider. - A `final`/`const` initializer is walked like a body, for its constant: calls, instantiations, static and member reads, types, closures with block bodies and local functions, function values. - A field's (instance, `static var`, typed, `late`) or a top-level `var`/`late final` initializer is walked for its class or the file, since neither mints a node. A local declaration's second variable (`for (var i = 0, j = n(); ...)`) stays its function's. - Each initializer is walked once: a new `walkInitializer` context method marks it, and the dispatcher's function-as-value scan skips it. Other languages' hooks are unchanged. - Resolver: a Dart call through a type's static constant (`FutureProvider.autoDispose(...)` on `static const autoDispose = ...Builder();`) links that constant, not another type's same-named method. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9f69e47836 |
fix(dart): link getter reads, enum-extension members and more type positions (#2338, #2327) (#2360)
Issue #2338: a Dart getter read (`x.area`, `s.label`) linked nothing, so `codegraph callers area` was empty while `x.grow()` worked, and a member an extension adds to an ENUM (`s.shout()` through `extension ShapeInfo on Shape`) never resolved. Issue #2327: `callers Report` saw only parameter and return types; the type an extension is `on`, field types, and types named in bodies and initializers (`Future<Report?>.value(null)`, riverpod's `final reportProvider = Family<Report?, String>()`) recorded nothing. Causes: - The Dart body walker only emitted calls (a selector with an argument list); a member read is a selector without one, so it produced no ref. - For a typed receiver, resolveMethodOnType looks for `Type::member` and the type's supertype edges; an extension's member is `ShapeInfo::shout`. On a class receiver the call fell through to name matching, which found a unique `twice` by luck; on an enum the "type the project doesn't declare" guard (it only counts classes) returned null. - Type references were taken from function and method signatures only. Fix (TS extractor and the Rust kernel, mirrored): - A member read `x.area` / `x?.area` / `Type.getter` whose receiver is a plain name, and that is not a call, is sent as a `references` ref named `x.area` on the member. The resolver (matchDartMemberRead, ahead of every other strategy) types the receiver — a static read through a project type, a parameter or local in scope, else a field of the enclosing class — and links the getter that type reaches: declared on it or a type it extends, mixes in or implements, else added by an extension on one of those (an unnamed extension only in its own library). The edge is `calls`: a Dart getter is a method node, reading it runs it, and `calls` is what callers, impact and explore's flow follow (as Ruby's paren-less `x.area` and VB.NET's paren-less calls through #2355 already are). A field read, a getter of a type outside the project and an untyped receiver link nothing; there is no name-only fallback. Kotlin/Swift/C#/TS link no instance property reads today. - resolveMethodOnType, for Dart, falls back to a member an extension on the type (or a supertype) adds when no declaration of the type has it. - `references` refs for the type an extension is `on`, a field's declared type and initializer (from its class — Dart fields mint no nodes), an enum field, a top-level variable's type (from the file), a constant's initializer (from the constant), and every type a function body names: locals, generic arguments, casts, type tests, catch clauses. Skipped: a `new`/`const` constructor's class (already instantiates/calls), an import prefix (`p` in `p.Foo`), built-ins, and lowercase names, which in Dart are built-ins (`num`, `dynamic`) or error-recovery artifacts around syntax the grammar predates (dot shorthands). A class whose field holds its own type does not get a self-edge. Verification: __tests__/dart-getter-reads.test.ts (7 tests) and __tests__/dart-type-positions.test.ts (3 tests) fail 8/10 on main (both the TS path and main's kernel) and pass on both arms here; the 2 that pass on main are the controls. New kernel parity fixture TortureReadsTypes.dart (+CRLF); kernel-dart-parity passes with CODEGRAPH_KERNEL_EXPECT=1, and scripts/kernel-parity.mjs over bloc/riverpod/flutter-samples is 0 diffs (2,103 files byte-parity, 300 parse-error files defer as before). tsc and build clean; the issues' repros now list c/a/b for area/label/shout and Holder, ReportX, reportProvider and run for Report. Validation (kernel loaded, main vs this branch): nodes unchanged, 0 edges removed, no new self-loops. - felangel/bloc: 16,372 -> 18,276 edges (+550 getter-read calls, +1,354 type references) - rrousselGit/riverpod: 76,432 -> 81,909 (+598 getter reads, +13 extension-method calls, +4,866 type references) - flutter/samples: 26,133 -> 26,943 (+142 getter reads, +668 type refs) Spot-checked 16 getter reads and 16 type references against source. Index time within noise on riverpod (median of 5 interleaved runs); reads that link nothing are kept for re-resolution like failed calls (riverpod db 92.9 -> 103.2 MB). No EXTRACTION_VERSION bump: it already moved to 28 this release. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
511d86e94d |
fix(mcp): replace a daemon left running by an older install (#2335) (#2343)
A daemon keeps running the code it started with. The 1.6.1 daemon in #2335 outlived an upgrade, kept loading grammars from the install the upgrade removed and wrote every file it re-indexed as empty, while every session from the new install served itself in-process, read-only, because the old daemon held the writer lock. Daemons from before this change cannot notice an upgrade, so the launcher acts: - A launcher that finds a daemon of an older plain X.Y.Z release stops it the way `codegraph daemon` does (daemon.pid and the socket hello must agree; SIGTERM, SIGKILL only if it still answers) and starts one from its own install. It finds the daemon by its hello on the launcher's own socket, or by the project lock when it listens elsewhere: on Windows, daemons before #2278 named their pipe after the root as typed. A daemon of the same, a newer, a prerelease or an unknown version is never touched, so two installs cannot take turns stopping each other's. - The writer slot never falls free on the way. The launcher swaps writer.pid to itself (mode `handover`) before the signal, clears the old daemon's leftovers while it holds the slot, and the daemon it spawns takes the slot over (CODEGRAPH_DAEMON_HANDOVER). Sessions of the old daemon that fall back in-process find the slot held and serve reads only, instead of claiming it with the removed install's code. A daemon that does not exit gets the slot back, and a successor outwaits a racing candidate that took daemon.pid first. Validated on Windows with real npm upgrades from the published 1.6.1 and 1.6.2 to this build packed as 1.6.3, with the old session calling throughout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ff75a6ca5f |
fix(csharp): namespaces, usings and nested types follow the language's scopes (#2220)
A block namespace qualifies only its own body (a nested one is Outer.Inner), mirrored in the kernel; a using names the project's namespace, never another file's using; a global using is its own project's; a nested type is reached by bare name only from inside its owner or a type deriving from it, through any partial part; visible C# types are filtered before ranking. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ad77e5fc8e |
fix(java,kotlin): extract enum constants' own bodies (#2194)
A Java enum constant or Kotlin enum entry with a class body -
`PLUS { int apply(...) { ... } }`, `NewBuffer { override fun pipe() ... }` - was
recorded as a bare enum_member: its body was never visited, so its methods
were missing and every call in them was dropped. jsoup's tokenizer
(`TokeniserState`, one `read` per state) and tree builder
(`HtmlTreeBuilderState`) are written this way.
The body is now visited under the member (QN `Op::PLUS::apply`), which
counts as class-like so its functions are methods and its properties fields.
Only a `class_body` child is visited, so Rust's struct-like variants (a
`body` field of field declarations) are untouched. Mirrored in the kernel's
Java and Kotlin walkers; kernel parity is byte-identical on the fixtures
(Torture.java gains a constant-with-body enum; torture.kt already had one)
and on gson/koin/okio.
jsoup +2156 edges, okio +138 -23, okhttp +106 -11, gson +97, halo +29,
commons-lang +16; koin, Exposed, mall unchanged.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
b50c7c0a70 |
fix(swift): a call through a type path lands on the type it names (#2118)
`API.PackageController.GetRoute.query(on:)` kept only `query` — the
receiver was a navigation_expression, not an identifier — and the bare
name exact-matched whichever type's `query` came first. On
SwiftPackageIndex-Server every route type has one, so health checks and
package-controller tests "called" the dependency controller (or a test's
own `query`).
Extraction (TS and the kernel's swift walker, byte-parity): a receiver that
is a path of capitalized type names, two segments or more, not led by
`Self.`, is kept: `API.PackageController.GetRoute.query`. Instance chains
(`self.store.load()`) and mixed paths (`URLSession.shared.data`) are
unchanged.
Resolution (swift-type-visibility.ts, called decisively from
matchReferenceInner so nothing falls through to name guessing): the member
on the type the path names — a method, an enum case with associated values,
or a nested type's initializer. A member's owner path is its qualified name
with the outermost extension's written path read back from its line
(`extension API.PackageController { enum ShowRoute … }` →
`API::PackageController::ShowRoute`), so twin `Feature::State`s declared in
`extension BasicsView.Feature` and `extension ObservableBasicsView.Feature`
are told apart. A path shortened inside its namespace or behind a module
qualifier fits; two owners, or none, leave the call unresolved. TCA's
`@Reducer enum Path { case detail }` generates `Path.State`/`Path.Action`,
so `Path.State.detail(…)` lands on the written case.
A/B vs main (edge diffs, every change classified): SPI 76 retargeted, all to
the type the line names, 7 dropped (Fluent `App.Version.query`, `Plot.Node.*`
— SDK members main pinned on project methods); TCA 61 retargeted, 1 dropped
(`State.StateReducer.scope` → an unrelated `Store::scope`); mastodon-ios 177
retargeted (`Mastodon.API.V2.Instance.instance`, `L10n.Plural.Count.vote`),
11 SDK drops; swift-nio 6 calls into a deprecated alias became instantiates
of `ChannelOptions.Types.*`; IceCubes 1/1, Alamofire 2 SDK drops.
Kernel/wasm parity 0 diffs over 2,752 files on six repos.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
c405ac5e6c |
fix(windows): make load-sensitive tests wait on real signals; longer pid-file retry (#1773) (#2078)
* test: make Windows load-sensitive tests wait on the tree, not the clock (#1773) Each failure from the loaded Windows run was traced to its mechanism and reproduced deterministically before changing it. No assertion is weakened. - daemon-lock-refresh: the PowerShell holder slept 200ms against a 375ms retry budget. Windows refuses a replace-rename while any handle to the target is open, even a Node read handle, so hold one in-process and close it inside the rename mock after the first real failure. Also asserts the first rename really failed with a retryable code. - mcp-projectpath-lifecycle: the owner daemon armed a 500ms idle timer at start and exited (code 0) before a starved engine reached it. It now starts with idle exit off and is armed over IPC once the engines hold a session. The file waits for catch-up instead of the gate's 3s serve-anyway deadline, and its index-heavy hooks and eviction case get explicit timeouts. - mcp-daemon / mcp-writer-lock teardown: racing launchers spawn detached daemon candidates whose cwd is the fixture; a loser still starting at teardown pinned it (EBUSY) or took over after the winner was stopped. A --require preload records candidate pids, and teardown waits for the losers before stopping the winner. - mcp-writer-lock: writer.pid appears before the daemon binds and rewrites daemon.pid, so cleanup could probe a daemon mid-start ('unverified'); wait for both proxies to attach. Removal uses fs.promises.rm, because fs.rmSync gives up on the first Windows access-denied error despite maxRetries. - mcp-daemon #1963: connect only after the proxy attached (lock before bind). - sync-rebuild-convergence / multi-repo-workspace: explicit timeouts scoped to the cases that build real indexes; the global timeout is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(daemon): give the Windows pid-file replace a longer retry budget (#1773) Windows refuses to replace a file while any handle to it is open, including a plain Node read handle, so an antivirus scan, an indexer, or another session reading daemon.pid can each block the daemon's startup refresh for a moment, and for longer on a loaded machine. The retry waited 375ms in all; it now backs off 25, 50, 100, 200, then 400ms three times, about 1.6s over eight attempts, still well inside a launcher's ~6s connect window. Permanent failures and non-Windows errors still abort at once, and an ownership change still stops the retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
fa25883ab0 |
fix(cpp): resolve local constructors and macro visibility (#1839) (#2035)
* fix(cpp): macro invocations are not calls; local objects call their constructor A function-like macro invocation (`TRACE_POINT(1)`) parses as a call, and the resolver bound it to a same-named free function in another translation unit — a caller and a callee that never existed (#1838). And local object initialization (`Widget w;` / `Widget w(1);` / `Widget w{1};`) reached the class node instead of a constructor, or nothing at all for the default form (#1839). Extraction (wasm walker and the Rust kernel, in parity): - every `preproc_function_def` becomes a `constant` node whose signature is the directive, so the index knows which names are macros; - a C++ declaration emits one `calls` ref per constructed object, shaped `ns::T::T/<arity>`; pointer, reference and function declarators, `extern`, and array element braces construct nothing; the #1035 `instantiates` ref is unchanged; - constructors carry their parameter list as the signature. Resolution: - `cpp-macro-visibility.ts` walks the translation unit (root file plus in-repo includes, each file once) over per-file summaries of function-like defines, undefs and includes, evaluating what each file can decide itself (literals, its own defines, the include-guard idiom). A call whose name is definitely a macro at that point, or a name that only macros bear, resolves to nothing. A macro that exists in one build configuration only (CMSIS's `__DSB()` under the ARM compilers, a target's HAL compat header) keeps the call to the function the other configuration compiles; a wrapper macro that calls its own name is how that function gets called and hides nothing. - `cpp-constructor.ts` resolves the constructor refs: the type in the lexical namespaces of the call site, then the single overload whose parameter count admits the arguments; two admitting overloads, an initializer_list overload, or an aggregate yield no edge. - a `#define` constant is never a `calls` target and does not count toward the same-name ceiling. Measured on a betaflight tree (4,017 files, wasm arm): 153 `calls` edges lost — all fabricated (`MIN`/`MAX` bound to a CLI enum member, macros misparsed as functions, a cross-vendor `SWAPBYTE`) — and 34 constructor edges gained; indexing 34s vs 30s. Fixes #1838 Fixes #1839 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(cpp): resolve local constructors and macro visibility (#1839) Local construction lacked constructor calls, while macro invocations could bind unrelated functions. Resolve constructors using lexical types and overload signatures, preserving separately declared defaults and array element construction. Evaluate macro directives in translation-unit order, respecting reinclusion, guards and pragma once. Mirror extraction in Rust and WASM, preserve type dependencies, and distinguish them in CLI output. Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
9649ce1bd6 |
fix(resolution): reject unrelated cross-language matches (#1986) (#2032)
* fix(resolution): stop expression-receiver calls from binding to unrelated methods A method called on an expression receiver reached the resolver as the bare method name, and exact-match bound it to any project method of that name: `(await list()).map(...)` in TypeScript onto a UI adapter class's `map` (0.9), and `v.iter().map(...)` in Rust onto the same TypeScript method across languages (0.5). TS/JS extraction (mirrored in the kernel): look through receiver wrappers that keep the same object — parentheses, `!`, `as`/`satisfies`, `<T>x`, `await` (including the grammar's `(a && b)!` parse of `a && b!`) — so `x!.m()` is `x.m` and `(await f()).m()` is `f().m`; any other receiver with no static type (`(a ?? b).m()`, `f().list.m()`, `arr[0].m()`) emits nothing instead of the bare name. `this`/`super` (and chains rooted at them), `window.*` chains and `new C().m()` keep their existing bare form. Resolution: a bare-named call may cross a language family only through the C ABI (a C/C++ symbol, or a free function a C/Objective-C/Swift caller reaches, e.g. a cgo `//export`). A class member of another family, or a function/class/variable outside that pair, is rejected as the candidate a strategy commits to (exact-match single/best and fuzzy), never filtered out of the pool, so no lone survivor is manufactured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(resolution): reject unrelated cross-language matches (#1986) Name-based calls, constructors, and inheritance could select unrelated foreign-language symbols. Apply a shared interoperability policy to selected results without manufacturing replacement matches, including JSX/Vue synthesis. Preserve framework exports and ABI-backed calls, building on PR #1944's TS/Rust expression-receiver fixes. Validate with both repro scripts, 529 targeted tests, kernel/wasm parity, and clean TypeScript checks. Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e27d6da6b2 |
fix(scala): resolve companion inheritance to type definitions (#1824) (#2029)
* fix(scala): resolve extends/with to the trait, not its companion object The Scala trait + companion object idiom puts two same-named symbols in one file. Both were indexed as kind 'class', so an 'extends X' reference tied between the trait and its companion and the winner was arbitrary. When the companion won, every subtype was detached from the trait's inheritance chain, and impact analysis on a widely-used trait stopped at depth 1. - classify Scala object_definition as 'module' (a singleton value, not a type - 'extends' can never target it) - add 'module' to the classifyClassNode contract and handle it in the extractor dispatch - in name matching, bias extends/implements references toward actual type definitions and penalize 'module' candidates, so the trait wins the tie deterministically Regression test: trait + companion in one file, subclass in another - the extends edge must land on the trait and impact must reach the subtype through it. * fix(scala): resolve companion inheritance to type definitions (#1824) Scala objects and their companion types could tie during inheritance resolution, disconnecting subtype impact paths. Classify objects as modules in both extraction engines and reject Scala singleton inheritance targets across resolution strategies. Preserve object method lookup and inherited receiver methods, with declaration-order, impact, and native/WASM parity regressions. Co-authored-by: Hubert Tarnacki <hubert.tarnacki@comarch.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Hubert Tarnacki <hubert.tarnacki@comarch.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b65e05b98f |
fix(resolution): require receiver evidence for built-in method calls (#1987) (#2028)
* fix(resolution): resolve this.#field.method() on the field's type (#1987) A call through an ES private field fell outside the `this.<field>` shape (#1496): the extractor accepted only a property_identifier, so `this.#items.add(x)` was emitted as the bare `add` and exact-matched whichever project method shared the name, often the calling method itself. The extractor and its kernel mirror now keep `this.#items.add`, and the resolver matches that shape and reads the field's type off the class declaration. The field regexes open with a lookbehind instead of `\b`, which never matches before `#`. A builtin or external field type (`new Set()`, a `Map`, an array) now yields no edge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(resolution): require receiver evidence for built-in method calls (#1987) Unknown JavaScript receivers could match unrelated methods by name or class-name similarity. Require validated receiver evidence for built-in method names while retaining typed, imported, object-literal and class resolution. Preserve private-field receivers in TypeScript and Rust extraction and keep public/private field declarations distinct. Build on PR #1990 with regression coverage across all four JS/TS variants and kernel/WASM parity. Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bd993b12fa |
fix(extraction): index Python body docstrings across backends (#1905) (#2004)
* feat(extraction): index Python docstrings, not just preceding comments (#1905) getPrecedingDocstring only walks preceding comment siblings, so a Python docstring — a bare string literal first in the body — never reached the docstring column, never entered nodes_fts, and was never shown by `codegraph node`. The identical sentence written as a leading `#` comment was both. For a Python codebase that is most of the prose there is. (#1905) Adds an optional getBodyDocstring() to LanguageExtractor, in the same shape as getSignature(), and routes every docstring call site through one docstringFor() helper that consults both sources. A node carrying a comment AND a docstring keeps both, joined: they are two things the author wrote about the same symbol and the column is free text. The Python implementation reads the grammar`s string_content rather than slicing quotes off the raw text, so r/u/b prefixes and both triple-quote forms work without a regex per case; f-strings are skipped because an interpolated string is code, not prose. Dedent follows PEP 257 (first line exempt). Scoped to Python deliberately. Julia (a string sibling before the def) and Elixir (@doc) fit the same hook and are left as follow-ups. * fix(extraction): index Python body docstrings across backends (#1905) Python extraction only consulted preceding comments, leaving body docstrings absent from search and rendered prose. Extend the contributor's hook to module and definition docstrings, and mirror it in the native kernel. Handle comments, concatenated literals, and indentation consistently while rejecting bytes, f-strings, and tuples. Verify persistence, exact-name ranking, MCP/CLI rendering, and native/WASM parity. Co-authored-by: Max Hsu <maxmilian@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Max Hsu <maxmilian@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9181dd1ef3 |
fix(extraction): land upstream declaration initializer walks (#1511) (#1802)
Squash danusha2345's PR #1511 at |
||
|
|
040ba388da |
fix(php): resolve static calls through import aliases (#1545) (#1795)
Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com> |
||
|
|
cece0720e3 |
fix(resolution): resolve this.<field>.<method>() on the fields declared type (#1496) (#1792)
Land the six-file fix from upstream PR #1691 by danusha2345
(pr-1691 at
|
||
|
|
8c047342cd |
fix(lua): index assignment-style function definitions (#1650) (#1778)
Apply upstream PR #1623 by danusha2345 (fix commit
|
||
|
|
8733c2880f |
fix(extraction): index const-bound functions inside a body as symbols (#1669) (#1774)
`const handleClear = () => {…}` inside a component — every React handler
that skips useCallback — was never a symbol: the body walker only named
nested function declarations and hook-bound arrows, so the handler was
absent from callers/impact ("Symbol not found", indistinguishable from
"no callers") and its calls attributed to the component. Bind the arrow
or function expression to its declarator the way module scope already
does, in both the wasm walker and the kernel.
A navigation such a handler makes is now the handler's own edge and a hop
in the Screens `via` chain — the shape a useCallback handler already has —
so the react-router and expo-router expectations follow that convention.
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
|
||
|
|
43271f3cd3 |
fix(extraction): index CommonJS export assignments as functions (#1675) (#1771)
`exports.getItems = async (req, res) => {…}` and `module.exports.x =
function () {…}` — the Express controller style — produced no symbol: the
arrow's parent is an assignment, not a declarator, so it stayed anonymous,
its calls attributed to the file, and `node`/`callers` answered "Symbol
not found" for a route-wired handler. Resolve the name from the export
property, mark it exported, in both the wasm walker and the kernel.
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
|
||
|
|
2f1a99d34c |
fix(extraction): index C++ pure virtual methods as nodes (#1727) (#1758)
Pure-virtual declarations (`virtual int read(int key) = 0;`) parse as field_declaration, not function_definition, so they minted no method node — calls through an abstract base and cpp-override synthesis had nothing to attach to. Mirror Java interface methods: mint the node (TS + kernel), mark isAbstract, and cover with extraction/e2e/parity fixtures. Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com> |
||
|
|
bb1d3093eb |
fix(extraction): never fabricate an edge from a call-result receiver (#1748)
A member call whose receiver is itself a call — `d.setdefault(k, []).append(v)`, `make().run()` — used to drop the receiver at extraction time, degrade to the bare method name, and exact-match any top-level project symbol of that name (Python and JavaScript/TypeScript). Keep the inner callee encoded as `<inner>().<method>` in the TS extractor and native kernel; the name-matcher refuses to guess for that shape (store-accessor exception only). Based on #1692, rebased onto main after #1746. Fixes #1683. Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com> |
||
|
|
7963672689 |
fix(rust): resolve self.field.method() on the field's declared type instead of a same-named method (#1585) (#1599)
Fixes #1585. **Stacked on #1596** (the base branch is `fix/1588-rust-impl-type-qualification`; this PR's own diff is the second commit). Merge #1596 first, then retarget/merge this one. ## What was wrong ```rust impl Outer { pub fn run(&mut self) { self.inner.run(); // inner: Inner } } ``` produced `Outer::run -> Outer::run` — recursion the source doesn't contain. The extractor collapsed every `self.<field>.<method>()` receiver to the bare method name (`run`), so the resolver only ever saw `run` and exact-matched the nearest same-named method — the calling method itself, or a method of an unrelated type. Nothing marked the edge as a guess, and no row stayed in `unresolved_refs`, so a consumer had no way to tell. The same happened when the field's type isn't a project type at all (`its: std::vec::IntoIter<_>` → `self.its.next()`, `matcher: Regex` → `self.matcher.is_match()`): the bare `next` / `is_match` attached to whatever local method shared the name. ripgrep had 279 self-edges on `main`; the issue lists three sites, all of this shape. (The issue's C++ control — "`Outer::run -> Inner::run` resolves correctly" — doesn't actually hold on `main`: `inner.h` is classified as C by the `.h` heuristic, so `Inner::run` never exists and the C++ repro self-edges too. That's #1592, fixed separately.) ## What this does Rust struct fields are not graph nodes, so the field's type can only come from the struct's declaration text. This follows the Go 2-hop precedent exactly (`matchGoFieldChainCall`, #1276), including its exclusivity rule: 1. **Extraction (TS walker + native kernel, identical, parity-tested):** a call whose receiver is `self.<field>` keeps the owner-field shape — `self.inner.run()` is emitted as `self.inner.run`. Deeper chains (`self.a.b.m()`), call receivers (`self.f().m()`), parenthesized receivers and bare `self` keep the bare name, exactly as before. 2. **Resolution (`matchRustSelfFieldCall`):** owner type = the calling method's qualified-name prefix (`Outer::run` → `Outer`); the field's declared type is read from the owner struct's **own declaration lines** (comment-stripped, line by line — same discipline as the Go helper); the method is resolved **and validated** on that type by `resolveMethodOnType` (confidence 0.85, `instance-method`). 3. **Exclusive:** when the field is declared with an external type, a generic parameter (`T`), a container that doesn't auto-deref (`Option`/`Vec`/`Mutex`/…), or can't be found, the ref **stays unresolved** — it never falls through to the bare-name strategies. That is the safe behaviour the issue asks for, and it is what #1276 already chose for Go. `rustFieldTypeName` looks through exactly the layers Rust's method-call auto-deref looks through: references (`&`, `&'a mut`) and the owning smart pointers `Box`/`Rc`/`Arc`. `Box<dyn Source>` yields the trait, whose method node the interface-impl synthesizer then fans out to every implementation. `Option<Inner>` is left alone — `self.inner.take()` is Option's method and must not become `Inner::take`. Why it stacks on #1596: the owner is taken from the method's qualified name, which for a generic/lifetime impl was the trait's name before that fix. ## Measured on ripgrep (110 `.rs` files, #1596 build vs this branch) | | #1596 | this PR | |---|---|---| | nodes | 4029 | 4029 | | `calls` self-edges | 279 | **146** (none of the `self.<field>` shape remain — 116 bare-receiver, 30 other dotted) | | `self.<field>.m()` calls resolved through a validated field type | — | **292** (`DecompressionMatcher::command -> GlobSet::matches`, `Parser::find_long -> FlagMap::find`, `Haystack::path -> DirEntry::path`, …) | | `self.<field>.m()` calls left unresolved | — | **417** — every sampled one is a std/container method: `self.commands.push`, `self.child.wait`, `self.pre.is_some`, `self.colors.clone`, `self.path_terminator.unwrap_or` | | `calls` edges total | 9150 | 8878 (the 272 removed are the former bare-name guesses for those 417) | The issue's three sites: `walk.rs:824` now resolves to `IgnoreBuilder::add_custom_ignore_filename` (was a self-edge); `walk.rs:1195` (`self.its.next`, `IntoIter`) and `globset/lib.rs:983` (`self.matcher.is_match`, `Regex`) are parked as unresolved instead of guessed. The issue's repro gives `Outer::run -> Inner::run` (`instance-method`, confidence 0.85) on both the kernel path and `CODEGRAPH_KERNEL=0`. ## Tests - `__tests__/extraction.test.ts`: only the single-hop `self.<field>.<method>()` call keeps the prefix; deeper / call / parenthesized / bare-`self` receivers and a local receiver are unchanged. - `__tests__/resolution.test.ts` (end-to-end, Cargo layout): the issue's repro → `Outer::run -> Inner::run`, no self-edge; an external field type (`std::vec::IntoIter`) with a local `next` decoy → no edge at all; `Box<Inner>` and `&'a mut Inner` resolve, `Option<Inner>` does not (even though `Inner` declares the method); a generic `T` field → no edge; genuine `self.run()` recursion keeps its self-edge; the #1588 repro's `UsesFile::go` / `UsesBuf::go` resolve to `FileSource::read` / `BufSource::read`, and a `Box<dyn Source>` field lands on `Source::read` with the synthesizer fanning out to both impls. - `__tests__/fixtures/kernel-parity/torture.rs` grows the receiver shapes; all 15 kernel parity suites pass against the rebuilt kernel (147 tests). - Full `npm test` on this branch: 189 files, 3187 passed, 9 skipped, 0 failed. Re-index after upgrading. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01LxZj6W6Y1SHXwvpT3uwJpK |