diff --git a/internal/documentation/helm/helm-documentation.go b/internal/documentation/helm/helm-documentation.go index 16253da8..86d8c1b5 100644 --- a/internal/documentation/helm/helm-documentation.go +++ b/internal/documentation/helm/helm-documentation.go @@ -1,6 +1,10 @@ package helmdocs -import "github.com/mrjosh/helm-ls/internal/util" +import ( + "slices" + + "github.com/mrjosh/helm-ls/internal/util" +) type HelmDocumentation struct { Name string @@ -187,6 +191,8 @@ var ( {"required", "required $str $val", "fail template with message $str if $val is not provided or is empty"}, } + AllFuncs = slices.Concat(HelmFuncs, SprigFuncs, BuiltinFuncs) + CapabilitiesVals = []HelmDocumentation{ {"TillerVersion", ".Capabilities.TillerVersion", "Tiller version"}, {"APIVersions", "Capabilities.APIVersions", "A set of versions."}, diff --git a/internal/handler/completion.go b/internal/handler/completion.go index c386a213..fb6a0c9a 100644 --- a/internal/handler/completion.go +++ b/internal/handler/completion.go @@ -2,42 +2,35 @@ package handler import ( "context" - "errors" "fmt" - "reflect" - "strings" - "github.com/mrjosh/helm-ls/internal/charts" languagefeatures "github.com/mrjosh/helm-ls/internal/language_features" lsplocal "github.com/mrjosh/helm-ls/internal/lsp" gotemplate "github.com/mrjosh/helm-ls/internal/tree-sitter/gotemplate" - "github.com/mrjosh/helm-ls/internal/util" - "github.com/mrjosh/helm-ls/pkg/chartutil" sitter "github.com/smacker/go-tree-sitter" "go.lsp.dev/protocol" lsp "go.lsp.dev/protocol" - yaml "gopkg.in/yaml.v2" "github.com/mrjosh/helm-ls/internal/documentation/godocs" helmdocs "github.com/mrjosh/helm-ls/internal/documentation/helm" ) var ( - emptyItems = make([]lsp.CompletionItem, 0) - functionsCompletionItems = make([]lsp.CompletionItem, 0) - textCompletionsItems = make([]lsp.CompletionItem, 0) + emptyItems = make([]lsp.CompletionItem, 0) + textCompletionsItems = make([]lsp.CompletionItem, 0) ) func init() { - functionsCompletionItems = append(functionsCompletionItems, getFunctionCompletionItems(helmdocs.HelmFuncs)...) - functionsCompletionItems = append(functionsCompletionItems, getFunctionCompletionItems(helmdocs.BuiltinFuncs)...) - functionsCompletionItems = append(functionsCompletionItems, getFunctionCompletionItems(helmdocs.SprigFuncs)...) textCompletionsItems = append(textCompletionsItems, getTextCompletionItems(godocs.TextSnippets)...) } func (h *langHandler) Completion(ctx context.Context, params *lsp.CompletionParams) (result *lsp.CompletionList, err error) { logger.Debug("Running completion with params", params) genericDocumentUseCase, err := h.NewGenericDocumentUseCase(params.TextDocumentPositionParams) + if err != nil { + return nil, err + } + var ( currentNode = lsplocal.NodeAtPosition(genericDocumentUseCase.Document.Ast, params.Position) pointToLoopUp = sitter.Point{ @@ -48,10 +41,6 @@ func (h *langHandler) Completion(ctx context.Context, params *lsp.CompletionPara ) genericDocumentUseCase = genericDocumentUseCase.WithNode(relevantChildNode) - if err != nil { - return nil, err - } - usecases := []languagefeatures.CompletionUseCase{ languagefeatures.NewTemplateContextFeature(genericDocumentUseCase), languagefeatures.NewFunctionCallFeature(genericDocumentUseCase), @@ -62,12 +51,8 @@ func (h *langHandler) Completion(ctx context.Context, params *lsp.CompletionPara return usecase.Completion() } } - doc, ok := h.documents.Get(params.TextDocument.URI) - if !ok { - return nil, errors.New("Could not get document: " + params.TextDocument.URI.Filename()) - } - word, isTextNode := completionAstParsing(doc, params.Position) + word, isTextNode := completionAstParsing(genericDocumentUseCase.Document, params.Position) if isTextNode { result := make([]lsp.CompletionItem, 0) @@ -77,41 +62,16 @@ func (h *langHandler) Completion(ctx context.Context, params *lsp.CompletionPara return &protocol.CompletionList{IsIncomplete: false, Items: result}, err } - var ( - splitted = strings.Split(word, ".") - items []lsp.CompletionItem - variableSplitted = []string{} - ) - - for n, s := range splitted { - // we want to keep the last empty string to be able - // distinguish between 'global.' and 'global' - if s == "" && n != len(splitted)-1 { - continue - } - variableSplitted = append(variableSplitted, s) - } - logger.Println(fmt.Sprintf("Word found for completions is < %s >", word)) - items = make([]lsp.CompletionItem, 0) + items := []lsp.CompletionItem{} for _, v := range helmdocs.BuiltInObjects { items = append(items, lsp.CompletionItem{ Label: v.Name, - InsertText: v.Name, + InsertText: "." + v.Name, Detail: v.Detail, Documentation: v.Doc, }) } - if len(variableSplitted) == 0 { - return &lsp.CompletionList{IsIncomplete: false, Items: items}, err - } - - // $ 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:] - } - return &lsp.CompletionList{IsIncomplete: false, Items: items}, err } @@ -136,9 +96,6 @@ func completionAstParsing(doc *lsplocal.Document, position lsp.Position) (string word string ) - logger.Println("currentNode", currentNode) - logger.Println("relevantChildNode", relevantChildNode) - nodeType := relevantChildNode.Type() switch nodeType { case gotemplate.NodeTypeIdentifier: @@ -150,133 +107,6 @@ func completionAstParsing(doc *lsplocal.Document, position lsp.Position) (string return word, false } -func (h *langHandler) getValuesCompletions(chart *charts.Chart, splittedVar []string) (result []lsp.CompletionItem) { - m := make(map[string]lsp.CompletionItem) - for _, queriedValuesFiles := range chart.ResolveValueFiles(splittedVar, h.chartStore) { - for _, valuesFile := range queriedValuesFiles.ValuesFiles.AllValuesFiles() { - for _, item := range h.getValue(valuesFile.Values, queriedValuesFiles.Selector) { - m[item.InsertText] = item - } - } - } - - for _, item := range m { - result = append(result, item) - } - - return result -} - -func (h *langHandler) getValue(values chartutil.Values, splittedVar []string) []lsp.CompletionItem { - var ( - err error - tableName = strings.Join(splittedVar, ".") - localValues chartutil.Values - items = make([]lsp.CompletionItem, 0) - ) - - if len(splittedVar) > 0 { - - localValues, err = values.Table(tableName) - if err != nil { - logger.Println(err) - if len(splittedVar) > 1 { - // the current tableName was not found, maybe because it is incomplete, we can use the previous one - // e.g. gobal.im -> im was not found - // but global contains the key image, so we return all keys of global - localValues, err = values.Table(strings.Join(splittedVar[:len(splittedVar)-1], ".")) - if err != nil { - logger.Println(err) - return emptyItems - } - values = localValues - } - } else { - values = localValues - } - - } - - for variable, value := range values { - items = h.setItem(items, value, variable) - } - - return items -} - -func (h *langHandler) setItem(items []lsp.CompletionItem, value interface{}, variable string) []lsp.CompletionItem { - var ( - itemKind = lsp.CompletionItemKindVariable - valueOf = reflect.ValueOf(value) - documentation = valueOf.String() - ) - - logger.Debug("ValueKind: ", valueOf) - - switch valueOf.Kind() { - case reflect.Slice, reflect.Map: - itemKind = lsp.CompletionItemKindStruct - documentation = h.toYAML(value) - case reflect.Bool: - itemKind = lsp.CompletionItemKindVariable - documentation = util.GetBoolType(value) - case reflect.Float32, reflect.Float64: - documentation = fmt.Sprintf("%.2f", valueOf.Float()) - itemKind = lsp.CompletionItemKindVariable - case reflect.Invalid: - documentation = "" - default: - itemKind = lsp.CompletionItemKindField - } - - return append(items, lsp.CompletionItem{ - Label: variable, - InsertText: variable, - Documentation: documentation, - Detail: valueOf.Kind().String(), - Kind: itemKind, - }) -} - -func (h *langHandler) toYAML(value interface{}) string { - valBytes, _ := yaml.Marshal(value) - return string(valBytes) -} - -func getVariableCompletionItems(helmDocs []helmdocs.HelmDocumentation) (result []lsp.CompletionItem) { - for _, item := range helmDocs { - result = append(result, variableCompletionItem(item)) - } - return result -} - -func variableCompletionItem(helmDocumentation helmdocs.HelmDocumentation) lsp.CompletionItem { - return lsp.CompletionItem{ - Label: helmDocumentation.Name, - InsertText: helmDocumentation.Name, - Detail: helmDocumentation.Detail, - Documentation: helmDocumentation.Doc, - Kind: lsp.CompletionItemKindVariable, - } -} - -func getFunctionCompletionItems(helmDocs []helmdocs.HelmDocumentation) (result []lsp.CompletionItem) { - for _, item := range helmDocs { - result = append(result, functionCompletionItem(item)) - } - return result -} - -func functionCompletionItem(helmDocumentation helmdocs.HelmDocumentation) lsp.CompletionItem { - return lsp.CompletionItem{ - Label: helmDocumentation.Name, - InsertText: helmDocumentation.Name, - Detail: helmDocumentation.Detail, - Documentation: helmDocumentation.Doc, - Kind: lsp.CompletionItemKindFunction, - } -} - func getTextCompletionItems(gotemplateSnippet []godocs.GoTemplateSnippet) (result []lsp.CompletionItem) { for _, item := range gotemplateSnippet { result = append(result, textCompletionItem(item)) diff --git a/internal/handler/completion_main_test.go b/internal/handler/completion_main_test.go index bd7d1544..d3b3fd5e 100644 --- a/internal/handler/completion_main_test.go +++ b/internal/handler/completion_main_test.go @@ -23,6 +23,18 @@ func TestCompletionMain(t *testing.T) { notExpectedInsertTexts []string expectedError error }{ + { + desc: "Test completion on {{ if (and .Values. ) }}", + position: lsp.Position{ + Line: 8, + Character: 19, + }, + expectedInsertText: "replicaCount", + notExpectedInsertTexts: []string{ + helmdocs.HelmFuncs[0].Name, + }, + expectedError: nil, + }, { desc: "Test completion on .Chart.N", position: lsp.Position{ diff --git a/internal/handler/completion_values_test.go b/internal/handler/completion_values_test.go deleted file mode 100644 index 179780d5..00000000 --- a/internal/handler/completion_values_test.go +++ /dev/null @@ -1,152 +0,0 @@ -package handler - -import ( - "testing" - - "github.com/mrjosh/helm-ls/internal/charts" - "github.com/mrjosh/helm-ls/pkg/chart" - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" -) - -func TestEmptyValues(t *testing.T) { - handler := &langHandler{ - linterName: "helm-lint", - connPool: nil, - documents: nil, - } - - result := handler.getValue(make(map[string]interface{}), []string{"global"}) - - if len(result) != 0 { - t.Errorf("Length of result was not zero.") - } - result = handler.getValue(make(map[string]interface{}), []string{""}) - - if len(result) != 0 { - t.Errorf("Length of result was not zero.") - } -} - -func TestValues(t *testing.T) { - handler := &langHandler{ - linterName: "helm-lint", - connPool: nil, - documents: nil, - } - nested := map[string]interface{}{"nested": "value"} - values := map[string]interface{}{"global": nested} - - result := handler.getValue(values, []string{"g"}) - - if len(result) != 1 || result[0].InsertText != "global" { - t.Errorf("Completion for g was not global but was %s.", result[0].InsertText) - } - - result = handler.getValue(values, []string{""}) - - if len(result) != 1 || result[0].InsertText != "global" { - t.Errorf("Completion for \"\" was not global but was %s.", result[0].InsertText) - } - - result = handler.getValue(values, []string{"global", "nes"}) - if len(result) != 1 || result[0].InsertText != "nested" { - t.Errorf("Completion for global.nes was not nested but was %s.", result[0].InsertText) - } -} - -func TestWrongValues(t *testing.T) { - handler := &langHandler{ - linterName: "helm-lint", - connPool: nil, - documents: nil, - } - nested := map[string]interface{}{"nested": 1} - values := map[string]interface{}{"global": nested} - - result := handler.getValue(values, []string{"some", "wrong", "values"}) - if len(result) != 0 { - t.Errorf("Length of result was not zero.") - } - - result = handler.getValue(values, []string{"some", "wrong"}) - if len(result) != 0 { - t.Errorf("Length of result was not zero.") - } - - result = handler.getValue(values, []string{"some", ""}) - if len(result) != 0 { - t.Errorf("Length of result was not zero.") - } - - result = handler.getValue(values, []string{"global", "nested", ""}) - if len(result) != 0 { - t.Errorf("Length of result was not zero.") - } -} - -func TestGetValuesCompletions(t *testing.T) { - handler := &langHandler{ - linterName: "helm-lint", - connPool: nil, - documents: nil, - } - nested := map[string]interface{}{"nested": "value"} - valuesMain := map[string]interface{}{"global": nested} - valuesAdditional := map[string]interface{}{"glob": nested} - chart := &charts.Chart{ - ChartMetadata: &charts.ChartMetadata{Metadata: chart.Metadata{Name: "test"}}, - ValuesFiles: &charts.ValuesFiles{ - MainValuesFile: &charts.ValuesFile{ - Values: valuesMain, - ValueNode: yaml.Node{}, - URI: "", - }, - AdditionalValuesFiles: []*charts.ValuesFile{ - { - Values: valuesAdditional, - ValueNode: yaml.Node{}, - URI: "", - }, - }, - }, - RootURI: "", - } - - result := handler.getValuesCompletions(chart, []string{"g"}) - assert.Equal(t, 2, len(result)) - - result = handler.getValuesCompletions(chart, []string{"something", "different"}) - assert.Empty(t, result) -} - -func TestGetValuesCompletionsContainsNoDupliactes(t *testing.T) { - handler := &langHandler{ - linterName: "helm-lint", - connPool: nil, - documents: nil, - } - nested := map[string]interface{}{"nested": "value"} - valuesMain := map[string]interface{}{"global": nested} - valuesAdditional := map[string]interface{}{"global": nested} - testChart := &charts.Chart{ - ChartMetadata: &charts.ChartMetadata{Metadata: chart.Metadata{Name: "test"}}, - ValuesFiles: &charts.ValuesFiles{ - MainValuesFile: &charts.ValuesFile{ - Values: valuesMain, - ValueNode: yaml.Node{}, - URI: "", - }, - AdditionalValuesFiles: []*charts.ValuesFile{ - { - Values: valuesAdditional, - URI: "", - }, - }, - }, - RootURI: "", - } - - result := handler.getValuesCompletions(testChart, []string{"g"}) - assert.Equal(t, 1, len(result)) -} diff --git a/internal/language_features/function_call.go b/internal/language_features/function_call.go index 99362d5d..a47cf650 100644 --- a/internal/language_features/function_call.go +++ b/internal/language_features/function_call.go @@ -32,6 +32,5 @@ func (f *FunctionCallFeature) Hover() (string, error) { } func (f *FunctionCallFeature) Completion() (result *lsp.CompletionList, err error) { - // TODO: add all functions here - return protocol.NewCompletionResults(helmdocs.HelmFuncs).ToLSP(), nil + return protocol.NewCompletionResults(helmdocs.AllFuncs).ToLSP(), nil } diff --git a/internal/language_features/template_context.go b/internal/language_features/template_context.go index e07fc12f..26cfb80c 100644 --- a/internal/language_features/template_context.go +++ b/internal/language_features/template_context.go @@ -13,7 +13,6 @@ import ( "github.com/mrjosh/helm-ls/internal/tree-sitter/gotemplate" "github.com/mrjosh/helm-ls/internal/util" "github.com/mrjosh/helm-ls/pkg/chart" - "github.com/mrjosh/helm-ls/pkg/chartutil" ) type TemplateContextFeature struct { @@ -109,7 +108,7 @@ func (f *TemplateContextFeature) valuesHover(templateContext lsplocal.TemplateCo ) for _, valuesFiles := range valuesFiles { for _, valuesFile := range valuesFiles.ValuesFiles.AllValuesFiles() { - result, err := f.getTableOrValueForSelector(valuesFile.Values, strings.Join(valuesFiles.Selector, ".")) + result, err := util.GetTableOrValueForSelector(valuesFile.Values, strings.Join(valuesFiles.Selector, ".")) if err == nil { hoverResults = append(hoverResults, util.HoverResultWithFile{URI: valuesFile.URI, Value: result}) } @@ -124,18 +123,6 @@ func (f *TemplateContextFeature) getMetadataField(v *chart.Metadata, fieldName s return util.FormatToYAML(field, fieldName) } -func (f *TemplateContextFeature) getTableOrValueForSelector(values chartutil.Values, selector string) (string, error) { - if len(selector) > 0 { - localValues, err := values.Table(selector) - if err != nil { - value, err := values.PathValue(selector) - return util.FormatToYAML(reflect.Indirect(reflect.ValueOf(value)), selector), err - } - return localValues.YAML() - } - return values.YAML() -} - func (f *TemplateContextFeature) Completion() (result *lsp.CompletionList, err error) { templateContext, err := f.getTemplateContext() if err != nil { @@ -158,22 +145,9 @@ func (f *TemplateContextFeature) Completion() (result *lsp.CompletionList, err e switch templateContext[0] { case "Values": - m := make(map[string]lsp.CompletionItem) - for _, queriedValuesFiles := range f.Chart.ResolveValueFiles(templateContext.Tail(), f.ChartStore) { - for _, valuesFile := range queriedValuesFiles.ValuesFiles.AllValuesFiles() { - for _, item := range util.GetValueCompletion(valuesFile.Values, queriedValuesFiles.Selector) { - m[item.InsertText] = item - } - } - } - completions := []lsp.CompletionItem{} - for _, item := range m { - completions = append(completions, item) - } - - return &lsp.CompletionList{Items: completions, IsIncomplete: false}, nil + return f.valuesCompletion(templateContext) case "Chart", "Release", "Files", "Capabilities", "Template": - // TODO: make this more fine, by checking the lenght + // TODO: make this more fine, by checking the length result, ok := helmdocs.BuiltInOjectVals[templateContext[0]] if !ok { result := helmdocs.BuiltInObjects @@ -184,5 +158,21 @@ func (f *TemplateContextFeature) Completion() (result *lsp.CompletionList, err e } return nil, nil +} + +func (f *TemplateContextFeature) valuesCompletion(templateContext lsplocal.TemplateContext) (*lsp.CompletionList, error) { + m := make(map[string]lsp.CompletionItem) + for _, queriedValuesFiles := range f.Chart.ResolveValueFiles(templateContext.Tail(), f.ChartStore) { + for _, valuesFile := range queriedValuesFiles.ValuesFiles.AllValuesFiles() { + for _, item := range util.GetValueCompletion(valuesFile.Values, queriedValuesFiles.Selector) { + m[item.InsertText] = item + } + } + } + completions := []lsp.CompletionItem{} + for _, item := range m { + completions = append(completions, item) + } + return &lsp.CompletionList{Items: completions, IsIncomplete: false}, nil } diff --git a/internal/language_features/template_context_completion_test.go b/internal/language_features/template_context_completion_test.go new file mode 100644 index 00000000..86cb17f9 --- /dev/null +++ b/internal/language_features/template_context_completion_test.go @@ -0,0 +1,85 @@ +package languagefeatures + +import ( + "testing" + + "github.com/mrjosh/helm-ls/internal/charts" + "github.com/mrjosh/helm-ls/pkg/chart" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" +) + +func TestGetValuesCompletions(t *testing.T) { + nested := map[string]interface{}{"nested": "value"} + valuesMain := map[string]interface{}{"global": nested} + valuesAdditional := map[string]interface{}{"glob": nested} + chart := &charts.Chart{ + ChartMetadata: &charts.ChartMetadata{Metadata: chart.Metadata{Name: "test"}}, + ValuesFiles: &charts.ValuesFiles{ + MainValuesFile: &charts.ValuesFile{ + Values: valuesMain, + ValueNode: yaml.Node{}, + URI: "", + }, + AdditionalValuesFiles: []*charts.ValuesFile{ + { + Values: valuesAdditional, + ValueNode: yaml.Node{}, + URI: "", + }, + }, + }, + RootURI: "", + } + + templateConextFeature := TemplateContextFeature{ + GenericTemplateContextFeature: &GenericTemplateContextFeature{ + GenericDocumentUseCase: &GenericDocumentUseCase{ + Chart: chart, + }, + }, + } + + result, err := templateConextFeature.valuesCompletion([]string{"Values", "g"}) + assert.NoError(t, err) + assert.Len(t, result.Items, 2) + + result, err = templateConextFeature.valuesCompletion([]string{"Values", "something", "different"}) + assert.NoError(t, err) + assert.Len(t, result.Items, 0) +} + +func TestGetValuesCompletionsContainsNoDupliactes(t *testing.T) { + nested := map[string]interface{}{"nested": "value"} + valuesMain := map[string]interface{}{"global": nested} + valuesAdditional := map[string]interface{}{"global": nested} + chart := &charts.Chart{ + ChartMetadata: &charts.ChartMetadata{Metadata: chart.Metadata{Name: "test"}}, + ValuesFiles: &charts.ValuesFiles{ + MainValuesFile: &charts.ValuesFile{ + Values: valuesMain, + ValueNode: yaml.Node{}, + URI: "", + }, + AdditionalValuesFiles: []*charts.ValuesFile{ + { + Values: valuesAdditional, + URI: "", + }, + }, + }, + RootURI: "", + } + + templateConextFeature := TemplateContextFeature{ + GenericTemplateContextFeature: &GenericTemplateContextFeature{ + GenericDocumentUseCase: &GenericDocumentUseCase{ + Chart: chart, + }, + }, + } + + result, err := templateConextFeature.valuesCompletion([]string{"Values", "g"}) + assert.NoError(t, err) + assert.Len(t, result.Items, 1) +} diff --git a/internal/lsp/ast.go b/internal/lsp/ast.go index 3d0dc7f8..00a134b1 100644 --- a/internal/lsp/ast.go +++ b/internal/lsp/ast.go @@ -54,6 +54,12 @@ func FindRelevantChildNodeCompletion(currentNode *sitter.Node, pointToLookUp sit return FindRelevantChildNodeCompletion(child, pointToLookUp) } } + if currentNode.Type() == " " { + return FindRelevantChildNodeCompletion(currentNode.Parent(), sitter.Point{ + Row: pointToLookUp.Row, + Column: pointToLookUp.Column - 1, + }) + } return currentNode } diff --git a/internal/util/values_test.go b/internal/util/values_test.go new file mode 100644 index 00000000..90116abd --- /dev/null +++ b/internal/util/values_test.go @@ -0,0 +1,47 @@ +package util + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEmptyValues(t *testing.T) { + _, err := GetTableOrValueForSelector(make(map[string]interface{}), "global") + assert.Error(t, err) + + result, err := GetTableOrValueForSelector(make(map[string]interface{}), "") + assert.NoError(t, err) + assert.Equal(t, "{}\n", result) +} + +func TestValues(t *testing.T) { + nested := map[string]interface{}{"nested": "value"} + values := map[string]interface{}{"global": nested} + + result := GetValueCompletion(values, []string{"g"}) + assert.Len(t, result, 1) + assert.Equal(t, "global", result[0].InsertText) + + result = GetValueCompletion(values, []string{""}) + assert.Len(t, result, 1) + assert.Equal(t, "global", result[0].InsertText) + + result = GetValueCompletion(values, []string{"global", "nes"}) + assert.Len(t, result, 1) + assert.Equal(t, "nested", result[0].InsertText) +} + +func TestWrongValues(t *testing.T) { + nested := map[string]interface{}{"nested": 1} + values := map[string]interface{}{"global": nested} + + _, err := GetTableOrValueForSelector(values, "some.wrong.values") + assert.Error(t, err) + + _, err = GetTableOrValueForSelector(values, "some.wrong") + assert.Error(t, err) + + _, err = GetTableOrValueForSelector(values, "some") + assert.Error(t, err) +} diff --git a/testdata/example/templates/completion-test.yaml b/testdata/example/templates/completion-test.yaml index 335ef7ee..cea82b73 100644 --- a/testdata/example/templates/completion-test.yaml +++ b/testdata/example/templates/completion-test.yaml @@ -5,3 +5,8 @@ {{ .Chart.N }} {{ . }} + +{{ toYaml .Release. }} +{{ toYaml (.Release. ) }} +{{ .Release. }} +