Commit Graph

1225 Commits

Author SHA1 Message Date
Colby Mchenry 85aad42f6f fix(resolution): a test double is never the one-method guess (#2197)
Strategy 3's scoring already skipped a test double (`Mock…`, `Fake…`,
`Stub…`, `Dummy…`, `Spy…`) the receiver and the calling file never name
(#2175), but the unique-method shortcut before it did not: django-allauth's
`resp.json()` on a Django test-client response went to `MockedResponse.json`
193 times, as the project's one `json`. The shortcut now applies the same
rule.

django-allauth -193, pytest -15 (`MockTiming`), mealie -8 (`FakeSession`,
`_FakePostClient`); netbox, healthchecks, typeorm, eShop, BookStack
unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:58:52 +00:00
Colby Mchenry da1312e83b fix(kotlin): read Kotlin imports; an outside import owns its name (#2196)
The JVM import reader required a `;`, so a Kotlin file had no import
mappings at all. It now reads `import a.b.C` and `import a.b.C as D`.

- The outside-import rule from #2195 applies to Kotlin: `import
  java.net.Proxy`, `import org.mockito.Mockito.mock`, `import okio.buffer`
  own their names. A declaration the file itself makes comes first, in its
  own namespace (a callable for a call, a type otherwise) - Exposed's
  `toLocalDateTime(value)` beside `import kotlinx.datetime.toLocalDateTime`
  is the column type's member; gson's `new URI(...)` stays `java.net.URI`
  beside `TypeAdapters`' field `URI`.
- The mappings pick among same-named project classes: okhttp's
  `MockResponse.Builder()` (642) and `Request.Builder()` reach their classes,
  Exposed's `TestDB`/`SchemaUtils` the module the file imports, moshi's
  `Moshi.Builder()` (102).
- A renaming import binds its alias by FQN: kotlinx.coroutines' `import
  ...internal.unsafeFlow as flow` makes its 28 `flow { }` builders
  `unsafeFlow`, not the public `flow`.

okhttp -486 +1303, Exposed -1685 +1351 (duplicate-module reroutes; losses
are kotlin.test/kotlinx/okio/clikt/Gradle functions), moshi -21 +121, okio
-128 +51, koin -22 +11, coroutines -68 +47, nowinandroid -44 (Gradle DSL,
Compose). Java repos (gson, commons-lang, retrofit, jsoup, halo)
unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:45:40 +00:00
Colby Mchenry 81ce7e5695 fix(java): an import from outside the project owns its name (#2195)
A single-type or static import (`import java.lang.reflect.Field;`,
`import static org.mockito.Mockito.verify;`) whose package prefix is none of
the project's JVM packages now makes that name mean the outside class: no
reference, call or instantiation resolves it to a same-named project symbol.
Imports of the project's own classes - nested ones included - resolve as
before, since their package prefix is a project package.

Wrong edges removed: gson's production code -> a test's nested
`ParameterizedTypesTest.Field` (42), jsoup's W3C converter -> jsoup's own
`Document`/`Element`/`Node` (51, it imports org.w3c.dom), retrofit's
`java.lang.reflect.Type` -> a test class `Type` (204) and `org.junit.Rule` ->
test rules, commons-lang `IOException` -> a test's nested exception (233),
halo's Mockito `verify(...)` -> a code store's `verify`, and
`new TemplateEngine()` -> an application.yaml key.

gson -60, commons-lang -389, jsoup -51, retrofit -466, halo -83, mall
unchanged; no edges gained.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:33:00 +00:00
Colby Mchenry ad77e5fc8e fix(java,kotlin): extract enum constants' own bodies (#2194)
A Java enum constant or Kotlin enum entry with a class body -
`PLUS { int apply(...) { ... } }`, `NewBuffer { override fun pipe() ... }` - was
recorded as a bare enum_member: its body was never visited, so its methods
were missing and every call in them was dropped. jsoup's tokenizer
(`TokeniserState`, one `read` per state) and tree builder
(`HtmlTreeBuilderState`) are written this way.

The body is now visited under the member (QN `Op::PLUS::apply`), which
counts as class-like so its functions are methods and its properties fields.
Only a `class_body` child is visited, so Rust's struct-like variants (a
`body` field of field declarations) are untouched. Mirrored in the kernel's
Java and Kotlin walkers; kernel parity is byte-identical on the fixtures
(Torture.java gains a constant-with-body enum; torture.kt already had one)
and on gson/koin/okio.

jsoup +2156 edges, okio +138 -23, okhttp +106 -11, gson +97, halo +29,
commons-lang +16; koin, Exposed, mall unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:26:19 +00:00
Colby Mchenry 8410db42a1 fix(scala): block-local vals, package imports and package objects (#2193)
- A `val` the graph files under a class but that is written inside a block
  of its body (cats' `test("…") { val f = … }`) is that block's alone. It
  took `f(…)` calls from lambda parameters in other suites and from cats'
  core library (`FreeT::FlatMapped::f` from option.scala, Invariant.scala…).
  A `val`/`const` declared inside a function's lines is likewise local
  (outside C/C++, whose nesting is not trusted; a Lua global stays global).
- In a class body with no enclosing method, a binder (a lambda parameter, a
  `val`) counts as local only inside a block still open at the site.
- Only an import rooted at a value (`import builder._`) lets any type's
  members in; a lowercase package path (`import cats.syntax.all._`) did too.
- An imported object's supertypes are in scope, and `package object x
  extends Api` (no node in the graph) is read from source - imported, or the
  file's own package: sttp's `multipart(...)`, `asJson(...)`, `asEither(...)`.

cats -478 +63, sttp -153 +81 (sampled losses: other tests' locals, scalatest
`test`/`eventually`, libc `free`, `sttp.model.Header`). fs2, http4s, okio,
koin, Exposed, lodash, excalidraw, flask, kong, jekyll, halo unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:15:47 +00:00
Colby Mchenry 7c1c9a3691 fix(ts): export * as ns exports the namespace, not every member (#2192)
`export * as core from '../core/index.js'` was recorded as a plain wildcard,
so a barrel walk for `z.number()` went into core, on through core's own
`export * as regexes`, and landed on the `number` regex constant - ahead of
the `export * from './schemas.js'` that holds the schema function. zod's
`z.string()` (262 calls), `z.number()` (141), `z.email()`, `z.boolean()`...
all went to regex constants.

- A namespace re-export is now its own kind: it supplies only `ns`, and a
  member path through it (`z.coerce.number()`, `z.core.safeParse()`)
  continues in that module. A 3-segment chain is resolved when its root is an
  `import * as` binding and only through the import resolver - untyped chains
  stay unresolved as before.
- A named re-export (`export { safeParse } from '../core'`) is found for a
  namespace import's member; the walk compared against `'*'` and only reached
  those through the misread wildcard.

zod -471 +571 (every regex-constant edge now reaches the schema function;
`z.lt` -> core `_lt`, `z.coerce.string` -> coerce.ts), trpc +4 and kit +1
(namespace-import members through named re-exports). typeorm, hono,
excalidraw, element-plus, starlight unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:01:48 +00:00
Colby Mchenry e77ca83621 fix(js): resolve calls through CommonJS require bindings (#2189)
Four gaps left CommonJS calls on the local alias variable - or on the wrong
function:

- `require('../')` up to the project root produced an empty relative path, so
  its `/index.js` candidate was never found;
- a default import never followed `module.exports = X` /
  `exports = module.exports = X` / `module.exports = require('./lib/x')`;
- only the first declarator of `var a = require('x'), b = require('y')` was a
  binding, so `b()` - a package call - stayed on the local variable;
- `require('./utils').member` was read as the module's DEFAULT export: every
  such alias in express resolved to utils' first exported function
  (`normalizeType`). It is now the named member, and `exports.name =
  function impl(...)`, `module.exports.name = impl` and `module.exports = { a,
  b: c }` bind names the export index could not see.

express -1087 +727 (707 `express()` calls reach createApplication; supertest /
node:assert calls no longer edge to the local variable), eslint -750 +827
(`new RuleTester()` reaches the class, not another test's variable), mocha
-83 +90 (`run(...)` no longer lands in another spec file), koa 65 wrong
`request` edges -> `response`, body-parser +20. zod, trpc, hono, typeorm,
axios unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:49:15 +00:00
Colby Mchenry 60f1db03c8 fix(lua): follow a local alias to the function it names (#2188)
Kong (and OpenResty code generally) localizes everything it calls:
`local splitn = require("kong.tools.string").splitn`, `local check_phase =
phase_checker.check`, `local fmt = string.format`, `local type = type`. Every
bare call then resolved to the same-file alias variable - 8,000 edges on kong,
more than its calls into functions - a dead end for the real callee.

A bare Lua call whose name is a `local` alias in scope is now followed:
- `require "m"` (or a require-named loader like kong's `reload_module`) plus
  members, or a member of such a local, goes to the module's function of that
  name, or to what the module exports under it (`return { check = check_phase }`,
  `unindent = misc.unindent`, up to three hops);
- an alias of a Lua global or standard-library table is no edge, unless the
  project patches that function itself outside its tests (kong's `ngx.sleep`);
- a module outside the repo is no edge;
- anything else resolves as before.
A `local` reached this way is visible across files.

kong: -7057 +1150 (every loss is a std/outside alias or a reroute), indexing
4.3s -> 3.3s. apisix -3507 +170, lua-resty-core -967 +231, telescope -28 +17
(all reroutes or outside modules), lazy.nvim reroute only, roact unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:34:52 +00:00
Colby Mchenry 1f64e15886 fix(react-router): read links through a route-config object's getHref (#2187)
bulletproof-react writes every link as `<Link to={paths.app.discussion.getHref(id)}>`
and every redirect as `navigate(paths.auth.login.getHref(...))`, so none of its
navigation reached a route. The href reader now follows the config object to
where it is declared (through imports and tsconfig aliases), takes the string or
template the helper returns, keeps a whole-segment `${id}` as a parameter and
drops a hole glued onto a segment (a `?redirectTo=` suffix).

The Link tag pattern also stopped at the `>` of an arrow attribute written
before `to` (`onMouseEnter={() => prefetch(id)}`), which hid those links.

bulletproof-react: 0 -> 11 navigates edges; jira_clone, ecommerce-react,
react-redux-realworld, create-t3-turbo and excalidraw unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:17:06 +00:00
Colby Mchenry 5c3aa92f23 fix(cpp): keep classes written around macros and mid-declaration conditionals (#2186)
nlohmann/json's basic_json was lost whole. Offset-preserving preParse
fixes (the kernel parses the same text):

- an access specifier spelled as a macro (JSON_PRIVATE_UNLESS_TESTED:,
  Q_SIGNALS:) becomes the keyword it stands for, padded
- a lone attribute macro is blanked after a comment line (/// @sa
  https://…/meta/ ends in '/') and after a template head (ends in '>'),
  which read as operator continuations; a line's continuation is judged
  on its code, so 'CURLE_X, /* 100 */' still continues (curl's enums)
- a preprocessor conditional where the code before it hasn't ended a
  statement/declaration (in an initializer list, between a template head
  and its function, in an argument list) keeps its first branch:
  directives and later branches blanked; #define continuation lines and
  statement macros without ';' (RAPIDJSON_DIAG_OFF(x)) are not code

Parse-error files: redis 192->186, curl 210->203, pybind11 98->95,
spdlog 111->110, leveldb 21->20, none newly broken across json, rapidjson,
fmt, jq; kernel parity 0 diffs; basic_json 0 -> 393 members.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:07:46 +00:00
Colby Mchenry 2646b93829 test(ui-server): hold the engine's busiest symbol to the row cap, not 500 (#2183)
The local-only check against this repo's own index asserted LRUCache.get
has 500+ callers; sharper resolution (calls on maps and caches that were
never LRUCache's no longer link to it) brought it to 496 and the test
failed on every machine with a local index. What it checks is that the
hub's list is capped and grouped, so require more callers than the
300-row cap instead.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:52:25 +00:00
Colby Mchenry fcea0222f9 fix(kotlin): rewrite syntax newer than the grammar before parsing (#2182)
tree-sitter-kotlin 0.3.8 predates `when` guards (2.1), `..<` (1.9),
multi-dollar strings (2.1) and nullable function-type receivers; each
made error recovery drop the class around it (Exposed's dialect and
query classes came out as loose functions).

Offset-preserving preParse rewrites (so the kernel parses the same text):
`is X if cond ->` -> `is X,   cond ->` (the condition stops short of
`->`, so `is X -> if (…)` bodies are untouched), `..<` -> `.. `, the
`94189` prefix blanked, `T?.() ->` -> `T.( ) ->`. Parse-error files:
Exposed 65 -> 43, kotlinx.coroutines 50 -> 46; koin/okhttp/moshi/
nowinandroid/okio unchanged, none newly broken; kernel parity 0 diffs.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:51:32 +00:00
Colby Mchenry 5a2b5607ee perf(resolution): read a class's member lines once for member-type inference (#2181)
Member-type inference rescanned the whole class body — method bodies
included — once per distinct receiver name, and rebuilt the calling
method's body text for the shadow check on every ref; the inherited-
class-method walk re-read heads per ref. On Exposed (huge test classes)
methodCall misses cost ~3x the pre-#2171 time.

- a class's member-level lines (brace depth <= 1, comments/strings
  dropped) are collected once and each name only regexes those
- the supertype walk and the shadow check (now over the whole calling
  function) are memoized per (class|function, name)
- declared supertypes and inherited class methods are memoized

Edges unchanged on Newtonsoft/eShop/gson/Exposed/koin/horse/DRF (±2 on
AutoMapper/commons-lang/okio from the whole-body shadow check); Exposed
index 8.5s -> 7.7s, Newtonsoft 3.6s -> 3.3s.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:26:44 +00:00
Colby Mchenry aa0c73c416 fix(resolution): a call through a class name reaches the class method it inherits (#2180)
Strategy 1 (a receiver naming a class) looked only in the class's own
file, so an inherited class method fell to the name-word guess: Horse's
THorse.Get('/ping', …) (THorseCore's, three class(…) heads up) went to a
route-group interface's Get 120 times.

When the class itself declares no such method, walk the supertypes its
declaration heads name (Pascal class(…), Python bases, Ruby <, PHP/TS/JS
extends, and the existing Java-family / Dart / Swift / ObjC / Scala
readers), nearest first, up to five levels.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:13:27 +00:00
Colby Mchenry de74c050bc fix(objc): a message to super reaches the superclass (#2179)
`[super init]` was judged like a self-send, so the sending class's own
override passed (SDDiskCache's [super init] came back to itself), and
where tree-sitter-objc lost an @implementation's range no hierarchy was
read at all, so any class's init passed (NSObject subclasses' [super
init] -> SDDiskCache::init).

- a super-send excludes the class it is written in
- a lost @implementation's class is read from the enclosing method's
  qualified name (SDWebImageDownloaderDecryptor::initWithBlock:)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:04:59 +00:00
Colby Mchenry 2d0d120a35 fix(resolution): Solidity bare calls stay in the inheritance chain; Erlang follows -import (#2178)
- Solidity: a receiver-less call reaches a function of the contract
  around it or one it inherits (heads read from source, `is` clause with
  constructor args dropped); OpenZeppelin's _msgSender() in Context-based
  contracts went to ERC2771Context's override 83 times.
- Erlang: another module's function is called bare only through
  -import(Mod, [f/N]) (or from an included .hrl): cowboy's
  -import(req_SUITE, [do_get/3]) went to compress_SUITE's do_get/3; with
  no import, no cross-module guess.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:59:19 +00:00
Colby Mchenry 48498021e6 fix(resolution): calls on a library's types and modules stay the library's (#2177)
- Rust: a CamelCase path receiver the project doesn't declare (Vec::new,
  String::from, Default::default — not Self, not a SCREAMING static) is
  an outside type; a lowercase module path (task::spawn, io::stdout)
  never reaches a method. tokio: task::spawn -> TaskTracker::spawn x300.
- Rust/Go: a chain link on a later line (bat()\n.stdout(…)) is chained,
  named after the chain's head; std::process / assert_cmd pipe and
  assertion names join the Rust std list.
- Go: w.Header().Get/Set/… is net/http's Header.
- R: a plain call never reaches an object's method (base range(x) went
  to a ggproto Coord's range) — exact and fuzzy paths.
- JS: fetch body methods (text/json/arrayBuffer/blob/formData) are
  builtins.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:51:48 +00:00
Colby Mchenry 5844eb8986 fix(cpp): a qualified name reaches a declaration in a macro-opened namespace (#2176)
fmt opens its namespace with FMT_BEGIN_NAMESPACE, pybind11 with
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE), rapidjson/spdlog with
…_NAMESPACE_BEGIN — the extractor sees a bare macro line, so every
declaration inside is indexed without its namespace and fmt::format(…)
(1,728 calls on fmt) resolved to nothing.

As a last resort for a qualified C/C++ name, read the project's
namespace macros (object-like and single-parameter function-like
openers, through identifier aliases, inline namespaces transparent;
closers by brace count), the ranges each opens per file, and namespace
aliases (namespace py = pybind11;), and match the declaration whose
macro prefix + qualified name is the name called. fmt +2480, pybind11
+1446, spdlog +264; tokio/serde/gin/excalidraw/Newtonsoft unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:38:59 +00:00
Colby Mchenry c0a38a8758 fix(resolution): a receiver-name guess is never a dispatched handler or an unnamed test double (#2175)
The receiver-word guess picked framework-dispatched handlers and test
doubles by a single shared word: django-allauth's test client.post(…)
landed on a ClientRegistrationView's post 425 times, mealie's
response.json() on a test's _FakeHTTPResponse 424 times.

- view / viewset / controller / endpoint (and view-mixin) HTTP-verb and
  CRUD-action methods are ruled out of the guess (and the ruling-out can't
  leave a lone guess behind — narrowed)
- a Fake/Mock/Stub/Dummy/Spy owner is guessed only from a file that names
  it or a receiver named like a double
- Python: `x = Model.objects.create|get|first|last|latest|earliest(…)`
  types x as Model (Django manager calls), so x.save() reaches the model

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:27:41 +00:00
Colby Mchenry c6491ff62d fix(kotlin): record infix calls as calls (#2174)
`Users.id eq id1`, `single { … } bind I::class`: an infix_expression was
never extracted, so a project's infix DSL had no callers (Exposed's
`eq` > 1,000). Extract it as a call of the middle simple_identifier —
`lhs.fn` when the left operand is a plain name, else bare — in the TS
walker and the kernel (kernel-parity 0 diffs on okio/okhttp/Exposed).

A standard infix name on an expression receiver is gated like a chain
link (`alias(x) apply false` in a build script); a project's bitwise
extension on a number type (okio's `Byte.and`) is never chosen, typed or
guessed, since the stdlib's Int/Long members take `x and 0xff`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:52:23 +00:00
Colby Mchenry 0b74e94ee7 fix(kotlin): no automatic semicolon before a same-line infix e-word (#2173)
tree-sitter-kotlin 0.3.8's external scanner inserted an automatic
semicolon on the same line before any word starting with `e` other than
`else`, so Exposed's `Users.id eq id1` ended its statement early and
error recovery could swallow the class around it (r2dbc UpsertTests
became one loose function nesting every test).

Patch scanner.c (same line: never a semicolon before an e-word) in both
the vendored kernel C and the wasm build; provenance, shas and rebuild
recipe in docs/grammars/tree-sitter-kotlin.md. Exposed files with a parse
error 229 -> 71 of 1,004; koin, nowinandroid, moshi, okio, okhttp and
kotlinx.coroutines unchanged (one okhttp file fixed, none broken);
kernel-parity 0 diffs.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:36:22 +00:00
Colby Mchenry 2e2b98c94e fix(kotlin): a bare call reaches only a member in reach (#2172)
A receiver-less Kotlin call exact-matched any class's same-named method:
koin's module { } in one test went to another test class's private
module 268 times, error("…") to a Logger's error, get() inside a
definition lambda to Koin's get rather than the Scope it runs on.

A bare call now reaches a method of a class, anonymous object or extension
receiver around it (read from nodes and from the source's braces, so a
class the parser lost still counts) and what those inherit (with Android
framework ancestry), of a type the project's function types take as a
lambda receiver (plus outside types it writes extensions on), or of an
object the file imports. with/apply/run blocks and .kts scripts are left
unjudged; Kotlin's require/check/assert preconditions are the stdlib's.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:20:22 +00:00
Colby Mchenry f9530d7d5f fix(resolution): a call through a declared member calls that member's type (#2171)
C# / Java / Kotlin receivers that are fields, properties or parameters were
typed only when declared in the calling method without generics, so a call
through a field fell to the receiver-word guess: Newtonsoft's
_innerWriter.WriteValue() landed on the wrapper's own WriteValue, a
TextWriter's Write() on a test writer's, commons-lang's DateFormat field's
parse() on DateParser 284 times.

- read class-level member declarations (brace depth 1, comments skipped),
  walking base classes with their type arguments substituted, C# using
  aliases, type-parameter bounds, generic and for-each declarations
- C# interface members default public and namespace-level types internal
  (TS extractor + kernel, parity 0 diffs); interface calls resolve at all
- the receiver-word guess weighs the owner type's name only, the receiver's
  last link (a constant's type), and its head noun; .NET std names need a
  receiver named after the owner; production code never lands in a test
  suite's mock

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:54:31 +00:00
Colby Mchenry a39fd14e89 fix(swift): read a call's shape before reaching a member by name (#2170)
Swift's extractor keeps one receiver level, so super.init(…), a longer
chain's removeAll() and a bare min(a, b) all arrive as bare names and
exact-matched whichever project member shared the name. A bare or self./
super. call now reaches only a member of the types around it and what they
inherit or conform to (source-read heads, UIKit ancestry, the standard
collection protocols), nearest first; a standard-named chain link needs a
receiver that names the owner, an outside owner, or an argument label only
the project's method takes; playground and test-target globals stay put.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 13:43:36 +00:00
Colby Mchenry 791ae397e5 fix(resolution): a receiver declared with an outside type calls that type (#2169)
When a receiver's type is inferred from its declaration or initializer and
the method isn't on it, the matcher used to fall through to its
capitalized-name and name-overlap guesses. If the inferred name is a type
(capitalized last segment) that nothing in the project declares — JDK /
BCL / okhttp / Django types — it now stops: the method is that outside
type's. Python's initializer pattern no longer reads a keyword argument
(`prefix=IPNetwork(…),` inside a call) as a binding of `prefix`.

A/B (edges removed): commons-lang -1296 (`append` / `length` / `charAt` on
JDK types), Newtonsoft.Json -496 (`MemoryStream.ToArray`, `ToString`),
gson -367 (`List.add`, `Map.put`), retrofit -321 (okhttp `Request.headers()`
/ `method()`), jsoup -130, okio -26, pytest -44 (pathlib / io), netbox -25,
healthchecks -9 (`TimestampSigner.sign`), flask -9, zod -1.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 13:18:33 +00:00
Colby Mchenry b7b2a44cfa fix(kotlin): split primary constructors keep their class; std chain names stay std (#2168)
- tree-sitter-kotlin can't read `class Name` / (comments) / `internal
  constructor(…) : Super {` on separate lines — the class is dropped and its
  members become loose top-level functions (okio's ByteString, Kotlin
  Multiplatform's `expect` classes). The Kotlin preParse now blanks that
  constructor's modifiers / annotations / keyword to spaces, so `(…) :
  Super {` parses as the primary constructor; offsets and lines survive, and
  preParse runs before the kernel, so both paths parse the same bytes.
- With members back on their classes, a Kotlin standard-library name on an
  untyped chain link (scope functions, collection / string / conversion
  methods — not names project types commonly carry) needs a receiver named
  after its owner, in the exact / fuzzy filters and the unique-name guess.

A/B (edges removed / added): okio -1028/+1127, okhttp -979/+1145,
kotlinx-datetime -426/+515 (mostly re-keyed onto recovered classes),
koin -60/+4 (`msg.contains(…)` → a dependency map's `contains`).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 13:09:06 +00:00
Colby Mchenry 715c62b0d9 fix(rust,go): read a call's shape before guessing its method (#2167)
The extractors keep one receiver level, so chain links arrive as bare names.
Read at the call site:
- bare (no `.` / `::` before it): never a method — Rust reaches one only
  through `self.` / `Type::`, Go through a receiver (axum's routing
  `get(handler)` went to a cookie jar's `get`, tokio's `drop(x)` to `Drop`
  impls, #1861's pinned `reset()` → `Target::reset` is now the free fn);
- chained (after `.`, receiver read back through arguments, macros `name!`,
  `::` paths and Go composite literals): never a free function, and a
  standard-library method name (Option/Result/iterator/collection/string
  methods; Go's String/Error/Header/locks/reflect/time) needs a receiver
  named after its owner — project-specific names keep their match (clap's
  `flag("n").short('n')` → Arg::short, cobra's `c.Root().Name()`, gin's
  `c.Set(…)`).
The same std gate applies to the method-call matcher's unique-name guess.

A/B (edges removed / added): clap -1496/+93, tokio -1064/+235, axum
-695/+42, ripgrep -477/+32, cobra -167, serde -119/+1, bat -40/+4, gin
-32/+26.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:58:08 +00:00
Colby Mchenry 05740b55eb fix(resolution): a call on an outside type is not a project method (#2166)
`Name.method()` where `Name` reads as a type in the language's conventions
and nothing in the project (same language family) is called `Name` — JDK /
BCL / DOM / Indy types — now leaves the method-call matcher before the
capitalized-receiver and name-overlap guesses. Not applied in Go (exported
package variables), C / C++ / Rust / CUDA / Metal; in Pascal only Delphi
type prefixes (T/E/I…) and `Exception` count, since parameters and locals
are capitalized there too.

A/B (edges removed): commons-lang -1636 (`Integer.valueOf` → StringUtils
×804, `Calendar.getInstance`, `Instant.now`), horse -89 (`Exception.Create`
→ EHorseException), retrofit -79 (OkHttp `RequestBody.create` / `HttpUrl.get`),
typeorm -74 (`Object.assign` → ObjectUtils), gson -100, zod -63,
Newtonsoft.Json -57, jsoup -18; gin and cobra byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:44:03 +00:00
Colby Mchenry 37bcd5c0ae fix(react-router): nested, lazy and constant routes; per-app tsconfig aliases (#2165)
- Route tables compose nested paths: a child's `path` is relative to the
  path-bearing routes around it (`children` of a data router, nested
  `<Route>` elements), absolute ones reset. `lazy: () => import('./x')`
  (object and JSX) renders the module's default export (`Component` export
  as fallback) via a claimed `lazy-import:` ref. `path: paths.app.root.path`
  keeps its parts on the node's signature and is named in postExtract from
  the constant's object literal (found through the file's import). The
  route keeps its extracted id; `isReactRouterRoute` accepts it. An element
  wrapped in parentheses or a guard (`<ProtectedRoute>`, `<Suspense>`,
  `*Provider`, `*Guard`) renders the first element inside.
- Import aliases: the tsconfig / jsconfig nearest the importing file that
  declares `paths` is tried before the root's, so each monorepo app resolves
  its own `@/*` / `~/*`.

A/B (edges removed / added): bulletproof-react -43/+367 (0 → 9 named,
linked routes; cross-app imports fixed), react-native-reusables -103/+193,
kit -184/+218, trpc -45/+71 (examples' `~/…` now their own), create-t3-turbo
-1/+13; excalidraw, halo, obytes, realworld byte-identical. #1348's nested
fixtures now expect composed paths (`/dashboard/settings`, `/data/prefs`).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:34:11 +00:00
Colby Mchenry cf449fdad7 fix(resolution): a name destructured from a composable reaches what it returns (#2163)
`const { getDefaultActivityRoute } = useDefaultActivity(); getDefaultActivityRoute()`
bound the name locally, which ruled out every cross-file candidate, so the
call resolved to nothing. A bare JS-family call through a name destructured
(`{ key: alias }` included) from a call's result now resolves the callee —
through the file's imports, else the same file, else the project's one
function of that name (Nuxt's `~/` alias) — checks its source returns the
key, and takes the function declared in its body, else the module-level one
it returns. A later declaration at the call's scope shadows the binding.
Returned object literals now count as exporting their names for #2162's
unexported-binding rule.

A/B edges added (removed): mealie +176 (-4), excalidraw +156 (`const { t } =
useI18n()`), halo +58, kit +34 (-14, `validate` now the validator's own),
elk +19; trpc byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:15:50 +00:00
Colby Mchenry d03e4dbf6f fix(resolution): an unexported ES module binding is not reachable cross-file (#2162)
The sealed-module rule only covered modules exporting nothing. Per symbol
now: a JS/TS top-level binding declared by its own statement (function,
const/let/var, class, interface, type, enum) in an ES module is reachable
from another file only if it's exported — the `export` keyword (node flag or
its line), an `export { a as b }` list, `export default x` or
`export default { a, b }`. Classic scripts, CommonJS, `declare global`,
`.d.ts`, qualified members, object-literal members (zustand store actions)
and `proto.x = function x()` are exempt. Applied at the reachability gate,
so the winner is rejected rather than another promoted.

A/B (edges removed / added): zod -229 (package `z` imports → a local `z`),
excalidraw -66 (DOM `Element` → a test helper's type), kit -50, typeorm -47
(`import { ColumnMetadata } from "typeorm"` → a fixture's local), trpc -26,
hono -8, axios -1; express byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:07:48 +00:00
Colby Mchenry 2d1b644133 fix(extraction): Flow-typed JavaScript is read as TSX (#2161)
tree-sitter-javascript can't parse Flow annotations; `render(): React.Node`
ended a class early (segmented-control's component had no methods). A
`.js` / `.jsx` whose leading comments carry `@flow` (not `@noflow`) now
detects as `tsx`, and the TS extractor's offset-preserving preParse blanks
Flow-only syntax for those files: exact object `{| |}`, a maybe type's `?`,
an inexact object's bare `...`, `import typeof`, `opaque type`. preParse is
hoisted before the kernel, so both paths parse the same bytes;
kernel-parity now routes .js/.jsx through detectLanguage too (RN core
Libraries: 0 diffs over 641 files). Grammar preload adds tsx whenever
JavaScript is present.

Parse errors over 400 RN-core Flow files: 10,626 (JS grammar) → 391 (TSX).
A/B edges: RN core Libraries 29,649 → 38,194 (+12,639 / −4,094, the
removals mostly `root.render(…)` → the one DrawerLayoutAndroid::render the
old parse left, and edges re-keyed onto recovered methods);
segmented-control +35/−5; express and react-native-netinfo byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:55:43 +00:00
Colby Mchenry 7aa124c0aa fix(scala): a bare call reached by name alone must be in reach (#2160)
Read at the site:
- a later link of a chain (the extractor keeps one receiver level; the line
  shows the dot) reaches a member whose owner the receiver chain is named
  after (call / type arguments dropped: `proc("x").call()` → proc,
  `checker.value.onWrite` → checker), or a same-file member; never a package
  object's function unless it is a Scala 3 `extension` method;
- a name the enclosing def binds (parameter, val/var/def, lambda / for param)
  is only a def inside that function;
- otherwise a member of the types around it, an anonymous subclass's bases
  (`new OptionParser[C]("x") { head(…) }`), their extends/with supertypes
  (source-read heads), same file, or an imported object (`import Foo._`,
  `import Foo.{bar}`); a value import (`import builder._`) leaves the file
  unjudged.

A/B (edges removed / added): cats -1693/+678, sttp -887/+271, munit
-265/+175, scopt -245/+78, os-lib -115/+43.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:46:04 +00:00
Colby Mchenry 5ba3674629 fix(resolution): a framework-resolved edge names its resolver (#2159)
The README said every bridge hop carries `metadata.synthesizedBy`, but only
the synthesized channels (rn-event-channel, fabric-native-impl) did; a
resolver-made hop (Swift↔ObjC, RN legacy / TurboModules, Expo Modules JS)
carried only `resolvedBy: 'framework'`, and `expo-module-extract` named
nothing that exists. The framework loop now adds `metadata.framework` (the
resolver's name: swift-objc-bridge, react-native-bridge, expo-modules-js,
fabric-view, rails, laravel, …) and the README says what each kind carries.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:36:02 +00:00
Colby Mchenry 9269f92dd5 fix(aspnet): FastEndpoints routes, and minimal API paths get a leading slash (#2158)
- FastEndpoints: an endpoint class (`: Endpoint<…>`, `EndpointWithoutRequest`,
  `Ep.…`) declares `Get(…)` / `Post(…)` in `Configure()`; each becomes a route
  linked to the class's own HandleAsync / ExecuteAsync (the bare-member scope
  from #2151 keeps two `List` endpoints apart). A constant path
  (`CreateContributorRequest.Route`) is read in postExtract from wherever the
  class declares it; `$"/{nameof(Project)}s"` reads as `/Projects`.
- `app.MapGet("api/todos", …)` is named `/api/todos`.

A/B: ardalis/CleanArchitecture 0 → 24 routes (+24 edges); eShopOnWeb and
eShop only re-key routes whose path gained a leading slash; realworld and
jasontaylordev/CleanArchitecture byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:30:56 +00:00
Colby Mchenry 3151d5c469 fix(vapor): closure, webSocket and on(…) routes are routes (#2157)
Only `<builder>.METHOD(…, use: handler)` registrations were read. Now also:
- trailing-closure routes, `app.get("hello", ":name") { req in … }`, and
  `app.webSocket("chat") { req, ws in … }` (method WS), `routes.on(.GET, "x")
  { … }` — statement-start only, so `if let v = req.parameters.get("x") {`
  and an HTTP client's `req.client.get("https://…") { … }` don't count;
- `routes.on(.POST, "x", body: .collect(…), use: handler)`, with only
  unlabeled string arguments as path segments.

Routes main → this branch: SwiftPackageIndex-Server 13→34, vapor 6→265
(its tests), swiftchat 0→1, SteamPress 27→27, penny-bot 0→0.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:23:58 +00:00
Colby Mchenry a5b0160e5b fix(play): a Play project in a subdirectory is still Play (#2156)
Detection only looked at the root (`build.sbt`, `conf/routes`,
`conf/application.conf`); playframework/play-samples keeps each project's
`conf/routes` in its own directory and had no routes. Any indexed Play routes
file now counts. play-samples 0 → 139 routes (+108 edges); root-level Play
repos are unaffected.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:16:46 +00:00
Colby Mchenry 110208973f fix(rails): routes are read with their namespaces, scopes and nesting (#2155)
- Route files (`routes.draw`, or under config/routes/) are read as a block
  structure: `namespace` / `scope` add path + controller module, nested
  `resources … do` sit under `/:parent_id`, `member` / `collection` add
  `/:id/x` / `/x` actions, `root` and `resource` / `resources` options
  (`only:` / `except:` across lines, `path:`, `controller:`) are honored;
  `do` and `if` / `unless` / `case` / `begin` open frames an `end` closes.
- Detection accepts `gem "rails"` (double quotes), railties, and a Rails
  engine's `config/routes.rb` anywhere (solidus).
- A namespaced `controller#action` only reaches controllers of its own
  module path when any exist — an admin controller that inherits `index` is
  not the API's `ZonesController#index`.

Route→action edges main → this branch: mastodon 221→578, solidus 0→319,
rubygems.org 108→192, maybe 127→189, lobsters 141→196; no non-route edge
lost except solidus's CamelCase refs now resolved by the Rails model
resolver (`Order` → Spree::Order model, was a state-machine module).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:12:36 +00:00
Colby Mchenry 11cc3d9647 fix(flask): add_url_rule and view_func registrations are routes (#2154)
`bp.add_url_rule('/path', 'name', view_func | positional view, methods=[…])`
and a project's own wrapper that hands a list of paths and a `view_func=`
over (flaskbb's `register_view(auth, routes=[…], view_func=X.as_view(…))`)
now produce route nodes linked to the view. A class-based view is ANY unless
`methods=` says; a function defaults to GET. A call whose path isn't a
string literal (the wrapper's own `add_url_rule(route, …)`) adds nothing.

flaskbb 0 → 103 routes (+104 edges); flask's own tests +27; CTFd, microblog,
bluelog byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:03:06 +00:00
Colby Mchenry be91a3120b fix(lua): a library call is never the one project method of that name (#2153)
The unique-name method guess no longer applies to a Lua/Luau call through a
standard or host library table (string, table, math, io, os, coroutine,
debug, utf8, package, bit, jit, ffi, vim, ngx, busted's assert/spy/stub/mock,
love) unless the project defines that function on the table itself (kong's
globalpatches define ngx.sleep), nor to a string method called on a value
(`s:find`, `s:match`, `s:gsub` …). Other untyped receivers keep the guess:
a word rule like Ruby's was tried and lost koreader's OO calls
(`self.ui:reloadDocument()` → ReaderUI).

A/B (edges removed / added): kong -2742, koreader -101, lazy.nvim -84,
telescope.nvim -47, Fusion -9, lua-resty-core -1.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:54:33 +00:00
Colby Mchenry e6527a8d34 fix(php): an untyped receiver keeps a unique method only when named for it (#2152)
PHP joins the untyped-receiver languages for the unique-name method guess:
`$x->m()` takes the one project method named `m` only when the receiver
shares a word with its owner, or names a class (the whole name, or its last
camel word: `$newRole` → Role) whose ancestry — extends + trait `use`,
namespace-aware — has the method.

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

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

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

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

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

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

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

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

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

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 09:04:08 +00:00