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
138 lines
4.7 KiB
Go
138 lines
4.7 KiB
Go
package languages
|
|
|
|
import (
|
|
"bytes"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
"github.com/zzet/gortex/internal/parser"
|
|
sitter "github.com/zzet/gortex/internal/parser/tsitter"
|
|
"github.com/zzet/gortex/internal/parser/tsitter/yaml"
|
|
)
|
|
|
|
// YAMLExtractor extracts YAML files into graph nodes and edges.
|
|
// It focuses on top-level keys as KindVariable.
|
|
type YAMLExtractor struct {
|
|
lang *sitter.Language
|
|
}
|
|
|
|
func NewYAMLExtractor() *YAMLExtractor {
|
|
return &YAMLExtractor{lang: yaml.GetLanguage()}
|
|
}
|
|
|
|
func (e *YAMLExtractor) Language() string { return "yaml" }
|
|
func (e *YAMLExtractor) Extensions() []string {
|
|
// `.yaml` / `.yml` cover the bulk of YAML files (including
|
|
// `kustomization.yaml`). `Kustomization` is the bare-basename
|
|
// form Kustomize accepts when no extension is desired — it
|
|
// must be registered as a basename so the registry routes it
|
|
// to the YAML extractor (which then dispatches into the
|
|
// kustomize path inside Extract).
|
|
return []string{".yaml", ".yml", "Kustomization"}
|
|
}
|
|
|
|
func (e *YAMLExtractor) Extract(filePath string, src []byte) (*parser.ExtractionResult, error) {
|
|
tree, err := parser.ParseFile(src, e.lang)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tree.Close()
|
|
|
|
root := tree.RootNode()
|
|
result := &parser.ExtractionResult{}
|
|
|
|
fileNode := &graph.Node{
|
|
ID: filePath, Kind: graph.KindFile, Name: filePath,
|
|
FilePath: filePath, StartLine: 1, EndLine: int(root.EndPoint().Row) + 1,
|
|
Language: "yaml",
|
|
}
|
|
result.Nodes = append(result.Nodes, fileNode)
|
|
|
|
// Helm render manifests (templates/*.yaml) embed Go-template
|
|
// include/template directives. Scan them up front and emit EdgeCalls
|
|
// into the chart's named templates — this augments, it does not
|
|
// replace, the normal dispatch below (the file still gets its
|
|
// resource / config-key nodes). Conservative: only when an
|
|
// include/template directive is actually present.
|
|
if bytes.Contains(src, []byte("{{")) &&
|
|
(bytes.Contains(src, []byte("include")) || bytes.Contains(src, []byte("template"))) {
|
|
helmTemplateCallsFromYAML(filePath, fileNode.ID, src, result)
|
|
}
|
|
|
|
// Specialised YAML dispatch. Order matters:
|
|
// 1. Kustomize files have a fixed basename — short-circuit.
|
|
// 2. K8s manifests are detected by content (apiVersion+kind).
|
|
// 3. dbt schema / properties files are detected by content
|
|
// fingerprint (models/sources/seeds/snapshots + columns).
|
|
// 4. Otherwise fall through to the generic top-level-keys
|
|
// walker so plain config YAMLs still index.
|
|
if isKustomizationFile(filePath) {
|
|
extractKustomizeYAML(filePath, fileNode.ID, src, result)
|
|
return result, nil
|
|
}
|
|
if extractKubernetesYAML(filePath, fileNode.ID, src, result) {
|
|
return result, nil
|
|
}
|
|
if extractDbtSchemaYAML(filePath, fileNode.ID, src, result) {
|
|
return result, nil
|
|
}
|
|
if extractSymfonyServicesYAML(filePath, fileNode.ID, src, result) {
|
|
return result, nil
|
|
}
|
|
if extractAnsibleYAML(filePath, fileNode.ID, src, result) {
|
|
return result, nil
|
|
}
|
|
|
|
// Walk only top-level block_mapping_pair nodes.
|
|
e.extractTopLevelKeys(root, src, filePath, fileNode.ID, result)
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (e *YAMLExtractor) extractTopLevelKeys(root *sitter.Node, src []byte, filePath, fileID string, result *parser.ExtractionResult) {
|
|
// YAML tree-sitter grammar: stream -> document -> block_node -> block_mapping -> block_mapping_pair
|
|
// We walk looking for block_mapping_pair nodes at the top-level mapping only.
|
|
seen := make(map[string]bool)
|
|
e.findTopLevelPairs(root, src, filePath, fileID, result, seen, 0)
|
|
}
|
|
|
|
func (e *YAMLExtractor) findTopLevelPairs(node *sitter.Node, src []byte, filePath, fileID string, result *parser.ExtractionResult, seen map[string]bool, depth int) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
|
|
nodeType := node.Type()
|
|
|
|
// If we hit a block_mapping_pair at the top-level mapping, extract the key.
|
|
if nodeType == "block_mapping_pair" && depth <= 5 {
|
|
// The first child is typically the key.
|
|
keyNode := node.ChildByFieldName("key")
|
|
if keyNode == nil && node.ChildCount() > 0 {
|
|
keyNode = node.Child(0)
|
|
}
|
|
if keyNode != nil {
|
|
keyName := keyNode.Content(src)
|
|
if keyName != "" && !seen[keyName] {
|
|
seen[keyName] = true
|
|
id := filePath + "::" + keyName
|
|
result.Nodes = append(result.Nodes, &graph.Node{
|
|
ID: id, Kind: graph.KindVariable, Name: keyName,
|
|
FilePath: filePath, StartLine: int(node.StartPoint().Row) + 1, EndLine: int(node.EndPoint().Row) + 1,
|
|
Language: "yaml",
|
|
})
|
|
result.Edges = append(result.Edges, &graph.Edge{
|
|
From: fileID, To: id, Kind: graph.EdgeDefines,
|
|
FilePath: filePath, Line: int(node.StartPoint().Row) + 1,
|
|
})
|
|
}
|
|
}
|
|
return // Don't recurse into nested mappings.
|
|
}
|
|
|
|
for i, _nc := 0, int(node.ChildCount()); i < _nc; i++ {
|
|
child := node.Child(i)
|
|
if child != nil {
|
|
e.findTopLevelPairs(child, src, filePath, fileID, result, seen, depth+1)
|
|
}
|
|
}
|
|
}
|