Comments are NAMED nodes in tree-sitter, so a comment interleaved in a * decorator run would end the walk and silently drop every decorator above it: * * @Post('login') <-- lost * @HttpCode(HttpStatus.OK) <-- lost * // why this route is throttled <-- walk stopped here * @Throttle({ ... }) <-- kept * async login(...) * * Documenting a
| 1918 | * comments as transparent — like the anonymous tokens already skipped below. |
| 1919 | * (is_comment_node() is defined above, near the docstring helpers.) */ |
| 1920 | static const char **extract_decorators(CBMArena *a, TSNode node, const char *source, |
| 1921 | CBMLanguage lang, const CBMLangSpec *spec) { |
| 1922 | if (!spec->decorator_node_types || !spec->decorator_node_types[0]) { |
| 1923 | return NULL; |
| 1924 | } |
| 1925 | |
| 1926 | int count = 0; |
| 1927 | TSNode prev = ts_node_prev_sibling(node); |
| 1928 | while (!ts_node_is_null(prev)) { |
| 1929 | if (cbm_kind_in_set(prev, spec->decorator_node_types)) { |
| 1930 | count++; |
| 1931 | } else if (ts_node_is_named(prev) && !is_comment_node(ts_node_type(prev))) { |
| 1932 | /* A real preceding construct ends the decorator run. Anonymous |
| 1933 | * tokens (e.g. TS `export` between `@Decorator` and the |
| 1934 | * `class_declaration`) and comments are skipped so the decorator |
| 1935 | * is still seen. */ |
| 1936 | break; |
| 1937 | } |
| 1938 | prev = ts_node_prev_sibling(prev); |
| 1939 | } |
| 1940 | |
| 1941 | TSNode modifiers = {0}; |
| 1942 | int mod_count = 0; |
| 1943 | int child_count = 0; |
| 1944 | if (count == 0) { |
| 1945 | modifiers = find_jvm_modifiers(node, lang); |
| 1946 | if (!ts_node_is_null(modifiers)) { |
| 1947 | mod_count = count_modifier_annotations(modifiers, spec); |
| 1948 | } |
| 1949 | /* Languages like Scala attach the annotation directly as a child of the |
| 1950 | * definition node (no wrapper, no prev-sibling). */ |
| 1951 | if (mod_count == 0) { |
| 1952 | child_count = count_child_decorators(node, spec); |
| 1953 | } |
| 1954 | } |
| 1955 | |
| 1956 | int total = count + mod_count + child_count; |
| 1957 | if (total == 0) { |
| 1958 | return NULL; |
| 1959 | } |
| 1960 | |
| 1961 | const char **result = |
| 1962 | (const char **)cbm_arena_alloc(a, sizeof(const char *) * (total + NULL_TERM)); |
| 1963 | if (!result) { |
| 1964 | return NULL; |
| 1965 | } |
| 1966 | |
| 1967 | int idx = 0; |
| 1968 | prev = ts_node_prev_sibling(node); |
| 1969 | while (!ts_node_is_null(prev) && idx < count) { |
| 1970 | if (cbm_kind_in_set(prev, spec->decorator_node_types)) { |
| 1971 | result[idx++] = cbm_node_text(a, prev, source); |
| 1972 | } else if (ts_node_is_named(prev) && !is_comment_node(ts_node_type(prev))) { |
| 1973 | break; |
| 1974 | } |
| 1975 | prev = ts_node_prev_sibling(prev); |
| 1976 | } |
| 1977 | if (!ts_node_is_null(modifiers)) { |
no test coverage detected