Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions internal/cbm/extract_defs.c
Original file line number Diff line number Diff line change
Expand Up @@ -4440,6 +4440,25 @@ static void extract_class_methods(CBMExtractCtx *ctx, TSNode class_node, const c
}
}

// Java enums wrap methods inside enum_body_declarations; iterate
// through to reach the method_declaration nodes.
if (strcmp(ts_node_type(child), "enum_body_declarations") == 0) {
uint32_t dc = ts_node_child_count(child);
for (uint32_t j = 0; j < dc; j++) {
TSNode dchild = ts_node_child(child, j);
if (ts_node_is_null(dchild) ||
!cbm_kind_in_set(dchild, spec->function_node_types)) {
continue;
}
TSNode nn = resolve_method_name(dchild, ctx->language);
if (ts_node_is_null(nn)) {
continue;
}
push_method_def(ctx, dchild, class_node, class_qn, spec, nn);
}
continue;
}

// Python wraps @classmethod / @staticmethod / @property methods in
// a decorated_definition node. Peek through it to find the inner
// function_definition so we still emit a Method entry.
Expand Down Expand Up @@ -6349,6 +6368,10 @@ static void push_class_body_children(TSNode node, const CBMLangSpec *spec, wd_st
if (strcmp(ck, "field_declaration_list") == 0 || strcmp(ck, "class_body") == 0 ||
strcmp(ck, "declaration_list") == 0 || strcmp(ck, "body") == 0 ||
strcmp(ck, "block") == 0 || strcmp(ck, "suite") == 0 ||
// Java interface/enum bodies: gated to Java so grammars that
// share these node names (TypeScript, Apex, Dart) are unaffected.
(strcmp(ck, "interface_body") == 0 && spec->language == CBM_LANG_JAVA) ||
(strcmp(ck, "enum_body") == 0 && spec->language == CBM_LANG_JAVA) ||
// Groovy class bodies are a `closure` node; routing through the
// nested-class path keeps methods from being re-walked (and thus
// double-extracted) as top-level functions. Gated to Groovy so other
Expand Down
28 changes: 28 additions & 0 deletions tests/test_convergence_probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,32 @@ TEST(cp_enum_method_java) {
PASS();
}

/* D6d — Java interface method call (#1234).
* Prevention in push_class_body_children stops duplicate Function nodes
* from being minted for interface methods. The surviving Method node
* must still carry its CALLS edge. */
TEST(cp_interface_method_no_dup_function) {
static const CP_File f[] = {
{"Svc.java",
"package app;\n\n"
"interface Greeter {\n"
" String greet(String name);\n}\n\n"
"class SimpleGreeter implements Greeter {\n"
" public String greet(String name) { return \"Hello \" + name; }\n}\n\n"
"class App {\n"
" static String run(Greeter g) {\n"
" return g.greet(\"world\");\n }\n}\n"}};
CP_Proj lp;
cbm_store_t *store = cp_index_files(&lp, f, 1);
int calls = cp_edges(store, lp.project, "CALLS");
int fns = cp_count_label(store, lp.project, "Function");
if (calls < 1) cp_diag(store, lp.project, "interface_dedup/java_1234");
cp_cleanup(&lp, store);
ASSERT_TRUE(fns == 0);
ASSERT_TRUE(calls >= 1);
PASS();
}

/* ══════════════════════════════════════════════════════════════════
* AREA E — Node-creation corners
* ══════════════════════════════════════════════════════════════════ */
Expand Down Expand Up @@ -1574,6 +1600,8 @@ SUITE(convergence_probe) {
RUN_TEST(cp_enum_variant_rust_impl);
/* D6c Java enum method — EXPECTED UNCERTAIN */
RUN_TEST(cp_enum_method_java);
/* D6d Java interface method — #1234 regression */
RUN_TEST(cp_interface_method_no_dup_function);

/* ── AREA E1: Nested classes (3 cases) ──────────── */
/* E1a Python inner class — EXPECTED GREEN */
Expand Down
51 changes: 51 additions & 0 deletions tests/test_extraction.c
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,55 @@ TEST(java_interface) {
PASS();
}

/* Regression for #1234: Java interface/enum methods were emitted as both a
* Method node (correct, via extract_class_methods) and a duplicate Function
* node (incorrect, via walk_defs). Prevention in push_class_body_children
* (gated to Java) recognizes interface_body and enum_body as class body
* containers, stopping the fallback path from re-walking method_declaration
* children as top-level functions. */
TEST(java_interface_no_duplicate_function_issue1234) {
CBMFileResult *r =
extract("public interface MarketplaceService {\n"
" ReservationDTO createReservation(Authentication auth, RequestDTO req);\n"
" void cancelReservation(long id);\n"
"}\n",
CBM_LANG_JAVA, "t", "MarketplaceService.java");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);

ASSERT(has_def(r, "Interface", "MarketplaceService"));
ASSERT(has_def(r, "Method", "createReservation"));
ASSERT(has_def(r, "Method", "cancelReservation"));
ASSERT_EQ(count_defs_with_label(r, "Function"), 0);

cbm_free_result(r);
PASS();
}

TEST(java_enum_dedup_preserves_calls_issue1234) {
CBMFileResult *r =
extract("package app;\n\nenum Day {\n"
" MON, TUE, WED, THU, FRI, SAT, SUN;\n\n"
" public boolean isWeekend() { return this == SAT || this == SUN; }\n"
" public String label() { return name().toLowerCase(); }\n}\n\n"
"class DayUtil {\n"
" static String describe(Day d) {\n"
" return d.label() + (d.isWeekend() ? \"(rest)\" : \"(work)\");\n }\n}\n",
CBM_LANG_JAVA, "t", "Day.java");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);

ASSERT(has_def(r, "Enum", "Day"));
ASSERT(has_def(r, "Method", "isWeekend"));
ASSERT(has_def(r, "Method", "label"));
ASSERT(has_def(r, "Class", "DayUtil"));
ASSERT(has_def(r, "Method", "describe"));
ASSERT_EQ(count_defs_with_label(r, "Function"), 0);

cbm_free_result(r);
PASS();
}

/* Regression for #279: a Java class declaring both `extends` and
* `implements` must produce one INHERITS edge per base — the extends parent
* AND every implements interface — with bare type names (not the keyword
Expand Down Expand Up @@ -4947,6 +4996,8 @@ SUITE(extraction) {
RUN_TEST(java_class);
RUN_TEST(java_method);
RUN_TEST(java_interface);
RUN_TEST(java_interface_no_duplicate_function_issue1234);
RUN_TEST(java_enum_dedup_preserves_calls_issue1234);
RUN_TEST(java_class_extends_and_implements);
RUN_TEST(python_class_base_extracted_bare);
RUN_TEST(php_class);
Expand Down
Loading