From 46820bd8ecba59cbe560521289e282beccbe6934 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Thu, 13 Jan 2022 10:14:26 +0100 Subject: [PATCH] Introduce go-to-variable from `tfvars` files (#727) * collect origins for tfvars files * add new state.Module fields for vars * use new module fields for tfvars * removed unused target refs from vars decoder * move vars reference decoding into new module operation * add test for new module state functions * review feedback for test --- internal/decoder/decoder.go | 9 +- internal/langserver/handlers/did_change.go | 4 + internal/langserver/handlers/did_open.go | 1 + internal/state/module.go | 51 +++++++++++ internal/state/module_test.go | 85 +++++++++++++++++++ internal/terraform/module/module_loader.go | 9 ++ internal/terraform/module/module_ops.go | 37 +++++++- .../terraform/module/operation/operation.go | 1 + internal/terraform/module/walker.go | 4 + internal/terraform/module/watcher.go | 2 + 10 files changed, 193 insertions(+), 10 deletions(-) diff --git a/internal/decoder/decoder.go b/internal/decoder/decoder.go index 0fad02309..d073d4cc9 100644 --- a/internal/decoder/decoder.go +++ b/internal/decoder/decoder.go @@ -63,19 +63,14 @@ func varsPathContext(mod *state.Module) (*decoder.PathContext, error) { Schema: schema, ReferenceOrigins: make(reference.Origins, 0), ReferenceTargets: make(reference.Targets, 0), - Files: make(map[string]*hcl.File, 0), + Files: make(map[string]*hcl.File), } - for _, origin := range mod.RefOrigins { + for _, origin := range mod.VarsRefOrigins { if ast.IsVarsFilename(origin.OriginRange().Filename) { pathCtx.ReferenceOrigins = append(pathCtx.ReferenceOrigins, origin) } } - for _, target := range mod.RefTargets { - if target.RangePtr != nil && ast.IsVarsFilename(target.RangePtr.Filename) { - pathCtx.ReferenceTargets = append(pathCtx.ReferenceTargets, target) - } - } for name, f := range mod.ParsedVarsFiles { pathCtx.Files[name.String()] = f diff --git a/internal/langserver/handlers/did_change.go b/internal/langserver/handlers/did_change.go index 495ce4a1b..dc1f770d7 100644 --- a/internal/langserver/handlers/did_change.go +++ b/internal/langserver/handlers/did_change.go @@ -79,6 +79,10 @@ func TextDocumentDidChange(ctx context.Context, params lsp.DidChangeTextDocument if err != nil { return err } + err = modMgr.EnqueueModuleOp(mod.Path, op.OpTypeDecodeVarsReferences, nil) + if err != nil { + return err + } return nil } diff --git a/internal/langserver/handlers/did_open.go b/internal/langserver/handlers/did_open.go index a324d6f64..b8e6f3e4e 100644 --- a/internal/langserver/handlers/did_open.go +++ b/internal/langserver/handlers/did_open.go @@ -51,6 +51,7 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe modMgr.EnqueueModuleOp(mod.Path, op.OpTypeLoadModuleMetadata, nil) modMgr.EnqueueModuleOp(mod.Path, op.OpTypeDecodeReferenceTargets, nil) modMgr.EnqueueModuleOp(mod.Path, op.OpTypeDecodeReferenceOrigins, nil) + modMgr.EnqueueModuleOp(mod.Path, op.OpTypeDecodeVarsReferences, nil) if mod.TerraformVersionState == op.OpStateUnknown { modMgr.EnqueueModuleOp(mod.Path, op.OpTypeGetTerraformVersion, nil) diff --git a/internal/state/module.go b/internal/state/module.go index f6a9d38cf..51466b00c 100644 --- a/internal/state/module.go +++ b/internal/state/module.go @@ -93,6 +93,10 @@ type Module struct { RefOriginsErr error RefOriginsState op.OpState + VarsRefOrigins reference.Origins + VarsRefOriginsErr error + VarsRefOriginsState op.OpState + ParsedModuleFiles ast.ModFiles ParsedVarsFiles ast.VarsFiles ModuleParsingErr error @@ -135,6 +139,10 @@ func (m *Module) Copy() *Module { RefOriginsErr: m.RefOriginsErr, RefOriginsState: m.RefOriginsState, + VarsRefOrigins: m.VarsRefOrigins.Copy(), + VarsRefOriginsErr: m.VarsRefOriginsErr, + VarsRefOriginsState: m.VarsRefOriginsState, + ModuleParsingErr: m.ModuleParsingErr, VarsParsingErr: m.VarsParsingErr, ModuleParsingState: m.ModuleParsingState, @@ -830,3 +838,46 @@ func (s *ModuleStore) UpdateReferenceOrigins(path string, origins reference.Orig txn.Commit() return nil } + +func (s *ModuleStore) SetVarsReferenceOriginsState(path string, state op.OpState) error { + txn := s.db.Txn(true) + defer txn.Abort() + + mod, err := moduleCopyByPath(txn, path) + if err != nil { + return err + } + + mod.VarsRefOriginsState = state + err = txn.Insert(s.tableName, mod) + if err != nil { + return err + } + + txn.Commit() + return nil +} + +func (s *ModuleStore) UpdateVarsReferenceOrigins(path string, origins reference.Origins, roErr error) error { + txn := s.db.Txn(true) + txn.Defer(func() { + s.SetVarsReferenceOriginsState(path, op.OpStateLoaded) + }) + defer txn.Abort() + + mod, err := moduleCopyByPath(txn, path) + if err != nil { + return err + } + + mod.VarsRefOrigins = origins + mod.VarsRefOriginsErr = roErr + + err = txn.Insert(s.tableName, mod) + if err != nil { + return err + } + + txn.Commit() + return nil +} diff --git a/internal/state/module_test.go b/internal/state/module_test.go index f27b91934..33ded925b 100644 --- a/internal/state/module_test.go +++ b/internal/state/module_test.go @@ -7,6 +7,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/go-version" + "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/reference" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclparse" "github.com/hashicorp/hcl/v2/hclsyntax" @@ -15,6 +17,7 @@ import ( "github.com/hashicorp/terraform-ls/internal/terraform/module/operation" tfaddr "github.com/hashicorp/terraform-registry-address" tfmod "github.com/hashicorp/terraform-schema/module" + "github.com/zclconf/go-cty/cty" ) func TestModuleStore_Add_duplicate(t *testing.T) { @@ -508,6 +511,88 @@ dev = { } } +func TestModuleStore_SetVarsReferenceOriginsState(t *testing.T) { + s, err := NewStateStore() + if err != nil { + t.Fatal(err) + } + + tmpDir := t.TempDir() + err = s.Modules.Add(tmpDir) + if err != nil { + t.Fatal(err) + } + + s.Modules.SetVarsReferenceOriginsState(tmpDir, operation.OpStateQueued) + + mod, err := s.Modules.ModuleByPath(tmpDir) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(mod.VarsRefOriginsState, operation.OpStateQueued, cmpOpts); diff != "" { + t.Fatalf("unexpected module vars ref origins state: %s", diff) + } +} + +func TestModuleStore_UpdateVarsReferenceOrigins(t *testing.T) { + s, err := NewStateStore() + if err != nil { + t.Fatal(err) + } + + tmpDir := t.TempDir() + err = s.Modules.Add(tmpDir) + if err != nil { + t.Fatal(err) + } + + origins := reference.Origins{ + reference.PathOrigin{ + Range: hcl.Range{ + Filename: "terraform.tfvars", + Start: hcl.Pos{ + Line: 1, + Column: 1, + Byte: 0, + }, + End: hcl.Pos{ + Line: 1, + Column: 5, + Byte: 4, + }, + }, + TargetAddr: lang.Address{ + lang.RootStep{Name: "var"}, + lang.AttrStep{Name: "name"}, + }, + TargetPath: lang.Path{ + Path: tmpDir, + LanguageID: "terraform", + }, + Constraints: reference.OriginConstraints{ + reference.OriginConstraint{ + OfScopeId: "variable", + OfType: cty.String, + }, + }, + }, + } + s.Modules.UpdateVarsReferenceOrigins(tmpDir, origins, nil) + + mod, err := s.Modules.ModuleByPath(tmpDir) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(mod.VarsRefOrigins, origins, cmpOpts); diff != "" { + t.Fatalf("unexpected module vars ref origins: %s", diff) + } + if diff := cmp.Diff(mod.VarsRefOriginsState, operation.OpStateLoaded, cmpOpts); diff != "" { + t.Fatalf("unexpected module vars ref origins state: %s", diff) + } +} + func BenchmarkModuleByPath(b *testing.B) { s, err := NewStateStore() if err != nil { diff --git a/internal/terraform/module/module_loader.go b/internal/terraform/module/module_loader.go index 424cb6d3f..a0349e3fb 100644 --- a/internal/terraform/module/module_loader.go +++ b/internal/terraform/module/module_loader.go @@ -164,6 +164,11 @@ func (ml *moduleLoader) executeModuleOp(ctx context.Context, modOp ModuleOperati if opErr != nil { ml.logger.Printf("failed to decode reference origins: %s", opErr) } + case op.OpTypeDecodeVarsReferences: + opErr = DecodeVarsReferences(ctx, ml.modStore, ml.schemaStore, modOp.ModulePath) + if opErr != nil { + ml.logger.Printf("failed to decode vars references: %s", opErr) + } default: ml.logger.Printf("%s: unknown operation (%#v) for module operation", modOp.ModulePath, modOp.Type) @@ -207,6 +212,8 @@ func (ml *moduleLoader) EnqueueModuleOp(modOp ModuleOperation) error { ml.modStore.SetReferenceTargetsState(modOp.ModulePath, op.OpStateQueued) case op.OpTypeDecodeReferenceOrigins: ml.modStore.SetReferenceOriginsState(modOp.ModulePath, op.OpStateQueued) + case op.OpTypeDecodeVarsReferences: + ml.modStore.SetVarsReferenceOriginsState(modOp.ModulePath, op.OpStateQueued) } ml.queue.PushOp(modOp) @@ -233,6 +240,8 @@ func operationState(mod *state.Module, opType op.OpType) op.OpState { return mod.RefTargetsState case op.OpTypeDecodeReferenceOrigins: return mod.RefOriginsState + case op.OpTypeDecodeVarsReferences: + return mod.VarsRefOriginsState } return op.OpStateUnknown } diff --git a/internal/terraform/module/module_ops.go b/internal/terraform/module/module_ops.go index 47690e40f..8e5f4471b 100644 --- a/internal/terraform/module/module_ops.go +++ b/internal/terraform/module/module_ops.go @@ -299,17 +299,20 @@ func DecodeReferenceOrigins(ctx context.Context, modStore *state.ModuleStore, sc return err } - d, err := decoder.NewDecoder(ctx, &decoder.PathReader{ + d := decoder.NewDecoder(ctx, &decoder.PathReader{ ModuleReader: modStore, SchemaReader: schemaReader, - }).Path(lang.Path{ + }) + + moduleDecoder, err := d.Path(lang.Path{ Path: modPath, LanguageID: ilsp.Terraform.String(), }) if err != nil { return err } - origins, rErr := d.CollectReferenceOrigins() + + origins, rErr := moduleDecoder.CollectReferenceOrigins() sErr := modStore.UpdateReferenceOrigins(modPath, origins, rErr) if sErr != nil { @@ -318,3 +321,31 @@ func DecodeReferenceOrigins(ctx context.Context, modStore *state.ModuleStore, sc return rErr } + +func DecodeVarsReferences(ctx context.Context, modStore *state.ModuleStore, schemaReader state.SchemaReader, modPath string) error { + err := modStore.SetVarsReferenceOriginsState(modPath, op.OpStateLoading) + if err != nil { + return err + } + + d := decoder.NewDecoder(ctx, &decoder.PathReader{ + ModuleReader: modStore, + SchemaReader: schemaReader, + }) + + varsDecoder, err := d.Path(lang.Path{ + Path: modPath, + LanguageID: ilsp.Tfvars.String(), + }) + if err != nil { + return err + } + + origins, rErr := varsDecoder.CollectReferenceOrigins() + sErr := modStore.UpdateVarsReferenceOrigins(modPath, origins, rErr) + if sErr != nil { + return sErr + } + + return rErr +} diff --git a/internal/terraform/module/operation/operation.go b/internal/terraform/module/operation/operation.go index 218d0fed8..4016a9b9e 100644 --- a/internal/terraform/module/operation/operation.go +++ b/internal/terraform/module/operation/operation.go @@ -23,4 +23,5 @@ const ( OpTypeLoadModuleMetadata OpTypeDecodeReferenceTargets OpTypeDecodeReferenceOrigins + OpTypeDecodeVarsReferences ) diff --git a/internal/terraform/module/walker.go b/internal/terraform/module/walker.go index 93a50b6d6..2616a12ff 100644 --- a/internal/terraform/module/walker.go +++ b/internal/terraform/module/walker.go @@ -295,6 +295,10 @@ func (w *Walker) walk(ctx context.Context, rootPath string) error { if err != nil { return err } + err = w.modMgr.EnqueueModuleOp(dir, op.OpTypeDecodeVarsReferences, nil) + if err != nil { + return err + } } if dataDir.PluginLockFilePath != "" { diff --git a/internal/terraform/module/watcher.go b/internal/terraform/module/watcher.go index cafcdc4b9..670e9107c 100644 --- a/internal/terraform/module/watcher.go +++ b/internal/terraform/module/watcher.go @@ -250,6 +250,7 @@ func decodeCalledModulesFunc(modMgr ModuleManager, w Watcher, modPath string) De modMgr.EnqueueModuleOp(mc.Path, op.OpTypeParseVariables, nil) modMgr.EnqueueModuleOp(mc.Path, op.OpTypeDecodeReferenceTargets, nil) modMgr.EnqueueModuleOp(mc.Path, op.OpTypeDecodeReferenceOrigins, nil) + modMgr.EnqueueModuleOp(mc.Path, op.OpTypeDecodeVarsReferences, nil) if w != nil { w.AddModule(mc.Path) @@ -258,6 +259,7 @@ func decodeCalledModulesFunc(modMgr ModuleManager, w Watcher, modPath string) De modMgr.EnqueueModuleOp(modPath, op.OpTypeDecodeReferenceTargets, nil) modMgr.EnqueueModuleOp(modPath, op.OpTypeDecodeReferenceOrigins, nil) + modMgr.EnqueueModuleOp(modPath, op.OpTypeDecodeVarsReferences, nil) } }