| 3554 | } |
| 3555 | |
| 3556 | static void extract_func_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec) { |
| 3557 | CBMArena *a = ctx->arena; |
| 3558 | |
| 3559 | TSNode name_node = cbm_resolve_func_name(node, ctx->language); |
| 3560 | if (ts_node_is_null(name_node)) { |
| 3561 | return; |
| 3562 | } |
| 3563 | |
| 3564 | char *name = cbm_func_name_node_text(a, name_node, ctx->source, ctx->language); |
| 3565 | if (!name || !name[0] || strcmp(name, "function") == 0) { |
| 3566 | return; |
| 3567 | } |
| 3568 | |
| 3569 | // Makefile special targets (.PHONY, .DEFAULT, .SUFFIXES, …) are directives, |
| 3570 | // not build-rule defs. Their leading '.' would also make cbm_fqn_compute |
| 3571 | // emit a "..PHONY" segment (a "double dot") and thus a malformed QN. Skip |
| 3572 | // any dot-prefixed Make target. |
| 3573 | if (ctx->language == CBM_LANG_MAKEFILE && name[0] == '.') { |
| 3574 | return; |
| 3575 | } |
| 3576 | |
| 3577 | /* C++/CUDA: GoogleTest macros (TEST, TEST_F, TEST_P, ...) parse as |
| 3578 | * function definitions whose name resolves to the bare macro identifier. |
| 3579 | * Multiple test cases per file collide on qualified name; derive a unique |
| 3580 | * name from the macro arguments so each gets its own graph node (#1266). */ |
| 3581 | bool is_gtest = false; |
| 3582 | if ((ctx->language == CBM_LANG_CPP || ctx->language == CBM_LANG_CUDA) && |
| 3583 | is_cpp_test_macro(name)) { |
| 3584 | char *gtest_name = resolve_cpp_test_macro_name(a, name, node, ctx->source); |
| 3585 | if (gtest_name) { |
| 3586 | name = gtest_name; |
| 3587 | is_gtest = true; |
| 3588 | } |
| 3589 | } |
| 3590 | |
| 3591 | /* Nix `"${foo}" = x: …`: an interpolated attrpath segment has no statically |
| 3592 | * knowable name. Minting it would produce a def literally named `"${foo}"` |
| 3593 | * that nothing can ever look up or resolve a call against. An absent node is |
| 3594 | * the honest answer — same reasoning as the Makefile guard above. */ |
| 3595 | if (ctx->language == CBM_LANG_NIX && cbm_nix_attr_is_interpolated(name_node)) { |
| 3596 | return; |
| 3597 | } |
| 3598 | |
| 3599 | TSNode func_node = unwrap_template_inner(node, ctx->language); |
| 3600 | |
| 3601 | CBMDefinition def; |
| 3602 | memset(&def, 0, sizeof(def)); |
| 3603 | |
| 3604 | def.name = name; |
| 3605 | /* Nix: a binding's name is a path. The leaf is the name; the leading segments |
| 3606 | * are scope, so `a.b.fn = …` gets the same QN as `a = { b = { fn = …; }; }`. |
| 3607 | * Without this every binding whose path shares a leaf name collapsed onto one |
| 3608 | * node, silently discarding the later definition and its CALLS edges. */ |
| 3609 | const char *qn_name = name; |
| 3610 | if (ctx->language == CBM_LANG_NIX) { |
| 3611 | qn_name = cbm_nix_qn_name(a, node, ctx->source, name); |
| 3612 | } |
| 3613 | /* Java/Go derive the module from the containing directory (package), so the |
no test coverage detected