mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-11-09 09:33:37 +08:00
5753de20f5cac6a47d69d7372f3bebee992c6626
207
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5753de20f5 |
fix(swift): an extension is not the type it extends (#2107)
`extension View { … }` parses as a class_declaration, so the index held a
`class View` node for every file extending SwiftUI's View, Text, Color,
String, URL. With no declaration of the type in the project, every
`struct X: View`, `Text("…")` and `Color.red` bound to whichever
extension came first: on IceCubesApp 6 of the top 12 "most depended on"
symbols were extension files (View 632 edges, Text 402, Label 209). And
a bare `@State` bound to a view model's nested `enum State` (253 edges),
`@Test` to a test's nested struct (821 on SwiftPackageIndex), `: Error`
to a nested `IOError.Error` (233 on swift-nio).
A gate at resolveOne's seam (every strategy, framework patterns and
name matching alike) checks a Swift reference's type target against what
the site wrote:
- an extension moves to the declaration it extends, or the ref stays
unresolved when the SDK declares the type;
- a bare name of a nested type needs the site inside its parent, the type
itself, or a subtype that declares inheriting from the parent;
- a qualified name must fit its path. The index keeps `Build.Id` as a ref
named `Id`, so the qualifier is read back from the source line
(generic arguments stripped: `EventLoopFuture<T>.Isolated`); a member
indexed top-level because an `#if` ended its type's body early still
fits a parent declared in its file.
Redirects pick the unique declaration in scope, then the same file, then
the nearest directory; a tie is left unresolved.
Two things rode on the wrong edges and are kept:
- `getSupertypes` named `View` for `struct X: View` only because that
edge resolved to an extension. Swift conformances to a type the
project only extends are now read from the declaration head (once per
node, memoized; a per-node unresolved_refs query was +75% index time on
swift-nio), so `X().withEnvironments()` still resolves to the
`extension View` method.
- interface-impl dispatch read a protocol's methods off the node the
conformance landed on. Swift protocol requirements are not extracted as
methods; the default implementations live on `extension P` nodes, which
are now included — every extension, not one arbitrary one (+359
dispatch edges on swift-nio, +44 on Alamofire, none lost).
Measured edge diffs vs main, every removal classified:
IceCubesApp 2473 removed (172 redirects to the declaration, 1989
SDK-extension edges, 280 wrong bare nested-type bindings, ~30 from the
class the typealias parse error swallows); SwiftPackageIndex 3810 (1461
redirects); swift-nio 10991 (5085 redirects); Alamofire 1368 (198). Index
time at parity (swift-nio 4.1s vs 4.0s, Alamofire 1.33 vs 1.37,
IceCubes 1.31 vs 1.31).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
48b1a19b19 |
fix(vapor): bind a route to the handler on the type it names (#2106)
`use: SearchController.show` kept only `show`, and name matching bound every controller's `show` route to whichever `show` came first — on SwiftPackageIndex-Server 7 of 13 routes pointed at another endpoint's code (Search, HealthCheck, ValidateSPIManifest and ReadyForSwift6's `show` all at AuthorController.show). The route's handler ref now keeps the type: `Type@method` (dotted for nested types, `API.PackageController@get`), `@method` for `self.x` and a bare `x`, and a `let todos = TodoController()` receiver's type. The Vapor resolver claims these and resolves on the owner's qualified name: nested types, handlers declared in `extension Type` in another file, and `extension API.PackageController` (whose node carries only the last segment, so its declaration line decides). `self.x` / bare `x` take the method of the type the route is written in. Of one type's overloads, the one declared to take a `Request` wins. Two types, or none, leave the ref unresolved rather than guess. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
179ac85bd9 |
fix(steps): draw how a server-rendered endpoint answers; bare PHP calls are functions (#2103)
Found testing `codegraph ui` on Spring and Laravel: a server-rendered
endpoint's Steps picture never said how it answers. petclinic's POST
/owners/new showed its database write and no reply; BookStack's book page
showed a 404 and not the page it renders.
Spring MVC answers by what a handler RETURNS, which is no call:
- A new tree-sitter reader, returnsInTree / returnsForFile (graph/
branch-guards.ts, exposed as SiteReader.returns), lists the returns a
definition makes itself — never one in a lambda, a local function or a
nested / anonymous class — and the string a same-class constant holds.
- steps.ts: for a mapped method of a class that renders views (not
@RestController, not @ResponseBody), a returned view name / constant is a
200 render, `"redirect:…"` a 302, `"forward:…"` a forward, each under the
condition it is returned in (effectLink takes a preset effect for a site
that is no call). Non-literal returns are not guessed at.
- effects.ts: `new ModelAndView(…)` (200, or 302 for "redirect:…") and
`new RedirectView(…)` (302) are replies.
Laravel's `view(…)` and `redirect(…)` were already reply rules, but never
reached them:
- name-matcher.ts: a PHP call written without a receiver is a function
call, so only a `function` is a candidate — BookStack's every
`return redirect(…)` bound to ApiDocsController::redirect, every
`view(…)` to a `$view` field (and, through the fuzzy fallback, to the
class `View`). PHP refs record the call expression's column, so the call
text at that column decides. koel alone loses 60 such wrong edges
(`basename()`, `auth()`, `value()`, …).
- effects.ts: the PHP reply rule never matched a chain — rules see the text
with argument lists stripped (`response->json`), the rule wanted `()->` —
so `response()->json(…)`, `redirect()->route(…)`, `redirect()->back()->
withErrors(…)` were never replies; `to_route` added; redirect chains are
302s. PHP's SPL programming-error exceptions (InvalidArgumentException,
LogicException, …) are no longer replies, and a namespaced exception is
read by its class name.
petclinic POST /owners/new: 200 `view owners/createOrUpdateOwnerForm` WHEN
result.hasErrors(), 302 `redirect:/owners/${…}` WHEN !result.hasErrors().
BookStack GET /books/{slug}: 404, 200 `view`, 302 `redirect` (the
InvalidArgumentException from a helper is gone). realworld's REST login is
unchanged.
Not in scope: PHP has no site reader (no branch-guard rules), so Laravel
replies carry no WHEN and no arguments — `abort(Response::HTTP_…)` and
`response()->json()` show without a status.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
0419a51636 |
fix(synthesis): link a Spring or Laravel test's request to the route it reaches (#2100)
Tests reach a controller by URL, never by a call — MockMvc's
`perform(post("/owners/new"))`, Laravel's `$this->postJson('api/me')` — so
every controller those suites exercise read as untested: the viewer's
"No test reaches this" and explore's `tests:` line both walk callers.
petclinic's processCreationForm, hit three times by OwnerControllerTests,
said no test reached it.
A new synthesis pass (tier-synthesizer section 4, registered after the
cross-tier pass, gated on java / kotlin / php) reads test files only:
- Spring: MockMvc builders (with MockMvcRequestBuilders imported),
`request(HttpMethod.X, …)`, the Kotlin `mockMvc.get(…)` DSL,
WebTestClient `.get().uri(…)`, TestRestTemplate, RestAssured.
- Laravel: `$this->get/post/…Json(…)`, `->json('POST', …)`, `->call(…)`
on a chain rooted at `$this` that only calls methods (so
`$this->app->get('config')` is not a request); Pest's helpers where the
file imports Pest\Laravel; and a project's own verb-named helpers
(koel's getAs → jsonAs → `$this->json($method, $uri)`), recognised by
their body, never by name alone.
Paths are read as built — Spring `{var}` templates, PHP / Kotlin
interpolation and concatenation become holes — and matched with the
cross-tier pass's own route matcher (a hole fills a parameter, never a
literal). One `calls` edge per (test, route), `synthesizedBy:
'test-request'`. These edges are excluded from countIncomingEdges, so a
well-tested endpoint never becomes a hub the Steps walk refuses to enter.
Routes reached by a test: petclinic 16/17, realworld 18/19, BookStack
194/340, koel 105/134 (from 0 each). A random sample of edges checked
against the test source was all correct. The pass costs 86 ms on
BookStack and 68 ms on koel.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
ffced05e3c |
fix(laravel): name routes by the path a request takes — leading slash, prefix groups, the /api mount (#2099)
Routes were named by the bare literal in the routes file, so BookStack's
api.php showed `GET pages` for `/api/pages` and koel's `Route::prefix('api')`
group showed `GET ping` for `/api/ping`. Beyond the label, a front-end
`fetch('/api/pages')` could never pair with a route recorded as `pages`.
- extract(): `Route::prefix()` chains and `Route::group(['prefix' => …])`
scope their routes, nested ones in order; a computed prefix is kept as a
`{prefix}` segment. Paths are joined the way Laravel joins them (leading
slash, no doubled or trailing slashes). qualifiedName now holds that
in-file path.
- postExtract(): finds where each routes file is mounted — a group whose
argument is the file, a require / include / loadRoutesFrom inside a
group, Laravel 11's `withRouting(api:, web:, apiPrefix:)` — composes the
prefixes down the include tree, and names every Laravel route from its
qualifiedName plus its file's prefix (idempotent; a removed mount is
undone). routes/api.php defaults to `/api` unless something mounts it or
a `withRouting(using:)` loader might. A file mounted at two prefixes stays
at its in-file path. Only files in routes/ or naming `routes/` /
`withRouting(` are read.
On BookStack all 80 api.php routes become `/api/…` (from its
RouteServiceProvider group); on koel all 118 become `/api/…`; no route is
left without a leading slash.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
15a5d1d0cd |
fix(c): keep an #undef under an undecidable #if unknown; a macro call never binds to a type (#2069, #2070) (#2081)
Two follow-ups to the unreleased macro-visibility work (#1838/#1839): - #2069: `#undef FLAG` under an `#if` nothing in the source decides made a never-seen FLAG certainly undefined, so `#ifdef FLAG` was read as false and the functions it guards lost their callers (CPython's `Py_GIL_DISABLED`). A name no directive has touched is now unknown; only an #undef on a certain line clears it. - #2070: dropping the macro constant from a call's candidates let the next same-named node win, so expat's `PREFIX(scanRef)(...)` instantiated an unrelated `struct PREFIX`. When the name is a function-like macro in the project, only a function or method is an eligible callee. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
fef37765c1 |
perf(resolution): remove the indexing regressions since v1.6.0 (vscode stall, per-ref rescans) — graph byte-identical (#2072)
* perf(resolution): read JS/TS parameter lists without exponential backtracking isLocallyBoundJsName (#1759, |
||
|
|
49607201d6 |
fix(resolution): bind implements to the interface of a value+interface pair (#2055)
The inheritance target-kind gate (#1796) rejected any extends/implements resolution whose target cannot be a supertype. VS Code declares every service twice under one name, `export const IFoo = createDecorator<IFoo>(…)` beside `export interface IFoo`, and the import resolver takes the first export of that name, which is the value. The gate then dropped the edge instead of taking the interface the same file declares. Found by the pre-release v1.6.0 vs main comparison: 864 `implements` edges lost on vscode (AccessibilityService -> IAccessibilityService and ~860 like it), plus the ~5.8k interface-dispatch call edges synthesized from them. The gate now moves a TypeScript constant/variable target to the one same-named supertype in its file; with no such sibling it still drops the edge. vscode: implements 4,139 -> 5,003 (v1.6.0: 5,436, 254 of them on non-type targets; now 0), node count unchanged. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9be3f1a8a2 |
fix(resolution): keep unknown Python import members unresolved (#2040)
Bisect identifies
|
||
|
|
fa25883ab0 |
fix(cpp): resolve local constructors and macro visibility (#1839) (#2035)
* fix(cpp): macro invocations are not calls; local objects call their constructor A function-like macro invocation (`TRACE_POINT(1)`) parses as a call, and the resolver bound it to a same-named free function in another translation unit — a caller and a callee that never existed (#1838). And local object initialization (`Widget w;` / `Widget w(1);` / `Widget w{1};`) reached the class node instead of a constructor, or nothing at all for the default form (#1839). Extraction (wasm walker and the Rust kernel, in parity): - every `preproc_function_def` becomes a `constant` node whose signature is the directive, so the index knows which names are macros; - a C++ declaration emits one `calls` ref per constructed object, shaped `ns::T::T/<arity>`; pointer, reference and function declarators, `extern`, and array element braces construct nothing; the #1035 `instantiates` ref is unchanged; - constructors carry their parameter list as the signature. Resolution: - `cpp-macro-visibility.ts` walks the translation unit (root file plus in-repo includes, each file once) over per-file summaries of function-like defines, undefs and includes, evaluating what each file can decide itself (literals, its own defines, the include-guard idiom). A call whose name is definitely a macro at that point, or a name that only macros bear, resolves to nothing. A macro that exists in one build configuration only (CMSIS's `__DSB()` under the ARM compilers, a target's HAL compat header) keeps the call to the function the other configuration compiles; a wrapper macro that calls its own name is how that function gets called and hides nothing. - `cpp-constructor.ts` resolves the constructor refs: the type in the lexical namespaces of the call site, then the single overload whose parameter count admits the arguments; two admitting overloads, an initializer_list overload, or an aggregate yield no edge. - a `#define` constant is never a `calls` target and does not count toward the same-name ceiling. Measured on a betaflight tree (4,017 files, wasm arm): 153 `calls` edges lost — all fabricated (`MIN`/`MAX` bound to a CLI enum member, macros misparsed as functions, a cross-vendor `SWAPBYTE`) — and 34 constructor edges gained; indexing 34s vs 30s. Fixes #1838 Fixes #1839 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(cpp): resolve local constructors and macro visibility (#1839) Local construction lacked constructor calls, while macro invocations could bind unrelated functions. Resolve constructors using lexical types and overload signatures, preserving separately declared defaults and array element construction. Evaluate macro directives in translation-unit order, respecting reinclusion, guards and pragma once. Mirror extraction in Rust and WASM, preserve type dependencies, and distinguish them in CLI output. Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
5eaa6fedd5 |
fix(extraction): preserve receivers for method values (#1820) (#2034)
* fix(extraction): record callers for methods passed as values (#1820) pool.submit(obj.method), thread_pool_exec(self.store.fetch), and Go Submit(c.store.Fetch) produced no callers/impact edge because only obj.method(...) call expressions were captured. Member values are now emitted as *.method function-refs and resolve unique-or-drop (same-file first). Two methods of the same name still produce no edge. Go go c.store.Fetch(ids) remains a calls edge (it is a call expression). EXTRACTION_VERSION 27: MCP daemon catch-up rebuilds a stale index on start so the watcher does not keep serving the hole after upgrade. * fix(resolution): do not let test mocks veto #1820 method-value callers A unique production method plus a unit-test double of the same name was treated as ambiguous, so retrieve_vision_vectors still looked callback-invisible on a real repo. Prefer the non-test file when exactly one production target remains. * fix(resolution): prefer a real override over a NotImplementedError base #1820 unique-or-drop treated DocStoreConnection.delete_payload_fields (the ABC stub) and QdrantConnection.delete_payload_fields as ambiguous, so gc_stale_member_stamps stayed invisible. Skip short not-implemented stubs when exactly one real implementation remains. * fix(extraction): preserve receivers for method values (#1820) Python attributes and Go selectors used as callback values were dropped or reduced to bare method names. Preserve receiver paths in native and WASM extraction, resolve through type/import scope, and reject ambiguous or noncallable targets. Cover callback, assignment, collection, and query behavior, with extraction version 27 signaling the required reindex. Co-authored-by: Josh66 <info@aska-systems.de> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Josh66 <info@aska-systems.de> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2a2f71ec41 |
fix(sync): refresh synthesized edges after incremental changes (#1988) (#2033)
* fix(sync): refresh synthesized edges after incremental changes (#1988) Scoped sync skipped synthesis and retained obsolete dispatch edges. Refresh all synthesis passes after base resolution when dependencies or source patterns change. Stage and atomically replace owned edges, preserve ordinary edges, and retry interrupted refreshes. Stabilize traversal order and migrate existing indexes for rebuild convergence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: preserve structural Go containment during synthesis refresh --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9649ce1bd6 |
fix(resolution): reject unrelated cross-language matches (#1986) (#2032)
* fix(resolution): stop expression-receiver calls from binding to unrelated methods A method called on an expression receiver reached the resolver as the bare method name, and exact-match bound it to any project method of that name: `(await list()).map(...)` in TypeScript onto a UI adapter class's `map` (0.9), and `v.iter().map(...)` in Rust onto the same TypeScript method across languages (0.5). TS/JS extraction (mirrored in the kernel): look through receiver wrappers that keep the same object — parentheses, `!`, `as`/`satisfies`, `<T>x`, `await` (including the grammar's `(a && b)!` parse of `a && b!`) — so `x!.m()` is `x.m` and `(await f()).m()` is `f().m`; any other receiver with no static type (`(a ?? b).m()`, `f().list.m()`, `arr[0].m()`) emits nothing instead of the bare name. `this`/`super` (and chains rooted at them), `window.*` chains and `new C().m()` keep their existing bare form. Resolution: a bare-named call may cross a language family only through the C ABI (a C/C++ symbol, or a free function a C/Objective-C/Swift caller reaches, e.g. a cgo `//export`). A class member of another family, or a function/class/variable outside that pair, is rejected as the candidate a strategy commits to (exact-match single/best and fuzzy), never filtered out of the pool, so no lone survivor is manufactured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(resolution): reject unrelated cross-language matches (#1986) Name-based calls, constructors, and inheritance could select unrelated foreign-language symbols. Apply a shared interoperability policy to selected results without manufacturing replacement matches, including JSX/Vue synthesis. Preserve framework exports and ABI-backed calls, building on PR #1944's TS/Rust expression-receiver fixes. Validate with both repro scripts, 529 targeted tests, kernel/wasm parity, and clean TypeScript checks. Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
17d09bf506 |
fix(resolution): resolve object-literal function references safely (#1932) (#2031)
* fix(resolution): resolve same-file calls through object-literal aliases (#1932) `api.getUser()` where `api` is `const api = { getUser }` (or `{ getUser: fetchUser }`) and the function is declared outside the literal resolved to nothing in the literal's own file: the #1573 containment lookup only finds members defined inside the literal, and the binding-following alias path existed only on the import side, so same-file callers were missing from callers / impact / blast radius. - Extract the member-binding read into `objectLiteralMemberBinding`, which splits the literal into its own top-level members on comment- and string-blanked source, so a nested object, a member body, a comment or a string never donates a binding, and `{ fn: 1 }` names nothing. The import path now uses it too. - In the same-file object-literal receiver strategy, fall back to that binding: a symbol of the file, else one of its imports, skipped when a parameter or nested declaration shadows the name where the literal is written. Resolution-only change; extraction output (and kernel parity) unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(resolution): a literal wrapped in Object.freeze still names its members (#1932) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(resolution): resolve object-literal function references safely (#1932) Same-file object-member calls only found functions defined inside the literal. Share lexical binding resolution across same-file, import, and alias-forwarding paths. Respect exact source columns, top-level membership, shadowing, and property overwrites while preserving wrappers and inline members. Add JavaScript and TypeScript regressions and verify kernel/WASM parity. Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e27d6da6b2 |
fix(scala): resolve companion inheritance to type definitions (#1824) (#2029)
* fix(scala): resolve extends/with to the trait, not its companion object The Scala trait + companion object idiom puts two same-named symbols in one file. Both were indexed as kind 'class', so an 'extends X' reference tied between the trait and its companion and the winner was arbitrary. When the companion won, every subtype was detached from the trait's inheritance chain, and impact analysis on a widely-used trait stopped at depth 1. - classify Scala object_definition as 'module' (a singleton value, not a type - 'extends' can never target it) - add 'module' to the classifyClassNode contract and handle it in the extractor dispatch - in name matching, bias extends/implements references toward actual type definitions and penalize 'module' candidates, so the trait wins the tie deterministically Regression test: trait + companion in one file, subclass in another - the extends edge must land on the trait and impact must reach the subtype through it. * fix(scala): resolve companion inheritance to type definitions (#1824) Scala objects and their companion types could tie during inheritance resolution, disconnecting subtype impact paths. Classify objects as modules in both extraction engines and reject Scala singleton inheritance targets across resolution strategies. Preserve object method lookup and inherited receiver methods, with declaration-order, impact, and native/WASM parity regressions. Co-authored-by: Hubert Tarnacki <hubert.tarnacki@comarch.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Hubert Tarnacki <hubert.tarnacki@comarch.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b65e05b98f |
fix(resolution): require receiver evidence for built-in method calls (#1987) (#2028)
* fix(resolution): resolve this.#field.method() on the field's type (#1987) A call through an ES private field fell outside the `this.<field>` shape (#1496): the extractor accepted only a property_identifier, so `this.#items.add(x)` was emitted as the bare `add` and exact-matched whichever project method shared the name, often the calling method itself. The extractor and its kernel mirror now keep `this.#items.add`, and the resolver matches that shape and reads the field's type off the class declaration. The field regexes open with a lookbehind instead of `\b`, which never matches before `#`. A builtin or external field type (`new Set()`, a `Map`, an array) now yields no edge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(resolution): require receiver evidence for built-in method calls (#1987) Unknown JavaScript receivers could match unrelated methods by name or class-name similarity. Require validated receiver evidence for built-in method names while retaining typed, imported, object-literal and class resolution. Preserve private-field receivers in TypeScript and Rust extraction and keep public/private field declarations distinct. Build on PR #1990 with regression coverage across all four JS/TS variants and kernel/WASM parity. Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ce97ebc8e5 |
fix(resolution): preserve callback registration scope (#1355) (#2015)
Field-channel synthesis discarded receiver and binding identity when selecting handlers globally by name. Reuse resolved function-reference edges at the registration site, retaining class, inheritance, and import resolution without global fallback. Add regression coverage for target identity, unknown handlers, and explore registration metadata. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9a96b52991 |
fix(react-router): bound route scans to their declarations (#1348) (#2011)
* fix(react-router): bound route scans to their declarations (#1348) Fixed scan windows borrowed paths and components from neighboring or nested routes. Scan JSX opening tags and data-router objects structurally, pairing only their own attributes and properties. Add persisted-graph regressions across JavaScript and TypeScript route declarations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(react-router): bound route scans to their declarations (#1348) Fixed scan windows borrowed paths and components from neighboring or nested routes. Scan JSX opening tags and data-router objects structurally, pairing only their own attributes and properties. Add persisted-graph regressions across JavaScript and TypeScript route declarations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
276df3a975 |
fix(resolution): reject oversized targets before decoding (#1553) (#2006)
Workspace imports could follow excluded dependency archives and decode them without a size guard. Reuse extraction's 1 MiB limit, reject non-files before reading, and cache null results. Add sparse-HAR and real ohpm indexing coverage while preserving legitimate import edges. Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
53fe53deb5 |
fix(resolution): a receiver-less Go call never binds to a method (#1857) (#1999)
A Go method is only reachable through a value or a method expression, so a
bare call such as `relogin(ctx)` on a func parameter was being bound to a
same-named method in its own package, or (when capitalised) to an exported
method of a package the file does not import. Extend the #1714 receiver-less
check to Go, reading the call site back from source so the
`pkg.Factory().Method()` chain that reaches the resolver as a bare `Method`
ref keeps its edge.
Claude-Session: https://claude.ai/code/session_01MuSia5rNjiWCqzqW9iTJGD
(cherry picked from commit
|
||
|
|
c3faef260b |
fix(spring): gate DI convention patterns to Java/Kotlin non-inheritance refs (#1827)
In a polyglot repo the Spring resolver is detected globally (a sibling
Java module with spring-boot in its pom is enough), and its resolve()
then ran on references from every language. Pattern 4's bare-name
fallback ('any [A-Z][a-zA-Z]+ name, prefer /model/ dirs, else ANY
same-named class') could therefore hijack a Scala 'extends ExtCustomer'
to an unrelated same-named test class, corrupting the inheritance graph
that impact analysis walks.
Two gates in front of the DI/convention patterns (1-5):
- skip refs whose language is not java/kotlin - Spring DI conventions
say nothing about other languages
- skip extends/implements refs entirely - inheriting from a class is
never a DI injection point; those must resolve via imports/name
matching
The Spring config-key resolution above the gates is untouched (it
already gates itself). Regression tests cover the hijack case and the
still-working Java DI path.
Co-authored-by: Hubert Tarnacki <hubert.tarnacki@comarch.com>
|
||
|
|
d0996a28d8 |
fix(resolution): resolve awaited TypeScript receivers safely (#1885)
Fixes #1840. Reuses and extends #1855 with lexical/import ownership and cache invalidation controls. Co-authored-by: Max Hsu <maxmilian@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8f8081968e |
fix(rust): keep self method calls on the correct owner (#1882)
Fixes #1861. Reuses L4XB's receiver retention and owner resolution from #1866, with duplicate-owner and edit/sync guards. Co-authored-by: L4XB <lukas.buck@e-mail.de> |
||
|
|
4297b8e2ed |
fix: restore call graph relationships and Steps effects (#1862)
* docs: record regression audit and validation * test: cover audited call and extraction regressions * test: cover audited calls and selector guards * test: cover audited calls and selector guards * test: cover audited calls and selector guards * test: cover audited calls and selector guards * test: cover audited calls and selector guards * fix: retain qualified call sites and restore language-specific getters * fix: resolve typed calls and bound store actions * fix: retain qualified call sites in the native kernel * docs: explain restored call and Steps coverage * test: isolate watchdog and cover imported store aliases * refactor: expose import lookup through resolver context * refactor: remove import resolver cycle * test: canonicalize Claude config temp paths * docs: record review fixes and large TypeScript timings * test: cover store cache edits and document verification * perf: cache per-file store eligibility * fix: observe large benchmarks without a wall timeout * docs: explain completed indexing benchmarks docs: explain completed indexing benchmarks |
||
|
|
3ed73bc127 |
feat(ui): the Steps tab lays a screen out in clusters and says a far link in words; a <Card/> is the Card its file imports (#1817)
* fix(ui): a screen's steps read as clusters, and a link too far to follow is said in words The mobile app's /capture came back as a web: 100 boxes in a 1,227x5,588 ribbon, 113 lines drawn at rest crossing each other 652 times, each one running over about five other boxes' names. Measured, not guessed — three separate causes, very unequal. The region grouping had nothing to divide there (98 of 100 boxes take their region from one memoized component), so the picture fell back to a single 719px column. But the region dimension was not the lever. The lever was that `packRegions` packed every step of one distance onto shared rows and wrapped those rows at a fixed 720px, so a box and the thing it fires landed seven lines apart: 70 of the 113 lines joined boxes ONE step apart. That is what the crossings were made of. So a region is now packed as CLUSTERS — a step, then the steps it sets in motion on the line under it, stepped in — while the starting points that fire nothing still share a line, because a screen's handlers are siblings and giving each its own line turned a flat region into a column. A region's line width is earned rather than fixed (sqrt(total * pitch), clamped 720..2600), so a big screen comes out about as wide as it is tall. Clustering makes most links local but not all: a step reached from two places is drawn under whichever reached it first, so the other way in still crosses the picture. Those are now said in WORDS at both ends — `-> resumeInference` under the box that leads there, `<- CaptureView` under the box it arrives at, capped at three with `+N more` — rather than drawn. This is not a hiding: the link is stated, which says more than a line vanishing off the edge of the screen does, and selecting the box draws every one of its real lines exactly as before. It is the one at-rest cut that does not produce the "box that leads somewhere and draws nothing" every earlier cut produced. Also fixed while here, and predicted by the earlier region work: the in-region row relaxation had no cycle guard, so a region holding one loop pushed 65 of its boxes to rows 294-301 while the rest sat at 0-2. `forwardLinks` sets cycle-closing links aside first, as the order reading's `withoutBackEdges` already did. /capture: 1,227x5,588 -> 2,279x4,356, at-rest crossings 652 -> 1, lines-over-boxes 553 -> 26, with half the links still drawn as real lines and every quiet box still one the screen itself fires directly. The order reading is untouched (it keeps every line). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTUFNN5bH3aPw2gi2LqbYD * fix(ui): a stub names its box without the mark the box wears for its kind `← ⇠ onCaptureProgress +2` reads as two arrows arguing: the stub already leads with a direction, and the box's own kind mark was competing with it. Verified in the live canvas. The box keeps its mark, where nothing competes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTUFNN5bH3aPw2gi2LqbYD * fix(ui): the screen's line into each of its parts stops sweeping the picture Audited all 51 screens of the mobile app on this branch. 96 lines — the screen's own stand-in line into each region — were 17% of everything drawn and caused 79% of every crossing left. A screen with ten regions tiles them into bands, so the line into a region two bands down travelled the height of the whole picture. Two causes, both fixed. The entry the line lands on was the walk's first member of the region; clustering moves a step that fires something BELOW the ones that fire nothing, so that box could sit lines down inside the region and the line had to reach past everything above it. It now lands on the box nearest the region's top-left that the screen actually leads to. And the stand-in line is no longer exempt from the stub rule — when the region is still too far to follow, the link is said in words like any other. The rest of the anchor's fan stays quiet as before: it is already stood in for. Across the 51 screens: crossings 47 -> 10, no screen above 10 (worst was 19, now 2); lines-over-boxes 236 -> 182; boxes with neither a line nor a word 150 -> 135. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTUFNN5bH3aPw2gi2LqbYD * fix(ui): a screen's parts fill the canvas instead of squaring off into rows Audited the app's 28 regioned screens: the median canvas was 55% region and 45% nothing, and /home was 44% — 4,860px tall to hold about 2,160px of picture. The cause is that regions were tiled a row at a time with each row as tall as its tallest member, so one short region beside a tall one left the rest of that row blank, and a reader scrolls through the blank. Each region now goes as high as it can and then as far left as it can, over a skyline of what is already placed. Reading order is untouched: regions are still walked in the screen's own source order, so an earlier one is never pushed below a later one — a short one just tucks under another short one rather than waiting for the tall one beside it. Layering now comes from the finished geometry rather than a band counter, since once regions drop independently what a reader sees as one row IS one row. /home 4,860px -> 3,584px, aspect 0.59 -> 0.94. Tallest screen in the app 4,860 -> 4,356. Crossings 10 -> 13 across all 51 screens, still none above 10. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTUFNN5bH3aPw2gi2LqbYD * fix(ui): the width a picture wraps at is tried, not estimated A region's line width came from sqrt(total * pitch) — the width at which total/width lines come out square. That estimate is wrong for how these pictures are drawn: a cluster spends lines on its own structure (a hub gets a line to itself, and what it fires starts another), so it undercounts a region's lines badly and wrapped /capture's 98 boxes into a 4,356px column. Laying a picture out is cheap and exact, so the widths are tried instead: layoutAt runs the whole pack at each of eight widths and the best finished canvas wins (~2ms for the model, all eight included). It has to be scored on the CANVAS, not per region — squaring each region off individually leaves fewer of them side by side, which took /home from 3,584px to 5,624px while every region looked better on its own. Also measured and rejected while here: dropping a region's CLUSTERS side by side the way the regions drop onto the canvas. Total height 42,084 -> 39,756px (-6%), but lines-over-boxes 120 -> 134 and crossings 5 -> 8, because two clusters side by side put each one's lines through the other. Height is cheap to scroll; a crossed line is what made this picture unreadable. The reasoning is recorded in the code so it is not re-tried blindly. Regions differ — they sit far enough apart that few lines run between them. Across the app's 51 screens: tallest picture 4,356 -> 3,796px, total height 45,744 -> 42,084px, lines-over-boxes 184 -> 120, crossings 13 -> 5, and no screen is a tall ribbon any more. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTUFNN5bH3aPw2gi2LqbYD * stuff --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4453310eef |
fix(spring): index multi-path mappings and same-file constant paths (#1461) (#1811)
Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com> |
||
|
|
d3f9ef9bef |
fix(resolution): follow alias bindings to the function they name (#1482) (#1808)
Adapt upstream PR #1485 by @valkyriweb (
|
||
|
|
374b3b4209 |
fix(resolution): constrain inheritance/import reference target kinds (#1536, #1537) (#1796)
* fix(resolution): gate extends/implements to real supertypes
An inheritance reference bound to whatever local symbol shared its name.
The name-matcher scores node kind as a bonus, never a filter, and awards
no bonus at all for inheritance refs, so `use std::error::Error;` +
`impl Error for MapperError {}` resolved to the local `MapperError::Error`
VARIANT — an implementation relationship absent from the source.
Two changes, both needed. Filtering by kind alone was measured and it
only RELOCATES the false edge: with enum members excluded, the same 7
refs moved onto an unrelated local `type Error` alias, which is a legal
supertype kind and therefore harder for a consumer to reject.
1. Eligibility before ranking. `matchByExactName` restricts its candidate
pool to kinds that can BE a supertype, so a legitimate trait outranks
a same-named variant instead of merely losing its edge. `resolveOne`
is wrapped by a gate that applies the same set to every other strategy
at one seam — filtering inside the name-matcher would have missed the
framework, import, chain and CFML paths.
2. Locality. A name imported from outside the repository has no in-repo
referent at all, so no candidate is correct. Only oracles that cannot
be wrong are consulted: Rust `use` paths rooted at a stdlib crate, and
`isExternalImport` for ES modules. Generalizing the Rust side to "the
module path doesn't resolve to a file" was tried and reverted — a
crate re-exporting a sibling's modules (`pub use pupil_core::ports;`)
has no directory to walk, and that version deleted 13 real trait
implementations.
Measured on a Rust/Tauri project (2,682 nodes): the 11 false inheritance
edges are gone, all 59 real trait relationships are preserved, and node
count is unchanged. On this repository as a control, the only edge
removed is a class recorded as extending a function. Synthesized-edge
counts are identical in both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(resolution): an import never resolves to a member of a type
`import * as path from 'node:path'` is unresolvable — the module is
external — so the name-matcher fell back to finding any node called
`path`, and a common word like path/url/join/get matches a class property
or interface method somewhere in almost any repo. Nothing in any
supported language lets an import bind to a member that only exists
inside a type; you import the type.
Same shape as the inheritance gate that precedes it: eligibility applied
to the candidate pool before ranking, plus the resolveOne gate as the
backstop for every other strategy.
On this repository as a control: 19 imports pointing at methods and 4 at
properties are gone (all of them coincidences — `Walker::join`,
`Telemetry::events`), 3 refs now find the module constant they actually
name, node count unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(resolution): classify SFC script imports as ES module specifiers
`isExternalImport` had a TS/JS branch listing typescript/tsx/javascript/jsx/
arkts, so for Svelte, Vue and Astro it fell through every branch and returned
false — "not external" — for `import { Foo } from 'some-npm-pkg'`.
An SFC imports inside its `<script>` block (Astro: the `---` frontmatter) with
ordinary ES module syntax; `extractImportMappings` already routes all three
through the same `extractJSImports`. So the classifier disagreed with the
extractor about what those imports are.
Effect on the preceding commit: its locality check asks `isExternalImport`, so
it silently did nothing for SFCs. A class in a `.svelte`/`.vue`/`.astro` file
implementing a type imported from an npm package still bound to whatever local
class shared that name — verified against this branch before the fix, all three
languages.
The language set is now one constant used by both the classifier and the
locality check, so they cannot drift apart again. Relative and aliased
specifiers are unaffected: the branch returns "not external" for `./…`,
workspace members, tsconfig alias prefixes, `@/`, `~/` and `src/` exactly as it
does for `.ts`.
No edge changes on this repository as a control.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: ctype_lab <cksgud1226@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
040ba388da |
fix(php): resolve static calls through import aliases (#1545) (#1795)
Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com> |
||
|
|
cece0720e3 |
fix(resolution): resolve this.<field>.<method>() on the fields declared type (#1496) (#1792)
Land the six-file fix from upstream PR #1691 by danusha2345
(pr-1691 at
|
||
|
|
de5adba7ea |
fix(resolution): leave built-in Map calls unresolved (#1566) (#1790)
Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com> |
||
|
|
72c1ff13cc |
fix(resolution): restore orphan-sweep parity after interruption (#1577) (#1789)
Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com> |
||
|
|
7be699cd91 |
fix(resolution): resolve Python aliased module imports (#1626) (#1785)
* fix(resolution): resolve Python module members through an aliased from-import (#1626)
resolvePythonModuleMember rebuilt the submodule's dotted path by joining the
import source with the LOCAL name. Under 'from pkg import mod as alias' that
produces 'pkg.alias' — a module that does not exist — so the file lookup found
nothing and the call fell through to unresolved_refs with status='failed'.
codegraph_callers then reported the target as having fewer callers than it
does, which is the same wrong 'is this dead code?' answer #578 produced for
the unaliased form.
Join with the exported name instead. For an unaliased import the two names are
identical, so nothing changes there; '*' (the namespace form) keeps using the
local name, which is what it already bound to.
Scope note: the issue also reports 'import top as alias' failing. That form is
a namespace import and binds at source, so it resolves on current main — a
probe against the reverted resolver confirms it already produces its call edge.
The regression test pins both halves so the working one cannot silently break.
Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit
|
||
|
|
195888d71f |
fix(resolution): reject Python module collection method guesses (#1652) (#1776)
Use same-file module variable initializers to filter builtin collection calls before class-name heuristics, and require a Python type node for the class escape. Preserve imported project module calls. Add callers/callees regressions for dict, list, set, tuple, and frozenset, with a real instance control and same-name bindings across files. Validation on Linux with Node 22.19.0: the new suite had 14 failures and one passing control on main at 8733c288; all 216 tests in the new suite, call-receiver-no-fabrication, and resolution now pass. TypeScript and copy-assets pass. Re-indexed /tmp/cg-1652-repro: callers get is empty and read_setting no longer calls a cache.py method. Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com> |
||
|
|
3adf06772b |
fix(resolution): read a quoted Python annotation as a receiver type (#1684) (#1770)
`def f(o: "Alpha")` is the same annotation as `def f(o: Alpha)` — a forward reference, and what every file under `from __future__ import annotations` writes — but the receiver-type pattern stopped at the quote, read no type, and `o.render()` produced no edge. Admit the quoted form. Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com> |
||
|
|
28033f62f8 |
fix(resolution): an import naming the emitted .js extension resolves to its .ts source (#1767)
Fixes #1705. Lands #1706 (thanks @bompus), rebased onto main. |
||
|
|
7c758aaf0a |
fix(resolution): C and C++ nesting is never a scope
isLexicallyReachable trusted the graph's nesting for every language. C and
C++ have no nested named functions, so a function shown inside another is an
extraction artifact: tree-sitter-c cannot parse a macro call whose arguments
are designated initializers — betaflight's
RESET_CONFIG(pidProfile_t, pidProfile, .pid = { … }, …);
— and its error recovery runs the enclosing function_definition (source lines
168–309) to line 1667, nesting the 45 functions after it. That tree has 310
such functions in 73 files. Before this commit exact-match already rejected
them as unreachable and the fuzzy fallback picked them up at 0.5; with the
survivor-side check alone, fuzzy rejected them too and 117 real calls into
pid.c disappeared (base →
|
||
|
|
2521b49a9a |
fix(resolution): fuzzy reachability rejects a unique guess, never manufactures one
A function nested inside another function is only callable from inside its
container (#1230). matchByExactName already declined such candidates; the
fuzzy fallback did not, so a builtin method call (`res.text()`) whose only
same-named project symbol was some file's closure resolved onto that
closure at 0.5 (#1708).
and on vitejs/vite@8492422 that traded 12 correct removals for 59 wrong
additions: the repo has a dozen `resolve` definitions, most nested, so the
filter left exactly one reachable `resolve` method and the strategy
committed every `import { resolve } from 'node:path'` call in the
playground configs to it. Filtering a crowd down to one survivor is not
evidence the survivor was ever the target.
So the check sits on the ONE candidate matchFuzzy would commit to: a
unique candidate the call cannot reach is declined; a crowd stays a crowd.
Same tree, measured against this branch's own base
|
||
|
|
cd4e65b59c |
fix(resolution): a receiver-less JS/TS call never binds to a method (#1759)
`serialize(this.raw)` inside `Record.serialize`, with a module-scope `function serialize` in the same file, resolved onto the method itself: both were exact-name candidates, both same-file, and findBestMatch's line-proximity term always prefers the enclosing method (#1714). In JS/TS a call written without a receiver cannot reach a method at all — methods need `this.`, an object, or a bound reference. The extractor emits `this.m()` and `super.m()` under the bare method name, so the receiver is read back from the call site's own line: when the text there begins with the name itself and nothing but whitespace, an operator or an opener precedes it, the call is bare, and `method` nodes leave the candidate set before ranking. matchFuzzy declines a lone `method` survivor for the same ref. A name the file binds itself also has no cross-file candidate for a bare call. `this.serialize()` and `other.serialize()` are unchanged. Rebased #1735 onto current main (resolved conflicts with sealed-module / cross-file visibility guards from #1719/#1730/#1731). Fixes #1714 Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com> Co-authored-by: danusha2345 <ewidusoc498@gmail.com> |
||
|
|
a6f52d737a |
fix(resolution): keep the existence probe inside the project root (#1631)
Fixes #1631. Rebased contributor PR #1632 onto main (post-#1749). Lexical containment for `fileExists` filesystem fallback via `lexicalPathWithinRoot`; #935 in-root symlink behaviour preserved. |
||
|
|
edcd36e5f0 |
fix(resolution): resolve module-qualified calls colliding with builtin methods (#1749)
isBuiltInOrExternal treated ledger.append as list.append unless the receiver matched a known class, so real module exports never reached resolveViaImport. Allow project-module receivers (verified via resolveImportPath) through while keeping stdlib/PyPI silent. Completes #1681 after #1748 fixed the FP half. Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com> |
||
|
|
bb1d3093eb |
fix(extraction): never fabricate an edge from a call-result receiver (#1748)
A member call whose receiver is itself a call — `d.setdefault(k, []).append(v)`, `make().run()` — used to drop the receiver at extraction time, degrade to the bare method name, and exact-match any top-level project symbol of that name (Python and JavaScript/TypeScript). Keep the inner callee encoded as `<inner>().<method>` in the TS extractor and native kernel; the name-matcher refuses to guess for that shape (store-accessor exception only). Based on #1692, rebased onto main after #1746. Fixes #1683. Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com> |
||
|
|
bffd50e4f1 |
fix(resolution): a binding in a module that exports nothing is not a cross-file candidate (#1719) (#1746)
* fix(resolution): a binding in a module that exports nothing is not a cross-file candidate
On vitejs/vite, 157 cross-file `imports` refs — every `import { defineConfig }
from 'vite'` in the playground and the create-vite templates — resolved onto
`playground/ssr-html/test-stacktrace.js::vite`, which is `const vite = await
createServer(...)` at module scope in a file with zero exports.
Neither existing guard can see it. `isLexicallyReachable` returns early for any
candidate that is not a `function`, and the bare-import guard correctly declines
because `vite` IS a workspace member, so the specifier really is project-local.
What is wrong is only which node the name lands on.
A JS/TS file that contains an `import` statement and no export of any form
offers nothing to any other file, so none of its bindings is a candidate for a
cross-file name match. Applied in both name-based strategies: declining in
matchByExactName alone just hands the same target to matchFuzzy, which resolves
a unique candidate on its own.
Narrow on three axes, each a class this would otherwise get wrong in the
opposite direction: a classic script is exempt (a top-level binding really is a
reachable global), CommonJS is exempt (`module.exports` and `exports.x` count as
exports), and every non-JS/TS language is exempt. The export test reads source
rather than the node's `isExported` flag, because that flag is set only from an
`export_statement` ancestor and so reads false for `const x = ...; export { x }`.
* fix(resolution): count bracket CommonJS exports and `declare global` as exports
A file writing `exports["x"] = …` exports x, and a file with a `declare
global` block contributes every name in it to every other file whether or not
it exports anything of its own — the extractor emits nodes for the ambient
`var` and `interface` members, so sealing such a file would hide names that
really are reachable everywhere. Neither shape occurs on the vite corpus, so
this changes no measured count; both are now covered by the test.
* test(resolution): bind the #1719 fixture without a bare import
The consumer bound every name from 'some-external-pkg'. A bare specifier
names a package that is not in the graph, so no project node is the right
target for such a reference and #1715 declines it -- which made four of the
five positive assertions depend on a resolution that should not happen, and
they failed the moment this branch was stacked on #1715. Free references
reach the same exact-match path without asserting that.
`strayVar` was not testable at all: a bare identifier read emits no edge, so
that assertion only ever passed through the bare-import binding. The
`declare global` coverage moves to an interface reached through a type
annotation, paired with an identical file whose interface is not in a
`declare global` -- so the assertion turns on that clause rather than
passing whichever way the guard goes.
* docs(changelog): record the sealed-module guard under Unreleased
* fix(resolution): the sealed test rejects fuzzy's survivor, never filters its set
matchFuzzy declines an ambiguous name outright, so filtering sealed
candidates out of its set can leave a lone survivor and manufacture a 0.5
edge from an ambiguity that would have been declined. Testing the single
survivor instead closes that path; matchByExactName keeps the filter,
because it ranks a crowd rather than declining one.
No instance on vitejs/vite either way (row-identical, LOST 0 / GAINED 0
per #1720 review). It also declines one shape the filter form resolved: a
sealed same-language survivor no longer yields to a cross-language
candidate at 0.3.
* fix(resolution): reject invalid fallback targets without retargeting
---------
Co-authored-by: Aaron Queen <bompus@users.noreply.github.com>
Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
|
||
|
|
2c251e2c61 |
fix(resolution): a definition its language makes file-local is not a cross-file target (#1731) (#1745)
* fix(resolution): a definition its language makes file-local is not a cross-file target
Name matching accepted any same-named definition as the target of a call
from another file, however the language scopes it. isVisibleAcrossFiles
now declines, for a candidate in another file:
C / C++ a function whose definition line carries `static` (read from
source — the extractor records no storage class, and the kernel
arm would need the same field)
Kotlin, Java, C#, Swift, Scala, Dart, PHP
visibility === 'private'
Go a lowercase identifier from another directory (by the name's
case: the extractor's isExported is unset for every Go method)
Rust a non-`pub` item unless the reference is in the item's module
subtree (a child sees its ancestors' private items via super::);
a method in an `impl Trait for Type` block has the trait's
visibility and is exempt
The test runs in ReferenceResolver on the target the whole name-matching
pipeline settled on, so a rejection ends the reference unresolved. Declining
inside matchByExactName instead let the ref fall through to matchFuzzy,
which committed to a same-language namesake the ranking had passed over —
eight edges on one tree, all onto a local `const fail = …` arrow the graph
does not hold. matchFuzzy checks its own survivor too; nothing runs after it.
Five corpora, all against
|
||
|
|
882ea143e8 |
feat(steps): lay out screen pictures by region and render region captions
Adds region-based layout support for screens: steps now carry region information, and the server packs regions into dedicated bands with per-region captions. UI changes introduce RegionCaption and region-aware step rendering; StepsModel and related views (StepsView) consume region data, while the region-aware layout keeps anchor and region boundaries intact. Tests and docs updated to reflect region-driven organization and visualization of screen regions. This enables visualizing a screen’s picture as region-based columns rather than a single distance-driven row. |
||
|
|
6f4887db80 |
feat(steps): draw all arms of conditional navigations as separate edges
Adds multi-arm navigation support: when a destination is produced by a conditional, every arm is now drawn as its own edge. Introduces helpers (hrefArms, destinationsForHref) and updates framework resolvers and edge creation to emit multiple navigates edges (via alsoTargets) instead of a single one. Also introduces per-app rooted route tables to avoid cross-app crossings, and updates various resolvers (React Router, TanStack Router, Vue Router, SvelteKit, Vue, and SvelteKit’s linker) and the UI to reflect multiple possible destinations. Tests and docs updated to reflect the new behavior, ensuring the Screens tab shows all possible navigation paths from conditional destinations. This makes navigation visualization more accurate for forked destinations. |
||
|
|
02430ccc32 |
feat(steps): a reply that sets no status is a 200; inline Express handlers keep their replies
- effects.ts implicitResponseStatus: a body-sending reply with no status in its chain (res.json / send / render, reply.send, c.json, NextResponse.json, JSONResponse / jsonify / render_template, Rails render, Laravel response()->json) is a 200; a variable status, end, sendStatus and redirects stay as they were - branch-guards callSiteInTree: a status set by the statement just before the reply (`res.status(202); res.json(user)`) is that reply's — looked back within the block, only a statement that IS the status call counts - steps.ts: explicit chain/args → set-before → implicit 200 - express.ts: an inline handler's reply calls (`res.status(404).json(…)`, `res.json(user)`) are references at their own line and column instead of framework noise, so the route's own reply box exists - tests: servers fixture (inline route's 200 beside the service's 404; a 202 set before), ui-effects - CHANGELOG Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01REFyW9hmNrxhwN5wxRoAkC |
||
|
|
bc45e9071d |
feat(routes): FastAPI prefixes, ASP.NET endpoint groups, wrapped server actions on the Screens tab
- python.ts postExtract: APIRouter(prefix=) and literal include_router(prefix=) composed down the include tree (module import, alias, local); a computed prefix leaves that mount alone; full-stack-fastapi-template 23 routes named by path
- csharp.ts: handler-first MapPost(Handler[, "path"]) under the endpoint-group class, the app's $"/api/{groupName}" head read in postExtract, RoutePrefix honoured; detection covers Endpoints/ files; CleanArchitecture 10 routes
- tier-synthesizer: a type argument between a client call and its parentheses (useSWR<T>(…), ky.get<T>(…)); recorded callee without it
- screens.ts: a file-scope navigation attributed to the value spanning it; a value nothing calls attributed to the functions mentioning it in importing files (request-time source read, bounded); steps.ts lends navigates edges to a value root
- tests: servers fixture (FastAPI prefixed routers, ASP.NET endpoint group end to end), frameworks.test.ts (group form, RoutePrefix), cross-tier (generic useSWR)
- docs: CHANGELOG, plan, playbook rows
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REFyW9hmNrxhwN5wxRoAkC
|
||
|
|
9c6bc23b21 |
feat(screens): Next.js as a Screens app — pages, route handlers, links and redirects
- frameworks/nextjs.ts (split out of react.ts): App Router app/**/page.tsx and Pages Router pages → routes named by path ((group) stripped, [slug] → :slug, [...all] → :all*), bound to the default export; app/**/route.ts exports → METHOD /api/… endpoints referencing their functions; pages/api → ANY; resolve() claims router.push/replace/prefetch, redirect/permanentRedirect and NextResponse.redirect(new URL(…)) into navigates edges via the Expo href readers, against a Next-only route table gated on the app's root
- next-router-synthesizer.ts: <Link href> and internal <a href> → dashed navigates edges from the component (next-link, registeredAt)
- expo-router.ts: href readers exported; matcher accepts :param / :all* segments
- steps.ts: a Next page's own work fires from page load; a Next page makes the project a web app; {status: 201} read off the call site (branch-guards CallSiteText.status) for response rows
- frameworks/package-deps.ts: nested package.json files probed on disk (getAllFiles lists only sources); Express/React/Expo/Nest detectors use it; routing manifest names constant handlers
- tests: nextjs.test.ts (file→route rules, extract, verbs, end to end with Screens and Steps); frameworks.test.ts Next cases moved to the Next resolver
- docs: CHANGELOG, spec §3.12 frameworks paragraph, CLAUDE.md, synthesis doc, plan P4 built, playbook rows for Next / MERN / Nest channels
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REFyW9hmNrxhwN5wxRoAkC
|
||
|
|
b1f40c57dd |
feat(steps): cross-tier channels — a client's fetch onto its own route, queue jobs onto consumers, bus and socket events onto handlers
- resolution/tier-synthesizer.ts: http-client (literal fetch/axios/ky/got/$fetch paths, axios.create baseURL instances, template holes as :params, base-URL holes by a two-segment tail; unique match only), queue-job (BullMQ/Bull add ↔ @Process/@Processor, WorkerHost process, new Worker, queue.process), event-bus (EventEmitter2 emit ↔ @OnEvent with globs; socket emit ↔ @SubscribeMessage / socket.on both ways with tier); channel, tier, callee, registeredAt on every edge; generic transport events never pair; test and generated files never sources; registered before the emitter pass
- steps.ts: crossing() reads tier/channel before languages; an endpoint reached across a tier is a bridge box and a boundary like a screen (through=1 enters it); a channel's call is not also an effect; sites read as written; a Next 'use server' action is a crossing by its directive (when.ts directive); a function-valued constant handler (asyncHandler(...)) is a route root and borrows the file-scope calls and refs within its lines
- express.ts: app.use('/prefix', router) mounts composed onto route names in postExtract (nested, by import or require); chained router.route('/x').get(h).put(h2) extracted, across lines
- frameworks/package-deps.ts: dependencies read from workspace package.json files too (Express, React, Expo Router, NestJS detect)
- routing manifest names constant handlers; e2e/ is a test directory; explore's Flow section labels the new channels
- tests: ui-steps-cross-tier (monorepo fixture: Next client + Express/Nest API), servers test updated for the queue landing
- docs: CHANGELOG, spec §3.13 cross-tier paragraph, CLAUDE.md, callback-edge-synthesis.md, plan P3 built
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REFyW9hmNrxhwN5wxRoAkC
|