Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions internal/cbm/extract_calls.c
Original file line number Diff line number Diff line change
Expand Up @@ -1707,6 +1707,24 @@ static const char *extract_keyword_url(CBMExtractCtx *ctx, TSNode arg) {
return extract_string_value(ctx, val_node);
}

// `prefixVar + "/route"` (Go's idiomatic configurable-base-path pattern):
// recover the literal suffix. A right side that is not itself a literal is
// left unresolved rather than guessed (issue #1249).
static const char *extract_binary_concat_suffix(CBMExtractCtx *ctx, TSNode node) {
TSNode op_node = ts_node_child_by_field_name(node, TS_FIELD("operator"));
if (!ts_node_is_null(op_node)) {
char *op = cbm_node_text(ctx->arena, op_node, ctx->source);
if (!op || strcmp(op, "+") != 0) {
return NULL;
}
}
TSNode rhs = ts_node_child_by_field_name(node, TS_FIELD("right"));
if (ts_node_is_null(rhs) || !is_string_like(ts_node_type(rhs))) {
return NULL;
}
return strip_and_validate_string_arg(ctx->arena, cbm_node_text(ctx->arena, rhs, ctx->source));
}

// Try to extract URL/topic from a positional argument (string or constant).
static const char *extract_positional_url(CBMExtractCtx *ctx, TSNode arg, const char *ak) {
/* JS/TS template literals: `/things/${id}` normalizes to "/things/{}" so the
Expand All @@ -1717,6 +1735,12 @@ static const char *extract_positional_url(CBMExtractCtx *ctx, TSNode arg, const
return strip_and_validate_string_arg(ctx->arena, (char *)flat);
}
}
if (strcmp(ak, "binary_expression") == 0) {
const char *suffix = extract_binary_concat_suffix(ctx, arg);
if (suffix) {
return suffix;
}
}
if (is_string_like(ak)) {
char *text = cbm_node_text(ctx->arena, arg, ctx->source);
const char *validated = strip_and_validate_string_arg(ctx->arena, text);
Expand Down
53 changes: 53 additions & 0 deletions tests/test_extraction.c
Original file line number Diff line number Diff line change
Expand Up @@ -3181,6 +3181,57 @@ TEST(extract_ts_template_string_url_issue1006) {
PASS();
}

/* Issue #1249: a mux route built as `configVar + "/literal"` (Go's idiomatic
* configurable-base-path pattern) must index the literal suffix, both for a
* route registration and for an outbound URL built the same way. A real BFF
* with 47 such registrations produced only 9 Route nodes before this fix. */
TEST(extract_go_binary_concat_url_issue1249) {
CBMFileResult *r = extract("package main\n"
"import \"net/http\"\n"
"func setup(mux *http.ServeMux, base string) {\n"
" mux.HandleFunc(base+\"/login\", loginHandler)\n"
"}\n"
"func report(host string, port string) {\n"
" http.Get(\"http://\" + host + \":\" + port + \"/log\")\n"
"}\n",
CBM_LANG_GO, "t", "routes.go");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);

const CBMCall *reg = find_call_by_callee(r, "mux.HandleFunc");
ASSERT_NOT_NULL(reg);
ASSERT_NOT_NULL(reg->first_string_arg);
ASSERT_STR_EQ(reg->first_string_arg, "/login");

const CBMCall *out = find_call_by_callee(r, "http.Get");
ASSERT_NOT_NULL(out);
ASSERT_NOT_NULL(out->first_string_arg);
ASSERT_STR_EQ(out->first_string_arg, "/log");

cbm_free_result(r);
PASS();
}

/* Same issue: when the right side of the concatenation is not itself a
* literal (`base + suffixVar`), there is no literal route to recover. The
* fix must leave this unresolved rather than fabricate a path. */
TEST(extract_go_binary_concat_url_no_literal_suffix_issue1249) {
CBMFileResult *r = extract("package main\n"
"func setup(mux *http.ServeMux, base string, suffix string) {\n"
" mux.HandleFunc(base+suffix, dynHandler)\n"
"}\n",
CBM_LANG_GO, "t", "routes.go");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);

const CBMCall *reg = find_call_by_callee(r, "mux.HandleFunc");
ASSERT_NOT_NULL(reg);
ASSERT_NULL(reg->first_string_arg);

cbm_free_result(r);
PASS();
}


/* Reproduce-first: Java module QN must derive from the CONTAINING DIRECTORY, not
* the filename stem, so a top-level class `Outer` in `Outer.java` is `t.Outer`,
Expand Down Expand Up @@ -5315,6 +5366,8 @@ SUITE(extraction) {
RUN_TEST(extract_java_method_annotations_issue382);
RUN_TEST(extract_java_jaxrs_path_composition_issue1005);
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_java_no_double_class_qn);
RUN_TEST(extract_go_no_filename_in_module_qn);
RUN_TEST(extract_large_ts_has_functions_issue213);
Expand Down
Loading