diff --git a/internal/decoder/decoder.go b/internal/decoder/decoder.go index 74247e4e6..e951e7cf5 100644 --- a/internal/decoder/decoder.go +++ b/internal/decoder/decoder.go @@ -7,11 +7,9 @@ import ( "context" "github.com/hashicorp/hcl-lang/decoder" - "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/reference" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/terraform-ls/internal/codelens" - "github.com/hashicorp/terraform-ls/internal/decoder/validations" ilsp "github.com/hashicorp/terraform-ls/internal/lsp" lsp "github.com/hashicorp/terraform-ls/internal/protocol" "github.com/hashicorp/terraform-ls/internal/state" @@ -93,10 +91,5 @@ func DecoderContext(ctx context.Context) decoder.DecoderContext { } } - validations := []lang.ValidationFunc{ - validations.UnreferencedOrigins, - } - dCtx.Validations = append(dCtx.Validations, validations...) - return dCtx } diff --git a/internal/decoder/validations/unreferenced_origin.go b/internal/decoder/validations/unreferenced_origin.go index e8871153a..ad5f550d0 100644 --- a/internal/decoder/validations/unreferenced_origin.go +++ b/internal/decoder/validations/unreferenced_origin.go @@ -13,14 +13,9 @@ import ( "github.com/hashicorp/hcl/v2" ) -func UnreferencedOrigins(ctx context.Context) lang.DiagnosticsMap { +func UnreferencedOrigins(ctx context.Context, pathCtx *decoder.PathContext) lang.DiagnosticsMap { diagsMap := make(lang.DiagnosticsMap) - pathCtx, err := decoder.PathCtx(ctx) - if err != nil { - return diagsMap - } - for _, origin := range pathCtx.ReferenceOrigins { matchableOrigin, ok := origin.(reference.MatchableOrigin) if !ok { diff --git a/internal/decoder/validations/unreferenced_origin_test.go b/internal/decoder/validations/unreferenced_origin_test.go index 420d4f243..f36b6122d 100644 --- a/internal/decoder/validations/unreferenced_origin_test.go +++ b/internal/decoder/validations/unreferenced_origin_test.go @@ -109,9 +109,7 @@ func TestUnreferencedOrigins(t *testing.T) { ReferenceOrigins: tt.origins, } - ctx = decoder.WithPathContext(ctx, pathCtx) - - diags := UnreferencedOrigins(ctx) + diags := UnreferencedOrigins(ctx, pathCtx) if diff := cmp.Diff(tt.want["test.tf"], diags["test.tf"]); diff != "" { t.Fatalf("unexpected diagnostics: %s", diff) } diff --git a/internal/indexer/document_change.go b/internal/indexer/document_change.go index 74c68c33f..500dcfee5 100644 --- a/internal/indexer/document_change.go +++ b/internal/indexer/document_change.go @@ -121,6 +121,20 @@ func (idx *Indexer) decodeModule(ctx context.Context, modHandle document.DirHand } ids = append(ids, metaId) + // TODO! check if early validation setting is enabled + _, err = idx.jobStore.EnqueueJob(ctx, job.Job{ + Dir: modHandle, + Func: func(ctx context.Context) error { + return module.SchemaValidation(ctx, idx.modStore, idx.schemaStore, modHandle.Path()) + }, + Type: op.OpTypeSchemaValidation.String(), + DependsOn: job.IDs{metaId}, + IgnoreState: ignoreState, + }) + if err != nil { + return ids, err + } + refTargetsId, err := idx.jobStore.EnqueueJob(ctx, job.Job{ Dir: modHandle, Func: func(ctx context.Context) error { @@ -135,12 +149,13 @@ func (idx *Indexer) decodeModule(ctx context.Context, modHandle document.DirHand } ids = append(ids, refTargetsId) + // TODO! check if early validation setting is enabled _, err = idx.jobStore.EnqueueJob(ctx, job.Job{ Dir: modHandle, Func: func(ctx context.Context) error { - return module.EarlyValidation(ctx, idx.modStore, idx.schemaStore, modHandle.Path()) + return module.ReferenceValidation(ctx, idx.modStore, idx.schemaStore, modHandle.Path()) }, - Type: op.OpTypeEarlyValidation.String(), + Type: op.OpTypeReferenceValidation.String(), DependsOn: job.IDs{metaId, refTargetsId}, IgnoreState: ignoreState, }) diff --git a/internal/langserver/diagnostics/diagnostics.go b/internal/langserver/diagnostics/diagnostics.go index 163f62149..b8fb2e8ed 100644 --- a/internal/langserver/diagnostics/diagnostics.go +++ b/internal/langserver/diagnostics/diagnostics.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/hcl/v2" ilsp "github.com/hashicorp/terraform-ls/internal/lsp" lsp "github.com/hashicorp/terraform-ls/internal/protocol" + "github.com/hashicorp/terraform-ls/internal/terraform/ast" "github.com/hashicorp/terraform-ls/internal/uri" ) @@ -21,8 +22,6 @@ type diagContext struct { diags []lsp.Diagnostic } -type DiagnosticSource string - type ClientNotifier interface { Notify(ctx context.Context, method string, params interface{}) error } @@ -61,7 +60,7 @@ func (n *Notifier) PublishHCLDiags(ctx context.Context, dirPath string, diags Di for filename, ds := range diags { fileDiags := make([]lsp.Diagnostic, 0) for source, diags := range ds { - fileDiags = append(fileDiags, ilsp.HCLDiagsToLSP(diags, string(source))...) + fileDiags = append(fileDiags, ilsp.HCLDiagsToLSP(diags, source.String())...) } n.diags <- diagContext{ @@ -83,7 +82,7 @@ func (n *Notifier) notify() { } } -type Diagnostics map[string]map[DiagnosticSource]hcl.Diagnostics +type Diagnostics map[string]map[ast.DiagnosticSource]hcl.Diagnostics func NewDiagnostics() Diagnostics { return make(Diagnostics, 0) @@ -92,16 +91,16 @@ func NewDiagnostics() Diagnostics { // EmptyRootDiagnostic allows emptying any diagnostics for // the whole directory which were published previously. func (d Diagnostics) EmptyRootDiagnostic() Diagnostics { - d[""] = make(map[DiagnosticSource]hcl.Diagnostics, 0) + d[""] = make(map[ast.DiagnosticSource]hcl.Diagnostics, 0) return d } -func (d Diagnostics) Append(src string, diagsMap map[string]hcl.Diagnostics) Diagnostics { +func (d Diagnostics) Append(src ast.DiagnosticSource, diagsMap map[string]hcl.Diagnostics) Diagnostics { for uri, uriDiags := range diagsMap { if _, ok := d[uri]; !ok { - d[uri] = make(map[DiagnosticSource]hcl.Diagnostics, 0) + d[uri] = make(map[ast.DiagnosticSource]hcl.Diagnostics, 0) } - d[uri][DiagnosticSource(src)] = uriDiags + d[uri][src] = uriDiags } return d diff --git a/internal/langserver/diagnostics/diagnostics_test.go b/internal/langserver/diagnostics/diagnostics_test.go index 4a91f5a78..d3c95b680 100644 --- a/internal/langserver/diagnostics/diagnostics_test.go +++ b/internal/langserver/diagnostics/diagnostics_test.go @@ -11,6 +11,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/terraform-ls/internal/terraform/ast" ) var discardLogger = log.New(ioutil.Discard, "", 0) @@ -19,8 +20,8 @@ func TestDiags_Closes(t *testing.T) { n := NewNotifier(noopNotifier{}, discardLogger) diags := NewDiagnostics() - diags.Append("test", map[string]hcl.Diagnostics{ - "test": { + diags.Append(ast.HCLParsingSource, map[string]hcl.Diagnostics{ + ast.HCLParsingSource.String(): { { Severity: hcl.DiagError, }, @@ -46,8 +47,8 @@ func TestPublish_DoesNotSendAfterClose(t *testing.T) { n := NewNotifier(noopNotifier{}, discardLogger) diags := NewDiagnostics() - diags.Append("test", map[string]hcl.Diagnostics{ - "test": { + diags.Append(ast.TerraformValidateSource, map[string]hcl.Diagnostics{ + ast.TerraformValidateSource.String(): { { Severity: hcl.DiagError, }, @@ -61,7 +62,7 @@ func TestPublish_DoesNotSendAfterClose(t *testing.T) { func TestDiagnostics_Append(t *testing.T) { diags := NewDiagnostics() - diags.Append("foo", map[string]hcl.Diagnostics{ + diags.Append(ast.HCLParsingSource, map[string]hcl.Diagnostics{ "first.tf": { &hcl.Diagnostic{ Severity: hcl.DiagError, @@ -79,7 +80,7 @@ func TestDiagnostics_Append(t *testing.T) { }, }, }) - diags.Append("bar", map[string]hcl.Diagnostics{ + diags.Append(ast.SchemaValidationSource, map[string]hcl.Diagnostics{ "first.tf": { &hcl.Diagnostic{ Severity: hcl.DiagError, @@ -96,8 +97,8 @@ func TestDiagnostics_Append(t *testing.T) { }) expectedDiags := Diagnostics{ - "first.tf": map[DiagnosticSource]hcl.Diagnostics{ - DiagnosticSource("foo"): { + "first.tf": map[ast.DiagnosticSource]hcl.Diagnostics{ + ast.HCLParsingSource: { &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Something went wrong", @@ -113,7 +114,7 @@ func TestDiagnostics_Append(t *testing.T) { }, }, }, - DiagnosticSource("bar"): { + ast.SchemaValidationSource: { &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Something else went wrong", @@ -121,8 +122,8 @@ func TestDiagnostics_Append(t *testing.T) { }, }, }, - "second.tf": map[DiagnosticSource]hcl.Diagnostics{ - DiagnosticSource("bar"): { + "second.tf": map[ast.DiagnosticSource]hcl.Diagnostics{ + ast.SchemaValidationSource: { &hcl.Diagnostic{ Severity: hcl.DiagWarning, Summary: "Beware", diff --git a/internal/langserver/handlers/command/validate.go b/internal/langserver/handlers/command/validate.go index 8ad9d4265..5b61e2237 100644 --- a/internal/langserver/handlers/command/validate.go +++ b/internal/langserver/handlers/command/validate.go @@ -8,14 +8,11 @@ import ( "fmt" "github.com/creachadair/jrpc2" - lsctx "github.com/hashicorp/terraform-ls/internal/context" "github.com/hashicorp/terraform-ls/internal/document" + "github.com/hashicorp/terraform-ls/internal/job" "github.com/hashicorp/terraform-ls/internal/langserver/cmd" - "github.com/hashicorp/terraform-ls/internal/langserver/diagnostics" - "github.com/hashicorp/terraform-ls/internal/langserver/errors" - "github.com/hashicorp/terraform-ls/internal/langserver/progress" - "github.com/hashicorp/terraform-ls/internal/state" "github.com/hashicorp/terraform-ls/internal/terraform/module" + op "github.com/hashicorp/terraform-ls/internal/terraform/module/operation" "github.com/hashicorp/terraform-ls/internal/uri" ) @@ -31,51 +28,17 @@ func (h *CmdHandler) TerraformValidateHandler(ctx context.Context, args cmd.Comm dirHandle := document.DirHandleFromURI(dirUri) - mod, err := h.StateStore.Modules.ModuleByPath(dirHandle.Path()) - if err != nil { - if state.IsModuleNotFound(err) { - err = h.StateStore.Modules.Add(dirHandle.Path()) - if err != nil { - return nil, err - } - mod, err = h.StateStore.Modules.ModuleByPath(dirHandle.Path()) - if err != nil { - return nil, err - } - } else { - return nil, err - } - } - - tfExec, err := module.TerraformExecutorForModule(ctx, mod.Path) - if err != nil { - return nil, errors.EnrichTfExecError(err) - } - - notifier, err := lsctx.DiagnosticsNotifier(ctx) + id, err := h.StateStore.JobStore.EnqueueJob(ctx, job.Job{ + Dir: dirHandle, + Func: func(ctx context.Context) error { + return module.TerraformValidate(ctx, h.StateStore.Modules, dirHandle.Path()) + }, + Type: op.OpTypeTerraformValidate.String(), + IgnoreState: true, + }) if err != nil { return nil, err } - progress.Begin(ctx, "Validating") - defer func() { - progress.End(ctx, "Finished") - }() - progress.Report(ctx, "Running terraform validate ...") - jsonDiags, err := tfExec.Validate(ctx) - if err != nil { - return nil, err - } - - diags := diagnostics.NewDiagnostics() - validateDiags := diagnostics.HCLDiagsFromJSON(jsonDiags) - diags.EmptyRootDiagnostic() - diags.Append("terraform validate", validateDiags) - diags.Append("early validation", mod.ValidationDiagnostics) - diags.Append("HCL", mod.ModuleDiagnostics.AutoloadedOnly().AsMap()) - diags.Append("HCL", mod.VarsDiagnostics.AutoloadedOnly().AsMap()) - - notifier.PublishHCLDiags(ctx, mod.Path, diags) - - return nil, nil + return nil, h.StateStore.JobStore.WaitForJobs(ctx, id) } diff --git a/internal/langserver/handlers/hooks_module.go b/internal/langserver/handlers/hooks_module.go index 76ff0b7ff..a1bb67506 100644 --- a/internal/langserver/handlers/hooks_module.go +++ b/internal/langserver/handlers/hooks_module.go @@ -153,13 +153,14 @@ func updateDiagnostics(dNotifier *diagnostics.Notifier) notifier.Hook { diags := diagnostics.NewDiagnostics() diags.EmptyRootDiagnostic() - defer dNotifier.PublishHCLDiags(ctx, mod.Path, diags) - - if mod != nil { - diags.Append("early validation", mod.ValidationDiagnostics) - diags.Append("HCL", mod.ModuleDiagnostics.AutoloadedOnly().AsMap()) - diags.Append("HCL", mod.VarsDiagnostics.AutoloadedOnly().AsMap()) + for source, dm := range mod.ModuleDiagnostics { + diags.Append(source, dm.AutoloadedOnly().AsMap()) + } + for source, dm := range mod.VarsDiagnostics { + diags.Append(source, dm.AutoloadedOnly().AsMap()) } + + dNotifier.PublishHCLDiags(ctx, mod.Path, diags) } return nil } diff --git a/internal/state/module.go b/internal/state/module.go index c9f3f986e..b06765b36 100644 --- a/internal/state/module.go +++ b/internal/state/module.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/go-memdb" "github.com/hashicorp/go-version" - "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/reference" "github.com/hashicorp/hcl/v2" tfaddr "github.com/hashicorp/terraform-registry-address" @@ -123,22 +122,19 @@ type Module struct { VarsRefOriginsErr error VarsRefOriginsState op.OpState - ParsedModuleFiles ast.ModFiles - ParsedVarsFiles ast.VarsFiles - ModuleParsingErr error - VarsParsingErr error - ModuleParsingState op.OpState - VarsParsingState op.OpState + ParsedModuleFiles ast.ModFiles + ParsedVarsFiles ast.VarsFiles + ModuleParsingErr error + VarsParsingErr error Meta ModuleMetadata MetaErr error MetaState op.OpState - ModuleDiagnostics ast.ModDiags - VarsDiagnostics ast.VarsDiags - - ValidationDiagnostics lang.DiagnosticsMap - ValidationDiagnosticsState op.OpState + ModuleDiagnostics ast.SourceModDiags + ModuleDiagnosticsState ast.DiagnosticSourceState + VarsDiagnostics ast.SourceVarsDiags + VarsDiagnosticsState ast.DiagnosticSourceState } func (m *Module) Copy() *Module { @@ -177,16 +173,15 @@ func (m *Module) Copy() *Module { VarsRefOriginsErr: m.VarsRefOriginsErr, VarsRefOriginsState: m.VarsRefOriginsState, - ModuleParsingErr: m.ModuleParsingErr, - VarsParsingErr: m.VarsParsingErr, - ModuleParsingState: m.ModuleParsingState, - VarsParsingState: m.VarsParsingState, - - ValidationDiagnosticsState: m.ValidationDiagnosticsState, + ModuleParsingErr: m.ModuleParsingErr, + VarsParsingErr: m.VarsParsingErr, Meta: m.Meta.Copy(), MetaErr: m.MetaErr, MetaState: m.MetaState, + + ModuleDiagnosticsState: m.ModuleDiagnosticsState.Copy(), + VarsDiagnosticsState: m.VarsDiagnosticsState.Copy(), } if m.InstalledProviders != nil { @@ -214,26 +209,28 @@ func (m *Module) Copy() *Module { } if m.ModuleDiagnostics != nil { - newMod.ModuleDiagnostics = make(ast.ModDiags, len(m.ModuleDiagnostics)) - for name, diags := range m.ModuleDiagnostics { - newMod.ModuleDiagnostics[name] = make(hcl.Diagnostics, len(diags)) - copy(newMod.ModuleDiagnostics[name], diags) - } - } + newMod.ModuleDiagnostics = make(ast.SourceModDiags, len(m.ModuleDiagnostics)) + + for source, modDiags := range m.ModuleDiagnostics { + newMod.ModuleDiagnostics[source] = make(ast.ModDiags, len(modDiags)) - if m.ValidationDiagnostics != nil { - newMod.ValidationDiagnostics = make(lang.DiagnosticsMap, len(m.ValidationDiagnostics)) - for name, diags := range m.ValidationDiagnostics { - newMod.ValidationDiagnostics[name] = make(hcl.Diagnostics, len(diags)) - copy(newMod.ValidationDiagnostics[name], diags) + for name, diags := range modDiags { + newMod.ModuleDiagnostics[source][name] = make(hcl.Diagnostics, len(diags)) + copy(newMod.ModuleDiagnostics[source][name], diags) + } } } if m.VarsDiagnostics != nil { - newMod.VarsDiagnostics = make(ast.VarsDiags, len(m.VarsDiagnostics)) - for name, diags := range m.VarsDiagnostics { - newMod.VarsDiagnostics[name] = make(hcl.Diagnostics, len(diags)) - copy(newMod.VarsDiagnostics[name], diags) + newMod.VarsDiagnostics = make(ast.SourceVarsDiags, len(m.VarsDiagnostics)) + + for source, varsDiags := range m.VarsDiagnostics { + newMod.VarsDiagnostics[source] = make(ast.VarsDiags, len(varsDiags)) + + for name, diags := range varsDiags { + newMod.VarsDiagnostics[source][name] = make(hcl.Diagnostics, len(diags)) + copy(newMod.VarsDiagnostics[source][name], diags) + } } } @@ -249,9 +246,19 @@ func newModule(modPath string) *Module { PreloadEmbeddedSchemaState: op.OpStateUnknown, InstalledProvidersState: op.OpStateUnknown, RefTargetsState: op.OpStateUnknown, - ModuleParsingState: op.OpStateUnknown, MetaState: op.OpStateUnknown, - ValidationDiagnosticsState: op.OpStateUnknown, + ModuleDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: op.OpStateUnknown, + ast.SchemaValidationSource: op.OpStateUnknown, + ast.ReferenceValidationSource: op.OpStateUnknown, + ast.TerraformValidateSource: op.OpStateUnknown, + }, + VarsDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: op.OpStateUnknown, + ast.SchemaValidationSource: op.OpStateUnknown, + ast.ReferenceValidationSource: op.OpStateUnknown, + ast.TerraformValidateSource: op.OpStateUnknown, + }, } } @@ -824,49 +831,8 @@ func (s *ModuleStore) UpdateTerraformAndProviderVersions(modPath string, tfVer * return nil } -func (s *ModuleStore) SetModuleParsingState(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.ModuleParsingState = state - err = txn.Insert(s.tableName, mod) - if err != nil { - return err - } - - txn.Commit() - return nil -} - -func (s *ModuleStore) SetVarsParsingState(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.VarsParsingState = state - err = txn.Insert(s.tableName, mod) - if err != nil { - return err - } - - txn.Commit() - return nil -} - func (s *ModuleStore) UpdateParsedModuleFiles(path string, pFiles ast.ModFiles, pErr error) error { txn := s.db.Txn(true) - txn.Defer(func() { - s.SetModuleParsingState(path, op.OpStateLoaded) - }) defer txn.Abort() mod, err := moduleCopyByPath(txn, path) @@ -889,9 +855,6 @@ func (s *ModuleStore) UpdateParsedModuleFiles(path string, pFiles ast.ModFiles, func (s *ModuleStore) UpdateParsedVarsFiles(path string, vFiles ast.VarsFiles, vErr error) error { txn := s.db.Txn(true) - txn.Defer(func() { - s.SetVarsParsingState(path, op.OpStateLoaded) - }) defer txn.Abort() mod, err := moduleCopyByPath(txn, path) @@ -971,8 +934,11 @@ func (s *ModuleStore) UpdateMetadata(path string, meta *tfmod.Meta, mErr error) return nil } -func (s *ModuleStore) UpdateModuleDiagnostics(path string, diags ast.ModDiags) error { +func (s *ModuleStore) UpdateModuleDiagnostics(path string, source ast.DiagnosticSource, diags ast.ModDiags) error { txn := s.db.Txn(true) + txn.Defer(func() { + s.SetModuleDiagnosticsState(path, source, op.OpStateLoaded) + }) defer txn.Abort() oldMod, err := moduleByPath(txn, path) @@ -981,7 +947,10 @@ func (s *ModuleStore) UpdateModuleDiagnostics(path string, diags ast.ModDiags) e } mod := oldMod.Copy() - mod.ModuleDiagnostics = diags + if mod.ModuleDiagnostics == nil { + mod.ModuleDiagnostics = make(ast.SourceModDiags) + } + mod.ModuleDiagnostics[source] = diags err = txn.Insert(s.tableName, mod) if err != nil { @@ -997,7 +966,7 @@ func (s *ModuleStore) UpdateModuleDiagnostics(path string, diags ast.ModDiags) e return nil } -func (s *ModuleStore) SetValidationDiagnosticsState(path string, state op.OpState) error { +func (s *ModuleStore) SetModuleDiagnosticsState(path string, source ast.DiagnosticSource, state op.OpState) error { txn := s.db.Txn(true) defer txn.Abort() @@ -1005,8 +974,8 @@ func (s *ModuleStore) SetValidationDiagnosticsState(path string, state op.OpStat if err != nil { return err } + mod.ModuleDiagnosticsState[source] = state - mod.ValidationDiagnosticsState = state err = txn.Insert(s.tableName, mod) if err != nil { return err @@ -1016,10 +985,10 @@ func (s *ModuleStore) SetValidationDiagnosticsState(path string, state op.OpStat return nil } -func (s *ModuleStore) UpdateValidateDiagnostics(path string, diags lang.DiagnosticsMap) error { +func (s *ModuleStore) UpdateVarsDiagnostics(path string, source ast.DiagnosticSource, diags ast.VarsDiags) error { txn := s.db.Txn(true) txn.Defer(func() { - s.SetValidationDiagnosticsState(path, op.OpStateLoaded) + s.SetVarsDiagnosticsState(path, ast.HCLParsingSource, op.OpStateLoaded) }) defer txn.Abort() @@ -1029,7 +998,10 @@ func (s *ModuleStore) UpdateValidateDiagnostics(path string, diags lang.Diagnost } mod := oldMod.Copy() - mod.ValidationDiagnostics = diags + if mod.VarsDiagnostics == nil { + mod.VarsDiagnostics = make(ast.SourceVarsDiags) + } + mod.VarsDiagnostics[source] = diags err = txn.Insert(s.tableName, mod) if err != nil { @@ -1045,28 +1017,21 @@ func (s *ModuleStore) UpdateValidateDiagnostics(path string, diags lang.Diagnost return nil } -func (s *ModuleStore) UpdateVarsDiagnostics(path string, diags ast.VarsDiags) error { +func (s *ModuleStore) SetVarsDiagnosticsState(path string, source ast.DiagnosticSource, state op.OpState) error { txn := s.db.Txn(true) defer txn.Abort() - oldMod, err := moduleByPath(txn, path) + mod, err := moduleCopyByPath(txn, path) if err != nil { return err } - - mod := oldMod.Copy() - mod.VarsDiagnostics = diags + mod.VarsDiagnosticsState[source] = state err = txn.Insert(s.tableName, mod) if err != nil { return err } - err = s.queueModuleChange(txn, oldMod, mod) - if err != nil { - return err - } - txn.Commit() return nil } diff --git a/internal/state/module_changes.go b/internal/state/module_changes.go index c94e07d60..7bac6b2ab 100644 --- a/internal/state/module_changes.go +++ b/internal/state/module_changes.go @@ -142,10 +142,10 @@ func (s *ModuleStore) queueModuleChange(txn *memdb.Txn, oldMod, newMod *Module) oldDiags, newDiags := 0, 0 if oldMod != nil { - oldDiags = oldMod.ModuleDiagnostics.Count() + oldMod.VarsDiagnostics.Count() + oldMod.ValidationDiagnostics.Count() + oldDiags = oldMod.ModuleDiagnostics.Count() + oldMod.VarsDiagnostics.Count() } if newMod != nil { - newDiags = newMod.ModuleDiagnostics.Count() + newMod.VarsDiagnostics.Count() + newMod.ValidationDiagnostics.Count() + newDiags = newMod.ModuleDiagnostics.Count() + newMod.VarsDiagnostics.Count() } // Comparing diagnostics accurately could be expensive // so we just treat any non-empty diags as a change diff --git a/internal/state/module_test.go b/internal/state/module_test.go index a45bb1c2b..83b9e22bd 100644 --- a/internal/state/module_test.go +++ b/internal/state/module_test.go @@ -77,6 +77,18 @@ func TestModuleStore_ModuleByPath(t *testing.T) { Path: modPath, TerraformVersion: tfVersion, TerraformVersionState: operation.OpStateLoaded, + ModuleDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, + VarsDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, } if diff := cmp.Diff(expectedModule, mod); diff != "" { t.Fatalf("unexpected module: %s", diff) @@ -184,11 +196,35 @@ func TestModuleStore_CallersOfModule(t *testing.T) { Path: filepath.Join(tmpDir, "alpha"), ModManifest: alphaManifest, ModManifestState: operation.OpStateLoaded, + ModuleDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, + VarsDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, }, { Path: filepath.Join(tmpDir, "gamma"), ModManifest: gammaManifest, ModManifestState: operation.OpStateLoaded, + ModuleDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, + VarsDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, }, } @@ -223,9 +259,51 @@ func TestModuleStore_List(t *testing.T) { } expectedModules := []*Module{ - {Path: filepath.Join(tmpDir, "alpha")}, - {Path: filepath.Join(tmpDir, "beta")}, - {Path: filepath.Join(tmpDir, "gamma")}, + { + Path: filepath.Join(tmpDir, "alpha"), + ModuleDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, + VarsDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, + }, + { + Path: filepath.Join(tmpDir, "beta"), + ModuleDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, + VarsDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, + }, + { + Path: filepath.Join(tmpDir, "gamma"), + ModuleDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, + VarsDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, + }, } if diff := cmp.Diff(expectedModules, modules, cmpOpts); diff != "" { @@ -283,6 +361,18 @@ func TestModuleStore_UpdateMetadata(t *testing.T) { }, }, MetaState: operation.OpStateLoaded, + ModuleDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, + VarsDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, } if diff := cmp.Diff(expectedModule, mod, cmpOpts); diff != "" { @@ -319,6 +409,18 @@ func TestModuleStore_UpdateTerraformAndProviderVersions(t *testing.T) { TerraformVersion: testVersion(t, "0.12.4"), TerraformVersionState: operation.OpStateLoaded, TerraformVersionErr: vErr, + ModuleDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, + VarsDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateUnknown, + ast.SchemaValidationSource: operation.OpStateUnknown, + ast.ReferenceValidationSource: operation.OpStateUnknown, + ast.TerraformValidateSource: operation.OpStateUnknown, + }, } if diff := cmp.Diff(expectedModule, mod, cmpOpts); diff != "" { t.Fatalf("unexpected module data: %s", diff) @@ -427,37 +529,42 @@ provider "blah" { region = "london" `), "test.tf") - err = s.Modules.UpdateModuleDiagnostics(tmpDir, ast.ModDiagsFromMap(map[string]hcl.Diagnostics{ + err = s.Modules.UpdateModuleDiagnostics(tmpDir, ast.HCLParsingSource, ast.ModDiagsFromMap(map[string]hcl.Diagnostics{ "test.tf": diags, })) + if err != nil { + t.Fatal(err) + } mod, err := s.Modules.ModuleByPath(tmpDir) if err != nil { t.Fatal(err) } - expectedDiags := ast.ModDiagsFromMap(map[string]hcl.Diagnostics{ - "test.tf": { - { - Severity: hcl.DiagError, - Summary: "Unclosed configuration block", - Detail: "There is no closing brace for this block before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", - Subject: &hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 2, - Column: 17, - Byte: 17, - }, - End: hcl.Pos{ - Line: 2, - Column: 18, - Byte: 18, + expectedDiags := ast.SourceModDiags{ + ast.HCLParsingSource: ast.ModDiagsFromMap(map[string]hcl.Diagnostics{ + "test.tf": { + { + Severity: hcl.DiagError, + Summary: "Unclosed configuration block", + Detail: "There is no closing brace for this block before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", + Subject: &hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 2, + Column: 17, + Byte: 17, + }, + End: hcl.Pos{ + Line: 2, + Column: 18, + Byte: 18, + }, }, }, }, - }, - }) + }), + } if diff := cmp.Diff(expectedDiags, mod.ModuleDiagnostics, cmpOpts); diff != "" { t.Fatalf("unexpected diagnostics: %s", diff) } @@ -481,37 +588,42 @@ dev = { region = "london" `), "test.tfvars") - err = s.Modules.UpdateVarsDiagnostics(tmpDir, ast.VarsDiagsFromMap(map[string]hcl.Diagnostics{ + err = s.Modules.UpdateVarsDiagnostics(tmpDir, ast.HCLParsingSource, ast.VarsDiagsFromMap(map[string]hcl.Diagnostics{ "test.tfvars": diags, })) + if err != nil { + t.Fatal(err) + } mod, err := s.Modules.ModuleByPath(tmpDir) if err != nil { t.Fatal(err) } - expectedDiags := ast.VarsDiagsFromMap(map[string]hcl.Diagnostics{ - "test.tfvars": { - { - Severity: hcl.DiagError, - Summary: "Missing expression", - Detail: "Expected the start of an expression, but found the end of the file.", - Subject: &hcl.Range{ - Filename: "test.tfvars", - Start: hcl.Pos{ - Line: 4, - Column: 1, - Byte: 29, - }, - End: hcl.Pos{ - Line: 4, - Column: 1, - Byte: 29, + expectedDiags := ast.SourceVarsDiags{ + ast.HCLParsingSource: ast.VarsDiagsFromMap(map[string]hcl.Diagnostics{ + "test.tfvars": { + { + Severity: hcl.DiagError, + Summary: "Missing expression", + Detail: "Expected the start of an expression, but found the end of the file.", + Subject: &hcl.Range{ + Filename: "test.tfvars", + Start: hcl.Pos{ + Line: 4, + Column: 1, + Byte: 29, + }, + End: hcl.Pos{ + Line: 4, + Column: 1, + Byte: 29, + }, }, }, }, - }, - }) + }), + } if diff := cmp.Diff(expectedDiags, mod.VarsDiagnostics, cmpOpts); diff != "" { t.Fatalf("unexpected diagnostics: %s", diff) } @@ -732,16 +844,20 @@ func BenchmarkModuleByPath(b *testing.B) { b.Fatal(err) } mDiags := ast.ModDiagsFromMap(diags) - err = s.Modules.UpdateModuleDiagnostics(modPath, mDiags) + err = s.Modules.UpdateModuleDiagnostics(modPath, ast.HCLParsingSource, mDiags) if err != nil { b.Fatal(err) } expectedMod := &Module{ - Path: modPath, - ParsedModuleFiles: mFiles, - ModuleParsingState: operation.OpStateLoaded, - ModuleDiagnostics: mDiags, + Path: modPath, + ParsedModuleFiles: mFiles, + ModuleDiagnostics: ast.SourceModDiags{ + ast.HCLParsingSource: mDiags, + }, + ModuleDiagnosticsState: ast.DiagnosticSourceState{ + ast.HCLParsingSource: operation.OpStateLoaded, + }, } for n := 0; n < b.N; n++ { diff --git a/internal/terraform/ast/diagnostics.go b/internal/terraform/ast/diagnostics.go new file mode 100644 index 000000000..fa8ecb597 --- /dev/null +++ b/internal/terraform/ast/diagnostics.go @@ -0,0 +1,46 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ast + +import ( + "fmt" + + op "github.com/hashicorp/terraform-ls/internal/terraform/module/operation" +) + +// DiagnosticSource differentiates different sources of diagnostics. +type DiagnosticSource int + +const ( + HCLParsingSource DiagnosticSource = iota + SchemaValidationSource + ReferenceValidationSource + TerraformValidateSource +) + +func (d DiagnosticSource) String() string { + switch d { + case HCLParsingSource: + return "HCL" + case SchemaValidationSource: + return "early validation" + case ReferenceValidationSource: + return "early validation" + case TerraformValidateSource: + return "terraform validate" + default: + panic(fmt.Sprintf("Unknown diagnostic source %d", d)) + } +} + +type DiagnosticSourceState map[DiagnosticSource]op.OpState + +func (dss DiagnosticSourceState) Copy() DiagnosticSourceState { + newDiagnosticSourceState := make(DiagnosticSourceState, len(dss)) + for source, state := range dss { + newDiagnosticSourceState[source] = state + } + + return newDiagnosticSourceState +} diff --git a/internal/terraform/ast/module.go b/internal/terraform/ast/module.go index 3834321c5..147516120 100644 --- a/internal/terraform/ast/module.go +++ b/internal/terraform/ast/module.go @@ -106,3 +106,13 @@ func (md ModDiags) Count() int { } return count } + +type SourceModDiags map[DiagnosticSource]ModDiags + +func (smd SourceModDiags) Count() int { + count := 0 + for _, diags := range smd { + count += diags.Count() + } + return count +} diff --git a/internal/terraform/ast/variables.go b/internal/terraform/ast/variables.go index a2f6aaf9e..3486ba4e1 100644 --- a/internal/terraform/ast/variables.go +++ b/internal/terraform/ast/variables.go @@ -87,3 +87,13 @@ func (vd VarsDiags) Count() int { } return count } + +type SourceVarsDiags map[DiagnosticSource]VarsDiags + +func (svd SourceVarsDiags) Count() int { + count := 0 + for _, diags := range svd { + count += diags.Count() + } + return count +} diff --git a/internal/terraform/module/module_ops.go b/internal/terraform/module/module_ops.go index 0b2288bfa..d3aa0237d 100644 --- a/internal/terraform/module/module_ops.go +++ b/internal/terraform/module/module_ops.go @@ -21,8 +21,10 @@ import ( tfjson "github.com/hashicorp/terraform-json" lsctx "github.com/hashicorp/terraform-ls/internal/context" idecoder "github.com/hashicorp/terraform-ls/internal/decoder" + "github.com/hashicorp/terraform-ls/internal/decoder/validations" "github.com/hashicorp/terraform-ls/internal/document" "github.com/hashicorp/terraform-ls/internal/job" + "github.com/hashicorp/terraform-ls/internal/langserver/diagnostics" ilsp "github.com/hashicorp/terraform-ls/internal/lsp" "github.com/hashicorp/terraform-ls/internal/registry" "github.com/hashicorp/terraform-ls/internal/schemas" @@ -34,7 +36,7 @@ import ( "github.com/hashicorp/terraform-ls/internal/uri" tfaddr "github.com/hashicorp/terraform-registry-address" "github.com/hashicorp/terraform-schema/earlydecoder" - "github.com/hashicorp/terraform-schema/module" + tfmodule "github.com/hashicorp/terraform-schema/module" tfregistry "github.com/hashicorp/terraform-schema/registry" tfschema "github.com/hashicorp/terraform-schema/schema" "github.com/zclconf/go-cty/cty" @@ -364,7 +366,7 @@ func ParseModuleConfiguration(ctx context.Context, fs ReadOnlyFS, modStore *stat // TODO: Avoid parsing if the content matches existing AST // Avoid parsing if it is already in progress or already known - if mod.ModuleParsingState != op.OpStateUnknown && !job.IgnoreState(ctx) { + if mod.ModuleDiagnosticsState[ast.HCLParsingSource] != op.OpStateUnknown && !job.IgnoreState(ctx) { return job.StateNotChangedErr{Dir: document.DirHandleFromPath(modPath)} } @@ -372,9 +374,9 @@ func ParseModuleConfiguration(ctx context.Context, fs ReadOnlyFS, modStore *stat var diags ast.ModDiags rpcContext := lsctx.RPCContext(ctx) // Only parse the file that's being changed/opened, unless this is 1st-time parsing - if mod.ModuleParsingState == op.OpStateLoaded && rpcContext.IsDidChangeRequest() && lsctx.IsLanguageId(ctx, ilsp.Terraform.String()) { + if mod.ModuleDiagnosticsState[ast.HCLParsingSource] == op.OpStateLoaded && rpcContext.IsDidChangeRequest() && lsctx.IsLanguageId(ctx, ilsp.Terraform.String()) { // the file has already been parsed, so only examine this file and not the whole module - err = modStore.SetModuleParsingState(modPath, op.OpStateLoading) + err = modStore.SetModuleDiagnosticsState(modPath, ast.HCLParsingSource, op.OpStateLoading) if err != nil { return err } @@ -393,12 +395,17 @@ func ParseModuleConfiguration(ctx context.Context, fs ReadOnlyFS, modStore *stat existingFiles[ast.ModFilename(fileName)] = f files = existingFiles - existingDiags := mod.ModuleDiagnostics.Copy() + existingDiags, ok := mod.ModuleDiagnostics[ast.HCLParsingSource] + if !ok { + existingDiags = make(ast.ModDiags) + } else { + existingDiags = existingDiags.Copy() + } existingDiags[ast.ModFilename(fileName)] = fDiags diags = existingDiags } else { // this is the first time file is opened so parse the whole module - err = modStore.SetModuleParsingState(modPath, op.OpStateLoading) + err = modStore.SetModuleDiagnosticsState(modPath, ast.HCLParsingSource, op.OpStateLoading) if err != nil { return err } @@ -415,7 +422,7 @@ func ParseModuleConfiguration(ctx context.Context, fs ReadOnlyFS, modStore *stat return sErr } - sErr = modStore.UpdateModuleDiagnostics(modPath, diags) + sErr = modStore.UpdateModuleDiagnostics(modPath, ast.HCLParsingSource, diags) if sErr != nil { return sErr } @@ -434,11 +441,11 @@ func ParseVariables(ctx context.Context, fs ReadOnlyFS, modStore *state.ModuleSt // TODO: Only parse the file that's being changed/opened, unless this is 1st-time parsing // Avoid parsing if it is already in progress or already known - if mod.VarsParsingState != op.OpStateUnknown && !job.IgnoreState(ctx) { + if mod.VarsDiagnosticsState[ast.HCLParsingSource] != op.OpStateUnknown && !job.IgnoreState(ctx) { return job.StateNotChangedErr{Dir: document.DirHandleFromPath(modPath)} } - err = modStore.SetVarsParsingState(modPath, op.OpStateLoading) + err = modStore.SetVarsDiagnosticsState(modPath, ast.HCLParsingSource, op.OpStateLoading) if err != nil { return err } @@ -450,7 +457,7 @@ func ParseVariables(ctx context.Context, fs ReadOnlyFS, modStore *state.ModuleSt return sErr } - sErr = modStore.UpdateVarsDiagnostics(modPath, diags) + sErr = modStore.UpdateVarsDiagnostics(modPath, ast.HCLParsingSource, diags) if sErr != nil { return sErr } @@ -559,7 +566,7 @@ func LoadModuleMetadata(ctx context.Context, modStore *state.ModuleStore, modPat } meta.ProviderRequirements = providerRequirements - providerRefs := make(map[module.ProviderRef]tfaddr.Provider, len(meta.ProviderReferences)) + providerRefs := make(map[tfmodule.ProviderRef]tfaddr.Provider, len(meta.ProviderReferences)) for localRef, pAddr := range meta.ProviderReferences { // TODO: check pAddr for migrations via Registry API? providerRefs[localRef] = pAddr @@ -699,18 +706,18 @@ func DecodeVarsReferences(ctx context.Context, modStore *state.ModuleStore, sche return rErr } -func EarlyValidation(ctx context.Context, modStore *state.ModuleStore, schemaReader state.SchemaReader, modPath string) error { +func SchemaValidation(ctx context.Context, modStore *state.ModuleStore, schemaReader state.SchemaReader, modPath string) error { mod, err := modStore.ModuleByPath(modPath) if err != nil { return err } // Avoid validation if it is already in progress or already finished - if mod.ValidationDiagnosticsState != op.OpStateUnknown && !job.IgnoreState(ctx) { + if mod.ModuleDiagnosticsState[ast.SchemaValidationSource] != op.OpStateUnknown && !job.IgnoreState(ctx) { return job.StateNotChangedErr{Dir: document.DirHandleFromPath(modPath)} } - err = modStore.SetValidationDiagnosticsState(modPath, op.OpStateLoading) + err = modStore.SetModuleDiagnosticsState(modPath, ast.SchemaValidationSource, op.OpStateLoading) if err != nil { return err } @@ -730,7 +737,7 @@ func EarlyValidation(ctx context.Context, modStore *state.ModuleStore, schemaRea } diags, rErr := moduleDecoder.Validate(ctx) - sErr := modStore.UpdateValidateDiagnostics(modPath, diags) + sErr := modStore.UpdateModuleDiagnostics(modPath, ast.SchemaValidationSource, ast.ModDiagsFromMap(diags)) if sErr != nil { return sErr } @@ -738,6 +745,68 @@ func EarlyValidation(ctx context.Context, modStore *state.ModuleStore, schemaRea return rErr } +func ReferenceValidation(ctx context.Context, modStore *state.ModuleStore, schemaReader state.SchemaReader, modPath string) error { + mod, err := modStore.ModuleByPath(modPath) + if err != nil { + return err + } + + // Avoid validation if it is already in progress or already finished + if mod.ModuleDiagnosticsState[ast.ReferenceValidationSource] != op.OpStateUnknown && !job.IgnoreState(ctx) { + return job.StateNotChangedErr{Dir: document.DirHandleFromPath(modPath)} + } + + err = modStore.SetModuleDiagnosticsState(modPath, ast.ReferenceValidationSource, op.OpStateLoading) + if err != nil { + return err + } + + pathReader := &idecoder.PathReader{ + ModuleReader: modStore, + SchemaReader: schemaReader, + } + pathCtx, err := pathReader.PathContext(lang.Path{ + Path: modPath, + LanguageID: ilsp.Terraform.String(), + }) + if err != nil { + return err + } + + diags := validations.UnreferencedOrigins(ctx, pathCtx) + return modStore.UpdateModuleDiagnostics(modPath, ast.ReferenceValidationSource, ast.ModDiagsFromMap(diags)) +} + +func TerraformValidate(ctx context.Context, modStore *state.ModuleStore, modPath string) error { + mod, err := modStore.ModuleByPath(modPath) + if err != nil { + return err + } + + // Avoid validation if it is already in progress or already finished + if mod.ModuleDiagnosticsState[ast.TerraformValidateSource] != op.OpStateUnknown && !job.IgnoreState(ctx) { + return job.StateNotChangedErr{Dir: document.DirHandleFromPath(modPath)} + } + + err = modStore.SetModuleDiagnosticsState(modPath, ast.TerraformValidateSource, op.OpStateLoading) + if err != nil { + return err + } + + tfExec, err := TerraformExecutorForModule(ctx, mod.Path) + if err != nil { + return err + } + + jsonDiags, err := tfExec.Validate(ctx) + if err != nil { + return err + } + validateDiags := diagnostics.HCLDiagsFromJSON(jsonDiags) + + return modStore.UpdateModuleDiagnostics(modPath, ast.TerraformValidateSource, ast.ModDiagsFromMap(validateDiags)) +} + func GetModuleDataFromRegistry(ctx context.Context, regClient registry.Client, modStore *state.ModuleStore, modRegStore *state.RegistryModuleStore, modPath string) error { // loop over module calls calls, err := modStore.ModuleCalls(modPath) diff --git a/internal/terraform/module/module_ops_test.go b/internal/terraform/module/module_ops_test.go index 3bb0486fb..9a84dfdac 100644 --- a/internal/terraform/module/module_ops_test.go +++ b/internal/terraform/module/module_ops_test.go @@ -30,6 +30,7 @@ import ( ilsp "github.com/hashicorp/terraform-ls/internal/lsp" "github.com/hashicorp/terraform-ls/internal/registry" "github.com/hashicorp/terraform-ls/internal/state" + "github.com/hashicorp/terraform-ls/internal/terraform/ast" "github.com/hashicorp/terraform-ls/internal/terraform/exec" "github.com/hashicorp/terraform-ls/internal/terraform/module/operation" "github.com/hashicorp/terraform-ls/internal/uri" @@ -1008,12 +1009,12 @@ func TestParseModuleConfiguration(t *testing.T) { } // examine diags should change for foo.tf - if before.ModuleDiagnostics["foo.tf"][0] == after.ModuleDiagnostics["foo.tf"][0] { + if before.ModuleDiagnostics[ast.HCLParsingSource]["foo.tf"][0] == after.ModuleDiagnostics[ast.HCLParsingSource]["foo.tf"][0] { t.Fatal("diags should mismatch") } // examine diags should change for main.tf - if before.ModuleDiagnostics["main.tf"][0] != after.ModuleDiagnostics["main.tf"][0] { + if before.ModuleDiagnostics[ast.HCLParsingSource]["main.tf"][0] != after.ModuleDiagnostics[ast.HCLParsingSource]["main.tf"][0] { t.Fatal("diags should match") } } @@ -1082,7 +1083,7 @@ func TestParseModuleConfiguration_ignore_tfvars(t *testing.T) { } // diags should be missing for example.tfvars - if _, ok := before.ModuleDiagnostics["example.tfvars"]; ok { + if _, ok := before.ModuleDiagnostics[ast.HCLParsingSource]["example.tfvars"]; ok { t.Fatal("there should be no diags for tfvars files right now") } } diff --git a/internal/terraform/module/operation/op_type_string.go b/internal/terraform/module/operation/op_type_string.go index a53707460..5b4ac8744 100644 --- a/internal/terraform/module/operation/op_type_string.go +++ b/internal/terraform/module/operation/op_type_string.go @@ -21,12 +21,14 @@ func _() { _ = x[OpTypeGetModuleDataFromRegistry-10] _ = x[OpTypeParseProviderVersions-11] _ = x[OpTypePreloadEmbeddedSchema-12] - _ = x[OpTypeEarlyValidation-13] + _ = x[OpTypeSchemaValidation-13] + _ = x[OpTypeReferenceValidation-14] + _ = x[OpTypeTerraformValidate-15] } -const _OpType_name = "OpTypeUnknownOpTypeGetTerraformVersionOpTypeObtainSchemaOpTypeParseModuleConfigurationOpTypeParseVariablesOpTypeParseModuleManifestOpTypeLoadModuleMetadataOpTypeDecodeReferenceTargetsOpTypeDecodeReferenceOriginsOpTypeDecodeVarsReferencesOpTypeGetModuleDataFromRegistryOpTypeParseProviderVersionsOpTypePreloadEmbeddedSchemaOpTypeEarlyValidation" +const _OpType_name = "OpTypeUnknownOpTypeGetTerraformVersionOpTypeObtainSchemaOpTypeParseModuleConfigurationOpTypeParseVariablesOpTypeParseModuleManifestOpTypeLoadModuleMetadataOpTypeDecodeReferenceTargetsOpTypeDecodeReferenceOriginsOpTypeDecodeVarsReferencesOpTypeGetModuleDataFromRegistryOpTypeParseProviderVersionsOpTypePreloadEmbeddedSchemaOpTypeSchemaValidationOpTypeReferenceValidationOpTypeTerraformValidate" -var _OpType_index = [...]uint16{0, 13, 38, 56, 86, 106, 131, 155, 183, 211, 237, 268, 295, 322, 343} +var _OpType_index = [...]uint16{0, 13, 38, 56, 86, 106, 131, 155, 183, 211, 237, 268, 295, 322, 344, 369, 392} func (i OpType) String() string { if i >= OpType(len(_OpType_index)-1) { diff --git a/internal/terraform/module/operation/operation.go b/internal/terraform/module/operation/operation.go index 908d04ea0..fe408450d 100644 --- a/internal/terraform/module/operation/operation.go +++ b/internal/terraform/module/operation/operation.go @@ -30,5 +30,7 @@ const ( OpTypeGetModuleDataFromRegistry OpTypeParseProviderVersions OpTypePreloadEmbeddedSchema - OpTypeEarlyValidation + OpTypeSchemaValidation + OpTypeReferenceValidation + OpTypeTerraformValidate )