Skip to content

Commit

Permalink
fix it
Browse files Browse the repository at this point in the history
  • Loading branch information
qvalentin committed Nov 23, 2024
1 parent 28efb0b commit 00eba70
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 30 deletions.
2 changes: 1 addition & 1 deletion internal/handler/completion_main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ func TestCompletionMainSingleLines(t *testing.T) {
notExpectedInsertTexts []string
err error
}{
{"Test completion on {{.Bad.^}}", []string{}, []string{}, errors.New("[Bad ] is no valid template context for helm")},
{"Test completion on {{ .Values.^ingress.hosts }}", []string{"ingress"}, []string{"hosts"}, nil},
{"Test completion on {{.Bad.^}}", []string{}, []string{}, errors.New("[Bad ] is no valid template context for helm")},
{"Test completion on {{ .Bad.^ }}", []string{}, []string{}, errors.New("[Bad ] is no valid template context for helm")},
{"Test completion on {{ n^ }}", []string{"not"}, []string{}, nil},
{"Test completion on {{ .Values.^ }}", []string{"replicaCount"}, []string{}, nil},
Expand Down
17 changes: 14 additions & 3 deletions internal/lsp/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ func NodeAtPosition(tree *sitter.Tree, position lsp.Position) *sitter.Node {
}

func NestedNodeAtPositionForCompletion(tree *sitter.Tree, position lsp.Position) *sitter.Node {
currentNode := NodeAtPosition(tree, position)
pointToLoopUp := sitter.Point{
pointToLookUp := sitter.Point{
Row: position.Line,
Column: position.Character - 1,
}
return FindRelevantChildNodeCompletion(currentNode, pointToLoopUp)
currentNode := tree.RootNode().NamedDescendantForPointRange(pointToLookUp, pointToLookUp)
node := FindRelevantChildNodeCompletion(currentNode, pointToLookUp)

return node
}

func FindRelevantChildNode(currentNode *sitter.Node, pointToLookUp sitter.Point) *sitter.Node {
Expand All @@ -44,6 +46,8 @@ func FindRelevantChildNode(currentNode *sitter.Node, pointToLookUp sitter.Point)

func FindRelevantChildNodeCompletion(currentNode *sitter.Node, pointToLookUp sitter.Point) *sitter.Node {
childCount := int(currentNode.ChildCount())

// iterate over children backwards
for i := childCount - 1; i >= 0; i-- {
child := currentNode.Child(i)
if child == nil {
Expand All @@ -69,6 +73,13 @@ func isPointLargerOrEq(a sitter.Point, b sitter.Point) bool {
return a.Row > b.Row
}

func isPointLarger(a sitter.Point, b sitter.Point) bool {

Check failure on line 76 in internal/lsp/ast.go

View workflow job for this annotation

GitHub Actions / lint (1.22.7, ubuntu-latest)

func `isPointLarger` is unused (unused)
if a.Row == b.Row {
return a.Column > b.Column
}
return a.Row > b.Row
}

func (d *Document) ApplyChangesToAst(newContent string) {
d.Ast = ParseAst(nil, newContent)
}
Expand Down
106 changes: 85 additions & 21 deletions internal/lsp/ast_test.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,100 @@
package lsp

import (
"strings"
"testing"

sitter "github.com/smacker/go-tree-sitter"
"github.com/stretchr/testify/assert"
"go.lsp.dev/protocol"
)

func TestFindRelevantChildNodeCompletio(t *testing.T) {
template := `{{ .Values. }}
{{ .Values.re }}
func TestFindRelevantChildNodeCompletion(t *testing.T) {
tests := []struct {
template string
startColumn uint32
endColumn uint32
nodeType string
nodeContent string
}{
{
template: `{{ .Values.^test }}`,
startColumn: 10,
endColumn: 11,
nodeType: ".",
nodeContent: ".",
},
{
template: `{{ .Values.^ }}`,
startColumn: 10,
endColumn: 11,
nodeType: ".",
nodeContent: ".",
},
{
template: "{{ .Bad.^ }}",
startColumn: 7,
endColumn: 8,
nodeType: ".",
nodeContent: ".",
},
{
template: "this is some additional text {{ .Bad.^ }}",
startColumn: 36,
endColumn: 37,
nodeType: ".",
nodeContent: ".",
},
{
template: `{{ .Values.te^st }}`,
startColumn: 11,
endColumn: 15,
nodeType: "field_identifier",
nodeContent: "test",
},
{
template: `{{ .Values.t^est }}`,
startColumn: 11,
endColumn: 15,
nodeType: "field_identifier",
nodeContent: "test",
},
}

{{ toY }}
for _, tt := range tests {
t.Run(tt.template, func(t *testing.T) {
position, content := getPositionForMarkedTestLine(tt.template)

{{ .Chart.N }}
{{ . }}
`
ast := ParseAst(nil, template)
ast := ParseAst(nil, content)

logger.Println("RootNode:", ast.RootNode().String())
t.Logf("RootNode: %s", ast.RootNode().String())

node := FindRelevantChildNodeCompletion(ast.RootNode(), sitter.Point{
Row: 0,
Column: 11,
})
node := NestedNodeAtPositionForCompletion(ast, position)

assert.Equal(t, sitter.Point{
Row: 0,
Column: 10,
}, node.StartPoint())
assert.Equal(t, sitter.Point{
Row: 0,
Column: 11,
}, node.EndPoint())
assert.Equal(t, tt.nodeContent, node.Content([]byte(content)))
assert.Equal(t, tt.nodeType, node.Type())

assert.Equal(t, sitter.Point{Column: tt.startColumn}, node.StartPoint())
assert.Equal(t, sitter.Point{Column: tt.endColumn}, node.EndPoint())

t.Log(node.Content([]byte(content)))
})
}
}

// Takes a string with a mark (^) in it and returns the position and the string without the mark
func getPointForMarkedTestLine(buf string) (sitter.Point, string) {
col := strings.Index(buf, "^")
buf = strings.Replace(buf, "^", "", 1)
pos := sitter.Point{
Column: uint32(col),
}
return pos, buf
}

func getPositionForMarkedTestLine(buf string) (protocol.Position, string) {
col := strings.Index(buf, "^")
buf = strings.Replace(buf, "^", "", 1)
pos := protocol.Position{Line: 0, Character: uint32(col)}
return pos, buf
}
24 changes: 19 additions & 5 deletions internal/lsp/symbol_table_template_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,29 @@ func (v *TemplateContextVisitor) Enter(node *sitter.Node) {
v.symbolTable.AddTemplateContext(append(v.currentContext, content), GetRangeForNode(node.ChildByFieldName("name")))
case gotemplate.NodeTypeUnfinishedSelectorExpression:
operandNode := node.ChildByFieldName("operand")
content := getContextForSelectorExpression(operandNode, v.content)
if !content.IsVariable() {
content = append(v.currentContext, content...)
context := getContextForSelectorExpression(operandNode, v.content)
if !context.IsVariable() {
context = append(v.currentContext, context...)
}
v.symbolTable.AddTemplateContext(append(content, ""),
GetRangeForNode(node.Child(int(node.ChildCount())-1)))
dotNode := node.Child(int(node.ChildCount()) - 1)
v.symbolTable.AddTemplateContext(append(context, ""),
GetRangeForNode(dotNode))
case gotemplate.NodeTypeSelectorExpression:
operandNode := node.ChildByFieldName("operand")
if operandNode == nil {
return
}
if operandNode.Type() == gotemplate.NodeTypeVariable {
v.StashContext()
v.PushContext(operandNode.Content(v.content))
}
nextSibling := operandNode.NextSibling()
logger.Println("NextSibling", nextSibling.Type())

Check failure on line 83 in internal/lsp/symbol_table_template_context.go

View workflow job for this annotation

GitHub Actions / lint (1.22.7, ubuntu-latest)

SA5011: possible nil pointer dereference (staticcheck)

if nextSibling != nil && nextSibling.Type() == gotemplate.NodeTypeDotSymbol {

Check failure on line 85 in internal/lsp/symbol_table_template_context.go

View workflow job for this annotation

GitHub Actions / lint (1.22.7, ubuntu-latest)

SA5011(related information): this check suggests that the pointer can be nil (staticcheck)
v.symbolTable.AddTemplateContext(append(getContextForSelectorExpression(operandNode, v.content), ""), GetRangeForNode(nextSibling))
}
v.symbolTable.AddTemplateContext(v.currentContext, GetRangeForNode(node))
}
}

Expand Down Expand Up @@ -123,6 +134,9 @@ func (v *TemplateContextVisitor) ExitContextShift(node *sitter.Node) {
}
}

func (v *TemplateContextVisitor) addTemplateContextForDotInSelectorExpression() {

Check failure on line 137 in internal/lsp/symbol_table_template_context.go

View workflow job for this annotation

GitHub Actions / lint (1.22.7, ubuntu-latest)

func `(*TemplateContextVisitor).addTemplateContextForDotInSelectorExpression` is unused (unused)
}

func getContextForSelectorExpression(node *sitter.Node, content []byte) TemplateContext {
if node == nil {
return TemplateContext{}
Expand Down
50 changes: 50 additions & 0 deletions internal/lsp/symbol_table_template_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,59 @@ package lsp
import (
"testing"

sitter "github.com/smacker/go-tree-sitter"
"github.com/stretchr/testify/assert"
)

func TestTemplateContextVisitor(t *testing.T) {
tC := struct {
desc string
template string
nodeContent string
expected TemplateContext
}{
template: "{{ .Values.ingress.host }}",
}

s := &SymbolTable{
contexts: map[string][]sitter.Range{},
contextsReversed: map[sitter.Range]TemplateContext{},
}

ast := ParseAst(nil, tC.template)
rootNode := ast.RootNode()
v := Visitors{
symbolTable: s,
visitors: []Visitor{
NewTemplateContextVisitor(s, []byte(tC.template)),
},
}

v.visitNodesRecursiveWithScopeShift(rootNode)

keys := make([]sitter.Range, 0, len(s.contextsReversed))
for r := range s.contextsReversed {
keys = append(keys, r)
}

t.Log("Keys", keys)

assert.Len(t, s.GetTemplateContextRanges([]string{"Values"}), 1)
assert.Len(t, s.GetTemplateContextRanges([]string{"Values", "ingress"}), 1)
assert.Len(t, s.GetTemplateContextRanges([]string{"Values", "ingress", "host"}), 1)

templateContext, err := s.GetTemplateContext(sitter.Range{
StartPoint: sitter.Point{Column: 10},
EndPoint: sitter.Point{Column: 11},
StartByte: 10,
EndByte: 11,
})

assert.NoError(t, err)
assert.NotNil(t, templateContext)
assert.Equal(t, TemplateContext{"Values", ""}, templateContext)
}

func TestGetContextForSelectorExpression(t *testing.T) {
testCases := []struct {
desc string
Expand Down

0 comments on commit 00eba70

Please sign in to comment.