| 2330 | } |
| 2331 | |
| 2332 | static void process_function_like(PHPLSPContext *ctx, TSNode node) { |
| 2333 | const char *kind = ts_node_type(node); |
| 2334 | bool is_method = (strcmp(kind, "method_declaration") == 0); |
| 2335 | bool is_named = is_method || (strcmp(kind, "function_definition") == 0) || |
| 2336 | (strcmp(kind, "function_static_declaration") == 0); |
| 2337 | |
| 2338 | /* Save context. */ |
| 2339 | CBMScope *saved_scope = ctx->current_scope; |
| 2340 | const char *saved_func = ctx->enclosing_func_qn; |
| 2341 | |
| 2342 | ctx->current_scope = cbm_scope_push(ctx->arena, ctx->current_scope); |
| 2343 | |
| 2344 | /* Determine func QN. Mirror what the unified extractor produces: classes |
| 2345 | * and free functions are namespaced by file module_qn, NOT the PHP |
| 2346 | * namespace declaration (the unified extractor ignores `namespace`). |
| 2347 | * Method QN = enclosing_class_qn + "." + method_name. Free function QN = |
| 2348 | * module_qn + "." + name. */ |
| 2349 | if (is_named) { |
| 2350 | TSNode name_node = ts_node_child_by_field_name(node, "name", 4); |
| 2351 | if (!ts_node_is_null(name_node)) { |
| 2352 | char *fname = php_node_text(ctx, name_node); |
| 2353 | if (fname) { |
| 2354 | if (is_method && ctx->enclosing_class_qn) { |
| 2355 | ctx->enclosing_func_qn = |
| 2356 | cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->enclosing_class_qn, fname); |
| 2357 | } else if (ctx->module_qn) { |
| 2358 | ctx->enclosing_func_qn = |
| 2359 | cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, fname); |
| 2360 | } else { |
| 2361 | ctx->enclosing_func_qn = cbm_arena_strdup(ctx->arena, fname); |
| 2362 | } |
| 2363 | } |
| 2364 | } |
| 2365 | } |
| 2366 | |
| 2367 | /* Parameters. */ |
| 2368 | TSNode params = ts_node_child_by_field_name(node, "parameters", 10); |
| 2369 | bind_typed_parameters(ctx, params); |
| 2370 | |
| 2371 | /* Closure `use ($a, $b)` clause: copy captured-variable types from the |
| 2372 | * parent scope into the closure scope. tree-sitter-php emits an |
| 2373 | * `anonymous_function_use_clause` child of `anonymous_function`. */ |
| 2374 | if (strcmp(kind, "anonymous_function") == 0) { |
| 2375 | uint32_t fnc = ts_node_child_count(node); |
| 2376 | for (uint32_t i = 0; i < fnc; i++) { |
| 2377 | TSNode c = ts_node_child(node, i); |
| 2378 | if (ts_node_is_null(c) || !ts_node_is_named(c)) |
| 2379 | continue; |
| 2380 | if (strcmp(ts_node_type(c), "anonymous_function_use_clause") != 0) |
| 2381 | continue; |
| 2382 | uint32_t unc = ts_node_child_count(c); |
| 2383 | for (uint32_t j = 0; j < unc; j++) { |
| 2384 | TSNode v = ts_node_child(c, j); |
| 2385 | if (ts_node_is_null(v) || !ts_node_is_named(v)) |
| 2386 | continue; |
| 2387 | if (strcmp(ts_node_type(v), "variable_name") != 0) |
| 2388 | continue; |
| 2389 | char *vt = php_node_text(ctx, v); |
no test coverage detected