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

91 lines
2.9 KiB
Go

package contracts
import (
"fmt"
"regexp"
"strings"
"github.com/zzet/gortex/internal/graph"
)
// NestMicroserviceExtractor detects NestJS microservice message handlers —
// @MessagePattern (request/response) and @EventPattern (fire-and-forget) — and
// records each as a topic contract on the pattern it serves.
type NestMicroserviceExtractor struct{}
func (e *NestMicroserviceExtractor) SupportedLanguages() []string {
return []string{"typescript", "javascript"}
}
var (
// nestMsgPatternRe matches a @MessagePattern( / @EventPattern( decorator.
nestMsgPatternRe = regexp.MustCompile(`@(MessagePattern|EventPattern)\s*\(`)
// nestPatternStringRe pulls the first quoted token out of the decorator
// args, covering both `'cmd'` and `{ cmd: 'cmd' }` pattern forms.
nestPatternStringRe = regexp.MustCompile(`["']([^"']+)["']`)
// nestMethodDefRe matches the handler method definition a decorator wraps.
nestMethodDefRe = regexp.MustCompile(`^\s*(?:public\s+|private\s+|protected\s+|readonly\s+|async\s+|static\s+)*([A-Za-z_]\w*)\s*\(`)
)
// Extract scans for @MessagePattern / @EventPattern handlers.
func (e *NestMicroserviceExtractor) Extract(filePath string, src []byte, nodes []*graph.Node, edges []*graph.Edge) []Contract {
text := string(src)
if !strings.Contains(text, "@MessagePattern(") && !strings.Contains(text, "@EventPattern(") {
return nil
}
lines := strings.Split(text, "\n")
fileNodes := filterFileNodes(filePath, nodes)
var out []Contract
for i, line := range lines {
m := nestMsgPatternRe.FindStringSubmatchIndex(line)
if m == nil {
continue
}
kind := line[m[2]:m[3]] // MessagePattern | EventPattern
pm := nestPatternStringRe.FindStringSubmatch(line[m[1]:])
if pm == nil {
continue
}
pattern := pm[1]
handlerName, _ := nestHandlerAfter(lines, i)
symbolID := ""
if handlerName != "" {
symbolID = findFunctionByName(fileNodes, handlerName)
}
out = append(out, Contract{
ID: fmt.Sprintf("topic::%s", pattern),
Type: ContractTopic,
Role: RoleProvider,
SymbolID: symbolID,
FilePath: filePath,
Line: i + 1,
Meta: map[string]any{
"pattern": pattern,
"message_kind": kind,
"framework": "nestjs",
},
Confidence: 0.9,
})
}
return out
}
// nestHandlerAfter returns the name and 1-based line of the method a NestJS
// decorator stack wraps: the first method definition below fromIdx, skipping
// blank lines and further decorators. Shared across the GraphQL, websocket and
// microservice NestJS passes.
func nestHandlerAfter(lines []string, fromIdx int) (name string, lineNum int) {
for j := fromIdx + 1; j < len(lines); j++ {
t := strings.TrimSpace(lines[j])
if t == "" || strings.HasPrefix(t, "@") || strings.HasPrefix(t, "//") {
continue
}
if m := nestMethodDefRe.FindStringSubmatch(lines[j]); m != nil {
return m[1], j + 1
}
return "", 0
}
return "", 0
}