diff --git a/internal/charts/chart.go b/internal/charts/chart.go index 66177e3e..4e4f16b4 100644 --- a/internal/charts/chart.go +++ b/internal/charts/chart.go @@ -1,8 +1,11 @@ package charts import ( + "strings" + "github.com/mrjosh/helm-ls/internal/log" "github.com/mrjosh/helm-ls/internal/util" + lsp "go.lsp.dev/protocol" "go.lsp.dev/uri" ) @@ -55,3 +58,19 @@ func (c *Chart) ResolveValueFiles(query []string, chartStore *ChartStore) []*Que return append(ownResult, parentChart.ResolveValueFiles(extendedQuery, chartStore)...) } + +func (c *Chart) GetValueLocation(templateContext []string) (lsp.Location, error) { + modifyedVar := make([]string, 0) + // for Charts, we make the first letter lowercase + for _, value := range templateContext { + restOfString := "" + if (len(value)) > 1 { + restOfString = value[1:] + } + firstLetterLowercase := strings.ToLower(string(value[0])) + restOfString + modifyedVar = append(modifyedVar, firstLetterLowercase) + } + position, err := util.GetPositionOfNode(&c.ChartMetadata.YamlNode, modifyedVar) + + return lsp.Location{URI: c.ChartMetadata.URI, Range: lsp.Range{Start: position}}, err +} diff --git a/internal/handler/definition.go b/internal/handler/definition.go index 2e2cac0f..7b593a7f 100644 --- a/internal/handler/definition.go +++ b/internal/handler/definition.go @@ -3,7 +3,6 @@ package handler import ( "context" "fmt" - "strings" "github.com/mrjosh/helm-ls/internal/charts" languagefeatures "github.com/mrjosh/helm-ls/internal/language_features" @@ -12,7 +11,6 @@ import ( "github.com/mrjosh/helm-ls/internal/util" sitter "github.com/smacker/go-tree-sitter" lsp "go.lsp.dev/protocol" - "gopkg.in/yaml.v3" ) func (h *langHandler) Definition(ctx context.Context, params *lsp.DefinitionParams) (result []lsp.Location, err error) { @@ -45,6 +43,19 @@ func (h *langHandler) definitionAstParsing(genericDocumentUseCase *languagefeatu parentType = parentNode.Type() } + usecases := []languagefeatures.DefinitionUseCase{ + languagefeatures.NewBuiltInObjectsFeature(genericDocumentUseCase), // has to be before template context + languagefeatures.NewTemplateContextFeature(genericDocumentUseCase), + languagefeatures.NewIncludesCallFeature(genericDocumentUseCase), + } + + for _, usecase := range usecases { + if usecase.AppropriateForNode(genericDocumentUseCase.NodeType, genericDocumentUseCase.ParentNodeType, genericDocumentUseCase.Node) { + result, err := usecase.Definition() + return result, err + } + } + nodeType := relevantChildNode.Type() switch nodeType { case gotemplate.NodeTypeIdentifier: @@ -52,18 +63,6 @@ func (h *langHandler) definitionAstParsing(genericDocumentUseCase *languagefeatu 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) - } - - if parentType == gotemplate.NodeTypeArgumentList { - includesCallFeature := languagefeatures.NewIncludesCallFeature(genericDocumentUseCase) - return includesCallFeature.Definition() } return []lsp.Location{}, fmt.Errorf("Definition not implemented for node type %s", relevantChildNode.Type()) @@ -77,94 +76,3 @@ func (h *langHandler) getDefinitionForVariable(node *sitter.Node, doc *lsplocal. } return []lsp.Location{{URI: doc.URI, Range: lsp.Range{Start: util.PointToPosition(definitionNode.StartPoint())}}}, nil } - -// getDefinitionForFixedIdentifier checks if the current identifier has a constant definition and returns it -func (h *langHandler) getDefinitionForFixedIdentifier(chart *charts.Chart, node *sitter.Node, doc *lsplocal.Document) ([]lsp.Location, error) { - name := node.Content([]byte(doc.Content)) - switch name { - case "Values": - result := []lsp.Location{} - - for _, valueFile := range chart.ValuesFiles.AllValuesFiles() { - result = append(result, lsp.Location{URI: valueFile.URI}) - } - return result, nil - - case "Chart": - return []lsp.Location{ - {URI: chart.ChartMetadata.URI}, - }, - nil - } - - 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) { - var ( - yamlPathString = getYamlPath(node, doc) - yamlPath, err = util.NewYamlPath(yamlPathString) - definitionFileURI lsp.DocumentURI - positions []lsp.Position - ) - if err != nil { - return []lsp.Location{}, err - } - - if yamlPath.IsValuesPath() { - return h.getValueDefinition(chart, yamlPath.GetTail()), nil - } - if yamlPath.IsChartPath() { - definitionFileURI = chart.ChartMetadata.URI - position, err := h.getChartDefinition(&chart.ChartMetadata.YamlNode, yamlPath.GetTail()) - if err == nil { - positions = append(positions, position) - } - } - - if definitionFileURI != "" { - locations := []lsp.Location{} - for _, position := range positions { - locations = append(locations, lsp.Location{ - URI: definitionFileURI, - Range: lsp.Range{Start: position}, - }) - } - return locations, nil - } - 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, gotemplate.NodeTypeIdentifier: - return lsplocal.GetFieldIdentifierPath(node, doc) - default: - logger.Error("Could not get yaml path for node type ", node.Type()) - return "" - } -} - -func (h *langHandler) getValueDefinition(chart *charts.Chart, splittedVar []string) []lsp.Location { - locations := []lsp.Location{} - for _, value := range chart.ResolveValueFiles(splittedVar, h.chartStore) { - locations = append(locations, value.ValuesFiles.GetPositionsForValue(value.Selector)...) - } - return locations -} - -func (h *langHandler) getChartDefinition(chartNode *yaml.Node, splittedVar []string) (lsp.Position, error) { - modifyedVar := make([]string, 0) - // for Charts, we make the first letter lowercase - for _, value := range splittedVar { - restOfString := "" - if (len(value)) > 1 { - restOfString = value[1:] - } - firstLetterLowercase := strings.ToLower(string(value[0])) + restOfString - modifyedVar = append(modifyedVar, firstLetterLowercase) - } - return util.GetPositionOfNode(chartNode, modifyedVar) -} diff --git a/internal/handler/definition_test.go b/internal/handler/definition_test.go index 6170fe80..f9cfe690 100644 --- a/internal/handler/definition_test.go +++ b/internal/handler/definition_test.go @@ -208,11 +208,11 @@ func TestDefinitionValueFile(t *testing.T) { URI: testValuesURI, Range: lsp.Range{ Start: lsp.Position{ - Line: 1, + Line: 0, Character: 0, }, End: lsp.Position{ - Line: 1, + Line: 0, Character: 0, }, }, @@ -289,11 +289,11 @@ func TestDefinitionValueFileMulitpleValues(t *testing.T) { URI: testValuesURI, Range: lsp.Range{ Start: lsp.Position{ - Line: 1, + Line: 0, Character: 0, }, End: lsp.Position{ - Line: 1, + Line: 0, Character: 0, }, }, @@ -301,11 +301,11 @@ func TestDefinitionValueFileMulitpleValues(t *testing.T) { URI: testOtherValuesURI, Range: lsp.Range{ Start: lsp.Position{ - Line: 1, + Line: 0, Character: 0, }, End: lsp.Position{ - Line: 1, + Line: 0, Character: 0, }, }, diff --git a/internal/handler/hover.go b/internal/handler/hover.go index 6c6ea382..d3955918 100644 --- a/internal/handler/hover.go +++ b/internal/handler/hover.go @@ -35,7 +35,8 @@ func (h *langHandler) Hover(ctx context.Context, params *lsp.HoverParams) (resul } usecases := []languagefeatures.HoverUseCase{ - languagefeatures.NewValuesFeature(genericDocumentUseCase), + languagefeatures.NewBuiltInObjectsFeature(genericDocumentUseCase), // has to be before template context + languagefeatures.NewTemplateContextFeature(genericDocumentUseCase), } for _, usecase := range usecases { diff --git a/internal/handler/references.go b/internal/handler/references.go index 80477444..12b76de6 100644 --- a/internal/handler/references.go +++ b/internal/handler/references.go @@ -16,7 +16,7 @@ func (h *langHandler) References(ctx context.Context, params *lsp.ReferenceParam usecases := []languagefeatures.ReferencesUseCase{ languagefeatures.NewIncludesDefinitionFeature(genericDocumentUseCase), languagefeatures.NewIncludesCallFeature(genericDocumentUseCase), - languagefeatures.NewValuesFeature(genericDocumentUseCase), + languagefeatures.NewTemplateContextFeature(genericDocumentUseCase), } for _, usecase := range usecases { diff --git a/internal/language_features/built_in_objects.go b/internal/language_features/built_in_objects.go new file mode 100644 index 00000000..e5aa586f --- /dev/null +++ b/internal/language_features/built_in_objects.go @@ -0,0 +1,78 @@ +package languagefeatures + +import ( + helmdocs "github.com/mrjosh/helm-ls/internal/documentation/helm" + lsplocal "github.com/mrjosh/helm-ls/internal/lsp" + "github.com/mrjosh/helm-ls/internal/tree-sitter/gotemplate" + sitter "github.com/smacker/go-tree-sitter" + lsp "go.lsp.dev/protocol" +) + +type BuiltInObjectsFeature struct { + *GenericTemplateContextFeature +} + +func NewBuiltInObjectsFeature(genericDocumentUseCase *GenericDocumentUseCase) *BuiltInObjectsFeature { + return &BuiltInObjectsFeature{ + GenericTemplateContextFeature: &GenericTemplateContextFeature{genericDocumentUseCase}, + } +} + +func (f *BuiltInObjectsFeature) AppropriateForNode(currentNodeType string, parentNodeType string, node *sitter.Node) bool { + if !(parentNodeType == gotemplate.NodeTypeField && currentNodeType == gotemplate.NodeTypeIdentifier) && currentNodeType != gotemplate.NodeTypeDot && currentNodeType != gotemplate.NodeTypeDotSymbol { + return false + } + + allowedBuiltIns := []string{"Chart", "Values"} + + templateContext, _ := f.getTemplateContext() + if len(templateContext) != 1 { + return false + } + for _, allowedBuiltIn := range allowedBuiltIns { + if templateContext[0] == allowedBuiltIn { + return true + } + } + return false +} + +func (f *BuiltInObjectsFeature) References() (result []lsp.Location, err error) { + templateContext, err := f.getTemplateContext() + if err != nil { + return []lsp.Location{}, err + } + + locations := f.getReferencesFromSymbolTable(templateContext) + return append(locations, f.getDefinitionLocations(templateContext)...), err +} + +func (f *BuiltInObjectsFeature) getDefinitionLocations(templateContext lsplocal.TemplateContext) []lsp.Location { + locations := []lsp.Location{} + + switch templateContext[0] { + case "Values": + for _, valueFile := range f.Chart.ValuesFiles.AllValuesFiles() { + locations = append(locations, lsp.Location{URI: valueFile.URI}) + } + return locations + case "Chart": + return []lsp.Location{{URI: f.Chart.ChartMetadata.URI}} + } + + return []lsp.Location{} +} + +func (f *BuiltInObjectsFeature) Hover() (string, error) { + templateContext, _ := f.getTemplateContext() + docs, error := f.builtInOjectDocsLookup(templateContext[0], helmdocs.BuiltInObjects) + return docs.Doc, error +} + +func (f *BuiltInObjectsFeature) Definition() (result []lsp.Location, err error) { + templateContext, err := f.getTemplateContext() + if err != nil { + return []lsp.Location{}, err + } + return f.getDefinitionLocations(templateContext), nil +} diff --git a/internal/language_features/generic_template_context.go b/internal/language_features/generic_template_context.go new file mode 100644 index 00000000..fcb4c225 --- /dev/null +++ b/internal/language_features/generic_template_context.go @@ -0,0 +1,43 @@ +package languagefeatures + +import ( + "fmt" + + helmdocs "github.com/mrjosh/helm-ls/internal/documentation/helm" + lsplocal "github.com/mrjosh/helm-ls/internal/lsp" + "github.com/mrjosh/helm-ls/internal/util" + lsp "go.lsp.dev/protocol" +) + +type GenericTemplateContextFeature struct { + *GenericDocumentUseCase +} + +func (f *GenericTemplateContextFeature) getTemplateContext() (lsplocal.TemplateContext, error) { + templateContext := f.GenericDocumentUseCase.Document.SymbolTable.GetTemplateContext(lsplocal.GetRangeForNode(f.Node)) + if len(templateContext) == 0 || templateContext == nil { + return lsplocal.TemplateContext{}, fmt.Errorf("no template context found") + } + return templateContext, nil +} + +func (f *GenericTemplateContextFeature) getReferencesFromSymbolTable(templateContext lsplocal.TemplateContext) []lsp.Location { + locations := []lsp.Location{} + for _, doc := range f.GenericDocumentUseCase.DocumentStore.GetAllDocs() { + referenceRanges := doc.SymbolTable.GetTemplateContextRanges(templateContext) + for _, referenceRange := range referenceRanges { + locations = append(locations, util.RangeToLocation(doc.URI, referenceRange)) + } + } + + return locations +} + +func (f *GenericTemplateContextFeature) builtInOjectDocsLookup(key string, docs []helmdocs.HelmDocumentation) (helmdocs.HelmDocumentation, error) { + for _, item := range docs { + if key == item.Name { + return item, nil + } + } + return helmdocs.HelmDocumentation{}, fmt.Errorf("key %s not found on built-in object", key) +} diff --git a/internal/language_features/template_context.go b/internal/language_features/template_context.go index 5b0847e0..df452ee3 100644 --- a/internal/language_features/template_context.go +++ b/internal/language_features/template_context.go @@ -17,12 +17,12 @@ import ( ) type TemplateContextFeature struct { - *GenericDocumentUseCase + *GenericTemplateContextFeature } -func NewValuesFeature(genericDocumentUseCase *GenericDocumentUseCase) *TemplateContextFeature { +func NewTemplateContextFeature(genericDocumentUseCase *GenericDocumentUseCase) *TemplateContextFeature { return &TemplateContextFeature{ - GenericDocumentUseCase: genericDocumentUseCase, + GenericTemplateContextFeature: &GenericTemplateContextFeature{genericDocumentUseCase}, } } @@ -34,21 +34,21 @@ func (f *TemplateContextFeature) AppropriateForNode(currentNodeType string, pare } func (f *TemplateContextFeature) References() (result []lsp.Location, err error) { - includeName, err := f.getTemplateContext() + templateContext, err := f.getTemplateContext() if err != nil { return []lsp.Location{}, err } - locations := f.getReferenceLocations(includeName) - return locations, nil + locations := f.getReferenceLocations(templateContext) + return append(locations, f.getDefinitionLocations(templateContext)...), nil } -func (f *TemplateContextFeature) getTemplateContext() (lsplocal.TemplateContext, error) { - templateContext := f.GenericDocumentUseCase.Document.SymbolTable.GetTemplateContext(lsplocal.GetRangeForNode(f.Node)) - if len(templateContext) == 0 || templateContext == nil { - return lsplocal.TemplateContext{}, fmt.Errorf("no template context found") +func (f *TemplateContextFeature) Definition() (result []lsp.Location, err error) { + templateContext, err := f.getTemplateContext() + if err != nil { + return []lsp.Location{}, err } - return templateContext, nil + return f.getDefinitionLocations(templateContext), nil } func (f *TemplateContextFeature) getReferenceLocations(templateContext lsplocal.TemplateContext) []lsp.Location { @@ -65,11 +65,16 @@ func (f *TemplateContextFeature) getReferenceLocations(templateContext lsplocal. func (f *TemplateContextFeature) getDefinitionLocations(templateContext lsplocal.TemplateContext) []lsp.Location { locations := []lsp.Location{} + switch templateContext[0] { case "Values": for _, value := range f.Chart.ResolveValueFiles(templateContext.Tail(), f.ChartStore) { locations = append(locations, value.ValuesFiles.GetPositionsForValue(value.Selector)...) } + return locations + case "Chart": + location, _ := f.Chart.GetValueLocation(templateContext.Tail()) + return []lsp.Location{location} } return locations } @@ -77,20 +82,16 @@ func (f *TemplateContextFeature) getDefinitionLocations(templateContext lsplocal func (f *TemplateContextFeature) Hover() (string, error) { templateContext, err := f.getTemplateContext() - if len(templateContext) == 1 { - return f.builtInObjectHover(templateContext[0]) - } - switch templateContext[0] { case "Values": return f.valuesHover(templateContext.Tail()) case "Chart": - doc, error := f.builtInOjectValues(templateContext.Tail().Format(), helmdocs.BuiltInOjectVals[templateContext[0]]) - value := f.getMetadataField(&f.Chart.ChartMetadata.Metadata, doc.Name) - return fmt.Sprintf("%s\n\n%s\n", doc.Doc, value), error + docs, error := f.builtInOjectDocsLookup(templateContext.Tail().Format(), helmdocs.BuiltInOjectVals[templateContext[0]]) + value := f.getMetadataField(&f.Chart.ChartMetadata.Metadata, docs.Name) + return fmt.Sprintf("%s\n\n%s\n", docs.Doc, value), error case "Release", "Files", "Capabilities", "Template": - doc, error := f.builtInOjectValues(templateContext.Tail().Format(), helmdocs.BuiltInOjectVals[templateContext[0]]) - return doc.Doc, error + docs, error := f.builtInOjectDocsLookup(templateContext.Tail().Format(), helmdocs.BuiltInOjectVals[templateContext[0]]) + return docs.Doc, error } return templateContext.Format(), err @@ -112,24 +113,6 @@ func (f *TemplateContextFeature) valuesHover(templateContext lsplocal.TemplateCo return hoverResults.Format(f.ChartStore.RootURI), nil } -func (f *TemplateContextFeature) builtInOjectValues(key string, docs []helmdocs.HelmDocumentation) (helmdocs.HelmDocumentation, error) { - for _, item := range docs { - if key == item.Name { - return item, nil - } - } - return helmdocs.HelmDocumentation{}, fmt.Errorf("%s not found on built-in object", key) -} - -func (f *TemplateContextFeature) builtInObjectHover(key string) (string, error) { - for _, item := range helmdocs.BuiltInObjects { - if key == item.Name { - return item.Doc, nil - } - } - return "", fmt.Errorf("%s was no known built-in object", key) -} - func (f *TemplateContextFeature) getMetadataField(v *chart.Metadata, fieldName string) string { r := reflect.ValueOf(v) field := reflect.Indirect(r).FieldByName(fieldName) diff --git a/internal/language_features/template_context_hover_test.go b/internal/language_features/template_context_hover_test.go index 59a1f2d4..47af0098 100644 --- a/internal/language_features/template_context_hover_test.go +++ b/internal/language_features/template_context_hover_test.go @@ -248,7 +248,7 @@ value }, // Node: tt.args.chart.ValuesFiles.MainValuesFile.Node, } - valuesFeature := NewValuesFeature(genericDocumentUseCase) + valuesFeature := NewTemplateContextFeature(genericDocumentUseCase) got, err := valuesFeature.valuesHover(tt.args.splittedVar) if (err != nil) != tt.wantErr { t.Errorf("langHandler.getValueHover() error = %v, wantErr %v", err, tt.wantErr) diff --git a/internal/language_features/usecases.go b/internal/language_features/usecases.go index 390ca87a..c7d8d496 100644 --- a/internal/language_features/usecases.go +++ b/internal/language_features/usecases.go @@ -19,3 +19,8 @@ type HoverUseCase interface { UseCase Hover() (result string, err error) } + +type DefinitionUseCase interface { + UseCase + Definition() (result []lsp.Location, err error) +} diff --git a/internal/util/yaml.go b/internal/util/yaml.go index 66f758ef..6bef26f2 100644 --- a/internal/util/yaml.go +++ b/internal/util/yaml.go @@ -17,7 +17,7 @@ func GetPositionOfNode(node *yamlv3.Node, query []string) (lsp.Position, error) return lsp.Position{Line: uint32(node.Line) - 1, Character: uint32(node.Column) - 1}, nil } - query[0] = strings.TrimSuffix(query[0], "[0]") + query[0] = strings.TrimSuffix(query[0], "[]") switch node.Kind { case yamlv3.DocumentNode: diff --git a/pkg/chartutil/values.go b/pkg/chartutil/values.go index 192339b5..1f6dd3b8 100644 --- a/pkg/chartutil/values.go +++ b/pkg/chartutil/values.go @@ -92,7 +92,7 @@ func (v Values) Encode(w io.Writer) error { } func tableLookup(v Values, simple string) (Values, error) { - if strings.HasSuffix(simple, "[0]") { + if strings.HasSuffix(simple, "[]") { return arryLookup(v, simple) }