Commit Graph

1430 Commits

Author SHA1 Message Date
Colby Mchenry df3ce972f7 fix(cpp): a call on a data member of the caller's class reaches its type's method, wherever the class is declared (#2533)
Receiver inference read back through the call's file and the header of the
same name only, so a member of a class declared elsewhere — rocksdb's
DBImpl is declared in db_impl.h, its methods in db_impl_write.cc,
db_impl_compaction_flush.cc, db_impl_open.cc and more — was never read, and
calls on it (`versions_->GetColumnFamilySet()`, `mutex_.Lock()`) were
guessed by the receiver's name.

- Where the scan leaves the calling function, the name is looked up as a
  data member of the function's class before anything outside it
  (cppCallersMember): the class's own declaration lines wherever it is
  declared (the declaration in the caller's own translation unit, of
  classes sharing a name), then the classes it derives from through their
  base edges, skipping a base that depends on the template's parameters.
  A gtest `TEST_F` / `TEST_P` body is a method of a class derived from its
  fixture, so `db_` there is the fixture's.
- The member goes into the declaration record as the caller's own, with
  its class as the lookup site, so #2493's lookup, the library-type gate and
  #2440's held-type path apply. A member of a template parameter's type,
  or of an alias of one, and a name the scan saw an unreadable declaration
  of, stay as before. (isCppTemplateDependentType now also counts a std
  trait of a parameter: protobuf's `using MapFieldBase =
  std::conditional_t<kIsMutable, …>`.)
- cppDataMemberIn reads only declarations outside parentheses and not
  followed by an argument list (method declarations have no nodes, so their
  parameters are in the class's lines), skips nested functions and classes,
  follows block comments across lines, and blanks string literals.
- A member declared below an inline method that uses it, and a member of
  the class itself rather than of a class nested in it, are found the same
  way; cppExpressionType's own member fallback became redundant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:46:13 +00:00
Colby Mchenry 79cd737bec fix(cpp): a call on an auto variable or a chained call reaches the class C++ finds, not a namesake (#2532)
#2493 resolved calls on declared pointers, values and references by C++
lookup. Calls whose receiver type came from an initializer or a return
type still went by the type's last name:

- An `auto` local now has the type its initializer has, as written where
  that is declared: a cast or smart pointer written out
  (`std::unique_ptr<Iterator>(…)`, `static_cast<InstrumentedMutex*>(arg)`),
  `new` / `std::make_unique` / a construction of a class, or what a
  function or method is declared to return (`txn->GetIterator(…)`), with
  the initializer read to its `;` across lines. Its calls then go through
  matchCppDeclaredTypeCall from that site. A type that depends on a
  template parameter (`T*`, `std::shared_ptr<T>`) is left to the old path.
- A chained call `a->b()->c()` looks `c` up on the class C++ finds for the
  written return type of `b`, through a smart pointer too
  (matchCppChainOnWrittenType), before the last-name path.
- A type from outside the project gets no project method: a std/abseil
  library type (#2413's gate, now applied before the last-name match, so a
  namesake class like fmt's own `ostream` no longer wins), and a
  capitalized type under a namespace the project declares no type in
  (`absl::Status`, which reached `upb::Status::ok`).
- A bare call in a method types an `auto` local from the class's own or
  inherited method; a `template <…>` header followed by macros or
  specifiers still names its parameters; a trailing annotation macro
  (`T* PROTOBUF_NONNULL`) is no part of a return type; and memoized
  `auto` types are worked out with a fresh depth budget, so a deep chain
  that reads a local first no longer leaves it untyped for its other calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:42:38 +00:00
Colby Mchenry c19f0284f8 fix(cpp): a declaration inside a string literal is not a receiver's declaration (#2531)
C++ receiver inference reads back from a call for a line that declares
the receiver, blanking comments (cppCodeOf) but not string literals. Text
inside one was taken for the declaration:

- protobuf's upb generator emits C code from raw strings:
  `GenerateRepeatedSetters(upb::FieldDefPtr field, …)` writes
  `upb_MiniTableField field = $field_init$;` inside an `R"cc( … )cc"`,
  then calls `field.ctype()` on its own parameter. The scan found the
  template's line first, and the call got no edge (a C struct has no
  methods).
- rocksdb's log messages read as declarations: `"… in WriteBatch handler;
  …"` made `handler` a `WriteBatch`, and `"… remote compaction (…"` gave
  `compaction` the type `remote`, so calls fell to guesses by the method's
  name.

New cpp-raw-strings.ts reads a file once, when it holds `R"`, as the
compiler does (comments, string and character literals, raw strings with
their delimiters), and keeps the column ranges each raw string takes per
line. The receiver scan, in the file and its header, blanks those and the
contents of the line's ordinary string and character literals before
cppCodeOf. A character literal holding a quote (`'"'`), a digit separator
(`1'000`) and an `R"(` inside a comment don't hide the code after them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:17:33 +00:00
Colby Mchenry 85201e95b3 fix(cpp): a smart pointer's held class lacking a method gets no edge only when its bases are known (#2530)
matchCppHeldTypeCall (#2440) returned null (no edge) for a `->` call on a
`std::unique_ptr`/`std::shared_ptr`/`std::optional` whose held class
lacks the method, unless that class is a class template. #2493 found the
gap in the same rule for raw pointers and values: a base the index can't
link may declare the method. protobuf's `class FlatAllocator : public
decltype(ApplyTypeList<FlatAllocatorImpl>(…))` has no extends edge to
`FlatAllocatorImpl`, and rocksdb's `WritePreparedTxnDB` derives from an
alias each `#if` arm declares differently.

Both paths now share one verdict, cppLacksMethod: no class template, and
cppBasesKnown (every base on the way is linked to a class, or is a library
type). Otherwise the call goes on as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:16:09 +00:00
Colby Mchenry 80e4b459ff fix(sync): an import moves to an added file that outranks the one it found (#2529)
An import written without an extension that resolved to one file, `./button`
to `button.vue` while `button.ts` was missing, kept that edge and what it
binds when `button.ts` was added beside it, though the resolver now finds
`button.ts` first: sync only retried imports that had failed. A fresh index
links the added file.

When a sync adds a file, the module edges into the files an import of the
same name finds instead (`dir/name.*` under another extension, the folder's
own `dir/name/index.*` or `dir/name/__init__.py`) whose specifier now
resolves to the added file go back to the sweep, and their importers join
the added module's importers with that statement, so the names it binds are
re-resolved too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:05:25 +00:00
Colby Mchenry 4f3eaf33ae fix(resolution): component and scoped-alias imports link the module file (#2528)
An import statement's own module reference linked nothing in two cases,
even when its bindings resolved through the file: a Vue, Svelte or Astro
component's import (`isJsPathImportRef` covered only the script languages),
and a bare scoped package that a `tsconfig` path or a workspace package maps
to a project file (`isJsPathSpecifier` excluded `@scope/pkg`). So a barrel or
a side-effect module showed no dependency from those files, and sync's
importer discovery, which follows the module edge into a changed file,
never found them when a barrel they import through was deleted or added.

Both now resolve through `resolveImportPath`, as a script's path import
does; an outside scoped package still names no file. Component imports
written with an extension, which a basename match linked before, now link
through the resolver too. When a sync adds a file, it also retries the
failed imports that now resolve to it but wait under no key its path gives:
a scoped package parked under its last segment (`common` for
`@excalidraw/common`, mapped to `packages/common/src/index.ts`, whose keys
are `src` and `index`), under the module ceiling, and an import of the
importer's own folder or the one above (`.`, `..`), parked under no tail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:23:41 +00:00
Colby Mchenry 041db10146 fix(ts): a re-export imports its module, and sync follows barrels of barrels (#2526)
An `export … from './x'` statement emitted one `imports` reference per
re-exported name and nothing for the module itself; `export * from` emitted
nothing at all. So a barrel made only of `export *` lines had no dependency
on what it re-exports (no dependent for `affected`), and sync could not find
an `index.ts` that re-exports another barrel: when the inner one was deleted
or added, the outer one's importers kept their stale or name-matched edges.

A re-export now imports its module the way `import … from` does, in both
extractors (TS and the Rust kernel): an import node named by the specifier
and a module reference that links the barrel to the file it names. Sync's
importer discovery follows such modules to their own importers, carrying
the names each passes on (`export *`, a named or renamed re-export, an
`export * as ns`), as deep as the resolver chases a re-export; an ES
namespace import reads whatever its module passes on. A re-export's import
node does not count as a symbol for the "parse produced no symbols" warning,
so a barrel with an unparseable line still reports it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:56:50 +00:00
Colby Mchenry 7ba8eee4a7 fix(sync): imports through a re-exporting module follow it when it is deleted or added (#2512)
Deleting a module that re-exports what it imports (a package's
`__init__.py`, an `index.ts` barrel) left every file importing through it
linked to the symbols it re-exported: those edges point into files that
stay, so the removal cascaded none of them. When such a module was added or
came back, its importers kept the edges a name lookup had made in its
absence, or stayed failed; and in a large Python project the failed import
of the module itself stayed failed, because the failed-import retry skips a
stem with more than 500 imports waiting (Django's `test`, `models`).

Sync now re-opens, as pending references for its sweep, what the files
importing a removed or added module bind through those imports: before the
removed files are deleted, and after the retries for the added ones (with
their failed references and the module's own failed import). Importers are
found by the import edge into the file, by Python import nodes naming the
module or its package, and through what the module passes on (an import
inside a function has no node); in Python, a package or star import
re-exporting a changed module leads on to its own importers, as deep as the
resolver chases a name. All changed files are handled in one pass, with
importer edges and failed references filtered by the bound names in SQL.

Python and the ES-module family only; no schema or extraction change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:10:08 +00:00
Colby Mchenry a3fdb920a8 fix(sync): a Python import of a name a package re-exports links when the package appears later (#2507)
`from fastapi import FastAPI` reaches `fastapi/applications.py` through
`fastapi/__init__.py`, which re-exports it and declares no node named
`FastAPI`. While that `__init__.py` was missing, or before it gained the
re-export, the calls through the name failed and were parked under the
name, which the package file that then appears or changes never carries.
A fresh index linked them; a sync never did.

An unaliased from-import of a module in the project's own folders is now
parked under the module's key, as an aliased one is (#2501): a relative
import, or an absolute one whose dotted path, or the package holding it,
is a project folder at any depth. An outside package's names (`from
django.db.models import Q`) keep their tails, since on netbox they would
crowd 'module:models', 'module:urls' and others past the retry ceiling.
#2450's by-name lookup still finds every parked ref by its own name.

Two limits made the restored package's importers wait anyway:

- A module key gets a retry ceiling of its own, 5,000 rather than the 500
  a name tail has. A large population under a module key is every
  importer of a package that was missing, not noise: fastapi's 1,069
  `from fastapi import ...` refs, Django's 2,257 under 'module:urls'.
- The CG-33 rebind's per-name ceiling counts only the edges that existed
  before the retry. Relinking every `from django.urls import path` pushed
  `path` over it, so the `path` edges the sync's new definitions moved
  were never rebound; the retry's own edges are still rebound under a
  name that stays below it, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:19:11 +00:00
Colby Mchenry 5171a6aa9c fix(sync): a Python import alias links its module when the module appears later (#2501)
`from .models import User as UserModel` and `from . import helpers as h`
bind a name their module declares under another: `User`, `helpers`. Every
reference through the alias (the import, a call, a base class, the class
or function passed as a value) was parked under the alias, which no file
a sync adds or changes carries. So a module added after its importers were
indexed, restored after a delete, or given the name in a later edit never
relinked them, while a fresh index did. #2450 fixed the same gap for ES
modules.

Such a reference is now parked under its module's key. A Python import
names its module by a dotted path, counted from the importing file's own
package when it starts with dots: `.models` in app/main.py is
'module:models', `.` is 'module:app', `..pkg` the package above's
'module:pkg'. #2450's lookup still finds it by its own name, which is how
a fresh index links an alias its module never declares. A package's
`__init__.py` also carries the key of the package above it, as a module
file beside it does through its folder, so `from app.pkg import sub as s`
relinks when the subpackage appears.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:22:41 +00:00
Colby Mchenry fc0da38f0f fix(cpp): a call on a declared pointer, value or reference reaches the class its type names, not a namesake (#2493)
Receiver typing resolved a C++ call on a variable, parameter or member by
the declared type's last name: `Iterator* iter; iter->Valid()` became
resolveMethodOnType('Iterator', 'Valid'), which takes any class named
`Iterator` that declares `Valid`. facebook/rocksdb has 22 such classes,
and the `Iterator` the code means inherits `Valid`, `Next` and `key` from
`IteratorBase`, so nested namesakes like `MemTableRep::Iterator` took the
calls (google/leveldb's went to `SkipList::Iterator`).

A declaration the calling function or its class writes, called with the
operator it takes (`->` on a pointer, `.` on a value or reference), now
goes through the same C++ lookup #2440 uses for smart pointers'
held types (cppClassWritten from the caller's scopes, then the file's
`using`s, aliases followed) and cppMethodOf (the class's own method, else
one through its own base edges):

- An alias #2399 followed from the caller (function-local typedefs
  included) is the class it names, by qualified name. An `auto` loop's
  element type (#2492) is looked up where its range's type is written.
- A class found only as the one class of that name the file can see, a
  template parameter's type, a library type, or a type nothing visible
  declares: the call goes on as before.
- A class with no such method gets no edge, unless it is a class template
  or a base on the way is one the index doesn't link (a `decltype(…)`
  base, an alias each `#if` arm declares differently), which may declare
  it: then as before.
- Declarations read from elsewhere, or past one the scan couldn't read,
  are unchanged.

cppClassWritten now tries the file's `using`s before the global scope:
protobuf's upb generator writes `using google::protobuf::Descriptor;`
inside its namespace, and the PHP extension declares a global C struct
`Descriptor` in a header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:06:58 +00:00
Colby Mchenry 01ab45d068 fix(cpp): a range-based for loop over auto elements has the element type of its range (#2492)
#2439 typed a range-for variable when the loop names its type. A loop
over `auto` elements names none, so receiver inference kept scanning as
for an `auto` local with an unreadable initializer: it reached an earlier
declaration of the same name, often another variable's, or fell through
to the guess by the receiver's name. facebook/rocksdb's db_impl_write.cc
loops `for (auto cfd : cfds)` over a `const autovector<ColumnFamilyData*>&`.

The loop's range is now read from its header and typed as written,
template arguments and all, since the index keeps only a return type's
last name:
- a name the calling function declares, or a data member of its class
  read from the class's own lines (`versions_` from db_impl_write.cc);
- an `auto` local's initializer;
- `*x`, `x.m`, `x->m`, `this`, and calls whose declaration writes their
  return type, free or on the object's class (own or inherited);
- aliases followed with their template arguments (cppAliasedTypeWritten);
- the element: a standard or abseil sequence container's first template
  argument, protobuf's RepeatedPtrField and rocksdb's autovector's, or what
  a project class's begin() returns, dereferenced (ColumnFamilySet's
  iterator's operator* gives ColumnFamilyData*). Nullability annotations
  are dropped.

The element goes through the same declaration record as any declared
receiver, so the typed path, #2413's gate for a standard-library element
and #2440's held-type lookup for `->` on a smart-pointer element apply
unchanged. Classes are looked up with #2440's cppClassWritten, methods
through its cppMethodOf, and matchCppHeldTypeCall now reads its held type
through the shared cppStdHeldType.

A/B on main 8a5ba3d0, site-keyed: leveldb identical; fmt +1 / 3
retargeted; rocksdb -68 / +173 / 104 retargeted / 231 confidence only;
protobuf -13 / +25 / 7 / 59. Every removal and retarget was a wrong
guess; 4 of rocksdb's additions reach MemTableRep::Iterator through the
typed path's last-name lookup, as a declared `Iterator* it` does on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:19:01 +00:00
Colby Mchenry 8a5ba3d02c fix(go): a call through a parameter or local named like a standard-library package reaches the project's method (#2489)
isBuiltInOrExternal dropped every dotted Go ref whose first segment is in
GO_STDLIB_PACKAGES before any resolution, so a call through a parameter or
local of that name linked to nothing: gin's `context.AbortWithError(…)` in
`func(context *Context)`, harbor's `context.IsAuthenticated()` after
`context := NewSecurityContext(…)` and `log.Errorf(…)` after
`log := log.G(ctx)`, etcd's `printer.DBHashKV(…)` and `trace.Step(…)`.

Such a call now gets past the package list when #2448's scope reader finds
the name declared in the function around it, unless the declaration says it
holds no project method: a type from outside the project or a predeclared
one, or what an outside package's function hands out
(`scanner := bufio.NewScanner(r)`, `url, err := url.Parse(raw)`). #2478's
exemption for locals bound from a type assertion stays beside it.

matchMethodCall resolves these calls by the declaration alone, never by the
receiver's name, which fits dozens of a big tree's types (kubernetes'
`printer := NewTablePrinter(…)` is no kubeadm `Printer`):
- a written or asserted project type (goWrittenType) calls its own method,
  an interface's or a promoted one, or nothing;
- a value a project function hands out calls a method of that function's
  package: the type named like the variable, or the only one of the name;
- anything else calls only a method no other type declares, never one with
  a standard-library method name.
The scope reader also records `x := T{…}` / `new(T)` types and the
own-package function a `:=` calls. GO_STDLIB_PACKAGES moves to name-matcher.

A/B against main 1d3619d6, natural-key edge diffs with identical nodes and
no edge removed: gin +2, prometheus 0, etcd +44, harbor +60, kubernetes
+508, every one triaged correct. The first version, which let these calls
reach name matching, added 811 on kubernetes, 346 of them guesses by the
receiver's name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:50:55 +00:00
Colby Mchenry 1d3619d657 fix(c,cpp): a file in a unittests/ or foo_unittest/ folder is a test (#2484)
isTestPath knew `tests/`, `test/`, `__tests__/`, CamelCase source sets and
`foo-test/` modules, but not the folders LLVM, Clang, clangd, lldb, offload
and Breakpad keep their unit tests in (`unittests/`), nor glog's
`dcheck_unittest/`-style test programs: `/unittests/` holds `tests/` only
after a `t`, and the `foo_test/` form wants a separator right before `test`.
So a helper there with no test-like name (LLVM's CountCopyAndMove.h,
SelectionDAGTestBase.h, Breakpad's dump_analysis.cc) read as production code.

isTestPath now also counts a `unittests/` folder and a `foo_unittest/` or
`foo-unittests/` one. A bare `unittest/` stays production: CPython's
`Lib/unittest/` is the framework, and LLVM vendors googletest under
`third-party/unittest/`.

isTestSuitePath counts the same folders as suites, as it counts `tests/`:
their files build into test binaries and never link into the program, and
glog builds each `foo_unittest/` folder as a test program of its own (a
`foo-test/` module stays out, as shipped test support). That matters for
C/C++ because the suite rule's CamelCase file names skip `.cpp`, so on main
none of LLVM's `*Test.cpp` files were suites and production locals named
`Ctx`, `KV`, `V` or `OpInfo` linked to what a unit test declares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 18:09:06 +00:00
Colby Mchenry 1ce1167e6f test(js,ts): pin require().default and abstract classes to the declared default (#2481)
#2432 made a default import read the module's own `export default`
statement, and its merge pointed #2433's `require('./x').default`
fallback (esmDefaultExport) at the same reading. Nothing in the suite
pinned that path with a guessable helper above the default: #2433's
fixture keeps its helper unexported.

Two cases:
- React Native's AnimatedColor shape, an exported helper above
  `export default class AnimatedColor`, loaded with
  `require("./AnimatedColor").default` and
  `const { default: Color } = require("./AnimatedColor")`. With the
  first-exported guess both `new` calls instantiate the helper.
- `export default abstract class Repository` below an exported
  `connect()`, extended by `class Users extends Repository`. With the
  guess the `extends` edge is missing.

Both fail with e47cb258's resolver (before #2432). On main, putting the
guess back on the require path fails only the first, and dropping
`abstract` from the declaration pattern fails only the second.

The CHANGELOG's default-import bullet now says `require('./x').default`
is read the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 09:04:33 +00:00
Colby Mchenry a9781b0e05 fix(react-router): a lazy route links the page its loader picks (#2480)
A data-router `lazy` loader resolves to the route's properties, and many
pick their page from the module they load: luci-go milo/ui's
`lazy: async () => { const { TestTab } = await import('…/tabs'); return
{ Component: TestTab }; }`, `const { default: Component } = await
import('./pages/Login')`, `import('./x').then((m) => ({ Component: m.Page }))`,
`({ Component: (await import('./x')).Page })`, React Router 7's
`lazy: { Component: async () => (await import('./x')).Page }`. scanRoutes
recorded only the first `import('…')` as `lazy-import:<spec>`, and
resolution linked the module's default export, else its `Component`: a
different page, or nothing (milo/ui's tabs barrel has neither).

lazyRouteReference reads the loader: its `Component`, or the page its
`element` shows, followed through the loader's bindings (destructured
`await import`, a module binding, `.then` callbacks, `Promise.all`, the
v7.5 object form), and the one lazily imported export a guard shows
(`<AgeGate><FireworksPage /></AgeGate>`). A loader that returns the module
keeps `lazy-import:<spec>`; one this does not read (a helper's, `.then(convert)`)
keeps its first import, as before; one that hands over only a `loader`
renders nothing. `async lazy() { … }` methods, which scanRoutes skipped as
entries, are read too, and comments no longer hide an import's specifier.

The picked export is named the way Vue Router and Angular name a lazy
component, `import:<path>#<export>`, so sync's module-tail retry (#2422)
parks and retries it unchanged, and it resolves through #2436's
exportedComponent, barrels included. React is registered before Vue Router
and Angular, so it answers only its own routes' references (tsx or jsx,
which theirs never are). lazyModules (#2452) names the files a picked
export is read from, so a sync that moves it redraws the route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 08:47:19 +00:00
Colby Mchenry fa56256c49 fix(cpp): a call a method makes with no receiver links to its class's own or inherited method (#2479)
A C++ call written with no receiver, or through `this->`, inside a method
was matched by its name alone, so another class's method of that name won
by file proximity: protobuf's generated `Api::operator=` calling
`InternalSwap(&from)` reached `Any::InternalSwap`, `Any::InternalSwap`'s
inherited `GetArena()` reached `Arena::InternalHelper::GetArena`, and
googletest's `~linked_ptr() { depart(); }` reached `linked_ptr_internal::depart`.

matchMethodCall now resolves C++'s implicit `this` the way it resolves a
typed receiver: the calling method's class, then the classes it derives
from (its own base edges, through #2440's cppMethodOf), then the classes it
is nested in, with the overload the arguments fit. The call's shape is read
at its column (the extractor drops `this->` and some receivers it can't
spell). A free function, a parameter or local of the name, a dependent base
reached by a bare call, a namesake class in another translation unit and a
member a macro may declare all leave the call to the name strategies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 08:22:50 +00:00
Colby Mchenry 16bc05a208 fix(go): a call through a local bound from a type assertion reaches the asserted type's method (#2478)
Receiver inference read `v := T{}`, `var v T` and parameters, never a
local bound from a type assertion. So `flusher.Flush()` after
`flusher, ok := w.(http.Flusher)` went to whichever project method name
matching picked, and calls like etcd's `fi.Action()` after
`fi := s.kvindex.(*fakeIndex)` linked to nothing.

A Go local whose declaration in scope at the call binds the value of a
type assertion (`v := x.(T)`, `v, ok := x.(T)`, `var v, _ = x.(T)`) now
has the asserted type, found where Go finds it, as #2444 does for a call
made through the assertion itself (`x.(T).M()`). Calls, method values and
field chains through the local (`wr.job.Start()`) reach the type's own
method, its interface's, or one embedding promotes into it. The binding is
the one the scope reader from #2448 finds in scope at the call: an `if`
header's ends with its statement, and a nearer declaration in a block that
has ended does not hide it. A type switch's variable and a value that only
starts with an assertion (`x.(T).field`) resolve as before.

A type from outside the project, a predeclared type or an alias of an
outside type links nothing, as for the assertion itself. A type the index
can't tell is left to name matching, as before: a type literal
(`interface{ GetDeadline() time.Time }`) or a type declared inside a
function (grpc-go's `internalInfo` and `grpcstatus`). A local bound from an
assertion and named like a standard-library package
(`parser, ok := builder.(balancer.ConfigParser)`) is no longer dropped as
a call into that package.

On etcd, harbor, kratos and grpc-go, 358 call sites change. 216 gain a
link, 38 move to the asserted type's method and 89 keep their target, now
resolved through the asserted type. 15 lose a link that went through a
type from outside the project, or from a field read to a same-named
function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 07:15:20 +00:00
Colby Mchenry 0951105538 fix(react): a tag that passes type arguments renders its generic component (#2476)
The jsx-render pass read a tag's name only where whitespace, `/` or `>`
ended it, so a generic component's tag, whose type arguments follow its
name, was never read: outline's `<PaginatedList<Document> items={…} />`
and excalidraw's `<DropdownMenuItemContentRadio<"contain" | "overlap">`
left the parent with no edge to the component it renders. The tag pattern
now also ends a name at `<`, as a lookahead, so that `<` still opens the
next match, which `opensTag` (#2442) reads as a type argument, not a tag.

This lands after the package-import gate for JSX tags (#2475). On main alone the
same change also linked 5 package tags to project namesakes by name
(refine's react95 `<Select<number>>`, antd `<Table<IPost>>` and
`<Form<ILoginForm>>`; signoz's antd `<Form<…>>` ×2 to a styled `Form`),
which the gate declines.

Fresh before/after indexes with the gate as the base: 22 jsx-render edges
added and nothing else changed (outline 10 to `PaginatedList`, signoz 8,
excalidraw 2, mastodon 2). Each target is what the file's import or own
declaration binds, checked with the TypeScript checker with module
resolution. refine-examples, bulletproof-react, mantis, proshop_mern,
next-saas-starter and create-t3-turbo are byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 05:56:32 +00:00
Colby Mchenry 34183992ac fix(react,vue): a tag imported from a package renders no project namesake (#2475)
A JSX tag the file imports from a package (antd's `<Button>`, react-router's
`<Link>`, `@mui/material/Typography`'s default) renders that package's
component, which the index does not hold. `jsxChild` took a project node of
the same name anyway: a unique name without looking at the file's imports at
all, and a repeated one by language and order, because a package import never
resolves to a file. On SigNoz, antd and @signozhq/ui `<Button>`s rendered
`export const Button = styled(Link)` from a 404 page's styles.

- `jsxChild`: a component the file declares itself still comes first. Then a
  tag the file imports from a package binds nothing: the import's specifier
  passes `ctx.isOutOfRepoImport`, the test the name matcher applies to calls
  and values (no project file answers it, and a package.json on the file's
  way to the root declares it). An alias that reads like a package
  (`components/Modal`), an undeclared bare specifier and a workspace package
  keep the old rules.
- Vue templates (`vueTemplateEdges`'s `resolve`): the same rule for a
  PascalCase or kebab-case tag, a handler, and the composable a destructured
  handler comes from. A package-imported tag no longer falls back to a Nuxt
  auto-imported component either.

Fresh before/after indexes of 35 repos on main b635dd46: 6,550 jsx-render
edges removed, and one added: a same-file component (refine's win95
`AddButton`) that the 30-children cap had crowded out. A script that reads
each parent's import and the package.json chain, independent of the
resolver, confirms every removed edge is a tag imported from a declared
package. Nodes, refs, files and every other edge are unchanged, and 13 React
and 5 Vue repos are byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 05:53:29 +00:00
Colby Mchenry c0f45c7d1c fix(cpp): a -> call on a smart pointer or optional reaches the type it holds (#2440)
A C++ call made with `->` on a receiver the calling function or its class
declares as a `std::unique_ptr<T>`, `std::shared_ptr<T>` or
`std::optional<T>` now resolves on `T`: on the class C++ name lookup finds for
it where the call is written, or on a class that class derives from. Before,
receiver inference read the type as `unique_ptr`, found no method, and the
call fell through to the guess by method name: rocksdb's
`std::unique_ptr<Iterator> iter; iter->Valid()` reached
`ArenaWrappedDBIter::Valid`.

The held type goes through #2407's lookup (`cppClassWritten`, extracted from
`matchCppSupertype`), not `resolveMethodOnType` on its last name: rocksdb has
22 classes named `Iterator`, and the one the calls mean inherits its methods
from `IteratorBase`, so a last-name lookup reaches `MemTableRep::Iterator`.
The method is taken from that class or its bases through the class's own
extends edges (`getSupertypeNodes`). A template parameter, a library type or
an alias of one gets no edge; a type the project doesn't declare, or an alias
the lookup can't follow, leaves the call as it was.

The namespace-macro helpers move from name-matcher.ts to cpp-namespaces.ts
unchanged, so name-matcher can import cpp-supertypes without a cycle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:15:05 +00:00
Colby Mchenry 33c1436130 fix(js,ts): a default import is what export default declares, not the first exported function (#2432)
A default import resolved to the module's first exported function or
component whenever the `export default` statement wrote a declaration
(`export default function Vans()`, `export default class Store`): only a
separate `export default NAME` statement was read. A React Router 6.4+
data-router page exports its `loader` or `action` above the page
component, so `import Vans, { loader as vansLoader } from './Vans'` bound
the route `/vans` (and every call through `Vans`) to `loader`.

The resolver now reads the file's own `export default` statement from
comment-stripped code. A declaration it writes is found by position, a
binding it names by name, and an anonymous function, class or arrow
function is no node at all (the guess took an exported function beside it,
or one nested inside it). The statement also beats an exported styled/memo
component above it. Every line-initial statement is read and the first one
that names a node of the file wins, so template text that only reads like
a statement names nothing. Strings are not masked: a masker loses step on a
template whose `${…}` holds another template or a regex with a backtick,
and on outline it blanked the real statement below one. Only an expression
default (`memo(Card)`, `new Service()`), a binding the file doesn't
declare, or no statement keeps the old guess.

`require('./x').default` (#2433) reads the same statement first, so it
reaches the declared default too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:11:59 +00:00
Colby Mchenry b8b3eb9778 fix(cpp): a range-based for loop's variable has the type the loop declares (#2439)
C++ receiver inference read a declaration only when the declared name was
followed by `;`, `=`, `,`, `)`, `[`, `{` or `(`, so a range-based for's
`Type name :` was never read. In protocolbuffers/protobuf's conformance
runner, `suite->SetVerbose(…)`, `suite->RunSuite(…)` and
`suite->GetFailureListFlagName()` inside `for (ConformanceTestSuite *suite :
suites)` fell through to Strategy 3's guess by the receiver's name, and in
RocksDB `log->file()` on a `log::Writer` loop variable went to
`BlobLogWriter::file`.

A line the declarator regex doesn't match is now read as a range-based for
when its `for (` header declares the receiver before a single `:` (not
`::`), and the loop's variable is taken only when the call is inside the
loop's body: past the header, up to the `}` closing a braced body or the end
of a single-statement body, with comments and literals skipped. After the
loop, or in a later function, the same name is another variable, and the
scan goes on as before. A loop over `auto` elements has nothing to read and
keeps the existing fallback. Bit-fields and the scope operator are never
read as a loop's declaration.

A loop's declaration is recorded like any other (#2413's
noteCppDeclaration), so the std-type gate applies to
`for (const std::string& s : …)` too, and a loop that doesn't enclose the
call still marks the receiver as shadowed. With #2421, the 13 conformance
runner calls reach ConformanceTestSuite's methods at 0.9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:04:59 +00:00
Colby Mchenry b994365b12 fix(sync): a route rendering a same-file lazy value follows its module, and a new component gets its JSX edges (#2452)
A route that renders `const Docs = lazy(() => import('./pages/Docs'))` binds
to the component that module exports, and to the declaration while the module
is missing or exports none (#2400's declaredComponent). The reference had
resolved, so no failed-ref retry saw it, and CG-33's rebind reached it only
when the module's component shared the declaration's name: a sync that added
the module, gave it its default export or moved that export left the route
on its old answer. A data router's `lazy` route kept a moved default export
the same way.

FrameworkResolver.lazyModules names the modules a route's answer reads,
including the files a barrel forwards it through (#2436); React implements it
for lazy-import references and the same-file lazy values Pattern 1 reads.
After the CG-33 rebind, sync puts back the route references whose modules it
added or changed (reopenRouteModuleReaders), in the order their edges were
written, for the orphan sweep.

The jsx-render refresh read only each changed file's own content, so a new
component without JSX of its own never got the edge from `<Team />` in an
unchanged file. A sync whose definition delta holds a JSX-renderable node now
refreshes synthesis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:04:20 +00:00
Colby Mchenry cd97bb37d2 fix(context): call paths label synthesized hops like explore, not as events (#2428)
Two surfaces label a synthesized (dynamic-dispatch) `calls` hop:
codegraph_explore's Flow and dynamic-dispatch links plus the
codegraph_node trail (`dynamic: interface → impl @file:line`), and
ContextBuilder's "## Call paths", which `codegraph context` prints
(`→[…]`). Each kept its own list. The call paths' list stopped at
callback, react-render, jsx-render, vue-handler, http-client, queue-job
and socket messages, so every other synthesizer fell through to
`event ${m.event ?? ''}`: an interface-impl hop read
`→[event  @src/pdf-exporter.ts:5]`, and so did a C++ override, a redux
thunk, a gin middleware chain or a closure collection.

The wording now lives once, in src/graph/synthesized-hop.ts
(`describeSynthesizedHop`: a summary, the detail only the call paths
print, and the wiring site), as AGENTS.md asks of a derivation two
surfaces render. ToolHandler renders `dynamic: <summary> @site`, the
call paths `<summary><detail> @site`. Explore and the node trail print
exactly what they printed before; the call paths keep their wording for
the kinds they already labelled, including the queue name, a socket's
direction and the HTTP clause, and now name the rest the way explore
does. A bus event reads `bus event` there too, and the section's
footnote no longer says hops are marked `[callback/event …]`.

The helper also holds the wording #2414 gives a Go method a struct gets
by embedding (`metadata.promotedInto`), so that arm moves here too. The
unused long `label` form of ToolHandler.synthEdgeNote (last read by the
removed codegraph_trace) is dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 01:51:29 +00:00
Colby Mchenry 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>
2026-10-07 16:07:02 +00:00
Colby Mchenry e47cb258c1 fix(sync): a reference through an import binding links its module when the module appears later (#2450)
A default, namespace or aliased import binds a name its module never
declares: `import tagsController from './tag/tag.controller'`,
`import { Component as GroupsPageWrapper } from './groups_page'`. Every
reference through that name (the import, a call, `new`, a base class, a
JSX or template tag, an Express mount) was parked under the name, which
no file a sync adds or changes carries. So a module added after its
importers were indexed, restored after a delete, or given its export in a
later edit never relinked them, while a fresh index did.

Such a reference is now parked under its project module's key
(`module:tag`), which sync already looks up for every file it adds or
changes (#2422), and it is still found by its own name: a binding its
module never resolves is linked by that name alone, as vben's
`{ VbenFormSchema as FormSchema }` from a workspace package is. Schema
v15 adds the partial index that by-name lookup needs; its leading
`status` keeps the planner off idx_unresolved_status, which read 830K
rows on vscode's index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:50:09 +00:00
Colby Mchenry 78f5c8893a fix(react): a typed styled component is a component, not a constant (#2429)
`const Wrapper = styled.div<WrapperProps>` + a template literal came out as a
`constant`, so the JSX-render synthesizer skipped it: `<Wrapper>` got no
render edge, or bound to a same-named component in another file.
tree-sitter's tagged-template call takes no type arguments, so when the type
argument also reads as an expression the initializer parses as comparisons,
`(styled.div < WrapperProps) > template`, not as a call. (A type argument
that can't be an expression, `<{ open: boolean }>`, error-recovers into a
call on the tag, which #841 already took.)

reactComponentHoc now also takes that comparison chain: the leftmost operand
is the `styled` tag against a `<`, the rightmost is the template against a
`>` (`>>` when the type argument ends in its own `<...>`), and operators in
between belong to the type argument (`<A & B>`). Mirrored in the kernel's
TS/JS walker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:48:58 +00:00
Colby Mchenry 7a79654157 fix(cpp): a call written from the global scope links the declaration of exactly that name (#2445)
The resolver's name-existence pre-filter read no leading `::`: its `::`
branch wanted the separator past the first character, and the single-`:`
loop skips names holding `::`. So `::leveldb::RepairDB(…)`,
`::ROCKSDB_NAMESPACE::RepairDB(…)` or protobuf's `::_pbi::…` calls were
dropped before any strategy ran, unless a detected framework claimed them.
protobuf's got through only because it ships three Swift files: the
Swift ↔ Objective-C bridge claims any name with a `:` in it, which is why
#2415 left claimsReference() universal.

- The pre-filter checks a C or C++ name without its leading `::`.
- claimsReference() is asked only of the frameworks that resolve the
  reference's language (frameworksFor), like resolve() since #2415.
- matchByQualifiedName takes a global-qualified C/C++ name as exactly
  that qualified name. Its suffix match took a namesake nested in another
  namespace or class: with the pre-filter alone, 23 of the 24 edges it
  added on leveldb, fmt and rocksdb were wrong, mostly wrappers like fmt's
  mock `test::open` linked to the `::open(…)` they wrap, and protobuf's
  `::operator delete` went to `DynamicMessage::operator delete`. A
  declaration inside a namespace a macro opens (fmt's `struct pipe`) only
  looks global in the index, so it is skipped; step 5 still reads those.
- `::std::…` and `::memset` count as `std::…` and `memset` for the
  built-in check, and step 5 returns early for a single-segment name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:39:31 +00:00
Colby Mchenry 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>
2026-10-07 15:35:08 +00:00
Colby Mchenry 462def1f36 fix(explore): a Flow through an interface goes through the implementation the query names (#2453)
Explore's named walk is breadth-first, and the first callee listed claimed
each node it reached. An interface method calls every implementation at the
same depth, so when several of them call the next named symbol, the Flow went
through whichever implementation the index listed first, even when the query
named another one. On prometheus, `Engine.execEvalStmt Queryable.Querier
fanout.Querier NewMergeQuerier` went through the TSDB's `DB.Querier`; on gin,
once defined types count as implementers, `setter.TrySet` went through
`headerSource.TrySet` when the query named `formSource.TrySet`.

A node reached again from the same depth now takes the route that ends on
fewer unnamed hops, then the one through more named symbols, and keeps the
first otherwise. Of two equally deep named ends, the seed keeps the one whose
route passes more named symbols. The expansion order, the visited set and the
NAMED_VISIT_CAP bound are unchanged, so the walk makes the same callee lookups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:32:02 +00:00
Colby Mchenry cdf0681295 fix(cpp): a receiver declared with nested template arguments calls its own class's method (#2443)
normalizeCppTypeName cut each `<…>` at its first `>`, so a declared type
with template arguments nested in its template arguments kept debris:
`autovector<std::pair<int, FileMetaData*>>` read as `autovector >` and
`Striped<CacheAlignedWrapper<port::Mutex>>` as `Striped >`. No class has
such a name, so the call was never resolved on the receiver's class. A
capitalized debris name made isUndeclaredTypeName take the type for an
outside one and silence the call (rocksdb's `mutex_.Get(key)`), and a
lowercase one fell to a guess by the receiver's words (rocksdb's
`files_marked_for_compaction_.clear()` reached `CompactionInputFiles::clear`).

Template arguments are now stripped with their nesting by
cpp-type-aliases' depth-tracking helper, exported as
stripCppTemplateArguments; cpp-supertypes' identical private copy now
imports it. A `>` that closes no `<` is the end of a declaration begun on
an earlier line (`std::unique_ptr<BlobContents>>>& blob_reqs` under
`autovector<std::pair<…,`): a depth strip alone would read the argument's
type there, so such text names no type. The scan back to the receiver's
declaration then marks it shadowed (#2413), as it does for a declaration
whose type it can't read, so a member or earlier variable of the same
name found further up doesn't decide the call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:29:46 +00:00
Colby Mchenry ce78b63d6e fix(go): implementing an interface needs matching parameter and result counts (#2447)
goImplementsEdges matched method names only, so a struct whose same-named
method takes or returns a different number of values, which Go can never
accept, was linked as an implementation (etcd's watch cache as a
peerGetter), and enough such look-alikes filled the per-interface cap
before the real implementer (grpc-go's xDS TransportBuilder).

Each wanted method now also needs a declaration with its parameter and
result counts, read off the stored signature text; a signature that
doesn't read rules nothing out. A gRPC client interface (every method
takes `opts ...grpc.CallOption` last) also accepts its RPCs' server
arities, so `KVClient.Range -> kvServer.Range`, the bridge explore
follows from a client call to its handler, stays. Defined types (#2419)
are held to the same counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:29:41 +00:00
Colby Mchenry 16344af8ff fix(react-router): a lazy route links the page a barrel module forwards (#2436)
A data router's `lazy: () => import('@/pages/invocation_page')` loads the
folder's `index.ts`. When that file only forwards the page, with
`export * from './invocation_page'`, `export { default } from './page'` or
`export { Page as Component } from './page'`, the route linked nothing: the
module was read for an export of its own and none was found. The lookup now
follows the module's re-exports a few hops to the file that declares the
page, the way JavaScript resolves them (`export *` never forwards the
default, a name two `export *` modules both forward is exported by
neither), and reads a module's own `export { Page as Component }` clause.
`React.lazy` values in a route file, which go through the same lookup,
follow such barrels too.

On luci-go's milo/ui, 51 of 91 failed lazy route refs now link; the rest
load a file under `src/build/` (not indexed), export only a `loader` or a
`handle`, or pick a named export in an async loader. Sync links a page
added or edited behind its barrel the way a fresh index does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:25:59 +00:00
Colby Mchenry 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>
2026-10-07 15:20:33 +00:00
Colby Mchenry ca90841fb8 perf(resolution): read a bare-recorded call's receiver on its own line first — graph byte-identical (#2431)
bareCallReceiver joined the ref's line with the seven after it for every
bare-recorded call, then cut the text at the ref's column. On a minified
bundle that line is the whole file, so every call copied it: about 1 MB
per call on go-ethereum's graphiql.min.js, 9 GB over that repo's call
refs.

It now searches the ref's own line from the column first. Every match of
the call pattern starts with the name, so a match there with no earlier
occurrence of the name on the line is where the joined lines match first
too. With no match on the line (a call that continues on the next one),
or with the name earlier on it (an earlier `name[…` or `name<…` can run
past the line break and enclose the match), the joined lines decide as
before. The result is the same for every input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:17:00 +00:00
Colby Mchenry 2c807f5c13 fix(js,ts): a catch-all or node_modules path alias no longer makes package imports look local (#2437)
The out-of-repo import guard (`isOutOfRepoImport`) never fired in two
setups:

- A catch-all `paths` key. The guard started with `isExternalImport`,
  which treats an import as local when it starts with a root alias's
  prefix, and a `"*"` key's prefix is empty. On cord-field
  (`"*": ["./typings/*"]`), 169 imports of `Typography` from
  `@mui/material` bound to the project's own `Typography`.
- An alias that lands on disk but outside the index. `fileExists` falls
  back to `fs.existsSync`, which also answers for a directory or a file
  under `node_modules`. home-assistant's
  `"lit/decorators": ["./node_modules/lit/decorators.js"]` bound 4,916
  `@property()` decorators to an unrelated `property` field, and
  topcoder's `config` package landed on its `config/` folder.

The guard now skips the alias-prefix test (it already asks
`resolveImportPath` whether an alias maps the import to a file) and
counts that resolution only when it lands on an indexed file. Other
callers of `isExternalImport` are unchanged: resolving an import still
needs the prefix test so `"*": ["src/*"]` aliases resolve at all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:04:13 +00:00
Colby Mchenry c051ea0a0e fix(go): a dotted call is written through its own receiver, and a local named like an import is that variable (#2448)
goRefQualification read a dotted Go call's package qualifier from its line,
as the line's only `X.Name` spelling. A second spelling of the name left it
with none (`Digest:` beside `digest.Digest(dig)`, `"Log using Infof"`), and
another selector before it was taken instead (`klog` for `err.Error` in
`klog.Error(err.Error())`). It now takes the reference name's first segment,
the only one that can be a package.

A call through a parameter or local that takes an import's name (etcd's
`jwt, err := newTokenProviderJWT(…)`, testify suites' `suite` receivers) is
a call on that variable. A per-file scope reader finds the parameters,
receivers, results, `:=`, `var` and `const` names in scope at the call, so
such a call is no longer one into the package.

The variable's declared type, when its declaration writes one, decides what
the call reaches:
- An outside package's type, or a value an outside package's function hands
  out (`clock := clocktesting.NewFakePassiveClock(…)`), has none of the
  project's methods.
- A project package's type is resolved as the receiver's type. This also
  covers parameters declared through a package (`logger logger.Interface`).
- A field read through the local (`metadata.Type.String()`) keeps its own
  type.

A sync drops a file's scope reading with the other per-file memos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:03:52 +00:00
Colby Mchenry 1fb8691dfc fix(c,cpp): a file named like a Google unittest, foo_unittest.cc, is a test (#2434)
Google names C++ tests `foo_unittest.cc` (protobuf, Breakpad, glog,
Chromium), not `foo_test.cc`. The test-file check wanted a separator right
before `test`, so each one read as production code: search and explore
ranked it alongside the code it tests, `codegraph affected` never listed
it, and the resolver's test rules ran backwards for it. Production calls
landed on a unittest's own declarations (`stack_frame_entries_.size()` on
a unittest's `StackHelper`, a template's `AddressType()` on a unittest's
typedef), and a unittest's calls into test suites outside what it
includes (a Python `*_unittest.py` using a `tests/` fake) were treated as
production calls. #2421 already links a C/C++ unittest to the test
helpers it includes.

`unittest` and `unittests` join the separator-delimited test suffixes
(`foo_unittest.cc`, `foo-unittest.cpp`, `foo.unittest.js`), and a file
named so is a test suite to the resolver, as `foo_test.cc` is. A bare
`unittest.go` stays production code: promtool's runs rule tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 14:57:38 +00:00
Colby Mchenry 3de5bef0d2 fix(go): a call through a type assertion reaches the asserted type's method (#2444)
Both extractors record `srv.(KVServer).Range(ctx, in)` as a bare `Range`
call at the column where its receiver expression starts, as they record
any call through an expression. Resolution matched the name alone, so
every handler protoc-gen-go-grpc emits went to the `UnimplementedKVServer`
stub beside the `KVServer` interface (72 edges in etcd), and calls like
`c.Reader.(*pipe).Close()`, `p.(Pausable).Pause()` or
`v.(featuregate.MutableFeatureGate).Set(…)` linked to a namesake or to
nothing.

The call's chain is now read back from its column (operand, selectors,
assertions, calls and indexes, past strings, runes and comments, and on to
the next line after a trailing `.`). When the link of the call's name
follows an assertion, the call is a method of the asserted type, found
where Go finds it:
- a bare `T` or `*T` in the call's own package, or one it dot-imports;
- a `pkg.T` in the imported project package;
- its own method, the one its interface declares, or one promoted from a
  type it embeds, through the package-scoped lookup #2361 added; an alias
  is followed to the type it names (#2417).

A type from outside the project (`http.Flusher`), a predeclared one
(`error`), an alias of an outside type (`type Ctx = context.Context`, for
which the package-scoped lookup would fall back to matching by name) or a
type literal (`interface{ Flush() }`) links nothing. The check runs ahead
of the framework, import and name strategies, and ahead of the built-in
filter, which dropped a method named `close` or `copy` called through an
assertion. Two calls of one name in a chain share the column
(`b.(*Builder).Add(1).Add(2)`); both are taken for the one made through the
assertion, whose edge is there either way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 14:53:32 +00:00
Colby Mchenry 9a1d4bd91d fix(js,ts): require('./x').default reaches an ES module's default export (#2433)
`const X = require('./x').default` and `const { default: X } = require('./x')`
map to a named import of `default`. That is right for a CommonJS module that
sets the property itself (`exports.default = fn`, or the dual
`module.exports = X; module.exports.default = X`), but a module written as an
ES module and compiled to CommonJS sets it to its default export, which is no
named export. References through the binding stayed unresolved (bitwarden's
`new OsBiometricsServiceMac(...)`) or fell to name matching: the local
variable holding the module, or a namesake method in another package.

findExportedSymbolWalk now falls back to the module's ESM default export when
the named lookup finds no `default`. Only for JS-family refs, only in the
module the require names (`export * from` forwards no default), and only when
that module has an ESM default export at all: a single-file component, or an
`export default` statement. A CommonJS module's `exports.x = function`
declarations are flagged exported, so without that gate the
first-exported-function guess would invent a default for it.

bitwarden +2 edges (the two failed `instantiates` refs). react-native +872
-617: all 617 re-resolved at the same site (541 from the local require
binding to the real function or class, 47 from a namesake method in another
package to the right module, 28 metadata only, 1 from a type signature to the
implementation), 870 of 872 added edges on the declared default or what it
holds. express, eslint, mocha, koa, body-parser, fastify, topcoder,
proshop_mern, next-saas-starter and create-t3-turbo byte-identical. Dead-code
claims unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 14:49:21 +00:00
Colby Mchenry cdb994b417 fix(cpp): a class keeps its members past attribute macros the parser can't read (#2449)
tree-sitter-cpp has no preprocessor, so an attribute macro where it
expects a name or a type misparses the declaration around it. The C++
preParse already blanked one export macro before a class name, an inline
macro before a return type, and a lone macro line before a declaration.
It missed:

- several macros, or one with arguments, between `class` and the name
  (protobuf's generated `class PROTOBUF_EXPORT
  PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Any final : public Message`,
  rocksdb's `struct ALIGN_AS(64U) HandleImpl`), and a macro before a
  partial specialization's name;
- a macro between a pointer and the declared name (`const Descriptor*
  PROTOBUF_NONNULL descriptor()`), after a parameter list
  (`unknown_fields() const ABSL_ATTRIBUTE_LIFETIME_BOUND {`, leveldb's
  `LOCKS_EXCLUDED(mu_) {`), after a declared name (`int count_
  GUARDED_BY(mu_);`), or opening a declaration
  (`PROTOBUF_FUTURE_ADD_EARLY_NODISCARD absl::string_view name() const`);
- a lone macro line with a comment under it ({fmt}'s
  `FMT_BEGIN_EXPORT` above `// A generic formatting context ...`).

Every generated protobuf message class misparsed, so its members were
indexed as namespace-level functions; leveldb's annotated methods became
phantoms named after the annotation.

The new passes match on the code alone (comments and directives as
spaces, literals as placeholders), blank only the macro tokens, and keep
every offset. They run in the hoisted preParse, so files that now parse
clean go through the native kernel, at parity. The passes share one mask
per file: a pass that blanks nothing hands the next the same string, and
one that blanks hands it the mask blanked the same way.

#2426's brace-scope fixtures misparsed only through
PROTOBUF_FUTURE_ADD_EARLY_NODISCARD and ALIGN_AS(64U), which the preParse
now blanks. They now use macros it doesn't know (NODISCARD,
cacheline_aligned(64U)), with the same expected scopes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 14:45:30 +00:00
Colby Mchenry 883504d201 fix(go): a call on an embedded struct's method does not reach the embedder's method of that name (#2430)
Go has no inheritance. A struct that embeds another (`type Engine struct
{ RouterGroup }`) has an `extends` edge to it since #2397, and the
interface-dispatch bridge read every `extends` edge as an override, so it
linked RouterGroup.Use to Engine.Use. A call on a *RouterGroup only ever
runs RouterGroup's own Use, so a flow or impact walk through it went on
into Engine.Use and the code only the engine runs.

The bridge now skips a Go type's supertype edge unless it points at an
interface: only a call through an interface dispatches. Structs that embed
an interface keep their links, and so do the go-implements edges, so a call
through IRoutes still reaches both RouterGroup.Use and Engine.Use. Other
languages are unchanged.

Removed on gin / prometheus / etcd: 1 / 112 / 60 interface-impl edges, all
from a struct's method to a struct embedding it; nothing else in the graph
changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 14:42:45 +00:00
Colby Mchenry 4731149469 fix(react): a type argument or a name the component binds itself renders no JSX child (#2442)
The jsx-render pass reads tag names off a parent's source with a pattern,
and two kinds of name it read that way linked a same-named class or
component elsewhere:

- A name written only in a type argument or type parameter list:
  outline's `<PaginatedList<Document> …>` and `useState<User>()`, or
  bulletproof-react's generic `<Entry extends BaseEntity>(…) =>`. A `<Name`
  right after an identifier opens a type argument list (the rule #2420
  applies to the values a file declares, here for every name), and a
  `<Name extends …` opens a type parameter list.
- A name the parent binds itself by its first tag, a `const`/`let`/`var` or
  a parameter (name-matcher's `jsCodeBindsName`, now exported): outline's
  `const Content = variant === "dropdown" ? DropdownMenu.SubContent :
  ContextMenu.SubContent`, `(Widget, index) => <Widget />`. Such a tag
  renders only a component the parent declares inside itself, else
  nothing. The check runs only for names the parent also writes outside a
  tag. Destructured names still link by name, as for a destructured call.

Fresh before/after indexes on 13 repos (main ed199e60): 66 jsx-render
edges removed and none added; no other edge, node, ref or file changes.
Every removal checks out against the TypeScript compiler (38 names with
no JSX tag in the parent, 28 tags bound to a local); 64 of the 66 targets
were wrong, 2 right by luck. mantis, proshop_mern, next-saas-starter and
create-t3-turbo are byte-identical, and Screens is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 14:42:43 +00:00
Colby Mchenry 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>
2026-10-07 10:16:45 +00:00
Colby Mchenry 61f8b09cb8 fix(cpp): a receiver declared as a std or other outside type with a lowercase name calls that type's own member (#2413)
A C++ call on a variable, parameter or member the calling function or its
class declares as a namespace-qualified type the project does not declare,
with a lowercase name (std::string, std::vector, absl::flat_hash_set, ...),
no longer falls through to Strategy 3's guess by the method's name. leveldb's
`std::string saved_key_; saved_key_.clear()` reached Slice::clear, protobuf's
`std::string proto; proto.append(...)` LeftoverBuffer::append.

The gate applies to `.` on a value and `->` through a raw pointer; `->` on a
smart pointer, iterator or optional still reaches the element type. It skips
declarations it cannot trust: read from outside the caller's function and
class (including a class nested in the caller's), contradicted by the call
(`.` on a pointer), shadowed by an unreadable re-declaration (`auto`,
range-for, structured binding), unqualified, with unbalanced `<>` (the tail
of a multi-line declaration), or a `_t` name (scalars, tags, traits like
std::conditional_t). A `std::` name counts as the project's only when the
project declares it in `std` (vendored googletest's testing::internal::string).

Receiver inference now blanks comments before matching declarations, so
`// ... non-null imm_` no longer types leveldb's imm_ as `null`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:43:10 +00:00
Colby Mchenry 2f2afea88a fix(go): a defined type implements the interfaces its methods satisfy (#2419)
goImplementsEdges (#584) offered only structs as implementers. A Go defined
type over a slice, map, function or basic type declares methods the same
way (gin's `formSource map[string][]string` has TrySet, prometheus's
`staticDiscoverer []*targetgroup.Group` has Run, an adapter
`HandlerFunc func(...)` has ServeHTTP) and is extracted as a `type_alias`
that owns them through go-method-contains edges. So it never satisfied an
interface, and interfaceOverrideEdges, which walks only class, struct and
union, could not link a call through the interface to its methods.

Structs and defined types are now candidates together, in one canonical
(file, line) order. A defined type declares every method it has, so under
the per-interface cap it ranks with the structs that declare theirs, ahead
of promoted-only ones. interfaceOverrideEdges also walks Go `type_alias`
nodes. Every Go type's embeddings come from the batched prefetch (the
per-type lazy lookup for defined types is gone), and a type without a
method is dropped before the per-interface loop. A true alias
(`type A = B`) is not extracted, so it never implements anything itself.

iterateNodesByKindIn takes several kinds, interleaved in its one order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:32:39 +00:00
Colby Mchenry d348d301c1 fix(sync): a navigation call links its route when the route appears later (#2425)
A router's navigation call (`history.push('/login')`, `navigate('/login')`,
`router.push('/x')`, `goto('/x')`) names its route by path and its reference
by the router's method, so sync never noticed a route that changed in another
file. A synced index then kept answers a fresh index of the same files does
not have (CG-33):

- A call parked as failed while its route was missing never matched the #1240
  retry, which keys on the names the synced files define.
- A call bound to a catch-all, a parameter route or the other arm of a
  conditional kept that binding.
- A route renamed in place by runPostExtract kept the calls bound to it.
- Template links from a routes file that matches no synthesis trigger were
  never redrawn.

When a navigation router is detected, sync now reads the route nodes at its
first file change and again after runPostExtract. For each route on one side
only, it puts back the calls the routers' new `navigation` hook (method tails
and app scope) says that route can answer: the failed ones, and the
`navigates` edges a resolver made. The orchestrator's orphan sweep resolves
them, under the same 500-per-name ceiling, and the sync refreshes synthesis.
This covers re-extracted and removed files, postExtract renames and
cross-file table routes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:16:49 +00:00
Colby Mchenry 3bf6b8f1f5 fix(go): a type in a signature or composite literal links a type, never a method of its name (#2416)
A Go type position -- a parameter or result type, a composite literal's
type -- names a type of the package Go reads it from. Name matching (and
the instance-method guess, and the Express resolver reading Go refs) took
whatever declaration shared the name: etcd's
`func (ti *treeIndex) KeyIndex(keyi *keyIndex) *keyIndex` linked both types
to the method `treeIndex.keyIndex`, prometheus's `samples{...}` literals to
`sampleRing.samples`, and `config.URL{...}` from an outside package to
`scrape.Target.URL`.

gateTargetKind now hands every Go type position to goTypePositionTarget:
a method or function target moves to the type of that name in the
reference's own package (bare) or the imported project package (`pkg.T`),
or is dropped when there is none; a bare name that found another
package's type moves to its own package's type when it declares one
(prometheus's `prompb` builds its own `Histogram_CountInt`). A generic
method's receiver (`func (p *Pool[T]) Get() T`) now declares type
parameters for gateTypeParameter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:10:09 +00:00
Colby Mchenry 9d07864e65 fix(cpp): a class keeps its namespaces and enclosing class past code the parser misreads (#2426)
tree-sitter recovers from a construct it can't parse by inserting the token
it expected, and inside a class body that is often a `}`: an unknown macro
in front of a member (`PROTOBUF_FUTURE_ADD_EARLY_NODISCARD absl::string_view
name() const`) or in a class head (`struct ALIGN_AS(64U) HandleImpl`) closed
the class there, and every `}` after it closed the scope one level out.
Recovery also ran the other way, keeping a scope open past its own `}` once
an ERROR swallowed it. The walker scoped declarations by tree nesting, so
qualified names lost their namespace and class segments, or gained ones they
don't belong to: protobuf's `FieldDescriptor`, `MapFieldBase` and
`MutableRepeatedFieldProxyImpl`, fmt's `detail::` buffers (and
`fmt::format_int` put in `detail`), rocksdb's clock_cache classes and the
`Opts` structs nested in them, gtest's `PrettyUnitTestResultPrinter`.

Every such file parses with errors, and the kernel defers those to wasm, so
the wasm walker produced all of them; the kernel's walker has the same
tree-nested stack and reaches erroring trees only under the
CODEGRAPH_KERNEL_CCPP_ERROR_EXTRACT sweep hatch.

For a C++ file whose tree has errors, the walker now takes each
declaration's namespaces, and at declaration level its enclosing classes,
from the source's braces (languages/cpp-brace-scopes.ts: comments,
literals, raw strings, digit separators and preprocessor lines skipped,
each #if branch read from the braces open at the #if). A class-like node
ends at its body's `}`, and a class the tree glued into a namespace-level
declaration's type is walked as a class. A file whose braces don't balance,
and every file that parses cleanly, keeps the tree's scopes. Ported to the
kernel (ccpp/brace_scopes.rs) so the hatch stays parity-true: the same
erroring files diverge before and after (UTF-8/UTF-16 recovery), every
other one is byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:06:36 +00:00