From 6aad3f8acb3ad8ec55418c9b7d6587f9227e4fb3 Mon Sep 17 00:00:00 2001 From: qvalentin Date: Fri, 29 Dec 2023 18:19:25 +0100 Subject: [PATCH 1/3] test(completion): start testing buildFieldIdentifierPath --- internal/lsp/ast.go | 4 +- internal/lsp/ast_field_identifier_test.go | 96 +++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 internal/lsp/ast_field_identifier_test.go diff --git a/internal/lsp/ast.go b/internal/lsp/ast.go index be80a750..b1dca045 100644 --- a/internal/lsp/ast.go +++ b/internal/lsp/ast.go @@ -54,7 +54,6 @@ func GetFieldIdentifierPath(node *sitter.Node, doc *Document) (path string) { } func buildFieldIdentifierPath(node *sitter.Node, doc *Document) string { - prepend := node.PrevNamedSibling() currentPath := node.Content([]byte(doc.Content)) @@ -64,6 +63,9 @@ func buildFieldIdentifierPath(node *sitter.Node, doc *Document) string { nodeContent = "" } currentPath = prepend.Content([]byte(doc.Content)) + "." + nodeContent + logger.Println("Adding currentpath", currentPath) + } else { + logger.Println("Prepend is nil currentpath is ", currentPath) } if currentPath[0:1] == "$" { diff --git a/internal/lsp/ast_field_identifier_test.go b/internal/lsp/ast_field_identifier_test.go new file mode 100644 index 00000000..021ef9e1 --- /dev/null +++ b/internal/lsp/ast_field_identifier_test.go @@ -0,0 +1,96 @@ +package lsp + +import ( + "testing" + + sitter "github.com/smacker/go-tree-sitter" + "github.com/stretchr/testify/assert" +) + +func TestGetFieldIdentifierPathSimple(t *testing.T) { + template := `{{ .Values.test }}` + + var ast = ParseAst(template) + // (template [0, 0] - [1, 0] + // (selector_expression [0, 3] - [0, 15] + // operand: (field [0, 3] - [0, 10] + // name: (identifier [0, 4] - [0, 10])) + // field: (field_identifier [0, 11] - [0, 15])) + + test_start := sitter.Point{Row: 0, Column: 12} + testNode := ast.RootNode().NamedDescendantForPointRange(test_start, test_start) + + if testNode.Content([]byte(template)) != "test" { + t.Errorf("Nodes were not correctly selected") + } + + doc := Document{ + Content: template, + Ast: ast, + } + + result := GetFieldIdentifierPath(testNode, &doc) + assert.Equal(t, ".Values.test", result) +} + +func TestGetFieldIdentifierPathWith(t *testing.T) { + template := `{{ with .Values }}{{ .test }} {{ end }}` + + var ast = ParseAst(template) + // (template [0, 0] - [1, 0] + // (with_action [0, 0] - [0, 39] + // condition: (field [0, 8] - [0, 15] + // name: (identifier [0, 9] - [0, 15])) + // consequence: (field [0, 21] - [0, 26] + // name: (identifier [0, 22] - [0, 26])))) + + test_start := sitter.Point{Row: 0, Column: 22} + testNode := ast.RootNode().NamedDescendantForPointRange(test_start, test_start) + + if testNode.Content([]byte(template)) != "test" { + t.Errorf("Nodes were not correctly selected") + } + + doc := Document{ + Content: template, + Ast: ast, + } + + result := GetFieldIdentifierPath(testNode, &doc) + assert.Equal(t, ".Values.test", result) +} + +func TestGetFieldIdentifierPathFunction(t *testing.T) { + template := `{{ and .Values.test1 .Values.test2 }}` + + var ast = ParseAst(template) + // (template [0, 0] - [1, 0] + // (function_call [0, 3] - [0, 35] + // function: (identifier [0, 3] - [0, 6]) + // arguments: (argument_list [0, 7] - [0, 35] + // (selector_expression [0, 7] - [0, 20] + // operand: (field [0, 7] - [0, 14] + // name: (identifier [0, 8] - [0, 14])) + // field: (field_identifier [0, 15] - [0, 20])) + // (selector_expression [0, 21] - [0, 34] + // operand: (field [0, 21] - [0, 28] + // name: (identifier [0, 22] - [0, 28])) + // field: (field_identifier [0, 29] - [0, 34]))))) + // + test1_start := sitter.Point{Row: 0, Column: 16} + test2_start := sitter.Point{Row: 0, Column: 23} + test1Node := ast.RootNode().NamedDescendantForPointRange(test1_start, test1_start) + test2Node := ast.RootNode().NamedDescendantForPointRange(test2_start, test2_start) + + if test1Node.Content([]byte(template)) != "test1" || test2Node.Content([]byte(template)) != "test2" { + t.Errorf("Nodes were not correctly selected") + } + + doc := Document{ + Content: template, + Ast: ast, + } + + // assert.Equal(t, ".Values.test1", GetFieldIdentifierPath(test1Node, &doc)) + assert.Equal(t, ".Values.test2", GetFieldIdentifierPath(test2Node, &doc)) +} From 0b432fe2f31db70d68f8d234871302054e22ca69 Mon Sep 17 00:00:00 2001 From: qvalentin Date: Fri, 12 Jan 2024 22:03:53 +0100 Subject: [PATCH 2/3] fix(completion): complete after dot on selector expression --- internal/handler/completion.go | 6 +-- internal/lsp/ast.go | 8 +-- internal/lsp/ast_field_identifier_test.go | 41 +++++++++++++-- internal/tree-sitter/gotemplate/node-types.go | 51 ++++++++++--------- 4 files changed, 70 insertions(+), 36 deletions(-) diff --git a/internal/handler/completion.go b/internal/handler/completion.go index be1184fc..d2b7998a 100644 --- a/internal/handler/completion.go +++ b/internal/handler/completion.go @@ -73,7 +73,7 @@ func (h *langHandler) handleTextDocumentCompletion(ctx context.Context, reply js variableSplitted = append(variableSplitted, s) } - logger.Println(fmt.Sprintf("Word < %s >", word)) + logger.Println(fmt.Sprintf("Word found for completions is < %s >", word)) if len(variableSplitted) == 0 { return reply(ctx, basicItems, err) @@ -122,13 +122,13 @@ func completionAstParsing(doc *lsplocal.Document, position lsp.Position) (string ) logger.Debug("currentNode", currentNode) - logger.Debug("relevantChildNode", relevantChildNode.Type()) + logger.Debug("relevantChildNode", relevantChildNode) switch relevantChildNode.Type() { case gotemplate.NodeTypeIdentifier: word = relevantChildNode.Content([]byte(doc.Content)) case gotemplate.NodeTypeDot: - logger.Debug("TraverseIdentifierPathUp") + logger.Debug("TraverseIdentifierPathUp for dot node") word = lsplocal.TraverseIdentifierPathUp(relevantChildNode, doc) case gotemplate.NodeTypeDotSymbol: logger.Debug("GetFieldIdentifierPath") diff --git a/internal/lsp/ast.go b/internal/lsp/ast.go index b1dca045..047d4a2d 100644 --- a/internal/lsp/ast.go +++ b/internal/lsp/ast.go @@ -2,6 +2,7 @@ package lsp import ( "context" + "fmt" "github.com/mrjosh/helm-ls/internal/tree-sitter/gotemplate" sitter "github.com/smacker/go-tree-sitter" @@ -49,7 +50,8 @@ func isPointLargerOrEq(a sitter.Point, b sitter.Point) bool { func GetFieldIdentifierPath(node *sitter.Node, doc *Document) (path string) { path = buildFieldIdentifierPath(node, doc) - logger.Debug("buildFieldIdentifierPath:", path) + logger.Debug(fmt.Sprintf("buildFieldIdentifierPath: %s for node %s with parent %s", path, node, node.Parent())) + return path } @@ -64,8 +66,8 @@ func buildFieldIdentifierPath(node *sitter.Node, doc *Document) string { } currentPath = prepend.Content([]byte(doc.Content)) + "." + nodeContent logger.Println("Adding currentpath", currentPath) - } else { - logger.Println("Prepend is nil currentpath is ", currentPath) + } else if node.Parent() != nil && node.Parent().Type() == gotemplate.NodeTypeError { + return buildFieldIdentifierPath(node.Parent(), doc) } if currentPath[0:1] == "$" { diff --git a/internal/lsp/ast_field_identifier_test.go b/internal/lsp/ast_field_identifier_test.go index 021ef9e1..b5064677 100644 --- a/internal/lsp/ast_field_identifier_test.go +++ b/internal/lsp/ast_field_identifier_test.go @@ -5,6 +5,7 @@ import ( sitter "github.com/smacker/go-tree-sitter" "github.com/stretchr/testify/assert" + lsp "go.lsp.dev/protocol" ) func TestGetFieldIdentifierPathSimple(t *testing.T) { @@ -78,19 +79,49 @@ func TestGetFieldIdentifierPathFunction(t *testing.T) { // field: (field_identifier [0, 29] - [0, 34]))))) // test1_start := sitter.Point{Row: 0, Column: 16} - test2_start := sitter.Point{Row: 0, Column: 23} + test2_start := sitter.Point{Row: 0, Column: 33} test1Node := ast.RootNode().NamedDescendantForPointRange(test1_start, test1_start) test2Node := ast.RootNode().NamedDescendantForPointRange(test2_start, test2_start) - if test1Node.Content([]byte(template)) != "test1" || test2Node.Content([]byte(template)) != "test2" { - t.Errorf("Nodes were not correctly selected") - } + test1NodeContent := test1Node.Content([]byte(template)) + test2NodeContent := test2Node.Content([]byte(template)) + + assert.Equal(t, "test1", test1NodeContent, "Nodes were not correctly selected") + assert.Equal(t, "test2", test2NodeContent, "Nodes were not correctly selected") doc := Document{ Content: template, Ast: ast, } - // assert.Equal(t, ".Values.test1", GetFieldIdentifierPath(test1Node, &doc)) + assert.Equal(t, ".Values.test1", GetFieldIdentifierPath(test1Node, &doc)) assert.Equal(t, ".Values.test2", GetFieldIdentifierPath(test2Node, &doc)) } + +func TestGetFieldIdentifierPathFunctionForCompletion(t *testing.T) { + template := `{{ and .Values.image .Values. }}` + // | -> complete at dot + + var ast = ParseAst(template) + + var ( + position = lsp.Position{Line: 0, Character: 29} + currentNode = NodeAtPosition(ast, position) + pointToLoopUp = sitter.Point{ + Row: position.Line, + Column: position.Character, + } + relevantChildNode = FindRelevantChildNode(currentNode, pointToLoopUp) + ) + + childNodeContent := relevantChildNode.Content([]byte(template)) + + assert.Equal(t, ".", childNodeContent, "Nodes were not correctly selected ") + + doc := Document{ + Content: template, + Ast: ast, + } + + assert.Equal(t, ".Values.", GetFieldIdentifierPath(relevantChildNode, &doc)) +} diff --git a/internal/tree-sitter/gotemplate/node-types.go b/internal/tree-sitter/gotemplate/node-types.go index 3f092686..dddaf9f1 100644 --- a/internal/tree-sitter/gotemplate/node-types.go +++ b/internal/tree-sitter/gotemplate/node-types.go @@ -1,38 +1,39 @@ package gotemplate const ( - NodeTypeIdentifier = "identifier" - NodeTypeVariable = "variable" - NodeTypeDot = "dot" + NodeTypeBlock = "block" + NodeTypeBlockAction = "block_action" + NodeTypeChainedPipeline = "chained_pipeline" + NodeTypeCloseBraces = "}}" + NodeTypeCloseBracesDash = "-}}" + NodeTypeComment = "comment" + NodeTypeDefine = "define" + NodeTypeDefineAction = "define_action" NodeTypeDollar = "$" + NodeTypeDot = "dot" NodeTypeDotSymbol = "." - NodeTypeVariableDefinition = "variable_definition" - NodeTypeRangeVariableDefinition = "range_variable_definition" + NodeTypeElse = "else" + NodeTypeElseIf = "else if" + NodeTypeEnd = "end" + NodeTypeError = "ERROR" NodeTypeFieldIdentifier = "field_identifier" - NodeTypeText = "text" - NodeTypeTemplate = "template" - NodeTypeIfAction = "if_action" + NodeTypeFunctionCall = "function_call" + NodeTypeIdentifier = "identifier" NodeTypeIf = "if" - NodeTypeBlockAction = "block_action" - NodeTypeWithAction = "with_action" + NodeTypeIfAction = "if_action" + NodeTypeInterpretedStringLiteral = "interpreted_string_literal" + NodeTypeOpenBraces = "{{" + NodeTypeOpenBracesDash = "{{-" + NodeTypeRange = "range" NodeTypeRangeAction = "range_action" - NodeTypeDefineAction = "define_action" - NodeTypeFunctionCall = "function_call" - NodeTypeComment = "comment" + NodeTypeRangeVariableDefinition = "range_variable_definition" NodeTypeSelectorExpression = "selector_expression" - NodeTypeElse = "else" - NodeTypeElseIf = "else if" - NodeTypeRange = "range" + NodeTypeTemplate = "template" + NodeTypeText = "text" + NodeTypeVariable = "variable" + NodeTypeVariableDefinition = "variable_definition" NodeTypeWith = "with" - NodeTypeDefine = "define" - NodeTypeOpenBraces = "{{" - NodeTypeOpenBracesDash = "{{-" - NodeTypeCloseBraces = "}}" - NodeTypeCloseBracesDash = "-}}" - NodeTypeEnd = "end" - NodeTypeInterpretedStringLiteral = "interpreted_string_literal" - NodeTypeBlock = "block" - NodeTypeChainedPipeline = "chained_pipeline" + NodeTypeWithAction = "with_action" FieldNameAlternative = "alternative" FieldNameCondition = "condition" From a3bab9e21375cadb27aac477f577d4a2ed121531 Mon Sep 17 00:00:00 2001 From: qvalentin Date: Sat, 13 Jan 2024 12:14:09 +0100 Subject: [PATCH 3/3] chore(lints): rename functions --- internal/adapter/yamlls/trimTemplate.go | 8 ++++---- internal/documentation/{go_docs => godocs}/gotemplate.go | 2 +- internal/handler/completion.go | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) rename internal/documentation/{go_docs => godocs}/gotemplate.go (99%) diff --git a/internal/adapter/yamlls/trimTemplate.go b/internal/adapter/yamlls/trimTemplate.go index 1a27a2fb..1827c4e0 100644 --- a/internal/adapter/yamlls/trimTemplate.go +++ b/internal/adapter/yamlls/trimTemplate.go @@ -16,9 +16,9 @@ func prettyPrintNode(node *sitter.Node, previous []byte, result []byte) { switch node.Type() { case gotemplate.NodeTypeIfAction: - trim_if_action(node, previous, result) + trimIfAction(node, previous, result) case gotemplate.NodeTypeBlockAction, gotemplate.NodeTypeWithAction, gotemplate.NodeTypeRangeAction: - trim_action(childCount, node, previous, result) + trimAction(childCount, node, previous, result) case gotemplate.NodeTypeDefineAction: earaseTemplate(node, previous, result) case gotemplate.NodeTypeFunctionCall: @@ -32,7 +32,7 @@ func prettyPrintNode(node *sitter.Node, previous []byte, result []byte) { } } -func trim_action(childCount uint32, node *sitter.Node, previous []byte, result []byte) { +func trimAction(childCount uint32, node *sitter.Node, previous []byte, result []byte) { for i := 0; i < int(childCount); i++ { child := node.Child(i) switch child.Type() { @@ -60,7 +60,7 @@ func trim_action(childCount uint32, node *sitter.Node, previous []byte, result [ } } -func trim_if_action(node *sitter.Node, previous []byte, result []byte) { +func trimIfAction(node *sitter.Node, previous []byte, result []byte) { curser := sitter.NewTreeCursor(node) curser.GoToFirstChild() for curser.GoToNextSibling() { diff --git a/internal/documentation/go_docs/gotemplate.go b/internal/documentation/godocs/gotemplate.go similarity index 99% rename from internal/documentation/go_docs/gotemplate.go rename to internal/documentation/godocs/gotemplate.go index 43bf3bb4..79121e0c 100644 --- a/internal/documentation/go_docs/gotemplate.go +++ b/internal/documentation/godocs/gotemplate.go @@ -30,7 +30,7 @@ // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package go_docs +package godocs type GoTemplateSnippet struct { Name string diff --git a/internal/handler/completion.go b/internal/handler/completion.go index d2b7998a..bce97f80 100644 --- a/internal/handler/completion.go +++ b/internal/handler/completion.go @@ -17,7 +17,7 @@ import ( lsp "go.lsp.dev/protocol" yaml "gopkg.in/yaml.v2" - "github.com/mrjosh/helm-ls/internal/documentation/go_docs" + "github.com/mrjosh/helm-ls/internal/documentation/godocs" ) var ( @@ -30,7 +30,7 @@ func init() { functionsCompletionItems = append(functionsCompletionItems, getFunctionCompletionItems(helmFuncs)...) functionsCompletionItems = append(functionsCompletionItems, getFunctionCompletionItems(builtinFuncs)...) functionsCompletionItems = append(functionsCompletionItems, getFunctionCompletionItems(sprigFuncs)...) - textCompletionsItems = append(textCompletionsItems, getTextCompletionItems(go_docs.TextSnippets)...) + textCompletionsItems = append(textCompletionsItems, getTextCompletionItems(godocs.TextSnippets)...) } func (h *langHandler) handleTextDocumentCompletion(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) (err error) { @@ -258,14 +258,14 @@ func functionCompletionItem(helmDocumentation HelmDocumentation) lsp.CompletionI } } -func getTextCompletionItems(gotemplateSnippet []go_docs.GoTemplateSnippet) (result []lsp.CompletionItem) { +func getTextCompletionItems(gotemplateSnippet []godocs.GoTemplateSnippet) (result []lsp.CompletionItem) { for _, item := range gotemplateSnippet { result = append(result, textCompletionItem(item)) } return result } -func textCompletionItem(gotemplateSnippet go_docs.GoTemplateSnippet) lsp.CompletionItem { +func textCompletionItem(gotemplateSnippet godocs.GoTemplateSnippet) lsp.CompletionItem { return lsp.CompletionItem{ Label: gotemplateSnippet.Name, TextEdit: &lsp.TextEdit{