Recover a queue/topic identity from a Go composite-literal input struct, e.g. &sqs.SendMessageInput{QueueUrl: queueUrl, MessageBody: body} sns.PublishInput{TopicArn: "arn:aws:sns:..."} The dispatch target is carried by a struct field (QueueUrl/TopicArn/...), not a bare string arg, so the async edge would otherwise degrade to a plain CALLS. Returns the field's value: the string-literal content when
| 1426 | // Returns the field's value: the string-literal content when present, else the |
| 1427 | // referenced identifier text (which still names the queue/topic for edge formation). |
| 1428 | static const char *extract_composite_queue_field(CBMExtractCtx *ctx, TSNode node) { |
| 1429 | // Unwrap a pointer-of-composite: `&Type{...}` is a unary_expression whose |
| 1430 | // operand is the composite_literal. |
| 1431 | if (strcmp(ts_node_type(node), "unary_expression") == 0) { |
| 1432 | TSNode operand = ts_node_child_by_field_name(node, TS_FIELD("operand")); |
| 1433 | if (ts_node_is_null(operand)) { |
| 1434 | return NULL; |
| 1435 | } |
| 1436 | node = operand; |
| 1437 | } |
| 1438 | if (strcmp(ts_node_type(node), "composite_literal") != 0) { |
| 1439 | return NULL; |
| 1440 | } |
| 1441 | TSNode body = ts_node_child_by_field_name(node, TS_FIELD("body")); |
| 1442 | if (ts_node_is_null(body)) { |
| 1443 | body = cbm_find_child_by_kind(node, "literal_value"); |
| 1444 | } |
| 1445 | if (ts_node_is_null(body)) { |
| 1446 | return NULL; |
| 1447 | } |
| 1448 | uint32_t nc = ts_node_named_child_count(body); |
| 1449 | for (uint32_t i = 0; i < nc; i++) { |
| 1450 | TSNode el = ts_node_named_child(body, i); |
| 1451 | if (strcmp(ts_node_type(el), "keyed_element") != 0) { |
| 1452 | continue; |
| 1453 | } |
| 1454 | // keyed_element children: key then value. Each side may be wrapped in a |
| 1455 | // literal_element; unwrap to the underlying identifier/literal. |
| 1456 | uint32_t ec = ts_node_named_child_count(el); |
| 1457 | if (ec < PAIR_LEN) { |
| 1458 | continue; |
| 1459 | } |
| 1460 | TSNode key_n = ts_node_named_child(el, 0); |
| 1461 | TSNode val_n = ts_node_named_child(el, 1); |
| 1462 | if (strcmp(ts_node_type(key_n), "literal_element") == 0 && |
| 1463 | ts_node_named_child_count(key_n) > 0) { |
| 1464 | key_n = ts_node_named_child(key_n, 0); |
| 1465 | } |
| 1466 | if (strcmp(ts_node_type(val_n), "literal_element") == 0 && |
| 1467 | ts_node_named_child_count(val_n) > 0) { |
| 1468 | val_n = ts_node_named_child(val_n, 0); |
| 1469 | } |
| 1470 | char *key = cbm_node_text(ctx->arena, key_n, ctx->source); |
| 1471 | if (!is_queue_topic_field(key)) { |
| 1472 | continue; |
| 1473 | } |
| 1474 | const char *resolved = extract_string_value(ctx, val_n); |
| 1475 | if (resolved && resolved[0]) { |
| 1476 | return resolved; |
| 1477 | } |
| 1478 | // Value is a variable/expression (no constant value); use its source text |
| 1479 | // as the queue/topic identity so the async edge still forms. |
| 1480 | char *raw = cbm_node_text(ctx->arena, val_n, ctx->source); |
| 1481 | if (raw && raw[0]) { |
| 1482 | return raw; |
| 1483 | } |
| 1484 | } |
| 1485 | return NULL; |
no test coverage detected