190 lines
8.2 KiB
C
190 lines
8.2 KiB
C
/*
|
|
* repro_issue382.c — Reproduce-first case for OPEN bug #382.
|
|
*
|
|
* Bug #382: "Java: @Annotation, signatures, and all AST properties missing
|
|
* from graph nodes"
|
|
*
|
|
* Root cause (confirmed by maintainer + reporter re-open):
|
|
* extract_decorators() in internal/cbm/extract_defs.c first scans
|
|
* ts_node_prev_sibling() looking for nodes of type "annotation" /
|
|
* "marker_annotation". In the Java AST emitted by tree-sitter-java, those
|
|
* nodes are NOT prev-siblings of either the class_declaration or the
|
|
* method_declaration — they live INSIDE the node's own `modifiers` child:
|
|
*
|
|
* class_declaration
|
|
* modifiers
|
|
* marker_annotation <- @Entity
|
|
* marker_annotation <- @RestController
|
|
* type_identifier: "User"
|
|
* class_body
|
|
* method_declaration
|
|
* modifiers
|
|
* marker_annotation <- @Override
|
|
* annotation <- @GetMapping("/users")
|
|
* type_identifier: "String"
|
|
* ...
|
|
*
|
|
* The code does have a fallback that calls find_jvm_modifiers() to search
|
|
* the `modifiers` child when prev-sibling count == 0, which covers the
|
|
* simple @GetMapping-on-method case already tested in test_extraction.c
|
|
* (extract_java_method_annotations_issue382, which passes green on v0.7.0).
|
|
*
|
|
* What is NOT covered by that existing test:
|
|
* a) CLASS-LEVEL annotations (@Entity, @RestController) on the class node
|
|
* itself — the existing test only extracts Method nodes; it never
|
|
* checks the Class node's .decorators.
|
|
* b) marker_annotation (no-arg form, e.g. @Override, @Entity) on methods
|
|
* — the existing test uses @GetMapping("/x") which is a full
|
|
* `annotation` node with arguments and does a substring match against
|
|
* the whole text "@GetMapping(\"/x\")". marker_annotations have a
|
|
* different tree-sitter node type and are historically mis-counted.
|
|
* c) Multiple stacked annotations on a single method/class.
|
|
*
|
|
* These cases regress when the fallback path is absent or broken (e.g. the
|
|
* fix only wired the method path, not the class path, or it works for
|
|
* `annotation` nodes but not `marker_annotation`).
|
|
*
|
|
* Expected (correct) behaviour:
|
|
* - The Class def for "User" carries decorators:
|
|
* decorators[0] contains "Entity"
|
|
* decorators[1] contains "RestController" (or vice-versa)
|
|
* - The Method def for "getUser" carries decorators:
|
|
* at least one entry contains "Override"
|
|
* at least one entry contains "GetMapping"
|
|
* - method "getUser" has a non-empty signature.
|
|
*
|
|
* Actual (buggy) behaviour:
|
|
* - Class def for "User": decorators == NULL (no annotations extracted)
|
|
* - Method def for "getUser": marker_annotation @Override is dropped;
|
|
* decorators may be NULL or miss @Override.
|
|
* → assertions below are RED on current code if either path is broken.
|
|
*
|
|
* Why this is STRONGER than the existing test_extraction.c #382 reference:
|
|
* 1. It asserts decorators on the CLASS node — never checked before.
|
|
* 2. It specifically asserts that a marker_annotation (@Override, @Entity)
|
|
* is captured, not just a full annotation with arguments.
|
|
* 3. It asserts BOTH annotations on a multi-annotated class, exercising the
|
|
* count loop that must find > 1 entry.
|
|
* 4. It uses ASSERT_NOT_NULL(m->decorators) before touching decorators[i],
|
|
* so a NULL decorators field fails loudly rather than crashing/skipping.
|
|
*/
|
|
|
|
#include "test_framework.h"
|
|
#include "cbm.h"
|
|
|
|
/* Convenience: extract one file, return result (caller frees). */
|
|
static CBMFileResult *rx(const char *src, CBMLanguage lang,
|
|
const char *proj, const char *path) {
|
|
return cbm_extract_file(src, (int)strlen(src), lang, proj, path,
|
|
0, NULL, NULL);
|
|
}
|
|
|
|
/* Return the first definition whose label AND name both match (either may be
|
|
* NULL to wildcard). Mirrors the helper in repro_extraction.c. */
|
|
static CBMDefinition *find_def(CBMFileResult *r, const char *label,
|
|
const char *name) {
|
|
for (int i = 0; i < r->defs.count; i++) {
|
|
CBMDefinition *d = &r->defs.items[i];
|
|
if (label && (!d->label || strcmp(d->label, label) != 0))
|
|
continue;
|
|
if (name && (!d->name || strcmp(d->name, name) != 0))
|
|
continue;
|
|
return d;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Return 1 if any entry in the NULL-terminated decorators array contains
|
|
* needle as a substring. */
|
|
static int decorators_contain(const CBMDefinition *d, const char *needle) {
|
|
if (!d || !d->decorators)
|
|
return 0;
|
|
for (int i = 0; d->decorators[i]; i++) {
|
|
if (strstr(d->decorators[i], needle))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ───────────────────────────────────────────────────────────────────
|
|
* repro_issue382_java_annotations_on_nodes
|
|
*
|
|
* Asserts that BOTH the Class node AND the Method node produced by
|
|
* cbm_extract_file carry their Java annotations in .decorators:
|
|
*
|
|
* @Entity
|
|
* @RestController
|
|
* public class User {
|
|
* @Override
|
|
* @GetMapping("/users")
|
|
* public String getUser(String id) { return id; }
|
|
* }
|
|
*
|
|
* RED if:
|
|
* • The Class "User" has decorators == NULL (class-level annots dropped)
|
|
* • The Class "User" decorators do not contain "Entity"
|
|
* • The Class "User" decorators do not contain "RestController"
|
|
* • The Method "getUser" has decorators == NULL (method-level annots dropped)
|
|
* • The Method "getUser" decorators do not contain "Override" ← marker_annotation
|
|
* • The Method "getUser" decorators do not contain "GetMapping" ← annotation
|
|
* • The Method "getUser" has NULL or empty signature
|
|
* ─────────────────────────────────────────────────────────────────── */
|
|
TEST(repro_issue382_java_annotations_on_nodes) {
|
|
CBMFileResult *r = rx(
|
|
"@Entity\n"
|
|
"@RestController\n"
|
|
"public class User {\n"
|
|
" @Override\n"
|
|
" @GetMapping(\"/users\")\n"
|
|
" public String getUser(String id) { return id; }\n"
|
|
"}\n",
|
|
CBM_LANG_JAVA, "t", "User.java");
|
|
|
|
ASSERT_NOT_NULL(r);
|
|
ASSERT_FALSE(r->has_error);
|
|
|
|
/* ── Class node: two class-level marker_annotations ── */
|
|
CBMDefinition *cls = find_def(r, "Class", "User");
|
|
ASSERT_NOT_NULL(cls);
|
|
|
|
/* The Class def MUST carry a non-NULL decorators array.
|
|
* RED if class-level annotations are silently dropped. */
|
|
ASSERT_NOT_NULL(cls->decorators);
|
|
|
|
/* @Entity (marker_annotation) must be present on the Class. */
|
|
ASSERT_TRUE(decorators_contain(cls, "Entity"));
|
|
|
|
/* @RestController (marker_annotation) must also be present. */
|
|
ASSERT_TRUE(decorators_contain(cls, "RestController"));
|
|
|
|
/* ── Method node: one marker_annotation + one annotation ── */
|
|
CBMDefinition *method = find_def(r, "Method", "getUser");
|
|
ASSERT_NOT_NULL(method);
|
|
|
|
/* Method decorators must be non-NULL. */
|
|
ASSERT_NOT_NULL(method->decorators);
|
|
|
|
/* @Override is a marker_annotation (no argument list) — historically
|
|
* the most likely to be missed if the extractor only handles the
|
|
* `annotation` node type but not `marker_annotation`. */
|
|
ASSERT_TRUE(decorators_contain(method, "Override"));
|
|
|
|
/* @GetMapping("/users") is a full annotation (with argument) — this is
|
|
* what the existing test_extraction.c case checks; include it here too
|
|
* so we catch any regression. */
|
|
ASSERT_TRUE(decorators_contain(method, "GetMapping"));
|
|
|
|
/* Signature must be extracted: Java method_declaration has a `parameters`
|
|
* field that the extractor reads into def.signature. */
|
|
ASSERT_NOT_NULL(method->signature);
|
|
ASSERT_TRUE(method->signature[0] != '\0');
|
|
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── Suite ──────────────────────────────────────────────────────── */
|
|
SUITE(repro_issue382) {
|
|
RUN_TEST(repro_issue382_java_annotations_on_nodes);
|
|
}
|