| 3055 | } |
| 3056 | |
| 3057 | static void process_function_body(TSLSPContext *ctx, TSNode func_node, const char *func_qn, |
| 3058 | const char *class_qn) { |
| 3059 | if (ts_node_is_null(func_node)) |
| 3060 | return; |
| 3061 | CBMScope *saved_scope = ctx->current_scope; |
| 3062 | const char *saved_func = ctx->enclosing_func_qn; |
| 3063 | const char *saved_class = ctx->enclosing_class_qn; |
| 3064 | |
| 3065 | ctx->current_scope = cbm_scope_push(ctx->arena, saved_scope); |
| 3066 | ctx->enclosing_func_qn = func_qn; |
| 3067 | ctx->enclosing_class_qn = class_qn ? class_qn : saved_class; |
| 3068 | |
| 3069 | if (class_qn) { |
| 3070 | // Bind `this` to the class type for member resolution within methods. |
| 3071 | cbm_scope_bind(ctx->current_scope, "this", cbm_type_named(ctx->arena, class_qn)); |
| 3072 | } |
| 3073 | |
| 3074 | TSNode params = |
| 3075 | ts_node_child_by_field_name(func_node, "parameters", TS_LSP_FIELD_LEN("parameters")); |
| 3076 | if (!ts_node_is_null(params)) { |
| 3077 | uint32_t nc = ts_node_named_child_count(params); |
| 3078 | for (uint32_t i = 0; i < nc; i++) |
| 3079 | bind_parameter(ctx, ts_node_named_child(params, i)); |
| 3080 | |
| 3081 | // JSDoc / signature-derived param-type fallback: if bind_parameter left a |
| 3082 | // param bound to UNKNOWN (no inline TS annotation), use the registered func's |
| 3083 | // signature param_types[i] (which apply_jsdoc_signatures may have populated). |
| 3084 | if (func_qn) { |
| 3085 | const CBMRegisteredFunc *rf = cbm_registry_lookup_func(ctx->registry, func_qn); |
| 3086 | if (rf && rf->signature && rf->signature->kind == CBM_TYPE_FUNC && |
| 3087 | rf->signature->data.func.param_names && rf->signature->data.func.param_types) { |
| 3088 | for (int i = 0; rf->signature->data.func.param_names[i] && |
| 3089 | rf->signature->data.func.param_types[i]; |
| 3090 | i++) { |
| 3091 | const char *pname = rf->signature->data.func.param_names[i]; |
| 3092 | const CBMType *ptype = rf->signature->data.func.param_types[i]; |
| 3093 | if (cbm_type_is_unknown(ptype)) |
| 3094 | continue; |
| 3095 | const CBMType *existing = cbm_scope_lookup(ctx->current_scope, pname); |
| 3096 | if (existing && !cbm_type_is_unknown(existing)) |
| 3097 | continue; |
| 3098 | cbm_scope_bind(ctx->current_scope, pname, ptype); |
| 3099 | } |
| 3100 | } |
| 3101 | } |
| 3102 | } |
| 3103 | |
| 3104 | TSNode body = ts_node_child_by_field_name(func_node, "body", TS_LSP_FIELD_LEN("body")); |
| 3105 | if (!ts_node_is_null(body)) { |
| 3106 | // statement_block: walk every child statement. Expression body (concise |
| 3107 | // arrow): walk the expression directly — process_node on that handles |
| 3108 | // call_expression resolution. |
| 3109 | if (strcmp(ts_node_type(body), "statement_block") == 0) { |
| 3110 | TSTreeCursor cursor = ts_tree_cursor_new(body); |
| 3111 | if (ts_tree_cursor_goto_first_child(&cursor)) { |
| 3112 | do { |
| 3113 | process_node(ctx, ts_tree_cursor_current_node(&cursor)); |
| 3114 | } while (ts_tree_cursor_goto_next_sibling(&cursor)); |
no test coverage detected