Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

114 lines
3.9 KiB
Go

package languages
import (
"strings"
sitter "github.com/zzet/gortex/internal/parser/tsitter"
)
// Python event pub/sub detection (F15). The Python extractor buffers
// every attribute-style call (`callattr.expr`); this file recognises
// the subset that publish to or subscribe from a message broker —
// NATS (`nc.publish` / `nc.subscribe`), Kafka (`producer.send`),
// RabbitMQ / pika (`channel.basic_publish` / `channel.basic_consume`),
// Redis (`r.publish` / `pubsub.subscribe` / `pubsub.psubscribe`).
// Classification, transport inference, and graph emission are the
// cross-language helpers in `pubsub.go`.
// pyPubsubKeywordKeys is the set of keyword-argument names that carry a
// pub/sub topic when the broker call names it as a keyword rather than
// a positional string. pika's `basic_publish(exchange=…, routing_key=…)`
// and `basic_consume(queue=…)` are the motivating cases.
var pyPubsubKeywordKeys = map[string]struct{}{
"routing_key": {},
"queue": {},
"exchange": {},
"subject": {},
"channel": {},
"topic": {},
"event": {},
}
// detectPyPubsubCall inspects an attribute call and, when its method is
// a known pub/sub operation with a resolvable topic, returns the
// classified event. importPaths is the file's import set, used to
// disambiguate generic method names and infer the transport.
func detectPyPubsubCall(callExpr *sitter.Node, method string, src []byte, importPaths []string, line int) (pubsubEvent, bool) {
if callExpr == nil {
return pubsubEvent{}, false
}
if _, known := pubsubMethods[method]; !known {
return pubsubEvent{}, false
}
topic := firstPyPubsubTopicArg(callExpr, src)
if topic == "" {
return pubsubEvent{}, false
}
return classifyPubsubCall(method, topic, importPaths, line)
}
// firstPyPubsubTopicArg pulls the topic string out of a Python `call`
// node — the first positional string literal, or the first recognised
// topic keyword argument (`routing_key=` / `queue=` / …). Returns ""
// for f-strings, concatenations, and non-string values.
func firstPyPubsubTopicArg(callExpr *sitter.Node, src []byte) string {
args := callExpr.ChildByFieldName("arguments")
if args == nil {
return ""
}
for i, _nc := 0, int(args.NamedChildCount()); i < _nc; i++ {
c := args.NamedChild(i)
if c == nil {
continue
}
switch c.Type() {
case "string":
if s := pyStringLiteralContent(c, src); s != "" {
return s
}
case "keyword_argument":
nameNode := c.ChildByFieldName("name")
valNode := c.ChildByFieldName("value")
if nameNode == nil || valNode == nil || valNode.Type() != "string" {
continue
}
key := strings.ToLower(nameNode.Content(src))
if _, ok := pyPubsubKeywordKeys[key]; !ok {
continue
}
if s := pyStringLiteralContent(valNode, src); s != "" {
return s
}
}
}
return ""
}
// pyStringLiteralContent returns the body of a tree-sitter Python
// `string` node. The grammar splits the literal into `string_start`,
// `string_content`, and `string_end`; reading the `string_content`
// child drops the quotes and any prefix (r / b / f). A prefixed string
// (f-string / b-string) with interpolation is still returned by its
// literal content — callers gate on the method classification, not the
// string shape, and a stable-enough topic name is the common case.
func pyStringLiteralContent(strNode *sitter.Node, src []byte) string {
for i, _nc := 0, int(strNode.NamedChildCount()); i < _nc; i++ {
c := strNode.NamedChild(i)
if c == nil {
continue
}
switch c.Type() {
case "string_content":
return strings.TrimSpace(c.Content(src))
case "interpolation":
// f-string with a substitution — no stable topic name.
return ""
}
}
// Fallback for grammars that don't split the body out: trim quotes
// and any single-letter prefix.
text := strings.TrimSpace(strNode.Content(src))
text = strings.TrimLeft(text, "rbfRBF")
return strings.TrimSpace(strings.Trim(text, "\"'"))
}