| 464 | } |
| 465 | |
| 466 | static void py_process_call(CBMExtractCtx *ctx, TSNode call, const chan_const_table_t *consts) { |
| 467 | /* Python call: attribute { object, attribute }, argument_list */ |
| 468 | TSNode func = ts_node_child_by_field_name(call, TS_FIELD("function")); |
| 469 | if (ts_node_is_null(func)) { |
| 470 | return; |
| 471 | } |
| 472 | const char *fk = ts_node_type(func); |
| 473 | if (strcmp(fk, "attribute") != 0) { |
| 474 | return; |
| 475 | } |
| 476 | TSNode object = ts_node_child_by_field_name(func, TS_FIELD("object")); |
| 477 | TSNode attr = ts_node_child_by_field_name(func, TS_FIELD("attribute")); |
| 478 | if (ts_node_is_null(object) || ts_node_is_null(attr)) { |
| 479 | return; |
| 480 | } |
| 481 | |
| 482 | char *method = cbm_node_text(ctx->arena, attr, ctx->source); |
| 483 | const char *transport = py_classify_receiver(ctx, object); |
| 484 | if (!transport || !method) { |
| 485 | return; |
| 486 | } |
| 487 | |
| 488 | int dir = py_classify_direction(transport, method); |
| 489 | if (dir == CHAN_DIR_UNKNOWN) { |
| 490 | return; |
| 491 | } |
| 492 | CBMChannelDirection direction = (CBMChannelDirection)dir; |
| 493 | |
| 494 | TSNode args = ts_node_child_by_field_name(call, TS_FIELD("arguments")); |
| 495 | if (ts_node_is_null(args)) { |
| 496 | return; |
| 497 | } |
| 498 | const char *channel_name = extract_channel_name(ctx, args, consts); |
| 499 | if (!channel_name) { |
| 500 | return; |
| 501 | } |
| 502 | push_channel(ctx, channel_name, transport, direction, call); |
| 503 | } |
| 504 | |
| 505 | /* Detect Python decorator-based listeners: @sio.on("event") / @sio.event */ |
| 506 | static void py_process_decorator(CBMExtractCtx *ctx, TSNode decorator, |
no test coverage detected