Commit Graph
638 Commits
Author SHA1 Message Date
Colby MchenryandClaude Opus 5.5 cc17978174 fix(rust): a module a #[path] attribute loads is the module that declares it (#3064)
A Rust module loaded with `#[path = "…"]` was found by its file's directory:
fast-mlsirm's `#[path = "../../../tests/unit/scaling_tests.rs"] mod tests;`
in `crates/core/src/scaling.rs` made `tests/unit/scaling_tests.rs`'s
`use super::*` walk to a `tests/unit` module that doesn't exist, so its
`predict_rating_multi(1)` linked to nothing, or to a same-named function of
another crate; its `crate::` had no crate, and the parent's private items
looked out of its reach. From the declaring side, a path through such a
module (`utils::mock_event(…)` under `#[path = "./utils.rs"] mod utils;`)
reached nothing.

The fix reads each file's `#[path]` declarations as the Rust Reference
reads the attribute (from the declaring file's directory, or inside an
inline `mod` from that module's own; a path that leaves the project, by
escapesRoot, or that a `cfg_attr` sets, names no file):
- rustSubmodule: a module the module declares once with a `#[path]` is
  the file it names, whatever else loads that file too (a helper several
  tests load is each test's); cfg alternatives that name a file each stay
  unknown, and a path's 2015 crate-root fallback no longer reaches past
  such a module to a namesake at the crate root.
- rustPathClaim: a file the regular rules don't place is the module of the
  one `#[path]` declaration that loads it, if exactly one does. Placed
  (rustPlacedByName) are crate roots, files with a `fn main` or
  `#![no_main]` of their own (trybuild cases, fuzz targets), and files a
  plain `mod name;` declares in the module owning their directory or in an
  inline module of one above it (dioxus's `mod components { pub mod home; }`
  in main.rs). Only for a file left over are the project's Rust files that
  write `path =` read (a byte check first), once per resolver. With a
  claim, rustSuperModule is the declaring module, rustFileModule owns the
  file's directory (rustc reads every `#[path]` file as a mod.rs),
  rustCrateRootModule is the declaring file's crate, and rustModuleWithin,
  isRustItemVisibleFrom and isVisibleAcrossFiles see the file inside the
  declaring module, so `use super::*` brings in its private items.
- rustCrateGraph: `[[example]]`, `[[test]]` and `[[bench]]` paths are
  target roots, as `[lib]`/`[[bin]]` ones are since PR #3014, and such an
  example is its own crate's root (dioxus's
  `examples/07-fullstack/handling_errors.rs`).

