Skip to content

Commit

Permalink
feat(definition): definition for variables finished
Browse files Browse the repository at this point in the history
  • Loading branch information
qvalentin committed Jul 28, 2023
1 parent dedc4fd commit 7735bf0
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 100 deletions.
127 changes: 50 additions & 77 deletions internal/handler/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"path/filepath"
"log"
"strings"

lsplocal "github.com/mrjosh/helm-ls/internal/lsp"
Expand All @@ -17,8 +17,6 @@ import (
)

func (h *langHandler) handleDefinition(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) (err error) {

logger.Println(fmt.Sprintf("Definition provider"))
if req.Params() == nil {
return &jsonrpc2.Error{Code: jsonrpc2.InvalidParams}
}
Expand All @@ -32,62 +30,17 @@ func (h *langHandler) handleDefinition(ctx context.Context, reply jsonrpc2.Repli
if !ok {
return errors.New("Could not get document: " + params.TextDocument.URI.Filename())
}
result, err := h.definitionAstParsing(doc, params.Position)

var (
word = doc.ValueAt(params.Position)
splitted = strings.Split(word, ".")
variableSplitted = []string{}
position lsp.Position
definitionFilePath string
)

if word == "" {
return reply(ctx, nil, err)
}

for _, s := range splitted {
if s != "" {
variableSplitted = append(variableSplitted, s)
}
}

// $ always points to the root context so we can safely remove it
// as long the LSP does not know about ranges
if variableSplitted[0] == "$" && len(variableSplitted) > 1 {
variableSplitted = variableSplitted[1:]
}

logger.Println(fmt.Sprintf("Definition checking for word < %s >", word))

switch variableSplitted[0] {
case "Values":
definitionFilePath = filepath.Join(h.rootURI.Filename(), "values.yaml")
if len(variableSplitted) > 1 {
position, err = h.getValueDefinition(variableSplitted[1:])
}
case "Chart":
definitionFilePath = filepath.Join(h.rootURI.Filename(), "Chart.yaml")
if len(variableSplitted) > 1 {
position, err = h.getChartDefinition(variableSplitted[1:])
}
}

if err == nil && definitionFilePath != "" {
result := lsp.Location{
URI: "file://" + lsp.DocumentURI(definitionFilePath),
Range: lsp.Range{Start: position},
}
log.Println("result", result)

return reply(ctx, result, err)
if err != nil {
return reply(ctx, err, err)
}
logger.Printf("Had no match for definition. Error: %v", err)
return reply(ctx, nil, err)
return reply(ctx, result, err)
}

// definitionAstParsing takes the current node
// depending on the node type it either returns the node that defines the current variable
// or the yaml selector for the current value
func (h *langHandler) definitionAstParsing(doc *lsplocal.Document, position lsp.Position) lsp.Location {
func (h *langHandler) definitionAstParsing(doc *lsplocal.Document, position lsp.Position) (lsp.Location, error) {
var (
currentNode = lsplocal.NodeAtPosition(doc.Ast, position)
pointToLoopUp = sitter.Point{
Expand All @@ -100,52 +53,74 @@ func (h *langHandler) definitionAstParsing(doc *lsplocal.Document, position lsp.
switch relevantChildNode.Type() {
case gotemplate.NodeTypeIdentifier:
if relevantChildNode.Parent().Type() == gotemplate.NodeTypeVariable {

variableName := relevantChildNode.Content([]byte(doc.Content))
var node = lsplocal.GetVariableDefinition(variableName, relevantChildNode.Parent(), doc.Content)
return lsp.Location{URI: doc.URI, Range: lsp.Range{Start: node.StartPoint(), End: node.EndPoint()}}
return h.getDefinitionForVariable(relevantChildNode, doc)
}
case gotemplate.NodeTypeDot, gotemplate.NodeTypeDotSymbol:
return h.getDefinitionForFixedIdentifier(relevantChildNode, doc)
case gotemplate.NodeTypeDot, gotemplate.NodeTypeDotSymbol, gotemplate.NodeTypeFieldIdentifier:
return h.getDefinitionForValue(relevantChildNode, doc)
}

return lsp.Location{}
return lsp.Location{}, fmt.Errorf("Definition not implemented for node type %s", relevantChildNode.Type())
}

func (h *langHandler) getDefinitionForVariable(node *sitter.Node, doc *lsplocal.Document) (lsp.Location, error) {
variableName := node.Content([]byte(doc.Content))
var 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{URI: doc.URI, Range: lsp.Range{Start: util.PointToPosition(defintionNode.StartPoint())}}, nil
}

func (h *langHandler) getDefinitionForValue(node *sitter.Node, doc *lsplocal.Document) lsp.Location {
// getDefinitionForFixedIdentifier checks if the current identifier has a constant definition and returns it
func (h *langHandler) getDefinitionForFixedIdentifier(node *sitter.Node, doc *lsplocal.Document) (lsp.Location, error) {
var name = node.Content([]byte(doc.Content))
switch name {
case "Values":
return lsp.Location{
URI: h.projectFiles.GetValuesFileURI()}, nil
case "Chart":
return lsp.Location{
URI: h.projectFiles.GetChartFileURI()}, nil
}

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

func (h *langHandler) getDefinitionForValue(node *sitter.Node, doc *lsplocal.Document) (lsp.Location, error) {
var (
yamlPathString = getYamlPath(node, doc)
yamlPath, err = util.NewYamlPath(yamlPathString)
definitionFilePath string
position lsp.Position
yamlPathString = getYamlPath(node, doc)
yamlPath, err = util.NewYamlPath(yamlPathString)
definitionFileURI lsp.DocumentURI
position lsp.Position
)
if err != nil {
return lsp.Location{}
return lsp.Location{}, err
}

if yamlPath.IsValuesPath() {
definitionFilePath = filepath.Join(h.rootURI.Filename(), "values.yaml")
definitionFileURI = h.projectFiles.GetValuesFileURI()
position, err = h.getValueDefinition(yamlPath.GetTail())
}
if yamlPath.IsChartPath() {
definitionFilePath = filepath.Join(h.rootURI.Filename(), "Chart.yaml")
definitionFileURI = h.projectFiles.GetChartFileURI()
position, err = h.getChartDefinition(yamlPath.GetTail())
}

if err == nil && definitionFilePath != "" {
if err == nil && definitionFileURI != "" {
return lsp.Location{
URI: "file://" + lsp.DocumentURI(definitionFilePath),
URI: definitionFileURI,
Range: lsp.Range{Start: position},
}
}, nil
}
return lsp.Location{}
return lsp.Location{}, fmt.Errorf("Could not find definition for %s", yamlPath)
}

func getYamlPath(node *sitter.Node, doc *lsplocal.Document) string {
switch node.Type() {
case gotemplate.NodeTypeDot:
return lsplocal.TraverseIdentifierPathUp(node, doc)
case gotemplate.NodeTypeDotSymbol:
case gotemplate.NodeTypeDotSymbol, gotemplate.NodeTypeFieldIdentifier:
return lsplocal.GetFieldIdentifierPath(node, doc)
default:
return ""
Expand All @@ -155,11 +130,10 @@ func getYamlPath(node *sitter.Node, doc *lsplocal.Document) string {
func (h *langHandler) getValueDefinition(splittedVar []string) (lsp.Position, error) {
return util.GetPositionOfNode(h.valueNode, splittedVar)
}
func (h *langHandler) getChartDefinition(splittedVar []string) (lsp.Position, error) {

func (h *langHandler) getChartDefinition(splittedVar []string) (lsp.Position, error) {
modifyedVar := make([]string, 0)

// for Releases, we make the first letter lowercase TODO: only do this when really needed
// for Charts, we make the first letter lowercase
for _, value := range splittedVar {
restOfString := ""
if (len(value)) > 1 {
Expand All @@ -168,6 +142,5 @@ func (h *langHandler) getChartDefinition(splittedVar []string) (lsp.Position, er
firstLetterLowercase := strings.ToLower(string(value[0])) + restOfString
modifyedVar = append(modifyedVar, firstLetterLowercase)
}

return util.GetPositionOfNode(h.chartNode, modifyedVar)
}
17 changes: 9 additions & 8 deletions internal/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,23 @@ type langHandler struct {
connPool jsonrpc2.Conn
linterName string
documents *lsplocal.DocumentStore
projectFiles ProjectFiles
values chartutil.Values
chartMetadata chart.Metadata
valueNode yamlv3.Node
chartNode yamlv3.Node
rootURI uri.URI
}

func NewHandler(connPool jsonrpc2.Conn) jsonrpc2.Handler {
fileStorage, _ := fs.NewFileStorage("")
handler := &langHandler{
linterName: "helm-lint",
connPool: connPool,
documents: lsplocal.NewDocumentStore(fileStorage),
values: make(map[string]interface{}),
valueNode: yamlv3.Node{},
chartNode: yamlv3.Node{},
rootURI: "",
linterName: "helm-lint",
connPool: connPool,
documents: lsplocal.NewDocumentStore(fileStorage),
projectFiles: ProjectFiles{},
values: make(map[string]interface{}),
valueNode: yamlv3.Node{},
chartNode: yamlv3.Node{},
}
logger.Printf("helm-lint-langserver: connections opened")
return jsonrpc2.ReplyHandler(handler.handle)
Expand Down Expand Up @@ -93,6 +93,7 @@ func (h *langHandler) handleInitialize(ctx context.Context, reply jsonrpc2.Repli
}

vf := filepath.Join(workspace_uri.Path, "values.yaml")
h.projectFiles = NewProjectFiles(uri.URI(params.WorkspaceFolders[0].URI), "")

vals, err := chartutil.ReadValuesFile(vf)
if err != nil {
Expand Down
39 changes: 39 additions & 0 deletions internal/handler/project_files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package handler

import (
"errors"
"os"
"path/filepath"

"github.com/mrjosh/helm-ls/pkg/chartutil"
lsp "go.lsp.dev/protocol"
"go.lsp.dev/uri"
)

type ProjectFiles struct {
ValuesFile string
ChartFile string
}

func (p ProjectFiles) GetValuesFileURI() lsp.DocumentURI {
return "file://" + lsp.DocumentURI(p.ValuesFile)
}
func (p ProjectFiles) GetChartFileURI() lsp.DocumentURI {
return "file://" + lsp.DocumentURI(p.ChartFile)
}

func NewProjectFiles(rootURI uri.URI, valuesFileName string) ProjectFiles {

if valuesFileName == "" {
valuesFileName = chartutil.ValuesfileName
}
valuesFileName = filepath.Join(rootURI.Filename(), valuesFileName)
if _, err := os.Stat(valuesFileName); errors.Is(err, os.ErrNotExist) {
valuesFileName = filepath.Join(rootURI.Filename(), "values.yml")
}

return ProjectFiles{
ValuesFile: valuesFileName,
ChartFile: filepath.Join(rootURI.Filename(), chartutil.ChartfileName),
}
}
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 @@ -7,4 +7,5 @@ const (
NodeTypeDotSymbol = "."
NodeTypeVariableDefinition = "variable_definition"
NodeTypeRangeVariableDefinition = "range_variable_definition"
NodeTypeFieldIdentifier = "field_identifier"
)
14 changes: 14 additions & 0 deletions internal/util/points.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package util

import (
sitter "github.com/smacker/go-tree-sitter"
lsp "go.lsp.dev/protocol"
)

func PointToPosition(point sitter.Point) lsp.Position {
return lsp.Position{Line: point.Row, Character: point.Column}
}

func PositionToPoint(position lsp.Position) sitter.Point {
return sitter.Point{Row: position.Line, Column: position.Character}
}
12 changes: 5 additions & 7 deletions internal/util/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
func GetPositionOfNode(node yamlv3.Node, query []string) (lsp.Position, error) {

if node.IsZero() {
return lsp.Position{}, fmt.Errorf("Could not find Position of %s in values.yaml. Node was zero.", query)
return lsp.Position{}, fmt.Errorf("could not find Position of %s in values.yaml. Node was zero", query)
}
println(node.Value)

Expand All @@ -24,15 +24,13 @@ func GetPositionOfNode(node yamlv3.Node, query []string) (lsp.Position, error) {
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)
} else {
return GetPositionOfNode(*node.Content[index+1], query[1:])
return lsp.Position{}, fmt.Errorf("could not find Position of %s in values.yaml", query)
}
} else {
return lsp.Position{Line: uint32(value.Line) - 1, Character: uint32(value.Column) - 1}, nil
return GetPositionOfNode(*node.Content[index+1], query[1:])
}
return lsp.Position{Line: uint32(value.Line) - 1, Character: uint32(value.Column) - 1}, nil
}
}
return lsp.Position{}, fmt.Errorf("Could not find Position of %s in values.yaml. Found no match.", query)
return lsp.Position{}, fmt.Errorf("could not find Position of %s in values.yaml. Found no match", query)

}
7 changes: 3 additions & 4 deletions internal/util/yaml_path.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,15 @@ func NewYamlPath(yamlPathString string) (YamlPath, error) {
variableSplitted = append(variableSplitted, s)
}
}
if len(variableSplitted) == 0 {
return YamlPath{}, fmt.Errorf("Could not parse yaml path: %s", yamlPathString)
}
// $ always points to the root context so we can safely remove it
// as long the LSP does not know about ranges
if variableSplitted[0] == "$" && len(variableSplitted) > 1 {
variableSplitted = variableSplitted[1:]
}

if len(variableSplitted) == 0 {
return YamlPath{}, fmt.Errorf("Could not parse yaml path: %s", yamlPathString)
}

return YamlPath{
TableNames: variableSplitted,
}, nil
Expand Down
6 changes: 3 additions & 3 deletions internal/util/yaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func TestGetPositionOfNode(t *testing.T) {
}

result, err := GetPositionOfNode(node, []string{"replicaCount"})
expected := lsp.Position{Line: 6, Character: 1}
expected := lsp.Position{Line: 5, Character: 0}
if err != nil {
t.Errorf("Result had error: %s", err)
}
Expand All @@ -35,7 +35,7 @@ func TestGetPositionOfNode(t *testing.T) {
}

result, err = GetPositionOfNode(node, []string{"image", "repository"})
expected = lsp.Position{Line: 9, Character: 3}
expected = lsp.Position{Line: 8, Character: 2}
if err != nil {
t.Errorf("Result had error: %s", err)
}
Expand All @@ -44,7 +44,7 @@ func TestGetPositionOfNode(t *testing.T) {
}

result, err = GetPositionOfNode(node, []string{"service", "test", "nested", "value"})
expected = lsp.Position{Line: 31, Character: 7}
expected = lsp.Position{Line: 30, Character: 6}
if err != nil {
t.Errorf("Result had error: %s", err)
}
Expand Down
1 change: 0 additions & 1 deletion pkg/chartutil/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package chartutil
import (
"fmt"
"io"
"io/ioutil"
"os"
"strings"

Expand Down

0 comments on commit 7735bf0

Please sign in to comment.