Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(symbol-table): add variables #88

Merged
merged 4 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions internal/handler/completion_main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"errors"
"os"
"strings"
"testing"

"github.com/mrjosh/helm-ls/internal/adapter/yamlls"
Expand All @@ -13,7 +12,6 @@ import (
lsplocal "github.com/mrjosh/helm-ls/internal/lsp"
"github.com/mrjosh/helm-ls/internal/util"
"github.com/stretchr/testify/assert"
"go.lsp.dev/protocol"
lsp "go.lsp.dev/protocol"
"go.lsp.dev/uri"
)
Expand Down Expand Up @@ -176,17 +174,20 @@ func TestCompletionMainSingleLines(t *testing.T) {
{`Test completion on {{ range .Values.ingress.hosts }} {{ .^ }} {{ end }}`, []string{"host", "paths"}, []string{}, nil},
{`Test completion on {{ range .Values.ingress.hosts }} {{ .ho^ }} {{ end }}`, []string{"host", "paths"}, []string{}, nil},
{`Test completion on {{ range .Values.ingress.hosts }} {{ range .paths }} {{ .^ }} {{ end }} {{ end }}`, []string{"pathType", "path"}, []string{}, nil},
{`Test completion on {{ root := . }} {{ $root.test.^ }}`, []string{}, []string{}, errors.New("[$root test ] is no valid template context for helm")},
{`Test completion on {{ $root := . }} {{ $root.test.^ }}`, []string{}, []string{}, errors.New("[test ] is no valid template context for helm")},
{`Test completion on {{ range $type, $config := $.Values.deployments }} {{ .^ }} {{ end }}`, []string{"some"}, []string{}, nil},
{`Test completion on {{ range $type, $config := $.Values.deployments }} {{ .s^ }} {{ end }}`, []string{"some"}, []string{}, nil},
{`Test completion on {{ range $type, $config := $.Values.deployments }} {{ $config.^ }} {{ end }}`, []string{"some"}, []string{}, nil},
{`Test completion on {{ range .Values.deploymentsWithNestedStuff }} {{ .hpa.cpuUtilization.^ }} {{ end }}`, []string{"targetAverageUtilization", "enabled"}, []string{}, nil},
{`Test completion on {{ range $type, $config := .Values.deploymentsWithNestedStuff }} {{ .hpa.cpuUtilization.^ }} {{ end }}`, []string{"targetAverageUtilization", "enabled"}, []string{}, nil},
{`Test completion on {{ range $type, $config := .Values.deploymentsWithNestedStuff }} {{ $config.hpa.cpuUtilization.^ }} {{ end }}`, []string{"targetAverageUtilization", "enabled"}, []string{}, nil},
{`Test completion on {{ range $type, $config := $.Values.deployments }} {{ $config.s^ }} {{ end }}`, []string{"some"}, []string{}, nil},
}

