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
114 lines
3.9 KiB
Go
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, "\"'"))
|
|
}
|