Commit Graph
244 Commits
Author SHA1 Message Date
Colby MchenryandClaude Opus 5.5 e6527a8d34 fix(php): an untyped receiver keeps a unique method only when named for it (#2152)
PHP joins the untyped-receiver languages for the unique-name method guess:
`$x->m()` takes the one project method named `m` only when the receiver
shares a word with its owner, or names a class (the whole name, or its last
camel word: `$newRole` → Role) whose ancestry — extends + trait `use`,
namespace-aware — has the method.

A/B (edges removed / added): guzzle -359, BookStack -302, console -124,
Slim -124, monica -53. Removals are PSR-7 / framework / exception calls
bound to a test double or unrelated helper; the deliberate loss is calls
through receivers named after neither (`$loaner->loansAsLoaner()`).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:47:28 +00:00
Colby MchenryandClaude Opus 5.5 6eb08d68cf fix(csharp): a bare name reaches only members in scope (#2151)
A bare C# name (a `calls` or `references` ref with no dot, where the line
shows no receiver) can mean a member only of the types around it (outer
classes included), their base types (read from `class X : Base, IFoo`
heads, every partial declaration), or a static using: the file's
`using static`, any `global using static`, or `<Using Include="…"
Static="true"/>` in the .csproj / Directory.Build.props files above it.
Names outside any recovered type are not judged.

A/B (edges removed / added): Newtonsoft.Json -1394/+910 (enum-valued
properties now reach the enum type), AutoMapper -135/+58, eShop -117/+5,
serilog -20/+17, CleanArchitecture 0.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:41:06 +00:00
Colby MchenryandClaude Opus 5.5 b238e240e2 fix(objc): a message reached by name alone must be in reach (#2150)
* fix(objc): a message reached by name alone must be in reach

- `[self m]` / `[super m]` / `[[self class] m]` (receiver dropped by the
  extractor) reach only methods of the sender's hierarchy, read from
  `@interface Name : Super` plus a small UIKit/AppKit/MapKit superclass table
  for categories on system classes. When tree-sitter-objc loses an
  @implementation and its methods become functions, a same-file function,
  one in the file named after a hierarchy class, or any when the sender's
  class itself was lost, still counts; `[super init]` never lands on another
  file's function.
- C call syntax (`completionBlock()`) never means a method or property.
- Local receivers are typed from ObjC declarations (`FMResultSet *rs`,
  `(nullable FMResultSet *)rs`, block params), and objc joins the untyped-
  receiver languages for the unique-name guess (`weakSelf` / `strongSelf`
  count as self). Shared receiver words must be 3+ letters so class prefixes
  (`SD`, `NS`) don't match; Ruby/CFML unchanged but one wrong lobsters edge.

A/B (edges removed / added): SDWebImage -321/+47, fmdb -28/+85,
CocoaLumberjack -19/+10, AFNetworking -13/+1, MBProgressHUD 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(objc): a class or typed-property receiver keeps its inherited method

`[AllTypesObject objectsInRealm:…]` (a class method inherited from RLMObject)
and `[managed.anyDataObj containsObject:]` (a property declared
`RLMSet<…> *anyDataObj`) now keep the unique-name match when the receiver's
class — or one of the property's declared types — has the method's owner in
its hierarchy. realm-swift goes from -2562/+588 to -504/+529 vs main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:33:32 +00:00
Colby MchenryandClaude Opus 5.5 889b6a3526 fix(vbnet): a member access resolves through what the line names (#2149)
VB.NET's extractor keeps a call's last name only, so `Me.Panel.Controls.Add(x)`
and `New System.Drawing.Size(1, 2)` arrived as bare `Add` / `Size` and exact /
fuzzy matching took any class's member (even an enum case). The receiver is
now read from the line: a member candidate needs `Me` / `MyBase` / `MyClass`,
its own type or module as the last receiver segment, or a `(Of T)` type
argument (`GetService(Of Notifier).Notify()`). Genuinely bare names are
unchanged.

A/B (edges removed / added): SCrawler -3452/+6, remoteapptool -143/+0,
CompactGUI -31/+5, ViVeTool-GUI -1, TaskbarX 0.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:15:37 +00:00
Colby MchenryandClaude Opus 5.5 bd9741432f fix(cfml): a component method reached by name alone must be in reach (#2148)
- A bare call in a .cfc reaches only the component's own methods and those
  of the components it extends (extends="a.b.C" read from source, matched
  to indexed .cfc files by longest path suffix). Calls in .cfm templates and
  multi-line chain links (name not on the ref's line) are not judged.
- The untyped-receiver rule from the Ruby fix now covers CFML too: the one
  project method named `m` needs a receiver named after its owner. The word
  match also accepts the receiver's whole name (`cbsecurity` → CBSecurity)
  and ignores trailing digits (`executor1` → Executor); Ruby is
  byte-identical to main.

A/B (edges removed / added): coldbox -2429/+295, TestBox -255/+31,
fw1 -103/+4, cbsecurity -8/+5, cfdocs 0. Deliberate losses: framework-repo
calls through conventionally named, untyped receivers (ColdBox's `event.*`
→ RequestContext, TestBox's `variables.assert.*` → Assertion).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:09:18 +00:00
Colby MchenryandClaude Opus 5.5 df761e4596 fix(ruby): a method reached by name alone must be in reach (#2147)
Ruby types nothing, so two guesses produced most of a Ruby repo's wrong
call edges:
- `receiver.m()` through an untyped receiver took the project's one method
  named `m`. It now also needs a word shared between the receiver and the
  method's owner (`web_push_request.legacy_encrypt` → WebPushRequest);
  `self` / `self.class` are exempt.
- a bare `m()` inside a class took any class's `m`. It now reaches only the
  class's ancestry — superclasses and include/extend/prepend modules, read
  from source and resolved against the lexical nesting. Calls in module
  bodies and top-level blocks (specs, DSLs) are not judged.

A/B (edges removed / added): rubocop -4406/+262, mastodon -2642/+540,
jekyll -343/+10, lobsters -243/+4, sinatra -207/+2, devise -112/+3.
Deliberate losses: instance_eval'd DSL blocks inside a class method
(sinatra's own tests' `mock_app { get '/' }`), and helpers on a framework
class the repo reopens past an external superclass (devise setup_mailer).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 09:58:12 +00:00
Colby MchenryandClaude Opus 5.5 75976f9929 fix(kotlin): top-level functions resolve only where they're visible (#2146)
A Kotlin call's top-level candidate (a function, an extension function, a
property) must be in the caller's package, imported by name, or in a
star-imported package. Class members aren't judged: a lambda's receiver can
put any type's members in scope.

A function type with a qualified receiver (`(A.Builder.() -> Unit)?`) broke
the tree-sitter-kotlin parse badly enough to drop the enclosing class;
an offset-preserving preParse blanks the qualifier (hoisted, so the kernel
parses the same bytes — parity 0 diffs on Exposed, okhttp, nowinandroid).

A/B (edges removed / added): Exposed -5370/+4256 (1,318 R2DBC assertEquals
misroutes; DatabaseTestsBase's members and 250 extends/imports edges back),
koin -965/+960, okhttp -690/+675 (567 MockResponse to the imported
mockwebserver3 class), okio -98/+44, nowinandroid -14.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 09:44:16 +00:00
Colby MchenryandClaude Opus 5.5 0ff11b36e7 fix(dart): a bare call stays in the class hierarchy (#2145)
A receiver-less Dart call now reaches only a method of the enclosing class,
something it extends / mixes in / implements, or an extension on one of those
types, and the nearest one wins. The hierarchy is read from each declaration's
head (comments and type arguments dropped), since supertype edges don't exist
on the first pass. Body-less abstract members (extracted as owned functions)
are scoped too.

Mixin-application classes (`class A = B with C;`) were extracted as
`<anonymous>` with no supertypes; both the wasm extractor and the Rust kernel
now name them and emit their extends/implements refs (kernel parity verified
on bloc, riverpod, shelf, dio, getx).

A/B (edges removed / added): riverpod -1945/+517, shelf -785, getx -855/+18,
bloc -178/+95, dio -78.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 09:30:58 +00:00
Colby MchenryandClaude Opus 5.5 422db1866e fix(r): a test file's top-level variables stay in that file (#2144)
testthat runs each test file in an environment of its own, so a variable a
test assigns is invisible to the package and to other tests. ggplot2's
`c <- ggplot(…)` in test-facet-grid-.R was the one node named `c`, and took
2,455 of the package's `c(…)` calls (the sweep's top-depended symbol).

isVisibleAcrossFiles rejects an R variable/constant under `tests/` from
another file — except `helper-*.R` / `setup-*.R`, which testthat sources for
every test. (A first cut that limited every R call to function nodes lost
ggplot2's factory-defined `geom_point <- make_constructor(…)`, dplyr's
`summarize <- summarise` and shiny's aliases.)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 09:09:12 +00:00
Colby MchenryandClaude Opus 5.5 7ac8eefb85 fix(lua): a local is invisible outside its chunk (#2143)
A Lua `local` (variable or function) belongs to the file that declares it,
but cross-file name matching treated it like a global. kong's spec helpers
re-bind busted's globals (`local it = it`, `local assert = require
"luassert"`), and every other spec file's `it(…)` / `assert(…)` /
`describe(…)` linked to them — 15,776 edges; koreader's `local ipairs =
ipairs` in one module took the project's `ipairs` / `pairs` / `type` /
`tonumber` (2,329).

isVisibleAcrossFiles now reads a Lua/Luau variable or function's
declaration line and rejects a `local` one from another file (the reference
stays unresolved rather than taking a runner-up). Globals stay visible.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 09:04:08 +00:00
Colby MchenryandClaude Opus 5.5 dcbbd1687d fix(php): $this / self / static / parent calls stay in the class hierarchy (#2142)
Extraction records `$this->m()` under the bare name, so exact and fuzzy
matching took ANY same-named method: Drupal core's tests' `$this->assertEquals()`
(PHPUnit's) went to a comparator class 8,832 times, `$this->assertSession()`
to WebDriverTestBase 9,556 times, `$this->t()` to `Views::t` 6,811 times;
Laravel's `$this->assertCount()` to FilesystemAdapter 1,722 times.

A call written through `$this->` / `self::` / `static::` (or `parent::`) now
takes only a METHOD of the enclosing class's ancestry — `extends` and trait
`use`, read from source and resolved through the file's `namespace` / `use`
imports (aliases included) to fully qualified classes — or a subclass's
(a base calling what a subclass defines). Past an ancestor outside the
repository (Orchestra's TestCase) a repository trait's method still counts,
an unrelated class's never does. Inside a trait, `$this` is the using class,
so trait bodies are not scoped. Multi-line `use A,\n B;` trait lists parse.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 08:58:39 +00:00
Colby MchenryandClaude Opus 5.5 ca3bb444f7 fix(resolution): case-insensitive names pass the prefilter; PHP => fn() is a bare call (#2141)
- The name prefilter checks a case-SENSITIVE set of known names, so in PHP,
  Pascal, CFML, COBOL and VB.NET — whose identifiers ignore case — a call
  written in another case (`formatprice()` for `FormatPrice`, TestBox's
  `ToMatchWithCase()`) was dropped before any strategy ran. Those languages
  now also check a lowercase set (built lazily, only when asked).
- `isBarePhpCall` took any `>` before a name for `->`: every call after `=>`
  in a PHP array (`'total' => count($items)`, `'by' => user()->id`,
  `'label' => trans('…')`) was a member call, and found a same-named method
  — 166 wrong edges on BookStack (`trans` → LocaleDefinition::trans,
  `new Response` → ImageStorageDisk::response, `url()`, `config()`, `count()`).
  It now requires `->` / `?->` / `::`.
- A fuzzy `instantiates` never names a method.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 08:31:10 +00:00
Colby MchenryandClaude Opus 5.5 a3d675ef4d fix(vue): Options API methods, computed, watchers and hooks are symbols (#2140)
A `<script>` block goes through the TS/JS extractor, which extracts no
symbol for a method in an object literal — so every Vue 2 component, and
every Vue 3 one not on `<script setup>`, had no `handleLogin`, no
`mounted`: their calls, their `this.$router.push`, and the template's
`@click="handleLogin"` all belonged to the file.

`vue-options-api.ts` reads the options object the default export is
(directly, or via defineComponent / Vue.extend / Vue.component) and names
each function in it — `methods`, `computed` (incl. get/set objects),
`watch` (incl. `{ handler }`), `data`, `setup`, lifecycle hooks, Nuxt 2's
asyncData/fetch — as `method` nodes owned by the component; the Vue
extractor re-attributes the references and edges written inside each.

A component's own method is only reached as `this.m()` in that component:
the member-call and exact-name strategies no longer hand it
`e.preventDefault()` (ImageCropper's `preventDefault` method took every
event's call), `this.editor.setValue()`, or `this.$refs['input'].click()`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 08:22:09 +00:00
Colby MchenryandClaude Opus 5.5 66d5dc3641 fix(resolution): fuzzy matching is exact-case outside case-insensitive languages (#2139)
The last-resort fuzzy match looks names up in a lowercase index — right for
PHP, Pascal/Delphi, CFML, COBOL and VB.NET, whose identifiers ignore case,
and wrong for every other language. jsoup's 1,844 `@Test` annotations
decorated a `CharPredicate.test` method; commons-lang's `new BitSet()`
instantiated a `bitSet` field accessor 64 times; mall's `new Info()` an
`info()` endpoint; gson's `Method` type a test's `method()`; fmt's
`#include <optional>` gtest's `Optional`.

This generalizes the per-language exact-case rules (Rust, Python, JS) into
one: outside CASE_INSENSITIVE_LANGUAGES a fuzzy candidate must match the
reference's exact name. The cross-language fuzzy-gate test now builds its
winner from an exact-case name.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 08:10:26 +00:00
Colby MchenryandClaude Opus 5.5 6a02dcaa76 fix(react-native): event names held in constants join the native↔JS channel (#2138)
The rn-event-channel pairs a native emit with a JS listener on the event's
LITERAL name. Most libraries name events through constants — NetInfo
listens with `addListener(PrivateTypes.DEVICE_CONNECTIVITY_EVENT, …)`, many
emit with `.emit(EVENT_NAME, …)` or `sendEventWithName:kEvent` — so their
channels were empty.

Identifier / member-chain event names are now read to the literal their
declaration holds (JS/TS const & enum, Java `static final`, Kotlin
`const val`, ObjC `NSString *const` / `#define`, Swift `let` / enum case),
scoped as the language scopes them: a bare name in the same file or behind
an import, `Owner.NAME` inside Owner or a namespace import's file. The
handler of a constant-named listener is a function in the same file, else
the enclosing one. A first cut that read constants by name alone turned a
`.emit(eventName, …)` parameter into some test's `eventName = "pong"`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 07:57:43 +00:00
Colby MchenryandClaude Opus 5.5 6f798dce6b fix(react-native): Paper requireNativeComponent views reach native; JSX via default imports (#2137)
- `requireNativeComponent('X')` (Paper) now emits the same JS `component`
  node a Codegen spec does, so the Fabric pass links it to the native view /
  manager classes; the extractor also runs on `.js` / `.jsx` spec modules
  (segmented-control's spec is Flow `.js`). react-native-maps' `AIRMap`
  gains its iOS implementation.
- A JSX tag that names no node is the file's DEFAULT import of a module's
  one component: `<RNCSegmentedControlNativeComponent>`, and element-plus's
  test `<Autocomplete>` for `autocomplete.vue` (whose component node is
  `autocomplete`). A named import never takes a barrel's one component.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 07:49:17 +00:00
Colby MchenryandClaude Opus 5.5 a4f9820233 fix(resolution): a super call in an override is not a self-call (#2136)
Extraction keeps `super.didMoveToWindow()` under the bare method name, so
every strategy resolved it to the enclosing override itself. Across the
bridge sweep that was 139 self-edges on Charts, 142 on commons-lang, 101
on BookStack, 92 on realm-swift, 82 on react-native-svg — lifecycle
overrides and delegates drawn as recursion.

`gateSuperSelfCall` declines a `calls` result that targets the calling
node when the call site is written through `super` / `base` (C#) /
`[super …]` (Objective-C) / `parent::` (PHP) / `super().` / `super(C, self).`
(Python). Plain recursion keeps its edge.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 07:48:48 +00:00
Colby MchenryandClaude Opus 5.5 72a9346b12 fix(expo-router): +api files are endpoints; the nearest router manifest decides (#2135)
- `app/blog/og-image/[post]+api.ts` was a screen named
  `/blog/og-image/[post]+api` (evanbacon.dev). An Expo API route is now one
  endpoint per exported HTTP method (`GET /blog/og-image/:post`), bound to
  its handler, the way Next.js `route.ts` files are.
- The per-app framework gate (#2129) took ANY enclosing manifest's
  declaration: react-native-true-sheet's root declares expo-router for its
  example app, so its `docs/` Next.js app got 16 Expo screens (`/layout`,
  `/page`, `/api/search/route`, …). Walking up from a file, the first
  manifest that names any gated framework now decides.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 07:34:14 +00:00
Colby MchenryandClaude Opus 5.5 67744468d2 fix(resolution): Expo module bindings, imported receivers, TS type-only imports (#2134)
- Expo Modules: a JS call on a `requireNativeModule('N')` binding — typed
  (`<CameraNativeModule>`) or not, local or imported (expo-camera's
  `import CameraManager from './ExpoCameraManager'`) — resolves to module
  N's declared function, Swift first and Kotlin beside it, else to the
  member on the binding's declared type. It went to the same-named static
  method making the call.
- A JS/TS member call on an import binding never picks a method declared in
  the calling file, and ruling those out never manufactures a unique guess.
- `import type { X }` was parsed as a default import named `type` (every
  type-only import bound `type`), and `{ a, type B }` bound `type B`.
- A name the file binds from an out-of-repo package names nothing in the
  project, for every reference kind, and JS/TS fuzzy matching is exact-case:
  trpc's 402 `Record<…>` references went to a `record` property, element-plus's
  121 `mount` imports from @vue/test-utils to a test helper.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 07:28:28 +00:00
Colby MchenryandClaude Opus 5.5 cec041d241 fix(angular): Nx workspaces, barrels, class-constant paths and root-relative navigate (#2133)
From the router sweep:
- angular-spotify had 12 routes all named `/` and no navigation. Its lazy
  routes are `async () => (await import('@ws/home')).HomeModule` through an
  Nx library's `src/index.ts` barrel, which the mount pass never looked
  through; and every lib is its own `src/`, so each routes file got its own
  route table and no template's `routerLink` found the screen it names.
- jira-clone had 3 routes and no navigation: its issue route is
  `issue/:${ProjectConst.IssueId}` (a class `static readonly`), its root
  redirect lives in `app.routes.ts`, which only mounts, and
  `navigate(['project', 'issue', id])` has no leading `/`.

Now: lazy mounts follow `export … from` barrels (and an NgModule's routing
imports); `(await import(x)).M` is read; route constants may be class
statics (strings or objects), enum members, or live behind a barrel, and a
template-literal path takes constant holes; a routes file that only mounts
contributes its redirects at its mount prefix; the route table is keyed by
the `angular.json` / `nx.json` workspace; and `router.navigate` /
`createUrlTree` without `relativeTo` resolve a bare first command from the
root, as Angular does (template `routerLink` stays relative).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 07:06:23 +00:00
Colby MchenryandClaude Opus 5.5 4423891e76 fix(react-router): v5 <Redirect to> and styled(Link) wrappers navigate (#2132)
react-boilerplate's header links are `export default styled(Link)`…``,
used as `<HeaderLink to="/features">`; takenote's guards render v5's
`<Redirect to="/" />`. Neither tag was read, so both apps had routes and
no links between their screens.

The link synthesizer now reads `<Redirect to>` (navMethod `redirect`) and
any styled-components / emotion wrapper of `Link` / `NavLink` — declared in
the file, or imported from a file that declares or default-exports one.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 06:55:41 +00:00
Colby MchenryandClaude Opus 5.5 b62da73550 fix(sveltekit): route names drop (group) folders and parameter matchers (#2131)
A `(group)` directory shares a layout and is never part of the URL, and a
parameter's `=matcher` checks its value. Kept in the name, shadcn-svelte's
`src/routes/(app)/(layout)/blocks/+page.svelte` was `/(app)/(layout)/blocks`,
so no `goto('/blocks')` or `<a href="/blocks">` ever reached it and its
Screens showed no navigation at all.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 06:50:41 +00:00
Colby MchenryandClaude Opus 5.5 54a1698caa fix(vue-router): admin-template route tables, children and layouts; Nuxt routes only in Nuxt (#2130)
vue-element-admin, vue-admin-template and vben had no routes: their tables
are named arrays (`export const constantRoutes = [...]`, `const routes:
RouteRecordRaw[] = [...]`) or per-module objects (`const tableRouter = {…}`)
handed to `new Router(...)`, and most of their screens are `children`.

The Vue Router reader now reads those tables in any file that builds a
router, imports vue-router or lives in a router/ directory; joins children
onto their parent's path (a parent is a screen only when no child claims
its address and it doesn't redirect); binds a lazy view by the FILE it
imports (all of vue-element-admin's are `…/index.vue`), through an alias the
resolver can't follow by the one file in the app with that path; links each
screen to its parents' components as layouts; and takes `this.$router.push`.

Nuxt's file routes move into their own `nuxt` resolver, detected only in a
Nuxt app: halo's plain-Vue console got 30 made-up screens from its `pages/`
folders. A Nuxt app at the repository root now gets its routes, and a
top-level `pages/index.vue` is `/`, not `/index`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 06:46:26 +00:00
Colby MchenryandClaude Opus 5.5 92e14eda40 fix(extraction): a file-based router reads only its own app in a monorepo (#2129)
create-t3-turbo, tamagui's starter and react-native-reusables keep an Expo
app beside a Next.js app. Expo Router read the Next app's `app/` folder as
its own, so `layout.tsx` became a `/layout` screen, `page.tsx` a `/page`
one, `_components/posts.tsx` and `api/auth/[...all]/route.ts` screens too,
and a shared `packages/app/` library's files became screens as well.

A resolver can now name `appDependencies`; its extractor runs on a file
only when that file's package.json or an enclosing one declares one of
them. When no manifest in the project declares any, detection found the
framework by other evidence and it runs everywhere, as before. Expo Router
names `expo-router`, Next.js `next`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 06:32:33 +00:00
Colby MchenryandClaude Opus 5.5 1ef1eb886c fix(resolution): Vue, Svelte and Astro calls follow the JS/TS rules (#2128)
Calls in `.vue`, `.svelte` and `.astro` files carry their own language, so
the JS/TS rules from #2120 never applied to them: halo's `t('…')` from
`useI18n()` went to an interface property `t` (562 callers) and `ref(…)`
from `vue` to a local `ref` (523).

"Outside the repository" is now a package the importing file's package.json
chain (or a Deno import map) declares, a Node built-in, `@std/…`, or a
framework's virtual module. An alias the resolver can't follow — SvelteKit's
`$lib/…`, a nested Nuxt app's `~/…`, a nested app's own `@/…` — stays the
project's, and a `workspace:`/`file:`/`link:` dependency is in the repo.

SFC files also get the JS built-ins (`fetch`, `Error`, `Map`), and a file's
own import of a built-in's name (`import Map from './Map.svelte'`) is the
import. Untyped three-segment chains (`api.groupReports.getAll()`) keep
resolving in SFCs, where they reach their API client class.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 06:22:18 +00:00
Colby MchenryandClaude Opus 5.5 8987be4254 fix(resolution): a bare Java call reaches its class, supertypes or static imports (#2126)
Found by the README-wide sweep: halo's most-depended-on symbols were an
`EmailVerificationService.verify` (1,038 — Mockito's `verify(mock)`) and a
builder's `eq` (845 — ArgumentMatchers `eq`); retrofit's a test helper's
`assertThat` (1,357 — Truth); mall's a DTO's `hashCode` (Object's).

A bare Java call (`verify(x)`, `helper()`, `this.x()`, `super.x()`) now has
only method candidates declared on a class around it, on one of that class's
supertypes (read transitively from the declarations' `extends` /
`implements` — the resolved edges don't exist yet on the first pass), or
imported statically (`import static a.B.m;` / `a.B.*`). Memoized per type
and per file.

A/B vs main (edge diffs, every change classified): halo −2,103/+145,
retrofit −1,380/+18, commons-lang −202/+87, jsoup −92/+45, mall −76,
gson −7/+4. Gains are the right target where one exists: halo's `and` /
`equal` / `isNull` through `import static …Queries.*`, jsoup's `attr(…)`
through `LeafNode`, commons-lang's `addExact` on its own class instead of a
nested MathBridge, retrofit's `getRawType` through `CallAdapter.Factory`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 06:00:17 +00:00
Colby MchenryandClaude Opus 5.5 99209b6f3d fix(resolution): a Python call reaches a method through its receiver, never by name alone (#2125)
Found by the README-wide sweep — Django/Flask/FastAPI apps' most-depended-on
lists were test and serializer methods: netbox `UserConfig.all` (3,610 from
`X.objects.all()`), `PortSerializer.create` (2,093 from `.objects.create`),
a test-local `FakeQuerySet.count` (1,910); healthchecks `AuthTestCase.get`
(880 from `Channel.objects.get()`), `ProfileAdmin.login` (702 from
`self.client.login()`); mealie `_FakeHTTPResponse.json`.

Python keeps a call through a receiver it cannot name (`User.objects.get`,
`self.client.login`, `request.POST.get`) as the bare `get` / `login`, and
exact-name matching then took any class's method by proximity. The call's
shape is now read at its column (the call's start):
- `bare` (`get(1)`): not a method — Python has no implicit self — and not a
  project symbol when the file takes that name `from` a module the project
  doesn't contain (`from django.shortcuts import render`);
- `chained`: only a member of what the chain names last — a method of a
  class of that name (case/underscores ignored: `self.store` → `Store`,
  `self.user_service` → `UserService`) or a function/class in a module of
  that name (`app.helpers.slugify()` → helpers.py);
- `self.x()` / `cls.x()`, and chains split across lines: unchanged.
Fuzzy matching is case-exact for Python (`dir(…)` ≠ class `Dir`).
Lexical reachability now walks every enclosing scope, so a method of a
class declared inside a function is only reachable in there (memoized per
candidate).

A/B vs main (edge diffs, every change classified): netbox −18,091/+73
(indexes 8s → 5s), healthchecks −3,354/+361, pytest −2,950/+105,
mealie −1,802/+6, allauth −1,086/+57, DRF −943/+50, flask −167/+17,
httpx −147/+24. Gains are precise member links (`self.channel.notify` →
Channel.notify, `item.stash.get` → Stash.get, `config.cache.get` →
Cache.get, `self.provider.verify_token`); the one deliberate loss class is
calls through an instance whose type needs inference (`flask.g.setdefault`
in flask's own repo). excalidraw, typeorm, gson, okio, AutoMapper, gin and
hono byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 05:52:25 +00:00
Colby MchenryandClaude Opus 5.5 ddc03f7c79 fix(resolution): a bare Rust name reaches only what is in scope (#2124)
Found by the README-wide sweep: ripgrep bound 469 `Some(x)` calls to its
own `EncodingMode::Some` variant and 536 `Ok(x)` to `ParseResult::Ok`;
serde bound its `Ok(…)`/`Result<…>` to the `struct Ok` / `struct Result`
its macro-hygiene test declares (845 on one); tokio 924 `Ok` onto
`TransitionToIdle::Ok`, 290 `drop(x)` onto a `drop` method.

For a name that is bare IN THE SOURCE (a `::` path is kept by its last
segment and is not a prelude lookup — checked at the reference's column):
- an enum variant is in scope only through a `use` of it (and its enum) or
  of its enum's `*`, and never names a type;
- a prelude name (Ok/Err/Some/None/Result/Option/Box/Vec/String/drop/…)
  reaches a project item in another file only through a project `use` of
  that name or a glob of that item's own module (`use crate::glob::*`,
  `use super::*` for the parent) — `use std::…` trees don't count, and the
  `use` scan runs on comment-stripped text (a doc comment's "…use the
  Option…" once read as an import);
- fuzzy matching is case-exact for Rust (`Bytes` ≠ method `bytes`,
  `Ok` ≠ fn `ok`).
Applied as a candidate filter (so the in-scope item can win) and at
resolveOne's seam — a Rust framework resolver's `Ok(x)` → `struct Ok`
construction bypassed name matching.

A/B vs main (edge diffs, every change classified): ripgrep −1,462/+0,
serde −2,072/+40, tokio −1,820/+2, clap −752/+7, axum −397/+4, bat −45/+0 —
losses are Ok/Some/Err/Result/Option/Vec/drop and same-named variants
pinned on project items; gains are pre-existing same-name ambiguity
(serde's two `Content` enums).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 05:34:51 +00:00
Colby MchenryandClaude Opus 5.5 c5ba68076a fix(resolution): type parameters, def-local types and Scala scoping (#2123)
Found by the README-wide sweep: cats' most-depended-on symbols were an
`implicit def A` (4,916) and a `case class B()` declared inside a test
method (3,573) — every type parameter `A`/`B`/`F` in the library bound to
them; getx's `T` to a constant `T`; serde's `E`/`T`/`D`/`S`/`F` to test
structs.

New seam gate (type-parameters.ts, applied in resolveOne so every strategy
obeys it):
- A resolved TYPE reference whose name an enclosing declaration declares as
  a type parameter is that parameter: read from the heads (the index records
  none) — after the declared name (`class Foo<T>`, `fn f<T>`, `func F[T any]`,
  `def f[F[_]: Monad, A]`), before it detached from a type (`<T> T max(`,
  `fun <T> f`), a C++ `template <…>` above; annotations (`@sp(Int) A`),
  variance and keywords skipped; a `[…]` list counts only [] and () so
  Scala's `B >: A` bound does not close it; lists up to 40 lines.
- Scala: a bare name a `def` takes as a value parameter (`(f: A => B)`,
  `(implicit G: …)`) is that parameter — cats bound 846 `f(a)` calls to a
  case class's `f` field. Class parameters are fields and are left alone.
- Scala: a BARE (in the source) type reference reaches a member of another
  type only from inside it, a subtype, or a file importing its members;
  otherwise the in-scope type of that name is taken when one fits (cats'
  `trait FlatMap`, not `Eval`'s nested `FlatMap`; sttp's `trait Backend`).
Name matcher:
- A type (class/struct/enum/interface/trait/alias) declared inside a
  function is only reachable from inside it, like nested functions (#1230).
- A capitalized Scala type position never names a method.

A/B vs main (edge diffs, every change classified; each arm's init exit code
checked — an earlier cut crashed resolution on a Scala method named `*`,
now pinned by the test): cats −25,281/+179 (the gains retargets to the
in-scope FlatMap/State; the real types Shown and IsEq kept 5/5 and
315/315), sttp −1,237/+23, getx −430, serde −446/+9, typeorm −632/+152
(`EntityTarget<Entity>`'s type parameter; codemod fixtures' function-local
classes), gson −9; okio/fmt ±2, Newtonsoft.Json and gin byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 05:23:22 +00:00
Colby MchenryandClaude Opus 5.5 e453e820a8 fix(resolution): a C#/VB.NET type position names a type, never a same-named member (#2121)
Found by the README-wide sweep: AutoMapper's most-depended-on list held a
test class's `List` property (519) and an attribute's `Type` property (259);
eShop's held MSTest's `TestContext` and MAUI's `Easing` as properties.

A `references`/`type_of`/`returns` ref whose SITE is a type position — read
from the source at its column: `Type name` (a declaration names its type
first), `List<`, `Type?` (not `?.`), `Type[]`, a generic argument, `new X`,
VB's `As` / `New` / `Of` — and every `instantiates` ref (its column is the
`new`) now has only type candidates in exact-name and fuzzy matching: a
property, method (a constructor is one), field or enum case sharing the name
is not one. With the member out of the way the real type wins: `TypeMap
typeMap` now links the `TypeMap` class instead of a `TypeMap` property beside
it. Anything else — `Builder.Services`, `MapGet("/x", GetItems)`, a route's
reference to its action — keeps every candidate.

A/B vs main (edge diffs, every change classified): AutoMapper −1,871/+777,
Newtonsoft.Json −3,612/+599, serilog −447/+187, eShop −78/+15,
CleanArchitecture −9. Losses are BCL/framework types that had been pinned on
project members (List, Type, DateTime, CancellationToken, Exception,
TimeSpan, Easing, TestContext…); gains are the project's own types
(TypeMap, MemberMap, LogEvent, LogEventProperty, TypeNameHandling,
JTokenReader, OrderStatus). An earlier cut that dropped every member
candidate also dropped method groups, `Builder.Services` reads and ASP.NET
route→action references — the position reading keeps all of those.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 04:50:16 +00:00
Colby MchenryandClaude Opus 5.5 7a51969fb0 fix(resolution): JS/TS calls never pick a class member by name alone when the code rules it out (#2120)
Found by the README-wide sweep: the "most depended on" lists of popular
repos were topped by members that nothing actually calls.

- A member call on a NAMESPACE import (`import * as z from "zod/v4"`) calls
  one of the module's exports, never some class's method; a receiver
  imported from a package outside the repository names nothing in it.
  matchMethodCall's name/word-overlap strategies no longer run for either.
  zod: `z.string()` had bound 2,640 calls to a test helper's `string`
  getter (Mocker::string topped the hub list with 3,209), `z.toJSONSchema`
  371 to ZodType::toJSONSchema; trpc `z.record` to a generated SDK class.
- A receiver-less JS/TS or Go call cannot reach a PROPERTY, FIELD or enum
  case either (it already excluded methods, #1714/#1857): typeorm's mocha
  `it(…)` bound 2,879 calls to an interface's `it` property and `describe(…)`
  1,368 to a command class's `describe` string field.
- A bare call to a name the file imports from outside the repository
  (`import { useQuery } from '@tanstack/react-query'`, CommonJS
  `require('supertest')`, vitest's `test`) gets no name-matched candidate.
- The React resolver leaves an imported component/hook/context to import
  resolution (framework resolution runs first and picked any same-named
  project hook — trpc tests' `useQuery` landed on a hook nested inside one of
  trpc's factories), and never targets a nested hook; same-file hook first.
- "Outside the repository" is the coordinator's `isOutOfRepoImport`:
  isExternalImport (aliases, workspace members) AND not the repo's own package
  name AND unresolvable by resolveImportPath — so axios's own tests'
  `import { getAdapter } from 'axios'` now resolve to lib/adapters.js instead
  of the .d.ts interface member.

A/B vs main (edge diffs, every change classified): zod −5,228, typeorm
−4,318, express −700 (calls onto the `var request = require('supertest')`
binding), trpc −350/+17, excalidraw −233/+3 (props callbacks onto Props
interface members), hono −150, axios −52/+23 (the self-import upgrade),
bulletproof-react: 156 sites that pointed at ANOTHER app's component/hook in
the three-app monorepo now point at the importing app's own, 16 wrong
cross-app links dropped, 8 imports the React heuristic had guessed right lost
(per-app tsconfig paths don't resolve — separate follow-up); gin/cobra
byte-identical, react-redux-realworld byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 04:39:30 +00:00
Colby MchenryandClaude Opus 5.5 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>
2026-09-30 03:59:28 +00:00
Colby MchenryandClaude Opus 5.5 e845b1cd10 fix(nestjs): routes carry the app's global prefix and URI version (#2117)
`app.setGlobalPrefix('api')` and `app.enableVersioning({ type:
VersioningType.URI, defaultVersion: '1' })` put every controller under
`/api/v1`, but routes were named by the controller path alone (`GET /user`).
A front end's `this.http.get('/api/v1/user')` then linked to nothing:
Ghostfolio's Angular client had no cross-tier edge into its own NestJS API.

postExtract now reads each `NestFactory.create` bootstrap and renames the
routes of controllers in that app, in NestJS's order:
`/{prefix unless excluded}/{v}{version}/{RouterModule prefix}/{path}`.

- `exclude` paths (plain, `{ path, method }`, `{/*wildcard}`, `(.*)`, `*`)
  skip the prefix and keep their version, as NestJS does.
- A route's version: its own `@Version` (found past decorators with object
  arguments), else the class's `@Version` or `@Controller({ version })`,
  else `defaultVersion`. `VERSION_NEUTRAL` has none; header/media-type
  versioning adds no segment.
- A computed prefix (`config.get(...)`) is not guessed.
- Each app uses its own bootstrap (the longest root above it). A seed script
  that creates the same app without serving it doesn't displace the one that
  configures it, and an app with no config doesn't borrow another app's.
- Only routes whose decorator sits in a `@Controller` are touched, so an
  Express route in the same repo keeps its name.

Ghostfolio: 0 → 103 cross-tier edges (82 of 85 client call sites), no
mismatches. koel, proshop, IceCubes and nest-samples are byte-identical to main.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 03:17:35 +00:00
Colby MchenryandClaude Opus 5.5 c3efbbf4e7 feat(angular): a template's event bindings call the component's methods (#2114)
An Angular handler's only caller is its template: `(click)="toggleFavorite()"`,
`(ngSubmit)="submitForm()"`. With no edge, the method showed no callers,
impact stopped at it, and the Steps picture of a screen reached its
handlers only through class containment, with nothing to say what fires
them.

The template pass now reads each `(event)="…"` binding — not
`[(ngModel)]`'s two-way half — and links the component to every method of
its own the statement calls (`save()`, `this.toggle(x)`; not
`closed.emit()` or `form.reset()`), as a `calls` edge
(`synthesizedBy: 'angular-event'`) carrying the binding as
`metadata.trigger`: `{ kind: 'prop', name: '(click)', of: 'button' }`.
The binding is in the template, so it cannot be read back from the source
at the edge's line; Steps now takes a trigger an edge carries before
reading one at the site.

angular-realworld: 21 binding sites → 17 edges (3 repeats of a method and
event, 1 `delete.emit(true)`); Steps for /article/:slug draws
deleteArticle on (click) · <button> → DELETE and back to /, addComment on
(ngSubmit) · <form>, the favorite button's toggle → POST/DELETE or
/register. Ghostfolio 225 edges of 302 sites, ngx-admin 75 of 100 — the
rest are `$event.stopPropagation()`, output emits, assignments and
repeats. Controls (koel, proshop) byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:51:30 +00:00
Colby MchenryandClaude Opus 5.5 761611c18e feat(angular): routes, navigation and the component tree for Angular apps (#2113)
An Angular app had no route nodes, no navigates edges and no link from a
template to what it renders — the viewer's Steps tab said "No screens or
endpoints" and there was no Screens tab.

Routes (frameworks/angular-router.ts): every `Routes` array — typed
`Routes` / `Route[]`, `RouterModule.forRoot/forChild`, `provideRouter`, a
routes file's `export default [...]` / `as|satisfies Routes` — walked as
objects. `component` binds by `references` (route-roots takes a class as
a named handler); a lazy `loadComponent` binds to the file's `@Component`
class. `children` join their parent's path; a lazily loaded file's routes
are put under the path that loads them in postExtract (following an
NgModule `loadChildren` to the routing module it imports), idempotently
from the in-file path kept in the qualified name. A route with children
is a layout: a screen only where no child claims its address, and linked
from each screen inside it (`references`, `layout: true`). Paths written
as route constants (`internalRoutes.account.path`, through destructured
locals and `const x + '/:id'`) and `$localize` strings are read from the
constant object. `redirectTo` aliases an address to the screen it sends
the user to; a root `**` redirect answers `/` only. A `matcher` route and
a `**` catch-all are not screens.

Navigation: `router.navigate([...])`, `navigateByUrl`, a guard's
`createUrlTree` / `parseUrl` — a command array (non-literal elements are
holes), a static string, a route constant, a component property holding
one, a function constant called with arguments, `.concat(id)`. A
`relativeTo` navigation, a query-only `navigate([])` and a destination
made only of holes (it would match any route of its length) are left
unresolved.

Templates (angular-template-synthesizer.ts): a component's `templateUrl`
file or inline `template:` is read at synthesis time. `<app-foo>` by
element selector (same app first; two candidates = none) is a `calls`
edge from parent to child (`angular-template`), the edge a navigation in
a child component rides to its screen. `routerLink` / `[routerLink]` and
a `routerLink:` field written in the class (a tab bar's config) are
navigates edges. Gated on an `@angular/core` dependency.

Measured:
- angular-realworld: 10 routes all bound; 11/11 navigation sites
  accounted for (10 resolved, 1 query-only); 31/31 routerLink sites
  resolved; 18 renders.
- Ghostfolio: 84 routes all bound, 0 unresolved constant paths; 27
  navigate resolved, 23 correctly not (8 relative, 9 query-only, 2
  computed, 4 aimed at a matcher route); 162 routerLink edges, every one
  checked against its site; 170 renders.
- ngx-admin (NgModule): 55 routes, 47 bound (8 are @nebular/auth's); no
  navigation calls in the source.
- Controls byte-identical vs main (nodes and edges incl. metadata):
  koel, IceCubesApp, spring-petclinic, proshop. Ghostfolio index time at
  parity.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:43:58 +00:00
Colby MchenryandClaude Opus 5.5 7639c787e4 fix(swift): what a SwiftUI app's viewer showed that was not so (#2109)
Three things IceCubesApp showed in `codegraph ui`:

Steps labelled calls that leave the index by other stacks' names:
`rawValue.data(using: .utf8)` network (25 boxes), Foundation's `Timer`
telemetry, `Calendar` and a SiriKit `intent` device, the app's own
`Notifications` endpoint enum device (expo-notifications' row), and
`viewModel.votes.firstIndex(of:)` a database read (the `\w*Model` row).
Now: a Swift `.data/.upload/.download/.bytes` is network only on a
`*session` receiver; Timer/Calendar/intent are no effect in Swift; a
`viewModel` is not a table (any language); a keychain receiver is
storage ahead of Swift's generic `x.delete` database row. A call whose
receiver is a type the Swift project declares — not an `extension X {}`,
which declares nothing (read through the site reader) — no longer
matches the library-name rows; the model and reply rows, written for
project types, still do.

"Files that run something" listed every view with a `#Preview { … }`:
the macro sits at a file's top level, so its constructions were
module-level edges. Entry points now subtract calls inside a Swift
file's top-level previews (trailing closures included — WidgetKit's
`} timeline: {`), drop a file left with none, and re-rank.

Every `struct X: View` had a one-line `component` twin, and `@main`
apps, view controllers and UIView subclasses a `class` twin, from the
SwiftUI/UIKit resolvers' regex extract(): 188 nodes on IceCubesApp, 184
on mastodon-ios, zero edges into any of them — dead ends in search and
the symbol view. The two extract()s are removed.

IceCubesApp's effect sweep is now URLSession-only network, no
telemetry, no database, keychain storage; its entry files drop from
eleven preview files to its one real script. SwiftPackageIndex's Fluent
database and reply steps are unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:05:38 +00:00
Colby MchenryandClaude Opus 5.5 e9be68b61d fix(swift): a composition continued on & lines no longer swallows its type (#2108)
typealias EditorClient = AutocompleteService.Client
      & MediaUploadService.Client

is valid Swift, but in a type's body tree-sitter-swift ends the member at
the newline and the `&` line is an ERROR that swallows the enclosing
type. IceCubesApp's 980-line `EditorStore` came out as loose variables
and functions — no class, no init, no methods — so a call in its init
counted as file-level code ("a file that runs something"), and calls
into its methods from the views landed on same-named methods of other
services.

A Swift preParse moves a line-leading `&` onto the line before, for an
indented `typealias`/`let`/`var` statement's continuation lines only.
Same length and line count; the continuation line's tokens sit one
column left (the Swift type gate reads the site's qualifier at that one
column over). Measured shapes: the failure is type bodies only — a
top-level declaration, a function body and a parenthesized type parse as
written, and are left alone; `&&`, `&+` and inout `&x` are never
touched. The preParse is hoisted before the kernel call like the C/C#
ones, so both arms parse the same bytes, and the file no longer defers
to wasm.

Across IceCubesApp, SwiftPackageIndex-Server, swift-nio, Alamofire, TCA
and mastodon-ios (3,000 Swift files) it rewrites exactly the one failing
file, which now parses clean; none newly erroring. EditorStore is a class
again with 38 methods; views now reference it and call its
insertStatusText / runAssistant (62 edges into it), and
`urlSession.upload` no longer lands on MediaUploadService.upload.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 21:43:56 +00:00
Colby MchenryandClaude Opus 5.5 5753de20f5 fix(swift): an extension is not the type it extends (#2107)
`extension View { … }` parses as a class_declaration, so the index held a
`class View` node for every file extending SwiftUI's View, Text, Color,
String, URL. With no declaration of the type in the project, every
`struct X: View`, `Text("…")` and `Color.red` bound to whichever
extension came first: on IceCubesApp 6 of the top 12 "most depended on"
symbols were extension files (View 632 edges, Text 402, Label 209). And
a bare `@State` bound to a view model's nested `enum State` (253 edges),
`@Test` to a test's nested struct (821 on SwiftPackageIndex), `: Error`
to a nested `IOError.Error` (233 on swift-nio).

A gate at resolveOne's seam (every strategy, framework patterns and
name matching alike) checks a Swift reference's type target against what
the site wrote:
- an extension moves to the declaration it extends, or the ref stays
  unresolved when the SDK declares the type;
- a bare name of a nested type needs the site inside its parent, the type
  itself, or a subtype that declares inheriting from the parent;
- a qualified name must fit its path. The index keeps `Build.Id` as a ref
  named `Id`, so the qualifier is read back from the source line
  (generic arguments stripped: `EventLoopFuture<T>.Isolated`); a member
  indexed top-level because an `#if` ended its type's body early still
  fits a parent declared in its file.
Redirects pick the unique declaration in scope, then the same file, then
the nearest directory; a tie is left unresolved.

Two things rode on the wrong edges and are kept:
- `getSupertypes` named `View` for `struct X: View` only because that
  edge resolved to an extension. Swift conformances to a type the
  project only extends are now read from the declaration head (once per
  node, memoized; a per-node unresolved_refs query was +75% index time on
  swift-nio), so `X().withEnvironments()` still resolves to the
  `extension View` method.
- interface-impl dispatch read a protocol's methods off the node the
  conformance landed on. Swift protocol requirements are not extracted as
  methods; the default implementations live on `extension P` nodes, which
  are now included — every extension, not one arbitrary one (+359
  dispatch edges on swift-nio, +44 on Alamofire, none lost).

Measured edge diffs vs main, every removal classified:
IceCubesApp 2473 removed (172 redirects to the declaration, 1989
SDK-extension edges, 280 wrong bare nested-type bindings, ~30 from the
class the typealias parse error swallows); SwiftPackageIndex 3810 (1461
redirects); swift-nio 10991 (5085 redirects); Alamofire 1368 (198). Index
time at parity (swift-nio 4.1s vs 4.0s, Alamofire 1.33 vs 1.37,
IceCubes 1.31 vs 1.31).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 20:31:21 +00:00
Colby MchenryandClaude Opus 5.5 48b1a19b19 fix(vapor): bind a route to the handler on the type it names (#2106)
`use: SearchController.show` kept only `show`, and name matching bound
every controller's `show` route to whichever `show` came first — on
SwiftPackageIndex-Server 7 of 13 routes pointed at another endpoint's
code (Search, HealthCheck, ValidateSPIManifest and ReadyForSwift6's
`show` all at AuthorController.show).

The route's handler ref now keeps the type: `Type@method` (dotted for
nested types, `API.PackageController@get`), `@method` for `self.x` and a
bare `x`, and a `let todos = TodoController()` receiver's type. The Vapor
resolver claims these and resolves on the owner's qualified name:
nested types, handlers declared in `extension Type` in another file, and
`extension API.PackageController` (whose node carries only the last
segment, so its declaration line decides). `self.x` / bare `x` take the
method of the type the route is written in. Of one type's overloads, the
one declared to take a `Request` wins. Two types, or none, leave the
ref unresolved rather than guess.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 19:50:24 +00:00
Colby MchenryandClaude Opus 5.5 179ac85bd9 fix(steps): draw how a server-rendered endpoint answers; bare PHP calls are functions (#2103)
Found testing `codegraph ui` on Spring and Laravel: a server-rendered
endpoint's Steps picture never said how it answers. petclinic's POST
/owners/new showed its database write and no reply; BookStack's book page
showed a 404 and not the page it renders.

Spring MVC answers by what a handler RETURNS, which is no call:
- A new tree-sitter reader, returnsInTree / returnsForFile (graph/
  branch-guards.ts, exposed as SiteReader.returns), lists the returns a
  definition makes itself — never one in a lambda, a local function or a
  nested / anonymous class — and the string a same-class constant holds.
- steps.ts: for a mapped method of a class that renders views (not
  @RestController, not @ResponseBody), a returned view name / constant is a
  200 render, `"redirect:…"` a 302, `"forward:…"` a forward, each under the
  condition it is returned in (effectLink takes a preset effect for a site
  that is no call). Non-literal returns are not guessed at.
- effects.ts: `new ModelAndView(…)` (200, or 302 for "redirect:…") and
  `new RedirectView(…)` (302) are replies.

Laravel's `view(…)` and `redirect(…)` were already reply rules, but never
reached them:
- name-matcher.ts: a PHP call written without a receiver is a function
  call, so only a `function` is a candidate — BookStack's every
  `return redirect(…)` bound to ApiDocsController::redirect, every
  `view(…)` to a `$view` field (and, through the fuzzy fallback, to the
  class `View`). PHP refs record the call expression's column, so the call
  text at that column decides. koel alone loses 60 such wrong edges
  (`basename()`, `auth()`, `value()`, …).
- effects.ts: the PHP reply rule never matched a chain — rules see the text
  with argument lists stripped (`response->json`), the rule wanted `()->` —
  so `response()->json(…)`, `redirect()->route(…)`, `redirect()->back()->
  withErrors(…)` were never replies; `to_route` added; redirect chains are
  302s. PHP's SPL programming-error exceptions (InvalidArgumentException,
  LogicException, …) are no longer replies, and a namespaced exception is
  read by its class name.

petclinic POST /owners/new: 200 `view owners/createOrUpdateOwnerForm` WHEN
result.hasErrors(), 302 `redirect:/owners/${…}` WHEN !result.hasErrors().
BookStack GET /books/{slug}: 404, 200 `view`, 302 `redirect` (the
InvalidArgumentException from a helper is gone). realworld's REST login is
unchanged.

Not in scope: PHP has no site reader (no branch-guard rules), so Laravel
replies carry no WHEN and no arguments — `abort(Response::HTTP_…)` and
`response()->json()` show without a status.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 17:40:28 +00:00
Colby MchenryandClaude Opus 5.5 0419a51636 fix(synthesis): link a Spring or Laravel test's request to the route it reaches (#2100)
Tests reach a controller by URL, never by a call — MockMvc's
`perform(post("/owners/new"))`, Laravel's `$this->postJson('api/me')` — so
every controller those suites exercise read as untested: the viewer's
"No test reaches this" and explore's `tests:` line both walk callers.
petclinic's processCreationForm, hit three times by OwnerControllerTests,
said no test reached it.

A new synthesis pass (tier-synthesizer section 4, registered after the
cross-tier pass, gated on java / kotlin / php) reads test files only:
- Spring: MockMvc builders (with MockMvcRequestBuilders imported),
  `request(HttpMethod.X, …)`, the Kotlin `mockMvc.get(…)` DSL,
  WebTestClient `.get().uri(…)`, TestRestTemplate, RestAssured.
- Laravel: `$this->get/post/…Json(…)`, `->json('POST', …)`, `->call(…)`
  on a chain rooted at `$this` that only calls methods (so
  `$this->app->get('config')` is not a request); Pest's helpers where the
  file imports Pest\Laravel; and a project's own verb-named helpers
  (koel's getAs → jsonAs → `$this->json($method, $uri)`), recognised by
  their body, never by name alone.
Paths are read as built — Spring `{var}` templates, PHP / Kotlin
interpolation and concatenation become holes — and matched with the
cross-tier pass's own route matcher (a hole fills a parameter, never a
literal). One `calls` edge per (test, route), `synthesizedBy:
'test-request'`. These edges are excluded from countIncomingEdges, so a
well-tested endpoint never becomes a hub the Steps walk refuses to enter.

Routes reached by a test: petclinic 16/17, realworld 18/19, BookStack
194/340, koel 105/134 (from 0 each). A random sample of edges checked
against the test source was all correct. The pass costs 86 ms on
BookStack and 68 ms on koel.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:24:49 +00:00
Colby MchenryandClaude Opus 5.5 ffced05e3c fix(laravel): name routes by the path a request takes — leading slash, prefix groups, the /api mount (#2099)
Routes were named by the bare literal in the routes file, so BookStack's
api.php showed `GET pages` for `/api/pages` and koel's `Route::prefix('api')`
group showed `GET ping` for `/api/ping`. Beyond the label, a front-end
`fetch('/api/pages')` could never pair with a route recorded as `pages`.

- extract(): `Route::prefix()` chains and `Route::group(['prefix' => …])`
  scope their routes, nested ones in order; a computed prefix is kept as a
  `{prefix}` segment. Paths are joined the way Laravel joins them (leading
  slash, no doubled or trailing slashes). qualifiedName now holds that
  in-file path.
- postExtract(): finds where each routes file is mounted — a group whose
  argument is the file, a require / include / loadRoutesFrom inside a
  group, Laravel 11's `withRouting(api:, web:, apiPrefix:)` — composes the
  prefixes down the include tree, and names every Laravel route from its
  qualifiedName plus its file's prefix (idempotent; a removed mount is
  undone). routes/api.php defaults to `/api` unless something mounts it or
  a `withRouting(using:)` loader might. A file mounted at two prefixes stays
  at its in-file path. Only files in routes/ or naming `routes/` /
  `withRouting(` are read.

On BookStack all 80 api.php routes become `/api/…` (from its
RouteServiceProvider group); on koel all 118 become `/api/…`; no route is
left without a leading slash.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:03:02 +00:00
Colby MchenryandClaude Opus 5.5 15a5d1d0cd fix(c): keep an #undef under an undecidable #if unknown; a macro call never binds to a type (#2069, #2070) (#2081)
Two follow-ups to the unreleased macro-visibility work (#1838/#1839):

- #2069: `#undef FLAG` under an `#if` nothing in the source decides made a
  never-seen FLAG certainly undefined, so `#ifdef FLAG` was read as false
  and the functions it guards lost their callers (CPython's
  `Py_GIL_DISABLED`). A name no directive has touched is now unknown; only
  an #undef on a certain line clears it.
- #2070: dropping the macro constant from a call's candidates let the next
  same-named node win, so expat's `PREFIX(scanRef)(...)` instantiated an
  unrelated `struct PREFIX`. When the name is a function-like macro in the
  project, only a function or method is an eligible callee.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 00:46:41 +00:00
Colby MchenryandClaude Opus 5.5 fef37765c1 perf(resolution): remove the indexing regressions since v1.6.0 (vscode stall, per-ref rescans) — graph byte-identical (#2072)
* perf(resolution): read JS/TS parameter lists without exponential backtracking

isLocallyBoundJsName (#1759, cd4e65b5) scans a file for a parameter that
binds a bare-called name. Its per-parameter pattern `(type)?(default)?\s*`
let one `name: T = value` text split several ways, so when the name was
absent the search backtracked through every combination. On vscode's
markersModel.test.ts, whose helper takes nine typed, defaulted parameters,
each `suite`/`test` scan took 30-40s — one pool worker held a whole
resolution batch while the rest idled, a 70-400s stall per run.

Each parameter now has exactly one parse: its first non-space character
after the identifier picks the type, default or bare alternative. The
alternatives match exactly the strings the old optional groups did, so the
result is identical for every input (4M generated cases and 5.6k real
vscode file/name pairs agree); the scan is 0.1-0.3ms on that file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(resolution): memoize Python module-file lookups per resolution pass

#2034 (5eaa6fed) sent every Python named import whose path the import
resolver cannot place — stdlib and third-party modules such as
`from unittest import mock` — through findPythonModuleFile, which scans
every `__init__.py` file node for a path suffix. It ran once per ref:
on django that scan alone went from 0.6s to 1.4s of resolution CPU.

The candidates for a module depend only on the module path, so they are
now collected once per resolution context, in the same name-lookup order;
the importing file is still excluded per call, so the first survivor is
the node the scan returned. The memo drops with the other import-resolver
memos. Resolution output on django is hash-identical; single-threaded
resolution CPU there goes from 6.5-6.8s to 4.9s (v1.6.0: 4.5s).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(resolution): test sealed-module candidates cheapest-first

isSealedModule (#1719, bffd50e4) masks a candidate file's comments and
strings before asking whether it is a module that exports nothing. Import
refs call it for every same-named candidate across the tree, so on vscode
it masked nearly every TypeScript file once per resolver: 3.3s of masking
in a single-threaded pass, paid again in each pool worker.

The verdict is a pure conjunction, so it now asks the cheap questions
first: the source contains `import`, no node in the file is exported, and
there is no CommonJS export. Masking runs only for files that pass all
three, which in practice are the few that can be sealed. The masker blanks
text and never deletes it, so a file without `import` in its source has
none in its masked code either. Resolution output is hash-identical on
vscode and django.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(resolution): scan a class for a field's declared type once per field

matchTsThisFieldCall (#1496, cece0720) resolves `this.<field>.<method>()`
by finding the field's declaration in the enclosing class. It re-split the
whole file, stripped comments line by line and compiled three patterns for
every such call, although the answer depends only on the class and the
field: 16.5s of a 148s single-threaded resolution pass on vscode.

The first declaring line — `: typeof X`, `: X` or `= new X`, in that order
per line — is now memoized per (class node, field), and the last few
classes' stripped lines are kept, since calls arrive file by file. Owners
are still tried in the same order and everything after the declaration
lookup is unchanged. Resolution output is hash-identical on vscode and
django.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(synthesis): start cross-tier receiver chains only at a word's start

The cross-tier pass (b1f40c57) scans every non-test JS/TS file whose text
has a `.add(`, `.get(` or `.on(` — nearly all of vscode — with receiver-
chain patterns such as `a.b.c.add('job')`. A chain could begin at any
letter of an identifier, so each letter re-read the dotted chain behind
it; on vscode the pass ran 35s on one pool worker and set the synthesis
phase's wall.

A match that starts mid-word implies one from the word's first letter, and
every match of these patterns ends on a non-word character, so a scan
never resumes mid-word: a lookbehind that rejects those starts skips only
attempts bound to fail. Over all 14,135 vscode JS/TS files the five
patterns return identical matches (positions and groups) before and
after, in 8.8s instead of 32.8s; the pass itself drops from ~9.8s to
~5.6s CPU standalone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(resolution): filter same-named candidates in one pass, cheap checks first

Interface members (#1638) and nested const-bound functions (#1669) made
common names — `editor`, `model`, `dispose` — carry thousands of nodes.
matchByExactName ran ten chained filters over that list for every
reference, copying it once per rule before the source-reading checks ran,
and the method-call strategies copied it again to put the call site's file
first before looking at only the class-like or same-file entries.

Each rule judges one candidate on its own, so the exact-name rules now run
as a single pass with the kind and language checks ahead of the lexical,
sealed-module and local-binding checks; the class strategies narrow to
owner kinds before ordering by file (the same nodes in the same order),
and the object-literal strategy, which keeps same-file holders only,
skips the reordering altogether. Resolution output is hash-identical on
vscode and django.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(synthesis): scope language-specific node scans in SQL

Eleven synthesis passes stream every node of a kind and keep only their
language's (Go, Java/Kotlin, XML, native, ArkTS, Nix, Erlang). On a
TypeScript monorepo the Go passes materialized all 113k vscode methods to
find two Go ones — goMethodContains alone went from 0.5s to ~2.9s on the
main thread after interface members (#1638) grew the method count and the
canonical ORDER BY (#1988) landed.

iterateNodesByKindIn adds the language predicate to the same query. Its
ORDER BY (file_path, start_line, id) is a total order, so it yields
exactly the nodes the callers kept, in the same sequence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(synthesis): run the synthesis-site index and source-gate scan beside the passes

Two serial main-thread steps landed with the incremental synthesis refresh
(#1988, #2038). The bulk-edge window rebuilt idx_edges_synthesis_site
before synthesis — its partial predicate runs json_valid over the
metadata of every edge (~1.5M rows, 2.7s on vscode) — and after the merged
insert synthesis re-read every file in the project to record its source
gates (~1.9s). Neither result is read until the index is finished: only
sync consults the index, and the gates depend only on the files.

The window's end now leaves that one index for later, and the resolver
builds it while the pool computes the passes, when the main thread was
only waiting; the source-gate scan runs in the same window. Both still
happen before resolution returns, whatever path synthesis takes, and a
crash in between is healed on the next open as before. On vscode
edge-index-recreate drops from 8.3s to 3.6s of serial time; the graph,
synthesis_inputs and the final index set are identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(resolution): stop re-reading resolved targets and re-probing missing files

Every resolved reference now re-reads its target node several times —
the kind gate (#1796), the cross-family language gate (#1986), alias
following (#1482) and edge creation — through the query layer's
1,000-entry node cache, while targets recur ~5x (1.46M vscode edges onto
293k nodes). The language gate alone spent ~6s of single-threaded
resolution on vscode fetching whole nodes to read one field, and
fileExists re-probed the same missing candidate paths (every extension of
a `.js` specifier) from every importing file, now under a path.resolve
containment check (#1631).

The resolver keeps its own id → node cache and a memo of filesystem
probes, both in the stable window of its other caches and dropped by
clearCaches; the language gate returns early when the reference's
language has no code family (no target can cross one) and otherwise
memoizes each target's language. Resolution output is hash-identical on
vscode and django.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(resolution): cheaper sealed-module and receiver-less call checks

isSealedModule read and decoded a candidate file — its source and every
node — to learn whether anything is exported, though nearly every module
does. It now asks one indexed EXISTS probe first and reads the source only
for files that export nothing (the conjunction is unchanged).

isReceiverLessCall (#1759, #1857) compiled a pattern per call and
end-anchored two more over the line's prefix, which V8 scans from the
start. It now matches the name literally with a sticky `\s*[(<]`, and reads
the prefix backwards: the last non-space character decides whether a
receiver precedes the call, and the word run ending there decides the
keyword exception — exactly what `[.\w$\])]\s*$` and
`\b(?:return|...)\s*$` accepted. Resolution output is hash-identical on
vscode and django.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(synthesis): pull passes onto workers heaviest-first; cheaper registry and tier scans

The pooled fan-out handed every pass out at once, so each worker held a
count-based share and a heavy pass finished only with its worker's whole
share (vscode's registry pass ended ~16s in). Workers now pull one pass at
a time, heaviest first; edges still merge by registry index, so the output
is unchanged. vscode synthesis: 14.5s vs 19.6-24.4s.

The registry pass retried its greedy name at every character of every
identifier; both scans now start a name only at an identifier's first
character or a `this.` (a mid-identifier resume after a `.method` dispatch
falls back to the unguarded pattern), and dispatch lines come from a
newline-offset lookup instead of splitting the prefix per match. Checked
match-for-match against the old patterns on every vscode JS/TS file plus
300k fuzzed strings. The tier pass reads a file's nodes only when a site
needs them. Standalone on vscode: registry 5.6s -> 4.5s, tier 5.1s -> 4.4s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(resolution): lean export indexes; no per-name regex compiles in field and local-binding checks

Three per-lookup costs the resolver pool paid once per worker:

- A file's export index decoded every node in the file and read its source,
  for each file an import reached, in every worker. It now reads only the
  exported rows (same `start_line, id` order); the default-export binding
  and names a local `export { a as b }` clause introduces are read on first
  use, from name-targeted rows. findExportedSymbol across vscode's workers:
  12.5s -> 3.7s.
- The `this.<field>` declaration scan compiled three patterns per field of
  every class and walked every class line. Each pattern needs the field as
  a whole `[\w$#]` token, so a class's lines are indexed by token once and
  three sticky patterns, compiled once, are tried at the field's token
  occurrences. Identical on 721k vscode (file, field) pairs; 56.7s -> 1.1s
  standalone.
- isLocallyBoundJsName searched the whole file with four patterns per name.
  Each can only start at a `const`/`let`/`var` or `function`/`class`
  keyword, an `=>`, or the `(` opening a list before the name, so the
  patterns (compiled once per name, sticky) are tried there. Identical on
  198k vscode (file, name) pairs; 68.1s -> 10.7s standalone.

Replaying every vscode (2.19M) and django (270k) reference single-threaded
gives the same result hash before and after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(resolution): prepare kind-filtered edge reads once; memoize the inherited this-member walk

- getOutgoingEdges / getIncomingEdges with a kind filter prepared a fresh
  statement on every call, a third of the read's cost; they are issued per
  node by supertype walks and the member-lookup synthesis passes. Each SQL
  shape is now prepared once per connection (dropped on rebind).
- The post-loop `this.<member>` pass walked the same supertypes for every
  reference, reading each one's contains edges and decoding members one by
  one until a name matched. Nothing is written until the pass ends, so each
  type's supertype edges and each supertype's callable members (by name, in
  edge order) are read once. vscode: 4.1s -> 1.0s.
- Express's mount pass decoded every JS/TS file to look for `.use(`; it now
  checks the raw bytes first (an ASCII needle survives UTF-8 decoding
  unchanged) and decodes only files that have it.

A full vscode init dumps the identical graph before and after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(resolution): fan the next batch out before inserting edges resolution never reads

The batch loop inserted every batch's edges before fanning out the next
one, so the pool sat idle through each insert. That ordering exists for
one reason: later batches walk supertype chains over the `extends` /
`implements` edges earlier batches resolved. Those are the only edges
resolution reads, and the prerequisite phase drains every such ref first,
so after it no batch makes one. A batch without them now fans the next
batch out first and inserts while it resolves: the same writes in the same
order, and nothing the workers read differs. The kinds live in one
constant, shared with the supertype walk.

vscode resolution, three interleaved rounds against the parent commit:
50.3-51.0s vs 56.8-59.1s wall (the pool's wait per batch halves; the
overlapped inserts cost ~7s more CPU). Full vscode and django inits dump
identical graphs before and after, and identical to main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(db): prepare the edge-endpoint existence check once

Every edge insert checks its endpoints with a 500-id IN query, a chunk at
a time, and prepared that statement afresh for each chunk. The batch loop
now runs these inserts on the main thread while the pool resolves, so the
full-size statement is prepared once; only a final partial chunk prepares
ad hoc (as deleteReferencesByRowIds already does). Same SQL, same result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:57:18 +00:00
Colby MchenryandClaude Opus 5.5 49607201d6 fix(resolution): bind implements to the interface of a value+interface pair (#2055)
The inheritance target-kind gate (#1796) rejected any extends/implements
resolution whose target cannot be a supertype. VS Code declares every
service twice under one name, `export const IFoo = createDecorator<IFoo>(…)`
beside `export interface IFoo`, and the import resolver takes the first
export of that name, which is the value. The gate then dropped the edge
instead of taking the interface the same file declares.

Found by the pre-release v1.6.0 vs main comparison: 864 `implements` edges
lost on vscode (AccessibilityService -> IAccessibilityService and ~860 like
it), plus the ~5.8k interface-dispatch call edges synthesized from them.
The gate now moves a TypeScript constant/variable target to the one
same-named supertype in its file; with no such sibling it still drops the
edge. vscode: implements 4,139 -> 5,003 (v1.6.0: 5,436, 254 of them on
non-type targets; now 0), node count unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 19:55:29 +00:00
Colby Mchenry 9be3f1a8a2 fix(resolution): keep unknown Python import members unresolved (#2040)
Bisect identifies 5eaa6fed (#2034, #1820) as the first bad commit: its Python named-import fallback resolves task.delay() to the task function itself, hiding the queue effect in API steps. Require member resolution before returning a named Python import's target for attribute access.

Keep direct calls, callbacks, constructors, and known members working. Add absolute/relative aliased-import regression coverage without changing the existing steps assertions.

Validation: git bisect with preserved steps test, tsc and copy-assets at every step, CODEGRAPH_KERNEL=0 throughout bisect. 338 tests pass across ui-steps-api-servers, function-ref, rebuilt kernel-tsjs-parity, resolution, and ui-effects. Issue #1820 repro and tsc --noEmit pass.
2026-09-27 14:02:38 +00:00
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>
2026-09-27 13:46:34 +00:00
5eaa6fedd5 fix(extraction): preserve receivers for method values (#1820) (#2034)
* fix(extraction): record callers for methods passed as values (#1820)

pool.submit(obj.method), thread_pool_exec(self.store.fetch), and Go
Submit(c.store.Fetch) produced no callers/impact edge because only
obj.method(...) call expressions were captured. Member values are now
emitted as *.method function-refs and resolve unique-or-drop
(same-file first). Two methods of the same name still produce no edge.

Go go c.store.Fetch(ids) remains a calls edge (it is a call expression).

EXTRACTION_VERSION 27: MCP daemon catch-up rebuilds a stale index on
start so the watcher does not keep serving the hole after upgrade.

* fix(resolution): do not let test mocks veto #1820 method-value callers

A unique production method plus a unit-test double of the same name
was treated as ambiguous, so retrieve_vision_vectors still looked
callback-invisible on a real repo. Prefer the non-test file when
exactly one production target remains.

* fix(resolution): prefer a real override over a NotImplementedError base

#1820 unique-or-drop treated DocStoreConnection.delete_payload_fields
(the ABC stub) and QdrantConnection.delete_payload_fields as ambiguous,
so gc_stale_member_stamps stayed invisible. Skip short not-implemented
stubs when exactly one real implementation remains.

* fix(extraction): preserve receivers for method values (#1820)

Python attributes and Go selectors used as callback values were dropped or reduced to bare method names.
Preserve receiver paths in native and WASM extraction, resolve through type/import scope, and reject ambiguous or noncallable targets.
Cover callback, assignment, collection, and query behavior, with extraction version 27 signaling the required reindex.

Co-authored-by: Josh66 <info@aska-systems.de>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Josh66 <info@aska-systems.de>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:42:27 +00:00
Colby MchenryandClaude Opus 5.5 2a2f71ec41 fix(sync): refresh synthesized edges after incremental changes (#1988) (#2033)
* fix(sync): refresh synthesized edges after incremental changes (#1988)

Scoped sync skipped synthesis and retained obsolete dispatch edges.
Refresh all synthesis passes after base resolution when dependencies or source patterns change.
Stage and atomically replace owned edges, preserve ordinary edges, and retry interrupted refreshes.
Stabilize traversal order and migrate existing indexes for rebuild convergence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: preserve structural Go containment during synthesis refresh

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:41:49 +00:00
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>
2026-09-27 13:40:31 +00:00