for _, tt := range testCases {
t.Run(tt.templateWithMark, func(t *testing.T) {
// seen chars up to ^
col := strings.Index(tt.templateWithMark, "^")
buf := strings.Replace(tt.templateWithMark, "^", "", 1)
pos := protocol.Position{Line: 0, Character: uint32(col)}
pos, buf := getPositionForMarkedTestLine(tt.templateWithMark)

// to get the correct values file ../../testdata/example/values.yaml
fileURI := uri.File("../../testdata/example/templates/completion-test.yaml")

Expand Down
33 changes: 24 additions & 9 deletions internal/handler/definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var testFileContent = `
{{ range .Values.list }}
{{ . }} # line 12
{{ end }}
{{ range $index, $element := pipeline }}{{ $index }}{{ $element }}{{ end }} # line 14
`

var (
Expand Down Expand Up @@ -97,16 +98,14 @@ func genericDefinitionTest(t *testing.T, position lsp.Position, expectedLocation

// Input:
// {{ $variable }} # line 2
// -----| # this line incides the coursor position for the test
// -----| # this line indicates the cursor position for the test
func TestDefinitionVariable(t *testing.T) {
genericDefinitionTest(t, lsp.Position{Line: 2, Character: 8}, []lsp.Location{
{
URI: testDocumentTemplateURI,
Range: lsp.Range{
Start: lsp.Position{
Line: 1,
Character: 3,
},
Start: lsp.Position{Line: 1, Character: 3},
End: lsp.Position{Line: 1, Character: 22},
},
},
}, nil)
Expand All @@ -127,10 +126,26 @@ func TestDefinitionRange(t *testing.T) {
{
URI: testDocumentTemplateURI,
Range: lsp.Range{
Start: lsp.Position{
Line: 7,
Character: 17,
},
Start: lsp.Position{Line: 7, Character: 17},
End: lsp.Position{Line: 7, Character: 37},
},
},
}, nil)
}

// Input:
// {{ range $index, $element := pipeline }}{{ $index }}{{ $element }}{{ end }} # line 14
// ---------------------|
// Expected:
// {{ range $index, $element := pipeline }}{{ $index }}{{ $element }}{{ end }} # line 14
// -----------------|
func TestDefinitionRangeOnRedeclaration(t *testing.T) {
genericDefinitionTest(t, lsp.Position{Line: 14, Character: 23}, []lsp.Location{
{
URI: testDocumentTemplateURI,
Range: lsp.Range{
Start: lsp.Position{Line: 14, Character: 17},
End: lsp.Position{Line: 14, Character: 37},
},
},
}, nil)
Expand Down
15 changes: 15 additions & 0 deletions internal/handler/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package handler

import (
"strings"

"go.lsp.dev/protocol"
)

// Takes a string with a mark (^) in it and returns the position and the string without the mark
func getPositionForMarkedTestLine(buf string) (protocol.Position, string) {
col := strings.Index(buf, "^")
buf = strings.Replace(buf, "^", "", 1)
pos := protocol.Position{Line: 0, Character: uint32(col)}
return pos, buf
}
6 changes: 3 additions & 3 deletions internal/handler/hover_main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@ func TestHoverMain(t *testing.T) {
Line: 74,
Character: 50,
},
expected: "$root.Values.deployments",
expected: fmt.Sprintf("### %s\n%s\n\n\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "first:\n some: value\nsecond:\n some: value"),
expectedError: nil,
},
{
desc: "Test hover on template context with variables in range loop",
position: lsp.Position{
Line: 80,
Character: 35,
Character: 31,
},
expected: "$config.hpa.minReplicas",
expected: fmt.Sprintf("### %s\n%s\n\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "value"),
expectedError: nil,
},
{
Expand Down
1 change: 1 addition & 0 deletions internal/handler/references.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func (h *langHandler) References(_ context.Context, params *lsp.ReferenceParams)
languagefeatures.NewIncludesDefinitionFeature(genericDocumentUseCase),
languagefeatures.NewIncludesCallFeature(genericDocumentUseCase),
languagefeatures.NewTemplateContextFeature(genericDocumentUseCase),
languagefeatures.NewVariablesFeature(genericDocumentUseCase),
}

for _, usecase := range usecases {
Expand Down
163 changes: 56 additions & 107 deletions internal/handler/references_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,113 +15,6 @@ import (
"go.lsp.dev/uri"
)

func TestRefercesIncludes(t *testing.T) {
content := `{{define "name"}} T1 {{end}}
{{include "name" .}}
{{include "name" .}}
`

expected := []lsp.Location{
{
URI: uri.File("/tmp/testfile.yaml"),
Range: protocol.Range{
Start: protocol.Position{
Line: 0x1, Character: 0x3,
},
End: protocol.Position{
Line: 0x1,
Character: 0x13,
},
},
},
protocol.Location{
URI: uri.File("/tmp/testfile.yaml"),
Range: protocol.Range{
Start: protocol.Position{
Line: 0x2,
Character: 0x3,
},
End: protocol.Position{
Line: 0x2,
Character: 0x13,
},
},
},
protocol.Location{
URI: uri.File("/tmp/testfile.yaml"),
Range: protocol.Range{
Start: protocol.Position{
Line: 0x0,
Character: 0x0,
},
End: protocol.Position{
Line: 0x0,
Character: 0x1c,
},
},
},
}
testCases := []struct {
desc string
position lsp.Position
expected []lsp.Location
expectedError error
}{
{
desc: "Test references on define",
position: lsp.Position{
Line: 0,
Character: 11,
},
expected: expected,
},
{
desc: "Test references on include",
position: lsp.Position{
Line: 2,
Character: 14,
},
expected: expected,
},
}

for _, tt := range testCases {
t.Run(tt.desc, func(t *testing.T) {
documents := lsplocal.NewDocumentStore()

path := "/tmp/testfile.yaml"
fileURI := uri.File(path)

d := lsp.DidOpenTextDocumentParams{
TextDocument: lsp.TextDocumentItem{
URI: fileURI,
LanguageID: "",
Version: 0,
Text: string(content),
},
}
documents.DidOpen(&d, util.DefaultConfig)
h := &langHandler{
chartStore: charts.NewChartStore(uri.File("."), charts.NewChart),
documents: documents,
yamllsConnector: &yamlls.Connector{},
}
result, err := h.References(context.Background(), &lsp.ReferenceParams{
TextDocumentPositionParams: lsp.TextDocumentPositionParams{
TextDocument: lsp.TextDocumentIdentifier{
URI: fileURI,
},
Position: tt.position,
},
})
assert.Equal(t, tt.expectedError, err)
if err == nil {
assert.Equal(t, tt.expected, result)
}
})
}
}

func TestRefercesTemplateContext(t *testing.T) {
content := `
{{ .Values.test }}
Expand Down Expand Up @@ -291,3 +184,59 @@ func TestRefercesTemplateContextWithTestFile(t *testing.T) {
})
}
}

func TestRefercesSingleLines(t *testing.T) {
testCases := []struct {
templateWithMark string
expectedReferencesStartChars []int
err error
}{
{"Test References on {{ $test := .Values.value }} {{ $te^st }}", []int{22, 51}, nil},
{"Test References on {{ range $test := .Values.value }} {{ $te^st }}", []int{28, 57}, nil},
{"Test References on {{ range $test := .Values.value }} {{ $te^st }} {{ end }} {{ $test }}", []int{28, 57}, nil},
{"Test References on {{ range $test := .Values.value }} {{ $te^st }} {{ $test }} {{ end }}", []int{28, 57, 70}, nil},
{"Test References on {{ if .Values.bla }} {{ $test := 2 }} {{ $t^est }} {{ end }} {{ $test := 3 }} {{ $test }}", []int{43, 60}, nil},
{"Test References on {{ if .Values.bla }} {{ $test := 2 }} {{ $test }} {{ end }} {{ $te^st := 3 }} {{ $test }}", []int{82, 99}, nil},

{`{{define "na^me"}} T1 {{end}} {{include "name" .}} {{include "name" .}} `, []int{0, 31, 52}, nil},
{`{{define "name"}} T1 {{end}} {{include "na^me" .}} {{include "name" .}} `, []int{0, 31, 52}, nil},
{`{{define "name"}} T1 {{end}} {{include "name" .}} {{include "nam^e" .}} `, []int{0, 31, 52}, nil},
}

for _, tt := range testCases {
t.Run(tt.templateWithMark, func(t *testing.T) {
documents := lsplocal.NewDocumentStore()
pos, buf := getPositionForMarkedTestLine(tt.templateWithMark)
fileURI := uri.File("fake-testfile.yaml")

d := lsp.DidOpenTextDocumentParams{
TextDocument: lsp.TextDocumentItem{
URI: fileURI,
LanguageID: "",
Version: 0,
Text: buf,
},
}
documents.DidOpen(&d, util.DefaultConfig)
h := &langHandler{
chartStore: charts.NewChartStore(uri.File("."), charts.NewChart),
documents: documents,
yamllsConnector: &yamlls.Connector{},
}
result, err := h.References(context.Background(), &lsp.ReferenceParams{
TextDocumentPositionParams: lsp.TextDocumentPositionParams{
TextDocument: lsp.TextDocumentIdentifier{
URI: fileURI,
},
Position: pos,
},
})
assert.Equal(t, tt.err, err)
startPointChars := []int{}
for _, location := range result {
startPointChars = append(startPointChars, int(location.Range.Start.Character))
}
assert.ElementsMatch(t, tt.expectedReferencesStartChars, startPointChars)
})
}
}
16 changes: 14 additions & 2 deletions internal/language_features/built_in_objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,24 @@
if err != nil || 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, _ := f.getTemplateContext()
templateContext, err := f.getTemplateContext()
if err != nil {
return []lsp.Location{}, err
}

locations := f.getReferencesFromSymbolTable(templateContext)

return append(locations, f.getDefinitionLocations(templateContext)...), err
}

Expand All @@ -51,12 +57,13 @@
locations := []lsp.Location{}

switch templateContext[0] {
case "Values":

Check failure on line 60 in internal/language_features/built_in_objects.go

View workflow job for this annotation

GitHub Actions / lint (1.21.5, ubuntu-latest)

string `Values` has 4 occurrences, make it a constant (goconst)
for _, valueFile := range f.Chart.ValuesFiles.AllValuesFiles() {
locations = append(locations, lsp.Location{URI: valueFile.URI})
}

return locations
case "Chart":

Check failure on line 66 in internal/language_features/built_in_objects.go

View workflow job for this annotation

GitHub Actions / lint (1.21.5, ubuntu-latest)

string `Chart` has 3 occurrences, make it a constant (goconst)
return []lsp.Location{{URI: f.Chart.ChartMetadata.URI}}
}

Expand All @@ -64,9 +71,13 @@
}

func (f *BuiltInObjectsFeature) Hover() (string, error) {
templateContext, _ := f.getTemplateContext()
templateContext, err := f.getTemplateContext()
if err != nil {
return "", err
}

docs, err := f.builtInOjectDocsLookup(templateContext[0], helmdocs.BuiltInObjects)

return docs.Doc, err
}

Expand All @@ -75,5 +86,6 @@
if err != nil {
return []lsp.Location{}, err
}

return f.getDefinitionLocations(templateContext), nil
}
2 changes: 2 additions & 0 deletions internal/language_features/generic_template_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func (f *GenericTemplateContextFeature) getTemplateContext() (lsplocal.TemplateC

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 {
Expand All @@ -35,5 +36,6 @@ func (f *GenericTemplateContextFeature) builtInOjectDocsLookup(key string, docs
return item, nil
}
}

return helmdocs.HelmDocumentation{}, fmt.Errorf("key %s not found on built-in object", key)
}
Loading
Loading