mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-20 06:19:28 +08:00
main
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2344ce0722 |
test: stop test runs leaving temp dirs behind (#2622)
Every full test run left about 40 directories in the temp dir, even when it passed. On the Windows dev machine about 16,000 had piled up by mid-October. A full run with TEMP pointed at an empty folder showed where each one came from: - function-ref (20) and ts-field-classification (3): each case's `finally` cleared `tmpDir` right after `cg.destroy()`, so the `afterEach` that removes the dir found nothing to remove. - npm-sdk (3) and liveness-watchdog (3) never removed the dirs they made. - extraction's #1502 suite (3) made a dir in `beforeEach` and had no `afterEach`; pr19-improvements' schema-version case (1) made one inline. - setup-home-sandbox (6 on Windows): Vitest runs no hooks for a file whose every test is skipped, so each platform-gated file left its home sandbox. Each sandbox now records itself in a per-run registry, and a global teardown removes whatever is still listed and names any dir still in use. With the fix, the same full run leaves nothing behind but Node's own reused compile cache. test-home-sandbox gains two tests: a file's home is listed in the run's registry, and the teardown removes every listed home, then the registry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f838c5a02a |
fix(resolution): resolve method values on untyped Python module globals (#2074) (#2291)
* fix(resolution): resolve method values on untyped Python module globals (#1820) A method value whose receiver is a module global without a static type (`thread_pool_exec(settings.conn.fetch, ...)`, where `conn = None` is rebound by `global conn; conn = Backend()`) resolved to a `variable` node and produced no edge. The global's type is now the set of classes its module assigns to it, at module scope or in a function that declares it `global`. One class resolves to its own method; several bind to the nearest declaration every candidate inherits, as a base-typed receiver does. No edge (a wrong callback edge is worse than none) when: - any binding of the global in its module is not a whole constructor call: a factory, a conditional, a tuple target, `for`/`with`/import, a star import, or an annotation the constructor contradicts; - the caller or an enclosing function binds the name itself: parameter, assignment (any target position), loop, comprehension, `as`, `case` pattern, lambda parameter; - the receiver is a deeper chain (`settings.conn.pool.fetch`); - the importing file rebinds the name, or imports it from two sources. Statements are read with their continuation lines, and only statement lines (bracket depth 0) can assign. Not seen: rebinding the global from another module (`settings.conn = X`) or through `globals()`. `pkg.mod.Cls` now resolves through `import pkg.mod`, unless two namespace imports share the last segment. Python import mappings also read parenthesized from-imports (`from x import (\n A,\n B,\n)`), which previously yielded the single name `(` and hid every class imported that way from receiver and inheritance resolution. Comments and docstrings are stripped first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018CQubRS1bXvaoApgPyLf66 (cherry picked from commit |
||
|
|
9be3f1a8a2 |
fix(resolution): keep unknown Python import members unresolved (#2040)
Bisect identifies
|
||
|
|
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> |
||
|
|
9181dd1ef3 |
fix(extraction): land upstream declaration initializer walks (#1511) (#1802)
Squash danusha2345's PR #1511 at |
||
|
|
38580e0b04 |
fix(python): bare class references produce references edges to classes (#1478) (#1493)
Python's class-as-value idioms (return SomeClass, x = SomeClass, registry dicts, classes passed as arguments) produced no references edges, so callers/impact on a Django/DRF serializer missed the views that consume it. Three gates dropped them: - return_statement was never dispatched by PYTHON_SPEC (kernel mirrored) - the extraction gate (definedHere) collected function/method names only - resolution accepted function/method targets only (matchFunctionRef + the function_ref import fast path) Capture return_statement for Python (single expression; tuple returns not descended), admit same-file CLASS names to the gate, and accept class targets for Python bare identifiers — scoped to Python so the TS/JS KIND FILTER contract is untouched. The docopt false-positive mechanism behind the function-only rule (lowercase locals vs same-named methods) doesn't transfer: methods stay excluded for bare ids, and the same-file/import gate + unique-or-drop rules still apply. Probed on django-rest-framework (~250 files): 559 new references→class edges, 10/10 sampled genuine (serializer_class = AuthTokenSerializer, the ModelSerializer field-mapping registry, aliases, ctor args, isinstance). EXTRACTION_VERSION 24 → 25. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
dce61a5f4a |
fix(extraction): qualified Type::member refs skip the name gate — no-import references resolve (#812)
`KtHandlers::handle` registered from another file produced no edge: the
extraction gate required the scope to be a same-file type or an IMPORTED
name, but Java/Kotlin same-package references and Kotlin companion members
need no import at all, so the gate could never see them. (The "companion
members extract unqualified" limit recorded during Arc A was a probe
artifact: a SINGLE-LINE `class X { companion object { … } }` is an
upstream tree-sitter-kotlin misparse (ERROR node); real multi-line
companions extract transparently as qualified methods of the class.)
Qualified `Type::member` candidates now skip the name gate the same way
`this.<member>` ones do: the explicit-ref syntax is self-selecting, and
resolution stays scope-suffix-anchored + unique-or-drop, so a
`Decoy::handle` can never match a `KtHandlers::handle` ref (tested).
A/B vs main: rxjava +4 (same-package `Maybe::just` / `Single::just`
method refs), fmt +3 (gtest `&Test::DeleteSelf_` /
`&TestSuite::RunSetUpTestSuite` cross-file member pointers), okio 0-delta,
redis byte-identical — every new edge verified genuine, zero calls edges
touched, node counts identical.
Full suite 1392 passed. EXTRACTION_VERSION 22 → 23 (re-index to benefit).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
1f15f93feb |
feat(extraction): PHP string/array callables + Ruby lifecycle-hook symbols (#811)
The last two deferred callback-registration shapes from #756, each scoped to positions where the reference is trustworthy: PHP — a string is a callable ONLY in a known callable position: - string args of core HOFs (usort, array_map, array_filter, call_user_func*, preg_replace_callback, spl_autoload_register, set_error_handler, … — PHP_CALLABLE_HOFS): ungated (PHP globals are referenced cross-file without imports) + resolution unique-or-drop, function-kind only ('Cls::m' strings resolve qualified) - array callables anywhere in call args: [$this, 'method'] routes through the class-scoped this. resolver (parents included); [Foo::class, 'method'] resolves qualified - strings to arbitrary functions: deliberately nothing Ruby — hook-DSL symbols name a method of the enclosing class: (skip_)?(before|after|around)_* / validate / set_callback / helper_method / rescue_from(with:) symbols → class-scoped this.<sym>, riding the supertype pass so `before_action :authenticate` in a controller resolves to ApplicationController's method. `validates` (plural) excluded — its symbols name ATTRIBUTES. Class-body-level hooks attribute to the CLASS node (the scoped resolvers now accept class-like from-nodes). Also hardened while validating: the this.X supertype pass is now NODE-anchored — file-anchored class node → implements/extends edge targets → contains-anchored member lookup — replacing the name-keyed getSupertypes walk, which unioned every same-named class's parents (rails has a dozen `Engine`s) and produced a cross-class wrong edge. A/B vs main: WordPress +556 (14/14 sampled genuine — [$this,'m'] wiring, array_map('absint',…), sodium polyfill call_user_func_array dispatch); rails/rails +385 after the node-anchored fix (16/16 sampled genuine, incl. inherited hooks across real extends edges); controls byte-stable (excalidraw 0-delta, redis identical, typeorm keeps its +4 inherited getters). The only calls-edge deltas anywhere are pre-existing minified-bundle resolution jitter (wp-tinymce.js single-letter symbols). Full suite 1391 passed. EXTRACTION_VERSION 21 → 22 (re-index to benefit). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
38095aa95b |
feat(resolution): inherited this.X, Java/Kotlin cross-file method refs, Swift type scoping (#810)
Three callback-registration shapes deferred from #756/#808, one arc: 1. INHERITED this.X (TS/JS + every this.-routed language): a `this.<member>` registration whose member isn't on the enclosing class defers to a second pass (resolveDeferredThisMemberRefs — in-memory like deferredChainRefs, runs after implements/extends edges persist, same lifecycle as the #750 conformance pass) and resolves up the supertype chain, depth-capped BFS, validated targets only. `bus.on("submit", this.handleSubmit)` in a subclass links to FormBase::handleSubmit; same-named methods on unrelated classes never match. this.-prefixed candidates skip the extraction name gate (an inherited member can't be in definedHere). 2. JAVA/KOTLIN qualified method refs: `Handlers::onMessage` / `OtherClass::handle` emit QUALIFIED names resolved by the scoped suffix-matcher — cross-file capable, gated on the scope name being a same-file type or an imported name (dotted JVM imports now contribute their last segment). `this::m` and `super::m` route through the class-scoped resolver (super rides the supertype pass). References through a VARIABLE (`subscriber::onNext`) deliberately produce nothing — receiver type is unknowable; RxJava's baseline bare capture was resolving these to same-named same-file methods (a test method "registering" an anonymous class's onNext) — the rework drops 18 such wrong edges and keeps the 7 genuine Type::method refs RxJava's main tree actually has. 3. SWIFT enclosing-type scoping (implicit self): bare callback names match methods only of the from-symbol's own type (extension/nested scopes reconciled by suffix), and top-level code never matches methods. Alamofire: −44 wrong edges (parameters like `request`/`data`/`retrier` resolving to same-named methods on unrelated protocols), all verified; the same-class param collision (`task`) remains and is documented. New ResolutionContext.getNodeById lets matchers derive the from-symbol's class scope. Controls: redis/fmt fnref edges byte-identical; excalidraw stable; typeorm +4 genuine inherited-getter dependencies; zero calls edges changed on any of 7 A/B repos; nodes identical everywhere. Kotlin companion-object members extract unqualified (pre-existing) so `Type::companionFn` stays silent rather than guessing — documented. Full suite 1389 passed. EXTRACTION_VERSION 20 → 21 (re-index to benefit). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
38eb4e688c |
fix(extraction): classify TS/JS class fields by value — properties, not methods (#808) (#809)
Every TS `public_field_definition` / JS `field_definition` extracted as a
method-kind node, so a plain field (`public fonts: Fonts;`) was reported
as callable: class shape was misrepresented, kind-based filtering was
defeated, and bare-name call resolution landed on data fields — typeorm's
boolean `ColumnMetadata::isArray` field was soaking up Array.isArray(...)
call edges (685 such wrong edges on typeorm alone).
Classification now follows the VALUE (classifyMethodNode hook, mirroring
resolveBody's callable detection): arrow-function / function-expression
fields and HOF-wrapped ones (`onScroll = throttle(() => {…})`) stay
methods with their bodies walked; everything else becomes a property that
keeps its type-annotation references edge, visibility, static-ness, and
decorators. Field initializers are now walked too (`history =
createHistory()` attributes the call to the property — previously
invisible), and JS class fields — whose name lives in the grammar's
`property` field, so they never extracted a symbol at all — now appear in
the graph (resolveName on the JS extractor).
With fields correctly kinded, `this.X` callback registration is re-enabled
for TS/JS (removed in #807 because field pseudo-methods made it mostly
wrong): `this.<member>` candidates resolve CLASS-SCOPED
(resolveThisMemberFnRef) — the target must be a function/method sharing
the from-symbol's qualified-name class prefix, same file, no fallback —
so `addEventListener("online", this.onOfflineStatusToggle)` and API-object
wiring (`{ mutateElement: this.mutateElement }`) produce registration
edges to the enclosing class's own method, while `this.fonts` (a
property) and inherited/unknown members yield no edge.
A/B (baseline = #807 main): excalidraw / typeorm / express — node counts
identical on all three; kinds shift method→property only (typeorm: exactly
7,406 swapped; excalidraw also corrects 5 anonymous-class mock fields that
were function-kind); every one of the 736 dropped call edges targeted a
node that is now a property (calls into data fields — verified 100%);
gains are retargets to real callables, initializer-call attributions, and
+74/+7 class-scoped this.X registration edges (sampled: addEventListener/
removeEventListener wiring, imperative-API method maps). Full suite green
(1386).
EXTRACTION_VERSION 19 → 20 (re-index to benefit).
Closes #808
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
8a114ba53c |
feat(extraction): capture function-as-value — callback registration sites in callers/impact (#756) (#807)
A function name used as a VALUE — passed as an argument
(signal(SIGINT, handler), qsort(..., compare)), assigned to a function
pointer or field (ops->recv_cb = my_cb, OnClick := Handler), or placed in
a struct initializer / handler table ({ .recv_cb = my_cb },
{ "get", getCommand }) — produced no edge in ANY of the 19 tree-sitter
languages, so registered callbacks looked dead and their registration
sites were invisible to callers/impact.
This adds table-driven function-as-value capture across all 19 languages
(plus the wrapper forms: &fn, &Cls::method, Java Class::m, Kotlin ::f,
Swift #selector, ObjC @selector, Ruby method(:sym), Scala eta, Pascal
@Handler), gated at extraction (same-file definitions + imported
bindings; C-family file-scope initializers are constant-expression
contexts and skip the gate, which is how redis-style cross-file command
tables resolve), and resolved by a dedicated strategy: function/method
targets only, same-file first, unique-or-drop cross-file, no fuzzy
fallback ever. Edges persist as kind 'references' with metadata.fnRef,
so getCallers/getImpactRadius surface them with zero graph-layer
changes; MCP callers/callees label them "via callback registration".
Precision rules bought by real-repo false positives (full A/B record in
docs/design/function-ref-capture.md): C++ is &-explicit outside
file-scope tables (fmt's begin/out/size collisions; out-of-line member
defs are function-kind); TS/JS/Python bare ids resolve to functions only
(TS class fields extract as method-kind — pre-existing quirk); Swift
refuses same-file method overload-families; param-forward shapes
(this.x = x, value: value) and destructuring are skipped; minified
bundles (*.min.js) produce no candidates.
Validated on 17 public OSS repos (redis, excalidraw, gin, bytes, okhttp,
okio, Alamofire, flask, sinatra, Newtonsoft.Json, scopt, provider,
busted, Fusion, AFNetworking, PascalCoin, fmt): node counts identical,
zero calls edges lost or gained, references strictly additive
(+3,200 registration edges total), precision spot-checked by reading
sampled source lines (redis 30/30, flask 8/8). Deliberately NOT covered:
indirect-dispatch resolution (o->cb(x) → impl) — that needs data-flow
through struct fields, and a wrong edge is worse than none.
EXTRACTION_VERSION 18 → 19 (re-index to benefit).
Closes #756
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|