diff --git a/internal/cbm/extract_defs.c b/internal/cbm/extract_defs.c index 7cc4177ef..7391ee16b 100644 --- a/internal/cbm/extract_defs.c +++ b/internal/cbm/extract_defs.c @@ -6269,6 +6269,7 @@ 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 || + strcmp(ck, "interface_body") == 0 || strcmp(ck, "enum_body") == 0 || // 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 diff --git a/tests/test_extraction.c b/tests/test_extraction.c index 2bc3f3c88..986ccc33b 100644 --- a/tests/test_extraction.c +++ b/tests/test_extraction.c @@ -419,6 +419,35 @@ TEST(java_interface) { PASS(); } +/* Regression for #1234: Java interface methods were emitted as both a Method + * node (correct, via extract_class_methods) and a duplicate Function node + * (incorrect, via the walk_defs fallback). push_class_body_children did not + * recognize interface_body as a class body container, so method_declaration + * children were re-walked and extracted 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); + + /* The interface itself must exist. */ + ASSERT(has_def(r, "Interface", "MarketplaceService")); + + /* Each method must be a Method, not a Function. */ + ASSERT(has_def(r, "Method", "createReservation")); + ASSERT(has_def(r, "Method", "cancelReservation")); + + /* No Function nodes should exist for interface methods. */ + 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 @@ -4766,6 +4795,7 @@ 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_class_extends_and_implements); RUN_TEST(python_class_base_extracted_bare); RUN_TEST(php_class);