Commit Graph
599 Commits
Author SHA1 Message Date
danusha2345andClaude Opus 5.5 90c5dc6fc9 fix(extraction): a Go method carries the exportedness of its name
Every Go method was indexed as unexported, whatever the case of its
name, in both engines: extractMethod never set isExported, and the
kernel's go.rs mirrored the omission. Plain functions carried the flag,
so dead-code results listed exported methods like Close as dead.

A method that is a top-level declaration (methodsAreTopLevel, only Go)
now gets the extractor's isExported, as a function does, and go.rs sets
is_exported from the same name-case rule in extract_method.

Added on top of the contributor's change: findGoPackageMember, which
resolves pkg.Name through a project import, matched any exported node
of that name in the package's directory, and methods stayed out only
because none was exported. With the flag set, a package-level call
like harbor's log.Errorf(...) went to the method Logger::Errorf
declared beside the function, and prometheus's embedded
storage.Appender resolved to a method named Appender, which dropped
its implements edges. A method is no member of its package, so it is
now skipped there. Two comments in name-matcher.ts that said the flag
is unset on every Go method now say why the name's case is still read.

Validation: the contributor's test fails on main and passes here, and
the new package-call test fails with the PR as first written and
passes here and on main, in both engines. A/B against main on harbor
296f304, prometheus a2fda0e, gin 0f09c3a and cobra adbc881: nodes
differ only in is_exported 0 -> 1 on uppercase Go methods (5,704,
3,880, 391 and 141). Edges are identical on prometheus and cobra; 22
retargets remain, all name-only guesses between an interface's method
declaration and the struct method implementing it, which now win: gin's
engine().GET and its siblings reach RouterGroup's methods, and harbor's
jobservice routes reach DefaultHandler's. The PR as first written had
retargeted 1,384 harbor calls and removed 60 prometheus implements and
dispatch sites. The dead-code report lists 3 Go symbols on harbor
instead of 167, and 0 on prometheus instead of 11. Kernel parity: 2,493
of 2,493 Go files byte-identical. The controls express and flask are
byte-identical. Index CPU on harbor, median of 5 interleaved: 40.8 s on
main, 39.8 s here. EXTRACTION_VERSION stays: main already moved it to
28 after 1.6.2, so 1.6.2 indexes get the re-index hint anyway.

Original PR #1734 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 19:38:20 +00:00
danusha2345andClaude Opus 5.5 428b3c487d fix(js,ts): a nested function takes calls to a name the file imports from a package
When a JavaScript or TypeScript file imports a name from outside the
repository, like import { resolve } from 'node:path', matchByExactName
returns no edge for a bare call of that name (the early return main added
for issue 2101). A call to a function declared inside the calling function
under that name, which shadows the import, lost its edge with it.

The candidates for such a call are now narrowed to the calling file's
declarations nested in a function or method (innerScopeShadows), instead
of giving up; a top-level one is the import's own binding. Lexical
reachability, applied later, keeps only the nested declarations visible
where the call is.

Our changes: tests for a sibling function's nested declaration and one
nested a level below the call, which neither may take the call (both hold
through the reachability rule), and for a call from a function nested in
the declaring one; the CHANGELOG bullet moved next to the JS/TS import
bullets, with credit.

Validation: the tests fail 2 of 6 on main and pass here. A/B against main
on 8 repos, kernel per arm: flask, gin, excalidraw, codegraph, pixijs,
pdf.js and three.js byte-identical; vite gains 1 right link, scanImports'
call to its nested scan() beside import { scan } from
'rolldown/experimental'. Index CPU
time on codegraph, test files full of vitest imports, within noise:
81.7 s to 81.9 s (median of 3, interleaved).

Original PR #1715 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:58:14 +00:00
danusha2345andClaude Opus 5.5 0d46dba08f fix(js,ts): a call on a new C() receiver resolves through C
A TypeScript or JavaScript call on a constructed object, new C(args).m(),
reached the resolver as the bare method name in both engines, so exact
matching bound it to any project method called m: new RegExp(p).exec(s)
to a database wrapper's exec, new Array(n).fill(0) to a text class's
fill, new TextEncoder().encode(s) to a project encode function.

Both engines now emit new C().m (new ns.C().m for a namespaced class, and
nothing for a constructor that is not a plain name or member chain). The
resolver looks m up on C with resolveMethodOnType, inherited methods on
the conformance pass once extends edges exist, an aliased named import
mapped back to its class, and a JS built-in (RegExp, Map, Date...) never
a project class unless the file imports or declares one. An ES5
constructor function keeps its prototype method.

Our changes: the lookup passes the file the call's file imports the class
from (tsImportedTypeFile), so of two classes sharing a name the imported
one is the one constructed (the PR as written took the first indexed copy,
at 0.9); new this.constructor() and a static new this() keep the bare
method name, as a call on this does (the PR dropped three.js's clone
idiom, 29 links to a class's own copy); rebased onto PR #2852, which
edits the same keepsBareTsJsReceiver and test lists; resolved the import
list conflict with rustChainedCallByName from PR #2846; CHANGELOG bullet
moved next to the JS/TS receiver bullets.

Validation: the new tests fail 4 of 6 on main 449eefa7 with both engines
and pass here. A/B against main, kernel per arm: flask, gin and vite
byte-identical. codegraph 48 links added (new ToolHandler(cg).execute), 2
retargeted and 14 typed, all right, 58 removed, 57 wrong (RegExp exec and
test, Array fill, Set add) and 1 right one lost to the grammar's parse of
new nodeSqlite!.DatabaseSync(). excalidraw 22 wrong removed (TextEncoder,
TextDecoder, URLSearchParams, Array fill), 3 typed. pixijs 14 retargeted
and 278 typed, all right, 7 wrong removed. pdf.js 5 added, 47 retargeted
and 68 typed, all right, 76 wrong removed (Array keys and fill, typed
arrays, Response). three.js 33 added, 404 retargeted (Capsule::set to
Matrix4::set and Matrix3::set, NURBSCurve::copy to the class's own copy)
and 429 typed, all right; 135 removed, 133 wrong (UINumber onChange, which
three.js attaches at run time, TextDecoder, typed arrays, URL) and 2 right
only by name (Loader methods inherited through a base imported from the
three package). No default-imported class under another name lost an edge.
Kernel parity holds on all six TS/JS repos and this repository, 0 diffs.
Index CPU time unchanged or lower: codegraph 81.5 s to 79.5 s, three.js
72.0 s to 71.2 s (medians of 3, interleaved).

Original PR #1945 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:34:25 +00:00
danusha2345andClaude Opus 5.5 16f2d357bc fix(go): a project package named like a standard-library package is not the standard library
A Go reference written through a package qualifier was dropped as a
standard-library reference whenever the qualifier matched a name in
the built-in list (errors, log, types, parser, encoding, template, ...).
The check looked at the name alone, never at what the file imports
under that name, so a project with its own package of such a name lost
nearly every call into it: harbor's errors.New(...) through its
lib/errors, prometheus's parser.Inspect(...) through promql/parser.

What the file imports now decides before the name does. A qualifier
the file imports, by its last path segment, the name goimports assumes
for an unaliased import, or an alias, from a package of one of the
project's own modules is a project package, and the reference goes on
to normal resolution. A file that imports the standard library's
package of that name, and a qualifier the file doesn't import at all,
are skipped as before.

Validation: the new test file has 7 cases, 4 of which fail on main;
all pass here, with the native kernel and with CODEGRAPH_KERNEL=0.
A/B against main: harbor 296f304 (its Go tree, 1,611 Go files) +2,889
edges, prometheus a2fda0e +454, 0 removed and 0 retargeted on both,
nodes unchanged. Every added edge was checked against its source: the
line spells qualifier.Name and the file imports the target's package
directory under that qualifier from inside the module (3,343 of
3,343), and a sample of 10 was read by eye. gin and cobra, and the
controls express and flask, are byte-identical. Index CPU on harbor,
median of 3 interleaved: 40.0 s on main, 39.9 s here.

Original PR #2368 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:11:55 +00:00
Eric MinishandClaude Opus 5.5 449eefa7da fix(js,ts): a call on an element of a class field reaches the element's type
A TypeScript or JavaScript method called on an element of a class field,
like this.handlers[type].handle() or this.items[0].save(), kept the bare
method name: a member chain rooted at this collapses to it so the resolver
can read the owner off the enclosing class. A subscript in the chain leaves
the resolver nothing to read, so the bare name exact-matched any project
method of that name, often another class's (this.listeners[type].push(h)
reached an unrelated UndoStack.push).

Both engines now emit this.<field>[k].<m>() as this.<field>[].<m>, and
this['svc'].m() as this.svc.m; any other subscript in a this-rooted chain,
or under window, emits nothing, as arr[0].run() already did. The resolver
reads the element type the field's declaration writes (X[], Array<X>,
ReadonlyArray<X>, Record<K, X>), on the calling class or a base, and
resolves the method on it or leaves the call unresolved: a type parameter
of the declaring class, a union with another type, an array of arrays or a
non-plain element hold no one type. The class type-parameter reader now
reads from the keyword to the list's own >, over several lines and through
braces in constraints.

Our changes: kept escapeRegExp from PR #2769 in the conflict; the
type-parameter reader also accepts the type keyword, since the T['p'] path
passes type-alias owners and the PR as written read State<Item>['current']
as the project class Item (new case in ts-array-annotated-receivers);
CHANGELOG covers JavaScript and asks for a re-index.

Validation: the element tests fail 10 of 11 on main with both engines; the
type-alias case fails on the PR as written and its brace half on main. A/B
against main, kernel built per arm: flask and gin byte-identical. TS:
pixijs 7 links added, 5 retargeted and 5 typed, all right, 15 removed (9
wrong, 6 right only by name: a cast, a mixin's this, a T[] parameter, a
merged mixin member, an index-signature type, an any field); typeorm 3
wrong removed, 1 right lost, 2 retargeted to the declared interface;
excalidraw 1 wrong removed; vscode's base and editor 19 added, 24
retargeted and 35 typed, all right, 45 removed (37 wrong, 8 right only by
name: a getter, an index-signature field, deep chains, a T[] parameter, a
union alias). JS: three.js, playcanvas and pdf.js
lose 288 bare-name guesses, about 230 of them wrong (array, Map and DOM
calls on project classes). Kernel parity holds on every repo with
deferrals unchanged. Index CPU time within noise (pixijs 32.4 s to 33.3 s,
three.js 64.7 s to 67.9 s, medians of 3 interleaved).

Original PR #2766 by @inth3shadows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:50:28 +00:00
danusha2345andClaude Opus 5.5 5c5bfd4051 fix(php): a namespace alias matches whatever case it is written in
PHP reads namespace names without regard to case, so after
use App\Fields as Field; the name field\FirstName is the same class as
Field\FirstName. The qualified-name resolver compared the alias as
written, found no import, and read the name relative to the file's own
namespace instead, so new field\FirstName() stayed unlinked. The alias
is now matched without regard to case.

This lands the first half of the contributor's PR. Its second half
refused an alias that two namespace { } blocks of one file bind to
different namespaces. That also dropped the first block's correct edge,
because the use map is file-wide, and the case doesn't occur in real
code: across 8 PHP repos (33,820 files), 185 files declare more than
one namespace, and none binds one alias twice outside strings. The fix
for those files is to scope each use statement to its namespace block,
which needs block positions the index doesn't keep yet: a braced
namespace gets no namespace node, and neither does a second unbraced
one, so it is left for its own change.

Validation: the new test fails on main and passes here, with the
native kernel and with CODEGRAPH_KERNEL=0. Graphs of php-parser,
composer, psalm, monica, laravel and phpunit are byte-identical to
main, as are the controls express and flask. Index CPU on laravel,
median of 3 interleaved: 53.3 s on main, 52.9 s here. The PHP and
Laravel suites pass in both engines (10 files, 67 tests).

