Skip to content

Commit

Permalink
Merge pull request #70 from mrjosh/fix/go-to-range-values
Browse files Browse the repository at this point in the history
fix: hover and go to definition fixes for arrays/lists
  • Loading branch information
qvalentin authored Mar 31, 2024
2 parents 0e3cd8c + f87c46c commit c004c5b
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 59 deletions.
2 changes: 1 addition & 1 deletion internal/handler/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func (h *langHandler) setItem(items []lsp.CompletionItem, value interface{}, var
documentation = valueOf.String()
)

logger.Println("ValueKind: ", valueOf)
logger.Debug("ValueKind: ", valueOf)

switch valueOf.Kind() {
case reflect.Slice, reflect.Map:
Expand Down
20 changes: 14 additions & 6 deletions internal/handler/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,18 @@ func (h *langHandler) definitionAstParsing(chart *charts.Chart, doc *lsplocal.Do
relevantChildNode = lsplocal.FindRelevantChildNode(currentNode, pointToLoopUp)
)

switch relevantChildNode.Type() {
nodeType := relevantChildNode.Type()
switch nodeType {
case gotemplate.NodeTypeIdentifier:
if relevantChildNode.Parent().Type() == gotemplate.NodeTypeVariable {
logger.Println("Parent type", relevantChildNode.Parent().Type())
parentType := relevantChildNode.Parent().Type()
if parentType == gotemplate.NodeTypeVariable {
return h.getDefinitionForVariable(relevantChildNode, doc)
}

if parentType == gotemplate.NodeTypeSelectorExpression || parentType == gotemplate.NodeTypeField {
return h.getDefinitionForValue(chart, relevantChildNode, doc)
}
return h.getDefinitionForFixedIdentifier(chart, relevantChildNode, doc)
case gotemplate.NodeTypeDot, gotemplate.NodeTypeDotSymbol, gotemplate.NodeTypeFieldIdentifier:
return h.getDefinitionForValue(chart, relevantChildNode, doc)
Expand All @@ -63,7 +70,7 @@ func (h *langHandler) getDefinitionForVariable(node *sitter.Node, doc *lsplocal.
variableName := node.Content([]byte(doc.Content))
defintionNode := lsplocal.GetVariableDefinition(variableName, node.Parent(), doc.Content)
if defintionNode == nil {
return []lsp.Location{}, fmt.Errorf("Could not find definition for %s", variableName)
return []lsp.Location{}, fmt.Errorf("Could not find definition for %s. Variable definition not found", variableName)
}
return []lsp.Location{{URI: doc.URI, Range: lsp.Range{Start: util.PointToPosition(defintionNode.StartPoint())}}}, nil
}
Expand All @@ -87,7 +94,7 @@ func (h *langHandler) getDefinitionForFixedIdentifier(chart *charts.Chart, node
nil
}

return []lsp.Location{}, fmt.Errorf("Could not find definition for %s", name)
return []lsp.Location{}, fmt.Errorf("Could not find definition for %s. Fixed identifier not found", name)
}

func (h *langHandler) getDefinitionForValue(chart *charts.Chart, node *sitter.Node, doc *lsplocal.Document) ([]lsp.Location, error) {
Expand Down Expand Up @@ -122,16 +129,17 @@ func (h *langHandler) getDefinitionForValue(chart *charts.Chart, node *sitter.No
}
return locations, nil
}
return []lsp.Location{}, fmt.Errorf("Could not find definition for %s", yamlPath)
return []lsp.Location{}, fmt.Errorf("Could not find definition for %s. No definition found", yamlPath)
}

func getYamlPath(node *sitter.Node, doc *lsplocal.Document) string {
switch node.Type() {
case gotemplate.NodeTypeDot:
return lsplocal.TraverseIdentifierPathUp(node, doc)
case gotemplate.NodeTypeDotSymbol, gotemplate.NodeTypeFieldIdentifier:
case gotemplate.NodeTypeDotSymbol, gotemplate.NodeTypeFieldIdentifier, gotemplate.NodeTypeIdentifier:
return lsplocal.GetFieldIdentifierPath(node, doc)
default:
logger.Error("Could not get yaml path for node type ", node.Type())
return ""
}
}
Expand Down
48 changes: 44 additions & 4 deletions internal/handler/definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ var testFileContent = `
{{ range $index, $element := pipeline }}{{ $index }}{{ $element }}{{ end }} # line 7
{{ .Values.foo }} # line 8
{{ .Values.something.nested }} # line 9
{{ range .Values.list }}
{{ . }} # line 12
{{ end }}
`

var (
Expand All @@ -35,6 +39,8 @@ var (
foo: bar
something:
nested: false
list:
- test
`
)

Expand Down Expand Up @@ -145,7 +151,29 @@ func TestDefinitionValue(t *testing.T) {
}

// Input:
// {{ .Values.something.nested }} # line 9
// {{ range .Values.list }}
// {{ . }} # line 12
// ---|
func TestDefinitionValueInList(t *testing.T) {
genericDefinitionTest(t, lsp.Position{Line: 12, Character: 3}, []lsp.Location{
{
URI: testValuesURI,
Range: lsp.Range{
Start: lsp.Position{
Line: 4,
Character: 0,
},
End: lsp.Position{
Line: 4,
Character: 0,
},
},
},
}, nil)
}

// Input:
// {{ . }} # line 9
// ----------------------|
func TestDefinitionValueNested(t *testing.T) {
genericDefinitionTest(t, lsp.Position{Line: 9, Character: 26}, []lsp.Location{
Expand Down Expand Up @@ -173,7 +201,11 @@ func TestDefinitionValueFile(t *testing.T) {
URI: testValuesURI,
Range: lsp.Range{
Start: lsp.Position{
Line: 0,
Line: 1,
Character: 0,
},
End: lsp.Position{
Line: 1,
Character: 0,
},
},
Expand Down Expand Up @@ -237,15 +269,23 @@ func TestDefinitionValueFileMulitpleValues(t *testing.T) {
URI: testValuesURI,
Range: lsp.Range{
Start: lsp.Position{
Line: 0,
Line: 1,
Character: 0,
},
End: lsp.Position{
Line: 1,
Character: 0,
},
},
}, {
URI: testOtherValuesURI,
Range: lsp.Range{
Start: lsp.Position{
Line: 0,
Line: 1,
Character: 0,
},
End: lsp.Position{
Line: 1,
Character: 0,
},
},
Expand Down
24 changes: 16 additions & 8 deletions internal/handler/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/mrjosh/helm-ls/internal/charts"
lspinternal "github.com/mrjosh/helm-ls/internal/lsp"
"github.com/mrjosh/helm-ls/internal/tree-sitter/gotemplate"

"github.com/mrjosh/helm-ls/internal/util"
"github.com/mrjosh/helm-ls/pkg/chart"
Expand Down Expand Up @@ -43,21 +44,22 @@ func (h *langHandler) Hover(ctx context.Context, params *lsp.HoverParams) (resul

pt := parent.Type()
ct := currentNode.Type()
if ct == "text" {
if ct == gotemplate.NodeTypeText {
word := doc.WordAt(params.Position)
if len(word) > 2 && string(word[len(word)-1]) == ":" {
word = word[0 : len(word)-1]
}
response, err := h.yamllsConnector.CallHover(ctx, *params, word)
return response, err
}
if pt == "function_call" && ct == "identifier" {
if pt == gotemplate.NodeTypeFunctionCall && ct == gotemplate.NodeTypeIdentifier {
word = currentNode.Content([]byte(doc.Content))
}
if (pt == "selector_expression" || pt == "field") && (ct == "identifier" || ct == "field_identifier") {
if (pt == gotemplate.NodeTypeSelectorExpression || pt == gotemplate.NodeTypeField) &&
(ct == gotemplate.NodeTypeIdentifier || ct == gotemplate.NodeTypeFieldIdentifier) {
word = lspinternal.GetFieldIdentifierPath(currentNode, doc)
}
if ct == "dot" {
if ct == gotemplate.NodeTypeDot {
word = lspinternal.TraverseIdentifierPathUp(currentNode, doc)
}

Expand Down Expand Up @@ -149,7 +151,7 @@ func (h *langHandler) getValueHover(chart *charts.Chart, splittedVar []string) (

for _, valuesFiles := range valuesFiles {
for _, valuesFile := range valuesFiles.ValuesFiles.AllValuesFiles() {
result, err := getTableOrValueForSelector(valuesFile.Values, strings.Join(valuesFiles.Selector, "."))
result, err := h.getTableOrValueForSelector(valuesFile.Values, strings.Join(valuesFiles.Selector, "."))
if err == nil {
results[valuesFile.URI] = result
}
Expand Down Expand Up @@ -178,13 +180,13 @@ func (h *langHandler) getValueHover(chart *charts.Chart, splittedVar []string) (
return result, nil
}

func getTableOrValueForSelector(values chartutil.Values, selector string) (string, error) {
func (h *langHandler) getTableOrValueForSelector(values chartutil.Values, selector string) (string, error) {
if len(selector) > 0 {
localValues, err := values.Table(selector)
if err != nil {
logger.Debug("values.PathValue(tableName) because of error", err)
value, err := values.PathValue(selector)
return fmt.Sprint(value), err
return h.formatToYAML(reflect.Indirect(reflect.ValueOf(value)), selector), err
}
logger.Debug("converting to YAML", localValues)
return localValues.YAML()
Expand All @@ -205,11 +207,17 @@ func (h *langHandler) getBuiltInObjectsHover(items []HelmDocumentation, key stri
func (h *langHandler) getMetadataField(v *chart.Metadata, fieldName string) string {
r := reflect.ValueOf(v)
field := reflect.Indirect(r).FieldByName(fieldName)
return h.formatToYAML(field, fieldName)
}

func (h *langHandler) formatToYAML(field reflect.Value, fieldName string) string {
switch field.Kind() {
case reflect.String:
return field.String()
case reflect.Slice, reflect.Map:
case reflect.Map:
return h.toYAML(field.Interface())
case reflect.Slice:
return h.toYAML(map[string]interface{}{fieldName: field.Interface()})
case reflect.Bool:
return fmt.Sprint(h.getBoolType(field))
default:
Expand Down
24 changes: 22 additions & 2 deletions internal/handler/hover_main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestHoverMain(t *testing.T) {
Line: 25,
Character: 28,
},
expected: fmt.Sprintf("### %s\n%s\n\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "[]"),
expected: fmt.Sprintf("### %s\n%s\n\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "imagePullSecrets: []\n"),
expectedError: nil,
},
{
Expand Down Expand Up @@ -86,6 +86,24 @@ func TestHoverMain(t *testing.T) {
expected: "",
expectedError: nil,
},
{
desc: "Test hover values list",
position: lsp.Position{
Line: 71,
Character: 35,
},
expected: fmt.Sprintf("### %s\n%s\n\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "ingress.hosts:\n- host: chart-example.local\n paths:\n - path: /\n pathType: ImplementationSpecific\n"),
expectedError: nil,
},
{
desc: "Test not existing values list",
position: lsp.Position{
Line: 101,
Character: 35,
},
expected: "",
expectedError: fmt.Errorf("Could not parse ast correctly"),
},
}
for _, tt := range testCases {
t.Run(tt.desc, func(t *testing.T) {
Expand Down Expand Up @@ -121,7 +139,9 @@ func TestHoverMain(t *testing.T) {
},
})
assert.Equal(t, tt.expectedError, err)
assert.Equal(t, tt.expected, result.Contents.Value)
if err == nil {
assert.Equal(t, tt.expected, result.Contents.Value)
}
})
}
}
4 changes: 3 additions & 1 deletion internal/handler/hover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ nested: value
splittedVar: []string{"key"},
},
want: `### values.yaml
[map[nested:value]]
key:
- nested: value
`,
wantErr: false,
Expand Down
1 change: 1 addition & 0 deletions internal/tree-sitter/gotemplate/node-types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
NodeTypeElseIf = "else if"
NodeTypeEnd = "end"
NodeTypeError = "ERROR"
NodeTypeField = "field"
NodeTypeFieldIdentifier = "field_identifier"
NodeTypeFunctionCall = "function_call"
NodeTypeIdentifier = "identifier"
Expand Down
6 changes: 1 addition & 5 deletions internal/util/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@ import (
"regexp"
"strings"

"github.com/mrjosh/helm-ls/internal/log"
"go.lsp.dev/protocol"
)

var (
logger = log.GetLogger()
wordRegex = regexp.MustCompile(`[^ \t\n\f\r,;\[\]\"\']+`)
)
var wordRegex = regexp.MustCompile(`[^ \t\n\f\r,;\[\]\"\']+`)

// BetweenStrings gets the substring between two strings.
func BetweenStrings(value string, a string, b string) string {
Expand Down
40 changes: 26 additions & 14 deletions internal/util/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package util

import (
"fmt"
"strings"

lsp "go.lsp.dev/protocol"
yamlv3 "gopkg.in/yaml.v3"
Expand All @@ -12,23 +13,34 @@ func GetPositionOfNode(node *yamlv3.Node, query []string) (lsp.Position, error)
return lsp.Position{}, fmt.Errorf("could not find Position of %s in values.yaml. Node was zero", query)
}

for index, value := range node.Content {
if value.Value == "" {
result, err := GetPositionOfNode(value, query)
if err == nil {
return result, nil
}
if len(query) == 0 {
return lsp.Position{Line: uint32(node.Line) - 1, Character: uint32(node.Column) - 1}, nil
}

query[0] = strings.TrimSuffix(query[0], "[0]")

switch node.Kind {
case yamlv3.DocumentNode:
if len(node.Content) < 1 {
return lsp.Position{}, fmt.Errorf("could not find Position of %s in values.yaml. Document is empty", query)
}
return GetPositionOfNode(node.Content[0], query)
case yamlv3.SequenceNode:
if len(node.Content) > 0 {
return GetPositionOfNode(node.Content[0], query)
}
if value.Value == query[0] {
if len(query) > 1 {
if len(node.Content) < index+1 {
return lsp.Position{}, fmt.Errorf("could not find Position of %s in values.yaml", query)
}
return GetPositionOfNode(node.Content[index+1], query[1:])
}

for index, nestedNode := range node.Content {
if nestedNode.Value == query[0] {
if len(query) == 1 {
return GetPositionOfNode(nestedNode, query[1:])
}
return lsp.Position{Line: uint32(value.Line) - 1, Character: uint32(value.Column) - 1}, nil
if len(node.Content) < index+1 {
return lsp.Position{}, fmt.Errorf("could not find Position of %s in values.yaml", query)
}
return GetPositionOfNode(node.Content[index+1], query[1:])
}
}
return lsp.Position{}, fmt.Errorf("could not find Position of %s in values.yaml. Found no match", query)

}
Loading

0 comments on commit c004c5b

Please sign in to comment.