From 9fd77287046451d20973f350056763af85c52352 Mon Sep 17 00:00:00 2001 From: Julio Tain Sueiras Date: Tue, 14 Apr 2020 17:29:37 -0400 Subject: [PATCH] Added basic snippet and fixed map and array completion bug, close #60 --- hclstructs/main.go | 32 ++++++++++++++++- helper/file.go | 27 +++++--------- langserver/complete.go | 43 +++++++++++----------- loghelper/main.go | 15 ++++++++ main.go | 6 +++- tfstructs/main.go | 24 ++++++------- tfstructs/parser_config.go | 73 +++++++++++++++++++++++++++++--------- tfstructs/provider.go | 6 ++-- tfstructs/vars.go | 13 ++++--- 9 files changed, 159 insertions(+), 80 deletions(-) create mode 100644 loghelper/main.go diff --git a/hclstructs/main.go b/hclstructs/main.go index 9381406..bb4f2b0 100644 --- a/hclstructs/main.go +++ b/hclstructs/main.go @@ -39,6 +39,14 @@ func TemplateWrapExpr() reflect.Type { return GetType(&hclsyntax.TemplateWrapExpr{}) } +func AnonSymbolExpr() reflect.Type { + return GetType(&hclsyntax.AnonSymbolExpr{}) +} + +func SplatExpr() reflect.Type { + return GetType(&hclsyntax.SplatExpr{}) +} + // Traverse hcl func TraverseAttr() reflect.Type { return GetType(hcl.TraverseAttr{}) @@ -68,6 +76,10 @@ func GetExprStringType(origType reflect.Type) string { return "string interpolation" case ObjectConsExpr(): return "object" + case SplatExpr(): + return "splat" + case AnonSymbolExpr(): + return "anon symbol" default: return "undefined" } @@ -117,7 +129,12 @@ func GetExprVariables(origType reflect.Type, expr hcl.Expression, posHCL hcl.Pos case TupleConsExpr(): expr := expr.(*hclsyntax.TupleConsExpr) if expr.Range().ContainsPos(posHCL) { - return expr.Variables() + for _, newExpr := range expr.ExprList() { + if newExpr.Range().ContainsPos(posHCL) { + return newExpr.Variables() + } + } + return nil } // Need wrapped @@ -127,6 +144,18 @@ func GetExprVariables(origType reflect.Type, expr hcl.Expression, posHCL hcl.Pos return expr.Variables() } + case AnonSymbolExpr(): + expr := expr.(*hclsyntax.AnonSymbolExpr) + if expr.Range().ContainsPos(posHCL) || expr.SrcRange.ContainsPos(posHCL) { + return expr.Variables() + } + + case SplatExpr(): + expr := expr.(*hclsyntax.SplatExpr) + if expr.MarkerRange.ContainsPos(posHCL) || expr.Range().ContainsPos(posHCL) { + return expr.Source.Variables() + } + // Need more check case ObjectConsExpr(): expr := expr.(*hclsyntax.ObjectConsExpr) @@ -138,6 +167,7 @@ func GetExprVariables(origType reflect.Type, expr hcl.Expression, posHCL hcl.Pos firstVar := hcl.TraverseAttr{ Name: v.KeyExpr.(*hclsyntax.ObjectConsKeyExpr).AsTraversal().RootName(), } + vars := hcl.Traversal{ firstVar, } diff --git a/helper/file.go b/helper/file.go index 8a2f4a6..92d33e6 100644 --- a/helper/file.go +++ b/helper/file.go @@ -1,13 +1,11 @@ package helper import ( - "github.com/davecgh/go-spew/spew" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/terraform/configs" "github.com/juliosueiras/terraform-lsp/hclstructs" "github.com/juliosueiras/terraform-lsp/memfs" - log "github.com/sirupsen/logrus" "github.com/sourcegraph/go-lsp" "github.com/spf13/afero" "github.com/zclconf/go-cty/cty" @@ -23,12 +21,16 @@ func CheckAndGetConfig(parser *configs.Parser, originalFile afero.File, line int pos := FindOffset(string(fileText), line, character) tempFile, _ := afero.TempFile(memfs.MemFs, "", "check_tf_lsp") + found := false - defer memfs.MemFs.Remove(tempFile.Name()) + if int64(pos) != -1 { + found = true + originalFile.ReadAt(result, int64(pos)) + } - originalFile.ReadAt(result, int64(pos)) + defer memfs.MemFs.Remove(tempFile.Name()) - if string(result) == "." { + if found && string(result) == "." { fileText[pos] = ' ' fileText = []byte(strings.Replace(string(fileText), ". ", " ", -1)) @@ -52,7 +54,6 @@ func CheckAndGetConfig(parser *configs.Parser, originalFile afero.File, line int tempFile.Truncate(0) tempFile.Seek(0, 0) tempFile.Write([]byte(strings.Join(textLines, "\n"))) - DumpLog(textLines) resultConfig, diags := parser.LoadConfigFileOverride(tempFile.Name()) testRes, _ := parser.LoadHCLFile(tempFile.Name()) return resultConfig, diags, character, testRes.(*hclsyntax.Body), false @@ -69,6 +70,7 @@ func FindOffset(fileText string, line, column int) int { column = 1 } + //variable \"test\" {\n \n}\n\n currentCol := 1 currentLine := 1 @@ -87,14 +89,6 @@ func FindOffset(fileText string, line, column int) int { return -1 } -func DumpLog(res interface{}) { - result := spew.Sdump(res) - strSlice := strings.Split(result, "\n") - for _, s := range strSlice { - log.Debug(s) - } -} - func ParseVariables(vars hcl.Traversal, configVars map[string]*configs.Variable, completionItems []lsp.CompletionItem) []lsp.CompletionItem { if len(vars) == 0 { for _, t := range configVars { @@ -130,6 +124,7 @@ func parseVariables(vars hcl.Traversal, configVarsType *cty.Type, completionItem return completionItems } + if !configVarsType.IsObjectType() { if et := configVarsType.MapElementType(); et != nil { return parseVariables(vars[1:], et, completionItems) @@ -151,12 +146,8 @@ func parseVariables(vars hcl.Traversal, configVarsType *cty.Type, completionItem return parseVariables(vars[1:], &attr, completionItems) } } else if reflect.TypeOf(vars[0]) == hclstructs.TraverseIndex() { - DumpLog(configVarsType) return parseVariables(vars[1:], configVarsType, completionItems) - } else { - DumpLog(vars[0]) - DumpLog(configVarsType) } return nil diff --git a/langserver/complete.go b/langserver/complete.go index cbcf2bc..59351eb 100644 --- a/langserver/complete.go +++ b/langserver/complete.go @@ -74,19 +74,16 @@ func TextDocumentComplete(ctx context.Context, vs lsp.CompletionParams) (lsp.Com } if r, found, _ := tfstructs.GetTypeCompletion(result, fileDir, hclFile, posHCL, extraProvider); found { - helper.DumpLog("Found Type Completion") return r, nil } config, origConfig, configType := tfstructs.GetConfig(file, posHCL) if diags != nil || config == nil { - helper.DumpLog("With Error or No Config") - helper.DumpLog(diags) return lsp.CompletionList{ IsIncomplete: false, - Items: tfstructs.GetTopLevelCompletion(), + Items: tfstructs.GetTopLevelCompletionWithPos(posHCL), }, nil } @@ -139,10 +136,17 @@ func TextDocumentComplete(ctx context.Context, vs lsp.CompletionParams) (lsp.Com params = append(params, x.Name) } + var resultParams []string + for index, param := range params { + resultParams = append(resultParams, fmt.Sprintf("${%d:%s}", index+1, param)) + } + result = append(result, lsp.CompletionItem{ - Label: fmt.Sprintf("%s(%s)", k, strings.Join(params, ",")), - InsertText: k, - Detail: " function", + Label: fmt.Sprintf("%s(%s)", k, strings.Join(params, ",")), + Kind: lsp.CIKField, + InsertTextFormat: lsp.ITFSnippet, + InsertText: fmt.Sprintf("%s(%s)", k, strings.Join(resultParams, ",")), + Detail: " function", }) } @@ -173,7 +177,6 @@ func TextDocumentComplete(ctx context.Context, vs lsp.CompletionParams) (lsp.Com } } - //hclsyntax.LiteralValueExpr if r, found, _ := tfstructs.GetAttributeCompletion(result, configType, origConfig, fileDir); found { return r, nil } @@ -184,7 +187,6 @@ func TextDocumentComplete(ctx context.Context, vs lsp.CompletionParams) (lsp.Com if blocks != nil && attr == nil { //helper.DumpLog(blocks) if blocks[0].Type == "provisioner" { - helper.DumpLog(blocks) if len(blocks) == 1 { if r, found, _ := tfstructs.GetAttributeCompletion(result, "provisioner", blocks[0], fileDir); found { @@ -220,10 +222,11 @@ func TextDocumentComplete(ctx context.Context, vs lsp.CompletionParams) (lsp.Com } } + if expr == nil { + expr = attr.Expr + } + if expr != nil { - helper.DumpLog("Found Expression") - helper.DumpLog(posHCL) - helper.DumpLog(expr) //.*for.*in\s+([^:]*) //te, te2 := hclsyntax.ParseExpression([]byte("aws[0].test"), "test", hcl.Pos{ // Line: 0, @@ -241,7 +244,6 @@ func TextDocumentComplete(ctx context.Context, vs lsp.CompletionParams) (lsp.Com searchResult := re.FindSubmatch([]byte(textLines[vs.Position.Line])) if searchResult != nil { - helper.DumpLog(searchResult[1]) dynamicExpr, _ := hclsyntax.ParseExpression([]byte(searchResult[1]), "test", hcl.Pos{ Line: 0, Column: 0, @@ -266,9 +268,7 @@ func TextDocumentComplete(ctx context.Context, vs lsp.CompletionParams) (lsp.Com //reflect.New(origType) if origType == hclstructs.ForExpr() { expr := expr.(*hclsyntax.ForExpr) - helper.DumpLog(expr) resultName := []string{} - helper.DumpLog(expr.ValExpr.Range().ContainsPos(posHCL)) if expr.ValExpr.Range().ContainsPos(posHCL) { if reflect.TypeOf(expr.CollExpr) == hclstructs.ScopeTraversalExpr() { resultName = append(resultName, expr.CollExpr.(*hclsyntax.ScopeTraversalExpr).AsTraversal().RootName()) @@ -283,10 +283,7 @@ func TextDocumentComplete(ctx context.Context, vs lsp.CompletionParams) (lsp.Com } scopeExpr := expr.ValExpr.(*hclsyntax.ScopeTraversalExpr) - helper.DumpLog(haveDot) - helper.DumpLog((len(scopeExpr.AsTraversal()) == 1 && haveDot) || len(scopeExpr.AsTraversal()) > 1) if len(scopeExpr.AsTraversal()) == 1 && !haveDot { - helper.DumpLog(vs.Position.Character) result = append(result, lsp.CompletionItem{ Label: expr.ValVar, Detail: fmt.Sprintf(" foreach var(%s)", strings.Join(resultName, ".")), @@ -317,10 +314,12 @@ func TextDocumentComplete(ctx context.Context, vs lsp.CompletionParams) (lsp.Com } } //tests, errxs := lang.ReferencesInExpr(expr) - if origType != hclstructs.ObjectConsExpr() { - variables := hclstructs.GetExprVariables(origType, expr, posHCL) - if len(variables) != 0 { + variables := hclstructs.GetExprVariables(origType, expr, posHCL) + + if len(variables) == 1 || variables == nil { + + if variables != nil && len(variables) != 0 { result = tfstructs.GetVarAttributeCompletion(tfstructs.GetVarAttributeRequest{ Variables: variables[0], Result: result, @@ -390,7 +389,7 @@ func TextDocumentComplete(ctx context.Context, vs lsp.CompletionParams) (lsp.Com } } else { if blocks == nil && attr != nil { - if r, found, _ := tfstructs.GetNestingAttributeCompletion(attr, result, configType, origConfig, fileDir, posHCL); found { + if r, found, _ := tfstructs.GetNestingAttributeCompletion(attr, result, configType, origConfig, fileDir, posHCL, origType); found { return r, nil } } diff --git a/loghelper/main.go b/loghelper/main.go new file mode 100644 index 0000000..a7409f1 --- /dev/null +++ b/loghelper/main.go @@ -0,0 +1,15 @@ +package loghelper + +import ( + "github.com/davecgh/go-spew/spew" + log "github.com/sirupsen/logrus" + "strings" +) + +func DumpLog(res interface{}) { + result := spew.Sdump(res) + strSlice := strings.Split(result, "\n") + for _, s := range strSlice { + log.Debug(s) + } +} diff --git a/main.go b/main.go index 756c0b7..c5b4bef 100644 --- a/main.go +++ b/main.go @@ -39,7 +39,6 @@ func init() { } func main() { - oldLog.SetOutput(ioutil.Discard) oldLog.SetFlags(0) @@ -51,6 +50,11 @@ func main() { debug := viper.GetBool("debug") log.Infof("Log Level is Debug: %t", debug) + log.SetFormatter(&log.TextFormatter{ + DisableTimestamp: true, + ForceColors: true, + DisableLevelTruncation: true, + }) if debug { log.SetLevel(log.DebugLevel) diff --git a/tfstructs/main.go b/tfstructs/main.go index 2b25689..6e0c4b5 100644 --- a/tfstructs/main.go +++ b/tfstructs/main.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform/lang" "github.com/hashicorp/terraform/providers" "github.com/hashicorp/terraform/provisioners" - "github.com/juliosueiras/terraform-lsp/helper" + "github.com/juliosueiras/terraform-lsp/loghelper" "github.com/juliosueiras/terraform-lsp/memfs" "github.com/zclconf/go-cty/cty" "path/filepath" @@ -67,13 +67,13 @@ func GetResourceSchemaForDiags(resourceType string, config hcl.Body, targetDir s } if err != nil { - helper.DumpLog(err) + loghelper.DumpLog(err) return nil } providerResource, err := provider.GetRawResourceTypeSchema(resourceType) if err != nil { - helper.DumpLog(err) + loghelper.DumpLog(err) provider.Kill() return nil } @@ -107,13 +107,13 @@ func GetResourceSchema(resourceType string, config hcl.Body, targetDir string, o } if err != nil { - helper.DumpLog(err) + loghelper.DumpLog(err) return nil } providerResource, err := provider.GetRawResourceTypeSchema(resourceType) if err != nil { - helper.DumpLog(err) + loghelper.DumpLog(err) provider.Kill() return nil } @@ -155,13 +155,13 @@ func GetDataSourceSchemaForDiags(dataSourceType string, config hcl.Body, targetD provider, err = GetProvider(dataSourceType, targetDir) } if err != nil { - helper.DumpLog(err) + loghelper.DumpLog(err) return nil } providerDataSource, err := provider.GetRawDataSourceTypeSchema(dataSourceType) if err != nil { - helper.DumpLog(err) + loghelper.DumpLog(err) provider.Kill() return nil } @@ -191,13 +191,13 @@ func GetDataSourceSchema(dataSourceType string, config hcl.Body, targetDir strin provider, err = GetProvider(dataSourceType, targetDir) } if err != nil { - helper.DumpLog(err) + loghelper.DumpLog(err) return nil } providerDataSource, err := provider.GetRawDataSourceTypeSchema(dataSourceType) if err != nil { - helper.DumpLog(err) + loghelper.DumpLog(err) provider.Kill() return nil } @@ -243,7 +243,7 @@ func GetProvisioner(provisionerType string, targetDir string) (*Client, error) { func GetProvisionerSchema(provisionerType string, config hcl.Body, targetDir string) *TerraformProvisionerSchema { provisioner, err := GetProvider(provisionerType, targetDir) if err != nil { - helper.DumpLog(err) + loghelper.DumpLog(err) return nil } @@ -274,7 +274,7 @@ func GetProvisionerSchema(provisionerType string, config hcl.Body, targetDir str func GetProviderSchemaForDiags(providerType string, config hcl.Body, targetDir string, variables map[string]cty.Value) *TerraformSchema { provider, err := GetProvider(providerType, targetDir) if err != nil { - helper.DumpLog(err) + loghelper.DumpLog(err) return nil } @@ -300,7 +300,7 @@ func GetProviderSchemaForDiags(providerType string, config hcl.Body, targetDir s func GetProviderSchema(providerType string, config hcl.Body, targetDir string) *TerraformSchema { provider, err := GetProvider(providerType, targetDir) if err != nil { - helper.DumpLog(err) + loghelper.DumpLog(err) return nil } diff --git a/tfstructs/parser_config.go b/tfstructs/parser_config.go index 05ca14a..41b45ca 100644 --- a/tfstructs/parser_config.go +++ b/tfstructs/parser_config.go @@ -10,14 +10,15 @@ import ( "github.com/juliosueiras/terraform-lsp/helper" "github.com/sourcegraph/go-lsp" "github.com/zclconf/go-cty/cty" + "reflect" "strings" ) -func GetNestingAttributeCompletion(attr *hcl.Attribute, result []lsp.CompletionItem, configType string, origConfig interface{}, fileDir string, posHCL hcl.Pos) (lsp.CompletionList, bool, error) { +func GetNestingAttributeCompletion(attr *hcl.Attribute, result []lsp.CompletionItem, configType string, origConfig interface{}, fileDir string, posHCL hcl.Pos, origType reflect.Type) (lsp.CompletionList, bool, error) { topName := attr.Name - res := hclstructs.GetExprVariables(hclstructs.ObjectConsExpr(), attr.Expr, posHCL) + res := hclstructs.GetExprVariables(origType, attr.Expr, posHCL) attrs := hcl.Traversal{} if len(res) != 0 { @@ -189,18 +190,16 @@ func GetConfig(file *configs.File, posHCL hcl.Pos) (*hclsyntax.Body, interface{} } //* configs.Backends - for _, v := range file.Backends { - helper.DumpLog(v) - helper.DumpLog(TerraformBackends["remote"]) - // helper.DumpLog(backend.Backend{ - // - // }) - // if v.Config.(*hclsyntax.Body).Range().ContainsPos(posHCL) { - // configType = "backend" - // origConfig = v - // config = v.Config.(interface{}).(*hclsyntax.Body) - // } - } + //for _, v := range file.Backends { + // // helper.DumpLog(backend.Backend{ + // // + // // }) + // // if v.Config.(*hclsyntax.Body).Range().ContainsPos(posHCL) { + // // configType = "backend" + // // origConfig = v + // // config = v.Config.(interface{}).(*hclsyntax.Body) + // // } + //} for _, v := range file.ModuleCalls { if v.Config.(*hclsyntax.Body).Range().ContainsPos(posHCL) { @@ -298,8 +297,6 @@ func GetAttributeCompletion(result []lsp.CompletionItem, configType string, orig }, true, nil } - helper.DumpLog(res) - for k, v := range res.Schema.Block.Attributes { if v.Optional || v.Required { result = append(result, lsp.CompletionItem{ @@ -698,3 +695,47 @@ func GetTopLevelCompletion() []lsp.CompletionItem { } } + +func GetTopLevelCompletionWithPos(pos hcl.Pos) []lsp.CompletionItem { + return []lsp.CompletionItem{ + lsp.CompletionItem{ + Label: "resource", + Kind: lsp.CIKField, + Detail: " type", + InsertTextFormat: lsp.ITFSnippet, + InsertText: `resource "${1}" "${2}" { + ${3} +}`, + }, + lsp.CompletionItem{ + Label: "data", + Kind: lsp.CIKField, + Detail: " type", + InsertTextFormat: lsp.ITFSnippet, + InsertText: `data "${1}" "${2}" { + ${3} +}`, + }, + lsp.CompletionItem{ + Label: "module", + Detail: " type", + }, + lsp.CompletionItem{ + Label: "output", + Detail: " type", + }, + lsp.CompletionItem{ + Label: "variable", + Detail: " type", + }, + lsp.CompletionItem{ + Label: "provider", + Detail: " type", + }, + lsp.CompletionItem{ + Label: "terraform", + Detail: " type", + }, + } + +} diff --git a/tfstructs/provider.go b/tfstructs/provider.go index 31284d5..93bf4be 100644 --- a/tfstructs/provider.go +++ b/tfstructs/provider.go @@ -3,7 +3,7 @@ package tfstructs import ( "fmt" - log "github.com/sirupsen/logrus" + //log "github.com/sirupsen/logrus" "go/build" "io/ioutil" oldLog "log" @@ -190,7 +190,7 @@ func pluginDirs(targetDir string) ([]string, error) { for dir := targetDir; dir != "" && searchLevel != 0; dir = filepath.Dir(dir) { - log.Debug("[DEBUG] search .terraform dir in %s", dir) + //log.Debug("[DEBUG] search .terraform dir in %s", dir) if _, err := os.Stat(filepath.Join(dir, ".terraform")); err == nil { autoInstalledDir = filepath.Join(dir, ".terraform", "plugins", arch) @@ -219,7 +219,7 @@ func pluginDirs(targetDir string) ([]string, error) { gopath := build.Default.GOPATH dirs = append(dirs, filepath.Join(gopath, "bin")) - log.Debug("[DEBUG] plugin dirs: %#v", dirs) + //log.Debug("[DEBUG] plugin dirs: %#v", dirs) return dirs, nil } diff --git a/tfstructs/vars.go b/tfstructs/vars.go index eff55c9..9c7338c 100644 --- a/tfstructs/vars.go +++ b/tfstructs/vars.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform/lang" "github.com/juliosueiras/terraform-lsp/hclstructs" "github.com/juliosueiras/terraform-lsp/helper" + // "github.com/juliosueiras/terraform-lsp/loghelper" "github.com/sourcegraph/go-lsp" "github.com/zclconf/go-cty/cty" "os" @@ -39,8 +40,6 @@ func GetVarAttributeCompletion(request GetVarAttributeRequest) []lsp.CompletionI searchLevel -= 1 } - helper.DumpLog(targetDir) - variables := map[string]cty.Value{ "path": cty.ObjectVal(map[string]cty.Value{ "cwd": cty.StringVal(request.FileDir), @@ -57,6 +56,11 @@ func GetVarAttributeCompletion(request GetVarAttributeRequest) []lsp.CompletionI }), } + //rootName := "" + if request.Variables.IsRelative() { + request.Variables = request.Variables[1:] + } + if request.Variables.RootName() == "var" { vars := request.Variables @@ -79,8 +83,6 @@ func GetVarAttributeCompletion(request GetVarAttributeRequest) []lsp.CompletionI }, ) - helper.DumpLog(testVal) - origType := reflect.TypeOf(found.Expr) if origType == hclstructs.ObjectConsExpr() { @@ -99,7 +101,6 @@ func GetVarAttributeCompletion(request GetVarAttributeRequest) []lsp.CompletionI for _, v := range items { origType2 := reflect.TypeOf(v.ValueExpr) - helper.DumpLog(v.KeyExpr.(*hclsyntax.ObjectConsKeyExpr).Wrapped.(*hclsyntax.ScopeTraversalExpr).AsTraversal().RootName()) request.Result = append(request.Result, lsp.CompletionItem{ Label: v.KeyExpr.(*hclsyntax.ObjectConsKeyExpr).Wrapped.(*hclsyntax.ScopeTraversalExpr).AsTraversal().RootName(), Detail: fmt.Sprintf(" %s", hclstructs.GetExprStringType(origType2)), @@ -107,8 +108,6 @@ func GetVarAttributeCompletion(request GetVarAttributeRequest) []lsp.CompletionI } } - helper.DumpLog(request.Variables[2:]) - helper.DumpLog(testVal.Type()) request.Result = append(request.Result, helper.ParseOtherAttr(request.Variables[2:], testVal.Type(), request.Result)...) return request.Result