Skip to content

Commit 8e1de04

Browse files
authored
Merge pull request #1709 from benrfairless/fix/ruby-superclass-base-classes
fix(extraction): keep Ruby superclass names bare
2 parents 13b7d06 + 2efb7a6 commit 8e1de04

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

‎internal/cbm/extract_defs.c‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2445,7 +2445,13 @@ static int collect_bases_from_field(CBMArena *a, TSNode field_node, const char *
24452445
* dotted base like `mod.Base`). Without these the raw-text fallback
24462446
* below captured the whole "(Base)" field text, which never
24472447
* resolved -> zero INHERITS edges for Python subclasses. */
2448-
strcmp(ck, "identifier") == 0 || strcmp(ck, "attribute") == 0) {
2448+
strcmp(ck, "identifier") == 0 || strcmp(ck, "attribute") == 0 ||
2449+
/* Ruby `class C < Base` wraps the base in a `superclass` node whose
2450+
* child is a `constant` (or `scope_resolution` for `A::B`). Without
2451+
* these the raw-text fallback captured "< Base" (operator included),
2452+
* which never resolves — breaking INHERITS and the Ruby LSP's
2453+
* superclass chain. */
2454+
strcmp(ck, "constant") == 0 || strcmp(ck, "scope_resolution") == 0) {
24492455
char *t = cbm_node_text(a, child, source);
24502456
if (t) {
24512457
char *angle = strchr(t, '<');

‎tests/test_extraction_inheritance.c‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
* C++ — extraction is correct (regression guards).
3232
* Rust — impl_traits array (not base_classes) is the capture point; Rust
3333
* rows verify impl_traits entries for `impl Trait for Struct`.
34+
* Ruby — green as of the fix that accompanies these rows: the
35+
* `superclass` wrapper's `constant` / `scope_resolution` child is
36+
* now matched, so `class C < Base` no longer falls through to the
37+
* raw-text fallback and stores "< Base".
3438
*
3539
* STRUCTURE
3640
* ─────────
@@ -1658,6 +1662,69 @@ TEST(inherit_rust_impls) {
16581662
PASS();
16591663
}
16601664

1665+
/* ═══════════════════════════════════════════════════════════════════
1666+
* RUBY (expected: GREEN — regression guards for the fix in this PR)
1667+
*
1668+
* Root cause fixed here: tree-sitter-ruby wraps `class C < Base` in a
1669+
* `superclass` node whose child is a `constant` (or `scope_resolution`
1670+
* for `A::B`). collect_bases_from_field() matched neither, so the
1671+
* raw-text fallback stored the whole field text "< Base" — operator
1672+
* included — and a base name spelled that way never resolves, so Ruby
1673+
* subclasses produced zero INHERITS edges.
1674+
*
1675+
* Every row therefore also asserts "<" never appears inside a captured
1676+
* base name: that substring IS the bug's signature.
1677+
* ═══════════════════════════════════════════════════════════════════ */
1678+
1679+
static const inherit_case_t ruby_cases[] = {
1680+
/* ── single bare-constant superclass ────────────────────────── */
1681+
{CBM_LANG_RUBY,
1682+
"m.rb",
1683+
"class Animal\nend\n\nclass Dog < Animal\nend\n",
1684+
"Dog",
1685+
{"Animal", NULL},
1686+
{"<", NULL},
1687+
1},
1688+
/* ── scope-resolved superclass (A::B) ───────────────────────── */
1689+
{CBM_LANG_RUBY,
1690+
"m.rb",
1691+
"class ApplicationRecord < ActiveRecord::Base\nend\n",
1692+
"ApplicationRecord",
1693+
{"ActiveRecord::Base", NULL},
1694+
{"<", NULL},
1695+
1},
1696+
/* ── the Rails chain's second link (bare, resolves to the above) ─ */
1697+
{CBM_LANG_RUBY,
1698+
"m.rb",
1699+
"class ApplicationRecord < ActiveRecord::Base\nend\n"
1700+
"class User < ApplicationRecord\nend\n",
1701+
"User",
1702+
{"ApplicationRecord", NULL},
1703+
{"<", NULL},
1704+
1},
1705+
/* ── subclass declared inside a module body ─────────────────── */
1706+
{CBM_LANG_RUBY,
1707+
"m.rb",
1708+
"class Base\nend\n\nmodule Admin\n class Panel < Base\n end\nend\n",
1709+
"Panel",
1710+
{"Base", NULL},
1711+
{"<", NULL},
1712+
1},
1713+
/* ── exception subclass (stdlib superclass) ─────────────────── */
1714+
{CBM_LANG_RUBY,
1715+
"m.rb",
1716+
"class AppError < StandardError\nend\n",
1717+
"AppError",
1718+
{"StandardError", NULL},
1719+
{"<", NULL},
1720+
1},
1721+
};
1722+
1723+
TEST(inherit_ruby) {
1724+
RUN_CASES(ruby_cases);
1725+
PASS();
1726+
}
1727+
16611728
/* ═══════════════════════════════════════════════════════════════════
16621729
* SUITE declaration
16631730
* ═══════════════════════════════════════════════════════════════════ */
@@ -1668,6 +1735,7 @@ SUITE(extraction_inheritance) {
16681735
RUN_TEST(inherit_csharp);
16691736
RUN_TEST(inherit_cpp);
16701737
RUN_TEST(inherit_rust_impls);
1738+
RUN_TEST(inherit_ruby);
16711739

16721740
/* Languages expected RED (broken extractors — reproduce-first) */
16731741
RUN_TEST(inherit_python); /* RED: identifier-node not matched in collect_bases_from_field */

0 commit comments

Comments
 (0)