/* * repro_grammar_web.c -- Per-grammar INVARIANT battery for the * WEB / MARKUP / SCHEMA language family. * * One TEST() per language so per-language RED/GREEN shows on the bug-repro * board. Each test runs a battery adapted to what the language actually models: * many web/markup/schema languages have NO functions or calls (HTML, CSS, Vue, * Svelte, Astro, GraphQL, Prisma, JSDoc, GoTemplate as a pure-template host). * The battery dimensions applied per language are documented in the per-TEST * comment. * * Languages covered (12) and the CBM_LANG_* enum each uses (all verified in * internal/cbm/cbm.h; none missing, none skipped): * HTML -> CBM_LANG_HTML * CSS -> CBM_LANG_CSS * SCSS -> CBM_LANG_SCSS * Vue -> CBM_LANG_VUE * Svelte -> CBM_LANG_SVELTE * Astro -> CBM_LANG_ASTRO * GraphQL -> CBM_LANG_GRAPHQL * Protobuf -> CBM_LANG_PROTOBUF * Thrift -> CBM_LANG_THRIFT * Prisma -> CBM_LANG_PRISMA * GoTemplate -> CBM_LANG_GOTEMPLATE * JSDoc -> CBM_LANG_JSDOC * * BATTERY DIMENSIONS * ------------------ * SINGLE-FILE (cbm_extract_file, via inv_rx + inv_count_* helpers): * 1. extract-clean : inv_extract_clean(src,lang,file) == 1 * (parser returned a result and did not set has_error). * 2. labels-valid : inv_count_bad_labels(r) == 0 * (every extracted def label is in the known label set). * 3. fqn-wellformed : inv_count_bad_fqns(r) == 0 * (no empty/".."/leading or trailing '/'/whitespace QNs). * 4. ranges-valid : inv_count_bad_ranges(r) == 0 * (start_line >= 1 and start_line <= end_line). * 5. defs-present : at least one def with the expected label is extracted. * SKIPPED for languages whose spec has no func_types, * class_types, or field_types (HTML, CSS, Vue, Svelte, * Astro, GoTemplate, JSDoc). A SKIP is annotated in the * per-TEST comment; the dimension is not asserted. * 6. calls-extracted : inv_has_call(r, callee) == 1. * Only asserted for languages that have non-empty * call_types: CSS (call_expression), SCSS (call_expression, * include_statement), GoTemplate (function_call / * template_action). Skipped for all others. * * FULL-PIPELINE (rh_index_files -> cbm_store_t*, via inv_count_* store helpers): * 7. callable-sourcing : inv_count_calls_by_source(store,project,&mod,&call). * Only asserted when dim 6 is asserted (SCSS, GoTemplate). * For SCSS: expected RED (mixin_statement is parsed as * func_types so a "Function" def is extracted, but * cbm_find_enclosing_func relies on the same node being * recognised in func_kinds_for_lang; if that mapping is * absent the call will be sourced at Module). * For GoTemplate: expected RED (no func_types so no * Function/Method node exists to source the call). * 8. no-dangling : inv_count_dangling_edges(store, project, "CALLS") == 0. * Asserted together with dim 7 when the pipeline is run. * * STRUCTURAL-ONLY LANGUAGES (dims 1-5, no call/pipeline dims): * HTML, VUE, SVELTE, ASTRO -- only module_types in spec; no defs extracted * from the host grammar node tree (embedded \n" "\n" "\n"; return structural_base_battery("Vue", src, CBM_LANG_VUE, "Hello.vue"); } /* ── Svelte ────────────────────────────────────────────────────────────────── * Idiomatic Svelte component with a \n" "\n" "\n"; return structural_base_battery("Svelte", src, CBM_LANG_SVELTE, "Counter.svelte"); } /* ── Astro ─────────────────────────────────────────────────────────────────── * Idiomatic Astro component with a frontmatter fence (--- block) and a * template body. The Astro spec has only astro_module_types = {"document"}; * the frontmatter_js_block is re-parsed as JS for import extraction but the * Astro host grammar tree yields no func/class/field defs itself. * * Dims asserted: 1-4. * Dims 5-8 SKIPPED. * Expected GREEN: dims 1-4. */ TEST(repro_grammar_web_astro) { static const char src[] = "---\n" "import Header from './Header.astro';\n" "const title = 'Hello';\n" "---\n" "\n" "\n" " {title}\n" " \n" "
\n" "

Content

\n" " \n" "\n"; return structural_base_battery("Astro", src, CBM_LANG_ASTRO, "index.astro"); } /* ── GraphQL ───────────────────────────────────────────────────────────────── * Idiomatic schema with a type (object_type_definition -> "Class") containing * fields (field_definition -> "Field"), plus an interface and a query type. * graphql_class_types covers object_type_definition so "User" maps to "Class". * graphql_field_types covers field_definition so "id"/"name" map to "Field". * No call_types in spec; no call extraction. * * Dims asserted: 1-5 ("Class" + "Field"). * Dims 6-8 SKIPPED: no call_types. * Expected GREEN: dims 1-5 (schema languages with well-formed node types tend * to extract cleanly). Dim 5 RED would indicate the type/field mapping broke. */ TEST(repro_grammar_web_graphql) { static const char src[] = "interface Node {\n" " id: ID!\n" "}\n" "\n" "type User implements Node {\n" " id: ID!\n" " name: String!\n" " email: String\n" "}\n" "\n" "type Query {\n" " user(id: ID!): User\n" "}\n"; return schema_battery("GraphQL", src, CBM_LANG_GRAPHQL, "schema.graphql", "Class", "Field"); } /* ── Protobuf ──────────────────────────────────────────────────────────────── * Idiomatic proto3 file: an import, a message (protobuf_class_types -> "Class"), * fields inside the message (protobuf_field_types -> "Field"), a service * (also in class_types -> "Class"), and an rpc declaration * (protobuf_func_types = {"rpc"} -> "Function"). * call_types = empty_types so no call extraction occurs. * * Dims asserted: 1-5 ("Function" for the rpc, "Class" for the message). * Dims 6-8 SKIPPED: no call_types in spec. * Expected GREEN: dims 1-5. Dim 5 RED would indicate the rpc->Function or * message->Class mapping is broken. */ TEST(repro_grammar_web_protobuf) { static const char src[] = "syntax = \"proto3\";\n" "\n" "import \"google/protobuf/timestamp.proto\";\n" "\n" "message User {\n" " uint64 id = 1;\n" " string name = 2;\n" " string email = 3;\n" "}\n" "\n" "service UserService {\n" " rpc GetUser (User) returns (User);\n" "}\n"; return schema_battery("Protobuf", src, CBM_LANG_PROTOBUF, "user.proto", "Function", "Class"); } /* ── Thrift ────────────────────────────────────────────────────────────────── * Idiomatic Thrift IDL: a namespace declaration (mapped via import_types), * a struct (thrift_class_types -> "Class"), a field inside it * (thrift_field_types -> "Field"), a service, and a function_definition inside * the service (thrift_func_types = {"function_definition","service_definition"} * -> "Function"). call_types = empty_types; no call extraction. * * Dims asserted: 1-5 ("Function" for the service function, "Class" for the * struct). * Dims 6-8 SKIPPED: no call_types in spec. * Expected GREEN: dims 1-5. Dim 5 RED would indicate the Thrift struct->Class * or function_definition->Function mapping is broken. */ TEST(repro_grammar_web_thrift) { static const char src[] = "namespace go users\n" "\n" "struct User {\n" " 1: required i64 id,\n" " 2: required string name,\n" " 3: optional string email,\n" "}\n" "\n" "service UserService {\n" " User GetUser(1: i64 id),\n" " void CreateUser(1: User user),\n" "}\n"; return schema_battery("Thrift", src, CBM_LANG_THRIFT, "user.thrift", "Function", "Class"); } /* ── Prisma ────────────────────────────────────────────────────────────────── * Idiomatic Prisma schema: a datasource block, a generator block, a model * (prisma_class_types = {"model_declaration",...} -> "Class"), and field * declarations inside it (prisma_field_types = {"column_declaration"} -> * "Field"). prisma_call_types = {"call_expression"} covers default-value * function calls like now() and autoincrement(); these are extracted as calls * but there is no Function node to source them from. No func_types. * * Dims asserted: 1-5 ("Class" for the model, "Field" for the fields). * Dims 6-8 SKIPPED: while call_types exists, the call_expression nodes are * default-value fragments, not first-class callable definitions; running the * pipeline would produce zero callable-sourced edges and vacuously fail dim 7. * Expected GREEN: dims 1-5. Dim 5 RED would indicate the model->Class or * column_declaration->Field mapping is broken. */ TEST(repro_grammar_web_prisma) { static const char src[] = "datasource db {\n" " provider = \"postgresql\"\n" " url = env(\"DATABASE_URL\")\n" "}\n" "\n" "generator client {\n" " provider = \"prisma-client-js\"\n" "}\n" "\n" "model User {\n" " id Int @id @default(autoincrement())\n" " name String\n" " email String @unique\n" " createdAt DateTime @default(now())\n" "}\n"; return schema_battery("Prisma", src, CBM_LANG_PRISMA, "schema.prisma", "Class", "Field"); } /* ── GoTemplate ────────────────────────────────────────────────────────────── * Idiomatic Go template: a "greeting" named template whose body calls the * built-in printf, and a "page" named template whose body invokes greeting via * a {{ template }} action. gotemplate_call_types = {"function_call", * "method_call", "template_action"}; gotemplate_module_types = {"template"}. * gotemplate_func_types = {"define_action"} so each {{ define "x" }} block mints * a "Function" def and pushes a SCOPE_FUNC for call attribution. * * Dims asserted: 1-4 + 6 + 7-8. * Dim 6 expected GREEN: call to "printf" inside the greeting define body. * Dim 7 expected GREEN: the {{ template "greeting" }} call inside the page * define body resolves to the same-file greeting Function and sources to the * page Function. (Previously the spec had no func_types -- the def-extractor * minted a "Function" for define_action but the scope-tracking func_types list * was empty, so the call mis-sourced to Module: a production sync bug, now * fixed by adding define_action to gotemplate_func_types + a compute_func_qn * case that strips the quoted template name. The fixture also moved its only * call sites from top level into define bodies.) * Dim 8 expected GREEN: no dangling CALLS endpoints. */ TEST(repro_grammar_web_gotemplate) { static const char src[] = "{{ define \"greeting\" }}\n" " {{ $msg := printf \"Welcome to %s\" .Site }}\n" "

{{ $msg }}

\n" "{{ end }}\n" "\n" "{{ define \"page\" }}\n" " {{ template \"greeting\" . }}\n" "{{ end }}\n"; if (callable_battery("GoTemplate", src, CBM_LANG_GOTEMPLATE, "index.tmpl", NULL, "printf") != 0) return 1; return pipeline_battery("GoTemplate", "index.tmpl", src); } /* ── JSDoc ─────────────────────────────────────────────────────────────────── * Idiomatic JSDoc comment block. The JSDoc spec has only * jsdoc_module_types = {"document"}; no func/class/field or call types are * declared. No defs or calls are extracted from the JSDoc grammar tree. * * Dims asserted: 1-4 (extract-clean, labels-valid, fqn-wellformed, ranges-valid). * Dims 5-8 SKIPPED: no defs, no calls, no pipeline. * Expected GREEN: dims 1-4. extract-clean RED would indicate a parser crash or * has_error set on a valid JSDoc block. */ TEST(repro_grammar_web_jsdoc) { static const char src[] = "/**\n" " * Adds two numbers together.\n" " * @param {number} a - The first operand.\n" " * @param {number} b - The second operand.\n" " * @returns {number} The sum of a and b.\n" " * @example\n" " * const result = add(1, 2); // 3\n" " */\n"; return structural_base_battery("JSDoc", src, CBM_LANG_JSDOC, "api.jsdoc"); } /* ── Suite ──────────────────────────────────────────────────────────────────── */ SUITE(repro_grammar_web) { RUN_TEST(repro_grammar_web_html); RUN_TEST(repro_grammar_web_css); RUN_TEST(repro_grammar_web_scss); RUN_TEST(repro_grammar_web_vue); RUN_TEST(repro_grammar_web_svelte); RUN_TEST(repro_grammar_web_astro); RUN_TEST(repro_grammar_web_graphql); RUN_TEST(repro_grammar_web_protobuf); RUN_TEST(repro_grammar_web_thrift); RUN_TEST(repro_grammar_web_prisma); RUN_TEST(repro_grammar_web_gotemplate); RUN_TEST(repro_grammar_web_jsdoc); }