Read at the site:
- a later link of a chain (the extractor keeps one receiver level; the line
shows the dot) reaches a member whose owner the receiver chain is named
after (call / type arguments dropped: `proc("x").call()` → proc,
`checker.value.onWrite` → checker), or a same-file member; never a package
object's function unless it is a Scala 3 `extension` method;
- a name the enclosing def binds (parameter, val/var/def, lambda / for param)
is only a def inside that function;
- otherwise a member of the types around it, an anonymous subclass's bases
(`new OptionParser[C]("x") { head(…) }`), their extends/with supertypes
(source-read heads), same file, or an imported object (`import Foo._`,
`import Foo.{bar}`); a value import (`import builder._`) leaves the file
unjudged.
A/B (edges removed / added): cats -1693/+678, sttp -887/+271, munit
-265/+175, scopt -245/+78, os-lib -115/+43.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The README said every bridge hop carries `metadata.synthesizedBy`, but only
the synthesized channels (rn-event-channel, fabric-native-impl) did; a
resolver-made hop (Swift↔ObjC, RN legacy / TurboModules, Expo Modules JS)
carried only `resolvedBy: 'framework'`, and `expo-module-extract` named
nothing that exists. The framework loop now adds `metadata.framework` (the
resolver's name: swift-objc-bridge, react-native-bridge, expo-modules-js,
fabric-view, rails, laravel, …) and the README says what each kind carries.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- FastEndpoints: an endpoint class (`: Endpoint<…>`, `EndpointWithoutRequest`,
`Ep.…`) declares `Get(…)` / `Post(…)` in `Configure()`; each becomes a route
linked to the class's own HandleAsync / ExecuteAsync (the bare-member scope
from #2151 keeps two `List` endpoints apart). A constant path
(`CreateContributorRequest.Route`) is read in postExtract from wherever the
class declares it; `$"/{nameof(Project)}s"` reads as `/Projects`.
- `app.MapGet("api/todos", …)` is named `/api/todos`.
A/B: ardalis/CleanArchitecture 0 → 24 routes (+24 edges); eShopOnWeb and
eShop only re-key routes whose path gained a leading slash; realworld and
jasontaylordev/CleanArchitecture byte-identical.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Detection only looked at the root (`build.sbt`, `conf/routes`,
`conf/application.conf`); playframework/play-samples keeps each project's
`conf/routes` in its own directory and had no routes. Any indexed Play routes
file now counts. play-samples 0 → 139 routes (+108 edges); root-level Play
repos are unaffected.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Route files (`routes.draw`, or under config/routes/) are read as a block
structure: `namespace` / `scope` add path + controller module, nested
`resources … do` sit under `/:parent_id`, `member` / `collection` add
`/:id/x` / `/x` actions, `root` and `resource` / `resources` options
(`only:` / `except:` across lines, `path:`, `controller:`) are honored;
`do` and `if` / `unless` / `case` / `begin` open frames an `end` closes.
- Detection accepts `gem "rails"` (double quotes), railties, and a Rails
engine's `config/routes.rb` anywhere (solidus).
- A namespaced `controller#action` only reaches controllers of its own
module path when any exist — an admin controller that inherits `index` is
not the API's `ZonesController#index`.
Route→action edges main → this branch: mastodon 221→578, solidus 0→319,
rubygems.org 108→192, maybe 127→189, lobsters 141→196; no non-route edge
lost except solidus's CamelCase refs now resolved by the Rails model
resolver (`Order` → Spree::Order model, was a state-machine module).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
`bp.add_url_rule('/path', 'name', view_func | positional view, methods=[…])`
and a project's own wrapper that hands a list of paths and a `view_func=`
over (flaskbb's `register_view(auth, routes=[…], view_func=X.as_view(…))`)
now produce route nodes linked to the view. A class-based view is ANY unless
`methods=` says; a function defaults to GET. A call whose path isn't a
string literal (the wrapper's own `add_url_rule(route, …)`) adds nothing.
flaskbb 0 → 103 routes (+104 edges); flask's own tests +27; CTFd, microblog,
bluelog byte-identical.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The unique-name method guess no longer applies to a Lua/Luau call through a
standard or host library table (string, table, math, io, os, coroutine,
debug, utf8, package, bit, jit, ffi, vim, ngx, busted's assert/spy/stub/mock,
love) unless the project defines that function on the table itself (kong's
globalpatches define ngx.sleep), nor to a string method called on a value
(`s:find`, `s:match`, `s:gsub` …). Other untyped receivers keep the guess:
a word rule like Ruby's was tried and lost koreader's OO calls
(`self.ui:reloadDocument()` → ReaderUI).
A/B (edges removed / added): kong -2742, koreader -101, lazy.nvim -84,
telescope.nvim -47, Fusion -9, lua-resty-core -1.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
* 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>
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>
- 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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
- `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>
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>
- `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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Found by the README-wide sweep: vendored bundles topped "most depended on"
across many repos — delphimvcframework's bundled `Buffer` (6,162) and four
`n`s (2,386 each), retrofit's docs `main.js` `t`/`e`/`i`, TestBox's
`__webpack_require__` — because only `*.min.js` was recognized as generated.
- detectGeneratedFile (index time, persisted as files.generated) now also
flags a .js/.mjs/.cjs file whose text is mostly lines of 1,000+ chars AND
whose long lines are code (≥3% `;{}(),`) — not an ordinary file carrying
one long base64 string — and any file that defines webpack's
`__webpack_require__` loader, however readable its lines. The path rule
also matches `-min.js` (underscore-min.js).
- Entry points' hubs skip nodes in generated files, as they skip tests.
Scanned every .js file of express, axios, lodash, excalidraw, typeorm, zod,
trpc, hono, bulletproof-react: zero new flags on hand-written code; newly
caught: lodash vendor/underscore/underscore-min.js, retrofit website
main.js (754 KB) + prism.js, TestBox syntaxhighlighter.js (webpack).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
`codegraph ui` re-execs itself with `--liftoff-only`; the shim the user
started blocks in spawnSync and cannot forward a signal. Killing it by pid —
a process manager, an IDE task, `kill` — left the re-exec'd server serving
its port forever (found during a sweep: servers from hours earlier were still
answering on ports 4801–4808 against deleted indexes, holding their stdout
pipes so the harness that started them never exited). Ctrl+C was unaffected
(the signal reaches the whole process group).
`index`/`init` already guard this with the #277 PPID watchdog. Factor it out
of installCommandSupervision as `watchParent(onLost)` and use it in `ui` with
the same shutdown as SIGINT/SIGTERM (close the index, then the socket). The
liveness watchdog is deliberately not installed for `ui`: a long Steps
computation can legitimately hold the event loop.
Test: POSIX end-to-end through the real relaunch (CODEGRAPH_WASM_RELAUNCHED
unset, 200ms poll) — SIGKILL the shim, the port stops answering; confirmed
failing without the fix.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
`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>
`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>
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>
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>
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>
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>
`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>
`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>
`MCP staleness banner > lists pending files under "Pending sync"` flaked in
the full suite (2 of 4 runs): status showed "0 modified" and no Pending sync
section. The tests used `debounceMs: 4000` to keep an edit pending while they
queried, but since #1397 a lone edit syncs after a 300ms quiet window whatever
the configured debounce. codegraph_status awaits a freshly spawned worker to
count changes before reading the pending set, and under load that outlasted
the window, so the sync had already cleared the entry.
A 500ms pause before the query fails the status, banner and #1968 path tests
deterministically. Hold the watcher's sync open with a never-settling spy in
those four setups instead; no assertion changes, and the test that checks the
banner drops after a sync still runs a real one.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>