diff --git a/internal/handler/definition.go b/internal/handler/definition.go index 1f0d4943..9c8ecada 100644 --- a/internal/handler/definition.go +++ b/internal/handler/definition.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "fmt" - "path/filepath" + "log" "strings" lsplocal "github.com/mrjosh/helm-ls/internal/lsp" @@ -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} } @@ -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{ @@ -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 "" @@ -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 { @@ -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) } diff --git a/internal/handler/handler.go b/internal/handler/handler.go index de1deec1..9ec11760 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -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) @@ -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 { diff --git a/internal/handler/project_files.go b/internal/handler/project_files.go new file mode 100644 index 00000000..7b2420f6 --- /dev/null +++ b/internal/handler/project_files.go @@ -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), + } +} diff --git a/internal/tree-sitter/gotemplate/node-types.go b/internal/tree-sitter/gotemplate/node-types.go index 693b9513..4241a608 100644 --- a/internal/tree-sitter/gotemplate/node-types.go +++ b/internal/tree-sitter/gotemplate/node-types.go @@ -7,4 +7,5 @@ const ( NodeTypeDotSymbol = "." NodeTypeVariableDefinition = "variable_definition" NodeTypeRangeVariableDefinition = "range_variable_definition" + NodeTypeFieldIdentifier = "field_identifier" ) diff --git a/internal/util/points.go b/internal/util/points.go new file mode 100644 index 00000000..1e789c1d --- /dev/null +++ b/internal/util/points.go @@ -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} +} diff --git a/internal/util/yaml.go b/internal/util/yaml.go index 5d7d201f..bc6fc7f8 100644 --- a/internal/util/yaml.go +++ b/internal/util/yaml.go @@ -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) @@ -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) } diff --git a/internal/util/yaml_path.go b/internal/util/yaml_path.go index 9e6fe6b6..d07c4e84 100644 --- a/internal/util/yaml_path.go +++ b/internal/util/yaml_path.go @@ -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 diff --git a/internal/util/yaml_test.go b/internal/util/yaml_test.go index af48843a..01b3a867 100644 --- a/internal/util/yaml_test.go +++ b/internal/util/yaml_test.go @@ -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) } @@ -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) } @@ -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) } diff --git a/pkg/chartutil/values.go b/pkg/chartutil/values.go index 87037c68..1adfde25 100644 --- a/pkg/chartutil/values.go +++ b/pkg/chartutil/values.go @@ -19,7 +19,6 @@ package chartutil import ( "fmt" "io" - "io/ioutil" "os" "strings"