Validation:
- rust-path-super-glob.test.ts (PR 1892's case, credited) and
  rust-path-attribute-modules.test.ts (7 cases): on main all 8 fail; all
  pass. Every rust-* suite passes.
- A/B against main, native kernel: fast-mlsirm (its Rust files) +2,796
  links (1,839 calls, 427 references, 334 constructions, 196 imports), 71
  retargeted (exposure_tests.rs's `Lcg::next_f64` from cdm_tests.rs's
  private `Lcg` to exposure.rs's, the declaring module's helpers instead
  of bifactor_grm.rs's), 87 reaching the same item through the declaring
  module (confidence 0.4 to 0.9), 0 removed; dioxus +27 (each headless
  test's `utils::mock_event(…)` into the helper five tests load); tokio +2
  (`use super::AtomicU64` in the files atomic_u64_as_mutex.rs loads);
  ripgrep, axum, clap, serde, gleam, express and flask unchanged.
- Cost, native kernel, medians of 3 interleaved runs: fast-mlsirm CPU 15.3
  -> 15.8 s, wall 4.2 -> 5.2 s, peak RSS 825 -> 882 MB; tokio CPU 26.1 ->
  26.7 s, wall 10.4 -> 11.2 s, peak RSS 1,023 -> 1,028 MB; dioxus CPU 34.8
  -> 35.6 s, wall 10.3 -> 11.2 s, peak RSS 1,125 -> 1,120 MB; gleam CPU
  24.1 -> 23.8 s, wall 6.6 -> 6.7 s, peak RSS 1,027 -> 1,023 MB.

Builds on PR #1892 by @seonghobae.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:21:12 +00:00
Colby MchenryandClaude Opus 5.5 ed6b88e53a fix(js): an export default line inside a template literal no longer names the default (#3068)
statedDefaultExport reads a JS/TS file's own export default statement
by scanning comment-stripped source for line-initial `export default`
heads. Strings are deliberately not masked there (a masker that loses
step on nested templates blanked real statements), and a head counted
when it named a node of the file, the first such head winning. A code
generator's template text breaks that: in

  export function App() {}  export function Main() {}
  export const template = `
  export default App
  `;
  export default Main;

the template's line names the real App, so `import X from './gen'`
bound to App instead of Main (issue #2968). A template holding
`export default () => …` also made a module whose real default is an
expression read as anonymous, which keeps every guess away.

templateLiteralTextAt (strip-comments.ts) scans the source once for
template-literal text: it follows `${…}` nesting (a template inside an
interpolation, a brace inside a regex there), and skips comments,
quoted strings and regex literals, which may hold a backtick; a `/`
after a value divides, anywhere else it opens a regex. A head inside
template text is now skipped. When some template never closes the scan
vouches for nothing and every head counts, as before. Nothing else
changes: a head outside template text is read exactly as it was.

Validation: default-export-declaration.test.ts gains the issue's file,
a template returned by a function behind a nested template, one passed
at module scope and one in a class field, a template's anonymous
default above an expression default, and controls (a regex and a string
holding a backtick, a division after a parenthesis, and a file whose
scan can't close a template); strip-comments.test.ts tests the scan
directly. Red with main's resolver: 2 of 10 failed; green: all pass.
Checked against tree-sitter on every line-initial head of 4,191
JS/TS files in 16 corpora (outline, excalidraw, vben, bitwarden apps,
mastodon, shadcn docs, zod, bulletproof-react, express, astro, react,
trezor-suite, this repo's src, tests and ui): of 4,149 real export
default statements the scan puts none inside template text, and all
322 heads it does place there are template text. A/B on excalidraw
d3a9c2a1, vben 61c07936, astro e3bec3d2 (packages/astro/src and
packages/integrations), with gin-gonic/gin 0f09c3a9 and psf/requests
3c7baaa0 as controls, both arms on main 4b0f72b7 through the native
kernel (and again on 66449255 through wasm): all six byte-identical,
since no default import there is decided by template text; index time
unchanged.

Not in this change: hasEsmDefaultExport, which only gates
require('./x').default, still reads the raw text.

Reimplements PR #3042 by @johnhldiver-beep.

Fixes #2968

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:55:21 +00:00
danusha2345andClaude Fable 5.1 664492551d fix(sync): a barrel whose re-export points elsewhere re-links its importers
`c.ts` imports `foo` through `index.ts`, which re-exports it from `a.ts`.
Change the clause to `./b` and sync, and `bar` kept calling `a.ts:foo`
where a fresh index gives `b.ts:foo`. The barrel declares nothing of
`foo`, so the definition delta has nothing to rebind; no file was added
for the walks below a barrel or the added-files pass; the binding never
failed, so the retries never see it; and the name-matched-import pass
reads only bindings a name match took. `export *` moved the same way, and
a scoped sync of the barrel alone too.

The orchestrator now reports the modified ES modules whose `export … from`
statements changed, read off their import nodes (named by the module, with
the statement as signature) before and after the store. Their importers'
bindings through them go back for the sweep, up through the modules that
pass them on in turn, with the failures parked under them and the edges
into the members of what the bindings pointed at (`w.run()` with
`w: Widget`). It is the importer walk the removed-files pass uses, run
after the name-matched-import pass.

Review changes: the trigger compares the `export … from` statements
themselves. The original compared the modules a file imports and counted a
module that lost any import, so dropping a plain import from excalidraw's
`types.ts` (218 importers, no re-exports) re-opened 2,186 refs for the same
graph; it now re-opens none. Comparing statements also catches a name
dropped from a clause whose module stays (`export { foo, qux } from './a'`
to `export { foo } from './a'`), which the module set missed. Plus tests
for both, and the doc comments the new code sat between moved back.

Validation: 9 tests; on main's source 7 fail (the 2 controls pass), and
on the original trigger the 2 new ones fail. 15 fixture edits, each
compared with a fresh index, in a later process and in the same process:
main diverged in 8 of each, this in none. excalidraw with
`export * from "./utils"` in its math barrel (116 importers) retargeted to
`./constants`: main was 224 edges off, identical here over nodes, edges,
refs and files. Fresh indexes are identical to main's.

Original PR #2985 by @danusha2345.
Fixes #2978

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:29:45 +00:00
danusha2345andClaude Fable 5.1 80f6b50504 fix(sync): a tsconfig, jsconfig or go.mod change re-resolves the imports it governs
The resolver read tsconfig / jsconfig `paths` and `baseUrl` and go.mod's
module path once and kept the maps for its lifetime ("callers re-create the
resolver if config changes", which none did), and the watcher follows source
files only. So after an edit to one of them a long-running MCP server kept
resolving imports by the old mapping, and so did later processes: a sync
re-resolves only the changed files, and a config-only edit changes none.

The index now carries a snapshot of the config files resolution read: every
tsconfig / jsconfig the alias loaders opened, `extends` chains included,
every go.mod, and the root's candidates whether present or not. Every sync
reads them again and, when one differs, drops the resolver's alias and
module maps with the memos resolved through them, re-opens every import the
file governs (the ES-module family for a tsconfig / jsconfig, Go for a
go.mod) for the sweep, as a moved workspace map does, and marks synthesis
for a rebuild. An index an older engine stamped records a snapshot without
re-opening anything.

Review changes: each file's digest covers what resolution reads from it, a
go.mod's module path and a tsconfig / jsconfig's `extends`, `baseUrl` and
`paths`, not its bytes. A dependency bump in go.mod or a `strict` flag in
tsconfig had re-opened every import of the language for the same graph
(caddy: 48,945 refs, sync 3.8 s against 0.9 s; excalidraw: 88,860 refs,
9.2 s against 1.1 s). Plus a test for that, the ReferenceResolver class doc
comment back on its class, and the CHANGELOG entry beside the workspace-map
one.

Validation: 7 tests; on main's source the 5 behaviour tests fail, and the
new one fails on the byte digest. 11 fixture edits, each compared with a
fresh index of the same tree, in a later process and in the same process:
main diverged in 7 and 9, this in none. Real repos, sync against a fresh
index over nodes, edges, refs and files: excalidraw with `@excalidraw/math`
retargeted was 27 edges off on main and is identical here; caddy with its
module path renamed was 6,511 edges off and is identical here. Fresh indexes
are identical to main's. A one-file sync on excalidraw, median of 5
interleaved runs: main 3,834 ms, this 3,269 ms.

Original PR #2984 by @danusha2345.
Fixes #2977

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:57:07 +00:00
Colby MchenryandClaude Opus 5.5 bc9291afe6 fix(java): a call on a value a type holds in a static field reaches the method of the field's type (#3055)
A Java call written through a type's static field, Consts.HELPER.go()
or Holder.SHARED.run(), was resolved by the method's name alone: the
receiver Type.FIELD said nothing of its type, so the call went to a
same-named method of whatever class the name matcher found, and a field
of a library class (Intent.ACTION_VIEW.equals(...), System.out.print())
could reach a project method of that name. An interface's constants were
not indexed at all (neither engine read a constant_declaration), so a
constant a type declares in an interface, or inherits from one, had no
declaration to read.

A Java Type.FIELD.m() (Outer.Inner.FIELD.m(), a package written in
front) now finds the type as the calling file names it (javaTypeRef),
then the field, the type's own or one it inherits (five levels up), and
calls the method on the value the field's declaration gives it, read
with the field's own file's imports: an enum constant's own override,
else the enum's method; the method of the anonymous class the field is
initialized with (a CREATOR's createFromParcel); the class a new X(...)
initializer builds; else the type the declaration writes. A Kotlin
object's INSTANCE or Companion reaches the object's member. A library
owner, or a field of a library type, links nothing. A receiver whose
head is a local or a field links as before. Both engines index an
interface's constant_declaration as a static final constant.

Validation:
- java-static-field-receivers.test.ts: 9 tests, 7 fail on main 60b0d7a9
  (two already pass there: a field typed by its own declaration and an
  anonymous class's method), all pass here.
- Kernel parity (java), 0 files with diffs: Torture.java 1/1,
  TortureRecord.java 1/1, jextract 218/218, spring-modulith 682/682,
  SystemUI 1141/1141, okhttp 71/71, Minestom 1859/1894 (deferrals are
  main's).
- A/B against main 60b0d7a9, sites added/removed/retargeted (conf-only):
  Minestom +26003/-8/122 (751; 12,584 interface constants now indexed,
  409 calls typed), spring-ai +14/-21/786 (823; 719 enum constants' own
  overrides), kestra +27/-273/14 (37), playframework +5/-1/0 (21),
  SystemUI 0/-11/0 (2), spring-modulith +5/-3/4 (26), jextract
  +1/-9/6 (1); okhttp, nowinandroid, mihon, click, gin, serilog
  identical. The removed sites are guesses on library fields and
  methods (kestra's HttpStatus.X.getCode() x180).
- Cost (median of 3 interleaved): SystemUI CPU 92.0 -> 93.7 s, wall
  59.4 -> 58.6 s; Minestom CPU 46.7 -> 56.4 s, wall 17.5 -> 19.4 s, as
  its graph grows by the generated registries' 12,584 interface
  constants (29% more nodes, 30% more edges).

Reimplements #1949 by @mixxer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 09:46:25 +00:00
Colby MchenryandClaude Opus 5.5 7021de5c30 fix(rust): calls through a type a path or a use names reach that type's own function (#3025)
A Rust call through a type, `sim::items::ItemPool::for_def(…)` into another
crate of the workspace or `ItemPool::for_def(…)` under `use
sim::items::ItemPool;`, was matched by its qualified name alone, so a
namesake type's function in another crate or module could take it (a
workspace's `client` crate's `ItemPool::for_def`). The module walk stopped
at the type, a `use` of a `pub const` or `static` (`use sim::RANK_COUNT;`)
linked nothing, and a type rustfmt wrapped below its path (`h:
&scheduler::\n Handle`) linked by the file's `use` of its last name.

The fix, on the module walk:
- rustPathTarget reads `Type::member` calls: the type is the item the
  path's modules name, or the one the `use` that binds its name names
  (rustUseItemsAt), and rustTypeMember finds its member: an enum's variant;
  else the type's own (inherent) function or `const` in the type's crate,
  a trait impl's only when it is the one of that name; a trait's own
  declared method. A type alias, `Self`, a type the module declares itself
  and a type path in any other position are left as they were.
- Module items include `variable`s (a module-level `pub const`/`static`).
- matchRustUseBoundName reads a qualifier written on the line above a
  type (rust-scopes' blanked code, so a commented-out path is none) and
  resolves the whole path.
- Memoized per (type, member) and per path.
- Manifest paths stay inside the project: a workspace member that leaves
  it (`../sibling`, an absolute path) is neither read nor listed
  (escapesRoot, in expandMembers), and rustCrateGraph drops a
  `[lib]`/`[[bin]]` path that leaves it, as it already did a dependency's.

Validation:
- rust-cross-crate.test.ts (PR 2631's 9 cases, credited),
  rust-type-member-paths.test.ts and rust-manifest-paths.test.ts: on main
  10 of 15 fail; all pass. Every rust-* suite passes. On main the workspace
  map read `../outside/Cargo.toml`, `../vendor/x/Cargo.toml` and
  `/abs/crate/Cargo.toml` and listed `../vendor`; now only the project's.
- A/B against main, native kernel, 7 Rust repos (every site the same
  as against PR #3014's own commit; re-checked on 60b0d7a9): tokio +205
  links / 139 retargeted / 230 chains gaining their head call, ripgrep
  +155 / 13 / 1, axum +25, clap +179 / 0 / 295, serde +28, dioxus +142 /
  6 / 31, gleam +114 / 47 / 0; 0 removed. Retargets go to the type the
  code imports or its own crate's (tokio's Semaphore::new and loom mocks
  to tokio::sync, ripgrep's Error::from, gleam's Printer::new); express
  and flask byte-identical.
- Cost, native kernel, medians of 3 interleaved runs (dioxus of 7):
  tokio CPU 32.1 -> 29.7 s, peak RSS 1,000 -> 1,010 MB; gleam 29.1 ->
  28.1 s, 1,010 -> 1,004 MB; dioxus 38.7 -> 40.1 s, 1,111 -> 1,115 MB
  (timed on one thread, rustPathTarget 1.24 -> 1.32 s over 17,198 calls).

Builds on PR #2631 by @BGamboa13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 09:28:10 +00:00
Colby MchenryandClaude Opus 5.5 cdfa31be54 fix(ruby): a call on an instance variable that initialize types links to its class's method (#3053)
Both extractors record a Ruby call on an instance variable as
`@writer.flush`, and no strategy read the variable's type, so every such
call stayed unresolved: 2,780 in activesupport and 3,239 in actionpack on
main, for example.

ruby-constant-paths now resolves `@name.m` written in an instance method of
a class (not a class method, not a module) by the class's `initialize`.
When every assignment to `@name` there starts a statement and is a lone
`Const.new(...)`, `A::B.new` or `::Foo.new ...` of one constant
(arguments with or without parentheses, a block or a statement modifier
may follow), the class is the one Ruby's constant lookup finds for it from
`initialize`, and the method is that class's own instance method or the
first its ancestry declares. Anything else leaves the variable untyped for
the other strategies, as before: a conditional, two classes, `.tap`,
`writer || Foo.new`, `@a, @b = ...`, a chain continued on the next line. A
class the project doesn't declare (`Logger.new($stdout)`) links nothing.

Validation, kernel engine, main 3a4cd3bf vs this change:
- jekyll 97c3bdc +8, activesupport +19, actionpack +14, activerecord +30
  (rails 793d744 subtrees), sinatra cb22afd 0; nothing removed. 68 of the
  71 are right; 3 in activerecord follow `@pool = NullPool.new` from
  `initialize`, which `pool=` later replaces with the real pool.
- Controls requests (Python) and cobra (Go): dump-graph byte-identical.
- New tests fail with main's resolver (the positive case, in both engines)
  and pass with the fix; the untyped and outside-class cases pass on both.
- Index time, median of 3 interleaved: activesupport 4.72 vs 4.79 s,
  activerecord 11.54 vs 11.54 s.

The gap was found in the triage of PR #684.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 08:37:36 +00:00
Colby MchenryandClaude Opus 5.5 6d3804cf57 fix(resolution): C, C++ and Objective-C rank as one language, and a lone candidate is judged by family (#3052)
Name matching compared a candidate's raw language tag with the
reference's: the single-candidate confidence in matchByExactName and
matchFuzzy, and findBestMatch's same-language skip and +50/-80 score.
So a call whose name had several definitions in another of C, C++ and
Objective-C scored -80 for each and linked to nothing (an Objective-C
file's _glfwPlatformSetTls(), a C++ call into Dear ImGui's imgui.h, which
reads as C), and the one definition of a name across TSX and TS, or C and
C++, was a 0.5 cross-language guess.

The single-candidate confidence now compares language families
(sameLanguageFamily). Ranking treats C, C++ and Objective-C as one
language (sameRankingLanguage), the very language worth a point that only
breaks a tie; other families keep the language itself, because ranking
them by family moved excalidraw's Store::scheduleCapture calls to the
App.tsx method beside them and gave okhttp 83 wrong Java-to-Kotlin
Builder picks.

Validation:
- __tests__/name-match-language-family.test.ts, 8 tests (default and
  wasm): 6 fail on the base main; the pin that a .ts call keeps its .ts
  target over a .tsx namesake beside it passes on both; all pass with
  the fix. 24 related test files (cross-language, React Native, Expo,
  Fabric, frameworks, mixed TS/JSX, Objective-C, resolution): 746 pass.
- A/B, main vs fix: glfw +4 calls from Objective-C into C, imgui +93
  from C++ into imgui.h (read as C) and imstb_rectpack.h, all to the
  definitions meant; excalidraw, okhttp, leveldb and jq gain or lose no
  edge (43, 9, 97 and 30 unique-name links go from 0.5 to 0.9 with the
  same targets); flask and gin byte-identical.
- Index time, median of 3 interleaved: imgui 7.9 s main, 7.7 s fix;
  excalidraw 9.2 s main, 9.4 s fix.

Reimplements PR #1662 by ferrine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 08:15:23 +00:00
Colby MchenryandClaude Opus 5.5 2514467801 fix(jvm): a dotted supertype names its nested type, and a Kotlin object expression is an anonymous class (#3051)
A supertype written through its enclosing type was lost in three ways.
Java and C# kept the dotted path (class Named extends Outer.Inner,
class N : Outer.IListener), but the qualified-name match compared it
verbatim with p::Outer::Inner, so it stayed unresolved; a Java anonymous
class (new Api.Listener() {...}) kept only the last segment, which no
nested type is found by; and Kotlin (both engines) took the FIRST
segment, so class K : Outer.Inner() extended Outer. A Kotlin object
expression, object : Listener {...}, made no class at all: its overrides
became local functions of the code around it, which a call through the
interface never reached, and bare calls elsewhere could be guessed onto
them.

A dotted Java, Kotlin or C# supertype now resolves to the type whose
qualified name ends with the path, when the file can name the path's
first type (its own file, package or namespace, an import; C#'s usings);
of several, the one closest in the tree, or none on a tie. The Java
anonymous class keeps the written path in its extends reference, and
Kotlin joins every segment of a supertype (type arguments left out), in
the wasm extractor and the kernel alike. A Kotlin object expression is
indexed as Java's anonymous class is: a class <Listener$anon@line> that
extends each supertype as written, which the code around it
instantiates (the class it builds, else its first supertype, also as
written and only ever a type that can be a supertype), with its body's
declarations as members; its header (constructor arguments, a
by-delegate) stays the enclosing code's. An anonymous class's members,
Java's and Kotlin's, are named only inside the declaration holding the
class: a bare block() elsewhere is never an object's override val block.

The walk up a Java or Kotlin class's supertypes for an inherited method
asks each supertype by the chain of types that names it
(AssistantMessage::Builder) before its simple name, so a builder's
build() is its own AbstractBuilder's and not an MCP annotation's;
the name still answers when an extends edge reached a namesake
(Minestom's Player extends LivingEntity).

The code in an object expression still sees the scope it is written in,
as when its overrides were local functions: a property of the class
around the object types a receiver there (okhttp's server.url() in an
object : Callback is MockWebServer's), an override is not what a call
of its own name in it means (original!!.contentLength(),
this@Outer.onScroll(), an extension receiver's onDataChanged()), and an
object's members out of reach leave a by-name guess as it was
(okhttp's it.isReady() is still the SerialTask interface's).

Validation:
- jvm-dotted-supertypes.test.ts: 13 tests, 11 fail on main 60b0d7a9
  (the other two guard a dotted supertype the file cannot name and the
  calls inside an anonymous class), all pass here.
- Kernel parity, 0 files with diffs: torture.kt 1/1, Torture.java 1/1,
  okhttp 588/619 Kotlin and 71/71 Java, mihon 891/940, nowinandroid
  340/350, SystemUI 188/198 Kotlin and 1141/1141 Java, jextract 218/218,
  Minestom 1859/1894 (deferrals are main's).
- A/B against main 60b0d7a9, sites added/removed/retargeted: okhttp
  +640/-53/149, SystemUI +435/-53/116, Minestom +165/-3/6, kestra
  +108/-0/6, spring-ai +46/-0/38, playframework +27/-3/13, mihon
  +22/-5/6, jextract +22/-0/1, spring-modulith +9/-0/10, nowinandroid
  0/0/0; serilog, click, gin identical.
- Cost (median of 3 interleaved): okhttp CPU 27.8 -> 29.0 s, wall
  8.6 -> 8.8 s (its 360 object classes and 1,491 more edges); mihon
  23.6 -> 23.8 s, wall 7.8 -> 7.7 s.

Reimplements #1927 by @mixxer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 07:29:52 +00:00
Colby MchenryandClaude Opus 5.5 3a4cd3bf65 fix(ruby): calls to !/? methods and calls on what .new made link their method (#3049)
Two kinds of Ruby call linked nothing. A call to a method whose name ends
in `!` or `?` on a receiver (`writer.flush!`, `writer.ready?` after
`writer = CacheWriter.new`, `CacheWriter.create!(1)`) failed the method-call
pattern, whose method part took no `!` or `?`. A method called straight on
a new object (`CacheWriter.new(key).write_cache`, `Billing::Ledger.new.charge!`)
was recorded by both extractors with the receiver's raw text, arguments and
blocks included, which no strategy matched.

Both extractors now record a call on `Const.new(...)`, `A::B.new` or
`::Foo.new` as `new Const().m`, the constructed-receiver shape TS/JS, Java
and C# already use; the wasm extractor and the kernel's ruby.rs emit the
same refs. ruby-constant-paths resolves that shape before the name
pre-filter: the class by Ruby's constant lookup from the namespaces around
the call, the method as the class's own instance method or the first its
ancestry declares, and nothing when either is missing, so rack-protection's
`Request.new(env)` (rack's Rack::Request) links nothing. The Ruby dot pattern
in matchMethodCall takes a trailing `!` or `?`; the receiver-word scoring
among several project methods of the name stays off for such names on a
value receiver, where it was wrong on 55 of 74 new edges (`options.key?`
on a Hash -> InheritableOptions#key?). rubyAncestry no longer reads a
module a call returns (`include RouteSet.new.tap {...}.url_helpers`) as a
mixin of the constant the call starts at.

Validation, kernel engine, main 37052b18 vs this change:
- sinatra cb22afd +12, jekyll 97c3bdc +53, activesupport +398,
  actionpack +97 (2 removed: `ActionController::Base.with` had reached
  RoutesHelpers.with through a misread mixin), activerecord +1012
  (rails 793d744 subtrees). About 21 of 1,572 added edges are wrong, all
  from the class-name and single-method guesses that plain names get too.
- Controls requests (Python) and cobra (Go): dump-graph byte-identical.
- kernel-parity.mjs: fixtures 10/10, sinatra 147/147, jekyll + rails
  1044/1044 files byte-parity, none deferred.
- New tests: 10 fail with main's kernel and TS, all pass with the fix, in
  both engines. Dropping the scoring gate or the mixin fix fails its guard
  test.
- Index time, median of 3 interleaved: activesupport 5.35 vs 4.63 s,
  jekyll 2.66 vs 2.73 s (noise).

Reimplements PR #684 by @ricobl, who found both gaps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 06:47:47 +00:00
Colby MchenryandClaude Opus 5.5 b700a3eec2 fix(python): a parameter or local named like an imported module is not that module (#3046)
A Python call through a name the file imports as a module, like
other.update(...) after `from pkg import other`, linked to the module's
function whatever the calling function bound `other` to: a parameter, a
local, a with/except/loop target, a walrus, a case capture, a lambda or
comprehension variable. resolveViaImport read the file's import mappings
alone (its only scope check, pythonFunctionModuleAlias from PR #2600,
covers a function-local `import a.b as X`), and isPythonProjectModule,
which lets a builtin-named call like update/get/write past the builtin
filter for a project module, asked the same file-wide question. The
mappings also list from-imports at any depth ahead of plain imports, so a
function-local `from pkg import third as other` became every function's
`other`.

pythonReceiverBinding reads how the code around a reference binds the
name: the reference's own statement (lambda parameters, comprehension
targets), then each enclosing function, innermost first, through its
parameters and own statements (assignment, annotation, for, as, walrus,
del, nested def/class, case captures, imports; global keeps the module's
binding), memoized per function. pythonImportsInScope gives
resolveViaImport's Python branch and isPythonProjectModule only the
receiver's mappings that apply there.

Validation:
- __tests__/python-receiver-rebinding.test.ts, 80 tests (default and
  wasm, LF and CRLF): 48 fail on the base main (ten rebinding shapes,
  the local from-import, the cross-function leak), 32 pass (look-alikes:
  keyword argument, string, comment, comprehension over the module, lambda
  beside the call, global, keyword pattern, same-module local import);
  all pass with the fix. The 26 Python-related test files pass.
- A/B, main vs fix: django's own package (930 files, kernel path) changes
  2 edges' confidence (0.85 to 0.8, same targets: a closure over a
  parameter `apps` and a local `apps = model._meta.apps`); flask drops
  one cli.group() call and lowers 4 to 0.7 with the same targets (a
  nested `def cli` and a pytest fixture parameter `app` named like
  imports); requests, click, mealie and fastapi (package plus tests), and
  the express and gin controls are byte-identical.
- django package index time, median of 3 interleaved: 11.3 s main,
  10.7 s fix.

Reimplements PR #2632 by BGamboa13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 06:22:47 +00:00
Colby MchenryandClaude Opus 5.5 3d19da2f59 fix(spring): a route links the method it annotates, including a package-private one (#3009)
The Spring extractor found a mapping's handler with a pattern that wanted
`public`, `private` or `protected` (or Kotlin's `fun`) before the name, in
the next 600 characters. A package-private handler, like Spring Boot 3's
`@GetMapping List<Article> list()`, matched nothing, so its route linked the
next method declared with a modifier, or nothing at all, and a method-level
`@RequestMapping(value = "/feed", method = GET)` on such a method made no
route. A generic Kotlin `fun <T> find()` handler fell through the same way.
Separately, a route bound by name alone took the wrong overload when a tall
annotation stack (`@Operation`, `@ApiResponses`) above the mapping left the
next overload's declaration nearer the annotation.

The extractor now reads the declaration the annotation is written on: past
it and every annotation stacked with it (arguments read as balanced groups,
strings and text blocks included), the modifiers (none at all for a
package-private method), type parameters and the return type (void, a
primitive, a qualified or nested generic type, an array) to `name(`; for
Kotlin, past its modifiers to `fun`, type parameters and `name(`. A field,
a constructor or a class under the annotation gives no handler. The mapping
annotation's own arguments are read the same balanced way. At resolution,
the route's handler reference binds to the method of that name whose span
(which starts at its first annotation) holds the annotation's line, the
innermost such method; any other case resolves by name as before.

Validation, kernel engine, main 01e0ac3a vs this commit:
- realworld-java21 93e018e: Spring routes linked 4/19 -> 19/19; the 15 new
  edges each go to the method the mapping is written on.
- spring-petclinic 500158f (17 routes), spring-petclinic-kotlin 817dba3
  (18), eladmin 55fbf70 (133), mall dcaa93b (246), dolphinscheduler
  21968bc (239): same targets, only the edge metadata changes (exact-match
  0.7/0.9 -> framework 0.95).
- Controls jsoup 0c508f1 (Java, no Spring), requests 3c7baaa (Python),
  cobra adbc881 (Go): dump-graph byte-identical.
- Index time, median of 3 interleaved: mall 12.3 s vs 12.6 s, eladmin
  3.02 s vs 3.03 s (noise on a shared machine).
- New tests fail on main's java.ts (20 failing) and pass with the fix, in
  both engines; dropping the positional binding fails only the overload
  test.

Reimplements PR #851 by @Liurchen, who found the package-private gap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:06:35 +00:00
Colby MchenryandClaude Opus 5.5 a38a1ec0c0 fix(paths): a folder named like ..generated is inside the project, not outside it (#3028)
Several checks asked whether a path relative to the project root leaves
it with rel.startsWith('..'), which is also true of a folder whose name
starts with two dots, like `..generated/client.ts`. So a tsconfig paths
alias or baseUrl into such a folder linked nothing, the file watcher
dropped every edit made there (and the Linux per-folder watch tree never
skipped a gitignored one), a gitignored folder of that name could not be
brought back with codegraph.json include, a .gitignore rule for one was
never applied on the filesystem walk of a project that isn't a git
repository, and deprioritize could not name one.

One escapesRoot(rel) helper in utils.ts now answers it: `..` itself, a
first segment of `..` (`../`, or `..\` from path.relative on Windows),
or an absolute path, which path.relative gives for another Windows drive
(applyAliases did not reject that case before). It replaces the bare
prefix test at both path-aliases.ts sites, the watcher's handleChange
and shouldIgnoreDir, the extraction include walk and nested-gitignore
check, and the deprioritize matcher.

Left as they were, since a folder named that way is not a real input
there: Python source roots from config values and C/C++ include
directories in import-resolver.ts, Laravel require targets, and the
workspace-package targets, entries and path dependencies in
workspace-packages.ts.

Validation: new dotdot-folder-names tests (9) fail 8 of 9 on the base
and pass with the fix; the helper's own unit test runs against the new
helper in both. A/B against main on bulletproof-react, excalidraw,
outline, vben, gin and flask: byte-identical (none has a folder named
that way).

Reimplements PR #1406 by @amplifierplus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:17:18 +00:00
Colby MchenryandClaude Opus 5.5 f1da755a91 fix(resolution): calls on the browser's own objects link no project method (#3026)
`window.open('/help')`, `localStorage.getItem('token')`,
`location.reload()` and `history.back()` linked to a project class's
method of the same name, like an app's `WindowService::open` or
`StorageService::getItem`, so callers and impact on such a wrapper
listed every direct browser call in the app. Three readings did it:
- an untyped receiver went to matchMethodCall's guesses, by the
  receiver's word (`window` ~ WindowService) or a lone method name;
- a chain from `window`, which the extractor records by its bare method
  name so a project object hung on `window` can be found (#1707), fell to
  the exact-name match when the chain names no project object:
  `window.localStorage.getItem()`;
- a JS `self.close()` was read as `this.close()` by bareCallReceiver and
  callReceiverKind, though a `self` the file doesn't bind is the global
  object (a worker's scope, or `window.self`).

Now, in the JS family:
- a call on one of JS_BROWSER_HOST_GLOBALS (window, self, globalThis,
  document, localStorage, sessionStorage, location, history, navigator,
  screen, performance) gets no edge when the file writes that name only
  as a receiver (`document.x`, `history.back()`, `self?.close()`) or after
  `typeof`, decided before the receiver-type reader, whose backward scan
  has no scope. Any other mention (a parameter, destructured ones
  included, a declaration, an import, an argument) keeps the name the
  file's own: outline passes its Document model as `({ document }:
  Props)`, and those `document.save()` calls keep their links;
- matchCollapsedObjectCall declines a `window`/`globalThis` chain whose
  first link is one of them, after its project-object lookup; a chain
  through a project object (`window.App.init()`) is unchanged;
- an unbound `self` reaches matchCollapsedObjectCall as
  `globalThis.self`, and callReceiverKind no longer counts it as the
  instance. A `self` the file binds (`var self = this`) reads as before.

Validation: new browser-host-globals tests (4) pass with the fix. On
main the first fails with 8 wrong edges and the third (outline's
destructured `document`, a `typeof window` guard) with 2; the two that
pin what must keep working (a bound, imported or parameter name; `this`,
`var self = this`, `window.App`) pass on both. A first version that
asked only whether the file binds the name dropped outline's
destructured `document` calls; the third test fails against it.
A/B against main: only removals. excalidraw 12 (`window.open` ->
Portal::open, `document.createElement` -> DraggableTool::createElement,
a worker's `self.postMessage` -> WorkerPool::postMessage), outline 21
(test and app `sessionStorage`/`localStorage` calls -> MemoryStorage,
`window.document.body.*`, `window.history.back()` -> a Props property),
bitwarden apps/browser + apps/web 7 (`window.postMessage`,
`window.location.reload()`, `window.navigator.permissions.query()`), and
express unchanged. 39 of the 40 linked a project method by name only;
the 40th linked outline's own `declare global` typing of a browser API
(Document.modelContext.registerTool).

Reimplements PR #2915 by @KissFeng.

Fixes #2907

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:57:27 +00:00
Colby MchenryandClaude Opus 5.5 787eafbffa fix(jvm): a call through an import reaches the Java or Kotlin class it names (#3023)
A Java or Kotlin import was matched against a file path written with the
calling file's own extension (com/example/Foo.java from Java, .kt from
Kotlin), so an import across the two languages never bound: Kotlin's
JUtil.make() on an imported Java class went to a same-named class in
another package, JUtil.Nested.deep() and Outer.Inner() linked nothing,
and Java's KUtil.INSTANCE.kmake() went to another package's object.

The import is now read by qualified name in either language (the
package is some prefix of the name, so import p.Outer.Inner is
p::Outer::Inner), and the rest of the reference walks the imported
type's nested types: a Kotlin object's INSTANCE and a class's Companion,
as Java writes them, step onto the type, whose companion members the
index keeps on the class. Java's UtilsKt.helper() reaches the top-level
function of p/Utils.kt, and import p.UtilsKt links that file. A call
picks the member's method over a same-named field or nested class
(jextract's Index_h.clang_getRange() is the static method, not the
holder class of that name), the overload its argument count fits
(Minestom's NetworkBufferTemplate.template(a, g, b, h, ctor)), and, from
Kotlin, a nested Java class's own constructor. A Kotlin chain rooted at
an imported type is walked before the receiver-chain typing. Only an
imported type takes this path; an imported function or property is left
to the name strategies, which know a call's receiver.

A Kotlin extension declared in another package (fun Modes.hidden(),
indexed as Modes::hidden) is now a candidate only from a file that
imports it or its package, as Kotlin has it.

Validation:
- jvm-cross-language-imports.test.ts: 8 tests, all red on main, green
  here (cases from #1948's test and the T5 triage fixtures).
- A/B against main 86c85cff (the base, 8bd55fa7, gives identical graphs
  on jextract and nowinandroid, as 4955eac9 did there and on okhttp),
  sites added/removed/retargeted, with confidence raised at the same
  target:
  okhttp +172/-0/30 (167 raised), SystemUI +83/-0/25 (642), mihon
  +21/-26/28, nowinandroid +24/-0/5, jextract +96/-96/18 (calls that
  were instantiations of a same-named holder class now call the static
  method), spring-modulith 0/0/16, Minestom 0/0/843 (799 onto the
  overload their argument count fits, none against it), spring-ai
  +14/-12/73 (50 overloads; 22 onto the imported provider's nested
  class), playframework 0/0/37, kestra 0/0/302 (290 overloads); click
  and gin identical. Added: Java calls into Kotlin companions and objects
  (SystemUI's PhysicsAnimator.getInstance, okhttp's RequestBody.create),
  Kotlin calls into Java classes, type references across the two.
  Removed: guesses onto extensions a file cannot see (mihon's
  JsonObject.toByteArray on a String) and calls that become
  instantiations of a nested type.
- Cost (3 interleaved fresh indexes): mihon CPU 27.7 s on main vs
  27.5 s, wall 8.6 vs 8.7 s; okhttp CPU 31.5 s on both, wall 10.0 vs
  9.8 s.

Reimplements #1948 by @mixxer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:09:01 +00:00
Colby MchenryandClaude Opus 5.5 8bd55fa75c fix(frameworks): a component's same-folder preference compares whole folders (#3020)
The React, Svelte, Astro and Vue component resolvers each picked among a
name's definitions by preferring one whose path started with the
referencing file's folder, compared as text with no separator:
`src/ui/Card.tsx`'s `Widget` took `src/ui-kit/Widget.tsx`, since
`src/ui-kit/...` starts with `src/ui`. A file at the project root has the
folder `''`, which every path starts with, so its pick was whichever
definition came first, skipping the rule that a name with several
definitions and nothing to choose by links nothing (#764).

One helper, inReferencingFolder (frameworks/component-folder.ts), now
serves all four copies: a candidate counts when its path starts with the
folder plus `/`, so a folder below the referencing file's still counts,
and a root file has no folder to prefer.

Validation: new component-same-folder tests (7) fail 6 of 7 on main, one
per resolver plus the root and subfolder cases; the helper's unit test
runs on the new module in both. A/B against main: outline, mastodon
(app/javascript), excalidraw, vben, shadcn-svelte (docs/src) and gin
are byte-identical, since real code imports its components and the
framework pick runs only for a name the file doesn't import.

Reimplements PR #683 by @Mubashirrrr.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:30:39 +00:00
4955eac958 fix(rust): use bindings follow Rust's scopes, and module paths walk the crate's modules (#3014)
A bare Rust name took the file's `use`s all at once: a `use` inside one
function (`fn first() { use crate::util::take; take() }`) bound `take` for
every other function too, and for the file's own `fn take`; a renamed one
(`use crate::util::take as consume; consume()`) linked nothing, since no
item carries the alias; a parameter or closure of the function's name
(`fn run(take: fn()) { take() }`, `let take = || {};`) linked to the
imported function; and `use crate::models::E::Good; Good(1)` took any
`E::Good`. Paths through modules (`resolveRustPathReference`) walked the
directories instead of the modules: `super::helper()` and `crate::config()`
with no module between linked nothing, `super` inside an inline
`mod tests { }` went to the parent directory's module, and an item a module
passes on (`pub use`, a glob) or another workspace crate's was not found.

The fix keeps one `use` reader, rustUsesOf:
- each `use` keeps the block it is written in (a function's body or a
  block; an inline `mod` and a macro's braces, `cfg_if!`'s nested ones
  included, are the module's), and std `use`s are kept by position;
- a reference reads the binding of its innermost scope (rustVisibleUse);
  isRustNameInScope uses it, so a block's `use` shadows the file's own item
  and a variant `use` names its enum's variant (rustUsePathItems' enum tail);
- matchRustUseBoundName, ahead of the pre-filter, resolves a rename to the
  item its path names, and a call to a name a local binds (rust-scopes'
  bindings, nearer than any `use`) to nothing, unless the local is a `let`
  holding a function by its path (serde's `let test = test::<i8>;`);
- resolveRustPathReference is rustWalkModules + rustModuleItems from the
  module the path is written in (rustPathTarget), with today's crate-root
  fallback; resolveRustModuleFile, collectRustUseBindings and
  rustItemPlacement are gone, and isBoundToOutOfRepoImport reads
  rustUsesOf's std `use`s;
- `use`s are read with strings blanked (blankRustCode), so a string's
  "use crate::x;" binds nothing;
- the crate graph reads a package's name by its section (axum-core writes
  `categories = [..]` first) and a `[lib]`/`[[bin]]` path of its own
  (ripgrep's `crates/core/main.rs`), which `crate::` then starts from; a
  file no manifest covers starts it from the nearest lib.rs or main.rs
  above it, as the directory walk did.

Validation:
- rust-use-call.test.ts (PR 2308's 15 cases), rust-use-scopes.test.ts,
  rust-crate-roots.test.ts and 5 new rust-module-path-call cases: on main's
  source 18 of 32 fail; all pass. Every rust-* suite passes.
- A/B on main 01e0ac3a, 7 repos, the same sites on the kernel and on wasm:
  tokio +2,434 sites (1,354 calls, 1,053 imports), 159 removed (152 locals
  called or passed as values, 6 `Result`s std's, 1 import through a path
  attribute module); ripgrep +248 / -149; axum +710 / -60; clap +730 / -112;
  serde +155 / -207 (192 calls to a closure named `test`); dioxus
  +2,950 / -272; gleam +1,573 / -359. Groups and samples inspected, every
  changed site's crate checked against the manifests' dependencies;
  express and flask byte-identical.
- Cost (kernel, medians of 3 interleaved): tokio CPU 34.5 -> 33.0 s, peak
  RSS 991 -> 1,007 MB; gleam 32.5 -> 30.6 s, 1,022 -> 1,012 MB; dioxus
  45.4 -> 45.4 s, 1,101 -> 1,107 MB.
- Original PR #2308 by @danusha2345; its module-path part landed as PR #2955.

Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:39:13 +00:00
artttttandClaude Opus 5.5 86c85cff02 fix(kotlin): a call on an imported type reaches that package's class when two share a name
When several classes share a name, resolveMethodOnType picks the one the
call file imports by matching the import's path against the candidate's
file path (com/example/b/FooConverter.kt). That holds for Java, where a
public class names its file, but a Kotlin class often lives in a file
named otherwise (FooConverter in Converters.kt), so no candidate matched
and the call went to whichever class came first.

The imported name is now matched against each candidate's qualified
name first: import com.example.b.FooConverter expects the method
com.example.b::FooConverter::convert, which the package directive gives
every JVM declaration whatever its file is called. The file-path match
stays as the fallback.

The Java calls this once fixed, a Java file calling a Kotlin class it
imports (okhttp's Java tests and mockwebserver3.MockWebServer,
SystemUI's BlurUtils), are now resolved on main by the receiver's
declared type (#1942), so on today's main the change shows in Kotlin.

From #539 we take only this change and its test. Its other two parts,
indexing Kotlin class properties and reading Kotlin imports without a
trailing semicolon, have since landed on main another way.

Validation:
- resolution.test.ts: the Kotlin same-name case (#314's shape in
  Converters.kt files) is red on main, green here.
- A/B against main 40f58517 (with the Java receiver typing of #1942
  and the Kotlin changes of #1933, #1761 and #2258): no edge changes
  on okhttp, SystemUI, nowinandroid, mihon, jextract, spring-modulith,
  Minestom, spring-ai, kestra-java, playframework, click or gin; none
  of them has the test's shape on a resolved call. Against main
  4eb8c2a5, before #1942, it moved the 8 Java calls above onto the
  Kotlin class their file imports; main now reaches them at 0.9.
- Cost (mihon, median of 3 interleaved indexes, on main 4eb8c2a5): CPU
  25.7 s on main vs 25.2 s, wall 8.2 s vs 8.3 s.

Original PR #539 by @arttttt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:56:48 +00:00
danusha2345andClaude Opus 5.5 40f5851787 fix(kotlin): a call on an outside type reaches the project's extension on it
A Kotlin import from outside the project owns its name (#2196), so
Modifier.fillMaxSize() after import androidx.compose.ui.Modifier never
lands on a same-named project method. That rule returns before any other
strategy, so a project extension on the imported type, Compose's
fun Modifier.pad() (indexed as Modifier::pad), could not be reached by
Modifier.pad() either, nor Color.fromHex() on a
fun Color.Companion.fromHex().

Inside the rule, a Kotlin Type.member call now resolves to a project
extension Type::member that the calling file can see (same file or
package, or imported by name or with .*, as for other top-level Kotlin
declarations) and whose own file imports the same type. Anything else
stays unlinked, as before.

Validation:
- kotlin-extension-on-imported-type.test.ts: 2 cases, red on main,
  green here.
- A/B against main 13f983db (with the Kotlin receiver typing of #1933
  and #1761's private rule): mihon +26, all Compose Modifier
  extensions the project declares (secondaryItemAlpha 15,
  clickableNoIndication 5, selectedBackground 2, two scrollbars,
  keepInSection), nowinandroid +2 (Modifier.notificationDot,
  DeviceConfigurationOverride.WindowInsets), all right; okhttp,
  SystemUI, jextract, click and gin identical. The same sites as
  against main 4eb8c2a5, before #1933.
- Cost (mihon, median of 3 interleaved indexes, on main 4eb8c2a5): CPU
  25.7 s on main vs 25.6 s, wall 8.2 s vs 8.4 s.

Original PR #2258 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:42:25 +00:00
danusha2345andClaude Opus 5.5 13f983db5a fix(kotlin): a private property, and the members of a private class, stay in their file
A Kotlin property was indexed without its visibility modifier, so the
Kotlin file-local rule (#1745), which declines a private fun in another
file, read every property as public: a call like token(...) could link
to another file's @Volatile private var token, and JavaScript, Go or
Python callers to a Kotlin test fixture's private val. A public member
of a private class or object was reachable from other files too, though
nothing outside its file can name it.

Both extractors (wasm and the native kernel) now read a property's
modifiers the way they read a function's, and a candidate inside a
private Kotlin class or object is declined from another file. That
second rule is Kotlin's alone: a Scala private[pkg] class, or one with
a private constructor, is its package's, and a C# private nested class
serves every file of its partial class. Applied to every language where
private is file-local, as the PR had it, it removed 742 edges on http4s
(right ones among them, like stream.receiveHeaders() on a private[h2]
class H2Stream), 91 on scalachess and 10 on serilog (9 of them right
typed edges).

Validation:
- cross-file-visibility.test.ts: 2 cases, red on main, green here; the
  kernel parity fixture gains a private val and an internal var.
- Kernel parity (kotlin), 0 diffs: okhttp 588/619 files, mihon 891/940,
  nowinandroid 340/350, SystemUI's Kotlin 188/198 byte-identical, the
  same files deferred to wasm as with main's kernel; torture 1/1.
- A/B against main 01e0ac3a (with the Kotlin receiver typing of #1933):
  okhttp -274, mihon -177, SystemUI -94, every one a call from another
  file onto a private Kotlin property or a member of a private class or
  object (okhttp's private inner AndroidQuery.enqueue and
  RealWebSocketTest's private TestStreams, mihon's private IntentFilter
  filter that collections' filter { } reached, SystemUI's private
  MyExecutor.execute); nothing added. nowinandroid, jextract, click and
  gin identical; against main 4eb8c2a5, http4s, scalachess and serilog
  were identical too.
- Cost (mihon, median of 3 interleaved indexes, on main 4eb8c2a5): CPU
  25.7 s on main vs 25.5 s, wall 8.2 s on both.

Original PR #1761 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:07:50 +00:00
danusha2345andClaude Opus 5.5 01e0ac3aa7 fix(kotlin): a call through a property or a receiver chain resolves on its declared type
Kotlin indexes a class property without a signature, and a
primary-constructor property (class A(private val repo: Repo)) is no
node at all, so a call through either (probe.close() on a lateinit var
probe: HttpProbe) found no receiver type and fell to the name-only
strategies: the interface's method, or any same-named method elsewhere.
A call through a chain (engine.pump.drain()) was emitted as its bare
method name, and a local bound to a qualified call (val lease =
HardwareLock.tryBegin()) was untyped. tree-sitter-kotlin also predates
fun interface: such a declaration parsed as a broken function and, with
a doc comment after it, its error recovery swallowed the next
declaration too.

The declared type is now read from the declaration itself: a property's
own lines, the class header for a constructor property, a callee's
declared return type for a local bound to a call, an enum entry's enum,
each segment of a receiver chain in turn (the chain is kept in the
reference name, by the wasm extractor and the native kernel alike), a
superclass's property, and an outer function's local inside an
anonymous object. A receiver whose type the project does not declare
(Regex, Properties, a JDK class) gets no edge instead of a name-only
guess, unless the project extends that type. The extractor's preParse
blanks the fun of a fun interface, offset-preserving, so the file parses
cleanly in both paths.

Validation:
- kotlin-property-receiver.test.ts: 17 tests, 12 red on main, all
  green here; kernel-kotlin-parity.test.ts pins the fun interface
  fixture.
- Kernel parity (kotlin), 0 diffs: okhttp 588/619 files byte-identical
  (main's kernel 578, the rest deferred to wasm on parse errors), mihon
  891/940 (887), nowinandroid 340/350 (339): the fun interface files
  no longer defer.
- A/B against main with #1942 (positions only: the extractor now keeps
  a chain in the reference name): okhttp +299/-257, 2,554 retargeted,
  2,158 of them test calls moving from the deprecated
  okhttp3.mockwebserver.MockWebServer (a 0.65 name guess) to the
  mockwebserver3.MockWebServer each file imports; mihon +82/-118, 337
  retargeted (Preference get/changes/set/register onto the declared
  Preference); nowinandroid -9, 23 retargeted (properties onto their
  concrete test doubles); SystemUI's Kotlin +1/-104, 7 retargeted;
  jextract, click and gin identical. The removed are name guesses on
  library types (okio sinks, collections' filter and clear, OkHttp
  bodies, MutableStateFlow.update, Kotlin's apply). 7 right edges are
  lost where a chain passes through a library wrapper (Lazy.value,
  StateFlow.value, Pair.first, a ViewBinding's view).
- Cost (mihon, median of 3 interleaved indexes): CPU 25.8 s on main
  with #1942 vs 26.7 s, wall 8.3 vs 8.4 s.

Original PR #1933 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 02:14:52 +00:00
Colby MchenryandClaude Opus 5.5 77ee3484be fix(aliases): a paths wildcard no longer matches an import that leaves nothing for its * (#2969)
A tsconfig/jsconfig `paths` key with text on both sides of its `*`, like
"@x/*/index", matched an import that starts with the prefix and ends with
the suffix only by counting one character twice: `@x/index` starts with
`@x/` and ends with `/index` through its single `/`. applyAliases tested
startsWith and endsWith independently and sliced an empty capture, so the
import and every call through it linked to `widgets/index`, a file the
import never named.

The pattern is now skipped when the specifier is shorter than the prefix
and the suffix together, which is TypeScript's own isPatternMatch rule. A
less specific pattern can still match, and an empty capture at exactly
prefix + suffix length stays allowed, as tsc allows it.

Validation: new path-alias-overlap tests (4) fail 3 of 4 on main and pass
with the fix; the exact-length case passes on both. A/B against main on
bulletproof-react, excalidraw, outline and vben (all with paths aliases)
and gin and flask: byte-identical, as no paths key in them has text after
its `*` (0 of 44 configs with paths in our local clones). outline index
median of 3 interleaved: 19,165 ms main, 18,546 ms fix. Full suite: 9,226
passed, 0 failed, 85 skipped.

Reimplements PR #882 by @Dashsoap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 01:48:20 +00:00
danusha2345andClaude Opus 5.5 7973df0d09 fix(java): a call resolves on its receiver's declared type, and a library type gets no guess
Java typed a call receiver from a local declaration or a plain field of
the enclosing class only. A field of an outer class read inside an inner
or anonymous class (mHandler.post(...) inside new Runnable() {...}),
Outer.this.f, a chain of fields (this.mOwner.mRepo.save()) and a local
whose type has type arguments fell through to the name-only strategies,
and so did every receiver whose declared type is a library class:
parcel.readInt() on an android.os.Parcel went to a project
VersionedParcel.readInt, list.size() to any project size().

The declared type is now read for each of those shapes, a chain one hop
at a time with each hop's own imports, keeping a written package or
outer class (Map.Entry, play.api.mvc.BodyParser). A receiver whose
declared type the project does not declare, or that an import or a
written package pins outside the project, gets no edge. When no import
decides between same-named types, a Java call prefers the Java
declaration over a Scala or Kotlin one.

Also, from review: a field the class around the call declares is read
as written before the member reader, which keeps only the last name
(PauseTest.Suite, a field written with its package); a local or loop
variable keeps the package its declaration writes; a field typed by a
type parameter goes to its bound, and a class the class head names as
a type argument (Comparable<ApplicationModule>) is not taken for one; a
nested type a superclass declares, or one an on-demand import of its
class names (import play.mvc.Http.*), counts as named; and a typed miss
on a type no project class or interface is named (a URI, an enum's
name()) gets no guess, as on main.

Validation:
- java-receiver-type.test.ts: 19 tests, the PR's 5 red on main; the
  other 14 pass on main, and our 8 each fail without their change.
- A/B against main 25776014 (with #2942 and #2950; the same sites
  change against 7cceba52), sites added/removed/retargeted: jextract
  +2/-1/106, spring-modulith +1/-3/2, Minestom +52/-91/71, SystemUI
  +53/-319/96, spring-ai +69/-659/532, kestra-java +5/-504/162,
  playframework +0/-196/599; click and gin identical. Of the removed
  edges main had at 0.9, 1,004 were wrong (an import oracle plus 16
  read by hand) and 4 right (a lambda parameter, two nested types of a
  Scala object, a field read under a static import of a same-named
  member). Retargets land on the declared type or the supertype
  declaring the method, except about 16 build()/clone() calls on
  spring-ai's nested option builders, which move from one wrong target
  to another.
- Cost (3 interleaved rounds): spring-ai wall 27.9 s on main vs
  25.9 s, CPU 90.2 s vs 88.1 s. SystemUI read 71.3 vs 79.4 s wall
  under other jobs' load; CPU profiles of both arms put the new code at
  about 0.6 s of 115 s.

Original PR #1942 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 01:30:24 +00:00
5594843698 fix(go): a call on a value bound from a call or a conversion reaches its type's method (#2961)
A Go method call on a local bound from a call or a conversion, like
`r := newRing(); r.Reset()`, `v1 := router.Group(...); v1.GET(...)` or
`c, r := CreateTestContext(w); r.Use(...)`, was matched by the method's
name alone: inferLocalReceiverType reads a declaration's written type and a
few line patterns, none of which reads a call's result. The by-name guess
took a namesake (etcd's `lg := s.Logger()` linked `lg.Info(...)` to a
project logger's Info, prometheus's `NewWriteHandler(...)` results to the
read handler's ServeHTTP), or found nothing.

Main already types a value from its declaration for indexed and chained
calls (goDeclaredValueType, #2782). Plain receivers now use it too: a local
whose in-scope declaration has a call or a conversion as its value takes
that value's type (the k-th result of several, a method's result, a
conversion's type), read in the file that writes it (GoReceiverDecl.file),
for matchMethodCall, matchMemberFunctionRef and matchGoFieldChainCall. A
value of a type from outside the project links nothing, including one a
project function returns as an outside interface. Assertions stay with
goAssertedLocalType, and a type switch's guard is not typed from its value.

The typer itself, for every path that uses it:
- a trailing `.(T)` is a T whatever its prefix (`pool.Get().(*Conv)`);
- a call through a func-typed field gives the field's result type;
- a bare call a dot import brings in is typed (`package watch_test`
  calling `NewStreamWatcher(...)`);
- a method of a struct or interface literal, of an outside generic type
  (grpc's `ServerStreamingClient[Resp].Recv()`) or one the index doesn't
  show on a project type gives an unknown type, not "outside", so name
  matching runs as before.

Validation:
- go-receiver-type.test.ts (the PR's 23 cases, 2 expectations updated where
  the call is now typed, plus 4 ours), go-std-method-receivers.test.ts and
  go-chained-receivers.test.ts: on main 19 of 51 fail; all pass here. All
  45 go-*.test.ts files pass (491 tests).
- A/B vs main 25776014 (same kernel): gin +194/-52/65 moved, etcd
  +4,441/-359/743, prometheus +7,041/-239/1,409, a kubernetes staging
  subtree +1,154/-86/354, harbor without its portal +423/-138/366.
  express and flask byte-identical. Sampled additions right; removals are
  outside values (zap, os, grpc, otel, beego, pflag) and the outside-
  interface results, about 80% of which were wrong by sample. Known loss:
  a type defined over an interface (`type Codec Serializer`) has no methods
  in the index, so a coincidental guess on such a value goes, as in #2706.
- Compared with PR #1954 on the same repos: 4,962 more links and 360 of
  its edges gone, those mostly wrong guesses (166 of them the outside-
  interface results); its declaration-line restriction also lost right
  links on range variables and field values, which this leaves to main's
  scan.
- CPU (profile self time of receiver typing): prometheus 1.82 -> 1.72 s,
  k8s subtree 0.88 -> 0.92 s, harbor 1.02 -> 0.86 s.

Reimplements PR #1954 by @danusha2345 on main's value typer; its tests are
included.

Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 00:56:46 +00:00
danusha2345andClaude Opus 5.5 3f8e8b0f65 fix(rust): a module-path call links the function its module declares
A Rust path into a module, `crate::util::take(1)`, `super::util::take(1)`,
`util::take(1)` or a `use crate::util::take;`, went to the first node of
that name in the module's file, methods included, so an `impl Buf { fn
take }` above `pub fn take` caught every such call. A function written
inside another function, or in a `mod tests { ... }` above the module's
own item, could take it too, a path into the module's own file linked
nothing, and a path through a module a `use` takes from the standard
library (`fs::read` under `use std::fs;`) went to the project's own `fs`
module.

The path's item is now the module's own: never a method, an impl's or a
trait's associated item, or an item of a function body; one written at
the file's top level before one in a module the file declares inline,
which the path reaches through the module's re-export (`#[cfg(unix)] mod
imp { ... }` + `pub use imp::*;`). A path whose head a `use` binds from
the standard library is left to it (main's isBoundToOutOfRepoImport rule
for bare names), and a path into the module's own file resolves.

Validation:
- rust-module-path-call.test.ts: 8/8; on main 7 fail (method Buf::take,
  Deep::get, Only::grab, no import edge, the inline-module namesake, the
  re-export from its own file, std `fs::read` -> src/fs/mod.rs).
- A/B on main 7cceba52, 6 Rust repos: 162 changed sites, all inspected and
  right. gleam: 133 calls like `fs::write(...)` / `crate::fs::mkdir(...)` move
  from `impl ... for ProjectIO`'s methods to compiler-cli's free functions,
  4 wrong `type_::list(...)` -> `Type::list` edges are gone, 15 `use`s link;
  tokio: `crate::io::uring::open::open(...)` moves from `Op::open` to the free
  `open`, +4; dioxus +7, 1 more; ripgrep +1; clap and serde unchanged.
  express and flask byte-identical.
- Cost (medians of 3 interleaved runs): tokio CPU 36.6 -> 36.0 s, gleam
  34.3 -> 35.2 s, both within the runs' spread; peak RSS unchanged. In a
  CPU profile of tokio the changed functions take under 0.1 s. The
  standard-library check reads the file's `use`s, so it runs only when the
  path would otherwise link a project item (asked for every path, it cost
  0.3 s on tokio).

This is the separable part of PR #2308. Its bare-call bindings (`as`
aliases, block-local `use`, shadowing by locals, enum variants through a
`use`) and the second `use` parser it adds are left out: main's `use` walk
(rustUsesOf, rustWalkModules, rustModuleItems) and scope reader
(rust-scopes.ts) cover the same ground, and folding those cases into them
is proposed separately.

Original PR #2308 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 00:34:51 +00:00
danusha2345andClaude Opus 5.5 25776014e9 fix(resolution): a call on the caller's own class reaches that class's member, or what it inherits
Root cause: a bare call in Java or Scala, and a `self.`/`this.` call in
Python, TypeScript and JavaScript, reaches matchByExactName as a bare name.
With several same-named methods in reach, findBestMatch ranked them by file
and line distance, so a sibling class's method declared nearer the call won
over the caller's own class's or the one it inherits; above the 500
candidate ceiling the call was dropped.

Fix (contributor): matchEnclosingScopeMember runs before the ranking. It
reads the receiver back from the call line (bare, self/this, super) and
walks the caller's scopes innermost-out: the own class, then what it
inherits, then, for a bare call, the classes it is nested in. A member found
there is the answer (0.9), and its overloads are ranked among themselves.
A super call in a JS class expression is left to js-super-calls (PR 2497).

Ours:
- Inherited members first come from the declaration's own resolved
  supertypes (getSupertypeNodes), in the order its header lists them, a
  class further up before an interface that only declares the member; the
  PR's walk by name stays as the fallback when those edges reach nothing (an
  `extends` the index resolved to a namesake, as Minestom's Player to a
  nested EntityStatuses.LivingEntity). By name alone, Django's spatialite,
  oracle and mysql GIS editors reached the postgresql backend's
  DatabaseSchemaEditor.execute and _delete_index_sql (0.9), a class with two
  bases took either one's member (BaseDeleteView(DeletionMixin, FormMixin)
  got FormMixin.get_success_url), and SystemUI's RotationContextButton got
  RotationButton's declaration instead of ButtonDispatcher.getCurrentView.
- Only `super`, `this`/`self` and Python's `cls` name the receiver. The PR
  also read `parent.`/`base.` as super and `cls.`/`static.` as self, but
  Java's extractor drops a receiver variable of those names too: protobuf's
  `parent.getFullName()` on an EnumDescriptor went to GenericDescriptor.
- C++ is left to matchCppImplicitThisCall (PR 2479), which already looks
  members up in the class, its bases and its enclosing classes. The PR's
  C++ branch added the caller's own file's namespace functions, which beat
  the overload the arguments fit: fmt's 3-argument `copy<Char>(b, e, out)`
  calls went to format.h's 2-argument copy (0.9).
- Tests for each, and the CHANGELOG bullet in the house format.

Validation: enclosing-class-member-call.test.ts has 12 cases: 6 fail
against main's resolver, and all 5 of ours fail against the PR as written. Kernel A/B against main 7cceba52 on 15 repos (leveldb, fmt, gin and
serilog byte-identical). Every changed site was judged by a hierarchy
oracle (own class, then bases depth-first in header order, classes before
interfaces, enclosing classes for a bare call) or by hand. About 7,400
links moved from a wrong method to the right one (protobuf 3,208, vscode
base+editor 1,383, Play 806, django 766, netbox 316, SystemUI 314,
spring-ai 312), and about 9,100 right links rose from 0.7 to 0.9. One site
got worse (http4s `object RespPrelude { case class RespPrelude }`), and no
C++ site changed. CPU, median of 3 interleaved runs: kestra 154.9 s on main
vs 152.8 s, vscode base+editor 79.9 s vs 82.6 s (within the rounds' spread).
On a vendored-theme stand-in of 1,500 classes, the walk links the 4,500
calls main drops at the 500-candidate ceiling, for +2.2 s (JS) and +1.8 s
(Python). Full suite on 3df1cc64 (main 31ec9082): 768 files
passed, 6 skipped; 9,160 tests passed, 0 failed, 85 skipped.

Original PR #1952 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 00:11:08 +00:00
Colby MchenryandClaude Opus 5.5 31ec908224 fix(sync): a long-running session picks up a framework added while it runs (#2941)
The MCP server's watcher holds one CodeGraph for the whole session, and
its framework detection was cached from the first time it ran: the
orchestrator reset it only in indexAll, and CodeGraph.sync never
re-initialized the resolver. A framework that arrived mid-session, like
Express in a new package.json, was missing from every file synced after
it, so a new route file got no route nodes until `codegraph index`. The
resolver's declared-dependencies cache was also keyed by the indexed file
COUNT (PR #2519): swapping one app for another of the same size, or
editing a manifest, kept the old dependency set, even through
reinitializeResolver().

Detection now records what it read: indexed source files, which a sync's
change lists cover, and every other path it read or probed, with a size
and mtime signature, including the per-app manifests the extractor gating
reads. Before a sync extracts files, it detects again only when files
were added or removed since, a source file detection read changed, or one
of those paths changed on disk. A scoped sync takes the file list from
the index plus the files it adds, so it never walks the project (the
first sync in a process used to run a full scan for its first detection).
What detection read is kept between detections and dropped by the same
change lists and signatures, so detecting again reads only what changed.
When the detected set differs from the resolver's, the sync
re-initializes the resolver. Its initialize() now clears its caches
before detecting, and clearCaches() forgets the declared-dependencies
cache with the other per-context memos.

Files indexed before a framework change are not re-extracted. A framework
like Express applies to every file of its language, and re-reading them
all inside a watcher's sync would cost what a full index does. Instead a
full index records the frameworks it used, a sync records how detection
moved since, and `codegraph status` (text, and --json with
reindexRecommended and frameworksChangedSinceIndex) recommends
`codegraph index` while they differ. Status only reads that record.

Validation:
- sync-framework-detection.test.ts (new, 8 cases): Express arriving with a
  new route file, by full and by scoped sync; a .svelte file added with no
  manifest change; an edited source file that detection read; a
  manifest-only edit picked up by the next sync, with the status record,
  cleared on revert and by a full re-index; a fresh process's scoped
  syncs detecting once, from the index, with no scan; the equal-size app
  swap; and the dependency cache forgetting on request. 7 of 8 fail on
  main's source (the 8th needs the new export). Ten mutations, one per
  mechanism, each fail a test.
- The triage probes: main detects [] after Express arrives and routes.js
  gets no route; the fix detects express and extracts GET /hello, by full
  and by scoped sync. For the swap, main's reinitializeResolver() gives
  [react] and the fix [express].
- Cost on excalidraw (737 files, scoped syncs in one long-lived
  instance): the detection step takes 8 ms on a sync where nothing it
  read changed and 8-13 ms when it detects again, against syncs of about
  260 ms (add) to 2 s (modify). The first sync in a process detects once
  (about 300 ms) and no longer scans the project.

Reported in PRs #2516 and #2519 by @nk33-dev. This reimplements both
fixes independently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 23:31:26 +00:00
danusha2345andClaude Opus 5.5 7cceba52b0 fix(resolution): library calls on untyped Python, Scala and Java values stay unlinked
Root cause: when nothing types a call's receiver, matchMethodCall's last
strategy links `recv.m()` to the one project method named `m` (0.7), or to
the best receiver-word match among several. A standard-library call on a
plain value - `options.setdefault(...)` on a dict, `m.getOrElse(...)` on an
Option, `data.put(...)` on a map in a `var` - then linked to an unrelated
project method of that name. Go, Rust, Kotlin, C#, VB.NET and Dart already
keep such a guess only when the receiver names the method's owner
(stdMethodNames); Python, Scala and Java had no list.

Fix (contributor): Python, Scala and Java standard-library method lists in
stdMethodNames. Ours: the PR also exempted a receiver named `cls` from the
rule; both extractors already strip `cls.` from Python calls, so it never
applied there, while in C#, Dart, Rust and VB.NET it let
`var cls = Make(); cls.ToString()` link to an unrelated Money.ToString.
Removed, with a C# test case. CHANGELOG bullet rewritten and moved next to
the Python built-in method bullets.

Validation: library-method-calls.test.ts: 3 of its 5 cases fail on main
(Python build, Scala go, Java untyped), all 5 pass; the C# case fails when
`cls` is put back. Kernel A/B against main 0174f804, 11 repos: 0 added,
0 retargeted, 540 removed. Python: flask 12, netbox 2, django 140, fastapi 0,
all wrong (list/dict/file/re-match calls landing on ListMixin, QueryDict,
HttpRequest, AnonymousUser, _AppCtxGlobals). Java: Minestom 20, spring-ai 86,
all wrong (Map put/computeIfAbsent, reactor Context put, stream(), enum
valueOf). Scala: playframework 98 (4 of them Java), scalachess 31,
http4s 151; 267 wrong, 13 right links lost where a project type has
collection-like methods and the receiver is not named after it (scalachess
Board.put/replace through for-comprehension binds, ByColor through
`type Pockets`; http4s Headers.put; Play Form.fold, Router.orElse). gin and
serilog byte-identical. CPU on playframework, median of 3 interleaved:
71.7 s main, 71.1 s branch. Also settles the Holder.LOOKUP.get and
Mode.ON.equals cases of PR 1949.

Original PR #1947 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 22:57:59 +00:00
Aaron QueenandClaude Opus 5.5 c30696703d fix(cfnptr): a C function-pointer table links only to C and C++ functions
resolveFn in the C function-pointer pass looked a registered handler's
name up across every language, filtered only to function and method
kinds. In a repository that also holds Rust or Python, a C table could
link to a same-named Rust or Python function, and when that function
sorted first it also took the place of the real C handler.

The fix keeps candidates in C and C++ files only, using the pass's own
C_CPP_EXT, the file set it scans for tables and dispatch. Headers the
language detector labels Objective-C still count. Objective-C `.m` and
`.mm` files stay out, like they are out of the scan: a table in a `.m`
file is never read, and allowing them would let an Objective-C method
named `init` or `read` stand in for a C handler of that name.
On GLFW, which mixes C and Objective-C, main links 354 table entries,
none into a .m file; its one C table naming a .m function
(_glfwConnectCocoa) is an anonymous-struct table the pass doesn't
bridge on main either.

Validation, main b6379e6d vs this:
- c-fnptr-synthesizer.test.ts: the new case fails on main (impl_read
  links to aaa/gen.py) and passes here; 19/19.
- Linux d135ab45 subtree (rust/, drivers/android, drivers/base,
  drivers/net/dsa, fs, tools/net, include/linux; 6,580 files):
  byte-identical, 12,244 fn-pointer-dispatch edges, all into C or C++
  files on both arms. Main links none of this tree into Rust or Python,
  so the PR's 193 (a whole-kernel index on its fork) don't show here; a subtree holding its three example sites (pmcraid's
  `->reset_cmd = cmd`, afs's `->update_time = new`, map.c's
  `p->lock = lock`) has none either, since a plain assignment of a
  variable doesn't register on main.
- C and C++ controls byte-identical (dump-graph, every table): redis
  7f440372 (863 fn-pointer-dispatch edges), GLFW 92dcf4ce (354), jq
  49fbca12 and leveldb 7ee830d0.
- The resolver change is TS-side; the kernel ports only the per-file
  sweep, so both engines take the same edges. kernel-parity on the
  kernel's drivers/base, drivers/net/dsa and drivers/android (279 C
  files): 240 at parity, 0 diffs, 39 deferred to wasm by policy.

Ours on top: the CHANGELOG bullet moved from the released 1.6.1
section to Unreleased and reworded.

Original PR #1989 by @bompus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 18:57:30 +00:00
luojiyinandClaude Opus 5.5 0174f804c3 perf(resolution): read a bare JS/TS call's local binding once per match — graph byte-identical
matchByExactName asked isLocallyBoundJsName for every cross-file
candidate of a bare JavaScript or TypeScript call, though the answer
depends only on the reference's name and file, which are the same for
every candidate. The helper memoizes it per file and name, but each
call still built the key and read the memo. The answer is now kept in
a lazy local for the one match, at the same place in the filter, so
candidates, order, scoring and confidence are untouched.

Validation: fresh indexes of codegraph's own source, excalidraw, vben,
express and zod dump byte-identical to main's (nodes, edges with their
metadata, unresolved refs, files), and express stays identical through
sync after a JS file is added, edited and deleted. A CPU profile of
indexing codegraph's own source (all threads, two runs per side) puts
isLocallyBoundJsName's self time at 150-173 ms on main and 45-46 ms
here; the scans it guards (bindsAtSites) are unchanged. Whole-index CPU
on vben, 10 interleaved runs, moves less than its noise. The PR's 8
tests pin the matcher's behaviour and pass here and on main.

Original PR #2871 by @luojiyin1987.
Fixes #2870

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 18:32:43 +00:00
bompusandClaude Opus 5.5 7ff2841e37 fix(resolution): ties that decide a link break by code unit, not the host's locale
Several resolution tie-breaks sorted paths or ids with a bare
localeCompare, which follows the host's locale, so two machines could
index the same repository differently: under Czech collation `ch/` sorts
after `h/`, under Danish `aa` sorts after `z`, and locale collation puts
`alpha/` before `Beta/` where code-unit order does the opposite.

The PR fixes the nearest-declaration tie in tsFieldCall, for
`this.field.method()` on a type declared in two equally near apps. On
top, the same bug at the three other sites that decide resolution:
- cpp-type-aliases: the declaration an alias resolves to when two
  headers declare it differently, for a C++ base class named through it;
- frameworks/react-native: the Swift copy of a module's class that a
  JS-to-native call reaches;
- go-func-field-synthesizer: the last tie-break of wiringOrder, which
  decides the values a capped channel keeps (its site comparison already
  names 'en' and stays).
All compare by code unit now. Comparators that only order output (CLI,
MCP and viewer listings, dead-code and hierarchy order, the context
builder's copybook entries) are left alone. The CHANGELOG bullet moves
from the released 1.6.1 section to Unreleased.

Validation: a test each for the TS, C++ and React Native sites; all 3
fail on main and pass here. Indexing `ch/` vs `h/` fixtures for those
three under a simulated English and a simulated Czech host (a
localeCompare preload that reaches the worker threads), main links
different targets in all three, this commit the same in both. A/B, main
vs this commit on an English-locale host, 12 repos (excalidraw, zod,
vben, bulletproof-react, jq, cobra, gin, etcd, prometheus, okhttp, with
flask and spring-modulith as controls): every dump byte-identical.
Indexed again under a simulated Danish host (the Go repos) and Czech
host (the TS repos), both arms match their English dumps, so these
repos reach no tie the locale decides; the fix matters where one does.

Original PR #1983 by @bompus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:05:28 +00:00
Aaron QueenandClaude Opus 5.5 4eb8c2a5f2 perf(synthesis): line numbers from a newline index, not a split per match
Two quadratic idioms in callback-synthesizer.ts. Twelve passes computed
each match's line as `safe.slice(0, m.index).split('\n').length`, which
copies and splits the file's prefix for every match, and `sliceLines`
split the whole file again for every node a pass looked at (7 callers,
one per node of the same file in a row).

The twelve sites now call `lineOf(src, idx)`, which keeps the newline
index of the last text (built with synth-utils' `makeLineAt`, which
counts the newlines strictly before `idx`, exactly like the old idiom),
and `sliceLines` keeps the last file's lines.

Ours on top: `releaseLineCaches()` at the end of synthesizeCallbackEdges.
The pool workers' copies go with the workers (pool.destroy() after
synthesis), but a sync runs synthesis on the main thread, so a long-lived
MCP server or `watch` kept the last file's text, its lines and its newline
index until the next synthesis. And a celery test with two files of the
same length scanned in a row, which fails if the line index is reused
across texts (checked by keying the cache on length).

Validation, main 81559dcc vs this:
- dump-graph of every table (nodes, edges with metadata, refs, files)
  byte-identical on pretix c9f7bf86 (79,452 edges), trezor-suite
  505aece5 (493,138 edges), excalidraw d3a9c2a1 (46 react-render),
  vben 61c07936 (128 pinia-store), gin and eShopOnWeb (Go and C#
  controls with gin-middleware-chain and mediatr-dispatch edges).
- The pass outputs are identical, edge for edge and in the same order,
  when each touched pass runs alone over the same index. Their CPU, the
  median of 3 runs after a warm-up: pretix 1,018 -> 845 ms (React render
  204 -> 32 ms, field channels 235 -> 172 ms), trezor-suite 7,686 vs
  7,841 ms (noise; its Pinia and Vuex scans dominate and are untouched).
- Whole-index CPU on pretix, median of 3 interleaved: 99.8 vs 99.4 s.
  No CHANGELOG entry: the gain is per pass, not visible per index; the
  PR's bullet sat in the released 1.6.1 section and is dropped.

Original PR #1985 by @bompus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 16:45:47 +00:00
danusha2345andClaude Opus 5.5 0b194448dc fix(resolution): a call a method hands on to another object no longer links back to it
matchMethodCall's strategy 3 guesses a method by its name when the
receiver's type is unknown. With one method of that name in the project
it was taken outright, so a wrapper handing its call on, like Play's
`def acceptsURL(url) = proxied.acceptsURL(url)` or vben's
`form.validate()` inside `FormApi.validate()`, linked to the method
making the call, which then looked recursive in callers and impact.

The single-candidate branch now gives no edge when that method is the
caller itself and the receiver is another object (not this, self, Self,
super or cls), as the word-overlap branch already did.

On top of the PR: Go is left out. A Go method's receiver and struct
fields are typed before this point, so a Go call that still reaches the
guess is on a local the resolver could not type, mostly a range variable
over the type's own children, which is real recursion: cobra's
`sub.checkCommandGroups()` over `c.commands`, etcd's `child.Repr(...)`,
prometheus's `subParameter.toLabels()`. Also a TypeScript wrapper test, a
Go range-variable recursion test, and a CHANGELOG bullet.

Validation, main (3a93f57a) vs this change, 24 repos in 11 languages: no
edge added; 63 self-loop edges removed, 56 of them wrong (delegations to
a library or another object: playframework 19, vben 9, http4s 8, mihon 8,
okhttp 5, ripgrep 3, flask 2, spring-modulith 2) and 7 real recursion
through another instance of the caller's type, which now gets no edge
(okhttp's Kotlin extension functions on File and Throwable 3, fastapi's
_IncludedRouter 2, a serilog C# extension method 1, spring-modulith 1).
As written the PR also removed the 8 Go recursion edges. Re-run on
playframework after #2875 changed Play's resolver: 17 removed, the same
less Java's two Multipart.* calls, which main now links to the Scala
object itself. Index CPU on okhttp, median of 8 interleaved runs: 32.8 s
on main, 32.3 s with this change. Tests: 2 of 4 fail on main; the PR as
written fails the Go range-variable case.

Original PR #1939 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 16:45:04 +00:00
danusha2345andClaude Opus 5.5 b6379e6d5e fix(scala): an Outer.Inner(...) call reaches the Scala case class or object it builds
Scala qualified names carry no package (Outer::Inner), so a call written
Outer.Inner(args), a case-class or companion apply, found no type by its
written name. It fell through to the name strategies, and under a return
type annotation (def f(): WebSocket = WebSocket.Accepted(flow)) the
local-declaration reader took WebSocket for the receiver's type and handed
over an unrelated Java constructor, Other::Accepted::Accepted.

Before the receiver-word strategy, a Scala call whose member is
capitalized now resolves to the Scala type Outer::Inner when that is
unambiguous: every match in one file (a class and its companion), or one
in the call's own file; a class or trait wins over a same-named object.
It is recorded as instantiates for a class, as a call for an object. The
typed-receiver path no longer hands a Scala call another language's
constructor. Real Java static methods are still reached from Scala.

Validation:
- resolution.test.ts: 5 cases, 4 red on main; the fifth (a Scala call
  to a real Java static) passes on both and guards the Java side.
- A/B against main 81559dcc (with #1950 and #1936): scalachess +77/-0,
  http4s +371/-1 (the one a retarget from a wrong defaults::Host to the
  Origin.Host case class), playframework +291/-1 (the one a retarget:
  Writeable's HttpEntity.Strict(...) from the Java play.http constructor
  to the Scala case class), play-samples +2 (two case-class applies #1950
  unlinked from Java constructors); CMAK, click and gin identical. Every
  addition is a Scala case-class or object apply; sampled all right
  (Uci.Move, Header.Raw, H2Frame.Data, RemoteNode.Ip,
  ApplicationLoader.Context, Request.Connection over headers.Connection).
- Cost (http4s, median of 3 interleaved fresh indexes, on main
  69af19ac): wall 8.6 s on main vs 7.8 s, process CPU 27.5 s vs
  26.2 s, within noise. The A/B gave the same edges on both bases.

Original PR #1951 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 15:59:08 +00:00
danusha2345andClaude Opus 5.5 81559dcc37 fix(resolution): a call reaches the caller's own copy of a duplicated module
A module kept in several copies, like two Android build flavors that each
hold one package, gives every name in it one candidate per copy.
Directory proximity is capped at six shared folders, so in a deep tree
the copies scored the same and the first indexed won: a call in one
flavor could land on the other flavor's copy. And since the name is not
in doubt among copies, only which copy, scoring the right pick by
proximity (0.7, or 0.4 in a shallow tree) understated it.

findBestMatch now breaks a tie among copies of one symbol (same
qualified name, kind and file name) toward the copy whose file shares
the longest directory prefix with the reference, when that copy is
uniquely closest. matchByExactName scores that pick 0.8: above a
proximity guess, below a unique name or the file's own definition (0.9,
which keeps precedence).

On top of the PR: the copies are judged among the candidates
findBestMatch weighed, so a namesake in another language no longer
blocks the 0.8; a match of the caller itself keeps the proximity score,
as it does for a same-file match; and Kotlin and Java flavor tests, since
the PR's Python fixture calls a function it never imports.

Validation, main (cde4cd74) vs this commit, 21 repos in 11 languages:
no edge added or removed; 4 targets moved, all right: spring-modulith's
example-full tests now reach example-full's own OrderManagement and
OrderCompleted, where Spring's naming rule (0.7) took the epr-jdbc
example's copy, and playframework's shutdown-happy-path test its own
src/main HomeController, not the copy its script swaps in later. 109
edges keep their target and go to 0.8, each onto the copy in the
caller's own module or app; 4 of them are vben's `defineConfig` calls
from `import { defineConfig } from 'tsdown'`, which main already links
to a namesake because that dependency is declared only in the root
package.json. In 18 sites the 0.8 now beats a framework rule's 0.7.
Index CPU, median of 3 interleaved: okhttp 34.8 vs 34.8 s, etcd 36.5 vs
36.5 s. Tests: 3 of 6 fail on main, 2 on the PR as written.

Original PR #1936 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 15:26:51 +00:00
danusha2345andClaude Opus 5.5 4c572e07b6 fix(play): a Class.method reference reaches a member of the class it names
The Play resolver claims every Class.method reference in a Play project, in
routes files and in ordinary Java and Scala code alike, and took the first
method of that name anywhere in the class's file. In a file holding several
classes (Java nested classes, a Scala class with its companion and
siblings) that was usually another class's method: MediaType.parse(...)
went to Cookie.parse, a route controllers.Application.index to a nested
Helper.index, and Scala RequestAuthority.parse(...) to AuthorityPort.parse.

A method now counts only when its qualified name is the named type's own,
cls::method. A Scala object is an owner too, so a companion's own members
are found through it, and for a call in code so are a Java interface's and
enum's static methods. For a call written in code, the types of the
caller's language are tried first: Play declares RequestAuthority both as a
Scala case class and as a Java record, and each language's callers mean
their own; the other language's types still follow, since Play's Java API
calls its Scala classes. Among same-named types the caller's own file comes
first (Scala names carry no package), an overloaded member gives the
overload the call's arguments fit, and a Java file's import of the name
from outside the project leaves the call to the generic resolver. A route
keeps the plain order and takes a class or object. An inherited member is
left to the generic resolver, which finds it through declared supertypes.

Validation:
- frameworks-integration.test.ts: the PR's case (nested Http.Cookie /
  MediaType, Scala sibling objects, a route beside a nested Helper.index),
  red on main; ours, Scala and Java callers of a RequestAuthority declared
  in both languages (red on main and with the PR's own resolver) and an
  inherited route handler; and a guard for overloads, same-named objects
  in two packages and a Java interface's static method.
- A/B against main cde4cd74: playframework +113 / -257, 510 retargeted;
  CMAK 47 retargeted, all first-in-file picks corrected; play-samples -2;
  click and gin identical. The 50 Scala parse calls that records sent to
  the Java record reach their Scala type again.
- Cost: playframework process CPU, median of 3 interleaved fresh indexes,
  63.3 s on main and 62.8 s here.

Known costs, left as follow-ups:
- 177 right edges main reached only through its first-in-file rule are
  lost: 171 Assets.resourceNameAt calls in AssetsSpec through a local
  val Assets = new AssetsBuilder(...), which the generic resolver drops
  because it reads Scala's private[controllers] as file-private, and 6
  Writeable.writeableOf_* calls inherited by object Writeable extends
  DefaultWriteables, an object's supertypes the generic resolver does
  not follow.
- 8 calls in Scala files that import Play's Java EventSource, Comet,
  Environment or (in a nested scope) Accumulator reach the same-named
  Scala object: no Scala import is read.

Original PR #1950 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 01:52:27 +00:00
danusha2345andClaude Opus 5.5 cde4cd74eb fix(resolution): a lone same-file definition scores as a strong match
matchByExactName scored a winner among several same-named candidates by
directory proximity alone: 0.7 when the files share two folders, else
0.4. findBestMatch already ranks the definition in the reference's own
file first, so the target was right, but a shallow file's own function
read as an uncertain guess (below 0.6), and because the resolver keeps
the most confident strategy, the same-file definition lost to a
framework's naming guess in another file (ASP.NET's PascalCase and
Service rules at 0.7 and 0.85).

isDecisiveSameFileMatch now scores it 0.9, as a lone candidate, when it
is the only candidate in the file, is not the caller itself, and, as a
member of a type, module or namespace, belongs to one the caller is in.
The PR checked that last rule for methods only. We extended it to every
member kind whose owner the file declares, other than a function, so a
sibling enum's entry or a sibling class's field is not raised.

Validation, main vs this commit, 21 repos in 11 languages (flask, click,
fastapi, gin, cobra, etcd, ripgrep, jq, Slim, serilog, eShopOnWeb,
nowinandroid, okhttp, http4s, playframework, spring-modulith, express,
zod, bulletproof-react, excalidraw, vben):
- no edge added or removed;
- 9 targets moved, all in eShopOnWeb and all right: C# properties and
  fields named after their type that the code reads (Color Color), which
  ASP.NET's naming rule had linked to the class in another project;
- 6,927 edges keep their target and gain confidence, 535 of them from
  0.4; in 874 a framework rule's 0.7-0.85 now loses to the same target by
  exact name; 30 end in another file, Go type positions the type gate
  moves to the package's own type;
- as written, the PR also raised 77 edges onto another type's member to
  0.9, about 48 of them wrong (okhttp's InterceptorOverridesTest enum
  entries, playframework's sibling providers' `get`); they keep main's
  confidence.
Index CPU, median of interleaved runs: okhttp 32.3 vs 32.4 s, etcd 33.4
vs 33.1 s. Tests: 3 of 7 fail on main; the PR as written fails the
sibling-member case. js-class-expressions pinned the old 0.4 for
`this.run()` reaching the class's own `run` in a shallow file; it now
expects 0.9.

Original PR #1938 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 01:23:32 +00:00
danusha2345andClaude Opus 5.5 3a93f57ae1 fix(java): a record is indexed as a class, with its components' fields and accessors
Neither engine treated record_declaration as a class type, so a Java record
left no node of its own. Its methods and explicit constructor became
package-level functions (p::norm, p::Point), a nested record's methods were
filed under the class around it, compact constructors, static fields and
records nested in a record were dropped, and calls on a record-typed value
resolved to nothing or to a same-named member of another type.

The Java extractor and the kernel now treat record_declaration as a class
and a compact constructor as a method. Each component becomes a private
field ("<Type> <name>") and a public accessor name() with the component's
type as its return type, unless the body declares it; they are synthesized
before Lombok's members, the same way.

An implicit accessor is reached only through what types its receiver: a
typed receiver, a method reference, a qualified call, or a call from inside
the record. The name-only strategies (the receiver-word guesses, exact-name
and fuzzy matching) no longer settle on one, so result.content() on a
library type stops linking to whichever record has a content component.

Records usually sit in sealed hierarchies, and that exposed a resolver gap:
the member-type reader took the head "sealed interface Blocks permits Block"
for a member named Block of type "permits", typed the receiver of
Block.fromKey(...) inside the record as a built-in and dropped the call.
"permits" and "throws" are now among the words that are not a type there.

Validation:
- java-records.test.ts, 5 cases on both engines (10 tests), all red on
  main; the sealed-permits and name-only-guess cases are each red without
  their resolver line.
- Kernel parity: TortureRecord.java in the torture and CRLF runs; real-repo
  sweeps at full parity with deferrals unchanged (jextract 218/218,
  spring-modulith 682/682, playframework 801/801, kestra 2997/2997,
  spring-ai 2491/2492, Minestom 1859/1894).
- A/B against main d02a88d0, every bucket triaged by sampling (edge sites
  added/removed): jextract +207/-1, spring-modulith +375/-1, playframework
  +80/-2, Minestom +17,963/-287, spring-ai +8,806/-471, kestra
  +10,077/-497; most retargets are a constructor function becoming its
  record. The accessor rule drops only name-only results (spring-ai 1,063,
  kestra 765, Minestom 408 sites at 0.65-0.8 confidence); no typed call,
  method reference or qualified call moves. click and gin byte-identical.
- Cost (process CPU, median of 3 interleaved indexes): Minestom
  47.2s -> 50.3s (+6.6%, for 17% more nodes and 23% more edges),
  spring-ai 93.5s -> 94.8s (+1.4%).

Original PR #1946 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 22:14:17 +00:00
Colby MchenryandClaude Opus 5.5 d02a88d052 fix(java,csharp): a call on a new object reaches the class the file imports, not a namesake (#2864)
PR #2854 sends Java's and C#'s no-argument new Foo().bar() calls, which
the chain conformance pass retries, to the constructed-receiver resolver.
That links them (mybatis-3 +265, Newtonsoft.Json, serilog and jellyfin
+36, all right), but the hint for which Foo it passed was a file path,
which the resolver reads for these languages as a dotted package name,
and C#'s using names namespaces, not classes. So of two classes named
Foo the first indexed one won, whatever the file imported: import b.Foo;
new Foo().bar() linked a.Foo::bar at 0.9, and so did a call the language
itself cannot resolve, after import a.*; import b.*;.

When several project classes share the name, Java and C# now pick as
the language does: Java the written package (new a.Foo()), the file's
single-type import, else the caller's own package; C# the one type the
file's namespaces and usings can see. Only that class's own method links,
otherwise nothing. A name only one class has resolves as before,
inherited methods included.

Validation: the new test fails 4 of 4 on main 44adef82 with the kernel
and with CODEGRAPH_KERNEL=0 (import b.Foo linked a.Foo::bar, and the
ambiguous calls linked a.Foo and A.Foo), and passes here with both;
new-expression-receiver and kernel-tsjs-parity pass. A/B against main,
kernel per arm: flask, mybatis-3, Newtonsoft.Json, serilog and jellyfin
byte-identical, so the 301 links PR #2854 added there stay, mybatis-3's
2 calls on a class with a same-named copy in another package among them,
which resolve to the caller's own package as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 20:40:56 +00:00
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