From 6643afbcda1bc1c542f2bebea944f2d062bbc5ae Mon Sep 17 00:00:00 2001 From: Sparsh <73558748+prajapatisparsh@users.noreply.github.com> Date: Tue, 2 Jun 2026 17:50:01 +0530 Subject: [PATCH] =?UTF-8?q?fix(ruby):=20scope-resolution=20namespaced=20cl?= =?UTF-8?q?ass/module=20definitions=20=E2=80=94=20F62=20(#1933)=20(#1972)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(ruby): namespaced class/module definition captures — F62 (#1933) * chore(bench): regenerate Ruby golden captures after F62 scope_resolution patterns * fix(ruby): namespaced class/module definition captures — F62 (#1933) * chore: remove unused imports from ruby-namespaced test * chore: add comment about capture-only scope in ruby-namespaced test --------- Co-authored-by: Gergő Magyar --- gitnexus/bench/scope-capture/baselines.json | 5 +- .../core/ingestion/languages/ruby/query.ts | 10 +++ .../ruby-namespaced/namespaced.rb | 11 +++ .../expected-captures.json | 4 + .../resolvers/ruby-namespaced.test.ts | 82 +++++++++++++++++++ 5 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 gitnexus/test/fixtures/lang-resolution/ruby-namespaced/namespaced.rb create mode 100644 gitnexus/test/integration/resolvers/ruby-namespaced.test.ts diff --git a/gitnexus/bench/scope-capture/baselines.json b/gitnexus/bench/scope-capture/baselines.json index a1d4fbea4..a51cce109 100644 --- a/gitnexus/bench/scope-capture/baselines.json +++ b/gitnexus/bench/scope-capture/baselines.json @@ -37,9 +37,10 @@ "_rebaselined": "#1956: heritage-bearing scale source (class extends Base + use trait); both forms gated at scale; linear (~1.04)." }, "ruby": { - "fingerprint": "bdc7dbfbe5ce7b1e98292f88b404071b4a4b5566f6e756cd637e36a2214967e1", + "fingerprint": "c3e9eec6ed152eae1f7d9759c08041d7d6230a4c532ec07d33f3c9f1ff7b9588", "scaling_budget": 1.5, - "_rebaselined": "#1956 synth-widening: + ruby-qualified-base fixture; synth now reduces a scope_resolution superclass (class C < Mod::Super) to its trailing constant (matching the #1940 legacy leg), at parity. Linear (~1.03). (Earlier #1956: heritage-bearing scale source.)" + "_rebaselined": "#1956 synth-widening: + ruby-qualified-base fixture; synth now reduces a scope_resolution superclass (class C < Mod::Super) to its trailing constant (matching the #1940 legacy leg), at parity. Linear (~1.03). (Earlier #1956: heritage-bearing scale source.)", + "_note": "F62: + scope_resolution class/module declaration captures — fixture count 78→81, fingerprint drift expected." }, "swift": { "fingerprint": "53325c6345161c5a495f997297af5a24fb718fd3e6647040160f8ab2a2c8e4c0", diff --git a/gitnexus/src/core/ingestion/languages/ruby/query.ts b/gitnexus/src/core/ingestion/languages/ruby/query.ts index 36a4e36b3..853361f2d 100644 --- a/gitnexus/src/core/ingestion/languages/ruby/query.ts +++ b/gitnexus/src/core/ingestion/languages/ruby/query.ts @@ -54,11 +54,21 @@ const RUBY_SCOPE_QUERY = ` (class name: (constant) @declaration.name) @declaration.class +;; class Foo::Bar — namespaced class definition +(class + name: (scope_resolution + name: (constant) @declaration.name)) @declaration.class + ;; ── Declarations — module (labeled Trait for class-like registry lookup) ─ (module name: (constant) @declaration.name) @declaration.trait +;; module Baz::Qux — namespaced module definition +(module + name: (scope_resolution + name: (constant) @declaration.name)) @declaration.trait + ;; ── Declarations — method (instance) ───────────────────────────────────── (method diff --git a/gitnexus/test/fixtures/lang-resolution/ruby-namespaced/namespaced.rb b/gitnexus/test/fixtures/lang-resolution/ruby-namespaced/namespaced.rb new file mode 100644 index 000000000..4e40ba05a --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/ruby-namespaced/namespaced.rb @@ -0,0 +1,11 @@ +class Foo::Bar + def bar_method; end +end + +module Baz::Qux + def qux_method; end +end + +class Outer::Middle::Inner + def inner_method; end +end diff --git a/gitnexus/test/fixtures/ruby-captures-golden/expected-captures.json b/gitnexus/test/fixtures/ruby-captures-golden/expected-captures.json index dc2a4d1b2..a7ed5142a 100644 --- a/gitnexus/test/fixtures/ruby-captures-golden/expected-captures.json +++ b/gitnexus/test/fixtures/ruby-captures-golden/expected-captures.json @@ -203,6 +203,10 @@ "captureGroups": 12, "digest": "c10f36dbbbe2be16fc3fccbebb7ee79668ec7a77b75adbd5281ded31893d49de" }, + "ruby-namespaced/namespaced.rb": { + "captureGroups": 16, + "digest": "34e07387fece6c1d2deb49c39fc2bfe0badfe8015dd1f7ae956d57ac98322a1d" + }, "ruby-overload-dispatch/lib/app.rb": { "captureGroups": 10, "digest": "288d5386cf37fb76b52a94bc7da6bf8e7843830ebbb01fcd8100d1590c0e3f72" diff --git a/gitnexus/test/integration/resolvers/ruby-namespaced.test.ts b/gitnexus/test/integration/resolvers/ruby-namespaced.test.ts new file mode 100644 index 000000000..162de7ef4 --- /dev/null +++ b/gitnexus/test/integration/resolvers/ruby-namespaced.test.ts @@ -0,0 +1,82 @@ +/** + * Regression tests for Ruby namespaced class/module definitions (issue #1933 F62). + * + * The existing query captures (class) and (module) with name: (constant) only — + * missing namespaced forms like class Foo::Bar and module Baz::Qux where the + * name field is a scope_resolution node. + */ +// NOTE: Tests are capture-level only. Graph-node modeling for namespaced +// class/module definitions is a tracked follow-up — the scope-extractor +// doesn't yet handle scope_resolution names end-to-end. +import { describe, it, expect } from 'vitest'; +import { emitRubyScopeCaptures } from '../../../src/core/ingestion/languages/ruby/index.js'; +import type { CaptureMatch } from 'gitnexus-shared'; + +describe('Ruby namespaced class/module definitions (F62) — capture-level', () => { + it('class Foo::Bar captures @declaration.class with tail constant (Bar)', () => { + const src = `class Foo::Bar + def bar_method; end +end +`; + const matches = emitRubyScopeCaptures(src, 'test.rb') as CaptureMatch[]; + const classDecls = matches.filter((m) => m['@declaration.class']); + expect(classDecls.length).toBe(1); + expect(classDecls[0]['@declaration.name'].text).toBe('Bar'); + }); + + it('module Baz::Qux captures @declaration.trait with tail constant (Qux)', () => { + const src = `module Baz::Qux + def qux_method; end +end +`; + const matches = emitRubyScopeCaptures(src, 'test.rb') as CaptureMatch[]; + const moduleDecls = matches.filter((m) => m['@declaration.trait']); + expect(moduleDecls.length).toBe(1); + expect(moduleDecls[0]['@declaration.name'].text).toBe('Qux'); + }); + + it('nested chain Outer::Middle::Inner resolves to tail constant (Inner)', () => { + const src = `class Outer::Middle::Inner + def inner_method; end +end +`; + const matches = emitRubyScopeCaptures(src, 'test.rb') as CaptureMatch[]; + const classDecls = matches.filter((m) => m['@declaration.class']); + expect(classDecls.length).toBe(1); + expect(classDecls[0]['@declaration.name'].text).toBe('Inner'); + }); + + it('bare class Foo still works alongside namespaced class', () => { + const src = ` +class Foo + def foo_method; end +end + +class Foo::Bar + def bar_method; end +end +`; + const matches = emitRubyScopeCaptures(src, 'test.rb') as CaptureMatch[]; + const classDecls = matches.filter((m) => m['@declaration.class']); + expect(classDecls.length).toBe(2); + const names = classDecls.map((m) => m['@declaration.name'].text).sort(); + expect(names).toEqual(['Bar', 'Foo']); + }); + + it('bare module Baz still works alongside namespaced module', () => { + const src = ` +module Baz + def baz_method; end +end + +module Baz::Qux + def qux_method; end +end +`; + const matches = emitRubyScopeCaptures(src, 'test.rb') as CaptureMatch[]; + const moduleDecls = matches.filter((m) => m['@declaration.trait']); + expect(moduleDecls.length).toBe(2); + const names = moduleDecls.map((m) => m['@declaration.name'].text).sort(); + expect(names).toEqual(['Baz', 'Qux']); + }); +});