diff --git a/go.mod b/go.mod index 2e531d86..f72997c1 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/hashicorp/terraform-exec v0.17.2 github.com/hashicorp/terraform-json v0.14.0 github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c - github.com/hashicorp/terraform-schema v0.0.0-20220805102629-49cc6ae5bfa3 + github.com/hashicorp/terraform-schema v0.0.0-20220809100530-1ef9558a761c github.com/kylelemons/godebug v1.1.0 // indirect github.com/mh-cbon/go-fmt-fail v0.0.0-20160815164508-67765b3fbcb5 github.com/mitchellh/cli v1.1.4 diff --git a/go.sum b/go.sum index ca7ca600..e4ee185b 100644 --- a/go.sum +++ b/go.sum @@ -338,8 +338,8 @@ github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg= github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c/go.mod h1:Wn3Na71knbXc1G8Lh+yu/dQWWJeFQEpDeJMtWMtlmNI= -github.com/hashicorp/terraform-schema v0.0.0-20220805102629-49cc6ae5bfa3 h1:1MdlLPlGxrTKSrkIf8ehb8b9TQuiMwvZSZ0CPlGQRQk= -github.com/hashicorp/terraform-schema v0.0.0-20220805102629-49cc6ae5bfa3/go.mod h1:YfAL2BROQ9jq7ei+c8HqGyx0C1lc3xenQ/2FTr4AtKI= +github.com/hashicorp/terraform-schema v0.0.0-20220809100530-1ef9558a761c h1:NlNpLN/GHk2ycoeGcfGbVFt0nMIC+c0WLvYPu8s8faU= +github.com/hashicorp/terraform-schema v0.0.0-20220809100530-1ef9558a761c/go.mod h1:vZFv3NuAOpCxkP8j4d08ofVXVeCEjIbeKy/fNxYFbhg= github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= diff --git a/internal/decoder/module_schema.go b/internal/decoder/module_schema.go index b6b5b60b..8bd37acb 100644 --- a/internal/decoder/module_schema.go +++ b/internal/decoder/module_schema.go @@ -9,30 +9,14 @@ import ( ) func schemaForModule(mod *state.Module, schemaReader state.SchemaReader, modReader state.ModuleCallReader) (*schema.BodySchema, error) { - var coreSchema *schema.BodySchema - coreRequirements := make(version.Constraints, 0) - if mod.TerraformVersion != nil { - var err error - coreSchema, err = tfschema.CoreModuleSchemaForVersion(mod.TerraformVersion) - if err != nil { - return nil, err - } - coreRequirements, err = version.NewConstraint(mod.TerraformVersion.String()) - if err != nil { - return nil, err - } - } else { - coreSchema = tfschema.UniversalCoreModuleSchema() - } - - sm := tfschema.NewSchemaMerger(coreSchema) + sm := tfschema.NewSchemaMerger(coreSchema(mod)) sm.SetSchemaReader(schemaReader) sm.SetTerraformVersion(mod.TerraformVersion) sm.SetModuleReader(modReader) meta := &tfmodule.Meta{ Path: mod.Path, - CoreRequirements: coreRequirements, + CoreRequirements: mod.Meta.CoreRequirements, ProviderRequirements: mod.Meta.ProviderRequirements, ProviderReferences: mod.Meta.ProviderReferences, Variables: mod.Meta.Variables, @@ -42,3 +26,36 @@ func schemaForModule(mod *state.Module, schemaReader state.SchemaReader, modRead return sm.SchemaForModule(meta) } + +func coreSchema(mod *state.Module) *schema.BodySchema { + if mod.TerraformVersion != nil { + s, err := tfschema.CoreModuleSchemaForVersion(mod.TerraformVersion) + if err == nil { + return s + } + if mod.TerraformVersion.LessThan(tfschema.OldestAvailableVersion) { + return mustCoreSchemaForVersion(tfschema.OldestAvailableVersion) + } + + return mustCoreSchemaForVersion(tfschema.LatestAvailableVersion) + } + + s, err := tfschema.CoreModuleSchemaForConstraint(mod.Meta.CoreRequirements) + if err == nil { + return s + } + if mod.Meta.CoreRequirements.Check(tfschema.OldestAvailableVersion) { + return mustCoreSchemaForVersion(tfschema.OldestAvailableVersion) + } + + return mustCoreSchemaForVersion(tfschema.LatestAvailableVersion) +} + +func mustCoreSchemaForVersion(v *version.Version) *schema.BodySchema { + s, err := tfschema.CoreModuleSchemaForVersion(v) + if err != nil { + // this should never happen + panic(err) + } + return s +} diff --git a/internal/indexer/document_open.go b/internal/indexer/document_open.go new file mode 100644 index 00000000..f6fc80c2 --- /dev/null +++ b/internal/indexer/document_open.go @@ -0,0 +1,84 @@ +package indexer + +import ( + "context" + + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/terraform-ls/internal/document" + "github.com/hashicorp/terraform-ls/internal/job" + "github.com/hashicorp/terraform-ls/internal/terraform/exec" + "github.com/hashicorp/terraform-ls/internal/terraform/module" + op "github.com/hashicorp/terraform-ls/internal/terraform/module/operation" +) + +func (idx *Indexer) DocumentOpened(modHandle document.DirHandle) (job.IDs, error) { + mod, err := idx.modStore.ModuleByPath(modHandle.Path()) + if err != nil { + return nil, err + } + + ids := make(job.IDs, 0) + var errs *multierror.Error + + if mod.TerraformVersionState == op.OpStateUnknown { + _, err := idx.jobStore.EnqueueJob(job.Job{ + Dir: modHandle, + Func: func(ctx context.Context) error { + ctx = exec.WithExecutorFactory(ctx, idx.tfExecFactory) + return module.GetTerraformVersion(ctx, idx.modStore, modHandle.Path()) + }, + Type: op.OpTypeGetTerraformVersion.String(), + }) + if err != nil { + errs = multierror.Append(errs, err) + } + // Given that getting version may take time and we only use it to + // enhance the UX, we ignore the outcome (job ID) here + // to avoid delays when documents of new modules are open. + } + + parseId, err := idx.jobStore.EnqueueJob(job.Job{ + Dir: modHandle, + Func: func(ctx context.Context) error { + return module.ParseModuleConfiguration(idx.fs, idx.modStore, modHandle.Path()) + }, + Type: op.OpTypeParseModuleConfiguration.String(), + }) + if err != nil { + return ids, err + } + ids = append(ids, parseId) + + modIds, err := idx.decodeModule(modHandle, job.IDs{parseId}) + if err != nil { + return ids, err + } + ids = append(ids, modIds...) + + parseVarsId, err := idx.jobStore.EnqueueJob(job.Job{ + Dir: modHandle, + Func: func(ctx context.Context) error { + return module.ParseVariables(idx.fs, idx.modStore, modHandle.Path()) + }, + Type: op.OpTypeParseVariables.String(), + }) + if err != nil { + return ids, err + } + ids = append(ids, parseVarsId) + + varsRefsId, err := idx.jobStore.EnqueueJob(job.Job{ + Dir: modHandle, + Func: func(ctx context.Context) error { + return module.DecodeVarsReferences(ctx, idx.modStore, idx.schemaStore, modHandle.Path()) + }, + Type: op.OpTypeDecodeVarsReferences.String(), + DependsOn: job.IDs{parseVarsId}, + }) + if err != nil { + return ids, err + } + ids = append(ids, varsRefsId) + + return ids, errs.ErrorOrNil() +} diff --git a/internal/indexer/walker.go b/internal/indexer/walker.go index abde683e..b803a37e 100644 --- a/internal/indexer/walker.go +++ b/internal/indexer/walker.go @@ -83,21 +83,6 @@ func (idx *Indexer) WalkedModule(ctx context.Context, modHandle document.DirHand } } - tfVersionId, err := idx.jobStore.EnqueueJob(job.Job{ - Dir: modHandle, - Func: func(ctx context.Context) error { - ctx = exec.WithExecutorFactory(ctx, idx.tfExecFactory) - return module.GetTerraformVersion(ctx, idx.modStore, modHandle.Path()) - }, - Type: op.OpTypeGetTerraformVersion.String(), - }) - if err != nil { - errs = multierror.Append(errs, err) - } else { - ids = append(ids, tfVersionId) - refCollectionDeps = append(refCollectionDeps, tfVersionId) - } - dataDir := datadir.WalkDataDirOfModule(idx.fs, modHandle.Path()) idx.logger.Printf("parsed datadir: %#v", dataDir) diff --git a/internal/indexer/watcher.go b/internal/indexer/watcher.go index a2876d49..6fa667af 100644 --- a/internal/indexer/watcher.go +++ b/internal/indexer/watcher.go @@ -83,23 +83,5 @@ func (idx *Indexer) PluginLockChanged(ctx context.Context, modHandle document.Di } ids = append(ids, id) - id, err = idx.jobStore.EnqueueJob(job.Job{ - Dir: modHandle, - Func: func(ctx context.Context) error { - ctx = exec.WithExecutorFactory(ctx, idx.tfExecFactory) - eo, ok := exec.ExecutorOptsFromContext(ctx) - if ok { - ctx = exec.WithExecutorOpts(ctx, eo) - } - - return module.GetTerraformVersion(ctx, idx.modStore, modHandle.Path()) - }, - Type: op.OpTypeGetTerraformVersion.String(), - }) - if err != nil { - return ids, err - } - ids = append(ids, id) - return ids, nil } diff --git a/internal/langserver/handlers/code_action_test.go b/internal/langserver/handlers/code_action_test.go index 3db90a49..345a5a88 100644 --- a/internal/langserver/handlers/code_action_test.go +++ b/internal/langserver/handlers/code_action_test.go @@ -104,6 +104,8 @@ func TestLangServer_codeAction_basic(t *testing.T) { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/codeAction", ReqParams: fmt.Sprintf(`{ @@ -351,6 +353,7 @@ func TestLangServer_codeAction_no_code_action_requested(t *testing.T) { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, tt.request, tt.want) }) diff --git a/internal/langserver/handlers/code_lens_test.go b/internal/langserver/handlers/code_lens_test.go index 8353dda6..9004ee1a 100644 --- a/internal/langserver/handlers/code_lens_test.go +++ b/internal/langserver/handlers/code_lens_test.go @@ -41,7 +41,16 @@ func TestCodeLens_withoutOptIn(t *testing.T) { t.Fatal(err) } - ls := langserver.NewLangServerMock(t, NewMockSession(nil)) + ss, err := state.NewStateStore() + if err != nil { + t.Fatal(err) + } + wc := walker.NewWalkerCollector() + + ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{ + StateStore: ss, + WalkerCollector: wc, + })) stop := ls.Start(t) defer stop() @@ -52,6 +61,8 @@ func TestCodeLens_withoutOptIn(t *testing.T) { "rootUri": %q, "processId": 12345 }`, tmpDir.URI)}) + waitForWalkerPath(t, ss, wc, tmpDir) + ls.Notify(t, &langserver.CallRequest{ Method: "initialized", ReqParams: "{}", @@ -66,6 +77,8 @@ func TestCodeLens_withoutOptIn(t *testing.T) { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/codeLens", ReqParams: fmt.Sprintf(`{ @@ -170,6 +183,8 @@ output "test" { value = var.test } `, tmpDir.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/codeLens", ReqParams: fmt.Sprintf(`{ @@ -282,6 +297,8 @@ variable "instances" { type = number } `, submodUri.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/codeLens", ReqParams: fmt.Sprintf(`{ diff --git a/internal/langserver/handlers/complete_test.go b/internal/langserver/handlers/complete_test.go index c6ec8458..5dd2edf0 100644 --- a/internal/langserver/handlers/complete_test.go +++ b/internal/langserver/handlers/complete_test.go @@ -129,6 +129,7 @@ func TestModuleCompletion_withValidData_basic(t *testing.T) { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/completion", @@ -262,6 +263,372 @@ func TestModuleCompletion_withValidData_basic(t *testing.T) { }`) } +// verify that for old versions we serve earliest available (v0.12) schema +func TestModuleCompletion_withValidData_tooOldVersion(t *testing.T) { + tmpDir := TempDir(t) + InitPluginCache(t, tmpDir.Path()) + + err := ioutil.WriteFile(filepath.Join(tmpDir.Path(), "main.tf"), []byte("variable \"test\" {\n\n}\n"), 0o755) + if err != nil { + t.Fatal(err) + } + + var testSchema tfjson.ProviderSchemas + err = json.Unmarshal([]byte(testModuleSchemaOutput), &testSchema) + if err != nil { + t.Fatal(err) + } + + ss, err := state.NewStateStore() + if err != nil { + t.Fatal(err) + } + + wc := walker.NewWalkerCollector() + + ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{ + StateStore: ss, + TerraformCalls: &exec.TerraformMockCalls{ + PerWorkDir: map[string][]*mock.Call{ + tmpDir.Path(): { + { + Method: "Version", + Repeatability: 1, + Arguments: []interface{}{ + mock.AnythingOfType(""), + }, + ReturnArguments: []interface{}{ + version.Must(version.NewVersion("0.10.0")), + nil, + nil, + }, + }, + }, + }, + }, + WalkerCollector: wc, + })) + stop := ls.Start(t) + defer stop() + + ls.Call(t, &langserver.CallRequest{ + Method: "initialize", + ReqParams: fmt.Sprintf(`{ + "capabilities": {}, + "rootUri": %q, + "processId": 12345 + }`, tmpDir.URI)}) + waitForWalkerPath(t, ss, wc, tmpDir) + ls.Notify(t, &langserver.CallRequest{ + Method: "initialized", + ReqParams: "{}", + }) + ls.Call(t, &langserver.CallRequest{ + Method: "textDocument/didOpen", + ReqParams: fmt.Sprintf(`{ + "textDocument": { + "version": 0, + "languageId": "terraform", + "text": "variable \"test\" {\n\n}\n", + "uri": "%s/main.tf" + } + }`, tmpDir.URI)}) + waitForAllJobs(t, ss) + + ls.CallAndExpectResponse(t, &langserver.CallRequest{ + Method: "textDocument/completion", + ReqParams: fmt.Sprintf(`{ + "textDocument": { + "uri": "%s/main.tf" + }, + "position": { + "character": 0, + "line": 1 + } + }`, tmpDir.URI)}, `{ + "jsonrpc": "2.0", + "id": 3, + "result": { + "isIncomplete": false, + "itemDefaults": { + "editRange": { + "start": {"line": 0, "character": 0}, + "end": {"line": 0, "character": 0} + } + }, + "items": [ + { + "label": "default", + "labelDetails": {}, + "kind": 10, + "detail": "optional, any type", + "documentation": "Default value to use when variable is not explicitly set", + "insertTextFormat": 1, + "textEdit": { + "range": { + "start": { + "line": 1, + "character": 0 + }, + "end": { + "line": 1, + "character": 0 + } + }, + "newText": "default" + } + }, + { + "label": "description", + "labelDetails": {}, + "kind": 10, + "detail": "optional, string", + "documentation": "Description to document the purpose of the variable and what value is expected", + "insertTextFormat": 1, + "textEdit": { + "range": { + "start": { + "line": 1, + "character": 0 + }, + "end": { + "line": 1, + "character": 0 + } + }, + "newText": "description" + } + }, + { + "label": "type", + "labelDetails": {}, + "kind": 10, + "detail": "optional, type", + "documentation": "Type constraint restricting the type of value to accept, e.g. string or list(string)", + "insertTextFormat": 1, + "textEdit": { + "range": { + "start": { + "line": 1, + "character": 0 + }, + "end": { + "line": 1, + "character": 0 + } + }, + "newText": "type" + } + } + ] + } + }`) +} + +// verify that for unknown new versions we serve latest available schema +func TestModuleCompletion_withValidData_tooNewVersion(t *testing.T) { + tmpDir := TempDir(t) + InitPluginCache(t, tmpDir.Path()) + + err := ioutil.WriteFile(filepath.Join(tmpDir.Path(), "main.tf"), []byte("variable \"test\" {\n\n}\n"), 0o755) + if err != nil { + t.Fatal(err) + } + + var testSchema tfjson.ProviderSchemas + err = json.Unmarshal([]byte(testModuleSchemaOutput), &testSchema) + if err != nil { + t.Fatal(err) + } + + ss, err := state.NewStateStore() + if err != nil { + t.Fatal(err) + } + + wc := walker.NewWalkerCollector() + + ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{ + StateStore: ss, + TerraformCalls: &exec.TerraformMockCalls{ + PerWorkDir: map[string][]*mock.Call{ + tmpDir.Path(): { + { + Method: "Version", + Repeatability: 1, + Arguments: []interface{}{ + mock.AnythingOfType(""), + }, + ReturnArguments: []interface{}{ + version.Must(version.NewVersion("999.999.999")), + nil, + nil, + }, + }, + }, + }, + }, + WalkerCollector: wc, + })) + stop := ls.Start(t) + defer stop() + + ls.Call(t, &langserver.CallRequest{ + Method: "initialize", + ReqParams: fmt.Sprintf(`{ + "capabilities": {}, + "rootUri": %q, + "processId": 12345 + }`, tmpDir.URI)}) + waitForWalkerPath(t, ss, wc, tmpDir) + ls.Notify(t, &langserver.CallRequest{ + Method: "initialized", + ReqParams: "{}", + }) + ls.Call(t, &langserver.CallRequest{ + Method: "textDocument/didOpen", + ReqParams: fmt.Sprintf(`{ + "textDocument": { + "version": 0, + "languageId": "terraform", + "text": "variable \"test\" {\n\n}\n", + "uri": "%s/main.tf" + } + }`, tmpDir.URI)}) + waitForAllJobs(t, ss) + + ls.CallAndExpectResponse(t, &langserver.CallRequest{ + Method: "textDocument/completion", + ReqParams: fmt.Sprintf(`{ + "textDocument": { + "uri": "%s/main.tf" + }, + "position": { + "character": 0, + "line": 1 + } + }`, tmpDir.URI)}, `{ + "jsonrpc": "2.0", + "id": 3, + "result": { + "isIncomplete": false, + "itemDefaults": { + "editRange": { + "start": {"line": 0, "character": 0}, + "end": {"line": 0, "character": 0} + } + }, + "items": [ + { + "label": "default", + "labelDetails": {}, + "kind": 10, + "detail": "optional, any type", + "documentation": "Default value to use when variable is not explicitly set", + "insertTextFormat": 1, + "textEdit": { + "range": { + "start": { + "line": 1, + "character": 0 + }, + "end": { + "line": 1, + "character": 0 + } + }, + "newText": "default" + } + }, + { + "label": "description", + "labelDetails": {}, + "kind": 10, + "detail": "optional, string", + "documentation": "Description to document the purpose of the variable and what value is expected", + "insertTextFormat": 1, + "textEdit": { + "range": { + "start": { + "line": 1, + "character": 0 + }, + "end": { + "line": 1, + "character": 0 + } + }, + "newText": "description" + } + }, + { + "label": "sensitive", + "labelDetails": {}, + "kind": 10, + "detail": "optional, bool", + "documentation": "Whether the variable contains sensitive material and should be hidden in the UI", + "insertTextFormat": 1, + "textEdit": { + "range": { + "start": { + "line": 1, + "character": 0 + }, + "end": { + "line": 1, + "character": 0 + } + }, + "newText": "sensitive" + } + }, + { + "label": "type", + "labelDetails": {}, + "kind": 10, + "detail": "optional, type", + "documentation": "Type constraint restricting the type of value to accept, e.g. string or list(string)", + "insertTextFormat": 1, + "textEdit": { + "range": { + "start": { + "line": 1, + "character": 0 + }, + "end": { + "line": 1, + "character": 0 + } + }, + "newText": "type" + } + }, + { + "label": "validation", + "labelDetails": {}, + "kind": 7, + "detail": "Block", + "documentation": "Custom validation rule to restrict what value is expected for the variable", + "insertTextFormat": 1, + "textEdit": { + "range": { + "start": { + "line": 1, + "character": 0 + }, + "end": { + "line": 1, + "character": 0 + } + }, + "newText": "validation" + } + } + ] + } + }`) +} + func TestModuleCompletion_withValidDataAndSnippets(t *testing.T) { tmpDir := TempDir(t) InitPluginCache(t, tmpDir.Path()) @@ -355,6 +722,7 @@ func TestModuleCompletion_withValidDataAndSnippets(t *testing.T) { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/completion", @@ -669,6 +1037,7 @@ func TestVarsCompletion_withValidData(t *testing.T) { "uri": "%s/terraform.tfvars" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/completion", @@ -821,6 +1190,7 @@ output "test" { "uri": "%s/main.tf" } }`, mainCfg, tmpDir.URI)}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/completion", @@ -1089,6 +1459,7 @@ output "test" { "uri": "%s/main.tf" } }`, mainCfg, tmpDir.URI)}) + waitForAllJobs(t, ss) // first module ls.CallAndExpectResponse(t, &langserver.CallRequest{ @@ -1429,6 +1800,8 @@ variable "ccc" {} "uri": "%s/outputs.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/completion", ReqParams: fmt.Sprintf(`{ diff --git a/internal/langserver/handlers/did_change_test.go b/internal/langserver/handlers/did_change_test.go index 4c101bad..1d1a3f1d 100644 --- a/internal/langserver/handlers/did_change_test.go +++ b/internal/langserver/handlers/did_change_test.go @@ -72,6 +72,8 @@ module "app" { "text": %q } }`, TempDir(t).URI, originalText)}) + waitForAllJobs(t, ss) + ls.Call(t, &langserver.CallRequest{ Method: "textDocument/didChange", ReqParams: fmt.Sprintf(`{ diff --git a/internal/langserver/handlers/did_open.go b/internal/langserver/handlers/did_open.go index 241fee39..60c0531d 100644 --- a/internal/langserver/handlers/did_open.go +++ b/internal/langserver/handlers/did_open.go @@ -6,12 +6,8 @@ import ( "github.com/creachadair/jrpc2" "github.com/hashicorp/terraform-ls/internal/document" - "github.com/hashicorp/terraform-ls/internal/job" lsp "github.com/hashicorp/terraform-ls/internal/protocol" "github.com/hashicorp/terraform-ls/internal/state" - "github.com/hashicorp/terraform-ls/internal/terraform/exec" - "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" ) @@ -60,26 +56,11 @@ func (svc *service) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpenT // (originally parsed) content on the disk // TODO: Do this only if we can verify the file differs? modHandle := document.DirHandleFromPath(mod.Path) - jobIds, err := svc.indexer.DocumentChanged(modHandle) + jobIds, err := svc.indexer.DocumentOpened(modHandle) if err != nil { return err } - if mod.TerraformVersionState == op.OpStateUnknown { - jobId, err := svc.stateStore.JobStore.EnqueueJob(job.Job{ - Dir: modHandle, - Func: func(ctx context.Context) error { - ctx = exec.WithExecutorFactory(ctx, svc.tfExecFactory) - return module.GetTerraformVersion(ctx, svc.modStore, mod.Path) - }, - Type: op.OpTypeGetTerraformVersion.String(), - }) - if err != nil { - return err - } - jobIds = append(jobIds, jobId) - } - if svc.singleFileMode { err = svc.stateStore.WalkerPaths.EnqueueDir(modHandle) if err != nil { diff --git a/internal/langserver/handlers/did_open_test.go b/internal/langserver/handlers/did_open_test.go index deed28c1..fee30403 100644 --- a/internal/langserver/handlers/did_open_test.go +++ b/internal/langserver/handlers/did_open_test.go @@ -80,6 +80,8 @@ func TestLangServer_didOpenLanguageIdStored(t *testing.T) { "text": %q } }`, TempDir(t).URI, originalText)}) + waitForAllJobs(t, ss) + path := filepath.Join(TempDir(t).Path(), "main.tf") dh := document.HandleFromPath(path) doc, err := ss.DocumentStore.GetDocument(dh) diff --git a/internal/langserver/handlers/document_link_test.go b/internal/langserver/handlers/document_link_test.go index b6232ca3..a66cf88b 100644 --- a/internal/langserver/handlers/document_link_test.go +++ b/internal/langserver/handlers/document_link_test.go @@ -101,6 +101,7 @@ func TestDocumentLink_withValidData(t *testing.T) { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/documentLink", diff --git a/internal/langserver/handlers/execute_command_init_test.go b/internal/langserver/handlers/execute_command_init_test.go index 19403cb5..74500c7c 100644 --- a/internal/langserver/handlers/execute_command_init_test.go +++ b/internal/langserver/handlers/execute_command_init_test.go @@ -19,12 +19,20 @@ func TestLangServer_workspaceExecuteCommand_init_argumentError(t *testing.T) { tmpDir := TempDir(t) testFileURI := fmt.Sprintf("%s/main.tf", tmpDir.URI) + ss, err := state.NewStateStore() + if err != nil { + t.Fatal(err) + } + wc := walker.NewWalkerCollector() + ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{ TerraformCalls: &exec.TerraformMockCalls{ PerWorkDir: map[string][]*mock.Call{ tmpDir.Path(): validTfMockCalls(), }, }, + StateStore: ss, + WalkerCollector: wc, })) stop := ls.Start(t) defer stop() @@ -36,6 +44,8 @@ func TestLangServer_workspaceExecuteCommand_init_argumentError(t *testing.T) { "rootUri": %q, "processId": 12345 }`, tmpDir.URI)}) + waitForWalkerPath(t, ss, wc, tmpDir) + ls.Notify(t, &langserver.CallRequest{ Method: "initialized", ReqParams: "{}", @@ -50,6 +60,7 @@ func TestLangServer_workspaceExecuteCommand_init_argumentError(t *testing.T) { "uri": %q } }`, testFileURI)}) + waitForAllJobs(t, ss) ls.CallAndExpectError(t, &langserver.CallRequest{ Method: "workspace/executeCommand", @@ -134,6 +145,7 @@ func TestLangServer_workspaceExecuteCommand_init_basic(t *testing.T) { "uri": %q } }`, testFileURI)}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "workspace/executeCommand", @@ -223,6 +235,7 @@ func TestLangServer_workspaceExecuteCommand_init_error(t *testing.T) { "uri": %q } }`, testFileURI)}) + waitForAllJobs(t, ss) ls.CallAndExpectError(t, &langserver.CallRequest{ Method: "workspace/executeCommand", diff --git a/internal/langserver/handlers/execute_command_module_callers_test.go b/internal/langserver/handlers/execute_command_module_callers_test.go index 6de225b5..07fef7c6 100644 --- a/internal/langserver/handlers/execute_command_module_callers_test.go +++ b/internal/langserver/handlers/execute_command_module_callers_test.go @@ -18,15 +18,22 @@ import ( ) func TestLangServer_workspaceExecuteCommand_moduleCallers_argumentError(t *testing.T) { - rootDir := t.TempDir() - rootUri := uri.FromPath(rootDir) + rootDir := document.DirHandleFromPath(t.TempDir()) + + ss, err := state.NewStateStore() + if err != nil { + t.Fatal(err) + } + wc := walker.NewWalkerCollector() ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{ TerraformCalls: &exec.TerraformMockCalls{ PerWorkDir: map[string][]*mock.Call{ - rootDir: validTfMockCalls(), + rootDir.Path(): validTfMockCalls(), }, }, + StateStore: ss, + WalkerCollector: wc, })) stop := ls.Start(t) defer stop() @@ -37,7 +44,9 @@ func TestLangServer_workspaceExecuteCommand_moduleCallers_argumentError(t *testi "capabilities": {}, "rootUri": %q, "processId": 12345 - }`, rootUri)}) + }`, rootDir.URI)}) + waitForWalkerPath(t, ss, wc, rootDir) + ls.Notify(t, &langserver.CallRequest{ Method: "initialized", ReqParams: "{}", @@ -51,7 +60,8 @@ func TestLangServer_workspaceExecuteCommand_moduleCallers_argumentError(t *testi "text": "provider \"github\" {}", "uri": %q } - }`, fmt.Sprintf("%s/main.tf", rootUri))}) + }`, fmt.Sprintf("%s/main.tf", rootDir.URI))}) + waitForAllJobs(t, ss) ls.CallAndExpectError(t, &langserver.CallRequest{ Method: "workspace/executeCommand", @@ -109,6 +119,7 @@ func TestLangServer_workspaceExecuteCommand_moduleCallers_basic(t *testing.T) { "uri": %q } }`, fmt.Sprintf("%s/main.tf", baseDirUri))}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "workspace/executeCommand", diff --git a/internal/langserver/handlers/execute_command_module_providers_test.go b/internal/langserver/handlers/execute_command_module_providers_test.go index 39642c0d..2012b5fd 100644 --- a/internal/langserver/handlers/execute_command_module_providers_test.go +++ b/internal/langserver/handlers/execute_command_module_providers_test.go @@ -19,15 +19,22 @@ import ( ) func TestLangServer_workspaceExecuteCommand_moduleProviders_argumentError(t *testing.T) { - rootDir := t.TempDir() - rootUri := uri.FromPath(rootDir) + rootDir := document.DirHandleFromPath(t.TempDir()) + + ss, err := state.NewStateStore() + if err != nil { + t.Fatal(err) + } + wc := walker.NewWalkerCollector() ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{ TerraformCalls: &exec.TerraformMockCalls{ PerWorkDir: map[string][]*mock.Call{ - rootDir: validTfMockCalls(), + rootDir.Path(): validTfMockCalls(), }, }, + StateStore: ss, + WalkerCollector: wc, })) stop := ls.Start(t) defer stop() @@ -38,7 +45,8 @@ func TestLangServer_workspaceExecuteCommand_moduleProviders_argumentError(t *tes "capabilities": {}, "rootUri": %q, "processId": 12345 - }`, rootUri)}) + }`, rootDir.URI)}) + waitForWalkerPath(t, ss, wc, rootDir) ls.Notify(t, &langserver.CallRequest{ Method: "initialized", ReqParams: "{}", @@ -52,7 +60,8 @@ func TestLangServer_workspaceExecuteCommand_moduleProviders_argumentError(t *tes "text": "provider \"github\" {}", "uri": %q } - }`, fmt.Sprintf("%s/main.tf", rootUri))}) + }`, fmt.Sprintf("%s/main.tf", rootDir.URI))}) + waitForAllJobs(t, ss) ls.CallAndExpectError(t, &langserver.CallRequest{ Method: "workspace/executeCommand", diff --git a/internal/langserver/handlers/execute_command_modules_test.go b/internal/langserver/handlers/execute_command_modules_test.go index e4625722..70c997a9 100644 --- a/internal/langserver/handlers/execute_command_modules_test.go +++ b/internal/langserver/handlers/execute_command_modules_test.go @@ -59,6 +59,7 @@ func TestLangServer_workspaceExecuteCommand_modules_basic(t *testing.T) { "uri": %q } }`, testFileURI)}) + waitForAllJobs(t, ss) ls.CallAndExpectError(t, &langserver.CallRequest{ Method: "workspace/executeCommand", diff --git a/internal/langserver/handlers/execute_command_test.go b/internal/langserver/handlers/execute_command_test.go index 6592fcd8..c00c02a3 100644 --- a/internal/langserver/handlers/execute_command_test.go +++ b/internal/langserver/handlers/execute_command_test.go @@ -6,7 +6,9 @@ import ( "github.com/creachadair/jrpc2/code" "github.com/hashicorp/terraform-ls/internal/langserver" + "github.com/hashicorp/terraform-ls/internal/state" "github.com/hashicorp/terraform-ls/internal/terraform/exec" + "github.com/hashicorp/terraform-ls/internal/walker" "github.com/stretchr/testify/mock" ) @@ -14,6 +16,12 @@ func TestLangServer_workspaceExecuteCommand_noCommandHandlerError(t *testing.T) tmpDir := TempDir(t) testFileURI := fmt.Sprintf("%s/main.tf", tmpDir.URI) + ss, err := state.NewStateStore() + if err != nil { + t.Fatal(err) + } + wc := walker.NewWalkerCollector() + InitPluginCache(t, tmpDir.Path()) ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{ @@ -22,6 +30,8 @@ func TestLangServer_workspaceExecuteCommand_noCommandHandlerError(t *testing.T) tmpDir.Path(): validTfMockCalls(), }, }, + StateStore: ss, + WalkerCollector: wc, })) stop := ls.Start(t) defer stop() @@ -33,6 +43,8 @@ func TestLangServer_workspaceExecuteCommand_noCommandHandlerError(t *testing.T) "rootUri": %q, "processId": 12345 }`, tmpDir.URI)}) + waitForWalkerPath(t, ss, wc, tmpDir) + ls.Notify(t, &langserver.CallRequest{ Method: "initialized", ReqParams: "{}", @@ -47,6 +59,7 @@ func TestLangServer_workspaceExecuteCommand_noCommandHandlerError(t *testing.T) "uri": %q } }`, testFileURI)}) + waitForAllJobs(t, ss) ls.CallAndExpectError(t, &langserver.CallRequest{ Method: "workspace/executeCommand", diff --git a/internal/langserver/handlers/execute_command_validate_test.go b/internal/langserver/handlers/execute_command_validate_test.go index fd97059f..99164b14 100644 --- a/internal/langserver/handlers/execute_command_validate_test.go +++ b/internal/langserver/handlers/execute_command_validate_test.go @@ -7,7 +7,9 @@ import ( "github.com/creachadair/jrpc2/code" "github.com/hashicorp/terraform-ls/internal/langserver" "github.com/hashicorp/terraform-ls/internal/langserver/cmd" + "github.com/hashicorp/terraform-ls/internal/state" "github.com/hashicorp/terraform-ls/internal/terraform/exec" + "github.com/hashicorp/terraform-ls/internal/walker" "github.com/stretchr/testify/mock" ) @@ -15,12 +17,20 @@ func TestLangServer_workspaceExecuteCommand_validate_argumentError(t *testing.T) tmpDir := TempDir(t) testFileURI := fmt.Sprintf("%s/main.tf", tmpDir.URI) + ss, err := state.NewStateStore() + if err != nil { + t.Fatal(err) + } + wc := walker.NewWalkerCollector() + ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{ TerraformCalls: &exec.TerraformMockCalls{ PerWorkDir: map[string][]*mock.Call{ tmpDir.Path(): validTfMockCalls(), }, }, + StateStore: ss, + WalkerCollector: wc, })) stop := ls.Start(t) defer stop() @@ -32,6 +42,7 @@ func TestLangServer_workspaceExecuteCommand_validate_argumentError(t *testing.T) "rootUri": %q, "processId": 12345 }`, tmpDir.URI)}) + waitForWalkerPath(t, ss, wc, tmpDir) ls.Notify(t, &langserver.CallRequest{ Method: "initialized", ReqParams: "{}", @@ -46,6 +57,7 @@ func TestLangServer_workspaceExecuteCommand_validate_argumentError(t *testing.T) "uri": %q } }`, testFileURI)}) + waitForAllJobs(t, ss) ls.CallAndExpectError(t, &langserver.CallRequest{ Method: "workspace/executeCommand", diff --git a/internal/langserver/handlers/formatting_test.go b/internal/langserver/handlers/formatting_test.go index 1d18a340..6a8cf084 100644 --- a/internal/langserver/handlers/formatting_test.go +++ b/internal/langserver/handlers/formatting_test.go @@ -9,7 +9,9 @@ import ( "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-ls/internal/langserver" "github.com/hashicorp/terraform-ls/internal/langserver/session" + "github.com/hashicorp/terraform-ls/internal/state" "github.com/hashicorp/terraform-ls/internal/terraform/exec" + "github.com/hashicorp/terraform-ls/internal/walker" "github.com/stretchr/testify/mock" ) @@ -33,7 +35,15 @@ func TestLangServer_formattingWithoutInitialization(t *testing.T) { func TestLangServer_formatting_basic(t *testing.T) { tmpDir := TempDir(t) + ss, err := state.NewStateStore() + if err != nil { + t.Fatal(err) + } + wc := walker.NewWalkerCollector() + ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{ + StateStore: ss, + WalkerCollector: wc, TerraformCalls: &exec.TerraformMockCalls{ PerWorkDir: map[string][]*mock.Call{ tmpDir.Path(): { @@ -82,6 +92,7 @@ func TestLangServer_formatting_basic(t *testing.T) { "rootUri": %q, "processId": 12345 }`, tmpDir.URI)}) + waitForWalkerPath(t, ss, wc, tmpDir) ls.Notify(t, &langserver.CallRequest{ Method: "initialized", ReqParams: "{}", @@ -96,6 +107,8 @@ func TestLangServer_formatting_basic(t *testing.T) { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/formatting", ReqParams: fmt.Sprintf(`{ @@ -119,7 +132,16 @@ func TestLangServer_formatting_basic(t *testing.T) { func TestLangServer_formatting_oldVersion(t *testing.T) { tmpDir := TempDir(t) + + ss, err := state.NewStateStore() + if err != nil { + t.Fatal(err) + } + wc := walker.NewWalkerCollector() + ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{ + StateStore: ss, + WalkerCollector: wc, TerraformCalls: &exec.TerraformMockCalls{ PerWorkDir: map[string][]*mock.Call{ tmpDir.Path(): { @@ -168,6 +190,8 @@ func TestLangServer_formatting_oldVersion(t *testing.T) { "rootUri": %q, "processId": 12345 }`, tmpDir.URI)}) + waitForWalkerPath(t, ss, wc, tmpDir) + ls.Notify(t, &langserver.CallRequest{ Method: "initialized", ReqParams: "{}", @@ -182,6 +206,8 @@ func TestLangServer_formatting_oldVersion(t *testing.T) { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectError(t, &langserver.CallRequest{ Method: "textDocument/formatting", ReqParams: fmt.Sprintf(`{ @@ -194,7 +220,15 @@ func TestLangServer_formatting_oldVersion(t *testing.T) { func TestLangServer_formatting_variables(t *testing.T) { tmpDir := TempDir(t) + ss, err := state.NewStateStore() + if err != nil { + t.Fatal(err) + } + wc := walker.NewWalkerCollector() + ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{ + StateStore: ss, + WalkerCollector: wc, TerraformCalls: &exec.TerraformMockCalls{ PerWorkDir: map[string][]*mock.Call{ tmpDir.Path(): { @@ -243,6 +277,8 @@ func TestLangServer_formatting_variables(t *testing.T) { "rootUri": %q, "processId": 12345 }`, tmpDir.URI)}) + waitForWalkerPath(t, ss, wc, tmpDir) + ls.Notify(t, &langserver.CallRequest{ Method: "initialized", ReqParams: "{}", @@ -257,6 +293,8 @@ func TestLangServer_formatting_variables(t *testing.T) { "uri": "%s/terraform.tfvars" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/formatting", ReqParams: fmt.Sprintf(`{ @@ -298,7 +336,15 @@ func TestLangServer_formatting_diffBug(t *testing.T) { } ` + ss, err := state.NewStateStore() + if err != nil { + t.Fatal(err) + } + wc := walker.NewWalkerCollector() + ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{ + StateStore: ss, + WalkerCollector: wc, TerraformCalls: &exec.TerraformMockCalls{ PerWorkDir: map[string][]*mock.Call{ tmpDir.Path(): { @@ -347,6 +393,8 @@ func TestLangServer_formatting_diffBug(t *testing.T) { "rootUri": %q, "processId": 12345 }`, tmpDir.URI)}) + waitForWalkerPath(t, ss, wc, tmpDir) + ls.Notify(t, &langserver.CallRequest{ Method: "initialized", ReqParams: "{}", @@ -361,6 +409,8 @@ func TestLangServer_formatting_diffBug(t *testing.T) { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/formatting", ReqParams: fmt.Sprintf(`{ diff --git a/internal/langserver/handlers/go_to_ref_target_test.go b/internal/langserver/handlers/go_to_ref_target_test.go index f2765abb..9e217586 100644 --- a/internal/langserver/handlers/go_to_ref_target_test.go +++ b/internal/langserver/handlers/go_to_ref_target_test.go @@ -86,6 +86,8 @@ output "foo" { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/definition", ReqParams: fmt.Sprintf(`{ @@ -217,6 +219,8 @@ output "foo" { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/definition", ReqParams: fmt.Sprintf(`{ @@ -370,6 +374,7 @@ output "foo" { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/definition", @@ -476,6 +481,8 @@ func TestDefinition_moduleInputToVariable(t *testing.T) { "uri": "%s/main.tf" } }`, modHandle.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/definition", ReqParams: fmt.Sprintf(`{ @@ -576,6 +583,8 @@ output "foo" { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/declaration", ReqParams: fmt.Sprintf(`{ @@ -707,6 +716,8 @@ output "foo" { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/declaration", ReqParams: fmt.Sprintf(`{ diff --git a/internal/langserver/handlers/handlers_test.go b/internal/langserver/handlers/handlers_test.go index 29d3aef6..2b86346d 100644 --- a/internal/langserver/handlers/handlers_test.go +++ b/internal/langserver/handlers/handlers_test.go @@ -103,6 +103,18 @@ func waitForWalkerPath(t testOrBench, ss *state.StateStore, wc *walker.WalkerCol } } +func waitForAllJobs(t testOrBench, ss *state.StateStore) { + ctx := context.Background() + ids, err := ss.JobStore.ListAllJobs() + if err != nil { + t.Fatal(err) + } + err = ss.JobStore.WaitForJobs(ctx, ids...) + if err != nil { + t.Fatal(err) + } +} + type testOrBench interface { Fatal(args ...interface{}) Fatalf(format string, args ...interface{}) diff --git a/internal/langserver/handlers/hover_test.go b/internal/langserver/handlers/hover_test.go index 1e47b5ce..d9358c94 100644 --- a/internal/langserver/handlers/hover_test.go +++ b/internal/langserver/handlers/hover_test.go @@ -114,6 +114,7 @@ func TestHover_withValidData(t *testing.T) { "uri": "%s/main.tf" } }`, TempDir(t).URI)}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/hover", @@ -232,6 +233,7 @@ func TestVarsHover_withValidData(t *testing.T) { "uri": "%s/terraform.tfvars" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/hover", diff --git a/internal/langserver/handlers/references_test.go b/internal/langserver/handlers/references_test.go index dedfe1f9..d7eb4177 100644 --- a/internal/langserver/handlers/references_test.go +++ b/internal/langserver/handlers/references_test.go @@ -87,6 +87,8 @@ output "foo" { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/references", ReqParams: fmt.Sprintf(`{ @@ -198,6 +200,8 @@ variable "instances" { "uri": "%s/main.tf" } }`, subHandle.URI)}) + waitForAllJobs(t, ss) + ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/references", ReqParams: fmt.Sprintf(`{ diff --git a/internal/langserver/handlers/semantic_tokens_test.go b/internal/langserver/handlers/semantic_tokens_test.go index f60f5e4b..91799557 100644 --- a/internal/langserver/handlers/semantic_tokens_test.go +++ b/internal/langserver/handlers/semantic_tokens_test.go @@ -113,6 +113,7 @@ func TestSemanticTokensFull(t *testing.T) { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/semanticTokens/full", @@ -233,6 +234,7 @@ func TestSemanticTokensFull_clientSupportsDelta(t *testing.T) { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/semanticTokens/full", @@ -360,6 +362,7 @@ func TestVarsSemanticTokensFull(t *testing.T) { "uri": "%s/terraform.tfvars" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/semanticTokens/full", diff --git a/internal/langserver/handlers/service.go b/internal/langserver/handlers/service.go index 3f0683d0..f7931f8f 100644 --- a/internal/langserver/handlers/service.go +++ b/internal/langserver/handlers/service.go @@ -43,6 +43,7 @@ type service struct { sessCtx context.Context stopSession context.CancelFunc + // TODO: Rename to *scheduler to avoid confusion lowPrioIndexer *scheduler.Scheduler highPrioIndexer *scheduler.Scheduler diff --git a/internal/langserver/handlers/symbols_test.go b/internal/langserver/handlers/symbols_test.go index 733aa24e..d45fdb82 100644 --- a/internal/langserver/handlers/symbols_test.go +++ b/internal/langserver/handlers/symbols_test.go @@ -72,6 +72,7 @@ func TestLangServer_symbols_basic(t *testing.T) { "uri": "%s/main.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "textDocument/documentSymbol", diff --git a/internal/langserver/handlers/workspace_symbol_test.go b/internal/langserver/handlers/workspace_symbol_test.go index 3982a9a7..20fe92c1 100644 --- a/internal/langserver/handlers/workspace_symbol_test.go +++ b/internal/langserver/handlers/workspace_symbol_test.go @@ -90,6 +90,7 @@ func TestLangServer_workspace_symbol_basic(t *testing.T) { "uri": "%s/blah/third.tf" } }`, tmpDir.URI)}) + waitForAllJobs(t, ss) ls.CallAndExpectResponse(t, &langserver.CallRequest{ Method: "workspace/symbol", diff --git a/internal/state/jobs.go b/internal/state/jobs.go index d8159a63..425a5ae8 100644 --- a/internal/state/jobs.go +++ b/internal/state/jobs.go @@ -540,6 +540,23 @@ func (js *JobStore) ListQueuedJobs() (job.IDs, error) { return jobIDs, nil } +func (js *JobStore) ListAllJobs() (job.IDs, error) { + txn := js.db.Txn(false) + + it, err := txn.Get(js.tableName, "id") + if err != nil { + return nil, err + } + + jobIDs := make(job.IDs, 0) + for obj := it.Next(); obj != nil; obj = it.Next() { + sj := obj.(*ScheduledJob) + jobIDs = append(jobIDs, sj.ID) + } + + return jobIDs, nil +} + func (js *JobStore) allJobs() (job.IDs, error) { txn := js.db.Txn(false)