diff --git a/internal/cbm/extract_defs.c b/internal/cbm/extract_defs.c index 2e359af67..2de509833 100644 --- a/internal/cbm/extract_defs.c +++ b/internal/cbm/extract_defs.c @@ -1450,8 +1450,12 @@ static const char *find_route_path_literal(CBMArena *a, TSNode node, const char // Extract route path from decorator arguments (first string that starts with /). static const char *extract_route_path_from_args(CBMArena *a, TSNode args, const char *source) { + /* Every argument is checked. Java and Kotlin put no order on annotation + * attributes, so `path` can sit anywhere in the list. Stopping early left + * a real route unread and formed no Route node. Each argument's own + * subtree walk stays bounded by find_route_path_literal below. */ uint32_t nc = ts_node_named_child_count(args); - for (uint32_t ai = 0; ai < nc && ai < DECORATOR_SCAN_LIMIT; ai++) { + for (uint32_t ai = 0; ai < nc; ai++) { TSNode arg = ts_node_named_child(args, ai); /* Spring/Kotlin frequently uses named or array-valued annotation args: * @RequestMapping(value = ["/internal/v1"]) diff --git a/tests/test_edge_types_probe.c b/tests/test_edge_types_probe.c index f71091ae4..84d7713ee 100644 --- a/tests/test_edge_types_probe.c +++ b/tests/test_edge_types_probe.c @@ -416,6 +416,30 @@ TEST(handles_spring_java) { PASS(); } +/* Spring (Java) — the path attribute may sit anywhere in the annotation. + * Java puts no order on annotation attributes, so `path` after `name`, + * `produces` and `consumes` is ordinary source. The argument scan stopped + * after the third attribute, so the path was never read and no Route node + * formed. A HANDLES count alone cannot catch that, because the class-level + * @RequestMapping still produces one route on its own. */ +TEST(handles_spring_java_path_attribute_fourth) { + static const char *routes[] = {"/api/orders", NULL}; + static const EtFile f[] = { + {"OrderController.java", + "package com.example;\n\n" + "import org.springframework.web.bind.annotation.RequestMapping;\n" + "import org.springframework.web.bind.annotation.GetMapping;\n\n" + "@RequestMapping(\"/api\")\npublic class OrderController {\n" + " @GetMapping(name = \"listOrders\",\n" + " produces = \"application/json\",\n" + " consumes = \"application/json\",\n" + " path = \"/orders\")\n" + " public String listOrders() {\n" + " return \"orders\";\n }\n}\n"}}; + ASSERT_TRUE(et_routes_exact(f, 1, routes)); + PASS(); +} + /* Spring (Kotlin) — same prefix contract, including Kotlin's named array form * for class-level RequestMapping values. */ TEST(handles_spring_kotlin) { @@ -1578,6 +1602,7 @@ SUITE(edge_types_probe) { RUN_TEST(handles_fastify_js); RUN_TEST(handles_gin_go); RUN_TEST(handles_spring_java); + RUN_TEST(handles_spring_java_path_attribute_fourth); RUN_TEST(handles_spring_kotlin); RUN_TEST(handles_jaxrs_java); RUN_TEST(handles_aspnet_csharp);