diff --git a/internal/adapter/fs/fs.go b/internal/adapter/fs/fs.go index 7446ac0c..7bf35c98 100644 --- a/internal/adapter/fs/fs.go +++ b/internal/adapter/fs/fs.go @@ -66,7 +66,6 @@ func (fs *FileStorage) Canonical(path string) string { } func (fs *FileStorage) FileExists(path string) (bool, error) { - fi, err := fs.fileInfo(path) if err != nil { return false, err diff --git a/internal/adapter/yamlls/trimTemplate_test.go b/internal/adapter/yamlls/trimTemplate_test.go index 4343951c..fd35c77a 100644 --- a/internal/adapter/yamlls/trimTemplate_test.go +++ b/internal/adapter/yamlls/trimTemplate_test.go @@ -307,12 +307,12 @@ func TestTrimTemplate(t *testing.T) { func testTrimTemplateWithTestData(t *testing.T, testData TrimTemplateTestData) { doc := &lsplocal.Document{ Content: testData.documentText, - Ast: lsplocal.ParseAst(testData.documentText), + Ast: lsplocal.ParseAst(nil, testData.documentText), } - var trimmed = trimTemplateForYamllsFromAst(doc.Ast, testData.documentText) + trimmed := trimTemplateForYamllsFromAst(doc.Ast, testData.documentText) - var result = trimmed == testData.trimmedText + result := trimmed == testData.trimmedText if !result { t.Errorf("Trimmed templated was not as expected but was %s ", trimmed) diff --git a/internal/charts/chart.go b/internal/charts/chart.go index 2c38ff12..66177e3e 100644 --- a/internal/charts/chart.go +++ b/internal/charts/chart.go @@ -17,7 +17,10 @@ type Chart struct { func NewChart(rootURI uri.URI, valuesFilesConfig util.ValuesFilesConfig) *Chart { return &Chart{ - ValuesFiles: NewValuesFiles(rootURI, valuesFilesConfig.MainValuesFileName, valuesFilesConfig.LintOverlayValuesFileName, valuesFilesConfig.AdditionalValuesFilesGlobPattern), + ValuesFiles: NewValuesFiles(rootURI, + valuesFilesConfig.MainValuesFileName, + valuesFilesConfig.LintOverlayValuesFileName, + valuesFilesConfig.AdditionalValuesFilesGlobPattern), ChartMetadata: NewChartMetadata(rootURI), RootURI: rootURI, ParentChart: newParentChart(rootURI), @@ -32,20 +35,23 @@ type QueriedValuesFiles struct { // ResolveValueFiles returns a list of all values files in the chart // and all parent charts if the query tries to access global values func (c *Chart) ResolveValueFiles(query []string, chartStore *ChartStore) []*QueriedValuesFiles { - if len(query) > 0 && query[0] == "global" { - parentChart := c.ParentChart.GetParentChart(chartStore) - if parentChart != nil { - return append([]*QueriedValuesFiles{{Selector: query, ValuesFiles: c.ValuesFiles}}, parentChart.ResolveValueFiles(query, chartStore)...) - } + ownResult := []*QueriedValuesFiles{{Selector: query, ValuesFiles: c.ValuesFiles}} + if len(query) == 0 { + return ownResult } - chartName := c.ChartMetadata.Metadata.Name - if len(query) > 0 { - parentChart := c.ParentChart.GetParentChart(chartStore) - if parentChart != nil { - extendedQuery := append([]string{chartName}, query...) - return append([]*QueriedValuesFiles{{Selector: query, ValuesFiles: c.ValuesFiles}}, - parentChart.ResolveValueFiles(extendedQuery, chartStore)...) - } + + parentChart := c.ParentChart.GetParentChart(chartStore) + if parentChart == nil { + return ownResult + } + + if query[0] == "global" { + return append(ownResult, + parentChart.ResolveValueFiles(query, chartStore)...) } - return []*QueriedValuesFiles{{Selector: query, ValuesFiles: c.ValuesFiles}} + + chartName := c.ChartMetadata.Metadata.Name + extendedQuery := append([]string{chartName}, query...) + return append(ownResult, + parentChart.ResolveValueFiles(extendedQuery, chartStore)...) } diff --git a/internal/handler/handler.go b/internal/handler/handler.go index f7bc804a..ba7406e7 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" - "github.com/mrjosh/helm-ls/internal/adapter/fs" "github.com/mrjosh/helm-ls/internal/adapter/yamlls" "github.com/mrjosh/helm-ls/internal/charts" lsplocal "github.com/mrjosh/helm-ls/internal/lsp" @@ -28,8 +27,7 @@ type langHandler struct { } func NewHandler(connPool jsonrpc2.Conn) jsonrpc2.Handler { - fileStorage, _ := fs.NewFileStorage("") - documents := lsplocal.NewDocumentStore(fileStorage) + documents := lsplocal.NewDocumentStore() handler := &langHandler{ linterName: "helm-lint", connPool: connPool, diff --git a/internal/lsp/ast.go b/internal/lsp/ast.go index 047d4a2d..17b3b43c 100644 --- a/internal/lsp/ast.go +++ b/internal/lsp/ast.go @@ -9,10 +9,10 @@ import ( lsp "go.lsp.dev/protocol" ) -func ParseAst(content string) *sitter.Tree { +func ParseAst(oldTree *sitter.Tree, content string) *sitter.Tree { parser := sitter.NewParser() parser.SetLanguage(gotemplate.GetLanguage()) - tree, _ := parser.ParseCtx(context.Background(), nil, []byte(content)) + tree, _ := parser.ParseCtx(context.Background(), oldTree, []byte(content)) return tree } @@ -106,7 +106,7 @@ func TraverseIdentifierPathUp(node *sitter.Node, doc *Document) string { } func (d *Document) ApplyChangesToAst(newContent string) { - d.Ast = ParseAst(newContent) + d.Ast = ParseAst(d.Ast, newContent) } func GetLspRangeForNode(node *sitter.Node) lsp.Range { diff --git a/internal/lsp/ast_diagnostics_test.go b/internal/lsp/ast_diagnostics_test.go index 3b9e81be..c3354598 100644 --- a/internal/lsp/ast_diagnostics_test.go +++ b/internal/lsp/ast_diagnostics_test.go @@ -8,7 +8,7 @@ import ( func TestIsInElseBranch(t *testing.T) { template := `{{if pipeline}} t1 {{ else if pipeline }} t2 {{ else if pipeline2 }} t3 {{ else }} t4 {{ end }}` - var ast = ParseAst(template) + ast := ParseAst(nil, template) // (template [0, 0] - [1, 0] // (if_action [0, 0] - [0, 95] // condition: (function_call [0, 5] - [0, 13] @@ -52,5 +52,4 @@ func TestIsInElseBranch(t *testing.T) { if !IsInElseBranch(t4) { t.Errorf("t4 was incorrectly identified as not in else branch") } - } diff --git a/internal/lsp/ast_field_identifier_test.go b/internal/lsp/ast_field_identifier_test.go index b5064677..bb06b23d 100644 --- a/internal/lsp/ast_field_identifier_test.go +++ b/internal/lsp/ast_field_identifier_test.go @@ -11,7 +11,7 @@ import ( func TestGetFieldIdentifierPathSimple(t *testing.T) { template := `{{ .Values.test }}` - var ast = ParseAst(template) + ast := ParseAst(nil, template) // (template [0, 0] - [1, 0] // (selector_expression [0, 3] - [0, 15] // operand: (field [0, 3] - [0, 10] @@ -37,7 +37,7 @@ func TestGetFieldIdentifierPathSimple(t *testing.T) { func TestGetFieldIdentifierPathWith(t *testing.T) { template := `{{ with .Values }}{{ .test }} {{ end }}` - var ast = ParseAst(template) + ast := ParseAst(nil, template) // (template [0, 0] - [1, 0] // (with_action [0, 0] - [0, 39] // condition: (field [0, 8] - [0, 15] @@ -64,7 +64,7 @@ func TestGetFieldIdentifierPathWith(t *testing.T) { func TestGetFieldIdentifierPathFunction(t *testing.T) { template := `{{ and .Values.test1 .Values.test2 }}` - var ast = ParseAst(template) + ast := ParseAst(nil, template) // (template [0, 0] - [1, 0] // (function_call [0, 3] - [0, 35] // function: (identifier [0, 3] - [0, 6]) @@ -102,7 +102,7 @@ func TestGetFieldIdentifierPathFunctionForCompletion(t *testing.T) { template := `{{ and .Values.image .Values. }}` // | -> complete at dot - var ast = ParseAst(template) + ast := ParseAst(nil, template) var ( position = lsp.Position{Line: 0, Character: 29} diff --git a/internal/lsp/document.go b/internal/lsp/document.go index 31640cba..ba5f94a0 100644 --- a/internal/lsp/document.go +++ b/internal/lsp/document.go @@ -14,13 +14,11 @@ import ( // documentStore holds opened documents. type DocumentStore struct { documents map[string]*Document - fs FileStorage } -func NewDocumentStore(fs FileStorage) *DocumentStore { +func NewDocumentStore() *DocumentStore { return &DocumentStore{ documents: map[string]*Document{}, - fs: fs, } } @@ -32,26 +30,22 @@ func (s *DocumentStore) GetAllDocs() []*Document { return docs } -func (s *DocumentStore) DidOpen(params lsp.DidOpenTextDocumentParams,helmlsConfig util.HelmlsConfiguration) (*Document, error) { - logger.Debug(fmt.Sprintf("Opening document %s with langID %s", params.TextDocument.URI,params.TextDocument.LanguageID)) +func (s *DocumentStore) DidOpen(params lsp.DidOpenTextDocumentParams, helmlsConfig util.HelmlsConfiguration) (*Document, error) { + logger.Debug(fmt.Sprintf("Opening document %s with langID %s", params.TextDocument.URI, params.TextDocument.LanguageID)) uri := params.TextDocument.URI path := uri.Filename() doc := &Document{ - URI: uri, - Path: path, - Content: params.TextDocument.Text, - Ast: ParseAst(params.TextDocument.Text), + URI: uri, + Path: path, + Content: params.TextDocument.Text, + Ast: ParseAst(nil, params.TextDocument.Text), DiagnosticsCache: NewDiagnosticsCache(helmlsConfig), } s.documents[path] = doc return doc, nil } -func (s *DocumentStore) Close(uri lsp.DocumentURI) { - delete(s.documents, uri.Filename()) -} - func (s *DocumentStore) Get(docuri uri.URI) (*Document, bool) { path := docuri.Filename() d, ok := s.documents[path] @@ -66,12 +60,12 @@ type Document struct { Content string lines []string Ast *sitter.Tree - DiagnosticsCache DiagnosticsCache + DiagnosticsCache DiagnosticsCache } // ApplyChanges updates the content of the document from LSP textDocument/didChange events. func (d *Document) ApplyChanges(changes []lsp.TextDocumentContentChangeEvent) { - var content = []byte(d.Content) + content := []byte(d.Content) for _, change := range changes { start, end := util.PositionToIndex(change.Range.Start, content), util.PositionToIndex(change.Range.End, content) @@ -90,8 +84,7 @@ func (d *Document) ApplyChanges(changes []lsp.TextDocumentContentChangeEvent) { // WordAt returns the word found at the given location. func (d *Document) WordAt(pos lsp.Position) string { - - logger.Debug(pos) + logger.Debug(pos) line, ok := d.GetLine(int(pos.Line)) if !ok { @@ -100,187 +93,20 @@ func (d *Document) WordAt(pos lsp.Position) string { return util.WordAt(line, int(pos.Character)) } -func (d *Document) ValueAt(pos lsp.Position) string { - logger.Debug(pos) - - line, ok := d.GetLine(int(pos.Line)) - if !ok { - return "" - } - return util.ValueAt(line, int(pos.Character)) -} - -// ContentAtRange returns the document text at given range. -func (d *Document) ContentAtRange(rng lsp.Range) string { - return d.Content[rng.Start.Character:rng.End.Character] -} - // GetLine returns the line at the given index. func (d *Document) GetLine(index int) (string, bool) { - lines := d.GetLines() + lines := d.getLines() if index < 0 || index > len(lines) { return "", false } return lines[index], true } -// GetLines returns all the lines in the document. -func (d *Document) GetLines() []string { +// getLines returns all the lines in the document. +func (d *Document) getLines() []string { if d.lines == nil { // We keep \r on purpose, to avoid messing up position conversions. d.lines = strings.Split(d.Content, "\n") } return d.lines } - -// LookBehind returns the n characters before the given position, on the same line. -func (d *Document) LookBehind(pos lsp.Position, length int) string { - line, ok := d.GetLine(int(pos.Line)) - if !ok { - return "" - } - - charIdx := int(pos.Character) - if length > charIdx { - return line[0:charIdx] - } - return line[(charIdx - length):charIdx] -} - -// LookForward returns the n characters after the given position, on the same line. -func (d *Document) LookForward(pos lsp.Position, length int) string { - line, ok := d.GetLine(int(pos.Line)) - if !ok { - return "" - } - - lineLength := len(line) - charIdx := int(pos.Character) - if lineLength <= charIdx+length { - return line[charIdx:] - } - return line[charIdx:(charIdx + length)] -} - -// LinkFromRoot returns a Link to this document from the root of the given -// notebook. -//func (d *document) LinkFromRoot(nb *core.Notebook) (*documentLink, error) { - //href, err := nb.RelPath(d.Path) - //if err != nil { - //return nil, err - //} - //return &documentLink{ - //Href: href, - //RelativeToDir: nb.Path, - //}, nil -//} - -// DocumentLinkAt returns the internal or external link found in the document -// at the given position. -//func (d *Document) DocumentLinkAt(pos lsp.Position) (*documentLink, error) { - //links, err := d.DocumentLinks() - //if err != nil { - //return nil, err - //} - - //for _, link := range links { - //if positionInRange(d.Content, link.Range, pos) { - //return &link, nil - //} - //} - - //return nil, nil -//} - -// DocumentLinks returns all the internal and external links found in the -// document. -//func (d *document) DocumentLinks() ([]documentLink, error) { - //links := []documentLink{} - - //lines := d.GetLines() - //for lineIndex, line := range lines { - - //appendLink := func(href string, start, end int, hasTitle bool, isWikiLink bool) { - //if href == "" { - //return - //} - - //// Go regexes work with bytes, but the LSP client expects character indexes. - //start = strutil.ByteIndexToRuneIndex(line, start) - //end = strutil.ByteIndexToRuneIndex(line, end) - - //links = append(links, documentLink{ - //Href: href, - //RelativeToDir: filepath.Dir(d.Path), - //Range: protocol.Range{ - //Start: protocol.Position{ - //Line: protocol.UInteger(lineIndex), - //Character: protocol.UInteger(start), - //}, - //End: protocol.Position{ - //Line: protocol.UInteger(lineIndex), - //Character: protocol.UInteger(end), - //}, - //}, - //HasTitle: hasTitle, - //IsWikiLink: isWikiLink, - //}) - //} - - //for _, match := range markdownLinkRegex.FindAllStringSubmatchIndex(line, -1) { - //// Ignore embedded image, e.g. ![title](href.png) - //if match[0] > 0 && line[match[0]-1] == '!' { - //continue - //} - - //href := line[match[4]:match[5]] - //// Valid Markdown links are percent-encoded. - //if decodedHref, err := url.PathUnescape(href); err == nil { - //href = decodedHref - //} - //appendLink(href, match[0], match[1], false, false) - //} - - //for _, match := range wikiLinkRegex.FindAllStringSubmatchIndex(line, -1) { - //href := line[match[2]:match[3]] - //hasTitle := match[4] != -1 - //appendLink(href, match[0], match[1], hasTitle, true) - //} - //} - - //return links, nil -//} - -//// IsTagPosition returns whether the given caret position is inside a tag (YAML frontmatter, #hashtag, etc.). -//func (d *document) IsTagPosition(position protocol.Position, noteContentParser core.NoteContentParser) bool { - //lines := strutil.CopyList(d.GetLines()) - //lineIdx := int(position.Line) - //charIdx := int(position.Character) - //line := lines[lineIdx] - //// https://github.com/mickael-menu/zk/issues/144#issuecomment-1006108485 - //line = line[:charIdx] + "ZK_PLACEHOLDER" + line[charIdx:] - //lines[lineIdx] = line - //targetWord := strutil.WordAt(line, charIdx) - //if targetWord == "" { - //return false - //} - - //content := strings.Join(lines, "\n") - //note, err := noteContentParser.ParseNoteContent(content) - //if err != nil { - //return false - //} - //return strutil.Contains(note.Tags, targetWord) -//} - -//type documentLink struct { - //Href string - //RelativeToDir string - //Range lsp.Range - //// HasTitle indicates whether this link has a title information. For - //// example [[filename]] doesn't but [[filename|title]] does. - //HasTitle bool - //// IsWikiLink indicates whether this link is a [[WikiLink]] instead of a - //// regular Markdown link. - //IsWikiLink bool -//} diff --git a/internal/lsp/document_test.go b/internal/lsp/document_test.go new file mode 100644 index 00000000..ea0d84db --- /dev/null +++ b/internal/lsp/document_test.go @@ -0,0 +1,37 @@ +package lsp + +import ( + "testing" + + "github.com/mrjosh/helm-ls/internal/util" + "github.com/stretchr/testify/assert" + "go.lsp.dev/protocol" + "go.lsp.dev/uri" +) + +func TestDocumentStore(t *testing.T) { + assert := assert.New(t) + + sut := NewDocumentStore() + + assert.Empty(sut.GetAllDocs()) + + doc, ok := sut.Get(uri.File("test")) + assert.Nil(doc) + assert.False(ok) + + sut.DidOpen(protocol.DidOpenTextDocumentParams{ + TextDocument: protocol.TextDocumentItem{ + URI: uri.File("test.yaml"), + LanguageID: "helm", + Version: 0, + Text: "{{ .Values.test }}", + }, + }, util.DefaultConfig) + + assert.Len(sut.GetAllDocs(), 1) + + doc, ok = sut.Get(uri.File("test.yaml")) + assert.NotNil(doc) + assert.True(ok) +} diff --git a/internal/lsp/fs.go b/internal/lsp/fs.go deleted file mode 100644 index ca40a677..00000000 --- a/internal/lsp/fs.go +++ /dev/null @@ -1,36 +0,0 @@ -package lsp - -// FileStorage is a port providing read and write access to a file storage. -type FileStorage interface { - - // WorkingDir returns the current working directory. - WorkingDir() string - - // Abs makes the given file path absolute if needed, using the FileStorage - // working directory. - Abs(path string) (string, error) - - // Rel makes the given absolute file path relative to the current working - // directory. - Rel(path string) (string, error) - - // Canonical returns the canonical version of this path, resolving any - // symbolic link. - Canonical(path string) string - - // FileExists returns whether a file exists at the given file path. - FileExists(path string) (bool, error) - - // DirExists returns whether a directory exists at the given file path. - DirExists(path string) (bool, error) - - // IsDescendantOf returns whether the given path is dir or one of its descendants. - IsDescendantOf(dir string, path string) (bool, error) - - // Read returns the bytes content of the file at the given file path. - Read(path string) ([]byte, error) - - // Write creates or overwrite the content at the given file path, creating - // any intermediate directories if needed. - Write(path string, content []byte) error -} diff --git a/internal/util/strings.go b/internal/util/strings.go index 9c137ddc..03545cf6 100644 --- a/internal/util/strings.go +++ b/internal/util/strings.go @@ -56,25 +56,6 @@ func WordAt(str string, index int) string { return "" } -// ValueAt returns the value found at the given character position. -// It removes all content of the word after a "." right of the position. -func ValueAt(str string, index int) string { - wordIdxs := wordRegex.FindAllStringIndex(str, -1) - for _, wordIdx := range wordIdxs { - if wordIdx[0] <= index && index+1 <= wordIdx[1] { - leftOfWord := str[wordIdx[0] : index+1] - rightOfWord := str[index+1 : wordIdx[1]] - rightOfWordEnd := strings.Index(rightOfWord, ".") - if rightOfWordEnd == -1 { - rightOfWordEnd = len(rightOfWord) - 1 - } - return leftOfWord + rightOfWord[0:rightOfWordEnd+1] - } - } - - return "" -} - func PositionToIndex(pos protocol.Position, content []byte) int { index := 0 for i := 0; i < int(pos.Line); i++ {