mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-10-02 04:54:47 +08:00
fix: suppress spurious route nodes
Tighten service-pattern matching so short framework ids such as gin. do not match unrelated QNs like plugin.*, while keeping separator-based wrappers such as requests_get working. Reject bare HTTP-verb decorators like unittest.mock.patch as route decorators unless they have a receiver, and skip package manifests when promoting infra URL string refs to Route nodes. Validation: build/c/test-runner builds; CBM_ONLY_SUITE=infrascan 4/4; extraction 195/195; depindex 32/32; lang_contract 31/31; edge_types_probe 52/52; isolated autorun index now stores 0 Route nodes. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
@@ -1247,16 +1247,27 @@ static bool try_route_from_decorator_call(CBMArena *a, TSNode dchild, const char
|
||||
if (!method) {
|
||||
return false;
|
||||
}
|
||||
const char *dot = fn_text ? strrchr(fn_text, '.') : NULL;
|
||||
bool has_receiver = dot && dot[SKIP_CHAR] != '\0';
|
||||
bool is_generic_route = fn_text &&
|
||||
(strcmp(dot ? dot + SKIP_CHAR : fn_text, "route") == 0 ||
|
||||
strcmp(dot ? dot + SKIP_CHAR : fn_text, "api_route") == 0);
|
||||
|
||||
TSNode args = find_decorator_args(dchild);
|
||||
if (!ts_node_is_null(args)) {
|
||||
const char *path = extract_route_path_from_args(a, args, source);
|
||||
if (path) {
|
||||
if (!has_receiver && !is_generic_route) {
|
||||
return false;
|
||||
}
|
||||
*out_path = path;
|
||||
*out_method = method;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!has_receiver) {
|
||||
return false;
|
||||
}
|
||||
*out_path = "/";
|
||||
*out_method = method;
|
||||
return true;
|
||||
|
||||
@@ -95,6 +95,7 @@ static const lib_pattern_t http_libraries[] = {
|
||||
{"Net::HTTP", CBM_SVC_HTTP, NULL},
|
||||
|
||||
/* PHP */
|
||||
{"GuzzleHttp", CBM_SVC_HTTP, NULL},
|
||||
{"Guzzle", CBM_SVC_HTTP, NULL},
|
||||
{"guzzle", CBM_SVC_HTTP, NULL},
|
||||
{"curl", CBM_SVC_HTTP, NULL},
|
||||
@@ -534,17 +535,41 @@ static const method_suffix_t method_suffixes[] = {
|
||||
|
||||
/* ── Matching implementation ───────────────────────────────────── */
|
||||
|
||||
/* Check if any library identifier appears as a substring in the QN.
|
||||
* Case-sensitive: "requests" matches "project.venv.requests.api.get"
|
||||
* but not "Requests". Library names are specific enough to avoid
|
||||
* false positives even with substring matching. */
|
||||
static bool qn_token_char(char ch) {
|
||||
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
|
||||
(ch >= '0' && ch <= '9');
|
||||
}
|
||||
|
||||
static bool qn_pattern_occurrence_matches(const char *qn, const char *hit,
|
||||
const char *pattern) {
|
||||
size_t plen = strlen(pattern);
|
||||
if (plen == 0) {
|
||||
return false;
|
||||
}
|
||||
if (qn_token_char(pattern[0]) && hit > qn && qn_token_char(hit[-1])) {
|
||||
return false;
|
||||
}
|
||||
if (qn_token_char(pattern[plen - 1]) && qn_token_char(hit[plen])) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check if a library identifier appears as a token-aligned substring in the QN.
|
||||
* Case-sensitive: "requests" matches "project.venv.requests.api.get" but not
|
||||
* "myrequests". The boundary check also prevents short framework ids like
|
||||
* "gin." from firing inside unrelated names such as "plugin.". */
|
||||
static const lib_pattern_t *match_qn(const char *qn, const lib_pattern_t *patterns) {
|
||||
if (!qn || !qn[0]) {
|
||||
return NULL;
|
||||
}
|
||||
for (int i = 0; patterns[i].library_id != NULL; i++) {
|
||||
if (strstr(qn, patterns[i].library_id) != NULL) {
|
||||
return &patterns[i];
|
||||
const char *p = qn;
|
||||
while ((p = strstr(p, patterns[i].library_id)) != NULL) {
|
||||
if (qn_pattern_occurrence_matches(qn, p, patterns[i].library_id)) {
|
||||
return &patterns[i];
|
||||
}
|
||||
p++;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
|
||||
@@ -24,6 +24,7 @@ enum { CBM_DIR_PERMS = 0755, PL_RING = 4, PL_RING_MASK = 3, PL_SEQ_PASSES = 6 };
|
||||
#include "store/store.h"
|
||||
#include "discover/discover.h"
|
||||
#include "discover/userconfig.h"
|
||||
#include "depindex/depindex.h"
|
||||
#include "foundation/platform.h"
|
||||
#include "foundation/compat_fs.h"
|
||||
#include "foundation/log.h"
|
||||
@@ -621,6 +622,9 @@ static void cbm_pipeline_process_infra_bindings(cbm_gbuf_t *gbuf, const cbm_file
|
||||
}
|
||||
|
||||
static bool is_infra_file(const char *fp) {
|
||||
if (cbm_is_manifest_path(fp)) {
|
||||
return false;
|
||||
}
|
||||
return fp != NULL &&
|
||||
(strstr(fp, ".yaml") != NULL || strstr(fp, ".yml") != NULL ||
|
||||
strstr(fp, ".tf") != NULL || strstr(fp, ".hcl") != NULL || strstr(fp, ".toml") != NULL);
|
||||
|
||||
@@ -2645,6 +2645,32 @@ TEST(extract_java_method_annotations_issue382) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(extract_python_mock_patch_is_not_route) {
|
||||
CBMFileResult *r = extract("from unittest.mock import patch\n\n"
|
||||
"@patch(\"subprocess.run\")\n"
|
||||
"def test_cmd(mock_run):\n"
|
||||
" pass\n\n"
|
||||
"@app.patch(\"/items/{id}\")\n"
|
||||
"def update_item():\n"
|
||||
" pass\n",
|
||||
CBM_LANG_PYTHON, "t", "test_routes.py");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_FALSE(r->has_error);
|
||||
|
||||
const CBMDefinition *mocked = find_def_by_name(r, "test_cmd");
|
||||
ASSERT_NOT_NULL(mocked);
|
||||
ASSERT_NULL(mocked->route_path);
|
||||
ASSERT_NULL(mocked->route_method);
|
||||
|
||||
const CBMDefinition *route = find_def_by_name(r, "update_item");
|
||||
ASSERT_NOT_NULL(route);
|
||||
ASSERT_STR_EQ(route->route_path, "/items/{id}");
|
||||
ASSERT_STR_EQ(route->route_method, "PATCH");
|
||||
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Issue #213: large TS files were indexed as a File node with zero children. */
|
||||
TEST(extract_large_ts_has_functions_issue213) {
|
||||
enum { NFUNCS = 4000 };
|
||||
@@ -3163,6 +3189,7 @@ SUITE(extraction) {
|
||||
RUN_TEST(js_index_module_qn_not_collide_with_folder);
|
||||
RUN_TEST(python_regular_module_qn_unchanged);
|
||||
RUN_TEST(extract_java_method_annotations_issue382);
|
||||
RUN_TEST(extract_python_mock_patch_is_not_route);
|
||||
RUN_TEST(extract_large_ts_has_functions_issue213);
|
||||
|
||||
/* Per-function complexity metrics (Tier A) */
|
||||
|
||||
@@ -53,6 +53,20 @@ TEST(infrascan_http_route_literal_guard_rejects_filesystem_paths) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(infrascan_service_pattern_match_uses_qn_boundaries) {
|
||||
ASSERT_EQ(cbm_service_pattern_match(
|
||||
"proj.plugins.autorun.tests.test_plugin._dispatch"),
|
||||
CBM_SVC_NONE);
|
||||
ASSERT_EQ(cbm_service_pattern_match("proj.myrequests.client.get"), CBM_SVC_NONE);
|
||||
|
||||
ASSERT_EQ(cbm_service_pattern_match("proj.gin.router.GET"), CBM_SVC_ROUTE_REG);
|
||||
ASSERT_EQ(cbm_service_pattern_match("proj.express.router.get"), CBM_SVC_ROUTE_REG);
|
||||
ASSERT_EQ(cbm_service_pattern_match("proj.venv.requests.api.get"), CBM_SVC_HTTP);
|
||||
ASSERT_EQ(cbm_service_pattern_match("proj.service.requests_get"), CBM_SVC_HTTP);
|
||||
ASSERT_EQ(cbm_service_pattern_match("proj.GuzzleHttp.Client.get"), CBM_SVC_HTTP);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(infrascan_route_nodes_skip_bad_http_url_paths) {
|
||||
cbm_gbuf_t *gb = cbm_gbuf_new("test", "/tmp/cbm_infrascan_route_guard");
|
||||
ASSERT_NOT_NULL(gb);
|
||||
@@ -133,6 +147,7 @@ TEST(infrascan_http_calls_join_matching_handler_route) {
|
||||
|
||||
SUITE(infrascan) {
|
||||
RUN_TEST(infrascan_http_route_literal_guard_rejects_filesystem_paths);
|
||||
RUN_TEST(infrascan_service_pattern_match_uses_qn_boundaries);
|
||||
RUN_TEST(infrascan_route_nodes_skip_bad_http_url_paths);
|
||||
RUN_TEST(infrascan_http_calls_join_matching_handler_route);
|
||||
}
|
||||
|
||||
@@ -165,6 +165,9 @@ int main(void) {
|
||||
if (strstr("watcher", only_suite)) RUN_SUITE(watcher);
|
||||
if (strstr("security", only_suite)) RUN_SUITE(security);
|
||||
if (strstr("artifact", only_suite)) RUN_SUITE(artifact);
|
||||
if (strstr("extraction", only_suite)) RUN_SUITE(extraction);
|
||||
if (strstr("lang_contract", only_suite)) RUN_SUITE(lang_contract);
|
||||
if (strstr("edge_types_probe", only_suite)) RUN_SUITE(edge_types_probe);
|
||||
TEST_SUMMARY();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user