chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
package languages
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
sitter "github.com/zzet/gortex/internal/parser/tsitter"
|
||||
|
||||
"github.com/zzet/gortex/internal/graph"
|
||||
"github.com/zzet/gortex/internal/parser"
|
||||
)
|
||||
|
||||
// goFlagMethods maps a flag-check method name to its inferred
|
||||
// provider. The classification is best-effort: a domain function
|
||||
// named IsEnabled would be misclassified as Unleash, but in
|
||||
// practice every reasonable codebase that has its own IsEnabled
|
||||
// either *is* a flag client (matching the spec exactly) or carries
|
||||
// enough surrounding context that the false positive is acceptable
|
||||
// noise the gate can be turned off to suppress.
|
||||
//
|
||||
// Provider strings match the spec's enumeration so downstream
|
||||
// consumers (cleanup queries, drift detection) can scope by
|
||||
// provider without parsing meta.
|
||||
var goFlagMethods = map[string]string{
|
||||
// LaunchDarkly — typed Variation methods.
|
||||
"BoolVariation": "launchdarkly",
|
||||
"StringVariation": "launchdarkly",
|
||||
"IntVariation": "launchdarkly",
|
||||
"FloatVariation": "launchdarkly",
|
||||
"JSONVariation": "launchdarkly",
|
||||
// GrowthBook.
|
||||
"IsOn": "growthbook",
|
||||
"Feature": "growthbook",
|
||||
// Unleash.
|
||||
"IsEnabled": "unleash",
|
||||
// Generic / internal flags packages — fall back to "internal".
|
||||
// The provider field on the resulting node carries the inferred
|
||||
// provider; the heuristic-based classification is good-enough
|
||||
// for the cleanup-workflow use case (find old flags) without
|
||||
// needing per-call type resolution.
|
||||
}
|
||||
|
||||
// goFlagOpByMethod returns the operation kind ("read" / "write" /
|
||||
// "register") for a recognised flag-check method. Today only reads
|
||||
// are recognised — writes (toggling a flag from code) and
|
||||
// registrations (declaring a new flag) are uncommon enough that
|
||||
// they don't justify the per-method dispatch yet. Reserved for
|
||||
// future expansion.
|
||||
func goFlagOpByMethod(method string) string {
|
||||
_ = method
|
||||
return "read"
|
||||
}
|
||||
|
||||
// goFlagEvent is the deferred record emitted at capture time and
|
||||
// resolved during the post-pass. Mirrors goObservabilityEvent.
|
||||
type goFlagEvent struct {
|
||||
provider string // launchdarkly / growthbook / unleash / internal
|
||||
method string // exact method name
|
||||
name string // flag identifier — first string-literal arg
|
||||
line int // 1-based line of the call expression
|
||||
}
|
||||
|
||||
// detectGoFlagCheck inspects a callm.expr capture and returns the
|
||||
// resolved provider plus flag name when the call matches the
|
||||
// flag-method set and carries a string-literal flag identifier.
|
||||
// ok=false on every other shape.
|
||||
func detectGoFlagCheck(callExpr *sitter.Node, method string, src []byte) (provider, name string, ok bool) {
|
||||
if callExpr == nil {
|
||||
return "", "", false
|
||||
}
|
||||
provider, ok = goFlagMethods[method]
|
||||
if !ok {
|
||||
return "", "", false
|
||||
}
|
||||
args := callExpr.ChildByFieldName("arguments")
|
||||
if args == nil {
|
||||
return "", "", false
|
||||
}
|
||||
for i, _nc := 0, int(args.NamedChildCount()); i < _nc; i++ {
|
||||
c := args.NamedChild(i)
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
if c.Type() != "interpreted_string_literal" && c.Type() != "raw_string_literal" {
|
||||
continue
|
||||
}
|
||||
text := strings.Trim(c.Content(src), "\"`")
|
||||
if text == "" {
|
||||
return "", "", false
|
||||
}
|
||||
return provider, text, true
|
||||
}
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
// emitGoFlagChecks turns deferred flag-check records into KindFlag
|
||||
// nodes and EdgeTogglesFlag edges. Nodes share IDs across files in
|
||||
// a repo — the same flag name produces a single node per provider
|
||||
// that every check site links to. graph.AddNode dedupes on ID, so
|
||||
// emitting the same flag from multiple files in the same call is
|
||||
// cheap.
|
||||
//
|
||||
// callerLookup maps a 1-based line to the enclosing function ID,
|
||||
// matching the observability emitter's contract.
|
||||
func emitGoFlagChecks(events []goFlagEvent, callerLookup func(line int) string, filePath string, result *parser.ExtractionResult) {
|
||||
if len(events) == 0 {
|
||||
return
|
||||
}
|
||||
seen := make(map[string]struct{}, len(events))
|
||||
for _, e := range events {
|
||||
callerID := callerLookup(e.line)
|
||||
if callerID == "" {
|
||||
continue
|
||||
}
|
||||
flagID := goFlagNodeID(e.provider, e.name)
|
||||
if _, ok := seen[flagID]; !ok {
|
||||
seen[flagID] = struct{}{}
|
||||
result.Nodes = append(result.Nodes, &graph.Node{
|
||||
ID: flagID,
|
||||
Kind: graph.KindFlag,
|
||||
Name: e.name,
|
||||
FilePath: filePath, // first sighting; not authoritative
|
||||
Language: "go",
|
||||
Meta: map[string]any{
|
||||
"provider": e.provider,
|
||||
"name": e.name,
|
||||
},
|
||||
})
|
||||
}
|
||||
result.Edges = append(result.Edges, &graph.Edge{
|
||||
From: callerID,
|
||||
To: flagID,
|
||||
Kind: graph.EdgeTogglesFlag,
|
||||
FilePath: filePath,
|
||||
Line: e.line,
|
||||
Origin: graph.OriginASTInferred,
|
||||
Meta: map[string]any{
|
||||
"op": goFlagOpByMethod(e.method),
|
||||
"method": e.method,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// goFlagNodeID is the canonical ID for a feature-flag node. The
|
||||
// `flag::` prefix is reserved for shared flag identifiers and
|
||||
// matches the synthetic-ID convention used by `module::`,
|
||||
// `event::`, `external::`, and `annotation::` so the exporter
|
||||
// surfaces it through the same stub-node code path.
|
||||
func goFlagNodeID(provider, name string) string {
|
||||
if provider == "" {
|
||||
provider = "internal"
|
||||
}
|
||||
return "flag::" + provider + "::" + name
|
||||
}
|
||||
Reference in New Issue
Block a user