Skip to content

Commit 57cd3f9

Browse files
Merge branch 'main' into fix/cypher-coalesce-where-874
2 parents feae294 + 4c60111 commit 57cd3f9

14 files changed

Lines changed: 618 additions & 38 deletions

‎internal/cbm/extract_defs.c‎

Lines changed: 88 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,7 +1317,8 @@ static const char *annotation_route_method(const char *name) {
13171317
}
13181318
/* JAX-RS bare-verb annotations (@GET/@POST/...) — path comes from @Path. */
13191319
if (strcmp(name, "GET") == 0 || strcmp(name, "POST") == 0 || strcmp(name, "PUT") == 0 ||
1320-
strcmp(name, "DELETE") == 0 || strcmp(name, "PATCH") == 0) {
1320+
strcmp(name, "DELETE") == 0 || strcmp(name, "PATCH") == 0 || strcmp(name, "HEAD") == 0 ||
1321+
strcmp(name, "OPTIONS") == 0) {
13211322
return name;
13221323
}
13231324
return NULL;
@@ -1626,35 +1627,85 @@ static bool try_route_from_annotation(CBMArena *a, TSNode annotation, const char
16261627
return true;
16271628
}
16281629

1629-
/* Scan the annotation nodes nested in a JVM/C# `modifiers`/`attribute_list`
1630-
* wrapper (and direct children) for a route-mapping annotation. Java/Kotlin
1631-
* Spring annotations (@GetMapping, @RequestMapping, ...) live here rather than
1632-
* as prev-siblings, so the prev-sibling decorator walk never sees them. */
1633-
static bool extract_route_from_annotations(CBMArena *a, TSNode func_node, const char *source,
1634-
const CBMLangSpec *spec, const char **out_path,
1635-
const char **out_method) {
1636-
TSNode modifiers = find_jvm_modifiers(func_node, spec->language);
1630+
/* Scan ALL annotation nodes nested in a JVM/C# `modifiers`/`attribute_list`
1631+
* wrapper (and direct children), collecting route information from the whole
1632+
* set instead of stopping at the first mapping annotation. Java/Kotlin Spring
1633+
* annotations (@GetMapping, @RequestMapping, ...) live here rather than as
1634+
* prev-siblings, so the prev-sibling decorator walk never sees them.
1635+
*
1636+
* JAX-RS splits the route across two annotations: the verb comes from a bare
1637+
* @GET/@POST/... and the path from a sibling @Path("..."). Returning on the
1638+
* first mapping annotation therefore dropped every method-level @Path
1639+
* (the @GET matched first, defaulted the path to "/", and @Path was never
1640+
* read), and class-level @Path prefixes were never recognized at all. */
1641+
static void scan_route_annotations(CBMArena *a, TSNode owner, const char *source,
1642+
const CBMLangSpec *spec, const char **out_map_path,
1643+
const char **out_method, const char **out_jax_path) {
1644+
*out_map_path = NULL;
1645+
*out_method = NULL;
1646+
*out_jax_path = NULL;
1647+
1648+
TSNode wrappers[2];
1649+
int wn = 0;
1650+
TSNode modifiers = find_jvm_modifiers(owner, spec->language);
16371651
if (!ts_node_is_null(modifiers)) {
1638-
uint32_t mc = ts_node_child_count(modifiers);
1639-
for (uint32_t mi = 0; mi < mc; mi++) {
1640-
TSNode mchild = ts_node_child(modifiers, mi);
1641-
if (cbm_kind_in_set(mchild, spec->decorator_node_types) &&
1642-
try_route_from_annotation(a, mchild, source, out_path, out_method)) {
1643-
return true;
1644-
}
1645-
}
1652+
wrappers[wn++] = modifiers;
16461653
}
16471654
/* Direct-child annotations (some grammars attach the annotation as a child
16481655
* of the method node rather than under `modifiers`). */
1649-
uint32_t cc = ts_node_child_count(func_node);
1650-
for (uint32_t ci = 0; ci < cc; ci++) {
1651-
TSNode child = ts_node_child(func_node, ci);
1652-
if (cbm_kind_in_set(child, spec->decorator_node_types) &&
1653-
try_route_from_annotation(a, child, source, out_path, out_method)) {
1654-
return true;
1656+
wrappers[wn++] = owner;
1657+
1658+
for (int w = 0; w < wn; w++) {
1659+
uint32_t cc = ts_node_child_count(wrappers[w]);
1660+
for (uint32_t ci = 0; ci < cc; ci++) {
1661+
TSNode child = ts_node_child(wrappers[w], ci);
1662+
if (!cbm_kind_in_set(child, spec->decorator_node_types)) {
1663+
continue;
1664+
}
1665+
TSNode name_node = annotation_name_node(child);
1666+
if (ts_node_is_null(name_node)) {
1667+
continue;
1668+
}
1669+
char *name = cbm_node_text(a, name_node, source);
1670+
if (!name) {
1671+
continue;
1672+
}
1673+
if (!*out_jax_path && strcmp(name, "Path") == 0) {
1674+
TSNode args = annotation_args_node(child);
1675+
if (!ts_node_is_null(args)) {
1676+
*out_jax_path = extract_route_path_from_args(a, args, source);
1677+
}
1678+
continue;
1679+
}
1680+
if (!*out_method) {
1681+
const char *method = annotation_route_method(name);
1682+
if (method) {
1683+
*out_method = method;
1684+
TSNode args = annotation_args_node(child);
1685+
if (!ts_node_is_null(args)) {
1686+
*out_map_path = extract_route_path_from_args(a, args, source);
1687+
}
1688+
}
1689+
}
16551690
}
16561691
}
1657-
return false;
1692+
}
1693+
1694+
static bool extract_route_from_annotations(CBMArena *a, TSNode func_node, const char *source,
1695+
const CBMLangSpec *spec, const char **out_path,
1696+
const char **out_method) {
1697+
const char *map_path = NULL;
1698+
const char *method = NULL;
1699+
const char *jax_path = NULL;
1700+
scan_route_annotations(a, func_node, source, spec, &map_path, &method, &jax_path);
1701+
/* Method-level routes still require a verb/mapping annotation; a lone
1702+
* @Path (JAX-RS sub-resource locator) is not an endpoint by itself. */
1703+
if (!method) {
1704+
return false;
1705+
}
1706+
*out_method = method;
1707+
*out_path = map_path ? map_path : (jax_path ? jax_path : "/");
1708+
return true;
16581709
}
16591710

16601711
static void extract_route_from_decorators(CBMArena *a, TSNode func_node, const char *source,
@@ -1723,10 +1774,18 @@ static const char *join_route_paths(CBMArena *a, const char *prefix, const char
17231774

17241775
static const char *spring_class_route_prefix(CBMArena *a, TSNode class_node, const char *source,
17251776
const CBMLangSpec *spec) {
1726-
const char *prefix = NULL;
1777+
const char *map_path = NULL;
17271778
const char *method = NULL;
1728-
if (extract_route_from_annotations(a, class_node, source, spec, &prefix, &method)) {
1729-
return prefix;
1779+
const char *jax_path = NULL;
1780+
scan_route_annotations(a, class_node, source, spec, &map_path, &method, &jax_path);
1781+
if (map_path) {
1782+
return map_path; /* @RequestMapping("/api") and friends */
1783+
}
1784+
if (jax_path) {
1785+
return jax_path; /* JAX-RS class-level @Path("/api") carries no verb */
1786+
}
1787+
if (method) {
1788+
return "/"; /* mapping annotation without a path argument */
17301789
}
17311790
return NULL;
17321791
}
@@ -5901,8 +5960,8 @@ static void extract_class_fields(CBMExtractCtx *ctx, TSNode class_node, const ch
59015960
* For the nested case, the child has no "type" field directly. Detect by
59025961
* walking named children for a variable_declaration. */
59035962
TSNode type_node = ts_node_child_by_field_name(child, TS_FIELD("type"));
5904-
TSNode name_node =
5905-
ts_node_is_null(type_node) ? (TSNode){0} : resolve_field_name_node(child);
5963+
TSNode empty_node = {0};
5964+
TSNode name_node = ts_node_is_null(type_node) ? empty_node : resolve_field_name_node(child);
59065965

59075966
if (ts_node_is_null(type_node)) {
59085967
uint32_t cnc = ts_node_named_child_count(child);

‎src/mcp/mcp.c‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1793,10 +1793,23 @@ static bool db_internal_project_name(const char *full_path, char *name_out, size
17931793
cbm_project_t *projs = NULL;
17941794
int n = 0;
17951795
bool ok = false;
1796-
if (cbm_store_list_projects(st, &projs, &n) == CBM_STORE_OK && n == 1 && projs[0].name &&
1797-
projs[0].name[0]) {
1798-
snprintf(name_out, name_sz, "%s", projs[0].name);
1799-
ok = true;
1796+
if (cbm_store_list_projects(st, &projs, &n) == CBM_STORE_OK) {
1797+
/* Ignore internal shadow projects ("<name>::missed" miss-graph rows):
1798+
* they share the db with the primary project and must not make it
1799+
* unresolvable — requiring n == 1 over ALL rows made every project
1800+
* with a miss graph vanish from list_projects and the UI (#1044). */
1801+
int primary = -1;
1802+
int primary_count = 0;
1803+
for (int i = 0; i < n; i++) {
1804+
if (projs[i].name && projs[i].name[0] && !strstr(projs[i].name, "::")) {
1805+
primary = i;
1806+
primary_count++;
1807+
}
1808+
}
1809+
if (primary_count == 1) {
1810+
snprintf(name_out, name_sz, "%s", projs[primary].name);
1811+
ok = true;
1812+
}
18001813
}
18011814
cbm_store_free_projects(projs, n);
18021815
if (ok && out_store) {

‎src/pipeline/pass_pkgmap.c‎

Lines changed: 159 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,12 @@ static void parse_pyproject_toml(const char *source, int source_len, const char
426426
pkg_entries_push(entries, name, entry);
427427

428428
/* Also register <name>/__init__ as alternative (no src/ prefix) */
429-
snprintf(suffix, sizeof(suffix), "%s/__init__", name_copy);
430-
char *alt_entry = build_entry_path(rel_path, suffix);
429+
char *alt_entry = NULL;
430+
if (name_copy) {
431+
snprintf(suffix, sizeof(suffix), "%s/__init__", name_copy);
432+
alt_entry = build_entry_path(rel_path, suffix);
433+
}
434+
431435
if (name_copy && alt_entry) {
432436
pkg_entries_push(entries, name_copy, alt_entry);
433437
} else {
@@ -1265,6 +1269,151 @@ static bool import_targetable_label(const char *label) {
12651269
return false;
12661270
}
12671271

1272+
static const char *path_leaf(const char *path) {
1273+
const char *leaf = path;
1274+
for (const char *p = path; p && *p; p++) {
1275+
if (*p == '/' || *p == '\\') {
1276+
leaf = p + 1;
1277+
}
1278+
}
1279+
return leaf;
1280+
}
1281+
1282+
static bool is_header_include(const char *path) {
1283+
if (!path || !path[0]) {
1284+
return false;
1285+
}
1286+
static const char *header_exts[] = {".h", ".hh", ".hpp", ".hxx", ".inc", ".inl", ".ipp", NULL};
1287+
for (const char **ext = header_exts; *ext; ext++) {
1288+
size_t path_len = strlen(path);
1289+
size_t ext_len = strlen(*ext);
1290+
if (path_len > ext_len && strcmp(path + path_len - ext_len, *ext) == 0) {
1291+
return true;
1292+
}
1293+
}
1294+
return false;
1295+
}
1296+
1297+
static bool is_c_family_source(const char *source_rel) {
1298+
if (!source_rel || !source_rel[0]) {
1299+
return false;
1300+
}
1301+
static const char *exts[] = {".c", ".cc", ".cpp", ".cxx", ".c++",
1302+
".h", ".hh", ".hpp", ".hxx", NULL};
1303+
for (const char **ext = exts; *ext; ext++) {
1304+
size_t path_len = strlen(source_rel);
1305+
size_t ext_len = strlen(*ext);
1306+
if (path_len > ext_len && strcmp(source_rel + path_len - ext_len, *ext) == 0) {
1307+
return true;
1308+
}
1309+
}
1310+
return false;
1311+
}
1312+
1313+
static const cbm_gbuf_node_t *resolve_exact_file_node(const cbm_pipeline_ctx_t *ctx,
1314+
const char *file_path,
1315+
const char *source_file_qn) {
1316+
if (!ctx || !file_path || !file_path[0]) {
1317+
return NULL;
1318+
}
1319+
1320+
const char *leaf = path_leaf(file_path);
1321+
if (!leaf || !leaf[0]) {
1322+
return NULL;
1323+
}
1324+
1325+
char stem[PKGMAP_PATH_BUF];
1326+
snprintf(stem, sizeof(stem), "%s", leaf);
1327+
char *last_dot = strrchr(stem, '.');
1328+
if (last_dot && last_dot != stem) {
1329+
*last_dot = '\0';
1330+
}
1331+
1332+
const char *names[2] = {stem[0] ? stem : NULL, leaf};
1333+
const cbm_gbuf_node_t *best = NULL;
1334+
for (int ni = 0; ni < 2; ni++) {
1335+
const char *name = names[ni];
1336+
if (!name || !name[0]) {
1337+
continue;
1338+
}
1339+
1340+
const cbm_gbuf_node_t **hits = NULL;
1341+
int hit_count = 0;
1342+
if (cbm_gbuf_find_by_name(ctx->gbuf, name, &hits, &hit_count) != 0 || !hits) {
1343+
continue;
1344+
}
1345+
1346+
for (int i = 0; i < hit_count; i++) {
1347+
const cbm_gbuf_node_t *cand = hits[i];
1348+
if (!cand || !cand->file_path) {
1349+
continue;
1350+
}
1351+
1352+
/* Tie-breaker: Ensure the candidate's actual file_path ends with the
1353+
* specific include path requested (e.g., 'a/util.h' instead of just 'util.h'). */
1354+
if (!ends_with(cand->file_path, file_path)) {
1355+
continue;
1356+
}
1357+
1358+
if (!cand->label || !import_targetable_label(cand->label)) {
1359+
continue;
1360+
}
1361+
if (source_file_qn && cand->qualified_name &&
1362+
strcmp(cand->qualified_name, source_file_qn) == 0) {
1363+
continue;
1364+
}
1365+
if (strcmp(cand->label, "File") == 0) {
1366+
return cand;
1367+
}
1368+
if (!best) {
1369+
best = cand;
1370+
}
1371+
}
1372+
if (best) {
1373+
return best;
1374+
}
1375+
}
1376+
return NULL;
1377+
}
1378+
1379+
static const cbm_gbuf_node_t *resolve_header_include(const cbm_pipeline_ctx_t *ctx,
1380+
const char *source_rel,
1381+
const char *source_file_qn,
1382+
const char *module_path) {
1383+
if (!is_c_family_source(source_rel) || !is_header_include(module_path)) {
1384+
return NULL;
1385+
}
1386+
1387+
const char *base = module_path;
1388+
if (base[0] == '.' && base[1] == '/') {
1389+
base += 2;
1390+
}
1391+
1392+
const cbm_gbuf_node_t *exact = resolve_exact_file_node(ctx, base, source_file_qn);
1393+
if (exact) {
1394+
return exact;
1395+
}
1396+
1397+
if (source_rel && source_rel[0]) {
1398+
char *dir = path_dirname(source_rel);
1399+
if (dir) {
1400+
char candidate[PKGMAP_PATH_BUF];
1401+
if (dir[0]) {
1402+
snprintf(candidate, sizeof(candidate), "%s/%s", dir, base);
1403+
} else {
1404+
snprintf(candidate, sizeof(candidate), "%s", base);
1405+
}
1406+
free(dir);
1407+
exact = resolve_exact_file_node(ctx, candidate, source_file_qn);
1408+
if (exact) {
1409+
return exact;
1410+
}
1411+
}
1412+
}
1413+
1414+
return NULL;
1415+
}
1416+
12681417
/* Resolve a sibling-file import: a bare path/name (no leading "./") that names
12691418
* a file relative to the importer's directory. This covers build/markup
12701419
* grammars whose import string is a sibling filename or directory rather than a
@@ -1352,6 +1501,14 @@ const cbm_gbuf_node_t *cbm_pipeline_resolve_import_node(const cbm_pipeline_ctx_t
13521501
return NULL;
13531502
}
13541503

1504+
/* Prefer exact header-file nodes for C/C++ includes so same-stem source or
1505+
* module nodes do not steal the edge target. */
1506+
const cbm_gbuf_node_t *header_target =
1507+
resolve_header_include(ctx, source_rel, source_file_qn, imp->module_path);
1508+
if (header_target) {
1509+
return header_target;
1510+
}
1511+
13551512
/* Strategy 1: module-path resolution → existing node (Python/TS/Go).
13561513
* No label filter here: directory-module languages (Go/Java packages)
13571514
* legitimately resolve straight to a Folder node -- that's the intended,

‎src/pipeline/pipeline_incremental.c‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -885,8 +885,17 @@ int cbm_pipeline_run_incremental(cbm_pipeline_t *p, const char *db_path, cbm_fil
885885
for (int i = 0; i < ci; i++) {
886886
char *file_qn = cbm_pipeline_fqn_compute(project, changed_files[i].rel_path, "__file__");
887887
if (file_qn) {
888-
cbm_gbuf_upsert_node(existing, "File", changed_files[i].rel_path, file_qn,
889-
changed_files[i].rel_path, 0, 0, "{}");
888+
/* #994: the name must be the BASENAME with extension props,
889+
* mirroring the full build's File node (pipeline.c) — upserts
890+
* match by QN, so any other name here renames the node in place
891+
* and the incremental graph diverges from a full build. */
892+
const char *rel = changed_files[i].rel_path;
893+
const char *slash = strrchr(rel, '/');
894+
const char *basename = slash ? slash + SKIP_ONE : rel;
895+
char props[CBM_SZ_256];
896+
const char *ext = strrchr(basename, '.');
897+
snprintf(props, sizeof(props), "{\"extension\":\"%s\"}", ext ? ext : "");
898+
cbm_gbuf_upsert_node(existing, "File", basename, file_qn, rel, 0, 0, props);
890899
free(file_qn);
891900
}
892901
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#include "NodeController.h"
2+
3+
int node_controller_value() {
4+
return 1;
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#pragma once
2+
3+
struct NodeController {
4+
int value;
5+
};

0 commit comments

Comments
 (0)