mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
|
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* fix(resolution): resolve constructor-expression receivers (#2708) `Service(db).do_work()` emitted no CALLS edge, so the caller was missing from `impact(direction: "upstream")` and `context()` while the two-step spelling of the same call (`s = Service(db)` then `s.do_work()`) resolved. The receiver reaches `resolveCompoundReceiverClass` intact — Case 0 in `receiver-bound-calls` routes it there because the text contains `(`. The free-call branch then only knew one shape: a function whose return-type binding names a class. A class has no return-type binding, so `Service` resolved to nothing and the member call was dropped. Handle the constructor shape: in languages that construct without a `new` keyword (Python, Kotlin, Swift, Scala) a free call naming a class IS a constructor call, so the expression's type is that class. The existing return-type path still runs first and wins, keeping this strictly additive — `new`-keyword languages never reach the new line because their receiver text keeps the keyword (`new Service(db)`), which matches no class binding. Verified on the issue's 4-file repro: `route_inline` now emits `CALLS → Service.do_work` and `impactedCount` goes 1 → 2. Note the issue's second ask — degrading `epistemic` to `lower-bound` when a receiver goes unresolved — is NOT addressed here. `computeEpistemicBoundary` keys only on the target's own heritage edges and runs at query time against the index, while unresolved references live in an in-memory `resolutionOutcomes[]` that is never persisted. That needs unresolved-receiver counts in the index first, so it is left for a follow-up. Tests: new `python-inline-constructor-receiver` fixture plus three integration cases (inline resolves, two-step still resolves, no cross-class fan-out). Two of the three fail without the source change. Full `test/integration/resolvers` suite passes (2928 tests) — the fix is shared across every language, so no-regression coverage matters more than the new cases. Python captures golden regenerated: additions only, no existing digest changed, confirming capture output is untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqnsJK3Cnbu3bjdZbzgMMP * refactor(resolution): state the construction rule once, cover every spelling (#2708) The first commit fixed `Service(db).do_work()` by special-casing a bare class-name callee inside the free-call branch of the compound receiver resolver. That was the right rule in the wrong place: it covered one surface syntax out of three, and asserted rather than declared which languages it applied to. Probing the same shape across languages showed the bug is wider: | spelling | languages | dropped before? | |------------------------|--------------------|-----------------| | `Service(db).m()` | Python | yes | | `new Service(db).m()` | JS/TS, Java, C# | yes | | `Service.new.m()` | Ruby | yes | | both forms | PHP, Swift, Dart, | no — already | | | Kotlin | resolved | So the rule is stated once — "constructing a class yields an instance of that class" — and the per-language surface syntax is declared through a new `ScopeResolver.constructionSyntax` hook, matching how this file already gates language-varying behaviour (`stripReceiverCastExpressions`, `hoistTypeBindingsToModule`). Shared pipeline code names no language. - `bare: true` — Python - `keyword: 'new'` — JS/TS, Java, C# - `selector: 'new'` — Ruby, including the parenthesis-less `Service.new` spelling that reaches the chain walker rather than the call branch Opt-in is per-language for two reasons. Correctness: `bare` would mistype `stat(&st).field` in C, where a struct and a function may share a name. Evidence: PHP, Swift, Dart and Kotlin resolve this shape already, so they stay unwired instead of carrying a declaration that changes nothing — each verified by diffing analyzer output between builds with and without the change, not assumed. The keyword gate also keeps a bare factory call honest: in a `new` language, `makeOther(db).doWork()` still resolves through the factory's return type and is never read as constructing a same-named class. Tests: TypeScript fixture (inline `new`, a plain `.js` file for the javascript provider, two-step, and the factory guard) and a Ruby fixture (`Service.new` with and without an argument list, plus two-step). With the source change stashed, the inline cases fail and the factory/two-step cases still pass. The Python cases from the first commit are unchanged. No Kotlin fixture: its cases passed without the change, so they would document coverage this commit does not provide. Full `test/integration/resolvers` + `test/unit/scope-resolution`: 4234 passed, 1 skipped. Ruby captures golden regenerated — additions only, no existing digest changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqnsJK3Cnbu3bjdZbzgMMP * fix(resolution): only treat a construction selector as construction on the class itself (#2708) The `selector: 'new'` rule fired on any receiver whose type was class-like, which is true both when the receiver IS the class constant (`Factory.new`) and when it is a value of that class (`factory.new`). `isClassLike(...)` cannot tell those apart, so an instance receiver took the construction path too and skipped the member lookup that should have run. That replaced a CORRECT edge with a wrong one. Measured against the base build on a class defining an instance method `new` returning a `Product`: factory = Factory.new; factory.new.run before this PR: Product#run (correct) after this PR: Factory#run (wrong) Track whether resolution currently sits on the class constant or on a value of that class, and apply the selector rule only to the former. The head of a chain is a class constant only when it resolved straight to a class binding rather than through a typeBinding; every hop past it yields a value, so the flag clears. The `obj.method()` branch derives the same fact from whether `objExpr` is a bare name resolving to that class. `Factory.new.run` keeps the behaviour this PR introduced (Factory#run), which is itself a fix over the base build's Product#run. KNOWN LIMITATION, now documented on the contract field and asserted by a test so a future change to it is deliberate: a class-level override (`def self.new` returning another type) is still read as construction. The scope model records no staticness per member, so `def new` and `def self.new` are indistinguishable at this layer; separating them needs the language provider to record staticness first. An earlier attempt to use `TypeRef.source` as a proxy was abandoned after tracing showed Ruby records body-inferred return types as `return-annotation` too, so it does not discriminate. Tests: `ruby-construction-selector` fixture pins all three shapes — class constant, instance receiver, and the documented class-level-override limitation. Ruby resolver suites: 185 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqnsJK3Cnbu3bjdZbzgMMP * fix(resolution): resolve generic construction receivers (#2708) `new Box<string>().unwrap()` reached the class lookup as `Box<string>`, which names no class binding, so the member edge was still dropped while the non-generic spelling resolved. `new Foo<T>()` is ordinary in all three keyword-wired languages, so the fix covered a materially narrower slice of real code than intended. Retry the lookup on the base name via `stripTemplateArguments` — the same normalization `resolveClassBindingForName` already applies to typed receivers in the sibling `receiver-bound-calls` pass. The exact-name lookup still runs first, so a class whose name legitimately contains `<` is unaffected. Measured on the probe that first showed the gap: before: | viaGeneric | Class:src/box.ts:Box | (construction edge only) after: | viaGeneric | Method:src/box.ts:Box.get#0 | (member edge resolved) Tests: `viaGenericCtor` added to the typescript-inline-constructor-receiver fixture, asserting both the target file and that the resolved id is `Box`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqnsJK3Cnbu3bjdZbzgMMP * fix(resolution): resolve construction in the chain-head position (#2708) `new Service(db).inner.deep()` emitted only the construction edge. The chain walker seeds its starting class from the head segment, which arrives as `new Service(db)` and reduces via `stripCallParens` to `new Service` — no binding and no class of that name, so the walk was never seeded and every segment after it resolved to nothing. Seed the head through the same construction rule the call branch already uses. A constructed value is an instance, so the class-constant flag from the previous commit correctly stays false — `new Factory().new` does not get the selector treatment. The gap was asymmetric across the languages this PR wires: Python's bare form strips to a plain `Service` and was already seeded, so only the keyword languages were affected. Tests: `viaChainHead` added to the typescript-inline-constructor-receiver fixture. Note the fixture annotates `readonly inner: Inner` explicitly — with an unannotated initializer the walk stops at the field, which is field-type inference and a separate concern from head seeding. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqnsJK3Cnbu3bjdZbzgMMP * fix(resolution): match the construction keyword by token, not by one space (#2708) The keyword form was matched with `startsWith(`${keyword} `)`, so only a single space separated `new` from the type. Any other trivia the source used — a tab, a line break — failed the match and the member-call edge was lost. Match the keyword as a whole token followed by one or more whitespace characters instead. `newService()` still fails the match, which is the point: it is an ordinary call, not a construction, and must keep resolving through its own return type. The keyword is escaped before it enters the pattern. It comes from a language provider rather than from user input, but a keyword containing a regex metacharacter would otherwise build a silently wrong pattern. Tests: tab-separated and newline-separated `new` added to the typescript-inline-constructor-receiver fixture. Note these cases only survive because `gitnexus/test/fixtures/` is listed in the repo-root `.prettierignore` — running prettier from inside `gitnexus/` does not pick that file up and normalizes the tab away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqnsJK3Cnbu3bjdZbzgMMP * fix(resolution): resolve qualified construction callees (#2708) `new ns.Service().doWork()` emitted only the construction edge. The call branch splits the callee at its last `.` before construction is considered, so a qualified type name was routed into `obj.method()` resolution as if `ns` were a receiver and `Service` a member. A keyword-marked expression is never a member call, so resolve it as construction before the split. The callee lookup now also handles a dotted name: an unambiguous `qualifiedNames` match first, then the trailing simple name, mirroring how receiver resolution elsewhere in this pass degrades. Measured: before: | viaQualified | Class:src/svc.ts:Service | (construction only) after: | viaQualified | Method:src/svc.ts:Service.doWork#0 | Bare-form qualified construction (Python `models.User(db).save()`) is NOT addressed here: that shape currently emits no edges at all, including no construction edge, so it is a namespace-import resolution gap upstream of this pass rather than a construction-typing one. Tests: `viaQualifiedCtor` added to the typescript-inline-constructor-receiver fixture. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqnsJK3Cnbu3bjdZbzgMMP * fix(java): drop the unreachable constructionSyntax declaration (#2708) Java was wired `{ keyword: 'new' }`, and the PR described it as one of the languages that needed the fix. Measuring both ways shows it never did: Java resolves `new Svc().doWork()` identically with and without the change, because `java/captures.ts` (#2564) already rewrites an `object_creation_expression` receiver to the constructed type's simple name, so the raw `new Svc()` text never reaches this resolver. The decisive evidence is generics: Java resolves `new Box<User>().doWork()`, which the keyword path could not do before the template-argument fix earlier in this series — the resolution demonstrably comes from the capture rewrite, not from here. Removing the declaration rather than leaving it as defensive configuration: an unreachable per-language opt-in reads as coverage that does not exist, and the contract now records why Java is excluded so the omission is not mistaken for an oversight. Verified after removal: the Java probe still resolves both the inline and two-step spellings, and the Java resolver suites pass (252 passed, 1 skipped). An earlier coordinator measurement in this review claimed Java WAS broken on base; that comparison was invalid (the "without fix" build had not been rebuilt). Corrected here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqnsJK3Cnbu3bjdZbzgMMP * refactor(resolution): state the selector rule once and derive its option type (#2708) Two follow-ups from review, no behaviour change (643 resolver tests pass unchanged before and after): The `Class.new` selector rule was written out twice — in the `obj.method()` branch and again in the chain walker — against differently named locals, while the construction helper's own doc comment claimed the rule was stated in exactly one place. Both sites ask the identical question, so they now call one `isConstructionSelectorHop` predicate, and the doc comment says what is actually true. `ResolveCompoundReceiverOptions.constructionSyntax` re-declared the contract's object shape by hand. It was the file's first object-shaped duplicate, and because the value arrives as a non-literal variable, TypeScript's excess property check would not fire: a sub-field added to the contract later would type-check and then be silently ignored here. It is now derived with `ScopeResolver['constructionSyntax']`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqnsJK3Cnbu3bjdZbzgMMP * test(resolution): cover the C# construction path and pin the wiring inventory (#2708) Three coverage gaps from review, no behaviour change. C# had no fixture despite being the only keyword-wired language whose behaviour genuinely depends on the construction rule — measured absent on base and present on head. `csharp-inline-constructor-receiver` covers the inline spelling, the two-step spelling, and a static factory that must keep resolving through its return type rather than being read as construction. The TypeScript two-step assertion checked only `toContain('Service')`, and the same fixture defines `LegacyService` — `'LegacyService'.includes('Service')` is true, so the assertion could not distinguish the two targets. It now pins `targetFilePath` the way its sibling assertions already do. Nothing guarded the deliberate opt-in set, so an accidental wiring of a language that already resolves the shape, or a silent loss of one that needs it, would pass the whole suite. `construction-syntax-wiring.test.ts` pins the inventory in both directions: exactly which languages declare `constructionSyntax` and with which spelling, and that java/php/swift/dart/ kotlin stay unwired. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqnsJK3Cnbu3bjdZbzgMMP * chore(storage): bump INCREMENTAL_SCHEMA_VERSION to 23 for the #2708 edge changes This series changes which CALLS edges are emitted for source whose CONTENT has not changed — inline constructor receivers that previously emitted nothing now resolve, and the Ruby selector fix moves one edge back to the member it always belonged to. That is precisely the class of change the version-history block in this file requires a bump for, and the reuse gate is a strict equality on the persisted stamp. Without it, every existing v22 index passes the gate on the next `analyze` — or is served by the same-commit "already up to date" fast path — and keeps returning the pre-fix graph for unchanged files. `impact(direction: "upstream")` and `context()` would go on omitting the very callers #2708 is about, with no warning, until something unrelated forced a full re-analyze. The fix would have shipped without reaching anyone who already had an index. Precedent is unbroken across the recent resolution PRs: #2723 → v22, #2699 → v21, #2695 → v20, #2563 → v14, each with its own rationale paragraph. This adds v23 in the same form. The pinned assertion in call-summary-schema-version.test.ts moves with it, as that test documents it is designed to. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqnsJK3Cnbu3bjdZbzgMMP * chore(bench): re-baseline the fixture-corpus fingerprints for #2708 Both bench harnesses fingerprint an entire fixture corpus by directory prefix (`bench/python-scope/measure.mjs:38`, `bench/scope-capture/measure.mjs:76`), so every fixture directory this series adds moves a committed baseline. Neither script writes the baseline itself — running without `--check` only prints, and the file is edited deliberately, which is what its own comment asks for. Regenerated, last in the series so the fixture set was final: bench/python-scope/baseline-fingerprint.txt 36e29abc… -> f120df92… bench/scope-capture/baselines.json ruby 070e4e11… -> fea3edf8… typescript 281e9548… -> cad25be9… csharp e05dc274… -> 05a85bae… CI only ever reported the python drift, because the benchmarks job runs the python step first and aborts there; the cross-language step never ran. Both were verified locally after the update: [measure --check] PASS (capture fingerprint + scaling) [import-target-fingerprint --check] PASS (resolver fingerprint) [scope-capture --check] PASS (15 languages) The `csharp` and `ruby` entries moved because of the fixtures added earlier in this series, not the original ones — a reminder that this baseline moves with any fixture addition, not just the one that first triggered it. Captures goldens regenerated alongside (csharp, ruby); both additive only, no existing digest changed. The python golden did not move: no `python-*` fixture was added after its last regeneration. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqnsJK3Cnbu3bjdZbzgMMP * Update tests for passesReuseGate function --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| app.rb | ||
| svc.rb | ||