From da5d643084b8b4a98b2418f806982af468dd90a4 Mon Sep 17 00:00:00 2001 From: qvalentin Date: Sun, 19 May 2024 18:47:05 +0200 Subject: [PATCH] feat(symbol-table): read in template context with variables --- internal/handler/hover_main_test.go | 28 ++++++++----- internal/lsp/symbol_table.go | 2 +- internal/lsp/symbol_table_template_context.go | 15 +++++-- .../lsp/symbol_table_template_context_test.go | 40 +++++++++++++++++++ internal/lsp/symbol_table_test.go | 22 ++++++++++ internal/lsp/visitor.go | 6 ++- testdata/example/templates/deployment.yaml | 1 + 7 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 internal/lsp/symbol_table_template_context_test.go diff --git a/internal/handler/hover_main_test.go b/internal/handler/hover_main_test.go index b31509da..2e3959fa 100644 --- a/internal/handler/hover_main_test.go +++ b/internal/handler/hover_main_test.go @@ -2,7 +2,6 @@ package handler import ( "context" - "errors" "fmt" "os" "path/filepath" @@ -24,6 +23,24 @@ func TestHoverMain(t *testing.T) { expected string expectedError error }{ + { + desc: "Test hover on template context with variables", + position: lsp.Position{ + Line: 74, + Character: 50, + }, + expected: "$root.Values.deployments", + expectedError: nil, + }, + { + desc: "Test hover on template context with variables in range loop", + position: lsp.Position{ + Line: 80, + Character: 35, + }, + expected: "$config.hpa.minReplicas", + expectedError: nil, + }, { desc: "Test hover on dot", position: lsp.Position{ @@ -114,15 +131,6 @@ func TestHoverMain(t *testing.T) { expected: fmt.Sprintf("### %s\n%s\n\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "1"), expectedError: nil, }, - { - desc: "Test hover on template context with variables", - position: lsp.Position{ - Line: 74, - Character: 50, - }, - expected: "", - expectedError: errors.New("no template context found"), - }, } for _, tt := range testCases { t.Run(tt.desc, func(t *testing.T) { diff --git a/internal/lsp/symbol_table.go b/internal/lsp/symbol_table.go index 8588c57e..d5227111 100644 --- a/internal/lsp/symbol_table.go +++ b/internal/lsp/symbol_table.go @@ -18,7 +18,7 @@ func (t TemplateContext) Tail() TemplateContext { } func (t TemplateContext) IsVariable() bool { - return len(t) > 0 && t[0] == "$" + return len(t) > 0 && strings.HasPrefix(t[0], "$") } func (t TemplateContext) AppendSuffix(suffix string) TemplateContext { diff --git a/internal/lsp/symbol_table_template_context.go b/internal/lsp/symbol_table_template_context.go index b77cdcbf..18688dcb 100644 --- a/internal/lsp/symbol_table_template_context.go +++ b/internal/lsp/symbol_table_template_context.go @@ -67,8 +67,12 @@ func (v *TemplateContextVisitor) Enter(node *sitter.Node) { GetRangeForNode(node.Child(int(node.ChildCount())-1))) case gotemplate.NodeTypeSelectorExpression: operandNode := node.ChildByFieldName("operand") - if operandNode.Type() == gotemplate.NodeTypeVariable && operandNode.Content(v.content) == "$" { + if operandNode.Type() == gotemplate.NodeTypeVariable { v.StashContext() + if operandNode.Content(v.content) != "$" { + v.symbolTable.AddTemplateContext(append(v.currentContext, operandNode.Content(v.content)), GetRangeForNode(operandNode)) + v.PushContext(operandNode.Content(v.content)) + } } } } @@ -77,7 +81,10 @@ func (v *TemplateContextVisitor) Exit(node *sitter.Node) { switch node.Type() { case gotemplate.NodeTypeSelectorExpression, gotemplate.NodeTypeUnfinishedSelectorExpression: operandNode := node.ChildByFieldName("operand") - if operandNode.Type() == gotemplate.NodeTypeVariable && operandNode.Content(v.content) == "$" { + if operandNode.Type() == gotemplate.NodeTypeVariable { + if operandNode.Content(v.content) != "$" { + v.PopContext() + } v.RestoreStashedContext() } } @@ -97,7 +104,9 @@ func (v *TemplateContextVisitor) EnterContextShift(node *sitter.Node, suffix str s = s.AppendSuffix(suffix) if s.IsVariable() { v.StashContext() - s = s.Tail() + if s[0] == "$" { + s = s.Tail() + } } } v.PushContextMany(s) diff --git a/internal/lsp/symbol_table_template_context_test.go b/internal/lsp/symbol_table_template_context_test.go new file mode 100644 index 00000000..a912e13b --- /dev/null +++ b/internal/lsp/symbol_table_template_context_test.go @@ -0,0 +1,40 @@ +package lsp + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetContextForSelectorExpression(t *testing.T) { + testCases := []struct { + desc string + template string + nodeContent string + expected TemplateContext + }{ + { + desc: "Selects single selector expression correctly", + template: `{{ .Values.test }}`, + nodeContent: ".Values.test", + expected: TemplateContext{"Values", "test"}, + }, + { + desc: "Selects selector expression with variable correctly", + template: `{{ $x.test }}`, + nodeContent: "$x.test", + expected: TemplateContext{"$x", "test"}, + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + ast := ParseAst(nil, tC.template) + node := ast.RootNode().Child(1) + + assert.Equal(t, tC.nodeContent, node.Content([]byte(tC.template))) + result := getContextForSelectorExpression(node, []byte(tC.template)) + + assert.Equal(t, tC.expected, result) + }) + } +} diff --git a/internal/lsp/symbol_table_test.go b/internal/lsp/symbol_table_test.go index 530bf7e1..2b088431 100644 --- a/internal/lsp/symbol_table_test.go +++ b/internal/lsp/symbol_table_test.go @@ -223,6 +223,28 @@ func TestSymbolTableForValuesSingleTests(t *testing.T) { } testCases := []testCase{ + { + template: ` + {{- $root := . -}} + {{- range $type, $config := $root.Values.deployments }} + {{- .InLoop }} + {{- end }} + {{ .Values.test }} +`, + path: []string{"$root", "Values"}, + startPoint: sitter.Point{ + Row: 2, + Column: 40, + }, + }, + { + template: `{{ $x := .Values }}{{ $x.test }}{{ .Values.test }}`, + path: []string{"$x", "test"}, + startPoint: sitter.Point{ + Row: 0, + Column: 25, + }, + }, { template: `{{ if (and .Values. ) }} {{ end }} `, path: []string{"Values"}, diff --git a/internal/lsp/visitor.go b/internal/lsp/visitor.go index 7c96e639..8fdd9945 100644 --- a/internal/lsp/visitor.go +++ b/internal/lsp/visitor.go @@ -43,7 +43,11 @@ func (v *Visitors) visitNodesRecursiveWithScopeShift(node *sitter.Node) { case gotemplate.NodeTypeRangeAction: rangeNode := node.ChildByFieldName("range") if rangeNode == nil { - break // range is optional (e.g. {{ range $index, $element := pipeline }}) + rangeNode = node.NamedChild(0).ChildByFieldName("range") + if rangeNode == nil { + logger.Error("Could not find range node") + break + } } v.visitNodesRecursiveWithScopeShift(rangeNode) for _, visitor := range v.visitors { diff --git a/testdata/example/templates/deployment.yaml b/testdata/example/templates/deployment.yaml index d77fd2d8..b3ceb552 100644 --- a/testdata/example/templates/deployment.yaml +++ b/testdata/example/templates/deployment.yaml @@ -79,5 +79,6 @@ spec: name: my-app-{{ $type }} spec: replicas: {{ $config.hpa.minReplicas }} + {{ $.Values.ingress.hosts }} --- {{- end }}