Skip to content

Commit 01bf5f2

Browse files
authored
Merge pull request DeusData#2009 from CaptainMittens/fix/route-handler-last-argument
fix(routes): take a route's handler from the last argument, not the first
2 parents e6a34fc + 592894a commit 01bf5f2

2 files changed

Lines changed: 59 additions & 6 deletions

File tree

‎internal/cbm/extract_calls.c‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
enum { LEAN_MAX_PARENT_DEPTH = 20 };
2222
/* Max positional args to scan for URL/string. */
2323
enum { MAX_POSITIONAL_SCAN = 3 };
24-
/* Max positional args to scan for handler ref. */
25-
enum { MAX_HANDLER_SCAN = 4 };
2624
/* Max string arg length before rejection. */
2725
enum { MAX_STRING_ARG_LEN = CBM_SZ_512 };
2826
/* Min printable ASCII (space). */
@@ -2349,8 +2347,17 @@ static const char *normalize_string_handler(CBMArena *a, const char *raw) {
23492347
}
23502348

23512349
static const char *extract_handler_arg(CBMExtractCtx *ctx, TSNode args) {
2350+
/* The LAST eligible argument wins, and every argument is examined.
2351+
* Express, Fastify, gin and Laravel all put middleware between the route
2352+
* path and the handler, so the first function-shaped argument is usually a
2353+
* middleware. Taking the first one pointed the HANDLES edge at the
2354+
* middleware; stopping the scan early missed the handler outright when the
2355+
* middleware was written inline, because an arrow function matches none of
2356+
* the kinds below. Nothing that follows a handler matches them either — an
2357+
* options argument is an object node — so the last match is the handler. */
2358+
const char *handler = NULL;
23522359
uint32_t nc = ts_node_named_child_count(args);
2353-
for (uint32_t ai = HANDLER_START_IDX; ai < nc && ai < MAX_HANDLER_SCAN; ai++) {
2360+
for (uint32_t ai = HANDLER_START_IDX; ai < nc; ai++) {
23542361
TSNode arg2 = ts_node_named_child(args, ai);
23552362
/* PHP wraps each argument in an `argument` node — unwrap to the value. */
23562363
if (strcmp(ts_node_type(arg2), "argument") == 0 && ts_node_named_child_count(arg2) > 0) {
@@ -2362,17 +2369,18 @@ static const char *extract_handler_arg(CBMExtractCtx *ctx, TSNode args) {
23622369
if (strcmp(ak2, "identifier") == 0 || strcmp(ak2, "member_expression") == 0 ||
23632370
strcmp(ak2, "selector_expression") == 0 || strcmp(ak2, "attribute") == 0 ||
23642371
strcmp(ak2, "field_expression") == 0 || strcmp(ak2, "name") == 0) {
2365-
return cbm_node_text(ctx->arena, arg2, ctx->source);
2372+
handler = cbm_node_text(ctx->arena, arg2, ctx->source);
2373+
continue;
23662374
}
23672375
if (is_string_like(ak2)) {
23682376
const char *h =
23692377
normalize_string_handler(ctx->arena, cbm_node_text(ctx->arena, arg2, ctx->source));
23702378
if (h && h[0]) {
2371-
return h;
2379+
handler = h;
23722380
}
23732381
}
23742382
}
2375-
return NULL;
2383+
return handler;
23762384
}
23772385

23782386
// Extract JSX component refs (uppercase tags) as CALLS edges.

‎tests/test_extraction.c‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4057,6 +4057,49 @@ TEST(extract_ts_url_builder_issue1009) {
40574057
PASS();
40584058
}
40594059

4060+
/* A route registration names its middleware before its handler, and every
4061+
* framework here puts the handler last. The handler scan took the FIRST
4062+
* argument that looked like a function reference, so a named middleware won
4063+
* and the HANDLES edge pointed at the middleware instead of the handler. */
4064+
TEST(extract_ts_route_handler_after_named_middleware) {
4065+
CBMFileResult *r = extract("function requireAuth(req: any, res: any, next: any) { next(); }\n"
4066+
"function rateLimit(req: any, res: any, next: any) { next(); }\n"
4067+
"function listUsers(req: any, res: any) { res.json([]); }\n"
4068+
"routerGet(\"/users\", requireAuth, rateLimit, listUsers);\n",
4069+
CBM_LANG_TYPESCRIPT, "t", "routes.ts");
4070+
ASSERT_NOT_NULL(r);
4071+
const CBMCall *c = find_call_by_callee(r, "routerGet");
4072+
ASSERT_NOT_NULL(c);
4073+
ASSERT_NOT_NULL(c->first_string_arg);
4074+
ASSERT_STR_EQ(c->first_string_arg, "/users");
4075+
ASSERT_NOT_NULL(c->second_arg_name);
4076+
ASSERT_STR_EQ(c->second_arg_name, "listUsers");
4077+
cbm_free_result(r);
4078+
PASS();
4079+
}
4080+
4081+
/* The same route with its middleware written inline. An arrow function is not
4082+
* one of the kinds the handler scan accepts, so three of them pushed the real
4083+
* handler past the scan bound and no handler came back at all. */
4084+
TEST(extract_ts_route_handler_after_inline_middleware) {
4085+
CBMFileResult *r = extract("function listOrders(req: any, res: any) { res.json([]); }\n"
4086+
"routerGet(\"/orders\",\n"
4087+
" (req: any, res: any, next: any) => { next(); },\n"
4088+
" (req: any, res: any, next: any) => { next(); },\n"
4089+
" (req: any, res: any, next: any) => { next(); },\n"
4090+
" listOrders);\n",
4091+
CBM_LANG_TYPESCRIPT, "t", "orders.ts");
4092+
ASSERT_NOT_NULL(r);
4093+
const CBMCall *c = find_call_by_callee(r, "routerGet");
4094+
ASSERT_NOT_NULL(c);
4095+
ASSERT_NOT_NULL(c->first_string_arg);
4096+
ASSERT_STR_EQ(c->first_string_arg, "/orders");
4097+
ASSERT_NOT_NULL(c->second_arg_name);
4098+
ASSERT_STR_EQ(c->second_arg_name, "listOrders");
4099+
cbm_free_result(r);
4100+
PASS();
4101+
}
4102+
40604103
/* Issue #1009 (composed builders): a builder whose template inlines an earlier
40614104
* builder's call plus a query string: `return \`${basePath(id)}?${params}\``.
40624105
* The known-substitution is inlined and the query string is truncated, so the
@@ -6892,6 +6935,8 @@ SUITE(extraction) {
68926935
RUN_TEST(extract_go_binary_concat_url_issue1249);
68936936
RUN_TEST(extract_go_binary_concat_url_no_literal_suffix_issue1249);
68946937
RUN_TEST(extract_ts_url_builder_issue1009);
6938+
RUN_TEST(extract_ts_route_handler_after_named_middleware);
6939+
RUN_TEST(extract_ts_route_handler_after_inline_middleware);
68956940
RUN_TEST(extract_ts_url_builder_composed_issue1009);
68966941
RUN_TEST(extract_c_url_builder_gated_issue1009);
68976942
RUN_TEST(extract_ts_url_builder_mixed_returns_issue1009);

0 commit comments

Comments
 (0)