/*
* 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);
}