mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Fix CI lint: drop debug scaffolding, make lisp import walker iterative, apply clang-format
Remove leftover getenv(CBM_DBG_*) debug fprintf blocks in extract_calls.c/pass_calls.c (cppcheck always-true). Convert parse_lisp_imports to a stack-based walk (no recursion → no NOLINT, avoids deep-nesting overflow). Apply Homebrew clang-format to all touched sources. Local suite 5562/0, lint-ci clean.
This commit is contained in:
1 parent
54108092d8
commit
6f3b24df4f
25 files changed
+5606
-5048
No files matched your search
@@ -885,9 +885,8 @@ static bool is_url_or_topic_keyword(const char *key) {
|
||||
// arg (e.g. Go `SendMessageInput{QueueUrl: ...}`, `PublishInput{TopicArn: ...}`).
|
||||
// Case-insensitive so QueueUrl/QueueURL/queue_url all match.
|
||||
static bool is_queue_topic_field(const char *key) {
|
||||
static const char *fields[] = {"QueueUrl", "QueueURL", "TopicArn", "TopicARN",
|
||||
"QueueName", "TopicName", "QueueArn", "QueueARN",
|
||||
"Destination", NULL};
|
||||
static const char *fields[] = {"QueueUrl", "QueueURL", "TopicArn", "TopicARN", "QueueName",
|
||||
"TopicName", "QueueArn", "QueueARN", "Destination", NULL};
|
||||
if (!key || !key[0]) {
|
||||
return false;
|
||||
}
|
||||
@@ -1080,8 +1079,7 @@ static const char *extract_handler_arg(CBMExtractCtx *ctx, TSNode args) {
|
||||
for (uint32_t ai = HANDLER_START_IDX; ai < nc && ai < MAX_HANDLER_SCAN; ai++) {
|
||||
TSNode arg2 = ts_node_named_child(args, ai);
|
||||
/* PHP wraps each argument in an `argument` node — unwrap to the value. */
|
||||
if (strcmp(ts_node_type(arg2), "argument") == 0 &&
|
||||
ts_node_named_child_count(arg2) > 0) {
|
||||
if (strcmp(ts_node_type(arg2), "argument") == 0 && ts_node_named_child_count(arg2) > 0) {
|
||||
arg2 = ts_node_named_child(arg2, 0);
|
||||
}
|
||||
const char *ak2 = ts_node_type(arg2);
|
||||
@@ -1093,8 +1091,8 @@ static const char *extract_handler_arg(CBMExtractCtx *ctx, TSNode args) {
|
||||
return cbm_node_text(ctx->arena, arg2, ctx->source);
|
||||
}
|
||||
if (is_string_like(ak2)) {
|
||||
const char *h = normalize_string_handler(ctx->arena,
|
||||
cbm_node_text(ctx->arena, arg2, ctx->source));
|
||||
const char *h =
|
||||
normalize_string_handler(ctx->arena, cbm_node_text(ctx->arena, arg2, ctx->source));
|
||||
if (h && h[0]) {
|
||||
return h;
|
||||
}
|
||||
@@ -1138,11 +1136,6 @@ void handle_calls(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, Walk
|
||||
call.start_line = (int)ts_node_start_point(node).row + TS_LINE_OFFSET;
|
||||
|
||||
TSNode args = ts_node_child_by_field_name(node, TS_FIELD("arguments"));
|
||||
if (getenv("CBM_DBG_ARGS") && callee && (strstr(callee,"get")||strstr(callee,"Get")||strstr(callee,"client")||strstr(callee,"Route"))) {
|
||||
fprintf(stderr, "[DBGARGS] callee=%s node=%s args_null=%d", callee, ts_node_type(node), ts_node_is_null(args));
|
||||
if (!ts_node_is_null(args)) { uint32_t n=ts_node_named_child_count(args); fprintf(stderr, " nargs=%u:", n); for(uint32_t i=0;i<n&&i<4;i++) fprintf(stderr," [%s]",ts_node_type(ts_node_named_child(args,i))); }
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
if (!ts_node_is_null(args)) {
|
||||
call.first_string_arg = extract_url_or_topic_arg(ctx, args);
|
||||
if (call.first_string_arg && call.first_string_arg[0] == '/') {
|
||||
|
||||
+16
-21
@@ -1607,8 +1607,8 @@ static const char **extract_csharp_base_list(CBMArena *a, TSNode node, const cha
|
||||
}
|
||||
|
||||
// Append a base name (generic args stripped) to out[] if non-empty.
|
||||
static void push_base_text(CBMArena *a, TSNode n, const char *source, const char **out,
|
||||
int out_cap, int *count) {
|
||||
static void push_base_text(CBMArena *a, TSNode n, const char *source, const char **out, int out_cap,
|
||||
int *count) {
|
||||
if (*count >= out_cap) {
|
||||
return;
|
||||
}
|
||||
@@ -2977,8 +2977,7 @@ static void extract_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
|
||||
if (!ts_node_is_null(inner) && strcmp(ts_node_type(inner), "binary_expression") == 0 &&
|
||||
ts_node_named_child_count(inner) > 0) {
|
||||
name_node = ts_node_named_child(inner, 0); /* LHS of `<:` */
|
||||
} else if (!ts_node_is_null(inner) &&
|
||||
strcmp(ts_node_type(inner), "identifier") == 0) {
|
||||
} else if (!ts_node_is_null(inner) && strcmp(ts_node_type(inner), "identifier") == 0) {
|
||||
name_node = inner;
|
||||
} else {
|
||||
name_node = cbm_find_child_by_kind(th, "identifier");
|
||||
@@ -3078,8 +3077,8 @@ static void extract_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
|
||||
}
|
||||
break;
|
||||
case CBM_LANG_GLEAM: // type_definition > type_name > type_identifier
|
||||
name_node = find_first_descendant_by_kind(node, "type_identifier",
|
||||
CBM_DESCENDANT_MAX_DEPTH);
|
||||
name_node =
|
||||
find_first_descendant_by_kind(node, "type_identifier", CBM_DESCENDANT_MAX_DEPTH);
|
||||
break;
|
||||
case CBM_LANG_RESCRIPT: { // type_declaration > type_binding(name=type_identifier)
|
||||
TSNode binding = cbm_find_child_by_kind(node, "type_binding");
|
||||
@@ -3092,8 +3091,7 @@ static void extract_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
|
||||
break;
|
||||
}
|
||||
case CBM_LANG_FSHARP: { // type_definition > *_type_defn > type_name > identifier
|
||||
TSNode tn = find_first_descendant_by_kind(node, "type_name",
|
||||
CBM_DESCENDANT_MAX_DEPTH);
|
||||
TSNode tn = find_first_descendant_by_kind(node, "type_name", CBM_DESCENDANT_MAX_DEPTH);
|
||||
if (!ts_node_is_null(tn)) {
|
||||
name_node = ts_node_child_by_field_name(tn, TS_FIELD("type_name"));
|
||||
if (ts_node_is_null(name_node)) {
|
||||
@@ -3130,8 +3128,7 @@ static void extract_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
|
||||
}
|
||||
case CBM_LANG_PASCAL: { // declClass is nested; name lives on the parent declType
|
||||
TSNode parent = ts_node_parent(node);
|
||||
if (!ts_node_is_null(parent) &&
|
||||
strcmp(ts_node_type(parent), "declType") == 0) {
|
||||
if (!ts_node_is_null(parent) && strcmp(ts_node_type(parent), "declType") == 0) {
|
||||
name_node = ts_node_child_by_field_name(parent, TS_FIELD("name"));
|
||||
}
|
||||
break;
|
||||
@@ -3175,9 +3172,8 @@ static void extract_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
|
||||
// registry only indexes Function/Method/Class/Interface labels), letting
|
||||
// `inherit Base` resolve into an INHERITS edge.
|
||||
if (ctx->language == CBM_LANG_FSHARP && strcmp(label, "Type") == 0) {
|
||||
if (!ts_node_is_null(
|
||||
find_first_descendant_by_kind(node, "primary_constr_args",
|
||||
CBM_DESCENDANT_MAX_DEPTH)) ||
|
||||
if (!ts_node_is_null(find_first_descendant_by_kind(node, "primary_constr_args",
|
||||
CBM_DESCENDANT_MAX_DEPTH)) ||
|
||||
!ts_node_is_null(find_first_descendant_by_kind(node, "class_inherits_decl",
|
||||
CBM_DESCENDANT_MAX_DEPTH))) {
|
||||
label = "Class";
|
||||
@@ -5182,8 +5178,9 @@ static void extract_lisp_def(CBMExtractCtx *ctx, TSNode node) {
|
||||
* `ERROR` node, so the outer class is never recognized as a `class_declaration`
|
||||
* and disappears from the graph:
|
||||
*
|
||||
* class MyClass { companion object : Factory<MyClass>() { ... } } // anon companion w/ delegation
|
||||
* class Tree { inner class Node : BaseNode() { ... } } // inner + delegation
|
||||
* class MyClass { companion object : Factory<MyClass>() { ... } } // anon companion w/
|
||||
* delegation class Tree { inner class Node : BaseNode() { ... } } // inner +
|
||||
* delegation
|
||||
*
|
||||
* Inside the ERROR node the tokens are still present as a flat child list:
|
||||
* `class`/`object` keyword token → simple_identifier/type_identifier (name)
|
||||
@@ -5243,16 +5240,14 @@ static void recover_kotlin_error_classes(CBMExtractCtx *ctx, TSNode err_node) {
|
||||
/* delegation_specifier → user_type (directly or under
|
||||
* constructor_invocation) → type_identifier; strip generic args. */
|
||||
TSNode ut = ts_node_named_child(sib, 0);
|
||||
if (!ts_node_is_null(ut) &&
|
||||
strcmp(ts_node_type(ut), "constructor_invocation") == 0) {
|
||||
if (!ts_node_is_null(ut) && strcmp(ts_node_type(ut), "constructor_invocation") == 0) {
|
||||
ut = ts_node_named_child(ut, 0);
|
||||
}
|
||||
if (ts_node_is_null(ut)) {
|
||||
continue;
|
||||
}
|
||||
TSNode ti = ut;
|
||||
if (strcmp(ts_node_type(ut), "user_type") == 0 &&
|
||||
ts_node_named_child_count(ut) > 0) {
|
||||
if (strcmp(ts_node_type(ut), "user_type") == 0 && ts_node_named_child_count(ut) > 0) {
|
||||
ti = ts_node_named_child(ut, 0);
|
||||
}
|
||||
push_base_text(a, ti, ctx->source, bases, MAX_BASES_MINUS_1, &bcount);
|
||||
@@ -5268,8 +5263,8 @@ static void recover_kotlin_error_classes(CBMExtractCtx *ctx, TSNode err_node) {
|
||||
def.end_line = ts_node_end_point(err_node).row + TS_LINE_OFFSET;
|
||||
def.is_exported = cbm_is_exported(name, ctx->language);
|
||||
if (bcount > 0) {
|
||||
const char **result = (const char **)cbm_arena_alloc(
|
||||
a, (size_t)(bcount + NULL_TERM) * sizeof(const char *));
|
||||
const char **result = (const char **)cbm_arena_alloc(a, (size_t)(bcount + NULL_TERM) *
|
||||
sizeof(const char *));
|
||||
if (result) {
|
||||
for (int k = 0; k < bcount; k++) {
|
||||
result[k] = bases[k];
|
||||
|
||||
@@ -1465,7 +1465,6 @@ static void parse_embedded_imports(CBMExtractCtx *ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Namespace / package declaration capture ---
|
||||
// Java/Kotlin/C#/PHP put the file's symbols inside a namespace/package whose
|
||||
// name is NOT reflected in the path-based QN scheme. Capturing it lets the
|
||||
@@ -1699,8 +1698,7 @@ static void lisp_push_clause_modules(CBMExtractCtx *ctx, TSNode clause) {
|
||||
TSNode item = ts_node_named_child(clause, j);
|
||||
const char *ik = ts_node_type(item);
|
||||
/* `[app.util :as u]` — the module is the vector's first symbol. */
|
||||
if (strcmp(ik, "vec_lit") == 0 || strcmp(ik, "list_lit") == 0 ||
|
||||
strcmp(ik, "list") == 0) {
|
||||
if (strcmp(ik, "vec_lit") == 0 || strcmp(ik, "list_lit") == 0 || strcmp(ik, "list") == 0) {
|
||||
uint32_t vc = ts_node_named_child_count(item);
|
||||
if (vc > 0) {
|
||||
lisp_push_module(ctx, ts_node_named_child(item, 0));
|
||||
@@ -1807,22 +1805,23 @@ static void lisp_process_list(CBMExtractCtx *ctx, TSNode node) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Recursively walk lisp lists. Fennel binds requires inside other forms
|
||||
/* Iteratively walk lisp lists. Fennel binds requires inside other forms
|
||||
* (`(local util (require :util))`), so we must descend into every list, not
|
||||
* just root children. */
|
||||
static void lisp_walk(CBMExtractCtx *ctx, TSNode node) { // NOLINT(misc-no-recursion)
|
||||
const char *nt = ts_node_type(node);
|
||||
if (strcmp(nt, "list") == 0 || strcmp(nt, "list_lit") == 0) {
|
||||
lisp_process_list(ctx, node);
|
||||
}
|
||||
uint32_t cc = ts_node_named_child_count(node);
|
||||
for (uint32_t i = 0; i < cc; i++) {
|
||||
lisp_walk(ctx, ts_node_named_child(node, i));
|
||||
}
|
||||
}
|
||||
|
||||
* just root children. Stack-based (not recursive) to avoid deep-nesting stack
|
||||
* overflow, matching the other walkers in this file. */
|
||||
static void parse_lisp_imports(CBMExtractCtx *ctx) {
|
||||
lisp_walk(ctx, ctx->root);
|
||||
CBMArena *a = ctx->arena;
|
||||
TSNodeStack stack;
|
||||
ts_nstack_init(&stack, a, CBM_SZ_512);
|
||||
ts_nstack_push(&stack, a, ctx->root);
|
||||
while (stack.count > 0) {
|
||||
TSNode node = ts_nstack_pop(&stack);
|
||||
const char *nt = ts_node_type(node);
|
||||
if (strcmp(nt, "list") == 0 || strcmp(nt, "list_lit") == 0) {
|
||||
lisp_process_list(ctx, node);
|
||||
}
|
||||
ts_nstack_push_children(&stack, a, node);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Starlark imports ---
|
||||
@@ -2089,11 +2088,10 @@ static void push_path_import(CBMExtractCtx *ctx, TSNode node) {
|
||||
// back to text that still contains a directive keyword.
|
||||
static void push_string_descendant_import(CBMExtractCtx *ctx, TSNode node) {
|
||||
TSNode hit = node;
|
||||
static const char *content_kinds[] = {"string_content", "str_literal",
|
||||
"slStringLiteralPart", "include_path",
|
||||
"attribute_value", "path_fragment",
|
||||
"string_fragment", "literal_content",
|
||||
NULL};
|
||||
static const char *content_kinds[] = {
|
||||
"string_content", "str_literal", "slStringLiteralPart",
|
||||
"include_path", "attribute_value", "path_fragment",
|
||||
"string_fragment", "literal_content", NULL};
|
||||
for (const char **k = content_kinds; *k; k++) {
|
||||
if (find_first_descendant_of(node, *k, &hit)) {
|
||||
push_path_import(ctx, hit);
|
||||
@@ -2101,7 +2099,7 @@ static void push_string_descendant_import(CBMExtractCtx *ctx, TSNode node) {
|
||||
}
|
||||
}
|
||||
/* Quoted string nodes (Just/Pkl/Jsonnet variants): strip the quotes. */
|
||||
static const char *string_kinds[] = {"string", "string_value", "string_literal",
|
||||
static const char *string_kinds[] = {"string", "string_value", "string_literal",
|
||||
"static_string", "stringConstant", NULL};
|
||||
for (const char **k = string_kinds; *k; k++) {
|
||||
if (find_first_descendant_of(node, *k, &hit)) {
|
||||
@@ -2271,9 +2269,8 @@ static void parse_jsonnet_imports(CBMExtractCtx *ctx) {
|
||||
while (stack.count > 0) {
|
||||
TSNode node = ts_nstack_pop(&stack);
|
||||
const char *k = ts_node_type(node);
|
||||
if (ts_node_is_named(node) &&
|
||||
(strcmp(k, "import") == 0 || strcmp(k, "importstr") == 0 ||
|
||||
strcmp(k, "importbin") == 0)) {
|
||||
if (ts_node_is_named(node) && (strcmp(k, "import") == 0 || strcmp(k, "importstr") == 0 ||
|
||||
strcmp(k, "importbin") == 0)) {
|
||||
push_string_descendant_import(ctx, node);
|
||||
}
|
||||
ts_nstack_push_children(&stack, ctx->arena, node);
|
||||
|
||||
@@ -204,8 +204,8 @@ static TSNode resolve_write_lhs_node(TSNode node) {
|
||||
return left;
|
||||
}
|
||||
const char *nk = ts_node_type(node);
|
||||
if (strcmp(nk, "postfix_unary_expression") == 0 ||
|
||||
strcmp(nk, "prefix_unary_expression") == 0 || strcmp(nk, "update_expression") == 0) {
|
||||
if (strcmp(nk, "postfix_unary_expression") == 0 || strcmp(nk, "prefix_unary_expression") == 0 ||
|
||||
strcmp(nk, "update_expression") == 0) {
|
||||
// Only ++/-- mutate their operand. Other unary postfix/prefix forms
|
||||
// (C# null-forgiving `x!`, address-of `&x`, deref `*x`, logical `!x`)
|
||||
// READ the operand — never treat them as writes.
|
||||
@@ -230,9 +230,10 @@ static TSNode resolve_write_lhs_node(TSNode node) {
|
||||
TSNode c = ts_node_named_child(node, i);
|
||||
const char *ck = ts_node_type(c);
|
||||
if (strcmp(ck, "identifier") == 0 || strcmp(ck, "simple_identifier") == 0 ||
|
||||
strcmp(ck, "member_access_expression") == 0 || strcmp(ck, "field_expression") == 0 ||
|
||||
strcmp(ck, "field_access") == 0 || strcmp(ck, "selector_expression") == 0 ||
|
||||
strcmp(ck, "subscript_expression") == 0 || strcmp(ck, "index_expression") == 0) {
|
||||
strcmp(ck, "member_access_expression") == 0 ||
|
||||
strcmp(ck, "field_expression") == 0 || strcmp(ck, "field_access") == 0 ||
|
||||
strcmp(ck, "selector_expression") == 0 || strcmp(ck, "subscript_expression") == 0 ||
|
||||
strcmp(ck, "index_expression") == 0) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -775,7 +775,8 @@ static bool is_export_of_declaration(TSNode node) {
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
const char *ck = ts_node_type(ts_node_child(node, i));
|
||||
if (strcmp(ck, "function_declaration") == 0 || strcmp(ck, "class_declaration") == 0 ||
|
||||
strcmp(ck, "lexical_declaration") == 0 || strcmp(ck, "abstract_class_declaration") == 0 ||
|
||||
strcmp(ck, "lexical_declaration") == 0 ||
|
||||
strcmp(ck, "abstract_class_declaration") == 0 ||
|
||||
strcmp(ck, "interface_declaration") == 0 || strcmp(ck, "enum_declaration") == 0 ||
|
||||
strcmp(ck, "type_alias_declaration") == 0 || strcmp(ck, "variable_declaration") == 0 ||
|
||||
strcmp(ck, "generator_function_declaration") == 0) {
|
||||
|
||||
@@ -131,10 +131,10 @@ static const char *java_keywords[] = {
|
||||
* (`data`, `open`, `sealed`, `suspend`, …) are context-sensitive and usable as
|
||||
* identifiers, so they are intentionally omitted. */
|
||||
static const char *kotlin_keywords[] = {
|
||||
"as", "break", "class", "continue", "do", "else", "false", "for",
|
||||
"fun", "if", "in", "interface", "is", "null", "object", "package",
|
||||
"return", "super", "this", "throw", "true", "try", "typealias", "typeof",
|
||||
"val", "var", "when", "while", NULL};
|
||||
"as", "break", "class", "continue", "do", "else", "false", "for",
|
||||
"fun", "if", "in", "interface", "is", "null", "object", "package",
|
||||
"return", "super", "this", "throw", "true", "try", "typealias", "typeof",
|
||||
"val", "var", "when", "while", NULL};
|
||||
|
||||
static const char *generic_keywords[] = {
|
||||
"true", "false", "null", "nil", "None", "undefined", "void", "if",
|
||||
|
||||
@@ -363,8 +363,7 @@ static const char *cs_class_types[] = {"class_declaration",
|
||||
"type_declaration",
|
||||
NULL};
|
||||
static const char *cs_module_types[] = {"compilation_unit", NULL};
|
||||
static const char *cs_call_types[] = {"invocation_expression", "object_creation_expression",
|
||||
NULL};
|
||||
static const char *cs_call_types[] = {"invocation_expression", "object_creation_expression", NULL};
|
||||
static const char *cs_import_types[] = {"using_directive", "namespace_use_declaration",
|
||||
"using_statement", "namespace_declaration", NULL};
|
||||
static const char *cs_branch_types[] = {"if_statement", "for_statement", "foreach_statement",
|
||||
@@ -389,10 +388,9 @@ static const char *php_class_types[] = {"trait_declaration", "enum_declaration",
|
||||
static const char *php_import_types[] = {"extends", "include", "namespace_use_declaration",
|
||||
"require", "use_declaration", NULL};
|
||||
static const char *php_module_types[] = {"program", NULL};
|
||||
static const char *php_call_types[] = {"member_call_expression", "scoped_call_expression",
|
||||
"function_call_expression",
|
||||
"object_creation_expression",
|
||||
"nullsafe_member_call_expression", NULL};
|
||||
static const char *php_call_types[] = {
|
||||
"member_call_expression", "scoped_call_expression", "function_call_expression",
|
||||
"object_creation_expression", "nullsafe_member_call_expression", NULL};
|
||||
static const char *php_branch_types[] = {"if_statement", "for_statement", "foreach_statement",
|
||||
"while_statement", "switch_statement", "case_statement",
|
||||
"try_statement", "catch_clause", NULL};
|
||||
@@ -418,8 +416,9 @@ static const char *scala_class_types[] = {"class_definition", "object_definition
|
||||
"trait_definition", "enum_definition",
|
||||
"type_definition", NULL};
|
||||
static const char *scala_module_types[] = {"compilation_unit", NULL};
|
||||
static const char *scala_call_types[] = {"call_expression", "generic_function", "field_expression",
|
||||
"infix_expression", "instance_expression", NULL};
|
||||
static const char *scala_call_types[] = {"call_expression", "generic_function",
|
||||
"field_expression", "infix_expression",
|
||||
"instance_expression", NULL};
|
||||
static const char *scala_import_types[] = {"import_declaration", "extends", "import",
|
||||
"using_directive", NULL};
|
||||
static const char *scala_branch_types[] = {
|
||||
@@ -1528,8 +1527,8 @@ static const char *smithy_class_types[] = {"structure_statement", "union_stateme
|
||||
static const char *smithy_field_types[] = {"shape_member", NULL};
|
||||
static const char *smithy_import_types[] = {"use_statement", NULL};
|
||||
static const char *smithy_module_types[] = {"source_file", NULL};
|
||||
static const char *wit_func_types[] = {"func_item", "resource_method", "export_item",
|
||||
"import_item", NULL};
|
||||
static const char *wit_func_types[] = {"func_item", "resource_method", "export_item", "import_item",
|
||||
NULL};
|
||||
static const char *wit_class_types[] = {"record_item", "resource_item", "enum_items",
|
||||
"variant_items", "flags_items", NULL};
|
||||
static const char *wit_field_types[] = {"record_field", NULL};
|
||||
|
||||
+742
-499
File diff suppressed because it is too large.
Load diff
@@ -4113,7 +4113,8 @@ static void kt_register_cross_def(CBMTypeRegistry *reg, CBMArena *arena, const C
|
||||
n++;
|
||||
}
|
||||
}
|
||||
const char **emb = (const char **)cbm_arena_alloc(arena, (size_t)(n + 1) * sizeof(*emb));
|
||||
const char **emb =
|
||||
(const char **)cbm_arena_alloc(arena, (size_t)(n + 1) * sizeof(*emb));
|
||||
int idx = 0;
|
||||
const char *start = d->embedded_types;
|
||||
while (*start) {
|
||||
|
||||
@@ -59,10 +59,10 @@
|
||||
* `foo` is resolved when used as a bare identifier. */
|
||||
typedef enum {
|
||||
CBM_KT_USE_UNKNOWN = 0,
|
||||
CBM_KT_USE_TYPE, /* class / interface / object / typealias */
|
||||
CBM_KT_USE_FUNCTION, /* top-level fun, extension fun */
|
||||
CBM_KT_USE_PROPERTY, /* top-level val/var */
|
||||
CBM_KT_USE_WILDCARD, /* import a.b.* — local_name is a.b prefix */
|
||||
CBM_KT_USE_TYPE, /* class / interface / object / typealias */
|
||||
CBM_KT_USE_FUNCTION, /* top-level fun, extension fun */
|
||||
CBM_KT_USE_PROPERTY, /* top-level val/var */
|
||||
CBM_KT_USE_WILDCARD, /* import a.b.* — local_name is a.b prefix */
|
||||
} CBMKotlinUseKind;
|
||||
|
||||
/* KotlinLSPContext — per-file state for Kotlin call resolution. */
|
||||
@@ -74,27 +74,27 @@ typedef struct KotlinLSPContext {
|
||||
CBMScope *current_scope;
|
||||
|
||||
/* Package context. Empty string for default package. */
|
||||
const char *package_qn; /* dotted form, e.g. "com.example.foo" */
|
||||
const char *module_qn; /* file-level QN, e.g. "<project>.com.example.foo.<File>" */
|
||||
const char *project_name; /* project prefix (without trailing dot) */
|
||||
const char *file_class_qn; /* JVM file-class QN, "<package>.<File>Kt" */
|
||||
const char *rel_path; /* for diagnostics */
|
||||
const char *package_qn; /* dotted form, e.g. "com.example.foo" */
|
||||
const char *module_qn; /* file-level QN, e.g. "<project>.com.example.foo.<File>" */
|
||||
const char *project_name; /* project prefix (without trailing dot) */
|
||||
const char *file_class_qn; /* JVM file-class QN, "<package>.<File>Kt" */
|
||||
const char *rel_path; /* for diagnostics */
|
||||
|
||||
/* Import map (parallel arrays). Kotlin imports are flat — no grouping
|
||||
* — but each may have an `as` alias. Wildcard imports record the
|
||||
* package prefix in `import_targets[i]` with `import_kinds[i] = WILDCARD`
|
||||
* and `import_locals[i] = NULL`. */
|
||||
const char **import_locals; /* alias name (or short name) used in code */
|
||||
const char **import_targets; /* full FQN being imported */
|
||||
const char **import_locals; /* alias name (or short name) used in code */
|
||||
const char **import_targets; /* full FQN being imported */
|
||||
CBMKotlinUseKind *import_kinds;
|
||||
int import_count;
|
||||
int import_cap;
|
||||
|
||||
/* Current declaration context. */
|
||||
const char *enclosing_func_qn; /* function we are resolving inside */
|
||||
const char *enclosing_class_qn; /* innermost class/interface/object QN, or NULL */
|
||||
const char *enclosing_func_qn; /* function we are resolving inside */
|
||||
const char *enclosing_class_qn; /* innermost class/interface/object QN, or NULL */
|
||||
const char *enclosing_companion_qn; /* if inside a companion object body */
|
||||
const char *enclosing_super_qn; /* primary super-class QN of current class, or NULL */
|
||||
const char *enclosing_super_qn; /* primary super-class QN of current class, or NULL */
|
||||
|
||||
/* Current `this` and `super` types. */
|
||||
const CBMType *this_type;
|
||||
@@ -116,9 +116,8 @@ typedef struct KotlinLSPContext {
|
||||
|
||||
/* Initialize a KotlinLSPContext for processing one file. */
|
||||
void kotlin_lsp_init(KotlinLSPContext *ctx, CBMArena *arena, const char *source, int source_len,
|
||||
const CBMTypeRegistry *registry, const char *package_qn,
|
||||
const char *module_qn, const char *project_name, const char *rel_path,
|
||||
CBMResolvedCallArray *out);
|
||||
const CBMTypeRegistry *registry, const char *package_qn, const char *module_qn,
|
||||
const char *project_name, const char *rel_path, CBMResolvedCallArray *out);
|
||||
|
||||
/* Add an import mapping. local_name is the name used in code (alias or
|
||||
* short name); target_qn is the full dotted FQN. For wildcard imports,
|
||||
@@ -155,8 +154,8 @@ const CBMType *kotlin_lookup_property_type(KotlinLSPContext *ctx, const char *cl
|
||||
/* Entry point: build registry from file defs + stdlib, then run resolution.
|
||||
* Called from cbm_extract_file() after definitions and imports have been
|
||||
* extracted. */
|
||||
void cbm_run_kotlin_lsp(CBMArena *arena, CBMFileResult *result, const char *source,
|
||||
int source_len, TSNode root);
|
||||
void cbm_run_kotlin_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len,
|
||||
TSNode root);
|
||||
|
||||
/* Cross-file LSP: build a registry from project-wide defs (local + cross-file)
|
||||
* + stdlib, re-parse the source if no cached tree, walk and resolve calls.
|
||||
|
||||
+1240
-951
File diff suppressed because it is too large.
Load diff
+48
-56
@@ -5,47 +5,47 @@
|
||||
#include "scope.h"
|
||||
#include "type_registry.h"
|
||||
#include "../cbm.h"
|
||||
#include "go_lsp.h" // CBMLSPDef, CBMResolvedCallArray reused across languages
|
||||
#include "go_lsp.h" // CBMLSPDef, CBMResolvedCallArray reused across languages
|
||||
|
||||
// Lambda body record. When a lambda is assigned to a name (`fn =
|
||||
// lambda x: x.method()`), we stash its parameter list + body so the
|
||||
// next call site (`fn(arg)`) can do call-site driven inference.
|
||||
typedef struct {
|
||||
const char* name; // bound name, e.g. "fn"
|
||||
TSNode lambda_node; // the lambda AST node
|
||||
const char *name; // bound name, e.g. "fn"
|
||||
TSNode lambda_node; // the lambda AST node
|
||||
} CBMLambdaEntry;
|
||||
|
||||
// Function-as-dict-value entry. When `funcs = {"a": foo, "b": bar}` is
|
||||
// seen, we record the per-key target QN so `funcs["a"]()` emits an
|
||||
// edge to foo.
|
||||
typedef struct {
|
||||
const char* var_name; // e.g. "funcs"
|
||||
const char* literal_key; // e.g. "a"
|
||||
const char* target_qn; // e.g. "test.main.foo"
|
||||
const char *var_name; // e.g. "funcs"
|
||||
const char *literal_key; // e.g. "a"
|
||||
const char *target_qn; // e.g. "test.main.foo"
|
||||
} CBMDictLiteralEntry;
|
||||
|
||||
// PyLSPContext holds state for one Python file's type evaluation.
|
||||
typedef struct {
|
||||
CBMArena* arena;
|
||||
const char* source;
|
||||
CBMArena *arena;
|
||||
const char *source;
|
||||
int source_len;
|
||||
const CBMTypeRegistry* registry;
|
||||
CBMScope* current_scope;
|
||||
const CBMTypeRegistry *registry;
|
||||
CBMScope *current_scope;
|
||||
|
||||
// Import map: local_name -> module_qn (arena-allocated, NULL-terminated).
|
||||
// Built from CBMFileResult.imports.
|
||||
const char** import_local_names;
|
||||
const char** import_module_qns;
|
||||
const char **import_local_names;
|
||||
const char **import_module_qns;
|
||||
int import_count;
|
||||
|
||||
// Current function/class context for resolving `self`/`cls` and emitting
|
||||
// caller QNs.
|
||||
const char* enclosing_func_qn;
|
||||
const char* enclosing_class_qn;
|
||||
const char* module_qn;
|
||||
const char *enclosing_func_qn;
|
||||
const char *enclosing_class_qn;
|
||||
const char *module_qn;
|
||||
|
||||
// Output: resolved calls accumulate here.
|
||||
CBMResolvedCallArray* resolved_calls;
|
||||
CBMResolvedCallArray *resolved_calls;
|
||||
|
||||
// Syntactic-call list (result->calls), borrowed from the per-file
|
||||
// extraction result. The downstream pipeline only turns a resolved_call
|
||||
@@ -57,15 +57,15 @@ typedef struct {
|
||||
// non-NULL, py_emit_dunder_call injects a matching synthetic CBMCall so the
|
||||
// recovered dunder call becomes a real CALLS edge. NULL in the cross-file
|
||||
// path (no result->calls available there). Mirrors RustLSPContext.syn_calls.
|
||||
CBMCallArray* syn_calls;
|
||||
CBMCallArray *syn_calls;
|
||||
|
||||
// Lambda registry: simple linear list, looked up by name.
|
||||
CBMLambdaEntry* lambdas;
|
||||
CBMLambdaEntry *lambdas;
|
||||
int lambda_count;
|
||||
int lambda_cap;
|
||||
|
||||
// Dict-literal-as-dispatch-table tracking.
|
||||
CBMDictLiteralEntry* dict_literals;
|
||||
CBMDictLiteralEntry *dict_literals;
|
||||
int dict_literal_count;
|
||||
int dict_literal_cap;
|
||||
|
||||
@@ -74,77 +74,69 @@ typedef struct {
|
||||
} PyLSPContext;
|
||||
|
||||
// Initialize a PyLSPContext for processing one file.
|
||||
void py_lsp_init(PyLSPContext* ctx, CBMArena* arena, const char* source, int source_len,
|
||||
const CBMTypeRegistry* registry, const char* module_qn, CBMResolvedCallArray* out);
|
||||
void py_lsp_init(PyLSPContext *ctx, CBMArena *arena, const char *source, int source_len,
|
||||
const CBMTypeRegistry *registry, const char *module_qn, CBMResolvedCallArray *out);
|
||||
|
||||
// Add an import mapping (call once per CBMImport entry).
|
||||
void py_lsp_add_import(PyLSPContext* ctx, const char* local_name, const char* module_qn);
|
||||
void py_lsp_add_import(PyLSPContext *ctx, const char *local_name, const char *module_qn);
|
||||
|
||||
// Bind every recorded import into the root scope. Idempotent. Called from
|
||||
// py_lsp_process_file before AST traversal. Exposed for unit tests that
|
||||
// don't need a tree-sitter parse.
|
||||
void py_lsp_bind_imports(PyLSPContext* ctx);
|
||||
void py_lsp_bind_imports(PyLSPContext *ctx);
|
||||
|
||||
// Test hook: lookup a name in the context's scope chain. Returns
|
||||
// cbm_type_unknown() if not bound.
|
||||
const CBMType* py_lsp_lookup_in_scope(const PyLSPContext* ctx, const char* name);
|
||||
const CBMType *py_lsp_lookup_in_scope(const PyLSPContext *ctx, const char *name);
|
||||
|
||||
// Process all functions/classes in a file's AST, evaluating types and resolving calls.
|
||||
// root must be the tree-sitter Python `module` node.
|
||||
void py_lsp_process_file(PyLSPContext* ctx, TSNode root);
|
||||
void py_lsp_process_file(PyLSPContext *ctx, TSNode root);
|
||||
|
||||
// Entry point: build registry from file's own defs + run LSP resolution.
|
||||
// Mirrors cbm_run_go_lsp / cbm_run_c_lsp.
|
||||
void cbm_run_py_lsp(CBMArena* arena, CBMFileResult* result,
|
||||
const char* source, int source_len, TSNode root);
|
||||
void cbm_run_py_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len,
|
||||
TSNode root);
|
||||
|
||||
// Register Python stdlib types and functions into a registry.
|
||||
// Auto-generated by scripts/gen-py-stdlib.py from typeshed.
|
||||
// Phase 10 fills the body; Phase 2 ships a no-op so linkage works.
|
||||
void cbm_python_stdlib_register(CBMTypeRegistry* reg, CBMArena* arena);
|
||||
void cbm_python_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena);
|
||||
|
||||
// --- Cross-file LSP resolution ---
|
||||
|
||||
void cbm_run_py_lsp_cross(
|
||||
CBMArena* arena,
|
||||
const char* source, int source_len,
|
||||
const char* module_qn,
|
||||
CBMLSPDef* defs, int def_count,
|
||||
const char** import_names, const char** import_qns, int import_count,
|
||||
TSTree* cached_tree, // NULL = parse internally
|
||||
CBMResolvedCallArray* out);
|
||||
void cbm_run_py_lsp_cross(CBMArena *arena, const char *source, int source_len,
|
||||
const char *module_qn, CBMLSPDef *defs, int def_count,
|
||||
const char **import_names, const char **import_qns, int import_count,
|
||||
TSTree *cached_tree, // NULL = parse internally
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
/* Tier 2: pre-built per-language registry (mirrors Go pilot).
|
||||
* Filters all_defs to Python entries, builds + finalizes once. */
|
||||
CBMTypeRegistry* cbm_py_build_cross_registry(
|
||||
CBMArena* arena, CBMLSPDef* defs, int def_count);
|
||||
CBMTypeRegistry *cbm_py_build_cross_registry(CBMArena *arena, CBMLSPDef *defs, int def_count);
|
||||
|
||||
void cbm_run_py_lsp_cross_with_registry(
|
||||
CBMArena* arena,
|
||||
const char* source, int source_len,
|
||||
const char* module_qn,
|
||||
CBMTypeRegistry* reg, // pre-built, finalized, READ-ONLY
|
||||
const char** import_names, const char** import_qns, int import_count,
|
||||
TSTree* cached_tree,
|
||||
CBMResolvedCallArray* out);
|
||||
void cbm_run_py_lsp_cross_with_registry(CBMArena *arena, const char *source, int source_len,
|
||||
const char *module_qn,
|
||||
CBMTypeRegistry *reg, // pre-built, finalized, READ-ONLY
|
||||
const char **import_names, const char **import_qns,
|
||||
int import_count, TSTree *cached_tree,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
// --- Batch cross-file LSP ---
|
||||
|
||||
typedef struct {
|
||||
const char* source;
|
||||
const char *source;
|
||||
int source_len;
|
||||
const char* module_qn;
|
||||
TSTree* cached_tree; // from TSTree caching (NULL = parse internally)
|
||||
CBMLSPDef* defs; // combined file-local + cross-file defs
|
||||
const char *module_qn;
|
||||
TSTree *cached_tree; // from TSTree caching (NULL = parse internally)
|
||||
CBMLSPDef *defs; // combined file-local + cross-file defs
|
||||
int def_count;
|
||||
const char** import_names; // parallel arrays, import_count long
|
||||
const char** import_qns;
|
||||
const char **import_names; // parallel arrays, import_count long
|
||||
const char **import_qns;
|
||||
int import_count;
|
||||
} CBMBatchPyLSPFile;
|
||||
|
||||
void cbm_batch_py_lsp_cross(
|
||||
CBMArena* arena,
|
||||
CBMBatchPyLSPFile* files, int file_count,
|
||||
CBMResolvedCallArray* out);
|
||||
void cbm_batch_py_lsp_cross(CBMArena *arena, CBMBatchPyLSPFile *files, int file_count,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
#endif // CBM_LSP_PY_LSP_H
|
||||
+1591
-1307
File diff suppressed because it is too large.
Load diff
+71
-76
@@ -38,49 +38,49 @@ struct CBMCargoManifest;
|
||||
* shared override resolver (`pipeline/lsp_resolve.h`) only admits entries
|
||||
* scoring >= CBM_LSP_CONFIDENCE_FLOOR (0.6). Numbers here mirror the Go
|
||||
* LSP so the call-edge mix from a polyglot project stays comparable. */
|
||||
#define CBM_RUST_CONF_DIRECT 0.95f /* path::to::func or alias hit */
|
||||
#define CBM_RUST_CONF_METHOD 0.95f /* inherent method dispatch */
|
||||
#define CBM_RUST_CONF_TRAIT_SOLE 0.92f /* trait method, single impl */
|
||||
#define CBM_RUST_CONF_TRAIT_AMB 0.85f /* trait method, many impls */
|
||||
#define CBM_RUST_CONF_UFCS 0.93f /* T::method() / Self::new() */
|
||||
#define CBM_RUST_CONF_PROMOTED 0.90f /* through Deref / blanket impl */
|
||||
#define CBM_RUST_CONF_MACRO_KNOWN 0.85f /* known std macro mapped to fn */
|
||||
#define CBM_RUST_CONF_OPERATOR 0.88f /* a+b → T::add (operator trait) */
|
||||
#define CBM_RUST_CONF_DIRECT 0.95f /* path::to::func or alias hit */
|
||||
#define CBM_RUST_CONF_METHOD 0.95f /* inherent method dispatch */
|
||||
#define CBM_RUST_CONF_TRAIT_SOLE 0.92f /* trait method, single impl */
|
||||
#define CBM_RUST_CONF_TRAIT_AMB 0.85f /* trait method, many impls */
|
||||
#define CBM_RUST_CONF_UFCS 0.93f /* T::method() / Self::new() */
|
||||
#define CBM_RUST_CONF_PROMOTED 0.90f /* through Deref / blanket impl */
|
||||
#define CBM_RUST_CONF_MACRO_KNOWN 0.85f /* known std macro mapped to fn */
|
||||
#define CBM_RUST_CONF_OPERATOR 0.88f /* a+b → T::add (operator trait) */
|
||||
|
||||
/* Rust-flavoured LSP context: one per file, lifetime tied to a single
|
||||
* `cbm_extract_file()` invocation (or the cross-file caller's arena). */
|
||||
typedef struct {
|
||||
CBMArena* arena;
|
||||
const char* source;
|
||||
CBMArena *arena;
|
||||
const char *source;
|
||||
int source_len;
|
||||
|
||||
const CBMTypeRegistry* registry;
|
||||
CBMScope* current_scope;
|
||||
const CBMTypeRegistry *registry;
|
||||
CBMScope *current_scope;
|
||||
|
||||
/* `use` map: parallel arrays mapping a local-name (the last segment, or
|
||||
* the `as` alias) to its full module path (`std::collections::HashMap`,
|
||||
* `crate::foo::Bar`). Glob imports go in `glob_module_qns` instead. */
|
||||
const char** use_local_names;
|
||||
const char** use_module_paths;
|
||||
const char **use_local_names;
|
||||
const char **use_module_paths;
|
||||
int use_count;
|
||||
|
||||
const char** glob_module_qns;
|
||||
const char **glob_module_qns;
|
||||
int glob_count;
|
||||
|
||||
/* Module-qualified name for this file (e.g. "<project>.<crate>.foo"). */
|
||||
const char* module_qn;
|
||||
const char *module_qn;
|
||||
|
||||
/* Enclosing function context. `enclosing_func_qn` is the QN we attach
|
||||
* to every emitted CBMResolvedCall as `caller_qn`. */
|
||||
const char* enclosing_func_qn;
|
||||
const char *enclosing_func_qn;
|
||||
|
||||
/* `Self` resolution: when inside `impl T { ... }` or
|
||||
* `impl Trait for T { ... }`, `self_type_qn` is `T`'s QN. NULL outside
|
||||
* an impl. `self_trait_qn` is the trait QN (only set for trait impls)
|
||||
* — used when emitting trait method calls so we can prefer concrete
|
||||
* implementations over the trait method when only one impl exists. */
|
||||
const char* self_type_qn;
|
||||
const char* self_trait_qn;
|
||||
const char *self_type_qn;
|
||||
const char *self_trait_qn;
|
||||
|
||||
/* Closure parameter inference: when the call resolver descends into a
|
||||
* call's argument list and the callee is a method like
|
||||
@@ -88,7 +88,7 @@ typedef struct {
|
||||
* of the closure's first param here. The closure_expression handler
|
||||
* consumes it (and clears it) when binding params, so a chain like
|
||||
* `vec.iter().map(|x| x.method())` resolves `x.method()` correctly. */
|
||||
const CBMType* pending_closure_param_type;
|
||||
const CBMType *pending_closure_param_type;
|
||||
|
||||
/* User-defined `macro_rules!` definitions collected during the
|
||||
* pre-walk phase. Each rule stores its pattern + transcriber text;
|
||||
@@ -96,7 +96,7 @@ typedef struct {
|
||||
* against each rule and then substitutes/re-walks the expansion.
|
||||
*
|
||||
* The arrays are doubling-grown out of `arena`. */
|
||||
struct RustMacroRule** macro_rules_arr;
|
||||
struct RustMacroRule **macro_rules_arr;
|
||||
int macro_rules_count;
|
||||
|
||||
/* Recursion guard for macro expansion. Real macro_rules! can be
|
||||
@@ -119,7 +119,7 @@ typedef struct {
|
||||
* falling through to module-prefix fallback. NULL when no manifest
|
||||
* is available — the resolver still works, just without workspace
|
||||
* awareness. Owned by the caller; the RustLSPContext only borrows. */
|
||||
const struct CBMCargoManifest* cargo_manifest;
|
||||
const struct CBMCargoManifest *cargo_manifest;
|
||||
|
||||
/* Chalk-lite trait-bound environment for the *currently-active*
|
||||
* function or impl. Populated at function/impl entry from the
|
||||
@@ -132,19 +132,19 @@ typedef struct {
|
||||
* Arrays are arena-allocated and reset on every function entry.
|
||||
*/
|
||||
struct {
|
||||
const char* param_name; /* "T", "U", "Item" */
|
||||
const char* trait_qn; /* "core.clone.Clone" */
|
||||
const char *param_name; /* "T", "U", "Item" */
|
||||
const char *trait_qn; /* "core.clone.Clone" */
|
||||
} *type_param_bounds;
|
||||
int type_param_bound_count;
|
||||
|
||||
struct {
|
||||
const char* alias_name; /* "U" */
|
||||
const char* aliased_to; /* "T.Item" (representational) */
|
||||
const char *alias_name; /* "U" */
|
||||
const char *aliased_to; /* "T.Item" (representational) */
|
||||
} *type_param_aliases;
|
||||
int type_param_alias_count;
|
||||
|
||||
/* Output: resolved (and unresolved-with-reason) calls accumulate here. */
|
||||
CBMResolvedCallArray* resolved_calls;
|
||||
CBMResolvedCallArray *resolved_calls;
|
||||
|
||||
/* Syntactic-call list (result->calls), borrowed from the per-file
|
||||
* extraction result. The downstream pipeline only turns a resolved_call
|
||||
@@ -156,7 +156,7 @@ typedef struct {
|
||||
* see them. When this is non-NULL the resolver injects matching synthetic
|
||||
* CBMCall entries so those recovered calls become real edges. NULL in the
|
||||
* cross-file path (no result available). */
|
||||
CBMCallArray* syn_calls;
|
||||
CBMCallArray *syn_calls;
|
||||
|
||||
/* While >0, rust_emit_resolved_call also injects a matching synthetic
|
||||
* CBMCall into `syn_calls` so the recovered call becomes an edge. Set
|
||||
@@ -169,23 +169,24 @@ typedef struct {
|
||||
} RustLSPContext;
|
||||
|
||||
/* Initialise an empty context for processing one file. */
|
||||
void rust_lsp_init(RustLSPContext* ctx, CBMArena* arena, const char* source, int source_len,
|
||||
const CBMTypeRegistry* registry, const char* module_qn, CBMResolvedCallArray* out);
|
||||
void rust_lsp_init(RustLSPContext *ctx, CBMArena *arena, const char *source, int source_len,
|
||||
const CBMTypeRegistry *registry, const char *module_qn,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
/* Register a `use` alias. `local_name` may be the last segment of the path
|
||||
* (`HashMap` for `use std::collections::HashMap`) or an `as` alias. The
|
||||
* full `module_path` is stored verbatim (e.g. `std::collections::HashMap`).
|
||||
* Glob imports (`use foo::*`) go through `rust_lsp_add_glob` instead. */
|
||||
void rust_lsp_add_use(RustLSPContext* ctx, const char* local_name, const char* module_path);
|
||||
void rust_lsp_add_glob(RustLSPContext* ctx, const char* module_qn);
|
||||
void rust_lsp_add_use(RustLSPContext *ctx, const char *local_name, const char *module_path);
|
||||
void rust_lsp_add_glob(RustLSPContext *ctx, const char *module_qn);
|
||||
|
||||
/* Process every function/method in the file, walking statements and
|
||||
* evaluating expression types as we go. */
|
||||
void rust_lsp_process_file(RustLSPContext* ctx, TSNode root);
|
||||
void rust_lsp_process_file(RustLSPContext *ctx, TSNode root);
|
||||
|
||||
/* Evaluate the static type of a Rust expression node. Returns
|
||||
* `cbm_type_unknown()` for anything we cannot type. */
|
||||
const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node);
|
||||
const CBMType *rust_eval_expr_type(RustLSPContext *ctx, TSNode node);
|
||||
|
||||
/* Bidirectional variant: evaluate an expression with an `expected`
|
||||
* hint available. The hint is used to:
|
||||
@@ -199,27 +200,27 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node);
|
||||
* `expected` may be NULL (no hint). If the synthesised type matches
|
||||
* the expected, returns it; if a hint resolves an ambiguity that the
|
||||
* synthesis path cannot, returns the hint-substituted form. */
|
||||
const CBMType* rust_eval_expr_typed(RustLSPContext* ctx, TSNode node, const CBMType* expected);
|
||||
const CBMType *rust_eval_expr_typed(RustLSPContext *ctx, TSNode node, const CBMType *expected);
|
||||
|
||||
/* Convert a `*type*` AST node (type_identifier, scoped_type_identifier,
|
||||
* reference_type, primitive_type, …) into a CBMType. */
|
||||
const CBMType* rust_parse_type_node(RustLSPContext* ctx, TSNode node);
|
||||
const CBMType *rust_parse_type_node(RustLSPContext *ctx, TSNode node);
|
||||
|
||||
/* Bind the bindings introduced by a let/for/match-pattern statement into
|
||||
* the current scope. */
|
||||
void rust_process_statement(RustLSPContext* ctx, TSNode node);
|
||||
void rust_process_statement(RustLSPContext *ctx, TSNode node);
|
||||
|
||||
/* Look up an inherent method or field promoted through Deref / embedded
|
||||
* trait blanket impls. Returns NULL if nothing matches. */
|
||||
const CBMRegisteredFunc* rust_lookup_method(RustLSPContext* ctx,
|
||||
const char* type_qn, const char* member_name);
|
||||
const CBMRegisteredFunc *rust_lookup_method(RustLSPContext *ctx, const char *type_qn,
|
||||
const char *member_name);
|
||||
|
||||
/* Entry point — called from `cbm_extract_file()` after the unified
|
||||
* extractor has filled `result->defs`, `result->imports`, and
|
||||
* `result->impl_traits`. Builds a per-file registry, parses the `use`
|
||||
* graph, and walks every function body emitting CBMResolvedCall entries. */
|
||||
void cbm_run_rust_lsp(CBMArena* arena, CBMFileResult* result,
|
||||
const char* source, int source_len, TSNode root);
|
||||
void cbm_run_rust_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len,
|
||||
TSNode root);
|
||||
|
||||
/* Synthesize call edges for curated attribute proc-macros.
|
||||
*
|
||||
@@ -232,15 +233,15 @@ void cbm_run_rust_lsp(CBMArena* arena, CBMFileResult* result,
|
||||
* direct call attributions.
|
||||
*
|
||||
* Called automatically from `cbm_run_rust_lsp_with_manifest`. */
|
||||
void cbm_rust_synth_proc_macro_edges(CBMArena* arena, CBMFileResult* result);
|
||||
void cbm_rust_synth_proc_macro_edges(CBMArena *arena, CBMFileResult *result);
|
||||
|
||||
/* Same as `cbm_run_rust_lsp`, but accepts an optional Cargo manifest
|
||||
* so the resolver can route paths whose head is a workspace member /
|
||||
* declared dependency. Pass NULL to fall back to the manifest-free
|
||||
* behaviour. */
|
||||
void cbm_run_rust_lsp_with_manifest(CBMArena* arena, CBMFileResult* result,
|
||||
const char* source, int source_len, TSNode root,
|
||||
const struct CBMCargoManifest* manifest);
|
||||
void cbm_run_rust_lsp_with_manifest(CBMArena *arena, CBMFileResult *result, const char *source,
|
||||
int source_len, TSNode root,
|
||||
const struct CBMCargoManifest *manifest);
|
||||
|
||||
/* Register a curated subset of the Rust core/alloc/std prelude into the
|
||||
* given registry. The seed is intentionally compact (~150 types, ~600
|
||||
@@ -248,13 +249,13 @@ void cbm_run_rust_lsp_with_manifest(CBMArena* arena, CBMFileResult* result,
|
||||
* plus the prelude trait method names (`clone`, `to_string`, `into`, …).
|
||||
* Generated from `scripts/gen-rust-stdlib.py` if present, otherwise the
|
||||
* hand-written `rust_stdlib_data.c` module is used. */
|
||||
void cbm_rust_stdlib_register(CBMTypeRegistry* reg, CBMArena* arena);
|
||||
void cbm_rust_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena);
|
||||
|
||||
/* Register seeds for commonly-used external crates (serde, tokio,
|
||||
* anyhow, clap, regex, log, futures, parking_lot, once_cell, chrono,
|
||||
* uuid, reqwest, rayon). Best-effort curated entries — RUST_LSP_FOLLOWUP
|
||||
* §A3. Calls into crates not in this seed remain `unresolved`. */
|
||||
void cbm_rust_crates_register(CBMTypeRegistry* reg, CBMArena* arena);
|
||||
void cbm_rust_crates_register(CBMTypeRegistry *reg, CBMArena *arena);
|
||||
|
||||
/* --- Cross-file LSP resolution --- */
|
||||
|
||||
@@ -263,46 +264,40 @@ void cbm_rust_crates_register(CBMTypeRegistry* reg, CBMArena* arena);
|
||||
* — see that header for field semantics. We declare a separate typedef
|
||||
* to allow Rust-specific fields without touching the Go ABI. */
|
||||
typedef struct {
|
||||
const char* qualified_name;
|
||||
const char* short_name;
|
||||
const char* label; /* "Function", "Method", "Type", "Trait" */
|
||||
const char* receiver_type; /* for methods: receiver type QN (NULL for free fns) */
|
||||
const char* def_module_qn; /* module QN where this def lives */
|
||||
const char* return_types; /* "|"-separated return type texts */
|
||||
const char* embedded_types; /* "|"-separated embedded type QNs */
|
||||
const char* field_defs; /* "|"-separated "name:type" pairs */
|
||||
const char* method_names_str;/* "|"-separated method names for traits */
|
||||
const char* trait_qn; /* impl Trait for Type → set on Method defs */
|
||||
bool is_interface; /* true for traits */
|
||||
const char *qualified_name;
|
||||
const char *short_name;
|
||||
const char *label; /* "Function", "Method", "Type", "Trait" */
|
||||
const char *receiver_type; /* for methods: receiver type QN (NULL for free fns) */
|
||||
const char *def_module_qn; /* module QN where this def lives */
|
||||
const char *return_types; /* "|"-separated return type texts */
|
||||
const char *embedded_types; /* "|"-separated embedded type QNs */
|
||||
const char *field_defs; /* "|"-separated "name:type" pairs */
|
||||
const char *method_names_str; /* "|"-separated method names for traits */
|
||||
const char *trait_qn; /* impl Trait for Type → set on Method defs */
|
||||
bool is_interface; /* true for traits */
|
||||
} CBMRustLSPDef;
|
||||
|
||||
/* Run cross-file resolution on a single file. */
|
||||
void cbm_run_rust_lsp_cross(
|
||||
CBMArena* arena,
|
||||
const char* source, int source_len,
|
||||
const char* module_qn,
|
||||
CBMRustLSPDef* defs, int def_count,
|
||||
const char** import_names, const char** import_qns, int import_count,
|
||||
TSTree* cached_tree,
|
||||
CBMResolvedCallArray* out);
|
||||
void cbm_run_rust_lsp_cross(CBMArena *arena, const char *source, int source_len,
|
||||
const char *module_qn, CBMRustLSPDef *defs, int def_count,
|
||||
const char **import_names, const char **import_qns, int import_count,
|
||||
TSTree *cached_tree, CBMResolvedCallArray *out);
|
||||
|
||||
/* Per-file input for batch cross-file Rust LSP processing. */
|
||||
typedef struct {
|
||||
const char* source;
|
||||
const char *source;
|
||||
int source_len;
|
||||
const char* module_qn;
|
||||
TSTree* cached_tree;
|
||||
CBMRustLSPDef* defs;
|
||||
const char *module_qn;
|
||||
TSTree *cached_tree;
|
||||
CBMRustLSPDef *defs;
|
||||
int def_count;
|
||||
const char** import_names;
|
||||
const char** import_qns;
|
||||
const char **import_names;
|
||||
const char **import_qns;
|
||||
int import_count;
|
||||
} CBMBatchRustLSPFile;
|
||||
|
||||
/* Process several files in one CGo call (per-file arenas, result copy). */
|
||||
void cbm_batch_rust_lsp_cross(
|
||||
CBMArena* arena,
|
||||
CBMBatchRustLSPFile* files, int file_count,
|
||||
CBMResolvedCallArray* out);
|
||||
void cbm_batch_rust_lsp_cross(CBMArena *arena, CBMBatchRustLSPFile *files, int file_count,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
#endif /* CBM_LSP_RUST_LSP_H */
|
||||
@@ -198,11 +198,9 @@ static void handle_route_registration(cbm_pipeline_ctx_t *ctx, const CBMCall *ca
|
||||
"{\"callee\":\"%s\",\"url_path\":\"%s\",\"via\":\"route_registration\"}",
|
||||
call->callee_name, call->first_string_arg);
|
||||
cbm_gbuf_insert_edge(ctx->gbuf, source_node->id, route_id, "CALLS", props);
|
||||
if (getenv("CBM_DBG_ROUTE")) fprintf(stderr, "[DBG] route_reg callee=%s path=%s handler=%s\n", call->callee_name, call->first_string_arg, call->second_arg_name?call->second_arg_name:"(null)");
|
||||
if (call->second_arg_name != NULL && call->second_arg_name[0] != '\0') {
|
||||
cbm_resolution_t hres = cbm_registry_resolve(ctx->registry, call->second_arg_name,
|
||||
module_qn, imp_keys, imp_vals, imp_count);
|
||||
if (getenv("CBM_DBG_ROUTE")) fprintf(stderr, "[DBG] handler '%s' resolved=%s\n", call->second_arg_name, hres.qualified_name?hres.qualified_name:"(null)");
|
||||
if (hres.qualified_name != NULL && hres.qualified_name[0] != '\0') {
|
||||
const cbm_gbuf_node_t *handler = cbm_gbuf_find_by_qn(ctx->gbuf, hres.qualified_name);
|
||||
if (handler != NULL) {
|
||||
@@ -283,8 +281,6 @@ static void emit_classified_edge(cbm_pipeline_ctx_t *ctx, const CBMCall *call,
|
||||
const cbm_resolution_t *res, const char *module_qn,
|
||||
const char **imp_keys, const char **imp_vals, int imp_count) {
|
||||
cbm_svc_kind_t svc = cbm_service_pattern_match(res->qualified_name);
|
||||
if (getenv("CBM_DBG_SVC") && call->callee_name && (strstr(call->callee_name,"Route")||strstr(call->callee_name,"client")||strstr(call->callee_name,"Client")||strstr(call->callee_name,"RestClient")))
|
||||
fprintf(stderr, "[DBGSVC] callee=%s resolved=%s svc=%d strarg=%s\n", call->callee_name, res->qualified_name?res->qualified_name:"(null)", svc, call->first_string_arg?call->first_string_arg:"(null)");
|
||||
if (svc == CBM_SVC_ROUTE_REG && call->first_string_arg && call->first_string_arg[0] == '/') {
|
||||
handle_route_registration(ctx, call, source, module_qn, imp_keys, imp_vals, imp_count);
|
||||
return;
|
||||
|
||||
@@ -275,11 +275,9 @@ static void k8s_scan_labels(const char *source, k8s_record_t *rec) {
|
||||
char val[K8S_LABEL_LEN];
|
||||
if (k8s_split_kv(trimmed, key, sizeof(key), val, sizeof(val))) {
|
||||
/* Build the current dotted path for context decisions. */
|
||||
bool under_selector =
|
||||
(depth >= 1 && strcmp(stack[depth - 1].key, "selector") == 0);
|
||||
bool under_selector = (depth >= 1 && strcmp(stack[depth - 1].key, "selector") == 0);
|
||||
bool under_labels = (depth >= 1 && strcmp(stack[depth - 1].key, "labels") == 0);
|
||||
bool under_metadata =
|
||||
(depth >= 1 && strcmp(stack[depth - 1].key, "metadata") == 0);
|
||||
bool under_metadata = (depth >= 1 && strcmp(stack[depth - 1].key, "metadata") == 0);
|
||||
|
||||
if (val[0] == '\0') {
|
||||
/* Block-opening key: push onto the path stack. */
|
||||
@@ -586,9 +584,9 @@ static int parse_requirements_deps(cbm_pipeline_ctx_t *ctx, const cbm_gbuf_node_
|
||||
if (t[0] && t[0] != '#' && t[0] != '-' && strstr(t, "://") == NULL) {
|
||||
char name[CBM_SZ_256];
|
||||
size_t n = 0;
|
||||
while (t[n] && t[n] != '=' && t[n] != '<' && t[n] != '>' && t[n] != '!' && t[n] != '~' &&
|
||||
t[n] != '[' && t[n] != ';' && t[n] != ' ' && t[n] != '\t' && t[n] != '\r' &&
|
||||
n + 1 < sizeof(name)) {
|
||||
while (t[n] && t[n] != '=' && t[n] != '<' && t[n] != '>' && t[n] != '!' &&
|
||||
t[n] != '~' && t[n] != '[' && t[n] != ';' && t[n] != ' ' && t[n] != '\t' &&
|
||||
t[n] != '\r' && n + 1 < sizeof(name)) {
|
||||
name[n] = t[n];
|
||||
n++;
|
||||
}
|
||||
@@ -675,8 +673,7 @@ int cbm_pipeline_pass_k8s(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files,
|
||||
* pass and contain no "Resource" definitions. Pass the already-
|
||||
* read source buffer so handle_k8s_manifest does not re-read. */
|
||||
(void)cached; /* cached YAML result intentionally discarded */
|
||||
k8s_record_t *rec =
|
||||
(recs.count < recs.cap) ? &recs.items[recs.count] : NULL;
|
||||
k8s_record_t *rec = (recs.count < recs.cap) ? &recs.items[recs.count] : NULL;
|
||||
handle_k8s_manifest(ctx, path, rel, source, src_len, rec);
|
||||
if (rec && rec->node_id > 0) {
|
||||
recs.count++;
|
||||
|
||||
@@ -399,8 +399,8 @@ void cbm_pxc_run_one(CBMLanguage lang, CBMFileResult *r, const char *source, int
|
||||
imp_qns, imp_count, tree, &out);
|
||||
break;
|
||||
case CBM_LANG_KOTLIN:
|
||||
cbm_run_kotlin_lsp_cross(&scratch, source, source_len, module_qn, defs, def_count, imp_names,
|
||||
imp_qns, imp_count, tree, &out);
|
||||
cbm_run_kotlin_lsp_cross(&scratch, source, source_len, module_qn, defs, def_count,
|
||||
imp_names, imp_qns, imp_count, tree, &out);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -1941,8 +1941,8 @@ static void resolve_def_decorators(resolve_ctx_t *rc, resolve_worker_state_t *ws
|
||||
* and the edge target IDs are remapped consistently. */
|
||||
char syn_qn[CBM_SZ_512];
|
||||
snprintf(syn_qn, sizeof(syn_qn), "<decorator:%s>", fn);
|
||||
dn_id = cbm_gbuf_upsert_node(ws->local_edge_buf, "Decorator", fn, syn_qn, "", 0, 0,
|
||||
"{}");
|
||||
dn_id =
|
||||
cbm_gbuf_upsert_node(ws->local_edge_buf, "Decorator", fn, syn_qn, "", 0, 0, "{}");
|
||||
}
|
||||
if (dn_id != 0 && node->id != dn_id) {
|
||||
char dp[CBM_SZ_256];
|
||||
|
||||
@@ -1275,8 +1275,9 @@ static bool import_targetable_label(const char *label) {
|
||||
* File/Module-node QN (extension is stripped by fqn_module). Returns a borrowed
|
||||
* node or NULL. Several filename conventions are tried in turn. */
|
||||
static const cbm_gbuf_node_t *resolve_sibling_file(const cbm_pipeline_ctx_t *ctx,
|
||||
const char *source_rel, const char *source_file_qn,
|
||||
const char *module_path) {
|
||||
const char *source_rel,
|
||||
const char *source_file_qn,
|
||||
const char *module_path) {
|
||||
if (!module_path || !module_path[0]) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -252,7 +252,8 @@ static int check_go_class_implements(cbm_pipeline_ctx_t *ctx, const cbm_gbuf_nod
|
||||
* DEFINES_METHOD edges from the class. */
|
||||
const cbm_gbuf_edge_t **cls_dm = NULL;
|
||||
int cls_dm_count = 0;
|
||||
cbm_gbuf_find_edges_by_source_type(ctx->gbuf, cls->id, "DEFINES_METHOD", &cls_dm, &cls_dm_count);
|
||||
cbm_gbuf_find_edges_by_source_type(ctx->gbuf, cls->id, "DEFINES_METHOD", &cls_dm,
|
||||
&cls_dm_count);
|
||||
|
||||
char prefix[CBM_SZ_512];
|
||||
snprintf(prefix, sizeof(prefix), "%s.", cls->qualified_name);
|
||||
@@ -365,8 +366,7 @@ static void resolve_decorator(cbm_pipeline_ctx_t *ctx, const cbm_gbuf_node_t *no
|
||||
}
|
||||
cbm_resolution_t res =
|
||||
cbm_registry_resolve(ctx->registry, func_name, module_qn, imp_keys, imp_vals, imp_count);
|
||||
if ((!res.qualified_name || res.qualified_name[0] == '\0') &&
|
||||
!strchr(func_name, '.')) {
|
||||
if ((!res.qualified_name || res.qualified_name[0] == '\0') && !strchr(func_name, '.')) {
|
||||
/* C# attributes are referenced by their short name (`[Log]`) but declared
|
||||
* with the conventional `Attribute` suffix (`class LogAttribute`). Retry
|
||||
* with the suffix appended so the declaration resolves. */
|
||||
@@ -432,8 +432,9 @@ static void sem_process_def_edges(cbm_pipeline_ctx_t *ctx, const CBMDefinition *
|
||||
* (Java `implements`, C# `: IFace`, TS `implements`); a Class/
|
||||
* Type/Enum base is plain INHERITS. */
|
||||
const char *base_label = cbm_registry_label_of(ctx->registry, base_qn);
|
||||
const char *edge_type =
|
||||
(base_label && strcmp(base_label, "Interface") == 0) ? "IMPLEMENTS" : "INHERITS";
|
||||
const char *edge_type = (base_label && strcmp(base_label, "Interface") == 0)
|
||||
? "IMPLEMENTS"
|
||||
: "INHERITS";
|
||||
cbm_gbuf_insert_edge(ctx->gbuf, node->id, base_node->id, edge_type, "{}");
|
||||
(*inherits_count)++;
|
||||
}
|
||||
|
||||
@@ -399,9 +399,8 @@ static int process_one_infra_binding(cbm_gbuf_t *gbuf, const CBMInfraBinding *ib
|
||||
* upsert its Route node so the binding maps even when no code-side dispatch
|
||||
* call created the node first (e.g. a standalone scheduler/subscription
|
||||
* manifest). */
|
||||
topic_route_id =
|
||||
cbm_gbuf_upsert_node(gbuf, "Route", ib->source_name, topic_route_qn, rel_path, 0, 0,
|
||||
ib->broker ? ib->broker : "async");
|
||||
topic_route_id = cbm_gbuf_upsert_node(gbuf, "Route", ib->source_name, topic_route_qn,
|
||||
rel_path, 0, 0, ib->broker ? ib->broker : "async");
|
||||
if (topic_route_id <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
+1167
-1311
File diff suppressed because it is too large.
Load diff
@@ -276,8 +276,9 @@ TEST(grammar_label_goldens) {
|
||||
}
|
||||
}
|
||||
if (missing > 0) {
|
||||
fprintf(stderr, " [LABEL] %d grammar(s) missing a golden — copy the [LABEL-CAPTURE] lines "
|
||||
"into LABEL_GOLDENS\n",
|
||||
fprintf(stderr,
|
||||
" [LABEL] %d grammar(s) missing a golden — copy the [LABEL-CAPTURE] lines "
|
||||
"into LABEL_GOLDENS\n",
|
||||
missing);
|
||||
}
|
||||
ASSERT_EQ(failures, 0);
|
||||
@@ -305,9 +306,9 @@ static int non_module_defs(CBMFileResult *r) {
|
||||
* config / markup grammars (json/yaml/css/…) are intentionally excluded — a
|
||||
* lone Module node is correct for them. */
|
||||
static const char *MUST_EXTRACT_DEFS[] = {
|
||||
"agda", "pony", "move", "cobol", "janet", "pine", "smali", "verilog",
|
||||
"vhdl", "systemverilog", "protobuf", "graphql", "thrift", "capnp", "smithy",
|
||||
"wit", "prisma", "cmake", "puppet", "tablegen", "assembly", "nasm", NULL};
|
||||
"agda", "pony", "move", "cobol", "janet", "pine", "smali", "verilog",
|
||||
"vhdl", "systemverilog", "protobuf", "graphql", "thrift", "capnp", "smithy", "wit",
|
||||
"prisma", "cmake", "puppet", "tablegen", "assembly", "nasm", NULL};
|
||||
|
||||
TEST(grammar_code_extracts_defs) {
|
||||
int failures = 0;
|
||||
@@ -335,8 +336,9 @@ TEST(grammar_code_extracts_defs) {
|
||||
int nd = non_module_defs(r);
|
||||
cbm_free_result(r);
|
||||
if (nd < 1) {
|
||||
fprintf(stderr, " [CODE-DEFS] FAIL %-14s extracts 0 non-Module defs (under-extraction "
|
||||
"gap — only a Module node)\n",
|
||||
fprintf(stderr,
|
||||
" [CODE-DEFS] FAIL %-14s extracts 0 non-Module defs (under-extraction "
|
||||
"gap — only a Module node)\n",
|
||||
name);
|
||||
failures++;
|
||||
}
|
||||
|
||||
+239
-281
@@ -64,39 +64,45 @@ typedef struct {
|
||||
} GpgProj;
|
||||
|
||||
typedef struct {
|
||||
const char *name; /* relative filename, may include '/' for subdirs */
|
||||
const char *name; /* relative filename, may include '/' for subdirs */
|
||||
const char *content;
|
||||
} GpgFile;
|
||||
|
||||
static void gpg_to_fwd_slashes(char *p) {
|
||||
for (; *p; p++) {
|
||||
if (*p == '\\') *p = '/';
|
||||
if (*p == '\\')
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
|
||||
static cbm_store_t *gpg_open_indexed(GpgProj *lp) {
|
||||
lp->project = cbm_project_name_from_path(lp->tmpdir);
|
||||
if (!lp->project) return NULL;
|
||||
if (!lp->project)
|
||||
return NULL;
|
||||
const char *home = getenv("HOME");
|
||||
if (!home) home = "/tmp";
|
||||
if (!home)
|
||||
home = "/tmp";
|
||||
char cache_dir[512];
|
||||
snprintf(cache_dir, sizeof(cache_dir), "%s/.cache/codebase-memory-mcp", home);
|
||||
cbm_mkdir(cache_dir);
|
||||
snprintf(lp->dbpath, sizeof(lp->dbpath), "%s/%s.db", cache_dir, lp->project);
|
||||
unlink(lp->dbpath);
|
||||
lp->srv = cbm_mcp_server_new(NULL);
|
||||
if (!lp->srv) return NULL;
|
||||
if (!lp->srv)
|
||||
return NULL;
|
||||
char args[700];
|
||||
snprintf(args, sizeof(args), "{\"repo_path\":\"%s\"}", lp->tmpdir);
|
||||
char *resp = cbm_mcp_handle_tool(lp->srv, "index_repository", args);
|
||||
if (resp) free(resp);
|
||||
if (resp)
|
||||
free(resp);
|
||||
return cbm_store_open_path(lp->dbpath);
|
||||
}
|
||||
|
||||
static cbm_store_t *gpg_index_files(GpgProj *lp, const GpgFile *files, int nfiles) {
|
||||
memset(lp, 0, sizeof(*lp));
|
||||
snprintf(lp->tmpdir, sizeof(lp->tmpdir), "/tmp/cbm_gpg_XXXXXX");
|
||||
if (!cbm_mkdtemp(lp->tmpdir)) return NULL;
|
||||
if (!cbm_mkdtemp(lp->tmpdir))
|
||||
return NULL;
|
||||
gpg_to_fwd_slashes(lp->tmpdir);
|
||||
for (int i = 0; i < nfiles; i++) {
|
||||
char path[700];
|
||||
@@ -108,7 +114,8 @@ static cbm_store_t *gpg_index_files(GpgProj *lp, const GpgFile *files, int nfile
|
||||
*slash = '/';
|
||||
}
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return NULL;
|
||||
if (!f)
|
||||
return NULL;
|
||||
fputs(files[i].content, f);
|
||||
fclose(f);
|
||||
}
|
||||
@@ -116,8 +123,12 @@ static cbm_store_t *gpg_index_files(GpgProj *lp, const GpgFile *files, int nfile
|
||||
}
|
||||
|
||||
static void gpg_cleanup(GpgProj *lp, cbm_store_t *store) {
|
||||
if (store) cbm_store_close(store);
|
||||
if (lp->srv) { cbm_mcp_server_free(lp->srv); lp->srv = NULL; }
|
||||
if (store)
|
||||
cbm_store_close(store);
|
||||
if (lp->srv) {
|
||||
cbm_mcp_server_free(lp->srv);
|
||||
lp->srv = NULL;
|
||||
}
|
||||
free(lp->project);
|
||||
lp->project = NULL;
|
||||
th_rmtree(lp->tmpdir);
|
||||
@@ -147,8 +158,8 @@ typedef struct {
|
||||
int classes;
|
||||
int variables;
|
||||
int sections;
|
||||
int imports; /* IMPORTS edges */
|
||||
int depends; /* DEPENDS_ON edges */
|
||||
int imports; /* IMPORTS edges */
|
||||
int depends; /* DEPENDS_ON edges */
|
||||
} GpgMetrics;
|
||||
|
||||
static GpgMetrics gpg_metrics_files(const GpgFile *files, int nfiles) {
|
||||
@@ -156,14 +167,14 @@ static GpgMetrics gpg_metrics_files(const GpgFile *files, int nfiles) {
|
||||
cbm_store_t *store = gpg_index_files(&lp, files, nfiles);
|
||||
GpgMetrics m = {0};
|
||||
if (store) {
|
||||
m.ok = 1;
|
||||
m.ok = 1;
|
||||
m.total_nodes = cbm_store_count_nodes(store, lp.project);
|
||||
m.modules = gpg_count_label(store, lp.project, "Module");
|
||||
m.classes = gpg_count_label(store, lp.project, "Class");
|
||||
m.variables = gpg_count_label(store, lp.project, "Variable");
|
||||
m.sections = gpg_count_label(store, lp.project, "Section");
|
||||
m.imports = cbm_store_count_edges_by_type(store, lp.project, "IMPORTS");
|
||||
m.depends = cbm_store_count_edges_by_type(store, lp.project, "DEPENDS_ON");
|
||||
m.modules = gpg_count_label(store, lp.project, "Module");
|
||||
m.classes = gpg_count_label(store, lp.project, "Class");
|
||||
m.variables = gpg_count_label(store, lp.project, "Variable");
|
||||
m.sections = gpg_count_label(store, lp.project, "Section");
|
||||
m.imports = cbm_store_count_edges_by_type(store, lp.project, "IMPORTS");
|
||||
m.depends = cbm_store_count_edges_by_type(store, lp.project, "DEPENDS_ON");
|
||||
}
|
||||
gpg_cleanup(&lp, store);
|
||||
return m;
|
||||
@@ -184,10 +195,9 @@ static GpgMetrics gpg_metrics(const char *filename, const char *content) {
|
||||
|
||||
/* HTML: minimal page → at least 1 Module node, no crash. */
|
||||
TEST(probe_html_minimal_page) {
|
||||
GpgMetrics m = gpg_metrics("index.html",
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html><head><title>Test</title></head>\n"
|
||||
"<body><h1>Hello</h1></body></html>\n");
|
||||
GpgMetrics m = gpg_metrics("index.html", "<!DOCTYPE html>\n"
|
||||
"<html><head><title>Test</title></head>\n"
|
||||
"<body><h1>Hello</h1></body></html>\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: HTML always produces at least a Module node. */
|
||||
ASSERT_TRUE(m.modules >= 1);
|
||||
@@ -198,15 +208,11 @@ TEST(probe_html_minimal_page) {
|
||||
* RED: no HTML import resolver; script/link attributes are not walked into
|
||||
* IMPORTS edges by the pipeline. */
|
||||
TEST(probe_html_script_imports) {
|
||||
static const GpgFile files[] = {
|
||||
{"app.js",
|
||||
"function init() { console.log('ready'); }\n"},
|
||||
{"index.html",
|
||||
"<!DOCTYPE html><html><head>\n"
|
||||
"<link rel=\"stylesheet\" href=\"style.css\">\n"
|
||||
"<script src=\"app.js\"></script>\n"
|
||||
"</head><body></body></html>\n"}
|
||||
};
|
||||
static const GpgFile files[] = {{"app.js", "function init() { console.log('ready'); }\n"},
|
||||
{"index.html", "<!DOCTYPE html><html><head>\n"
|
||||
"<link rel=\"stylesheet\" href=\"style.css\">\n"
|
||||
"<script src=\"app.js\"></script>\n"
|
||||
"</head><body></body></html>\n"}};
|
||||
GpgMetrics m = gpg_metrics_files(files, 2);
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* RED: HTML <script src> / <link href> not resolved into IMPORTS edges. */
|
||||
@@ -216,8 +222,7 @@ TEST(probe_html_script_imports) {
|
||||
|
||||
/* HTML: pure-data — no Class/Variable/Section nodes expected. */
|
||||
TEST(probe_html_no_structural_nodes) {
|
||||
GpgMetrics m = gpg_metrics("page.html",
|
||||
"<html><body><p>text</p></body></html>\n");
|
||||
GpgMetrics m = gpg_metrics("page.html", "<html><body><p>text</p></body></html>\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: HTML produces zero Class/Variable/Section nodes (pure-data). */
|
||||
ASSERT_TRUE(m.classes == 0);
|
||||
@@ -236,12 +241,11 @@ TEST(probe_html_no_structural_nodes) {
|
||||
|
||||
/* XML: minimal document → Module node + at least 2 Class nodes. */
|
||||
TEST(probe_xml_element_nodes) {
|
||||
GpgMetrics m = gpg_metrics("config.xml",
|
||||
"<?xml version=\"1.0\"?>\n"
|
||||
"<config>\n"
|
||||
" <server host=\"localhost\" port=\"8080\"/>\n"
|
||||
" <database name=\"mydb\"/>\n"
|
||||
"</config>\n");
|
||||
GpgMetrics m = gpg_metrics("config.xml", "<?xml version=\"1.0\"?>\n"
|
||||
"<config>\n"
|
||||
" <server host=\"localhost\" port=\"8080\"/>\n"
|
||||
" <database name=\"mydb\"/>\n"
|
||||
"</config>\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: XML extractor produces Class nodes for elements. */
|
||||
ASSERT_TRUE(m.classes >= 2);
|
||||
@@ -251,12 +255,11 @@ TEST(probe_xml_element_nodes) {
|
||||
|
||||
/* XML: more elements → more Class nodes (≥ 2). */
|
||||
TEST(probe_xml_multiple_elements) {
|
||||
GpgMetrics m = gpg_metrics("pom.xml",
|
||||
"<project>\n"
|
||||
" <groupId>com.example</groupId>\n"
|
||||
" <artifactId>demo</artifactId>\n"
|
||||
" <version>1.0</version>\n"
|
||||
"</project>\n");
|
||||
GpgMetrics m = gpg_metrics("pom.xml", "<project>\n"
|
||||
" <groupId>com.example</groupId>\n"
|
||||
" <artifactId>demo</artifactId>\n"
|
||||
" <version>1.0</version>\n"
|
||||
"</project>\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: multiple element types must produce Class nodes. */
|
||||
ASSERT_TRUE(m.classes >= 2);
|
||||
@@ -265,8 +268,7 @@ TEST(probe_xml_multiple_elements) {
|
||||
|
||||
/* XML: no-crash guard for empty document. */
|
||||
TEST(probe_xml_no_crash) {
|
||||
GpgMetrics m = gpg_metrics("empty.xml",
|
||||
"<?xml version=\"1.0\"?><root/>\n");
|
||||
GpgMetrics m = gpg_metrics("empty.xml", "<?xml version=\"1.0\"?><root/>\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
ASSERT_TRUE(m.total_nodes >= 1);
|
||||
PASS();
|
||||
@@ -282,13 +284,12 @@ TEST(probe_xml_no_crash) {
|
||||
|
||||
/* CSS: rules file → only a Module node. */
|
||||
TEST(probe_css_module_only) {
|
||||
GpgMetrics m = gpg_metrics("styles.css",
|
||||
"body {\n"
|
||||
" margin: 0;\n"
|
||||
" font-family: sans-serif;\n"
|
||||
"}\n"
|
||||
".container { max-width: 1200px; }\n"
|
||||
"#header { background: #fff; }\n");
|
||||
GpgMetrics m = gpg_metrics("styles.css", "body {\n"
|
||||
" margin: 0;\n"
|
||||
" font-family: sans-serif;\n"
|
||||
"}\n"
|
||||
".container { max-width: 1200px; }\n"
|
||||
"#header { background: #fff; }\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: CSS produces exactly 1 Module node; no Class/Variable/Section. */
|
||||
ASSERT_TRUE(m.modules == 1);
|
||||
@@ -300,13 +301,9 @@ TEST(probe_css_module_only) {
|
||||
/* CSS: @import in two-file fixture → IMPORTS edge.
|
||||
* RED: no CSS import resolver in the pipeline. */
|
||||
TEST(probe_css_at_import) {
|
||||
static const GpgFile files[] = {
|
||||
{"base.css",
|
||||
"* { box-sizing: border-box; }\n"},
|
||||
{"main.css",
|
||||
"@import \"base.css\";\n"
|
||||
"body { margin: 0; }\n"}
|
||||
};
|
||||
static const GpgFile files[] = {{"base.css", "* { box-sizing: border-box; }\n"},
|
||||
{"main.css", "@import \"base.css\";\n"
|
||||
"body { margin: 0; }\n"}};
|
||||
GpgMetrics m = gpg_metrics_files(files, 2);
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* RED: CSS @import not resolved into IMPORTS edges. */
|
||||
@@ -324,14 +321,13 @@ TEST(probe_css_at_import) {
|
||||
|
||||
/* SCSS: variables + rules → at least 1 Variable node. */
|
||||
TEST(probe_scss_variable_node) {
|
||||
GpgMetrics m = gpg_metrics("theme.scss",
|
||||
"$primary: #3498db;\n"
|
||||
"$secondary: #2ecc71;\n"
|
||||
"\n"
|
||||
".button {\n"
|
||||
" background: $primary;\n"
|
||||
" color: white;\n"
|
||||
"}\n");
|
||||
GpgMetrics m = gpg_metrics("theme.scss", "$primary: #3498db;\n"
|
||||
"$secondary: #2ecc71;\n"
|
||||
"\n"
|
||||
".button {\n"
|
||||
" background: $primary;\n"
|
||||
" color: white;\n"
|
||||
"}\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: SCSS extracts at least 1 Variable node (histogram: Variable:1). */
|
||||
ASSERT_TRUE(m.variables >= 1);
|
||||
@@ -342,13 +338,9 @@ TEST(probe_scss_variable_node) {
|
||||
/* SCSS: @import / @use in two-file fixture → IMPORTS edge.
|
||||
* RED: no SCSS import resolver in the pipeline. */
|
||||
TEST(probe_scss_at_use_imports) {
|
||||
static const GpgFile files[] = {
|
||||
{"_vars.scss",
|
||||
"$brand: #e74c3c;\n"},
|
||||
{"main.scss",
|
||||
"@use 'vars';\n"
|
||||
".hero { color: vars.$brand; }\n"}
|
||||
};
|
||||
static const GpgFile files[] = {{"_vars.scss", "$brand: #e74c3c;\n"},
|
||||
{"main.scss", "@use 'vars';\n"
|
||||
".hero { color: vars.$brand; }\n"}};
|
||||
GpgMetrics m = gpg_metrics_files(files, 2);
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* RED: SCSS @use / @import not resolved into IMPORTS edges. */
|
||||
@@ -365,14 +357,13 @@ TEST(probe_scss_at_use_imports) {
|
||||
|
||||
/* Markdown: document with headings → at least 1 Section node. */
|
||||
TEST(probe_markdown_section_node) {
|
||||
GpgMetrics m = gpg_metrics("README.md",
|
||||
"# Project Title\n"
|
||||
"\n"
|
||||
"Description text.\n"
|
||||
"\n"
|
||||
"## Getting Started\n"
|
||||
"\n"
|
||||
"Install with `npm install`.\n");
|
||||
GpgMetrics m = gpg_metrics("README.md", "# Project Title\n"
|
||||
"\n"
|
||||
"Description text.\n"
|
||||
"\n"
|
||||
"## Getting Started\n"
|
||||
"\n"
|
||||
"Install with `npm install`.\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: Markdown headings produce Section nodes. */
|
||||
ASSERT_TRUE(m.sections >= 1);
|
||||
@@ -383,11 +374,10 @@ TEST(probe_markdown_section_node) {
|
||||
/* Markdown: multiple headings → at least 1 Section (histogram baseline: Section:1).
|
||||
* The histogram fixture has 1 section; additional headings may or may not add more. */
|
||||
TEST(probe_markdown_multi_heading) {
|
||||
GpgMetrics m = gpg_metrics("docs.md",
|
||||
"# Title\n"
|
||||
"## Setup\n"
|
||||
"### Install\n"
|
||||
"## Usage\n");
|
||||
GpgMetrics m = gpg_metrics("docs.md", "# Title\n"
|
||||
"## Setup\n"
|
||||
"### Install\n"
|
||||
"## Usage\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: at minimum 1 Section node. */
|
||||
ASSERT_TRUE(m.sections >= 1);
|
||||
@@ -396,8 +386,7 @@ TEST(probe_markdown_multi_heading) {
|
||||
|
||||
/* Markdown: no Class/Variable nodes (pure-data beyond Section). */
|
||||
TEST(probe_markdown_no_class_variable) {
|
||||
GpgMetrics m = gpg_metrics("notes.md",
|
||||
"# Notes\n\nSome content.\n");
|
||||
GpgMetrics m = gpg_metrics("notes.md", "# Notes\n\nSome content.\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: Markdown does not produce Class or Variable nodes. */
|
||||
ASSERT_TRUE(m.classes == 0);
|
||||
@@ -414,16 +403,15 @@ TEST(probe_markdown_no_class_variable) {
|
||||
|
||||
/* RST: document with sections → Module:1, no Section nodes. */
|
||||
TEST(probe_rst_module_only) {
|
||||
GpgMetrics m = gpg_metrics("docs.rst",
|
||||
"Project Title\n"
|
||||
"=============\n"
|
||||
"\n"
|
||||
"Description text.\n"
|
||||
"\n"
|
||||
"Installation\n"
|
||||
"------------\n"
|
||||
"\n"
|
||||
"Run ``pip install mypackage``.\n");
|
||||
GpgMetrics m = gpg_metrics("docs.rst", "Project Title\n"
|
||||
"=============\n"
|
||||
"\n"
|
||||
"Description text.\n"
|
||||
"\n"
|
||||
"Installation\n"
|
||||
"------------\n"
|
||||
"\n"
|
||||
"Run ``pip install mypackage``.\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: RST produces only a Module node (no Section nodes extracted). */
|
||||
ASSERT_TRUE(m.modules == 1);
|
||||
@@ -433,9 +421,8 @@ TEST(probe_rst_module_only) {
|
||||
|
||||
/* RST: no-crash guard. */
|
||||
TEST(probe_rst_no_crash) {
|
||||
GpgMetrics m = gpg_metrics("api.rst",
|
||||
".. automodule:: mypackage.api\n"
|
||||
" :members:\n");
|
||||
GpgMetrics m = gpg_metrics("api.rst", ".. automodule:: mypackage.api\n"
|
||||
" :members:\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
ASSERT_TRUE(m.total_nodes >= 1);
|
||||
PASS();
|
||||
@@ -450,11 +437,10 @@ TEST(probe_rst_no_crash) {
|
||||
|
||||
/* JSON: object with two top-level keys → 2 Variable nodes. */
|
||||
TEST(probe_json_variable_nodes) {
|
||||
GpgMetrics m = gpg_metrics("config.json",
|
||||
"{\n"
|
||||
" \"name\": \"myproject\",\n"
|
||||
" \"version\": \"1.0.0\"\n"
|
||||
"}\n");
|
||||
GpgMetrics m = gpg_metrics("config.json", "{\n"
|
||||
" \"name\": \"myproject\",\n"
|
||||
" \"version\": \"1.0.0\"\n"
|
||||
"}\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: JSON top-level keys become Variable nodes. */
|
||||
ASSERT_TRUE(m.variables >= 2);
|
||||
@@ -468,12 +454,11 @@ TEST(probe_json_more_variables) {
|
||||
* detect_file_language() returns CBM_LANG_COUNT and the file is never
|
||||
* indexed (0 nodes). Use a generic .json filename so the JSON grammar
|
||||
* actually runs (grammar_labels json=Variable:2). */
|
||||
GpgMetrics m = gpg_metrics("data.json",
|
||||
"{\n"
|
||||
" \"name\": \"demo\",\n"
|
||||
" \"version\": \"0.1.0\",\n"
|
||||
" \"description\": \"A demo project\"\n"
|
||||
"}\n");
|
||||
GpgMetrics m = gpg_metrics("data.json", "{\n"
|
||||
" \"name\": \"demo\",\n"
|
||||
" \"version\": \"0.1.0\",\n"
|
||||
" \"description\": \"A demo project\"\n"
|
||||
"}\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: at least 2 Variable nodes (histogram baseline). */
|
||||
ASSERT_TRUE(m.variables >= 2);
|
||||
@@ -489,12 +474,11 @@ TEST(probe_json_more_variables) {
|
||||
|
||||
/* JSON5: file with keys → only Module node. */
|
||||
TEST(probe_json5_module_only) {
|
||||
GpgMetrics m = gpg_metrics("config.json5",
|
||||
"{\n"
|
||||
" // comments allowed\n"
|
||||
" name: 'myproject',\n"
|
||||
" version: '1.0',\n"
|
||||
"}\n");
|
||||
GpgMetrics m = gpg_metrics("config.json5", "{\n"
|
||||
" // comments allowed\n"
|
||||
" name: 'myproject',\n"
|
||||
" version: '1.0',\n"
|
||||
"}\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: JSON5 produces only a Module node (no Variable nodes unlike JSON). */
|
||||
ASSERT_TRUE(m.modules == 1);
|
||||
@@ -511,9 +495,8 @@ TEST(probe_json5_module_only) {
|
||||
|
||||
/* YAML: document with two top-level keys → 2 Variable nodes. */
|
||||
TEST(probe_yaml_variable_nodes) {
|
||||
GpgMetrics m = gpg_metrics("config.yaml",
|
||||
"name: myproject\n"
|
||||
"version: 1.0.0\n");
|
||||
GpgMetrics m = gpg_metrics("config.yaml", "name: myproject\n"
|
||||
"version: 1.0.0\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: YAML top-level keys become Variable nodes. */
|
||||
ASSERT_TRUE(m.variables >= 2);
|
||||
@@ -523,10 +506,9 @@ TEST(probe_yaml_variable_nodes) {
|
||||
|
||||
/* YAML: three top-level keys → still ≥ 2 Variable nodes (baseline). */
|
||||
TEST(probe_yaml_three_keys) {
|
||||
GpgMetrics m = gpg_metrics("app.yaml",
|
||||
"host: localhost\n"
|
||||
"port: 8080\n"
|
||||
"debug: true\n");
|
||||
GpgMetrics m = gpg_metrics("app.yaml", "host: localhost\n"
|
||||
"port: 8080\n"
|
||||
"debug: true\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: at least 2 Variable nodes. */
|
||||
ASSERT_TRUE(m.variables >= 2);
|
||||
@@ -542,10 +524,9 @@ TEST(probe_yaml_three_keys) {
|
||||
|
||||
/* TOML: file with table + key → Class:1 + Variable:1. */
|
||||
TEST(probe_toml_table_and_key) {
|
||||
GpgMetrics m = gpg_metrics("Cargo.toml",
|
||||
"[package]\n"
|
||||
"name = \"mylib\"\n"
|
||||
"version = \"0.1.0\"\n");
|
||||
GpgMetrics m = gpg_metrics("Cargo.toml", "[package]\n"
|
||||
"name = \"mylib\"\n"
|
||||
"version = \"0.1.0\"\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: TOML table [package] becomes a Class node. */
|
||||
ASSERT_TRUE(m.classes >= 1);
|
||||
@@ -557,12 +538,11 @@ TEST(probe_toml_table_and_key) {
|
||||
|
||||
/* TOML: multiple tables → ≥ 1 Class node (may be exactly 1 per fixture). */
|
||||
TEST(probe_toml_multiple_tables) {
|
||||
GpgMetrics m = gpg_metrics("pyproject.toml",
|
||||
"[tool.poetry]\n"
|
||||
"name = \"demo\"\n"
|
||||
"\n"
|
||||
"[tool.poetry.dependencies]\n"
|
||||
"python = \"^3.9\"\n");
|
||||
GpgMetrics m = gpg_metrics("pyproject.toml", "[tool.poetry]\n"
|
||||
"name = \"demo\"\n"
|
||||
"\n"
|
||||
"[tool.poetry.dependencies]\n"
|
||||
"python = \"^3.9\"\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: at least one Class node for tables. */
|
||||
ASSERT_TRUE(m.classes >= 1);
|
||||
@@ -578,10 +558,9 @@ TEST(probe_toml_multiple_tables) {
|
||||
|
||||
/* INI: file with a section and a key → Class + Variable. */
|
||||
TEST(probe_ini_section_and_key) {
|
||||
GpgMetrics m = gpg_metrics("app.ini",
|
||||
"[database]\n"
|
||||
"host = localhost\n"
|
||||
"port = 5432\n");
|
||||
GpgMetrics m = gpg_metrics("app.ini", "[database]\n"
|
||||
"host = localhost\n"
|
||||
"port = 5432\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: [database] section becomes a Class node. */
|
||||
ASSERT_TRUE(m.classes >= 1);
|
||||
@@ -593,9 +572,8 @@ TEST(probe_ini_section_and_key) {
|
||||
|
||||
/* INI: .conf extension also maps to CBM_LANG_INI; must produce same structure. */
|
||||
TEST(probe_ini_conf_extension) {
|
||||
GpgMetrics m = gpg_metrics("server.conf",
|
||||
"[server]\n"
|
||||
"listen = 0.0.0.0\n");
|
||||
GpgMetrics m = gpg_metrics("server.conf", "[server]\n"
|
||||
"listen = 0.0.0.0\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: .conf files index as INI and produce Class + Variable nodes. */
|
||||
ASSERT_TRUE(m.classes >= 1);
|
||||
@@ -612,11 +590,10 @@ TEST(probe_ini_conf_extension) {
|
||||
|
||||
/* CSV: file with header + rows → only Module node. */
|
||||
TEST(probe_csv_module_only) {
|
||||
GpgMetrics m = gpg_metrics("data.csv",
|
||||
"id,name,score\n"
|
||||
"1,Alice,95\n"
|
||||
"2,Bob,87\n"
|
||||
"3,Carol,92\n");
|
||||
GpgMetrics m = gpg_metrics("data.csv", "id,name,score\n"
|
||||
"1,Alice,95\n"
|
||||
"2,Bob,87\n"
|
||||
"3,Carol,92\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: CSV produces only a Module node. */
|
||||
ASSERT_TRUE(m.modules == 1);
|
||||
@@ -634,13 +611,12 @@ TEST(probe_csv_module_only) {
|
||||
|
||||
/* SQL: CREATE TABLE + SELECT → at least 1 Variable node. */
|
||||
TEST(probe_sql_variable_node) {
|
||||
GpgMetrics m = gpg_metrics("schema.sql",
|
||||
"CREATE TABLE users (\n"
|
||||
" id INTEGER PRIMARY KEY,\n"
|
||||
" name TEXT NOT NULL\n"
|
||||
");\n"
|
||||
"\n"
|
||||
"SELECT id, name FROM users WHERE id = 1;\n");
|
||||
GpgMetrics m = gpg_metrics("schema.sql", "CREATE TABLE users (\n"
|
||||
" id INTEGER PRIMARY KEY,\n"
|
||||
" name TEXT NOT NULL\n"
|
||||
");\n"
|
||||
"\n"
|
||||
"SELECT id, name FROM users WHERE id = 1;\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: SQL table reference produces at least 1 Variable node. */
|
||||
ASSERT_TRUE(m.variables >= 1);
|
||||
@@ -650,9 +626,9 @@ TEST(probe_sql_variable_node) {
|
||||
|
||||
/* SQL: multiple statements → still at least 1 Variable. */
|
||||
TEST(probe_sql_insert_select) {
|
||||
GpgMetrics m = gpg_metrics("queries.sql",
|
||||
"INSERT INTO orders (user_id, total) VALUES (1, 99.99);\n"
|
||||
"SELECT o.id, u.name FROM orders o JOIN users u ON o.user_id = u.id;\n");
|
||||
GpgMetrics m = gpg_metrics(
|
||||
"queries.sql", "INSERT INTO orders (user_id, total) VALUES (1, 99.99);\n"
|
||||
"SELECT o.id, u.name FROM orders o JOIN users u ON o.user_id = u.id;\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN (fixture fix): SQL Variable nodes come ONLY from DDL
|
||||
* create_table/create_view (lang_specs.c sql_var_types), NOT from
|
||||
@@ -672,11 +648,10 @@ TEST(probe_sql_insert_select) {
|
||||
|
||||
/* SOQL: SELECT query → only Module node. */
|
||||
TEST(probe_soql_module_only) {
|
||||
GpgMetrics m = gpg_metrics("query.soql",
|
||||
"SELECT Id, Name, Email\n"
|
||||
"FROM Contact\n"
|
||||
"WHERE AccountId = :accountId\n"
|
||||
"ORDER BY Name\n");
|
||||
GpgMetrics m = gpg_metrics("query.soql", "SELECT Id, Name, Email\n"
|
||||
"FROM Contact\n"
|
||||
"WHERE AccountId = :accountId\n"
|
||||
"ORDER BY Name\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: SOQL produces only a Module node. */
|
||||
ASSERT_TRUE(m.modules == 1);
|
||||
@@ -693,9 +668,8 @@ TEST(probe_soql_module_only) {
|
||||
|
||||
/* SOSL: FIND query → only Module node. */
|
||||
TEST(probe_sosl_module_only) {
|
||||
GpgMetrics m = gpg_metrics("search.sosl",
|
||||
"FIND {Acme} IN ALL FIELDS\n"
|
||||
"RETURNING Account(Id, Name), Contact(Id, Name)\n");
|
||||
GpgMetrics m = gpg_metrics("search.sosl", "FIND {Acme} IN ALL FIELDS\n"
|
||||
"RETURNING Account(Id, Name), Contact(Id, Name)\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: SOSL produces only a Module node. */
|
||||
ASSERT_TRUE(m.modules == 1);
|
||||
@@ -713,10 +687,9 @@ TEST(probe_sosl_module_only) {
|
||||
|
||||
/* DotEnv: .env file → only Module node. */
|
||||
TEST(probe_dotenv_module_only) {
|
||||
GpgMetrics m = gpg_metrics(".env",
|
||||
"DATABASE_URL=postgres://localhost/mydb\n"
|
||||
"SECRET_KEY=supersecret\n"
|
||||
"DEBUG=true\n");
|
||||
GpgMetrics m = gpg_metrics(".env", "DATABASE_URL=postgres://localhost/mydb\n"
|
||||
"SECRET_KEY=supersecret\n"
|
||||
"DEBUG=true\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* REAL BUG (NEW class — file-index routing gap): the DotEnv grammar +
|
||||
* histogram (grammar_labels dotenv=Module:1) exist and work via DIRECT
|
||||
@@ -731,9 +704,8 @@ TEST(probe_dotenv_module_only) {
|
||||
|
||||
/* DotEnv: .env.local suffix variant → indexed as DotEnv. */
|
||||
TEST(probe_dotenv_local_suffix) {
|
||||
GpgMetrics m = gpg_metrics(".env.local",
|
||||
"API_KEY=local-test-key\n"
|
||||
"PORT=3001\n");
|
||||
GpgMetrics m = gpg_metrics(".env.local", "API_KEY=local-test-key\n"
|
||||
"PORT=3001\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* REAL BUG (NEW class — file-index routing gap): ".env.local" has no
|
||||
* FILENAME_TABLE/EXT_TABLE entry, so file-based index_repository never
|
||||
@@ -753,12 +725,11 @@ TEST(probe_dotenv_local_suffix) {
|
||||
|
||||
/* gitignore: pattern file → only Module node. */
|
||||
TEST(probe_gitignore_module_only) {
|
||||
GpgMetrics m = gpg_metrics(".gitignore",
|
||||
"*.o\n"
|
||||
"*.a\n"
|
||||
"build/\n"
|
||||
"dist/\n"
|
||||
".DS_Store\n");
|
||||
GpgMetrics m = gpg_metrics(".gitignore", "*.o\n"
|
||||
"*.a\n"
|
||||
"build/\n"
|
||||
"dist/\n"
|
||||
".DS_Store\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* CONTRACT (corrected): unlike .gitattributes, ".gitignore" is intentionally
|
||||
* NOT indexed as a source file — the cbm_discover walker consumes it as
|
||||
@@ -781,11 +752,10 @@ TEST(probe_gitignore_module_only) {
|
||||
|
||||
/* gitattributes: attribute file → only Module node. */
|
||||
TEST(probe_gitattributes_module_only) {
|
||||
GpgMetrics m = gpg_metrics(".gitattributes",
|
||||
"*.c text eol=lf\n"
|
||||
"*.h text eol=lf\n"
|
||||
"*.png binary\n"
|
||||
"*.jpg binary\n");
|
||||
GpgMetrics m = gpg_metrics(".gitattributes", "*.c text eol=lf\n"
|
||||
"*.h text eol=lf\n"
|
||||
"*.png binary\n"
|
||||
"*.jpg binary\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* REAL BUG (NEW class — file-index routing gap): gitattributes grammar +
|
||||
* histogram (Module:1) work via DIRECT extraction, but ".gitattributes"
|
||||
@@ -806,10 +776,9 @@ TEST(probe_gitattributes_module_only) {
|
||||
|
||||
/* Properties: key=value file → only Module node. */
|
||||
TEST(probe_properties_module_only) {
|
||||
GpgMetrics m = gpg_metrics("app.properties",
|
||||
"server.host=localhost\n"
|
||||
"server.port=8080\n"
|
||||
"log.level=INFO\n");
|
||||
GpgMetrics m = gpg_metrics("app.properties", "server.host=localhost\n"
|
||||
"server.port=8080\n"
|
||||
"log.level=INFO\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: .properties produces only a Module node. */
|
||||
ASSERT_TRUE(m.modules == 1);
|
||||
@@ -829,10 +798,9 @@ TEST(probe_properties_module_only) {
|
||||
|
||||
/* Requirements: dep file → only Module node. */
|
||||
TEST(probe_requirements_module_only) {
|
||||
GpgMetrics m = gpg_metrics("requirements.txt",
|
||||
"flask==2.3.0\n"
|
||||
"requests>=2.28.0\n"
|
||||
"sqlalchemy~=2.0\n");
|
||||
GpgMetrics m = gpg_metrics("requirements.txt", "flask==2.3.0\n"
|
||||
"requests>=2.28.0\n"
|
||||
"sqlalchemy~=2.0\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: requirements.txt produces only a Module node. */
|
||||
ASSERT_TRUE(m.modules == 1);
|
||||
@@ -842,10 +810,9 @@ TEST(probe_requirements_module_only) {
|
||||
/* Requirements: DEPENDS_ON edges for package entries.
|
||||
* RED: grammar-only requirements has no dependency-edge resolver. */
|
||||
TEST(probe_requirements_depends_on_edge) {
|
||||
GpgMetrics m = gpg_metrics("requirements.txt",
|
||||
"numpy==1.24.0\n"
|
||||
"pandas>=1.5.0\n"
|
||||
"scipy>=1.10.0\n");
|
||||
GpgMetrics m = gpg_metrics("requirements.txt", "numpy==1.24.0\n"
|
||||
"pandas>=1.5.0\n"
|
||||
"scipy>=1.10.0\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* RED: requirements deps not resolved into DEPENDS_ON edges. */
|
||||
ASSERT_TRUE(m.depends >= 1); /* expected RED — no requirements edge resolver */
|
||||
@@ -861,18 +828,17 @@ TEST(probe_requirements_depends_on_edge) {
|
||||
|
||||
/* Diff: unified diff → only Module node. */
|
||||
TEST(probe_diff_module_only) {
|
||||
GpgMetrics m = gpg_metrics("changes.diff",
|
||||
"--- a/main.c\n"
|
||||
"+++ b/main.c\n"
|
||||
"@@ -1,5 +1,6 @@\n"
|
||||
" #include <stdio.h>\n"
|
||||
"+#include <stdlib.h>\n"
|
||||
" \n"
|
||||
" int main(void) {\n"
|
||||
"- printf(\"hello\\n\");\n"
|
||||
"+ printf(\"hello world\\n\");\n"
|
||||
" return 0;\n"
|
||||
" }\n");
|
||||
GpgMetrics m = gpg_metrics("changes.diff", "--- a/main.c\n"
|
||||
"+++ b/main.c\n"
|
||||
"@@ -1,5 +1,6 @@\n"
|
||||
" #include <stdio.h>\n"
|
||||
"+#include <stdlib.h>\n"
|
||||
" \n"
|
||||
" int main(void) {\n"
|
||||
"- printf(\"hello\\n\");\n"
|
||||
"+ printf(\"hello world\\n\");\n"
|
||||
" return 0;\n"
|
||||
" }\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: diff produces only a Module node. */
|
||||
ASSERT_TRUE(m.modules == 1);
|
||||
@@ -889,17 +855,16 @@ TEST(probe_diff_module_only) {
|
||||
|
||||
/* PO: translation file → only Module node. */
|
||||
TEST(probe_po_module_only) {
|
||||
GpgMetrics m = gpg_metrics("messages.po",
|
||||
"# Translation file\n"
|
||||
"msgid \"\"\n"
|
||||
"msgstr \"\"\n"
|
||||
"\"Content-Type: text/plain; charset=UTF-8\\n\"\n"
|
||||
"\n"
|
||||
"msgid \"Hello\"\n"
|
||||
"msgstr \"Bonjour\"\n"
|
||||
"\n"
|
||||
"msgid \"Goodbye\"\n"
|
||||
"msgstr \"Au revoir\"\n");
|
||||
GpgMetrics m = gpg_metrics("messages.po", "# Translation file\n"
|
||||
"msgid \"\"\n"
|
||||
"msgstr \"\"\n"
|
||||
"\"Content-Type: text/plain; charset=UTF-8\\n\"\n"
|
||||
"\n"
|
||||
"msgid \"Hello\"\n"
|
||||
"msgstr \"Bonjour\"\n"
|
||||
"\n"
|
||||
"msgid \"Goodbye\"\n"
|
||||
"msgstr \"Au revoir\"\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: PO produces only a Module node. */
|
||||
ASSERT_TRUE(m.modules == 1);
|
||||
@@ -916,10 +881,9 @@ TEST(probe_po_module_only) {
|
||||
|
||||
/* Regex: pattern file → only Module node. */
|
||||
TEST(probe_regex_module_only) {
|
||||
GpgMetrics m = gpg_metrics("patterns.re",
|
||||
"(foo|bar)+\n"
|
||||
"^[a-zA-Z_][a-zA-Z0-9_]*$\n"
|
||||
"\\d{4}-\\d{2}-\\d{2}\n");
|
||||
GpgMetrics m = gpg_metrics("patterns.re", "(foo|bar)+\n"
|
||||
"^[a-zA-Z_][a-zA-Z0-9_]*$\n"
|
||||
"\\d{4}-\\d{2}-\\d{2}\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* REAL BUG (NEW class — file-index routing gap): the Regex grammar +
|
||||
* histogram (grammar_labels regex=Module:1) work via DIRECT extraction
|
||||
@@ -941,15 +905,14 @@ TEST(probe_regex_module_only) {
|
||||
|
||||
/* SSH Config: host file via ssh_config filename → only Module node. */
|
||||
TEST(probe_sshconfig_module_only) {
|
||||
GpgMetrics m = gpg_metrics("ssh_config",
|
||||
"Host bastion\n"
|
||||
" HostName 10.0.0.1\n"
|
||||
" User ubuntu\n"
|
||||
" IdentityFile ~/.ssh/id_rsa\n"
|
||||
"\n"
|
||||
"Host dev\n"
|
||||
" HostName dev.example.com\n"
|
||||
" User deploy\n");
|
||||
GpgMetrics m = gpg_metrics("ssh_config", "Host bastion\n"
|
||||
" HostName 10.0.0.1\n"
|
||||
" User ubuntu\n"
|
||||
" IdentityFile ~/.ssh/id_rsa\n"
|
||||
"\n"
|
||||
"Host dev\n"
|
||||
" HostName dev.example.com\n"
|
||||
" User deploy\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: SSH config produces only a Module node. */
|
||||
ASSERT_TRUE(m.modules == 1);
|
||||
@@ -966,19 +929,18 @@ TEST(probe_sshconfig_module_only) {
|
||||
|
||||
/* BibTeX: bibliography file → only Module node. */
|
||||
TEST(probe_bibtex_module_only) {
|
||||
GpgMetrics m = gpg_metrics("refs.bib",
|
||||
"@article{smith2023,\n"
|
||||
" author = {Smith, John},\n"
|
||||
" title = {A Study of Something},\n"
|
||||
" year = {2023},\n"
|
||||
" journal = {J. Examples}\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"@book{jones2020,\n"
|
||||
" author = {Jones, Alice},\n"
|
||||
" title = {Reference Book},\n"
|
||||
" year = {2020}\n"
|
||||
"}\n");
|
||||
GpgMetrics m = gpg_metrics("refs.bib", "@article{smith2023,\n"
|
||||
" author = {Smith, John},\n"
|
||||
" title = {A Study of Something},\n"
|
||||
" year = {2023},\n"
|
||||
" journal = {J. Examples}\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"@book{jones2020,\n"
|
||||
" author = {Jones, Alice},\n"
|
||||
" title = {Reference Book},\n"
|
||||
" year = {2020}\n"
|
||||
"}\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: BibTeX produces only a Module node. */
|
||||
ASSERT_TRUE(m.modules == 1);
|
||||
@@ -996,11 +958,10 @@ TEST(probe_bibtex_module_only) {
|
||||
|
||||
/* FORM: file with a variable declaration → at least 1 Variable node. */
|
||||
TEST(probe_form_variable_node) {
|
||||
GpgMetrics m = gpg_metrics("calc.frm",
|
||||
"Symbol x, y, z;\n"
|
||||
"Local F = x^2 + y^2 + z^2;\n"
|
||||
"Print;\n"
|
||||
".end\n");
|
||||
GpgMetrics m = gpg_metrics("calc.frm", "Symbol x, y, z;\n"
|
||||
"Local F = x^2 + y^2 + z^2;\n"
|
||||
"Print;\n"
|
||||
".end\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: FORM produces at least 1 Variable node (histogram: Variable:1). */
|
||||
ASSERT_TRUE(m.variables >= 1);
|
||||
@@ -1010,14 +971,13 @@ TEST(probe_form_variable_node) {
|
||||
|
||||
/* FORM: no-crash guard with procedure definition. */
|
||||
TEST(probe_form_procedure) {
|
||||
GpgMetrics m = gpg_metrics("proc.frm",
|
||||
"#procedure Square(x)\n"
|
||||
" id `x'^2 = `x'^2;\n"
|
||||
"#endprocedure\n"
|
||||
"\n"
|
||||
"Symbol a;\n"
|
||||
"Local expr = a^2;\n"
|
||||
".end\n");
|
||||
GpgMetrics m = gpg_metrics("proc.frm", "#procedure Square(x)\n"
|
||||
" id `x'^2 = `x'^2;\n"
|
||||
"#endprocedure\n"
|
||||
"\n"
|
||||
"Symbol a;\n"
|
||||
"Local expr = a^2;\n"
|
||||
".end\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: at least 1 node (Module). */
|
||||
ASSERT_TRUE(m.total_nodes >= 1);
|
||||
@@ -1033,14 +993,13 @@ TEST(probe_form_procedure) {
|
||||
|
||||
/* Linker script: SECTIONS directive → only Module node. */
|
||||
TEST(probe_linkerscript_module_only) {
|
||||
GpgMetrics m = gpg_metrics("link.ld",
|
||||
"SECTIONS\n"
|
||||
"{\n"
|
||||
" . = 0x10000;\n"
|
||||
" .text : { *(.text) }\n"
|
||||
" .data : { *(.data) }\n"
|
||||
" .bss : { *(.bss) }\n"
|
||||
"}\n");
|
||||
GpgMetrics m = gpg_metrics("link.ld", "SECTIONS\n"
|
||||
"{\n"
|
||||
" . = 0x10000;\n"
|
||||
" .text : { *(.text) }\n"
|
||||
" .data : { *(.data) }\n"
|
||||
" .bss : { *(.bss) }\n"
|
||||
"}\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: linker script produces only a Module node. */
|
||||
ASSERT_TRUE(m.modules == 1);
|
||||
@@ -1050,18 +1009,17 @@ TEST(probe_linkerscript_module_only) {
|
||||
|
||||
/* Linker script: MEMORY + SECTIONS → still only Module node. */
|
||||
TEST(probe_linkerscript_memory_sections) {
|
||||
GpgMetrics m = gpg_metrics("stm32.ld",
|
||||
"MEMORY\n"
|
||||
"{\n"
|
||||
" FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K\n"
|
||||
" RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K\n"
|
||||
"}\n"
|
||||
"SECTIONS\n"
|
||||
"{\n"
|
||||
" .isr_vector : { *(.isr_vector) } > FLASH\n"
|
||||
" .text : { *(.text*) } > FLASH\n"
|
||||
" .bss : { *(.bss*) } > RAM\n"
|
||||
"}\n");
|
||||
GpgMetrics m = gpg_metrics("stm32.ld", "MEMORY\n"
|
||||
"{\n"
|
||||
" FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K\n"
|
||||
" RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K\n"
|
||||
"}\n"
|
||||
"SECTIONS\n"
|
||||
"{\n"
|
||||
" .isr_vector : { *(.isr_vector) } > FLASH\n"
|
||||
" .text : { *(.text*) } > FLASH\n"
|
||||
" .bss : { *(.bss*) } > RAM\n"
|
||||
"}\n");
|
||||
ASSERT_TRUE(m.ok);
|
||||
/* GREEN: linker script produces only a Module node. */
|
||||
ASSERT_TRUE(m.modules >= 1);
|
||||
|
||||
+393
-434
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user