mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Merge pull request #1010 from CharlesQueiroz/fix/url-builder-routes
fix: resolve URL-builder helper calls to Route/HTTP_CALLS
This commit is contained in:
4 files changed
+437
-2
No files matched your search
@@ -551,6 +551,7 @@ typedef struct {
|
||||
typedef struct {
|
||||
const char *names[CBM_MAX_STRING_CONSTANTS];
|
||||
const char *values[CBM_MAX_STRING_CONSTANTS];
|
||||
bool is_url_builder[CBM_MAX_STRING_CONSTANTS];
|
||||
int count;
|
||||
} CBMStringConstantMap;
|
||||
|
||||
|
||||
@@ -30,14 +30,29 @@ enum { MIN_PRINTABLE = 0x20 };
|
||||
/* Handler arg scan start index (skip first positional). */
|
||||
enum { HANDLER_START_IDX = 1 };
|
||||
|
||||
/* Look up a module-level string constant by name. */
|
||||
/* Look up a module-level string constant by name. URL-builder entries share the
|
||||
* map but are not constants: a bare `thingPath` reference is the function, not
|
||||
* the URL it would build (issue #1009). */
|
||||
static const char *lookup_string_constant(const CBMExtractCtx *ctx, const char *name) {
|
||||
if (!name || !name[0]) {
|
||||
return NULL;
|
||||
}
|
||||
const CBMStringConstantMap *map = &ctx->string_constants;
|
||||
for (int i = 0; i < map->count; i++) {
|
||||
if (strcmp(map->names[i], name) == 0) {
|
||||
if (!map->is_url_builder[i] && strcmp(map->names[i], name) == 0) {
|
||||
return map->values[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *lookup_url_builder(const CBMExtractCtx *ctx, const char *name) {
|
||||
if (!name || !name[0]) {
|
||||
return NULL;
|
||||
}
|
||||
const CBMStringConstantMap *map = &ctx->string_constants;
|
||||
for (int i = 0; i < map->count; i++) {
|
||||
if (map->is_url_builder[i] && strcmp(map->names[i], name) == 0) {
|
||||
return map->values[i];
|
||||
}
|
||||
}
|
||||
@@ -1855,8 +1870,22 @@ static void extract_call_args(CBMExtractCtx *ctx, TSNode args, CBMCall *call) {
|
||||
ca->index = positional_idx++;
|
||||
if (is_string_like(ak) && ca->expr) {
|
||||
ca->value = strip_quotes(ctx->arena, ca->expr);
|
||||
} else if (strcmp(ak, "template_string") == 0) {
|
||||
/* Flattened {} form so downstream url-arg detection joins the
|
||||
* canonical server route shape (issue #1006/#1009). */
|
||||
ca->value = cbm_template_string_text(ctx->arena, arg_node, ctx->source);
|
||||
} else if (strcmp(ak, "identifier") == 0 && ca->expr) {
|
||||
ca->value = lookup_string_constant(ctx, ca->expr);
|
||||
} else if (strcmp(ak, "call_expression") == 0) {
|
||||
/* URL-builder helper call (issue #1009): resolve
|
||||
* client(buildPath(id)) through the per-file builder map. */
|
||||
TSNode fn = ts_node_child_by_field_name(arg_node, TS_FIELD("function"));
|
||||
if (!ts_node_is_null(fn) && strcmp(ts_node_type(fn), "identifier") == 0) {
|
||||
char *fname = cbm_node_text(ctx->arena, fn, ctx->source);
|
||||
if (fname) {
|
||||
ca->value = lookup_url_builder(ctx, fname);
|
||||
}
|
||||
}
|
||||
}
|
||||
call->arg_count++;
|
||||
}
|
||||
@@ -2085,6 +2114,19 @@ static const char *extract_url_or_topic_arg(CBMExtractCtx *ctx, TSNode args) {
|
||||
}
|
||||
}
|
||||
|
||||
/* URL-builder helper call (issue #1009): `client(buildPath(id))` — the
|
||||
* builder's returned URL was recorded in the per-file constant map. */
|
||||
if (strcmp(ak, "call_expression") == 0) {
|
||||
TSNode fn = ts_node_child_by_field_name(arg, TS_FIELD("function"));
|
||||
if (!ts_node_is_null(fn) && strcmp(ts_node_type(fn), "identifier") == 0) {
|
||||
char *fname = cbm_node_text(ctx->arena, fn, ctx->source);
|
||||
const char *val = fname ? lookup_url_builder(ctx, fname) : NULL;
|
||||
if (val) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ai < MAX_POSITIONAL_SCAN) {
|
||||
const char *val = extract_positional_url(ctx, arg, ak);
|
||||
if (val) {
|
||||
|
||||
@@ -1099,6 +1099,227 @@ static void handle_string_refs(CBMExtractCtx *ctx, TSNode node, const WalkState
|
||||
cbm_stringref_push(&ctx->result->string_refs, ctx->arena, ref);
|
||||
}
|
||||
|
||||
// --- URL-builder helpers (issue #1009) ---
|
||||
|
||||
/* Map-aware template flatten for builder bodies: a ${...} substitution that is
|
||||
* a bare identifier or a call to an already-recorded name (const or an earlier
|
||||
* builder in the same file) inlines that value; anything else becomes "{}".
|
||||
* The query string is not part of a route's identity, so the result is
|
||||
* truncated at the first '?'. Handles the composed-builder shape
|
||||
* `return \`${basePath(id)}?${params}\``. */
|
||||
static const char *builder_template_text(CBMExtractCtx *ctx, TSNode node) {
|
||||
enum { BLD_BUF = 512 };
|
||||
char buf[BLD_BUF];
|
||||
size_t pos = 0;
|
||||
uint32_t nc = ts_node_named_child_count(node);
|
||||
for (uint32_t i = 0; i < nc; i++) {
|
||||
TSNode c = ts_node_named_child(node, i);
|
||||
const char *k = ts_node_type(c);
|
||||
const char *piece = NULL;
|
||||
if (strcmp(k, "string_fragment") == 0) {
|
||||
piece = cbm_node_text(ctx->arena, c, ctx->source);
|
||||
} else if (strcmp(k, "template_substitution") == 0) {
|
||||
piece = "{}";
|
||||
if (ts_node_named_child_count(c) > 0) {
|
||||
TSNode expr = ts_node_named_child(c, 0);
|
||||
/* `${basePath(id)}` inlines a builder, `${BASE}` a const. A bare
|
||||
* name never resolves to a builder: that reads the function. */
|
||||
bool want_builder = strcmp(ts_node_type(expr), "call_expression") == 0;
|
||||
TSNode name_node =
|
||||
want_builder ? ts_node_child_by_field_name(expr, TS_FIELD("function")) : expr;
|
||||
if (!ts_node_is_null(name_node) &&
|
||||
strcmp(ts_node_type(name_node), "identifier") == 0) {
|
||||
char *nm = cbm_node_text(ctx->arena, name_node, ctx->source);
|
||||
if (nm) {
|
||||
const CBMStringConstantMap *map = &ctx->string_constants;
|
||||
for (int mi = 0; mi < map->count; mi++) {
|
||||
if (map->values[mi] && map->is_url_builder[mi] == want_builder &&
|
||||
strcmp(map->names[mi], nm) == 0) {
|
||||
piece = map->values[mi];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (!piece) {
|
||||
continue;
|
||||
}
|
||||
size_t pl = strlen(piece);
|
||||
if (pos + pl >= BLD_BUF) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy(buf + pos, piece, pl);
|
||||
pos += pl;
|
||||
}
|
||||
/* Route identity excludes the query string. */
|
||||
for (size_t qi = 0; qi < pos; qi++) {
|
||||
if (buf[qi] == '?') {
|
||||
pos = qi;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pos == 0) {
|
||||
return NULL;
|
||||
}
|
||||
return cbm_arena_strndup(ctx->arena, buf, pos);
|
||||
}
|
||||
|
||||
/* Flatten a string-ish node (plain string or template literal) to text. */
|
||||
static const char *url_builder_literal_text(CBMExtractCtx *ctx, TSNode value_node) {
|
||||
const char *kind = ts_node_type(value_node);
|
||||
if (strcmp(kind, "template_string") == 0) {
|
||||
return builder_template_text(ctx, value_node);
|
||||
}
|
||||
if (!is_string_node(kind)) {
|
||||
return NULL;
|
||||
}
|
||||
char *text = cbm_node_text(ctx->arena, value_node, ctx->source);
|
||||
if (!text || !text[0]) {
|
||||
return NULL;
|
||||
}
|
||||
int len = (int)strlen(text);
|
||||
if (len >= CBM_QUOTE_PAIR && (text[0] == '"' || text[0] == '\'')) {
|
||||
text = cbm_arena_strndup(ctx->arena, text + SKIP_ONE, (size_t)(len - PAIR_LEN));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/* A route-shaped URL literal: an absolute path that classifies as a URL. */
|
||||
static const char *builder_route_url(CBMExtractCtx *ctx, TSNode value_node) {
|
||||
const char *url = url_builder_literal_text(ctx, value_node);
|
||||
if (!url || url[0] != '/' || cbm_classify_string(url, (int)strlen(url)) != CBM_STRREF_URL) {
|
||||
return NULL;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
static void record_url_builder(CBMExtractCtx *ctx, const char *name, const char *url) {
|
||||
if (!name || !name[0] || !url) {
|
||||
return;
|
||||
}
|
||||
CBMStringConstantMap *map = &ctx->string_constants;
|
||||
for (int i = 0; i < map->count; i++) {
|
||||
if (strcmp(map->names[i], name) == 0) {
|
||||
if (map->is_url_builder[i] && map->values[i] && strcmp(map->values[i], url) != 0) {
|
||||
map->values[i] = NULL; /* ambiguous builder */
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (map->count < CBM_MAX_STRING_CONSTANTS) {
|
||||
map->names[map->count] = (char *)name;
|
||||
map->values[map->count] = (char *)url;
|
||||
map->is_url_builder[map->count] = true;
|
||||
map->count++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Every `return` this function owns yields a route-shaped URL literal. A body
|
||||
* that also returns a computed path (`return computePath(kind)`) would attribute
|
||||
* its one literal to call sites that never produce it, so a mixed builder is
|
||||
* declined rather than guessed. Returns inside a nested function belong to that
|
||||
* function, not to this one. */
|
||||
static bool builder_returns_only_urls(CBMExtractCtx *ctx, TSNode func) {
|
||||
TSNode body = ts_node_child_by_field_name(func, TS_FIELD("body"));
|
||||
if (ts_node_is_null(body)) {
|
||||
return false;
|
||||
}
|
||||
bool only_urls = true;
|
||||
TSTreeCursor cursor = ts_tree_cursor_new(body);
|
||||
for (;;) {
|
||||
TSNode node = ts_tree_cursor_current_node(&cursor);
|
||||
if (strcmp(ts_node_type(node), "return_statement") == 0 &&
|
||||
ts_node_eq(cbm_find_enclosing_func(node, ctx->language), func)) {
|
||||
if (ts_node_named_child_count(node) == 0 ||
|
||||
!builder_route_url(ctx, ts_node_named_child(node, 0))) {
|
||||
only_urls = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ts_tree_cursor_goto_first_child(&cursor)) {
|
||||
continue;
|
||||
}
|
||||
if (ts_tree_cursor_goto_next_sibling(&cursor)) {
|
||||
continue;
|
||||
}
|
||||
bool found = false;
|
||||
while (ts_tree_cursor_goto_parent(&cursor)) {
|
||||
if (ts_tree_cursor_goto_next_sibling(&cursor)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ts_tree_cursor_delete(&cursor);
|
||||
return only_urls;
|
||||
}
|
||||
|
||||
/* URL-builder helper pattern (issue #1009): a small function whose return value
|
||||
* is a URL-shaped literal, consumed as `client(buildPath(id))`. The literal
|
||||
* never appears as a call argument, so first_string_arg resolution cannot see
|
||||
* it; record `functionName -> url` in the per-file constant map and let the
|
||||
* call-site call_expression branch resolve it. Covers `return`-statement bodies
|
||||
* and arrow-function expression bodies.
|
||||
*
|
||||
* JS/TS only. The predicate accepts any absolute pathname, and `return` plus a
|
||||
* string literal is also the shape of every C or Go helper handing back a
|
||||
* filesystem path (`/etc/...`, `/proc/self/...`); recording those would mint a
|
||||
* Route node and an HTTP_CALLS edge in a language that speaks no HTTP. */
|
||||
static void handle_url_builders(CBMExtractCtx *ctx, TSNode node, const WalkState *state) {
|
||||
if (ctx->language != CBM_LANG_JAVASCRIPT && ctx->language != CBM_LANG_TYPESCRIPT &&
|
||||
ctx->language != CBM_LANG_TSX) {
|
||||
return;
|
||||
}
|
||||
const char *kind = ts_node_type(node);
|
||||
|
||||
if (strcmp(kind, "arrow_function") == 0) {
|
||||
TSNode body = ts_node_child_by_field_name(node, TS_FIELD("body"));
|
||||
if (ts_node_is_null(body) || strcmp(ts_node_type(body), "statement_block") == 0) {
|
||||
return;
|
||||
}
|
||||
const char *url = builder_route_url(ctx, body);
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
TSNode parent = ts_node_parent(node);
|
||||
if (!ts_node_is_null(parent) && strcmp(ts_node_type(parent), "variable_declarator") == 0) {
|
||||
TSNode name_node = ts_node_child_by_field_name(parent, TS_FIELD("name"));
|
||||
if (!ts_node_is_null(name_node)) {
|
||||
record_url_builder(ctx, cbm_node_text(ctx->arena, name_node, ctx->source), url);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(kind, "return_statement") != 0) {
|
||||
return;
|
||||
}
|
||||
if (!state->enclosing_func_qn || state->enclosing_func_qn == ctx->module_qn) {
|
||||
return;
|
||||
}
|
||||
if (ts_node_named_child_count(node) == 0) {
|
||||
return;
|
||||
}
|
||||
const char *url = builder_route_url(ctx, ts_node_named_child(node, 0));
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
TSNode func = cbm_find_enclosing_func(node, ctx->language);
|
||||
if (ts_node_is_null(func) || !builder_returns_only_urls(ctx, func)) {
|
||||
return;
|
||||
}
|
||||
const char *name = strrchr(state->enclosing_func_qn, '.');
|
||||
name = name ? name + 1 : state->enclosing_func_qn;
|
||||
record_url_builder(ctx, name, url);
|
||||
}
|
||||
|
||||
// --- YAML nested field extraction (D2) ---
|
||||
|
||||
/* Recursively walk YAML block_mapping_pair nodes, building dotted key paths.
|
||||
@@ -2160,6 +2381,7 @@ void cbm_extract_unified(CBMExtractCtx *ctx) {
|
||||
|
||||
handle_string_constants(ctx, node, &state);
|
||||
handle_objectscript_type_map(ctx, node, &state);
|
||||
handle_url_builders(ctx, node, &state);
|
||||
CBMInvocationDescriptor invocation = handle_calls(ctx, node, spec, &state);
|
||||
handle_usages(ctx, node, spec, &state);
|
||||
handle_throws(ctx, node, spec, &state);
|
||||
|
||||
@@ -3370,6 +3370,169 @@ static const CBMCall *find_call_by_callee(CBMFileResult *r, const char *callee)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Issue #1009: URL-builder helper pattern — a function returning a URL-shaped
|
||||
* literal, consumed as client(buildPath(id)). The builder's URL is recorded in
|
||||
* the per-file constant map and resolved at the call site, for both return
|
||||
* statements and arrow expression bodies. */
|
||||
TEST(extract_ts_url_builder_issue1009) {
|
||||
CBMFileResult *r = extract("function thingDetail(id: string): string {\n"
|
||||
" return `/api/v1/things/${id}/detail`;\n"
|
||||
"}\n"
|
||||
"const arrowPath = (id: string) => `/api/v1/arrows/${id}`;\n"
|
||||
"export function useThing(id: string) {\n"
|
||||
" return apiGet(thingDetail(id));\n"
|
||||
"}\n"
|
||||
"export function useArrow(id: string) {\n"
|
||||
" return apiFetch(arrowPath(id));\n"
|
||||
"}\n",
|
||||
CBM_LANG_TYPESCRIPT, "t", "builders.ts");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_FALSE(r->has_error);
|
||||
const CBMCall *c1 = find_call_by_callee(r, "apiGet");
|
||||
ASSERT_NOT_NULL(c1);
|
||||
ASSERT_NOT_NULL(c1->first_string_arg);
|
||||
ASSERT_STR_EQ(c1->first_string_arg, "/api/v1/things/{}/detail");
|
||||
const CBMCall *c2 = find_call_by_callee(r, "apiFetch");
|
||||
ASSERT_NOT_NULL(c2);
|
||||
ASSERT_NOT_NULL(c2->first_string_arg);
|
||||
ASSERT_STR_EQ(c2->first_string_arg, "/api/v1/arrows/{}");
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Issue #1009 (composed builders): a builder whose template inlines an earlier
|
||||
* builder's call plus a query string: `return \`${basePath(id)}?${params}\``.
|
||||
* The known-substitution is inlined and the query string is truncated, so the
|
||||
* resolved URL joins the server route exactly. */
|
||||
TEST(extract_ts_url_builder_composed_issue1009) {
|
||||
CBMFileResult *r = extract("function activityPath(id: string): string {\n"
|
||||
" return `/api/v1/team-members/${id}/activity`;\n"
|
||||
"}\n"
|
||||
"function buildPath(id: string, cursor: string): string {\n"
|
||||
" const params = new URLSearchParams();\n"
|
||||
" params.set('cursor', cursor);\n"
|
||||
" return `${activityPath(id)}?${params.toString()}`;\n"
|
||||
"}\n"
|
||||
"export function useActivity(id: string, cursor: string) {\n"
|
||||
" return apiGet(buildPath(id, cursor));\n"
|
||||
"}\n",
|
||||
CBM_LANG_TYPESCRIPT, "t", "composed.ts");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_FALSE(r->has_error);
|
||||
const CBMCall *c = find_call_by_callee(r, "apiGet");
|
||||
ASSERT_NOT_NULL(c);
|
||||
ASSERT_NOT_NULL(c->first_string_arg);
|
||||
ASSERT_STR_EQ(c->first_string_arg, "/api/v1/team-members/{}/activity");
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
static bool any_call_arg_resolves_to(CBMFileResult *r, const char *url) {
|
||||
for (int i = 0; i < r->calls.count; i++) {
|
||||
const CBMCall *c = &r->calls.items[i];
|
||||
if (c->first_string_arg && strcmp(c->first_string_arg, url) == 0) {
|
||||
return true;
|
||||
}
|
||||
for (int a = 0; a < c->arg_count; a++) {
|
||||
if (c->args[a].value && strcmp(c->args[a].value, url) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST(extract_c_url_builder_gated_issue1009) {
|
||||
CBMFileResult *r = extract("static const char *cfg_path(void) {\n"
|
||||
" return \"/etc/myapp/conf.d\";\n"
|
||||
"}\n"
|
||||
"void init(void) {\n"
|
||||
" parse_config(cfg_path());\n"
|
||||
"}\n",
|
||||
CBM_LANG_C, "t", "conf.c");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_FALSE(r->has_error);
|
||||
ASSERT_FALSE(any_call_arg_resolves_to(r, "/etc/myapp/conf.d"));
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* One literal return next to a computed one would attribute the literal to every
|
||||
* call site, including those taking the computed branch. A builder whose returns
|
||||
* are not all URL literals is declined. */
|
||||
TEST(extract_ts_url_builder_mixed_returns_issue1009) {
|
||||
CBMFileResult *r = extract("function pathFor(kind: string): string {\n"
|
||||
" if (kind === 'user') return '/api/users';\n"
|
||||
" return computePath(kind);\n"
|
||||
"}\n"
|
||||
"export function load(kind: string) {\n"
|
||||
" return apiGet(pathFor(kind));\n"
|
||||
"}\n",
|
||||
CBM_LANG_TYPESCRIPT, "t", "mixed.ts");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_FALSE(r->has_error);
|
||||
ASSERT_FALSE(any_call_arg_resolves_to(r, "/api/users"));
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Two different literal URLs make the call site's route unknowable, so the
|
||||
* builder is tombstoned and lookups miss. */
|
||||
TEST(extract_ts_url_builder_ambiguous_issue1009) {
|
||||
CBMFileResult *r = extract("function pathFor(kind: string): string {\n"
|
||||
" if (kind === 'user') return '/api/users';\n"
|
||||
" return '/api/teams';\n"
|
||||
"}\n"
|
||||
"export function load(kind: string) {\n"
|
||||
" return apiGet(pathFor(kind));\n"
|
||||
"}\n",
|
||||
CBM_LANG_TYPESCRIPT, "t", "ambiguous.ts");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_FALSE(r->has_error);
|
||||
ASSERT_FALSE(any_call_arg_resolves_to(r, "/api/users"));
|
||||
ASSERT_FALSE(any_call_arg_resolves_to(r, "/api/teams"));
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Handing a builder to a callback position builds no URL. Only a call to it
|
||||
* resolves, so the mapping function does not inherit an HTTP_CALLS edge to the
|
||||
* route. */
|
||||
TEST(extract_ts_url_builder_reference_issue1009) {
|
||||
CBMFileResult *r = extract("function thingPath(id: string): string {\n"
|
||||
" return `/api/v1/things/${id}`;\n"
|
||||
"}\n"
|
||||
"export function loadAll(ids: string[]) {\n"
|
||||
" return ids.map(thingPath);\n"
|
||||
"}\n",
|
||||
CBM_LANG_TYPESCRIPT, "t", "reference.ts");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_FALSE(r->has_error);
|
||||
ASSERT_FALSE(any_call_arg_resolves_to(r, "/api/v1/things/{}"));
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* A helper returning an ordinary string is not a URL builder, so its call site
|
||||
* gets no resolved string argument. */
|
||||
TEST(extract_ts_url_builder_non_url_issue1009) {
|
||||
CBMFileResult *r = extract("function label(): string {\n"
|
||||
" return 'plain text';\n"
|
||||
"}\n"
|
||||
"export function render() {\n"
|
||||
" return send(label());\n"
|
||||
"}\n",
|
||||
CBM_LANG_TYPESCRIPT, "t", "label.ts");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_FALSE(r->has_error);
|
||||
const CBMCall *c = find_call_by_callee(r, "send");
|
||||
ASSERT_NOT_NULL(c);
|
||||
ASSERT_NULL(c->first_string_arg);
|
||||
ASSERT_FALSE(any_call_arg_resolves_to(r, "plain text"));
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Issue #1006: JS/TS template-literal URLs must flatten ${...} substitutions
|
||||
* to the canonical "{}" placeholder, both as call arguments (HTTP_CALLS) and
|
||||
* as URL-shaped string_refs collected from const/return positions. */
|
||||
@@ -5786,6 +5949,13 @@ SUITE(extraction) {
|
||||
RUN_TEST(extract_ts_template_string_url_issue1006);
|
||||
RUN_TEST(extract_go_binary_concat_url_issue1249);
|
||||
RUN_TEST(extract_go_binary_concat_url_no_literal_suffix_issue1249);
|
||||
RUN_TEST(extract_ts_url_builder_issue1009);
|
||||
RUN_TEST(extract_ts_url_builder_composed_issue1009);
|
||||
RUN_TEST(extract_c_url_builder_gated_issue1009);
|
||||
RUN_TEST(extract_ts_url_builder_mixed_returns_issue1009);
|
||||
RUN_TEST(extract_ts_url_builder_ambiguous_issue1009);
|
||||
RUN_TEST(extract_ts_url_builder_reference_issue1009);
|
||||
RUN_TEST(extract_ts_url_builder_non_url_issue1009);
|
||||
RUN_TEST(extract_java_no_double_class_qn);
|
||||
RUN_TEST(extract_go_no_filename_in_module_qn);
|
||||
RUN_TEST(extract_large_ts_has_functions_issue213);
|
||||
|
||||
Reference in new issue
Block a user