From 43504120aea3f81d163ee947b381372ad66ead0d Mon Sep 17 00:00:00 2001 From: qvalentin Date: Tue, 20 Aug 2024 11:24:41 +0200 Subject: [PATCH] move SyncToDisk function to interface --- internal/adapter/yamlls/diagnostics.go | 2 + internal/charts/dependency_files.go | 47 +++++++ internal/charts/dependency_template_file.go | 20 --- internal/charts/values_file.go | 15 +++ internal/charts/values_file_test.go | 8 ++ internal/handler/definition_chart_test.go | 22 +++- internal/language_features/includes.go | 5 +- .../language_features/template_context.go | 9 +- internal/lsp/document.go | 25 +--- internal/lsp/document_store.go | 2 +- .../charts/subchartexample/values.yaml | 1 - testdata/dependenciesExample/values.yaml | 123 +++++------------- 12 files changed, 139 insertions(+), 140 deletions(-) create mode 100644 internal/charts/dependency_files.go delete mode 100644 internal/charts/dependency_template_file.go diff --git a/internal/adapter/yamlls/diagnostics.go b/internal/adapter/yamlls/diagnostics.go index f7fa48cb..7a600a44 100644 --- a/internal/adapter/yamlls/diagnostics.go +++ b/internal/adapter/yamlls/diagnostics.go @@ -2,6 +2,7 @@ package yamlls import ( "context" + "fmt" "runtime" "strings" @@ -15,6 +16,7 @@ func (c Connector) PublishDiagnostics(ctx context.Context, params *protocol.Publ doc, ok := c.documents.Get(params.URI) if !ok { logger.Println("Error handling diagnostic. Could not get document: " + params.URI.Filename()) + return fmt.Errorf("Could not get document: %s", params.URI.Filename()) } doc.DiagnosticsCache.SetYamlDiagnostics(filterDiagnostics(params.Diagnostics, doc.Ast.Copy(), doc.Content)) diff --git a/internal/charts/dependency_files.go b/internal/charts/dependency_files.go new file mode 100644 index 00000000..8d205195 --- /dev/null +++ b/internal/charts/dependency_files.go @@ -0,0 +1,47 @@ +package charts + +import ( + "os" + "path/filepath" + "strings" + + "helm.sh/helm/v3/pkg/chart" +) + +type DependencyTemplateFile struct { + Content []byte + Path string +} + +var DependencyCacheFolder = ".helm_ls_cache" + +func (c *Chart) NewDependencyTemplateFile(chartName string, file *chart.File) *DependencyTemplateFile { + path := filepath.Join(c.RootURI.Filename(), "charts", DependencyCacheFolder, chartName, file.Name) + + return &DependencyTemplateFile{Content: file.Data, Path: path} +} + +type PossibleDependencyFile interface { + GetContent() string + GetPath() string +} + +// SyncToDisk writes the content of the document to disk if it is a dependency file. +// If it is a dependency file, it was read from a archive, so we need to write it back, +// to be able to open it in a editor when using go-to-definition or go-to-reference. +func SyncToDisk(d PossibleDependencyFile) { + if !IsDependencyFile(d) { + return + } + err := os.MkdirAll(filepath.Dir(d.GetPath()), 0o755) + if err == nil { + err = os.WriteFile(d.GetPath(), []byte(d.GetContent()), 0o444) + } + if err != nil { + logger.Error("Could not write dependency file", d.GetPath(), err) + } +} + +func IsDependencyFile(d PossibleDependencyFile) bool { + return strings.Contains(d.GetPath(), DependencyCacheFolder) +} diff --git a/internal/charts/dependency_template_file.go b/internal/charts/dependency_template_file.go deleted file mode 100644 index 70abb09d..00000000 --- a/internal/charts/dependency_template_file.go +++ /dev/null @@ -1,20 +0,0 @@ -package charts - -import ( - "path/filepath" - - "helm.sh/helm/v3/pkg/chart" -) - -type DependencyTemplateFile struct { - Content []byte - Path string -} - -var DependencyCacheFolder = ".helm_ls_cache" - -func (c *Chart) NewDependencyTemplateFile(chartName string, file *chart.File) *DependencyTemplateFile { - path := filepath.Join(c.RootURI.Filename(), "charts", DependencyCacheFolder, chartName, file.Name) - - return &DependencyTemplateFile{Content: file.Data, Path: path} -} diff --git a/internal/charts/values_file.go b/internal/charts/values_file.go index 686d3a4c..1f35e291 100644 --- a/internal/charts/values_file.go +++ b/internal/charts/values_file.go @@ -59,3 +59,18 @@ func readInValuesFile(filePath string) (chartutil.Values, yaml.Node) { } return vals, valueNodes } + +// GetContent implements PossibleDependencyFile. +func (d *ValuesFile) GetContent() string { + yaml, err := yaml.Marshal(d.Values) + if err != nil { + logger.Error(fmt.Sprintf("Could not load values for file %s", d.URI.Filename()), err) + return "" + } + return string(yaml) +} + +// GetPath implements PossibleDependencyFile. +func (d *ValuesFile) GetPath() string { + return d.URI.Filename() +} diff --git a/internal/charts/values_file_test.go b/internal/charts/values_file_test.go index 64830f96..24c9b76a 100644 --- a/internal/charts/values_file_test.go +++ b/internal/charts/values_file_test.go @@ -45,3 +45,11 @@ func TestReload(t *testing.T) { assert.Equal(t, "baz", valuesFile.Values["foo"]) assert.NotEqual(t, yaml.Node{}, valuesFile.ValueNode) } + +func TestGetContent(t *testing.T) { + tempDir := t.TempDir() + valuesContent := `foo: bar\n` + _ = os.WriteFile(filepath.Join(tempDir, "values.yaml"), []byte(valuesContent), 0o644) + valuesFile := charts.NewValuesFile(filepath.Join(tempDir, "values.yaml")) + assert.Equal(t, valuesContent, valuesFile.GetContent()) +} diff --git a/internal/handler/definition_chart_test.go b/internal/handler/definition_chart_test.go index d0f3bf01..d060ddc2 100644 --- a/internal/handler/definition_chart_test.go +++ b/internal/handler/definition_chart_test.go @@ -2,6 +2,7 @@ package handler import ( "context" + "fmt" "os" "path/filepath" "strings" @@ -22,6 +23,7 @@ var ( ) type testCase struct { + // Must be content of a line in the file fileURI templateLineWithMarker string expectedFile string expectedStartPosition lsp.Position @@ -42,6 +44,12 @@ func TestDefinitionChart(t *testing.T) { lsp.Position{Line: 35, Character: 0}, nil, }, + { + `{{ .Values.gl^obal.subchart }}`, + "values.yaml", + lsp.Position{Line: 7, Character: 0}, + nil, + }, } fileContent, err := os.ReadFile(fileURI.Filename()) @@ -51,11 +59,14 @@ func TestDefinitionChart(t *testing.T) { lines := strings.Split(string(fileContent), "\n") for _, tC := range testCases { t.Run("Definition on "+tC.templateLineWithMarker, func(t *testing.T) { - pos := getPosition(tC, lines) + pos, found := getPosition(tC, lines) + if !found { + t.Fatal(fmt.Sprintf("%s is not in the file %s", tC.templateLineWithMarker, fileURI.Filename())) + } documents := lsplocal.NewDocumentStore() - chart := charts.NewChart(rootUri, util.ValuesFilesConfig{}) + chart := charts.NewChart(rootUri, util.DefaultConfig.ValuesFilesConfig) chartStore := charts.NewChartStore(rootUri, charts.NewChart) chartStore.Charts = map[uri.URI]*charts.Chart{rootUri: chart} @@ -63,6 +74,7 @@ func TestDefinitionChart(t *testing.T) { chartStore: chartStore, documents: documents, yamllsConnector: &yamlls.Connector{}, + helmlsConfig: util.DefaultConfig, } h.LoadDocsOnNewChart(chart) @@ -90,18 +102,20 @@ func TestDefinitionChart(t *testing.T) { } } -func getPosition(tC testCase, lines []string) lsp.Position { +func getPosition(tC testCase, lines []string) (lsp.Position, bool) { col := strings.Index(tC.templateLineWithMarker, "^") buf := strings.Replace(tC.templateLineWithMarker, "^", "", 1) line := uint32(0) + found := false for i, v := range lines { if strings.Contains(v, buf) { + found = true line = uint32(i) col = col + strings.Index(v, buf) break } } pos := lsp.Position{Line: line, Character: uint32(col)} - return pos + return pos, found } diff --git a/internal/language_features/includes.go b/internal/language_features/includes.go index 5fc4d0e5..d0a9746e 100644 --- a/internal/language_features/includes.go +++ b/internal/language_features/includes.go @@ -3,6 +3,7 @@ package languagefeatures import ( lsp "go.lsp.dev/protocol" + "github.com/mrjosh/helm-ls/internal/charts" lsplocal "github.com/mrjosh/helm-ls/internal/lsp" "github.com/mrjosh/helm-ls/internal/protocol" "github.com/mrjosh/helm-ls/internal/tree-sitter/gotemplate" @@ -91,7 +92,7 @@ func (f *IncludesFeature) getReferenceLocations(includeName string) []lsp.Locati locations = append(locations, util.RangeToLocation(doc.URI, referenceRange)) } if len(locations) > 0 { - doc.SyncToDisk() + charts.SyncToDisk(doc) } } @@ -106,7 +107,7 @@ func (f *IncludesFeature) getDefinitionLocations(includeName string) []lsp.Locat locations = append(locations, util.RangeToLocation(doc.URI, referenceRange)) } if len(locations) > 0 { - doc.SyncToDisk() + charts.SyncToDisk(doc) } } diff --git a/internal/language_features/template_context.go b/internal/language_features/template_context.go index 96a18fbb..614fda78 100644 --- a/internal/language_features/template_context.go +++ b/internal/language_features/template_context.go @@ -7,6 +7,7 @@ import ( lsp "go.lsp.dev/protocol" + "github.com/mrjosh/helm-ls/internal/charts" helmdocs "github.com/mrjosh/helm-ls/internal/documentation/helm" lsplocal "github.com/mrjosh/helm-ls/internal/lsp" "github.com/mrjosh/helm-ls/internal/protocol" @@ -70,7 +71,13 @@ func (f *TemplateContextFeature) getDefinitionLocations(templateContext lsplocal switch templateContext[0] { case "Values": for _, value := range f.Chart.ResolveValueFiles(templateContext.Tail(), f.ChartStore) { - locations = append(locations, value.ValuesFiles.GetPositionsForValue(value.Selector)...) + locs := value.ValuesFiles.GetPositionsForValue(value.Selector) + if len(locs) > 0 { + for _, valuesFile := range value.ValuesFiles.AllValuesFiles() { + charts.SyncToDisk(valuesFile) + } + } + locations = append(locations, locs...) } return locations case "Chart": diff --git a/internal/lsp/document.go b/internal/lsp/document.go index d57f0145..cf58a273 100644 --- a/internal/lsp/document.go +++ b/internal/lsp/document.go @@ -3,11 +3,8 @@ package lsp import ( "bytes" "fmt" - "os" - "path/filepath" "strings" - "github.com/mrjosh/helm-ls/internal/charts" "github.com/mrjosh/helm-ls/internal/util" sitter "github.com/smacker/go-tree-sitter" lsp "go.lsp.dev/protocol" @@ -82,22 +79,12 @@ func (d *Document) getLines() []string { return d.lines } -// SyncToDisk writes the content of the document to disk if it is a dependency file. -// If it is a dependency file, it was read from a archive, so we need to write it back, -// to be able to open it in a editor when using go-to-definition or go-to-reference. -func (d *Document) SyncToDisk() { - if !d.IsDependencyFile() { - return - } - err := os.MkdirAll(filepath.Dir(d.Path), 0o755) - if err == nil { - err = os.WriteFile(d.Path, []byte(d.Content), 0o444) - } - if err != nil { - logger.Error(err.Error()) - } +// GetContent implements PossibleDependencyFile. +func (d *Document) GetContent() string { + return d.Content } -func (d *Document) IsDependencyFile() bool { - return strings.Contains(d.Path, charts.DependencyCacheFolder) +// GetPath implements PossibleDependencyFile. +func (d *Document) GetPath() string { + return d.Path } diff --git a/internal/lsp/document_store.go b/internal/lsp/document_store.go index 860e7b07..c3420a52 100644 --- a/internal/lsp/document_store.go +++ b/internal/lsp/document_store.go @@ -53,7 +53,7 @@ func (s *DocumentStore) DidOpen(params *lsp.DidOpenTextDocumentParams, helmlsCon func (s *DocumentStore) Store(filename string, content []byte, helmlsConfig util.HelmlsConfiguration) { ast := ParseAst(nil, string(content)) fileUri := uri.File(filename) - s.documents.Store(fileUri, + s.documents.Store(fileUri.Filename(), &Document{ URI: fileUri, Path: filename, diff --git a/testdata/dependenciesExample/charts/subchartexample/values.yaml b/testdata/dependenciesExample/charts/subchartexample/values.yaml index 2039cd69..6e49e9d3 100644 --- a/testdata/dependenciesExample/charts/subchartexample/values.yaml +++ b/testdata/dependenciesExample/charts/subchartexample/values.yaml @@ -1,4 +1,3 @@ global: subchart: works - subchartWithoutGlobal: works diff --git a/testdata/dependenciesExample/values.yaml b/testdata/dependenciesExample/values.yaml index 2519dbb6..4100784c 100644 --- a/testdata/dependenciesExample/values.yaml +++ b/testdata/dependenciesExample/values.yaml @@ -1,115 +1,54 @@ -# Default values for dependeciesExample. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -replicaCount: 1 - +affinity: {} +autoscaling: + enabled: false + maxReplicas: 100 + minReplicas: 1 + targetCPUUtilizationPercentage: 80 +fullnameOverride: "" +global: + subchart: works image: - repository: nginx pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. + repository: nginx tag: "" - imagePullSecrets: [] -nameOverride: "" -fullnameOverride: "" - -serviceAccount: - # Specifies whether a service account should be created - create: true - # Automatically mount a ServiceAccount's API credentials? - automount: true - # Annotations to add to the service account - annotations: {} - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template - name: "" - -podAnnotations: {} -podLabels: {} - -podSecurityContext: {} - # fsGroup: 2000 - -securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 - -service: - type: ClusterIP - port: 80 - ingress: - enabled: false - className: "" annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" + className: "" + enabled: false hosts: - host: chart-example.local paths: - path: / pathType: ImplementationSpecific tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - -resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - livenessProbe: httpGet: path: / port: http +nameOverride: "" +nodeSelector: {} +podAnnotations: {} +podLabels: {} +podSecurityContext: {} readinessProbe: httpGet: path: / port: http - -autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 100 - targetCPUUtilizationPercentage: 80 - # targetMemoryUtilizationPercentage: 80 - -# Additional volumes on the output Deployment definition. -volumes: [] -# - name: foo -# secret: -# secretName: mysecret -# optional: false - -# Additional volumeMounts on the output Deployment definition. -volumeMounts: [] -# - name: foo -# mountPath: "/etc/foo" -# readOnly: true - -nodeSelector: {} - -tolerations: [] - -affinity: {} - -global: - subchart: works - +replicaCount: 1 +resources: {} +securityContext: {} +service: + port: 80 + type: ClusterIP +serviceAccount: + annotations: {} + automount: true + create: true + name: "" subchartWithoutGlobal: works - subchartexample: subchartWithoutGlobal: worksToo +tolerations: [] +volumeMounts: [] +volumes: []