@@ -1317,7 +1317,8 @@ static const char *annotation_route_method(const char *name) {
13171317 }
13181318 /* JAX-RS bare-verb annotations (@GET/@POST/...) — path comes from @Path. */
13191319 if (strcmp (name , "GET" ) == 0 || strcmp (name , "POST" ) == 0 || strcmp (name , "PUT" ) == 0 ||
1320- strcmp (name , "DELETE" ) == 0 || strcmp (name , "PATCH" ) == 0 ) {
1320+ strcmp (name , "DELETE" ) == 0 || strcmp (name , "PATCH" ) == 0 || strcmp (name , "HEAD" ) == 0 ||
1321+ strcmp (name , "OPTIONS" ) == 0 ) {
13211322 return name ;
13221323 }
13231324 return NULL ;
@@ -1626,35 +1627,85 @@ static bool try_route_from_annotation(CBMArena *a, TSNode annotation, const char
16261627 return true;
16271628}
16281629
1629- /* Scan the annotation nodes nested in a JVM/C# `modifiers`/`attribute_list`
1630- * wrapper (and direct children) for a route-mapping annotation. Java/Kotlin
1631- * Spring annotations (@GetMapping, @RequestMapping, ...) live here rather than
1632- * as prev-siblings, so the prev-sibling decorator walk never sees them. */
1633- static bool extract_route_from_annotations (CBMArena * a , TSNode func_node , const char * source ,
1634- const CBMLangSpec * spec , const char * * out_path ,
1635- const char * * out_method ) {
1636- TSNode modifiers = find_jvm_modifiers (func_node , spec -> language );
1630+ /* Scan ALL annotation nodes nested in a JVM/C# `modifiers`/`attribute_list`
1631+ * wrapper (and direct children), collecting route information from the whole
1632+ * set instead of stopping at the first mapping annotation. Java/Kotlin Spring
1633+ * annotations (@GetMapping, @RequestMapping, ...) live here rather than as
1634+ * prev-siblings, so the prev-sibling decorator walk never sees them.
1635+ *
1636+ * JAX-RS splits the route across two annotations: the verb comes from a bare
1637+ * @GET/@POST/... and the path from a sibling @Path("..."). Returning on the
1638+ * first mapping annotation therefore dropped every method-level @Path
1639+ * (the @GET matched first, defaulted the path to "/", and @Path was never
1640+ * read), and class-level @Path prefixes were never recognized at all. */
1641+ static void scan_route_annotations (CBMArena * a , TSNode owner , const char * source ,
1642+ const CBMLangSpec * spec , const char * * out_map_path ,
1643+ const char * * out_method , const char * * out_jax_path ) {
1644+ * out_map_path = NULL ;
1645+ * out_method = NULL ;
1646+ * out_jax_path = NULL ;
1647+
1648+ TSNode wrappers [2 ];
1649+ int wn = 0 ;
1650+ TSNode modifiers = find_jvm_modifiers (owner , spec -> language );
16371651 if (!ts_node_is_null (modifiers )) {
1638- uint32_t mc = ts_node_child_count (modifiers );
1639- for (uint32_t mi = 0 ; mi < mc ; mi ++ ) {
1640- TSNode mchild = ts_node_child (modifiers , mi );
1641- if (cbm_kind_in_set (mchild , spec -> decorator_node_types ) &&
1642- try_route_from_annotation (a , mchild , source , out_path , out_method )) {
1643- return true;
1644- }
1645- }
1652+ wrappers [wn ++ ] = modifiers ;
16461653 }
16471654 /* Direct-child annotations (some grammars attach the annotation as a child
16481655 * of the method node rather than under `modifiers`). */
1649- uint32_t cc = ts_node_child_count (func_node );
1650- for (uint32_t ci = 0 ; ci < cc ; ci ++ ) {
1651- TSNode child = ts_node_child (func_node , ci );
1652- if (cbm_kind_in_set (child , spec -> decorator_node_types ) &&
1653- try_route_from_annotation (a , child , source , out_path , out_method )) {
1654- return true;
1656+ wrappers [wn ++ ] = owner ;
1657+
1658+ for (int w = 0 ; w < wn ; w ++ ) {
1659+ uint32_t cc = ts_node_child_count (wrappers [w ]);
1660+ for (uint32_t ci = 0 ; ci < cc ; ci ++ ) {
1661+ TSNode child = ts_node_child (wrappers [w ], ci );
1662+ if (!cbm_kind_in_set (child , spec -> decorator_node_types )) {
1663+ continue ;
1664+ }
1665+ TSNode name_node = annotation_name_node (child );
1666+ if (ts_node_is_null (name_node )) {
1667+ continue ;
1668+ }
1669+ char * name = cbm_node_text (a , name_node , source );
1670+ if (!name ) {
1671+ continue ;
1672+ }
1673+ if (!* out_jax_path && strcmp (name , "Path" ) == 0 ) {
1674+ TSNode args = annotation_args_node (child );
1675+ if (!ts_node_is_null (args )) {
1676+ * out_jax_path = extract_route_path_from_args (a , args , source );
1677+ }
1678+ continue ;
1679+ }
1680+ if (!* out_method ) {
1681+ const char * method = annotation_route_method (name );
1682+ if (method ) {
1683+ * out_method = method ;
1684+ TSNode args = annotation_args_node (child );
1685+ if (!ts_node_is_null (args )) {
1686+ * out_map_path = extract_route_path_from_args (a , args , source );
1687+ }
1688+ }
1689+ }
16551690 }
16561691 }
1657- return false;
1692+ }
1693+
1694+ static bool extract_route_from_annotations (CBMArena * a , TSNode func_node , const char * source ,
1695+ const CBMLangSpec * spec , const char * * out_path ,
1696+ const char * * out_method ) {
1697+ const char * map_path = NULL ;
1698+ const char * method = NULL ;
1699+ const char * jax_path = NULL ;
1700+ scan_route_annotations (a , func_node , source , spec , & map_path , & method , & jax_path );
1701+ /* Method-level routes still require a verb/mapping annotation; a lone
1702+ * @Path (JAX-RS sub-resource locator) is not an endpoint by itself. */
1703+ if (!method ) {
1704+ return false;
1705+ }
1706+ * out_method = method ;
1707+ * out_path = map_path ? map_path : (jax_path ? jax_path : "/" );
1708+ return true;
16581709}
16591710
16601711static void extract_route_from_decorators (CBMArena * a , TSNode func_node , const char * source ,
@@ -1723,10 +1774,18 @@ static const char *join_route_paths(CBMArena *a, const char *prefix, const char
17231774
17241775static const char * spring_class_route_prefix (CBMArena * a , TSNode class_node , const char * source ,
17251776 const CBMLangSpec * spec ) {
1726- const char * prefix = NULL ;
1777+ const char * map_path = NULL ;
17271778 const char * method = NULL ;
1728- if (extract_route_from_annotations (a , class_node , source , spec , & prefix , & method )) {
1729- return prefix ;
1779+ const char * jax_path = NULL ;
1780+ scan_route_annotations (a , class_node , source , spec , & map_path , & method , & jax_path );
1781+ if (map_path ) {
1782+ return map_path ; /* @RequestMapping("/api") and friends */
1783+ }
1784+ if (jax_path ) {
1785+ return jax_path ; /* JAX-RS class-level @Path("/api") carries no verb */
1786+ }
1787+ if (method ) {
1788+ return "/" ; /* mapping annotation without a path argument */
17301789 }
17311790 return NULL ;
17321791}
@@ -5901,8 +5960,8 @@ static void extract_class_fields(CBMExtractCtx *ctx, TSNode class_node, const ch
59015960 * For the nested case, the child has no "type" field directly. Detect by
59025961 * walking named children for a variable_declaration. */
59035962 TSNode type_node = ts_node_child_by_field_name (child , TS_FIELD ("type" ));
5904- TSNode name_node =
5905- ts_node_is_null (type_node ) ? ( TSNode ){ 0 } : resolve_field_name_node (child );
5963+ TSNode empty_node = { 0 };
5964+ TSNode name_node = ts_node_is_null (type_node ) ? empty_node : resolve_field_name_node (child );
59065965
59075966 if (ts_node_is_null (type_node )) {
59085967 uint32_t cnc = ts_node_named_child_count (child );
0 commit comments