Original PR #2369 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:39:09 +00:00
Colby MchenryandClaude Opus 5.5 9c4794d694 fix(rust): a call keeps its whole receiver chain, and links through method calls in it (#2846)
A Rust method call made through more than one field, or on what another
method returns, was recorded by its bare method name in both extractors:
self.services.store.put("a") became `put`, as did self.store().put("m").
Only a single field kept its receiver (self.store.put). The edge's
refName then said nothing of which value the call goes through, and a
call on a method call's value was never typed, so self.store().put("m")
stayed unresolved. PR #2831 had already made the field-chain calls link
by reading the receiver from the source text, but their names stayed
bare.

Both extractors now keep a receiver that is a chain of fields and method
calls on `self` or on a name, a call written `name()` with its arguments
and turbofish dropped, up to 16 links: self.services.store.put,
self.store().put, vis.map.get, self.0.len, x.get().put. The shapes that
already kept a receiver (x.m, self.m, self.<field>.m, Foo::new().m) are
unchanged; a parenthesized receiver, `?`, `.await`, an index and a longer
chain rooted at a path keep the bare name. The kernel's receiver_chain
mirrors the wasm extractor's rustReceiverChain byte for byte.

Resolution reads such a call as the bare-named call was read before
(rustChainedCallByName, at the top of resolveOneIn), so the frameworks,
the strategies and the scope gates see what they saw, and the edge keeps
the call as written. The chain goes along: the written-receiver hook
reads the receiver the chain names where the call is written
(rustChainedReceiver), so a method call's value is typed from that
method's signature, and a later link that calls the same name again is
told apart instead of being left to a guess by the name.

Validation:
- New __tests__/rust-receiver-chains.test.ts (10 tests): the issue's
  workspace verbatim plus self.store().put("m"), chains whose links call
  the same name through self, a tuple field and a parameter, a call on a
  method's std return value, the recorded shapes and the 16-link cap,
  and a sync that adds the missing method. Red on main's sources, 7 of
  10 failing on both the wasm and the kernel path; green with the fix.
  The Rust kernel parity suite gains a chain case. The extraction test
  that pinned the old collapse now pins the chains.
- A/B against main 865ddf4a (same groups against d8338275), both arms
  with the native kernel, every changed call edge read. Per repo: edges
  that only gained their receiver in refName / same target now by type /
  wrong removed / right added / wrong to right:
  ripgrep 3fce3b5b: 159 / 45 / 6 / 6 / 15
  axum 86aa6153: 115 / 25 / 0 / 14 / 7
  tokio 8a2408ed: 1039 / 124 / 36 / 29 / 27, and 1 right link lost
  lemmy 92e077b5: 190 / 24 / 3 / 1 / 3, 1 right lost, 1 wrong added
  The two lost links and the wrong one follow rules the typed-receiver
  reading already applies to typed locals on main: a function-local glob
  `use` does not shadow a file-level named `use` (tokio), a call on a
  dependency's type reaches only impls of project traits, and `x.into()`
  takes the type's explicit `impl Into` (lemmy). Controls gin and
  express: byte-identical dumps. Nodes are identical on every repo.
- Kernel parity (Rust), ripgrep, axum, tokio, lemmy: 1752/1752 files at
  byte parity, 0 deferred.
- Index cost, interleaved median of 3 (wall): ripgrep 3.15 s -> 3.15 s,
  axum 3.39 s -> 3.42 s, tokio 10.48 s -> 10.54 s, lemmy 5.14 s ->
  5.15 s; CPU within noise.

Fixes #2836

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:07:45 +00:00
Colby MchenryandClaude Opus 5.5 d833827503 perf(resolution): skip receiver typing for JS/TS calls of a method no node is named (#2840)
A JS/TS call of a method that no node in the index is named, like
`list.push()`, `fs.writeFileSync()` or a test library's matcher, still
went through matchMethodCall's receiver-type inference, the TS binding
and import readers, and the class strategies. Every one of them ends in a
lookup by the method's name, so for such a name they can only answer
null. The one exception is the object-literal reader, which follows a
member to the binding the literal gives it: `const api = { alias:
renamed }` makes `api.alias()` a call of `renamed` (Issue #1932).

Root cause: matchMethodCall reads the calling file for the receiver's type
before anything tests the method's name.

Fix: the resolution context answers isAbsentName(name) from the set of
node names warmCaches already loads for the pre-filter. The answer is
exact, like getNodesByName, and false whenever the set isn't loaded. For a
JS-family call whose member is absent, matchMethodCall asks only the
object-literal reader, exactly as the full path would. When the reader
answers, the full path runs unchanged; otherwise the call returns null at
once. CodeGraph.indexFiles now drops the resolver's caches after it stores,
as indexAll and sync already do before they resolve, so a later resolution
never reads the name set from before the new files.

Validation:
- New __tests__/absent-member-calls.test.ts (6 cases). The skip reads
  nothing of the calling file (red on main). The name set answers only
  once loaded. Every path that links a member to a symbol of another name
  is pinned with the member asserted absent: a literal alias, a shorthand
  member bound to a renamed import, an `as const` literal, a holder in the
  same file and one imported, and a renamed re-export read through a
  namespace import. A method added through indexFiles after indexAll still
  links (main links it; the skip without the cache drop did not). A
  fixture indexes identically with and without the skip.
- Graphs byte-identical to main (scripts/dump-graph.mjs: nodes, edges with
  metadata, refs, files), kernel built per arm, on express,
  vue-element-admin, excalidraw and this repository (JS, TS, Vue, Svelte,
  Astro) and on the controls flask, gson, gin, ripgrep and slim (Python,
  Java, Go, Rust, PHP). Default mode (pooled on the two largest) for all
  nine, single-threaded for the JS four, and again after rebasing onto
  the current main. Main run twice is identical too. An incremental sync
  adding a file that declares formerly absent names (1,088 failed refs
  retried) is identical between the arms, with and without git, and
  matches a fresh index.
- Survey of main (instrumented build, nine repos, every language): no call
  whose member no node is named was resolved by matchMethodCall. The skip
  fires on exactly the calls the survey found (12,354 here).
- Profile counters, single-threaded: receiver inference runs 38,626 times
  instead of 50,980 on this repository, 6,460 instead of 9,306 on
  excalidraw, 2,488 instead of 3,586 on express. Outcome counts are equal.
- Cost, medians of 5 interleaved runs: the resolve loop on this repository
  4.54 to 4.41 s pooled and 16.85 to 16.29 s single-threaded, faster in 5
  of 5 paired rounds each; resolution phase -2.1% and -2.3%; whole index
  -1.3% and -2.2%. excalidraw's loop -1.6%, express's -5.0%; the five
  controls within noise (3 runs each).

Fixes #2829

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 12:48:07 +00:00
Colby MchenryandClaude Opus 5.5 f406fe0064 fix(cfml): createObject("component", path) links its component, and .init() on it (#2841)
`new a.b.Batch()` made an instantiates edge, but
`createObject("component", "a.b.Batch")`, the form older CFML uses most,
was extracted as a call to the engine function createObject, which never
resolves. A call chained onto either construction, `createObject(...).init()`
or `new a.b.Batch().init()`, was extracted as a bare `init`, which the
name matcher bound to whichever component's init came first (0.4), or to
none when there are many.

Fix:
- cfscript extraction (tag expressions included): a component
  createObject, with two arguments, one, or named ones, makes an
  instantiates reference under the dotted path as written, not a call.
  Java and other object types, and names built at runtime, are unchanged.
- A call on a construction is extracted as `<component>().<method>`.
- The resolver reads a dotted instantiates path with
  resolveCfmlComponentPath, the rules extends uses, and resolves a chained
  call to the method of that component or one it inherits (matched
  ignoring case; inheritance is wired before calls). A bare component name
  is the one in the calling file's folder, else the only one so named.
  Without both, the call gets no edge rather than a guess by name.
- resolveCfmlComponentPath also accepts a component at the project root
  for a path with one folder segment, its file named like it:
  `wheels.JobWorker` in Wheels' own source, where `wheels` maps onto the
  root. Without it Wheels' core lost calls like
  `new wheels.JobWorker().processNext()` that name matching had found. It
  ranks below any folder match; extends gains the same.

Validation:
- New cfml-create-object.test.ts (5 cases) and a root-component case in
  cfml-inheritance-resolution.test.ts: 5 of the 6 fail on main (the sixth
  pins the variable typing that already worked), all pass.
- A/B vs main d90f6abe on FW/1, BlogCFC, ColdBox, ContentBox, Preside,
  CFWheels and its vendor/wheels core indexed as its own project:
  1,022 edges removed: 563 resolved again to the same target by type,
  444 moved from a method guessed by name to the constructed component's
  own or inherited method, and 15 dropped, all name guesses for
  components outside the index, without the method, or tied. 1,342 added:
  641 instantiates, 38 calls, and in Wheels' core 653 extends and 10
  implements to root components. Oracles over the fix graphs: of the 983
  chained-construction call edges, 965 land in the file named like the
  component and 18 on a method its supertypes declare (each checked); all
  608 dotted instantiates and, in Wheels' core, all 786 dotted
  extends/implements edges target a file named like the path's last
  segment. One site is doubtful: Wheels' core public/docs/core.cfm names
  the application's app.models.Model, which that subtree lacks; one
  `models` folder corroborates a test asset's Model.cfc, as it would for
  extends. express and flask controls byte-identical.
- Index time, median of 4 interleaved runs, CPU with workers, main vs
  fix: Preside 58.7 / 54.5 s, Wheels core 32.2 / 33.0 s. Within noise.

Fixes #2652

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 11:49:44 +00:00
Colby MchenryandClaude Opus 5.5 d90f6abec7 fix(cfml): <cfinclude template> and cfinclude() link the code to the file they include (#2838)
Only cfscript's `include "x.cfm";`, written outside any function, linked
the including code to the template. CfmlExtractor never read
`<cfinclude template="...">`, which the grammar parses as a
cf_selfclose_tag whose name is only in the text after `<cf`, so the tag
form made nothing anywhere. cfscript's include hook read only the string
form, `cfinclude(template="x.cfm")` went through the call extractor as a
call to `cfinclude`, and the function-body walker never extracts import
statements, so an `include` in a function was dropped. Resolution went
through the generic pipeline: the file-name matcher links a relative path
like `../shared/helpers.cfm` only when no other file shares its name, and
past it the symbol strategies could take another template's import node
of the same path, or the including file itself.

Fix:
- `<cfinclude template="...">` is extracted like the script form: an
  import node in the scope it sits in (function, component, or the
  template's file) and an imports reference from that scope.
  `cfinclude(...)` and `include template="...";` make the same import and
  no call, and an `include` in a function body counts, from the function.
- A path built at runtime (`#dir#/x.cfm`) is no import, in every form.
- A CFML include of a template path resolves as CFML does: relative to the
  including file's directory, or from the project root for a leading `/`
  (less sure, 0.85), to the file at that path, ignoring case when one file
  matches. A path that names no file there goes to the file-name matcher,
  as before, but only ever to a file, and never to the including file.

Validation:
- New cfml-include.test.ts: the tag form at component level (same folder,
  ../, upper case, unquoted), in a function body inside <cfif>, in a
  template and from the web root; the script forms; runtime-built paths,
  cfinclude(template=variables.x) and <cfmodule> (no import); a relative
  path with a namesake elsewhere; an unmatched path that another template
  also includes, and a path whose only namesake is the includer. 5 of 7
  fail on main, all 7 pass.
- A/B vs main 51c4124d (CFML has no kernel path) on FW/1, BlogCFC,
  ColdBox, ContentBox, Preside, CFWheels and its vendor/wheels core as a
  project of its own: 127 new imports edges and 152 import nodes (36
  more resolved again to the same file, more surely); 9 import nodes of
  runtime-built paths gone; 1 edge gone, CFWheels' starter-app
  views/wheels/wheels.cfm including itself. No edge moved to another
  target. Every CFML imports edge in the fix graphs (225) targets a file
  named like the include path, and every relative path that stays inside
  the project reaches exactly that file but one: Wheels' Public.cfc
  including views/routetesterprocess.cfm, no file beside it, matched by
  name to public/views/routetesterprocess.cfm as before. express and flask
  controls byte-identical.
- Index time, median of 4 interleaved runs, CPU with workers, main vs
  fix: Preside 52.6 / 50.8 s, Wheels core 28.2 / 29.5 s. Within noise.

Refs #2651 (part 1; part 2 is a feature request and stays open)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:59:26 +00:00
Colby MchenryandClaude Opus 5.5 51c4124dad fix(cfml): a dotted extends finds its component when a test suite mirrors its path (#2835)
A CFML component that extends another by its dotted path, like
extends="contenscms.core.basecore", got no extends edge when a test
suite held a file of the same name in a folder of the same name, such as
a TestBox spec at tests/testbox/tests/specs/unit/core/basecore.cfc.
resolveCfmlComponentPath counts the path's folders that match a
candidate's, from the right. contenscms is a server mapping onto
contens/, so only core matched, for the component and the spec alike,
and a tie gives no edge. The rule the resolver applies elsewhere since
PR #2209, that production code never names a symbol in a test suite,
never ran on this path: it returns before the name matcher's visibility
gate.

Fix:
- A component in a test suite is no candidate for a reference from
  production code (isTestSuitePath, now exported from the name matcher).
  This also keeps application code from extending a test file that
  happens to match more folders.
- A test's tie between a component and a suite file that mirrors its
  path goes to the component outside the test suites. Two production
  components still tie.

Validation:
- cfml-inheritance-resolution.test.ts: 2 new cases (the issue's tree in
  script and tag form, a test extending the component, a mirror that
  matches more folders, a path only a test suite holds, a test naming its
  own suite) fail on main's resolver; all 12 pass with the fix.
- The issue's repro: page and tagpage now extend contens/core/basecore.cfc
  (qualified-name 0.9); on main both stayed unresolved.
- A/B vs main 8fdc8a1e (CFML has no kernel path): FW/1 (305 CFML files),
  BlogCFC (167), ColdBox (668), ContentBox (726), Preside (1,791) and
  CFWheels (the repo, and its vendor/wheels core of 1,528 CFML files as a
  project of its own): no node, edge, reference or file error changed.
  None has a component and a test-suite file that tie on a dotted path.
  express and flask controls: full dumps byte-identical (kernel path).
- Index time, median of 3 interleaved runs, CPU with workers, main vs
  fix: Preside 63.6 / 60.4 s, Wheels core 35.5 / 32.7 s. Within noise
  (main alone ranged 59-67 s and 35-38 s).

Fixes #2650

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 08:47:14 +00:00
Colby MchenryandClaude Opus 5.5 16a8e77c46 fix(ruby): a call reaches the class method or the instance method its receiver calls (#2834)
A Ruby class method (`def self.build`, `def Widget.build`, or a def inside
`class << self`) was indexed exactly like an instance method: is_static 0,
under the same qualified name as an instance method of that name
(Widget::build twice). So every call took whichever of the two the class
declared first. Widget.build could reach `def build`, and widget.build on an
instance could reach `def self.build`. In the repro for issue 2503,
`card = Billing::ChargeCard.new(...); card.call` linked to `def self.call`
rather than `def call`.

Fix:
- Extraction, both engines: the Ruby isStatic hook (isRubyClassMethod in
  languages/ruby.ts, is_class_method in codegraph-kernel/src/ruby.rs) marks a
  singleton_method, or a method whose parent past one body_statement is a
  singleton_class, as static. Every other def is marked not static, the same
  way on both, so the graph only changes for class methods.
- Resolution, Ruby only, where the call says which kind it is:
  rubyMethodsOfKindFirst orders a call's candidates with the wanted kind
  first. A call on a constant (Strategy 1, its inherited walk, and the
  namespaced path in ruby-constant-paths.ts) wants the class method. A
  receiver typed by `x = Widget.new` (resolveMethodOnType) wants the
  instance method. A bare call splits only a real pair, one qualified name
  among its candidates as both kinds, keeping the caller's kind: a block
  may run on another object, so nothing else moves. A receiver known only
  by its name (Strategies 2 and 3) says nothing of its kind and is left as
  it was: rubocop's `cop.cop_name` runs over cop classes. With only one
  kind present, every path picks what it did before.

Validation:
- New __tests__/ruby-singleton-methods.test.ts (6 cases, each on the
  kernel and on wasm) and a parity case in kernel-ruby-parity.test.ts. On
  main 9 of the 18 tests in the two files fail, every one that checks the
  new behaviour; the guards and the old parity cases pass. All pass with
  the fix.
- A/B against main f7820b58 (kernel path): nodes are unchanged except
  is_static going 0 to 1 on the class methods, 167 on sinatra, 358 on
  rubocop and 266 on mastodon's Ruby tree. Edges: none added or removed.
  20 changed their target or confidence: sinatra 10, rubocop 0, mastodon
  10. 19 are right: 13 moved from the wrong twin to the right one, such as
  InlineRenderer.render to def self.render and sinatra's instance methods'
  force_encoding to def force_encoding, and 6 kept their target with
  higher confidence. One kept a target wrong in both arms: a call inside
  sinatra's define_method block. Controls express and flask are
  byte-identical.
- The A/B narrowed two rules. Preferring instance methods for a receiver
  known only by its name moved rubocop's 19 right cop.cop_name edges, and
  dropping other-kind methods from any bare call moved block-DSL calls on
  sinatra. Both are gone: only a real pair is split.
- kernel-parity.mjs --lang ruby is the same on both arms: sinatra 147/147,
  rubocop 1799/1801 (2 deferred), mastodon 3333/3333, 0 diffs. The fix
  arm's kernel and wasm graphs are byte-identical on sinatra and rubocop.
- Index time, median of 3 interleaved runs: sinatra -3.7%, rubocop +0.5%,
  mastodon +0.7%, controls express -2.7% and flask +1.0%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 08:22:15 +00:00
Colby MchenryandClaude Opus 5.5 f7820b584f fix(dart): calls to a private name in another part of the same library link again (#2832)
Dart keeps a `_name` to its library: the library file and every file its
`part` directives name. isVisibleAcrossFiles still listed Dart in
PRIVATE_IS_FILE_LOCAL (added with PR #1745 for Kotlin, Java, C#, Swift,
Scala and PHP), and both Dart extractors mark a `_` function, method or
class `private`. So once the name-matching pipeline had picked a private
candidate in another file, the pick was declined and the reference stayed
unresolved. A library split into parts, generated `.g.dart` and
`.freezed.dart` parts included, lost the calls and type uses between its
files.

Fix:
- dartPrivateVisible (dart-libraries.ts) says whether a file belongs to the
  reference's library, read from `part` / `part of` alone. Unlike
  inSameDartLibrary it needs no pubspec.yaml, since a part names its library
  by a path or a library name. When the reference's library can't be found,
  the name stays file-local, as before.
- isVisibleAcrossFiles takes Dart out of PRIVATE_IS_FILE_LOCAL and lets a
  private Dart candidate through when it is in the reference's library.
- matchByExactName drops another library's private Dart candidates before
  the ranking, as it does for C#, PHP, Java and Go names the file can't see.
  Otherwise another library's namesake can rank first, be declined, and take
  the library's own declaration down with it. Without a pubspec nothing else
  filters a top-level `_name`, and a member's class hierarchy is matched by
  type name, which can reach another library's class of the same name (the
  SDK's wasm and VM `_ImmutableLinkedHashMapMixin`).

Validation:
- New __tests__/dart-part-privacy.test.ts, run with and without a pubspec:
  the issue's library plus a receiver call, a generated part's
  `_$UserFromJson` and `_$Counter`, another library that imports the one
  declaring the names, a namesake beside a part, a part named by library
  name, and a part whose library is missing. 6 of 12 fail on main's
  resolver, 12 pass. With the pre-ranking filter disabled, 1 fails (the
  namesake without a pubspec).
- A/B vs main 58eb57a3, each arm with its own dist and kernel; the kernel
  and wasm paths give the same changed-site sets. Re-run after the rebases,
  on 5b47bc49 (kernel) and 8fdc8a1e (wasm), with the same changed sites.
  Edges added: getx (217 Dart files) 0, bloc (616) +49, riverpod (1,300)
  +542, localsend (393) +1, protobuf.dart (225) +143, apidash (954) +342,
  dart-lang/sdk sdk/lib (480, no pubspec) +207. Nothing removed or
  retargeted, and 1,064 edges changed only their confidence. Every newly
  resolved ref has its edge at the same site; nodes and files are
  unchanged.
- Triage of all 1,284 added edges: 1,274 right. An independent
  part/part-of oracle confirms 1,195 (a top-level `_name` unique in its
  library, a member of the call site's own hierarchy, or a member of the
  receiver's declared type); 79 were checked by hand. 10 are wrong, all
  receiver-name guesses (instance-method 0.65-0.8) that pick one
  implementation of an abstract member, or the first of a tie: the guess
  public names already get, which the file-local rule had hidden. The
  1,064 confidence changes are same-file targets that had another
  library's namesake beside them (exact-match 0.7 to 0.9).
- gin and moshi controls byte-identical, kernel and wasm. Kernel and wasm
  full-index dumps byte-identical on every Dart repo. kernel-parity --lang
  dart: 0 diffs on all seven repos.
- Index time, median of 3 interleaved runs on the kernel path, CPU with
  workers, main vs fix: riverpod 55.6 / 50.3 s, sdk/lib 59.4 / 59.6 s,
  apidash 29.5 / 30.8 s, bloc 18.6 / 18.3 s, protobuf.dart 14.7 / 14.3 s.
  Within noise.

Fixes #2466

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 07:18:05 +00:00
Colby MchenryandClaude Opus 5.5 8fdc8a1efb fix(rust): calls on field chains, struct literals and outside-type paths link by type (#2831)
Issue #2614 reported four Rust calls that linked to a same-named project
method. Case 1 (a slice's contains) was fixed on main by the typed-receiver
work. The other three still reproduced on main in both engines:
vis.map.get("a") on a HashMap field went to Ext::get, J::from(i) under
`use serde_json::Value as J` to a local `impl From<io::Error> for Fail`, and
Xyz { x: 1.0 }.to_val() to Stat::to_val.

Root cause. The extractor keeps one receiver level on a call, so a deeper
field chain (vis.map.get, self.a.b.m, self.0.m) or a struct literal reaches
the resolver as the bare method name, and the typed-receiver reading only
ran for x.m and self.field.m refs. The name strategies then took the one
project get, or one of the two to_val impls. J::from matched no qualified
name, J names no project type, and a single capital letter is read as a
possible generic parameter, so Strategy 3 took the crate's one from. The
same family: under an unrenamed `use serde_json::Value`, Value::default()
linked by qualified name to a project Value's impl.

Fix.
- matchRustWrittenReceiverCall, an exclusive hook beside the as_mut one:
  for a bare-named call, read the receiver from the text at the ref's
  column. Only a field chain rooted at a local, a parameter or self, or a
  struct literal (alone or in parentheses) is read; it is typed with
  rustValueType and the call resolved with rustTypedMethodCall. Any other
  receiver, an unreadable one, or a type no node names (one a macro
  declares) is left to the name strategies. A provided method of a project
  trait the type derives counts (axum-extra's #[derive(TypedPath)]).
- rustFieldValue reads an element of a tuple-typed field (self.pair.0).
- isRustPathCallReachable in gateRustScope: a call through a type path
  whose type the file's use, or the written path, names from std or a
  dependency reaches only the project's impl of a trait for that type, or
  for a trait's path the project's impls of that trait. A path dependency
  the project holds is the project's.

Validation.
- New __tests__/rust-written-receivers.test.ts (12 tests: the issue's
  crate verbatim, chain/literal/derive/path variants, guards): 9 fail on
  main's sources, all pass with the fix. The 27 Rust test files plus
  resolution and cross-language resolution pass.
- A/B against main 5b47bc49 (kernel on), changed call sites triaged as
  removed wrong / added right / wrong to right / same target now by type:
  ripgrep 30 / 18 / 10 / 37 and tokio 281 / 58 / 54 / 141 (every site
  read), axum 18 / 7 / 0 / 7 (every site read), rust-analyzer without
  its parser test data (951 files) 737 / 186 / 434 / 843 (every path
  removal checked by its use; every group sampled). No right link lost.
  Two wrong links added on rust-analyzer: a block-scoped glob
  (`const _: () = { use syntax::ast::*; }`) is read file-wide by the
  existing glob lookup, so a `String` field there reads as ast::String;
  typed locals already misread it the same way on main. Nodes identical
  on every repo; controls gin and express byte-identical.
- Kernel parity (Rust): 1226/1226 files on ripgrep, tokio and axum.
  Resolver-only change, so the kernel needs no mirror.
- Cost: ripgrep median 6.7 s to 6.5 s, tokio 20.2 s to 20.8 s (interleaved,
  median of 3, noisy box); a CPU profile puts about 0.3 s of tokio's 57 s
  busy CPU in the new code and the typed reading it calls.

Fixes #2614

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 06:25:23 +00:00
Colby MchenryandClaude Opus 5.5 dcb985f1d2 fix(ruby): a call on a namespaced class links its caller to the class and its method (#2827)
A Ruby call on a constant written with its namespace, like
Billing::ChargeCard.call(...) or ::Foo.call, recorded no edge. Both
extractors (the kernel's ruby.rs and the wasm one) emitted the receiver's
references ref only for a bare `constant` receiver, so a scope_resolution
receiver got none. The call ref itself, Billing::ChargeCard.call, matched
no resolver strategy either: the method-call patterns reject a `::` in the
receiver. A class used only through such calls showed no callers and no
dependents.

Fix:
- Both engines treat a constant path (a constant, or a scope_resolution of
  constants, a leading `::` included) as a constant receiver and emit the
  references ref under the receiver as written. A path through a value
  (obj::Thing, self.class::LIMIT) still emits none, and .new stays an
  instantiation.
- A new resolver step, src/resolution/ruby-constant-paths.ts, resolves a
  Ruby references ref written as such a path, and a calls ref on one, the
  way Ruby looks the constant up: the class or module around the
  reference, then each namespace outside it, then the top level; a
  `::`-rooted path at the top level only. A call reaches the class's or
  module's own method, else the first one its ancestry declares
  (rubyAncestry, now exported). The step runs before the pre-filter,
  which dropped `::`-rooted names, and it is exclusive: a path the project
  doesn't declare (ActiveRecord::Base) or a class without the method links
  nothing, never a namesake by its last name, and production code never
  reaches a class only a test declares.

Validation:
- New __tests__/ruby-namespaced-receiver.test.ts (8 cases, each on the
  kernel and on wasm, plus a sync case) and a parity case in
  kernel-ruby-parity.test.ts. On main 16 of the 22 tests in the two files
  fail (the rest are guards and the old parity cases); all pass with the
  fix. Reverting only the resolver step, or only the wasm extractor,
  turns 13 and 10 of them red.
- A/B against main (kernel path), every edge triaged: sinatra +23 edges,
  rubocop +168, mastodon's Ruby tree (4,363 files) +676; 0 removed, nodes
  identical. 866 of 867 are right: references by the path as written or
  under an enclosing namespace (each such case checked against the call
  site's own nesting), calls to a class method, a module_function method,
  or an inherited class method. 1 is wrong: Paperclip::Transcoder.make
  reaches the instance method `make`, since a singleton method and an
  instance method share one qualified name today; a bare Transcoder.make
  does the same on main. Controls express and flask are byte-identical.
- kernel-parity.mjs --lang ruby: sinatra 147/147, rubocop 1799/1801 (the
  same 2 deferred on main), mastodon 3333/3333, 0 diffs. The fix arm's
  kernel and wasm graphs are byte-identical on sinatra and rubocop.
- Index time, median of 3 interleaved runs: +0.4% to +1.7%, the same as
  the controls (+0.8%, +1.3%).

Fixes #2503

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 05:20:21 +00:00
Colby MchenryandClaude Opus 5.5 5b47bc4963 fix(js,ts): .tsx and .jsx classes link their overrides as .ts and .js classes do (#2828)
The interface-impl pass links a base class's or an interface's method
to each subclass's override, so a call through the base reaches the
method that runs. It took a class's methods, and its supertype, by
language: typescript and javascript were in its language set, and tsx
and jsx were not. A class in a .tsx or .jsx file got no override
edges, and neither did a .ts class whose base or interface was in a
.tsx file. The pass's own gate, the static/instance split of #2761
and the other JS-family lists already count tsx and jsx, and the
set's history (#494, #708) gives no reason for leaving them out.

tsx and jsx are now in the set, so these classes get the edges that
.ts and .js classes get, statics paired with statics.

Validation: the new test fails 4 of 4 on main 013b91c1 and passes 4
of 4 on the fix, and dropping tsx or jsx alone from the set fails the
tests for it. A/B against main on 11 repos: outline gains 262 override
edges in 42 base-subclass pairs (26 emails overriding BaseEmail's
subject, render and preview; editor nodes, marks and extensions
overriding Node, Mark and Extension) and lexical 27 in 12, grouped by
pair and sampled against the source, none pairing a static with an
instance method. Nodes and all other edges are unchanged. excalidraw,
social-app, insomnia, two .jsx apps and the controls (three, webpack,
flask, gin) are byte-identical: their .tsx classes extend React's or
other packages' classes. explore on outline now leads with the flow
BaseEmail.send -> render -> WelcomeEmail.render, where main finds
none. A sync that adds an interface method, adds an override and
drops a subclass's base matches a fresh index. The pass takes 65-108
ms on both arms, and index time, median of 3 interleaved runs on
lexical, outline, excalidraw and social-app, is +1.7% in total, noise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 04:41:00 +00:00
Colby MchenryandClaude Opus 5.5 58eb57a324 fix(nuxt): navigateTo('/x') links to the page it opens, as router.push does (#2826)
Nuxt's navigateTo was in the Vue resolver's list of Nuxt auto-imports,
which answers each with a sink: an edge from the calling node to itself
at confidence 1. The Vue resolver runs before Vue Router's, so the call's
destination was never read. Every navigateTo, in pages and route
middleware alike, became a self-pointing calls edge, and no navigates
edge was drawn; the sink also kept sync from revisiting the call when a
page appeared (a navigation waits as a failed reference).

Fix:
- navigateTo leaves the auto-import list, so Vue Router reads it.
- A new optional hook, FrameworkResolver.ownsReference: a reference a
  framework answers alone stops at the frameworks' answer, and no import
  or name strategy guesses past it. nuxtResolver owns a bare navigateTo
  that no declaration or import in the file binds. One whose destination
  is computed, names no page or is an external URL stays unresolved,
  rather than linked to a project function that only shares the name
  (elk declares a navigateTo of its own inside a plugin). A file that
  declares or imports its own navigateTo calls that one, as before.
- Vue Router's call reader keeps only destinations that start with "/",
  as its markup reader does: navigateTo(url, { external: true }) leaves
  the app, and the shared reader had matched an absolute URL's path.
- framework-coverage.md: the Vue Router / Nuxt row and a new trap 21.

Validation:
- nuxt-navigate-to.test.ts (6 tests) and a Nuxt case in
  sync-navigation-retry.test.ts (the page appears later; sync equals a
  fresh index). All 7 fail on main. Without the ownership check the
  unresolved case fails (a composable's call reaches a namesake); without
  the "/" rule the external URL navigates to "/".
- A/B against main 9ee6718a (and before that 628f69e2, with the same
  deltas), kernel on, natural-key dumps and a site-keyed edge diff. Every
  changed edge is a navigateTo call site; no node changed. Across
  atidone, elk, nuxt.com, mealie and nuxt/ui, 54 navigateTo calls
  (elk's plugin with its own navigateTo aside, unchanged) lost their
  self-loop; 41 now navigate (42 edges, each checked against its source
  line, all correct) and 13 are unresolved: 6 computed, an API endpoint
  opened with external: true, 4 pages of a Nuxt layer (its own route
  table), and 2 pages the matcher can't read yet (a catch-all and an
  optional parameter).
- Byte-identical: nuxt/movies; vue-vben-admin and vue-element-plus-admin,
  plain Vue apps that declare their own navigateTo; the controls
  svelte-realworld (Svelte) and gin (Go).
- Index time unchanged within noise, medians of interleaved runs, main
  then fix: nuxt.com 5.21 s and 5.24 s (5 runs), mealie 11.52 s and
  11.60 s, elk 3.38 s and 3.55 s (3 runs).

Fixes #2634

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 04:01:39 +00:00
Colby MchenryandClaude Opus 5.5 013b91c1be fix(js,ts): a named class expression is a class with its own members (#2825)
A named class expression, like register(class Widget extends Base {})
or module.exports = class Router {}, was not indexed as a class, as
issue #2499 found. Its methods became free functions, its fields and
supertypes were dropped, and calls in it had no class to start from,
so its this-calls, super calls and own static calls went unresolved or
to a by-name guess, and calls elsewhere guessed into its methods.

Both engines now index a named class expression as a class, with its
methods, properties, extends and implements. An anonymous one is still
passed over. Its head (class Widget extends Base) is its signature,
which marks it as an expression, and it is never exported by itself.

JavaScript binds the name only inside the expression, and the resolver
now scopes it the same way. A reference written outside every class
expression resolves in a view of the graph that hides class
expressions and their members, so new Widget() elsewhere links
nothing, and a same-named class, function or binding keeps its links.
A name only class expressions carry is unknown there. A reference
inside one sees the whole graph, unless it names another class
expression. A class a CommonJS module exports, module.exports = class
Router {} or exports.Store = class Store {}, is the module's value,
which require hands to every importer, so it is not scoped: it reads
as a declaration does, and a require of it reaches its static methods
and its instances' methods. The reader of CommonJS named exports takes
exports.X = class Y {} too. Super calls in a class expression now
resolve through the super-call matcher.

Validation: the new test fails 11 of 13 on main d5fc1fbb and passes 13
of 13 on the fix, and a mutation of each of 7 parts (the view, the
inside check, the name rule, the expression marker, the class node,
the CommonJS export rule, the named-export reader) fails a test.
A/B against main d5fc1fbb on 22 repos (20 TS/JS, flask and gin): 193
classes are added in 10 repos with their members, and 801 edge sites
change in 7 repos, each bucketed and sampled by hand. 563 are jest's
calls to its global test(), 0.4 guesses that move from one wrong
namesake to another: the old one, a class expression's test() in an
e2e fixture, is now a method. Of the rest, 85 are new extends,
implements and type references of class expressions; 74 calls, news
and references move to the member they are written in; 65 calls and
news gain or change a target, inside class expressions (this-calls,
own static calls) and through require (static calls, typed instance
calls, new); and 14 guesses are removed, among them 3 super calls
whose parent the index can't name. 12 repos' dumps, the controls among
them, are byte-identical. Kernel parity holds on 22 repos. Three sync
scenarios match a fresh index. Index time, median of 5 interleaved
runs: lit +0.8%, webpack +2.7%, jest +0.6%; insomnia +12.8%, though it
indexes byte-identically, holds no class expression, and its resolver
time is the same on both arms, so that gap is other jobs' load.

Fixes #2499

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 03:16:07 +00:00
Colby MchenryandClaude Opus 5.5 23a80a847a fix(python): a from-import of a module with no dot in its name links the module's file (#2823)
In Python, `from loader import X` with loader.py at a package root left
the module reference `loader` unresolved, so the importing file had no
imports edge to loader.py. `import loader` and `from pkg.loader import X`
both link their module's file. The from-import's names (X) linked, but
`codegraph affected` and impact could miss the file's dependency on the
module. v1.6.2 behaves the same.

Root cause: resolvePythonAbsoluteModule resolves only dotted module refs.
It leaves bare names to resolveModuleImportToFile, because a bare
imported name like `authentication` in `from app.api.routes import
authentication` is ambiguous on its own. But resolveModuleImportToFile
matches an import by its local name, which for `from loader import X` is
X, never `loader`. So the module of an undotted from-import was never
looked up by its path. When the file also bound the module's name some
other way, the generic import loop resolved it through that binding:
next to `from django.core import mail`, the module of `from mail import
...` became django/core/mail/__init__.py. Django's tests/mail/__init__.py
is the module meant, and the statement's own names already resolve
there.

Fix: resolvePythonAbsoluteModule also takes the module a from-import
names (isPythonFromImportModule), dotted or not. It resolves it from the
package roots, as `import loader` is resolved. Imported names keep their
path, and a module from outside the project still links nothing.

Validation:
- New __tests__/python-from-import-module-file.test.ts, 7 tests: a
  root-level module, a top-level package, a src layout, a module named on
  a continuation line, a submodule imported by name, sync after the module
  file is added (matching a fresh index), and an outside module with a
  same-named nested file (links nothing). With main's import-resolver.ts
  the 6 positive tests fail and the guard passes. Seven assertions in
  python-module-resolution.test.ts listed a file's import edges exactly
  while the module edge was missing; they now include the module's file.
  What they test is unchanged: where the imported name binds, and no
  cycle.
- A/B against main d5fc1fbb. Both arms were built from their own sources
  with the kernel. Rebased onto 628f69e2 (PR #2822, Kotlin only), the fix
  arm's graphs are byte-identical to before the rebase:
  - requests: +2 edges;
  - sqlalchemy: +3,189;
  - django's 2,938 Python files: +103 edges and 2 retargeted;
  - every change is a from-import's module linking its file, and nothing
    else changed;
  - express and gin (controls): byte-identical.
  Triage of all 3,296 added edges:
  - 3,284 point at the file, or the package folder, where the same
    statement's imported names already resolve;
  - the other 12 were read by hand: multi-line statements, re-exported
    constants the name resolver doesn't follow, and a deliberately broken
    name in a Django test fixture, each on the right module file;
  - the 2 django retargets are the mail case above.
- Cost: index CPU medians of 3 interleaved runs, main vs fix: sqlalchemy
  100.7 s vs 100.3 s, django 98.4 s vs 97.8 s.

Found while verifying another Python issue in the sweep; no issue was
filed for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 02:38:35 +00:00
Colby MchenryandClaude Opus 5.5 628f69e275 fix(kotlin): repository().pending() links the method of the type repository() declares (#2822)
Issue #2805: in a Kotlin test class with `private fun repository():
RealRepo`, `repository().pending(1)` went to a test double's
`FakeRepo.pending` in another package, and `val repo = repository();
repo.pending(1)` to the interface `Repo.pending`. The extractor records a
call on an unqualified call's result by the outer method's bare name (a
capitalized inner, `Foo().bar()`, is kept as a chain), so the name matcher
took whichever `pending` came first; and the local's type was read only
from a constructor or an annotation, so the receiver's name guessed it.

The name matcher now reads such a call where it is written
(`repository().pending(1)`, `this.repository()…`, `nullable()?.pending()`)
and finds the inner function the way Kotlin does: a method of the calling
class, of a class it inherits from or one around it, read from their
declarations, else a top-level function the file can name. The call is
resolved on the type that function's signature declares, including what
the type inherits and extensions on it, or links nothing when the type
lacks the method: the #750 rule, as for `Foo.factory().bar()`. A local set
from such a call alone (`val repo = repository()`) takes that type for
the existing typed-local path. Edge cases the A/B surfaced:
- a nested type is the one the function names, `Request.Builder` or a
  plain `Builder` written in `Request`, matched by whole names
  (`MockResponse.Builder` is no `Response.Builder`);
- an extension is the one the calling file can name (Exposed declares
  `Expression<T>.year()` in three modules);
- a type from outside the project, a call inside an extension on one,
  and an extension the index keeps unattached (on a nullable receiver, or
  `fun Dns.Call.x()` filed under `Dns`) keep the name match as before.

Validation:
- New kotlin-call-result-receiver test (9 cases): 7 fail on main, the two
  that guard unchanged behavior pass on both; all pass with the fix on the
  kernel and on the wasm path. Mutation checks confirm the owner-path,
  scoped-nested-type, whole-segment and extension-visibility rules are
  each exercised.
- A/B on main e32021f6, calls by call site, nodes identical:
  okhttp c317930: 82 retargeted, 176 gained, 3 dropped, 516 confidence
  only; JetBrains/Exposed 09f814c: 152 retargeted, 138 gained, 1 dropped,
  1353 confidence only; nowinandroid 2982e63: 1 confidence only. Controls
  gin (Go), nest (TypeScript) and mybatis-3 (Java): byte-identical.
- Triage: retargets go from name guesses to the declared type
  (`request().post()` → `Request.Builder.post`, `varchar(…).index()` →
  `Column.index`, `rowNumber().over()` → `RowNumber.over`,
  `newReader().readInt()` → `Hpack.Reader.readInt`); gains are locals now
  typed (`val connection = connect(peer)`); the 4 drops are Kotlin's
  `apply` scope function, which went to a test helper's or a Gradle
  plugin's `apply`. Every retarget group was checked against the source.
- Cost: whole index, median of 3 interleaved: okhttp 10.5 s -> 10.9 s,
  Exposed 17.7 s -> 16.8 s, within noise.

Fixes #2805

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 02:12:42 +00:00
Colby MchenryandClaude Opus 5.5 d5fc1fbb21 fix(python): calls like reg.get() on a typed local or parameter reach the class's method (#2821)
In Python, a call of a method named like a builtin dict, list, string or
file method (get, update, items, pop, close and the rest of the
builtin-method list) was dropped by the builtin filter before any
strategy could read its receiver. Only a receiver whose capitalized name
is a project class, or an imported project module, got past it. So
`reg = SystemPackRegistry(); reg.get(k)`, `registry = load_registry()`
with `def load_registry() -> SystemPackRegistry`, and a parameter
`reg: SystemPackRegistry` gave no edge, while `self.reg.get()` linked
through the attribute reader of PR #1926.

A call the filter drops now gets one way back, by evidence only:
matchPythonTypedLocalCall. It reads the def that binds the receiver
where the call reads it (the calling def, or one it is nested in). It
types the name only when every binding there gives it the same project
class:
- a parameter gives what its annotation names ("T", Optional[T],
  T | None);
- an assignment gives its own annotation, or what its whole value
  makes: a constructor, or a factory typed by its return annotation or
  by its returns (the attribute reader's pythonValueEvidence and
  pythonFactoryType);
- `x = None` gives nothing either way;
- each class is the one the file's imports name (pythonClassNamed), so a
  class imported from outside the project is none of the project's;
- the method must be the class's own or a declared base's.

Everything else stays dropped: any other binding of the name (a loop,
with, except or comprehension target, a tuple or augmented assignment, a
walrus, an import, del, a nested def or class of the name, a nonlocal
write from a nested def), a value that names no class ({}, json.load(p),
Foo().data(), a union), a rebound callee, a module-level name. These are
the shapes the reporter's follow-up found making wrong edges with a
looser reader. Like an attribute call, the edge records the other files
its answer read (typeFrom), so a sync that edits the factory or a base
re-opens it.

Validation:
- New __tests__/python-typed-builtin-method-calls.test.ts, 20 tests:
  - the issue's three calls; annotated, quoted and Optional parameters;
    items, pop and close; a module-qualified constructor; typed, untyped
    and awaited factories; a None placeholder; a local annotation; a
    closure; typeFrom; two same-named classes; an inherited method;
  - the reporter's trap shapes, which stay unlinked;
  - three sync cases, each matching a fresh index.
  With main's source the 13 positive tests fail and the 7 trap tests
  pass.
- A/B against main 3f58e343. Both arms were built from their own sources
  with the kernel. Rebased onto e32021f6 (PR #2820, outside Python),
  the fix arm's graphs are byte-identical to before the rebase:
  - requests: +15 edges;
  - sqlalchemy: +52 edges;
  - django's 2,938 Python files: +73 edges;
  - 0 removed and no other change in any of them;
  - express and gin (controls): byte-identical.
  All 140 added edges were read at their source: each is a constructed
  local, a factory-typed local or an annotated parameter calling its own
  class's method, or one a declared base has. The issue's fixtures link
  all three calls, and the verification kit's nine trap call sites stay
  unresolved, kernel and wasm alike.
- Cost: a CPU profile puts the new reader at about 1.0 s on django and
  1.2 s on sqlalchemy, around 1% of index CPU. Index CPU medians of 3
  interleaved runs, main vs fix: sqlalchemy 98.6 s vs 98.5 s, django 97.9 s
  vs 99.1 s (single runs vary by several seconds on this shared
  machine).
- kernel-parity.mjs: 295/295 Python files at parity.

Fixes #2583

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 01:50:54 +00:00
Colby MchenryandClaude Opus 5.5 e32021f6fb fix(java,kotlin,csharp,scala): an overload links only the overrides with its parameters (#2820)
The override pass (interface-impl) grouped a class's methods by name and
linked every overload of an interface or base-class method to every
same-named method of the class. Issue #2663's ExampleService got four edges
for two overloads: putTairValue(String) also reached
putTairValue(List<String>, double), and the other way round, so flows,
callers and impact walked into overloads a call never runs. Java, Kotlin,
C# and Scala make an override repeat the parameter list of what it
overrides, and the stored Java and Kotlin signatures already told the
overloads apart; nothing compared them.

A new override-parameters module picks, of a class's same-named methods,
the ones that can override a base overload. The pass asks it only when a
name is overloaded on either side, so a name declared once on each side
links as before. A pair with another parameter count never links. Of those
with the base's count, the ones whose parameter types are written alike
win: qualifiers, annotations, final, default values and C#'s BCL names for
its keywords aside, varargs as arrays. When none is, the ones whose types
can't be told apart are kept: a type parameter of the base method or of a
type around it matches any type in its place (save(T) implemented as
save(String), List<T> as List<String>), a raw or nullable spelling of the
same type may be it, and a method written in another language than the
base is compared by count alone. An override's own type parameter matches
no concrete type of the base, as these languages rule. Java, Kotlin and
Scala lists come from the stored signatures; C# stores none, so its list
is read from the declaration past attributes, comments and string
literals, as the resolver's call-site overload choice already reads one.
Extraction is unchanged, so both engines give the same input.

Validation:
- New override-overload-pairing test (12 cases, Java, Kotlin, C#, Scala):
  11 fail on main, the single-method case passes on both; all pass with
  the fix on the kernel and on the wasm path.
- A/B on main df1cd1e1, override edges main -> fix; nodes and every other
  edge identical in each: hadoop-common-project (hadoop 2c01c36f)
  4651 -> 3762 (-1045 +156), guava 5765 -> 4360 (-1427 +22), mybatis-3
  1126 -> 898 (-278 +50), Exposed 999 -> 884 (-115), okhttp 709 -> 665
  (-44), Newtonsoft.Json 527 -> 404 (-125 +2), serilog 98 -> 91 (-7),
  twitter/util 838 -> 699 (-139). Controls gin (Go) and nest (TypeScript):
  graph dumps byte-identical.
- Triage: of the 3180 removed edges, 1761 pair methods with different
  parameter counts and 1419 methods whose concrete parameter types differ;
  every one of the 230 added edges is a same-parameter override the
  per-class cap of 40 had cut off. An independent re-read finds no
  same-typed override missing next to any removed pair, and each group of
  base methods that lost every link into a class was checked against the
  source (the class overrides other overloads only, or the method is final
  or static).
- Cost: the pass alone, median of 9 alternating runs, +3 to +96 ms per
  index (guava 715 -> 811 ms, hadoop-common 501 -> 555 ms); whole index,
  median of 3 interleaved: Newtonsoft.Json 12.3 s -> 11.9 s, okhttp
  12.3 s -> 12.9 s, within noise.

Fixes #2663

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 00:35:03 +00:00
Colby MchenryandClaude Opus 5.5 ab92c90a54 fix(js,ts): a super.method() call links to the method of the class's parent (#2818)
A TS/JS call written super.run() is recorded under its bare name, as
issue #2497 found. Inside an override of run every strategy found the
calling method itself, and the self-edge gate declined it, so the call
was unlinked; elsewhere it went to whatever method had the name, often
the class's own or a sibling class's in the same file, and, for a
parent outside the project like HTMLElement, Set or Promise, to a
project namesake.

A call written on super where it starts is now resolved through the
class's resolved extends edges, which the resolver persists before it
resolves calls: the nearest ancestor's method of that name, a static
one for a call in a static member, or nothing when no ancestor in the
project has it. The call may sit in a method, a class field, or an
arrow function inside either. One the index can't place in a class (a
class expression's method, an object literal's) keeps the old path, as
does one after a class keyword in the member, outside comments and
strings. The self-edge gate now reads a TS/JS call at its own column,
so a this.run() on the line of a super.run() keeps its edge.

The issue's first part, a method call on the receiver of an aliased
constructor, already resolves on main since PR #2661 and is unchanged.

Validation: the new test fails 3 of 4 on main 68fcf2f1 and passes 4 of
4 on the fix; the fourth pins the class-expression guard, and a
mutation of each guard fails a test. A/B against main on 22 repos (20
TS/JS, flask and gin): 660 call sites change, all super calls, checked
against each class's extends chain and sampled by hand. 643 gain an
edge to an ancestor's method (304 in three.js, like super.copy(source)
reaching Object3D.copy; 210 in webpack, 85 in lit); 2 move off the
class's own method to the parent's; 4 keep their target, now by the
parent; 11 lose a guess, where the parent is a builtin or a library's
class, like webpack's SortableSet extending Set, or a mixin's
parameter. Nodes and all other edges are
unchanged; 14 repos' dumps, the controls among them, are byte-identical.
A sync that moves a parent, swaps a parent or adds an override to an
intermediate class matches a fresh index. Index time, median of 3
interleaved runs on three.js, webpack, jest and insomnia: +1.7% total,
noise; 7 interleaved rounds on three.js alone: -1.5%.

Fixes #2497

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 22:11:29 +00:00
Colby MchenryandClaude Opus 5.5 68fcf2f1c7 fix(js,ts): static and instance methods override only their own kind (#2813)
The override-dispatch pass (interface-impl) linked a base class's
method to every subclass method of the same name, so a static method
counted as overriding an instance one, or the reverse, though a call to
one never runs the other (issue #2761). TypeScript nodes recorded
static already, so there the pass alone was wrong. JavaScript nodes
never did: the extractor had no isStatic hook and the kernel answered
nothing for the JS variants, so a JS class's static s() and s() were
both stored as instance methods.

JavaScript members now record their own static keyword with the hook
TypeScript uses (hasStaticModifier, which also keeps a method named
static() an instance one), and the kernel gives the JS variants the
same answer, so both engines agree. The pass skips a JS/TS base and
override whose static flags differ; a static method still overrides a
static one, and an instance method an instance one.

Validation: the new test fails 5 of 10 on main 08478eea (JS and JSX
static flags in both engines, and the override pairs) and passes 10 of
10 on the fix. A/B against main on 20 repos: webpack's lib loses 15
edges, every one a cross-kind pair, such as Module's instance
deserialize(context) to the static deserialize(context) factories of
NormalModule and six other modules, or Module's static
getSourceBasicTypes(module) to RuntimeModule's instance
getSourceBasicTypes(); the same-kind pairs stay and no edge is added.
No other repo's edges change. Nodes change only in is_static, 0 to 1,
on 315 JavaScript static members, none in TypeScript or another
language; the flask and gin control dumps are byte-identical. Kernel
parity over the 20 TS/JS repos: 16,612 of 16,873 files byte-parity,
none with diffs, the rest deferred to wasm as before. Index time,
median of 3 interleaved runs on vite, nuxt, webdriverio, insomnia and
webpack: -1.1% total, within noise.

Fixes #2761

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 20:08:41 +00:00
Colby MchenryandClaude Opus 5.5 efb091ec2f fix(liquid): editor-saved Shopify JSON and {% sections %} groups link their sections (#2812)
Shopify's theme editor writes a block comment ("IMPORTANT: The contents
of this file are auto-generated...") at the top of every JSON template
and section group it saves. The Liquid extractor read theme JSON with
JSON.parse, which rejects comments and trailing commas, so such a file
linked none of the sections it lists, and in an editor-saved theme most
sections looked unused. All 14 of Shopify/horizon's templates and
section groups carry the comment, so none of them linked. The links a
JSON file did produce all sat at line 1, column 0.

The same report found two more gaps. A layout's
{% sections 'header-group' %} tag, which renders the section group
sections/header-group.json, was never read. And a {% schema %} written
inside {% comment %} or {% raw %} was indexed as the section's own
schema, because only the tag scanner skipped those regions.

Fix:
- Read theme JSON with jsonc-parser's parseTree, which accepts
  comments, and allow trailing commas. Shopify's theme docs say theme
  JSON supports both, and Shopify's theme-check parses it with the same
  options. Any other parse error still links nothing.
- Put each section reference at its "type" value's line and column.
- Read the plural sections tag wherever the singular one is read:
  braced, whitespace-trimmed, or as a line of a {% liquid %} tag. It
  links to sections/<group>.json, and its component is named
  ::sections:<group>, apart from a section of the same name. The
  Shopify theme matcher already keeps any sections/ path inside the
  file's own theme, so a group resolves only there (its doc comment now
  says so).
- Compute the comment and raw regions once per file and share them
  between the tag scanner and the schema extractor. A schema inside one
  is skipped, and the search resumes after the region.

Validation:
- 10 new regression tests in __tests__/extraction.test.ts fail with
  main's extractor and pass with the fix. Re-run on 08478eea: 30 passed
  and 10 failed with main's extractor, 40 passed with the fix. 3
  strictness guards (a missing brace, an unclosed comment, a missing
  comma) pass on both.
- A/B on base 80c36800, each arm built from its own sources with the
  kernel: 8 theme repos (Shopify/dawn, Shopify/horizon,
  Shopify/skeleton-theme, ChumsInc/chums-impulse-dev,
  agencecinq/cinquante-et-un, noclocks/shopify-devstore-theme,
  panoply/syncify, Shopify/theme-tools) and 2 controls (express,
  requests). The controls and skeleton-theme are byte-identical, and no
  node or edge is removed anywhere. All 299 changed reference edges
  were checked against the source text: 299 right, 0 wrong. 197 are new
  links (horizon 25, chums-impulse-dev 138, cinquante-et-un 11,
  shopify-devstore-theme 16, dawn 2, syncify 2, theme-tools 3), 14 of
  them from sections tags, and 102 moved from 1:0 to their "type"
  value. 22 sections tags add 44 import and component nodes. The only
  new failed refs are a "multirow" section that cinquante-et-un's theme
  does not have, and 8 sections tags in theme-tools' prettier-plugin
  test fixtures that name a group no file provides.
- Cost: an extractor microbenchmark over 1,386 Liquid files (8 MB),
  median of 15 runs in 3 interleaved rounds, gave 436 ms on main and
  439 ms with the fix. Index wall time, median of 3 interleaved runs,
  main vs fix: horizon 2891 vs 3064 ms, chums-impulse-dev 2429 vs 2368,
  syncify 3846 vs 3810, theme-tools 6698 vs 6620. The byte-identical
  controls moved as much (express 2312 vs 2445, requests 1946 vs 2080),
  so that is noise on a shared machine.
- Rebased onto 08478eea, which adds only Pascal (PR #2810) and JS/TS
  resolution (PR #2811) changes. There the Liquid tests pass, and
  re-indexing the 8 theme repos gives graphs byte-identical to the
  pre-rebase fix arm, except for theme-tools' TypeScript calls, which
  PR #2811 changes.
- Kernel parity: not applicable, the kernel does not extract Liquid.

Fixes #2494

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:43:54 +00:00
Colby MchenryandClaude Opus 5.5 08478eea53 fix(js,ts): a receiver declared outside the calling function goes by its declared type (#2811)
react-dom's root.render(<X />) linked to the project's one class
component render() when root was declared outside the calling
function, a module's let root: Root | null or a let popupRoot: Root in
the function around the one rendering, or held what createRoot(el)
returned (#2589). The receiver reader scans only the calling function,
so the type that rules a react-dom Root out was never read, and the
call went to the one same-named method by its name.

A TS/JS receiver the calling function doesn't type, in a function
nested in another or one a module-scope variable names, is now read by
its binding in scope at the call. A per-file brace-scope reader finds
the innermost binding of the name: a const/let/var, a parameter, a
destructuring, a for head, a function or class, a lone or destructured
arrow parameter. A declaration or parameter written with a type, or a
const/let/var whose whole value is a new of a class, types the call as
the calling function's own declaration would; an annotated object
literal stays its own members', and a new of a parameter names no
class. A class component's render() and lifecycle methods are never a
by-name guess for a receiver other than this or super, since React
calls them, which also covers an untyped const r = createRoot(el). A
first version that also took any value a package function returned to
have no project method was dropped: it unlinked correct calls through
useContext(...) values in social-app and outline.

Validation: the new test fails 3 of 3 on main 89cd08fa and passes on
the fix. A/B against main on 14 TS/JS repos: 35 edges removed,
every one a wrong guess on a value whose type outside the calling
function comes from outside the project (rolldown's watcher, node's
Server and ChildProcess, playwright, unstorage, zod, NodeJS.WriteStream,
HTMLElement) or nitro's ReactDOM.createRoot root; 18 added and
7 retargeted, all precise (memfs's onAbort watcher.close() now
reaches FsaNodeFsWatcher::close, jest's currentSpec.cancel() Spec's);
92 same-target edges typed at 0.9 instead of guessed. The flask and
gin control dumps are byte-identical. Index time, median of 8
interleaved runs on vite, nuxt, webdriverio and insomnia: +1.7% total,
within noise (-3.6 to +5.2% per repo). A CLI sync that retypes a
module binding and adds files matches a fresh index.

Fixes #2589

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:11:37 +00:00
Colby MchenryandClaude Opus 5.5 f3fdba2955 fix(pascal): calls inside nested Delphi routines are indexed, each under its routine (#2810)
A Pascal routine may declare routines of its own between its header and
its begin. The extractor walked only a routine's body block, so a nested
routine's code was never read: what it called was missing, and a call to
it from the routine that declares it went by name to any routine
elsewhere with that name (in mORMot2's tests, Test, Check and One calls
landed on other classes' methods).

A nested routine is now a function node qualified under the routine it
is nested in (TFoo::Bar::Inner), contained by it and walked with itself
as the caller. It sees the parameters and locals of the routines around
it that are declared above it. Its name means it in the routine that
declares it, in the routines nested there after it and in its own body,
ahead of the class and the unit. A call resolved to a nested routine
from anywhere else is what the call's own scope declares, or nothing. A
nested routine's bare calls reach the class of the method it is nested
in first. Where a routine's tree has errors and its parse swallowed the
unit's routines after it (column 0, or after the body), those are walked
as the unit's own; of two bodies under {$IFDEF} the first is walked, as
before.

Validation against origin/main 7b50ca83, nodes and edges removed/added:
SynEdit +239, -6/+1127; HeidiSQL +404, -178/+2352; mORMot2 +574,
-956/+9565; DelphiMVCFramework +134, -279/+1049; DWScript +434,
-346/+3351; leveldb and SDWebImage byte-identical. Sampled: calls to
nested routines 38/38 right, calls made in them 44/59 (ordinary name
matching's misses), removed edges 30/30 wrong links. Index time within
about 5%, in proportion to the code now walked.

Covers the nested-routine part of #2550.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 17:50:35 +00:00
Colby MchenryandClaude Opus 5.5 7b50ca8321 fix(pascal): Delphi calls to compiler intrinsics and plain data no longer link namesakes (#2803)
A Pascal call to a name the compiler or the System unit gives meaning
to (SizeOf, TypeInfo, High, Low, Length, Inc, Dec, Assigned, Ord, Exit,
Include, Move, Assert, typecasts like Pointer(P)) went to name
matching, which linked any project member of that name: mORMot's
TRttiProp.TypeInfo, a record's length or high field, a property named
Pointer. The built-in name check compared case, so length(S) and dec(N)
got through too. And a call that resolved to a field, property or
variable linked whatever data had the name.

Now a bare call to a System name links only to a declaration its scope
reaches: the class and its ancestors, the unit, the units it uses last
first, skipping a unit's stand-in declared under {$IFDEF} or {$IF}. A
call resolved to data stands only when the data's declared type is a
routine type (written out, a project alias to one, generic or on two
lines, or an RTL procedural type by name), retargeted to what the scope
declares. A name the calling routine declares itself links nothing; a
name the scope doesn't declare at all (a with object's member, a
declaration the parse misplaced) links to the one routine-holding field
or variable of that name. Built-in names compare without case; the RTL
namespaces keep their spelling, except system. Scope lookups now see a
unit whose unit header the parser lost, by its file name (its indented,
lost class members aren't taken for its routines), and an include file
shares its unit's scope and uses clauses. A with body's bare call to a
System name is not extracted, since it may be the with object's.

Validation, edges removed/added against origin/main: SynEdit -243/+50,
HeidiSQL -858/+296, mORMot2 -9039/+987, DelphiMVCFramework -2420/+242,
DWScript -705/+222; leveldb and SDWebImage byte-identical. Sampled
precision of calls to data, before and after: 20/50 and 50/50; of calls
to System names: 2/50 and 47/47. Sampled removed edges: 40/40 wrong.

Follow-up to #2514.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:20:06 +00:00
Colby MchenryandClaude Opus 5.5 5d81d292fa fix(js,ts): a call or new never lands on an interface or a type alias (#2801)
A call or `new` that a strategy resolved by name or through an import could
target a TypeScript interface or type alias, which nothing can call or make
an instance of. memfs's fs-core `result.ts` declares `interface Ok` beside
`function Ok` (and `Err` beside `Err`), and `Ok(...)` through an import
reached the interface. A `new` of a global that a project only declares a
type for (`new Response()`, `new Worker()`, `new Event()`) was linked by
name to that type, and jest's `write()` parameter call to a `type write`.

Root cause: no rule on what a call may target. Whichever strategy found the
name picked among same-named nodes by order, and nothing stopped it on a
type.

Fix (gateTargetKind): for the JS family, a `calls` or `instantiates` result
whose target is an `interface` or a `type_alias` moves to the value the
target's file declares under the same name (a function, class, component,
constant or variable, first by position): `function Ok` beside
`interface Ok`, a class merged with an interface. With no such value the
result is dropped, and the reference stays unresolved.

Validation:
- __tests__/call-value-targets.test.ts (3 tests): all 3 fail on main and
  pass on the kernel. Each of 5 rules removed in turn fails at least one.
- Sync equals a fresh index (CLI) when the value beside a type is added or
  removed, and when a project type for a global or a parameter appears.
- A/B on 14 TS/JS repos and 2 controls: memfs's 11 Ok/Err calls move
  from the interfaces to the functions; 165 `new` of globals dropped (vite
  27, nuxt 57, webdriverio 26, insomnia 47, jest 7, memfs 1) and jest's
  `write()` call on `type write`. Every dropped `new` names a global
  (Worker, Event, Response, Request, Headers, ReadableStream, Node's Module,
  mocha's Context/Suite) or a local variable. vben, outline, social-app,
  t3-turbo, nitro, vue3-antdv-admin, pathe, c12, flask and gin are
  byte-identical.
- 91 related test files pass (2,178 tests).
- Cost: fresh index within noise (median of 3, interleaved): vite 9.07 s
  to 8.74 s, nuxt 11.71 s to 10.93 s, insomnia 15.26 s to 15.11 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 13:49:07 +00:00
Colby MchenryandClaude Opus 5.5 e8ddc4e2be fix(js,ts): imports of a repo's own packages follow their package.json entries (#2799)
Issue 2502: a pnpm (or npm, Yarn, Bun) workspace member that declares its
entry with `main`, `module`, `types` or `exports`, like
`"main": "./src/index.ts"`, has no index file at its root, so
`import { caseStatus } from '@acme/core'` failed and its calls fell back to
name matching, where a same-named private function nearer the caller took
them. Issue 2456: a sibling package another declares as a path dependency
(`"acme-shared": "file:../common"`), with no root `workspaces`, was not
known as a package at all.

Root cause: workspace-packages.ts mapped a member's name to its folder only
(its header said it did not yet honour `exports` or `main`), and the
resolver probed `<folder>/index.*`. Path dependencies were read only from
ohpm manifests.

Fix:
- workspace-packages.ts reads each package's declared entries: `exports`
  (subpaths, `*` patterns, fallback arrays, nested conditions, read with
  import/require/node/module/default first, other conditions next, types
  last) and the root's source/module/main/types/typings. It reads `file:`,
  `link:` and `portal:` dependencies from every package.json in the
  existing manifest walk: the root manifest's for every file, any other's
  for the files under it, as npm installs them.
- The resolver takes the first declared target, `exports` before the
  main-style fields, that the index holds or that is build output the
  index lacks, read as the source it is built from (dist/, build/, lib/,
  out/, es/, esm/, cjs/ mirrored under src/, then without a format folder).
  Then the package's folder, as before, and a path into its build output
  (`@acme/utils/lib/path`) read as its source. A file a local build left on
  disk never wins over an indexed source, and no mapping links nothing.
- Sync: a full reconcile reads the manifests again and, when their digest
  moved, re-opens every JavaScript-family reference, as a moved Python root
  set does. A file added inside a package re-checks the imports of that
  package that landed elsewhere in it. A full index drops the cached map.

Validation:
- __tests__/workspace-package-entries.test.ts (26 tests): 25 fail on main,
  the control passes; all pass on the kernel and with CODEGRAPH_KERNEL=0.
  Each of 17 rules removed in turn fails at least one test.
- Sync equals a fresh index in 15 CLI scenarios (entries, subpaths, a
  member renamed, added or leaving the globs, file: deps added, removed or
  retargeted, an entry's file appearing). On real repos: two vben manifest
  edits (237 edge rows move) and a vite playground file: edit, synced graph
  identical to a fresh index.
- A/B on 14 TS/JS repos and 2 controls: vben +2,955 edges, insomnia +2,222,
  webdriverio +2,472, jest +1,700, nuxt +706, memfs +470, vite +251,
  t3-turbo +47, social-app +40, vue3-antdv-admin +8, with name-matched
  namesakes corrected; nitro, outline, pathe, c12, flask and gin are
  byte-identical.
- Cost: fresh index within 3% (median of 3, interleaved); a full sync reads
  the manifests, +30 to 160 ms on these monorepos.

Fixes #2502
Fixes #2456

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:54:48 +00:00
Colby MchenryandClaude Opus 5.5 407efb1c65 fix(pascal): Delphi calls written without parentheses link to the routine (#2797)
Delphi calls a routine that takes no arguments without parentheses
(`Before;`, `X := FProvider.Value + 1`, `if Ready then`), and only a
statement-level `Obj.Method;` was recorded, so most call sites were
missing from callers, callees and impact. Bare names in arguments and
assignments were captured as function values and matched by name alone,
which linked locals and parameters to other classes' methods.

The extractor now records each name written without parentheses with
how it is used (a statement, a value, an event's method, a receiver),
the routine's class, and the declared type of a receiver the routine
declares. It leaves out what the routine declares, data the class and
the unit declare, names the class's code assigns or passes to Inc/Dec,
names Delphi's conventions give to data, and anything in a `with` body
but an event's method. A new resolver links such a name only to a method
or function Delphi's scope rules reach: the class and its ancestors, the
unit, then the units it uses, last first, or a method of a receiver's
declared type. Data of that name links nothing. A function runs (calls);
a procedure read as a value, an event's method, and a routine that needs
arguments written without them are method pointers (references). Where
the tree has errors, the class and declarations come from the source
text, and a routine's own header is no call. `class sealed (TBase)` and
`class abstract (TBase)` heads now name their ancestors. Pascal function
values capture only `@Handler`.

A/B against main 8073637d (SynEdit, HeidiSQL, mORMot2, DWScript,
DelphiMVCFramework): added edges 3011/4626/6501/5092/6776; main's
name-matched function values now linked by scope 2387/2727/948/726/712,
114/281/554/342/586 of them as calls; removed 85/139/802/333/707,
triaged as main's false positives (locals and parameters linked by
name, other classes' methods, routine headers misparsed in broken
trees) and own-class retargets, less deliberate losses (reads in `with`
bodies, lowercase or type-like names read as values). Sampled precision
of added edges: 196/196. leveldb and SDWebImage byte-identical. CPU,
median of 3 interleaved: +15.2%, +16.2%, +8.7%, +12.9%, and +0.8% for
DelphiMVCFramework (one run each).

Fixes #2514

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:10:10 +00:00
Colby MchenryandClaude Opus 5.5 8651990abd fix(js,ts): an import spelled with a trailing slash or a ./ segment links its file (#2792)
Issue 2577: `import('@/widgets/Chart/')`, `from 'src/widgets/./Chart'`, a
doubled slash, a `..` after an alias, and a workspace package's
`from '@acme/ui/'` lost the edges the plain spelling gets. The file import
was dropped, the names were matched by name alone, and the lazily loaded
component rendered the importing file's own `lazy(...)` constant.

Root cause: the fallback-alias, workspace, direct-path and own-config
branches of the import resolver join the specifier as written and probe
the result through tryWithExtensions. `fileExists` falls back to the disk,
which accepts any spelling of a file, so the resolver returned
`src/widgets/Chart//index.tsx` or `src/widgets/./Chart/index.tsx`, and every
lookup keyed by the indexed path missed it. The relative and tsconfig-path
branches go through path.resolve and were already fine.

Fix (import-resolver.ts): tryWithExtensions spells each candidate as the
index keys files (indexedSpelling): for the ES module family, a path with a
doubled slash or a `.`/`..` segment is posix-normalized before it is probed
and returned. That is how Node, tsc and the bundlers read a specifier. A
trailing slash still names only a folder, because `Chart/` plus `.tsx` is
no file. Other languages are untouched: a C include's `a/../b.h` follows the
folder on disk, as PR 2610 noted.

Validation:
- __tests__/import-path-spelling.test.ts: 4 of 5 fail on main (the plain
  spelling passes). They cover the issue's spellings, a doubled slash, `..`,
  a trailing slash beside a same-named file, and a workspace member. All
  pass on the kernel and with CODEGRAPH_KERNEL=0. Dropping the
  normalization, or its doubled-slash check, fails 4.
- Sync equals a fresh index (CLI) when the folder arrives after its
  importer, when the importer is re-spelled, when the folder is deleted
  and when the target is edited.
- A/B on 11 TS/JS repos plus 2 controls, both arms with the kernel:
  pulse-sms-web (53 imports like '@/utils/') gains 65 edges and corrects
  7, including 2 imports that had name-matched to same-named API classes;
  react-one gains 11 file imports and corrects 2 bindings. All 85 are right.
  The other nine (jvue-admin, vue-invoice-app, vue3-antdv-admin, v-ui,
  vue-blog-admin, dreammart, vite, outline, social-app) and the flask and
  gin controls are byte-identical.
- Cost: fresh index, median of 3 interleaved: vite 7.59 s main vs 7.49 s,
  outline 16.63 s vs 16.94 s.

Fixes #2577

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:48:56 +00:00
Colby MchenryandClaude Opus 5.5 e69546e4a3 fix(js,ts): code outside tests resolves as if Jest manual mocks were not there (#2790)
Issue 2455: when production code imports a workspace package the resolver
cannot follow (`import { formatLabel } from 'acme-shared'`), its names fall
back to name matching, and a Jest manual mock beside the code
(`__mocks__/acme-shared.ts`) is the nearest namesake. So the call, the
import's names, type references, constructions and `extends` all linked to
the mock, and the real function lost its callers. The visibility gate keeps
test suites from production code, but `__mocks__/` is not one of them.
jsx-render, Vue templates and the event-emitter pass pick by name with
their own lookups, which no gate covers.

A manual mock stands in for a module only while Jest or Vitest runs, and it
re-declares that module's names by design. Rejecting it after the ranking
picked it (what the gate does for `__tests__/`) leaves the call unlinked even
where the real function is the only other candidate. It also leaves sync
behind a fresh index: deleting the mock re-opens nothing, while a fresh
index then links the real function (main has this today for `__tests__/`).

Fix:
- ReferenceResolver.resolveOne resolves a reference from code that is not a
  test (isTestPath) on a view of the graph without manual mocks, when the
  project has any. Every lookup by name, lower-case name, qualified name,
  kind and method answers as if no `__mocks__/` file were indexed. Only
  names a mock declares are filtered, and those answers are cached. The
  view is swapped in as this.context for the call, so every strategy
  (frameworks, imports, name matching, fuzzy, function refs) reads it, and
  clearCaches drops it with the memos keyed by it. Test code keeps the
  whole graph.
- jsxChild, the Vue template resolver and the event-emitter handler lookup
  skip a mock's component or function for a non-test file
  (isManualMockHiddenFrom).
- The synthesized-edge merge drops any edge from non-test code into a node a
  manual mock declares, whatever pass made it.
- An import that names the mock's own path (`import x from
  './__mocks__/uuid'` in a vitest setup file) still links. Jest's
  `moduleNameMapper` and automock are not read: they act only while Jest
  runs, so they never change what production code calls.

Validation:
- __tests__/jest-manual-mocks.test.ts: 5 of 6 tests fail with main's
  sources. Each of five rules fails a test when removed: the view (three
  tests, the in-process sync case among them, which adds and deletes a
  mock), the JSX, Vue and event lookups, and the merge guard.
- Sync equals a fresh index (CLI) when the mock or the real module arrives
  later, when the production file is edited, and when the mock is deleted.
- A/B on 8 TS/JS repos plus 2 controls, both arms with the kernel. Edges
  from production code into mocks: webdriverio 23 to 0, outline 21 to 0,
  social-app 3 to 0, insomnia 19 to 13 (the 13 left are setup-vitest.ts
  importing mocks by path). webdriverio gains 23 right edges and 142
  confidence raises that the mocks had blocked. jest, nuxt, flask and gin
  are byte-identical. On webdriverio, production code's edges equal main's
  on a copy without the mocks, 20,885 of 20,885 with metadata. insomnia
  matches the same way, apart from the 13 path imports.
- Cost: fresh index, median of 3 interleaved: webdriverio 12.67 s main vs
  12.71 s, insomnia 12.84 s vs 12.60 s.

Fixes #2455

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:10:10 +00:00
Colby MchenryandClaude Opus 5.5 0953084bc5 fix(pascal): the receiver calls of a chained Delphi call are indexed (#2787)
A Pascal call whose receiver is itself a call recorded only its outer
method: in `MD5FileSized(TMeta.ExtendedPath(APath)).IsSame(AKey)` the
receiver call `MD5FileSized` and the `TMeta.ExtendedPath` in its
arguments were dropped, as were paren'd calls deeper in a chain
(`A(1).B.C`) or in an index (`List[GetIndex(x)].Free`). The extractor
read a receiver call only for a `TFoo.GetInstance()` factory chain.

The extractor now walks a call's receiver chain and records each paren'd
call in it, with the calls in its arguments and indexes. A paren-less
link stays a read. A link inside a chain is recorded only when it can be
named precisely (`Obj.Method`, a bare routine, a `TFoo.Factory().X`
chain), never as the bare method name a chain like
`Client.New.BaseURL(…)` leaves, which matched namesakes. The calls main
already read keep their names.

Receivers are very often typecasts (`TButton(Sender).Caption`), which
read like calls. Delphi builds objects only through a constructor
(`TFoo.Create`), so a bare-name Pascal call that resolves to a type is a
cast: resolution now gives it a `references` edge to the type instead of
`instantiates` or `calls`. That also corrects the edges main already
made for value casts (`F := TFoo(Obj)`).

Validation:
- New __tests__/pascal-chained-calls.test.ts (7 tests, the issue's unit
  included): all 7 fail with main's sources. Without only the resolver
  rule the typecast test fails; without only the chain-link gate the
  fluent-chain test fails.
- A/B against main 88ebefa1, both arms built from their own sources,
  diffed by natural keys:
  - Every removed edge is a cast changing kind at the same site, with
    the same source and target (SynEdit 491, HeidiSQL 1,262, mORMot2
    4,832, DWScript 3,115, DelphiMVCFramework 2,761), apart from 10
    function references that moved to an earlier use in the same
    routine.
  - New edges: SynEdit 55, HeidiSQL 249, mORMot2 1,196, DWScript 690,
    DelphiMVCFramework 2,727. Mostly casts read as references to their
    type (14 of 14 sampled right) and receiver calls.
  - 178 new call edges sampled: 140 right. instance-method 30/30,
    qualified-name 10/10, bare functions 26/30, bare methods 70/92,
    calls landing on a property, field or variable 0/12. The wrong ones
    follow rules main applies to every call: the TypeInfo intrinsic
    (main already links 1,709 such calls to a project method), calls
    through local procedural variables, and calls now read inside a
    receiver's arguments, named as main names them.
- Controls leveldb (C++, kernel) and SDWebImage (Objective-C, wasm) are
  byte-identical in dump-graph.
- Cost within noise: index time medians of 3 interleaved rounds,
  HeidiSQL 7.10 to 7.30 s, mORMot2 18.80 to 18.90 s, DWScript 20.00 to
  19.40 s wall.

Fixes #2547

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:17:14 +00:00
Colby MchenryandClaude Opus 5.5 7180fd0666 fix(js,ts): a dynamic import from Node or a package no longer links to a project namesake (#2788)
Issue 2491 was reported against 1.6.1: `import { resolve } from 'node:path'`,
bare `'fs'` and `require('node:fs')` bindings linked their calls to
same-named project functions. Main no longer does that for any static
form. PR 2120 (released in 1.6.2) declines a name match for a name the
file imports from outside the repository. PR 2437 made that hold in
projects with a catch-all tsconfig path alias, and PR 2671 extended it to
function values such as `promisify(readFile)`.

What still linked is the same binding written as a dynamic import. The
out-of-repo check reads the file's static import mappings only. The name
matcher deliberately treats `const { x } = await import('./m')` as an
import, not a local shadow, so the call fell through to a same-named
project function. vite's
`const { createServer } = await import('node:http'); return createServer(app)`
linked to vite's own `createServer`.

Fix (name-matcher.ts):
- isOutOfRepoBinding also asks dynamicImportSource for the module a
  dynamic import binds the name from, where the reference sits.
- A per-file scan (FIFO of 64, cleared with the other name-matcher memos)
  finds `const { a, b: c, default: d } = await import('x')` declarations
  and `import('x').then(({ a }) => ...)` callbacks on the raw text. Only
  files with a match are comment- and string-blanked, to drop matches in
  comments and strings and to find where each binding's scope ends: the
  enclosing block (a top-level await binds the whole module) or the
  callback body.
- A binding counts only when the reference sits inside that scope after
  it, and the innermost one wins, also over a static import of the name.
  nitro's `async function createWebSocketProxy()` imports crossws's
  `createWebSocketProxy` inside itself: only that inner call is dropped,
  and the file's other calls still reach the local function.
- jsBlockEnd and jsBodyEnd are the forward forms of jsBlockOpenAtEnd and
  jsBodyOpenAtEnd, which now call them.

Validation:
- __tests__/dynamic-import-binding.test.ts: 5 of 6 tests fail with main's
  name-matcher.ts (the project-module control passes there); all 6 pass on
  the kernel and with CODEGRAPH_KERNEL=0. Four mutations each fail their
  test: no scope end, no callback form, no dynamic source, and no memo
  clearing (the in-process sync test).
- Sync equals a fresh index (CLI) for adding and removing the dynamic
  import, for the namesake file arriving later, and for the nitro shape.
- A/B on 9 TS/JS repos plus 2 controls, both arms with the kernel: 6 edges
  removed, 0 added, 0 retargeted, all 6 wrong. vite has 3 (node:http
  createServer, node:fs/promises readFile, rolldown watch), memfs 2
  (node:util promisify), nitro 1 (the crossws self-loop). pathe, c12,
  unbuild, fs-extra, unenv and nuxt are byte-identical, as are the flask
  and gin controls.
- Cost: fresh index median of 3, interleaved: vite 9.17 s main vs 9.50 s,
  nitro 7.43 s vs 7.12 s (noise on a shared box). The scan itself over
  every vite source file takes 40 ms (24 files reach the blanking pass).
- Kernel parity is unchanged: 772/776 files byte-parity, 4 deferred,
  over nitro/src and vite's src (this is a resolver-only change).

Fixes #2491

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:10:12 +00:00
Colby MchenryandClaude Opus 5.5 800e582d55 fix(go): conversions, external test packages and build-tag twins link as Go builds them (#2784)
Three Go shapes recorded edges the source doesn't make. A conversion
like Category(s), (*Pair)(p) or model.Kind(3) became a calls edge to
the type, or an instantiates edge to a struct. A bare call in a
package's external tests (package a_test) could bind to a same-named
helper of package a in the same directory, which a_test can't see. And
a name a package defines once per build target, like sep in sep_unix.go
and sep_other.go, linked to whichever definition came first, so a Linux
caller reached the !unix file.

A Go call whose target is a type is now a references edge. Two files of
one directory under different package clauses see each other's
top-level names only through an import: qualified, or bare after a dot
import and exported. A reference whose definition has build-constrained
twins in its package reaches the one that builds for the most typical
target its own file builds for: linux/amd64, then darwin/arm64,
windows/amd64 and the rest. Constraints are read from the file name's
GOOS/GOARCH suffix and the //go:build or // +build lines, with tags as
go build sets them by default.

Validation: 9 new tests in three files, all failing on main, and each
part switched off fails its own file; the Go tests pass. A/B against
main 0c3e12dd, edges changed as conversions / twin retargets: gin 56/0,
etcd 556/89, prometheus 812/5270, sonic 49/0, x/sys 550/3072, gopsutil
31/179, fsnotify 3/7, kubernetes 16039/328. Nodes and refs are
unchanged, and express and flask dumps are byte-identical. Every kind
change sits on a conversion-shaped call, and the twin retargets read
right by their files' build lines (prometheus labels to the default
stringlabels file, x/sys syscalls to syscall_unix_gc.go). Indexing
cost, wall/CPU seconds, median of 3: x/sys 6.7/20.5 -> 7.1/21.7, etcd
9.6/29.9 -> 9.8/30.0, prometheus 12.5/49.2 -> 12.9/50.2; kubernetes, 2
runs each, 108.1/385.8 -> 110.4/390.0.

Fixes #2581

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 06:19:32 +00:00
Colby MchenryandClaude Opus 5.5 0c3e12ddbb fix(go): a call further along a chain links the method of the type the chain reaches (#2782)
A Go method called on a receiver the extractor keeps no name for, such as
a fluent chain's later link (model.WithContext(0).Where(1).First()), a
call on what a function returns (time.Now().UTC().Format(...)) or a
composite literal (GitOracle{}.Worktree()), reached the resolver as its
bare name at the column where the chain starts. Name matching then took
a project method of that name by proximity, or the only one: the issue's
GORM-style Where and First went to another query type, the standard
library's Format to the one project Format, and a literal's method to a
namesake type.

A new Go resolver step reads the chain at that column and types the
receiver link by link: a call's result, or the one a multi-value
assignment takes; a conversion's or a composite literal's type; a
generic call given its type arguments; an embedded field by its type's
name; a package-level variable from the call it is set to. A result
written with type parameters keeps them as parameters, read from the
declaration, since the indexed signature leaves them out, and the helper
that reads them now includes a one-line function's only line. The method
is the one Go selects on that type. A type from outside the project, or
one that can't be read, links nothing instead of a guess. When a chain
calls one name twice, the bare ref is the later call.

Validation: 8 new tests; 7 fail on main, and the repeated-name case
fails under a first-link rule; the 43 Go test files pass. A/B against
main c326b6be, edges removed/added: gin 80/72, etcd 1726/1327,
prometheus 2249/1265, sonic (gorm/gen) 910/877, kubernetes 88288/84331.
Every removed edge was a name-match guess except 173 in kubernetes: 171
are the same edge now owned by another ref of its chain, and 2 wrong
links of metav1.Now().Time.Unix(). Sampled retargets, drops and
additions read correct; express and flask dumps are byte-identical.
Indexing cost is within noise, median of 3 interleaved runs, wall/CPU
seconds main -> fix: etcd 9.5/28.6 -> 8.6/25.4, prometheus 10.5/42.1 ->
10.5/42.6, kubernetes 121.9/413.7 -> 121.3/416.8.

Fixes #2543

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 04:53:59 +00:00
Colby MchenryandClaude Opus 5.5 6bd217a263 fix(cpp): code that calls operator++ by its name no longer stops an index or sync (#2769)
Indexing or syncing a C++ project stopped at "Resolving refs" with
"Invalid regular expression: /\boperator++\s*(?:<[^<>()]*>)?\s*\(/:
Nothing to repeat" as soon as its code called an overloaded operator by
name, as `this->operator++();` inside `operator++(int)` does. Choosing a
method's overload reads the argument list after the name, and
cppParenListIn and cppOverloadParameters wrote the name into that
pattern with only `~` escaped. `operator++` did not compile. `operator()`
matched the name's own empty parentheses as the arguments, `operator[]`
never matched, and `operator+=` matched the next `operator=`. The same
helper serves every language that overloads by arity, so Scala `++`
overloads and Kotlin names in backticks that hold a `(` failed the same
way.

The fix adds one escape helper, escapeRegExp in src/utils.ts, the same
body as the private copies elsewhere. It is used at every place in src/
where a node name, a reference name, a piece of one, or a word read
from source goes into a RegExp unchecked: 114 interpolations in 13
files. 79 were raw, 31 escaped only `$`, 1 `$` and `\`, 1 only `.`, and
the 2 overload readers only `~`. An AST pass over all 490 dynamic
RegExp constructions followed each interpolated value to its sources.
The 72 it still flags were each read by hand. They are
identifier-checked first, built from escaped names, constants, or the
CLI/MCP glob converters. The kernel's one dynamic regex already
escapes.

With operator arguments now read, nlohmann/json's `p->operator[](0)`
reached the argument-type scorer and picked `template <typename T>
operator[](T* key)`: a `size_type` parameter scored as an unknown
class, a `0` as a null pointer. `size_type` and `difference_type` now
count as numbers, as `size_t` does.

Validation:
- New __tests__/operator-method-names.test.ts (5 tests: init and sync of
  an operator fixture, operator() and operator+= overload choice, the
  json operator[](0) shape, Scala and Kotlin). With main's sources,
  4 fail (3 throw "Nothing to repeat"); the json shape fails only with
  size_type taken out of the number types. All pass on the kernel and
  with CODEGRAPH_KERNEL=0.
- Census with a preload that logs every throwing RegExp: main threw
  only in cppParenListIn (CTRE 27, folly 3); the fix threw 0 times in
  all 12 repos below.
- A/B on main efff7457, graph dumps:
  - fmt, glm, rocksdb, protobuf: byte-identical.
  - json: 9 calls retargeted, all right (`j.at("name")` to the key
    overload, `v.at(i)` to size_type).
  - abseil-cpp: 3 retargeted, all right (operator() self-call to the
    two-argument overload, assign(3, 10) to the count overload).
  - CTRE and folly crash on main; both index now. CTRE: 10 of 12 calls
    retargeted against main's census run are right; 2 single-header
    calls reach the identical copy in include/. folly: 4 of 6 are right;
    2 small_vector assign(int, int) calls link the count overload where
    C++ takes the unconstrained template.
  - Controls gin (Go), bulletproof-react (TS), ktlint (Kotlin, 86
    backtick names with pattern characters) and cats (Scala symbolic
    methods): byte-identical.
- Cost: none measurable. Medians of 3 interleaved init runs: fmt -1.5%,
  json +2.1%, glm +0.2%, abseil -2.5%, gin -2.1%, bulletproof-react
  -0.3%, rocksdb +0.4% (45.2 s vs 45.3 s).
- kernel-parity on fmt + json (cpp/c): 458/669 at parity, 0 diffs,
  both arms.

Fixes #2457
Fixes #2482
Fixes #2636

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:39:08 +00:00
Colby MchenryandClaude Opus 5.5 efff745783 fix(go): a field typed by a dot-imported package resolves in that package (#2764)
A Go type written without a package qualifier was looked up only in the
file's own package. In a file that dot-imports a project package, like
harbor's config tests (`import . ".../controller/config"`, then a
`controller Controller` field), the type isn't there, so the call
`c.controller.AllConfigs()` fell back to name matching. Name matching
took the same-named type of a package the file doesn't import, the one
that sorts first: matchGoFieldChainCall used the struct's own directory
for a bare field type, resolveGoMethodInPackage found no such type there,
and resolveMethodOnType picked the first of every
`Controller::AllConfigs`.

The same gap affected calls through parameters, locals and package-level
variables of such a type (one set by a conversion), through an alias of
it, and through methods promoted from an embedded field. It also affected
method values, type references, and the embedding edge itself where the
namesake was as near.

The field chain, the typed-receiver and method-value package lookup,
embedded types, alias targets, package-level values and Go type positions
now ask goBareTypeDir. It reads a bare name as Go does: the file's own
package first, then a project package the file dot-imports that declares
it. An exported name that only a dot import of a package outside the
project's modules can supply (`. "github.com/onsi/gomega"`) names none
of the project's types. The exception is a type that one of the file's
functions declares, which the index holds no node for: name matching
still handles it, as before. A bare embedding is retargeted only when a
dot import decides, and files without dot imports resolve exactly as
before.

Tests: go-dot-import-field-type.test.ts has 10 cases on both the kernel
and wasm backends. 18 tests fail on main and 2 guards pass. All
go-*, goframe, gin-*, frameworks*, resolution*, sync* and dead-code test
files pass (65 files, 1,140 tests). tsc and the build are clean.

Validation compares main bf6f1fa6 with this commit, each built with a
kernel from its own sources. Each repo was indexed from scratch and
diffed by call site:
- gomega 820ad4d5: 30 calls retargeted from internal.Gomega's Expect to
  types.Gomega's, the type the dot-imported root package's `Gomega`
  alias names. All 30 were checked against source.
- ginkgo 7ff96d7d: 135 calls added and 1,022 confirmed (same target,
  0.7 to 0.9), all through package-level vars typed by the dot-imported
  internal/test_helpers. 9 retargeted (8 composite literals to the root
  package's SemVerConstraints aliases, 1 Status call to the fake
  handler). None removed.
- harbor src/ 4e07c69c and kubernetes d2c4b85b: byte-identical dumps.
  Their dot-imported types' methods have no namesakes, so main already
  linked them.
- Controls gin 0f09c3a9 and zustand d7a5583c (TypeScript): byte-identical.
- Index time and DB size are within noise. On kubernetes, the median of
  3 interleaved rounds was 151.5 s on main and 148.7 s with the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 21:46:24 +00:00
Colby MchenryandClaude Opus 5.5 dbb6aa9ba9 fix(vbnet): link reads through a namespace or a bare member name, and run the parameterless overload (#2762)
Three kinds of VB.NET member read that #2355 and #2377 left unlinked:

- A method named without parentheses (`Me.Load`, `Calc.GetTargetDAR`,
  a constructor's `Me.New` / `MyBase.New`) went to the first overload
  declared, so `Me.Load` reached `Load(path)` instead of
  `Load(Optional force = False)`, and a `Me.New` whose first overload was
  the calling constructor itself linked nothing.
- A read whose first name is a namespace (`Billing.Invoice.MaxLines`,
  `StaxRip.UI.ButtonEx.ButtonSymbol.Delete` with the root namespace in front,
  a Module's `RedGifs.RedGifsSiteKey`) linked nothing: the receiver was
  looked up as a value or a type only.
- A field, property or Module variable named bare before the dot
  (`Button1.Text`, `Settings.Plugins`, staxrip's global `p.TargetWidth`) was
  not counted as read: a bare name is no member access, so only what is read
  through it could link, and nothing when that is .NET's (`Text`).

Fix (src/resolution/vbnet-receivers.ts; resolution only, extraction output
unchanged):

- A method a reference runs without parentheses resolves among its type's
  overloads to one that takes no arguments (none, or only Optional and
  ParamArray parameters, read from the declaration), never the member the
  reference is in. AddressOf / NameOf keep the first overload, as before.
- A receiver name that is neither a value nor a type is looked up as a
  namespace the way VB.NET does: nested in a namespace around the read,
  outward to the root namespaces, then in the file's and project's Imports.
  Each further link is a type declared in it, a nested namespace, or a
  member of one of its Modules. The read links the type (`Billing.Invoice`)
  or the member (`Assets.Logo`), as #2355's `Outer.Mode` reads do.
- A bare name before the dot that VB.NET binds to a field, property or
  Module variable (not a local or parameter, which hide it) is linked as a
  read alongside what is read through it. Not for a `With` block's `.Text`,
  a method run through the name (the call is the method's alone, as with
  parentheses), another project's Module, or a member declared with an
  outside type of its own name (`Responser As Responser`), whose Shared
  members and nested types VB.NET reads through the type; a project type of
  its own name follows #2377's Color Color rule.
- membersNamed looked a type's members up in every project's same-named
  type and memoised the list by qualified name alone, so SCrawler's two
  plugin `UserData` classes shared one list ordered by whichever was asked
  first. It now keeps to the type's own project. Alone this leaves main's
  graph unchanged on SCrawler and staxrip; without it, 17 of the new
  bare-name reads in one plugin linked the other plugin's members.

Dropped from the 2026-10-04 test set: a method named through its class
also linking the class (#2355 treats it as a call, and its test pins that);
`AppSession.SessionId` through a property typed OtherSession linking
nothing (#2377 links OtherSession's field, pinned the same way);
`SCrawler.My.Resources...` reads (#2355/#2377 send no path that starts with
`My` or `Global`); and same-named classes picked by namespace, Imports and
project, which main already does once the fixture declares its
RootNamespace.

Verification: new vbnet-member-access-scope.test.ts (9 tests) fails 6/9 on
main and passes; a mutation that restores main's membersNamed fails the
own-project test. The other VB.NET, C#, extraction, resolution, sync and
framework suites pass; tsc and build clean.

Validation, main vs this branch (indexes from each arm's own build; nodes
unchanged; self-loops unchanged; serilog and gin dumps byte-identical):

- SCrawler 31,201 -> 36,378 edges. 1 removed: `COLOR_PICKER.ColorsGet`
  re-pointed from the two-argument overload to `ColorsGet() As DataColor`.
  Added: 4,973 bare-name reads (3,731 own class, 957 Module variables,
  285 inherited), 192 namespace reads, 12 reads through a Module member
  reached by namespace, and that 1 overload.
- staxrip 70,864 -> 83,909 edges, 0 removed. Added: 12,983 bare-name reads
  (8,991 own class, 3,820 Module variables, 172 inherited), 61 reads with
  the root namespace in front, and `box.Show` reaching
  `Function Show() As DialogResult` from its Shared overload.
- Checked against source: every bare-name read's name starts the access,
  has no local of that name, and binds to its class, an ancestor (23
  through a qualified `Inherits Instagram.UserData` the graph's own extends
  edge misses) or a Module of its project; every namespace read's path ends
  its target's full name and the target's project is the reader's or one it
  references; no edge crosses between staxrip and its AutoCrop tool; 48
  sampled edges hand-checked, all correct.
- Cost (5 interleaved rounds, medians, quiet machine): SCrawler wall
  5.60 -> 5.75 s, CPU 19.5 -> 20.3 s, DB 43.4 -> 45.1 MB; staxrip wall
  7.23 -> 7.11 s (noise), CPU 25.7 -> 26.3 s, DB 59.0 -> 64.4 MB, the
  growth being the new edges (unresolved refs fall 47,074 -> 43,607 and
  38,051 -> 36,165).

Not covered: a call through a bare name (`Button1.Focus()`) or a function
named before the dot, reads in a `With Button1` block, and `My.` /
`Global.` paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 19:46:36 +00:00
Colby MchenryandClaude Opus 5.5 8b5d1d543d chore(scripts): regenerate the Go standard-library method table from Go's source (#2760)
go-stdlib-methods.ts lists the methods of 130 standard-library types with
their parameter and result counts. Its generator lived in a validation kit.
scripts/gen-go-stdlib-methods.mjs reads a Go source tree with the engine's
Go grammar, counts with the engine's own goSignatureArity, and prints (or
--write's) the table for the types it lists, so a later Go release's
methods are one command away. Against go1.27.3 it reproduces the table byte
for byte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 18:27:03 +00:00
Colby MchenryandClaude Opus 5.5 847f1df77d fix(go): an interface or struct embedding a standard-library type counts its methods (#2759)
goImplementsEdges (#584) follows declared embeddings since #2402, but an
embedding of a type outside the index resolves to nothing, so it added no
methods: prometheus's TBRun (testing.TB plus Run) went to a struct with only
a Run, QueryLogger (slog.Handler plus io.Closer) had no implementations,
kapacitor's Diagnostic (a third-party Logger plus Log) took every struct with
a Log, and the predeclared `error` was not recorded at all.

- go-stdlib-methods.ts lists the methods of 130 standard-library types, each
  as `name:params/results`, generated from go1.27.3's source and counted the
  way goSignatureArity counts a stored signature.
- Both extractors record an embedded `error`, which stays unresolved. The
  predeclared-type set is unchanged, so alias and defined-type references
  still skip it.
- goImplementsEdges reads each Go type's failed `extends` rows and reads the
  package back off the line (goRefQualification, now exported). A
  standard-library type's methods join the method set, keyed as goMethodKey
  keys the project's (`std:<import path>` for an unexported one), and the
  arity gate holds them to the table's counts on both sides: a `sync.Mutex`
  makes a struct hugo's `Locker`, not etcd's gRPC `LockClient`. Any other
  outside type is met by a type embedding the same one, or by the only type
  with every other method the interface names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 18:13:54 +00:00
Colby MchenryandClaude Opus 5.5 1f3a274c53 fix(go): a method a type declares in a file the index couldn't read is not its embedded type's (#2757)
kubernetes' core/v1 generated.pb.go is 1.7 MB, over the index's 1 MB
limit, so every API type's own String, Marshal, Unmarshal, Size and
MarshalToSizedBuffer is missing from the graph. Resolving `pod.String()` on
a `*v1.Pod`, or `m.Template.Size()` on a `v1.PodTemplateSpec`, the walk over
embedded types found the method the embedded TypeMeta or ObjectMeta has and
linked that, a method the call never runs.

resolveGoMethodInPackage, which every typed Go receiver goes through
(parameters, locals, fields, assertions, package-level values, indexed
elements), now reads the method declarations of the files of the type's
package that the index couldn't read, from their text, and links nothing
when the type declares the method there. matchMethodCall's typed branch and
matchGoPackageValueCall keep that null instead of falling through to name
matching, which found the same promoted method. A method the type really
gets from an embedded type, like GetName from ObjectMeta, still links. The
check that #2755 added for indexed elements moves into the shared path.

On kubernetes 4c05e667, 63 calls lose that link and nothing else changes;
etcd, prometheus, express and flask are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 17:44:33 +00:00
Colby MchenryandClaude Opus 5.5 a5fe6e1a27 fix(go): a call on an indexed element reaches its element type's method (#2755)
A Go call on an indexed element (kubectl's `schemas[ix].ValidateBytes(data)`,
deepcopy-gen's `(*in)[i].DeepCopyInto(&(*out)[i])`, go-to-protobuf's
`m.Items[len(m.Items)-1].Unmarshal(...)`) reaches the resolver by its bare
name, at the column where the receiver starts, and nothing typed the
receiver: a method one type declares was guessed by name, often onto a
namesake, and a common one like `DeepCopyInto` got nothing.

matchGoIndexedCall, an early hook beside matchGoAssertedCall, reads the
receiver chain with goChainLinks and types it as Go does: the head from the
declaration in scope (goLocalDecl; readGoScopes now records where each `:=`
or `var` value starts and which name it is) or a package-level variable;
values (composite literals, make, new, append, conversions, fields,
elements, addresses, range elements); struct fields, promoted ones too; and
the element type of a slice, array, array pointer, map or named slice type,
with `(*p)` dereferenced. The method is the element type's own, its
interface's or one embedding promotes, and a type parameter's comes from its
constraint. An element type from outside the project, a predeclared one, a
type literal or an outside alias links nothing, and so does a method the type
declares in a file of its package the index couldn't read (kubernetes'
core/v1 generated.pb.go), which is read from that file's text. What can't be
typed (a call's result) is left to the other strategies.

On kubernetes 4c05e667 the indexed-element calls go from 760 resolved and
2,284 unresolved to 2,666 resolved and 378 unresolved; every changed site in
kubernetes, prometheus and etcd is such a call, nodes are identical, and the
express and flask controls are byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 16:56:19 +00:00
Colby MchenryandClaude Opus 5.5 553d5d101d fix(sync): an import a name match took follows its module once it resolves another way (#2752)
A named import that a name match linked to a declaration elsewhere while
its module did not export the name kept that link after an edit gave the
module the name. A barrel's `export { EmptyState } from './EmptyState'`
declares nothing, so no definition delta, failed-ref retry or barrel walk
saw it (the "retargets" PR #2744 left). A name match into the module
itself, a class exported only as its default and imported by name, kept
its link after the default was dropped, where a fresh index finds nothing.

reopenNameMatchedImports (module-importers.ts): for each module the sync
modified, its importers' binding edges that resolved some other way than
their import (getImportBindingsResolvedElsewhere, an indexed range per
importer), whose import statement names this module, are resolved again
against the graph as it is (ReferenceResolver.resolveNow, writing
nothing). When the answer differs, the importer's edges through the name
and into the members of what the name match took go back for the sweep.

Regression test sync-name-matched-imports.test.ts: 4 cases comparing the
synced graph with a fresh index of the final tree. Fresh indexes are
byte-identical on 8 repos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 15:23:05 +00:00
Colby MchenryandClaude Opus 5.5 fc293f0081 fix(sync): a default import follows the module's default when an edit moves it (#2749)
An edit that changes which declaration a module exports as its default
(`export default A` becoming `export default B` while A stays, or the
default dropped) left every reference through a default import of it on
the old declaration. A sync moves other files' edges into a changed file
onto the node that replaces each target, and an `export default` statement
is no node, so no definition delta, failed-ref retry or importer walk saw
the change. A fresh index links the new default, or nothing.

- ReferenceResolver.changedDefaultExports: for each modified JS/TS module
  that unchanged files import by default or as a namespace (an imports edge
  into its file node named for the binding), compare what the whole-name
  references through those bindings resolved to with the module's default
  now (moduleDefaultExport, the lookup resolveViaImport makes). No file is
  read; a module forwarding its default samples 8 importers' edges.
- reopenReferencesThroughChangedDefaults (module-importers.ts): when one
  moved, the edges bound before this sync resolved anything go back for
  the sweep: through every binding that reads the default (importersOf
  seeded with `default`: default imports, { default as Y },
  require('./x').default, modules passing it on), the routers'
  `layout:<binding>` references, and into the old default's own members
  (typed receivers, inherited members).

Regression test sync-default-export-change.test.ts: 6 cases comparing the
synced graph with a fresh index of the final tree; all fail on main.
Fresh indexes are byte-identical on 8 repos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 14:22:36 +00:00
Colby MchenryandClaude Opus 5.5 e450ae3a63 fix(go): an unexported interface method is satisfied only from its own package (#2746)
An unexported Go method name belongs to the package that declares it.
goImplementsEdges matched method names only, so an interface asking for
an unexported method was linked to every type in any package with a
method of that name: each of prometheus's `refresher { refresh(...) }`
interfaces (one per discovery package) went to about 37 providers in
other packages, such as discovery/aws.EC2Discovery, none of which Go
lets satisfy it.

Method sets now hold an unexported name together with the directory of
the file that declares the method (goMethodKey), on both sides. Where
the method is declared is what counts, not where the type is: etcd's
gRPC servers outside api/etcdserverpb keep their KVServer edges,
because the UnsafeKVServer or UnimplementedKVServer they embed declares
mustEmbedUnimplementedKVServer in the interface's package.

The interface-dispatch bridge uses the same key, so a call through the
interface reaches the method of the interface's package: not the
type's own namesake from another package, and not one Go's selector
would skip at a shallower embedding depth.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:31:48 +00:00
Colby MchenryandClaude Opus 5.5 6e24711590 fix(go): a call through a func-typed struct field reaches the functions put in it (#2745)
A Go struct can hold a function in a field and call it through the field.
Prometheus' service discovery runs every provider that way: refresh.Discovery
calls d.refreshf(ctx), NewDiscovery copies refreshf from Options.RefreshF,
and each provider passes its own method (RefreshF: d.refresh). Nothing
answered d.refreshf, so a flow from Discovery.Run stopped at the field; #2430
rightly dropped the interface-impl edges that had stood in for it.

goFuncFieldEdges, a Go-only synthesis pass, links a call x.F(...) through a
func field of a struct to each function or method value put in that field:
by a composite literal (keyed or positional, found through its instantiates
edge), by an assignment x.F = v, or by one level of field-to-field copy
(refreshf: opts.RefreshF). A value is what the resolver already linked it
to, else x's method m for x.m, an interface's method for an interface value.
Receivers are typed through the resolver's own Go scope reader
(goLocalDeclarationAt over goLocalDecl, #2706), or by a local's composite
literal, new(T), type assertion or resolved call result. Channels are keyed
by package, struct and field, so a generic field name never crosses between
unrelated structs; one package's test wiring links no other package's test;
an edge cites a function's production wiring; generated files are not read.

Edges are calls, provenance heuristic, synthesizedBy go-func-field, with
field, via (package-qualified when the copied field is another package's)
and registeredAt; explore, the node trail and ContextBuilder read them as
"func field `Discovery.refreshf` (set from `Options.RefreshF`)". The pass
sits after the Go framework passes, with a pool cost hint, and every Go file
now counts as a synthesis input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:04:48 +00:00
Colby MchenryandClaude Opus 5.5 e24827011e fix(sync): a name imported through a barrel links when an edit gives the barrel its re-export (#2744)
Sync and the file watcher now link `import { useInvocation } from './context'`,
and every use of the name, when an edit gives the barrel context/index.ts
that re-export after the importing file was indexed. Before, the import,
its calls and its JSX or template tags stayed unlinked until the importing
file changed or the project was re-indexed.

The barrel declares none of the names it passes on, so sync's retry by name
never reached the importers' failed references. The edit adds no file and
changes no definition, so neither the importer re-open through added files
(PR #2512) nor the barrel walks (PR #2560, PR #2665) run.

importBindingTail now parks the whole name of a plain named import under
its module's key (`module:context`), as PR #2450 does for default, namespace
and aliased bindings and PR #2507 for Python's unaliased imports. It does so
only while the import resolves to a project file that carries that key and
that module doesn't give the name yet; the by-name lookup still finds the
reference by its own name.

Three versions were measured on 18 repos, and all fix the same barrel-edit
cases. This narrowest one parks 2,727 references on a fresh index, against
11,061 for parking every plain named import, and keeps the retries per
changed file within +1% to +3% of main on folo, vben and milo-ui. Fresh
indexes are identical in nodes, edges and files in all 18 repos, and on
vben's gut scenario sync's difference from a fresh index falls from 2,337
lines to 194.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 12:42:28 +00:00
Colby MchenryandClaude Opus 5.5 ae0fc631e0 fix(js,ts): a callee that names nothing records no reference, and a wrapped callee is the plain call (#2742)
A TS/JS call whose callee was no name and no member access (an IIFE, a
curried `connect(mapState)(View)`, `handlers[type]()`, `(0, fn)()`, a
tagged template on a call) stored the callee's whole source text as the
reference's name. Nothing can answer such a name, so each one parked as a
failed reference, an IIFE's entire body among them. extractCall now reads
the callee past the wrappers that keep it the same callee (parentheses,
`!`, `as` / `satisfies`, `await`, type arguments) and records nothing when
that is not a name or a dotted chain; the calls inside are visited on
their own. Mirrored in the kernel (tsjs/extractors.rs callee_name_of).

The reference of a wrapped callee sits at its name, where a plain call's
does, and the resolver's bare-call check reads `fn!(`, `(fn)(`,
`(fn as F)(` and `(fn satisfies F)(` as it reads `fn(`: SigNoz's
`(callback as FrameRequestCallback)(0)` and `(callback)(0)` name the
arrow's own parameter, and from the `(` they bound a generated interface's
`callback` property.

tree-sitter-typescript parses a prefix operator over a generic call as it
parses `await f<T>()` (#2586): `!settings.get<boolean>('X')` and
`void apiRequest<T>()` are calls whose callee is `!settings.get` /
`void apiRequest`. The callee peel that handled `await` now peels a prefix
operator too (peelTsPrefixedCallee; the kernel's peel_await_callee), so the
call is to `settings.get`, and `void apiRequest<T>().then()` encodes as
`apiRequest().then`, not `voidapiRequest().then`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 11:01:53 +00:00