Skip to content

Commit 08f226d

Browse files
authored
fix: decode all submodules with the right path (hashicorp#810)
* handler: add test for completion w/ multiple modules * fix: copy module path instead of using reference https://github.com/golang/go/wiki/CommonMistakes#using-reference-to-loop-iterator-variable
1 parent 62002c3 commit 08f226d

File tree

2 files changed

+355
-4
lines changed

2 files changed

+355
-4
lines changed

internal/langserver/handlers/complete_test.go

+349
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,355 @@ output "test" {
882882
}`)
883883
}
884884

885+
func TestCompletion_multipleModulesWithValidData(t *testing.T) {
886+
tmpDir := TempDir(t)
887+
888+
writeContentToFile(t, filepath.Join(tmpDir.Path(), "submodule-alpha", "main.tf"), `
889+
variable "alpha-var" {
890+
type = string
891+
}
892+
893+
output "alpha-out" {
894+
value = 1
895+
}
896+
`)
897+
writeContentToFile(t, filepath.Join(tmpDir.Path(), "submodule-beta", "main.tf"), `
898+
variable "beta-var" {
899+
type = number
900+
}
901+
902+
output "beta-out" {
903+
value = 2
904+
}
905+
`)
906+
mainCfg := `module "alpha" {
907+
source = "./submodule-alpha"
908+
909+
}
910+
module "beta" {
911+
source = "./submodule-beta"
912+
913+
}
914+
915+
output "test" {
916+
917+
}
918+
`
919+
writeContentToFile(t, filepath.Join(tmpDir.Path(), "main.tf"), mainCfg)
920+
mainCfg = `module "alpha" {
921+
source = "./submodule-alpha"
922+
923+
}
924+
module "beta" {
925+
source = "./submodule-beta"
926+
927+
}
928+
929+
output "test" {
930+
value = module.
931+
}
932+
`
933+
934+
tfExec := tfExecutor(t, tmpDir.Path(), "1.0.2")
935+
err := tfExec.Get(context.Background())
936+
if err != nil {
937+
t.Fatal(err)
938+
}
939+
940+
var testSchema tfjson.ProviderSchemas
941+
err = json.Unmarshal([]byte(testModuleSchemaOutput), &testSchema)
942+
if err != nil {
943+
t.Fatal(err)
944+
}
945+
946+
ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{
947+
TerraformCalls: &exec.TerraformMockCalls{
948+
PerWorkDir: map[string][]*mock.Call{
949+
tmpDir.Path(): {
950+
{
951+
Method: "Version",
952+
Repeatability: 1,
953+
Arguments: []interface{}{
954+
mock.AnythingOfType(""),
955+
},
956+
ReturnArguments: []interface{}{
957+
version.Must(version.NewVersion("0.12.0")),
958+
nil,
959+
nil,
960+
},
961+
},
962+
{
963+
Method: "GetExecPath",
964+
Repeatability: 1,
965+
ReturnArguments: []interface{}{
966+
"",
967+
},
968+
},
969+
{
970+
Method: "ProviderSchemas",
971+
Repeatability: 1,
972+
Arguments: []interface{}{
973+
mock.AnythingOfType(""),
974+
},
975+
ReturnArguments: []interface{}{
976+
&testSchema,
977+
nil,
978+
},
979+
},
980+
},
981+
},
982+
}}))
983+
stop := ls.Start(t)
984+
defer stop()
985+
986+
ls.Call(t, &langserver.CallRequest{
987+
Method: "initialize",
988+
ReqParams: fmt.Sprintf(`{
989+
"capabilities": {},
990+
"rootUri": %q,
991+
"processId": 12345
992+
}`, tmpDir.URI)})
993+
ls.Notify(t, &langserver.CallRequest{
994+
Method: "initialized",
995+
ReqParams: "{}",
996+
})
997+
ls.Call(t, &langserver.CallRequest{
998+
Method: "textDocument/didOpen",
999+
ReqParams: fmt.Sprintf(`{
1000+
"textDocument": {
1001+
"version": 0,
1002+
"languageId": "terraform",
1003+
"text": %q,
1004+
"uri": "%s/main.tf"
1005+
}
1006+
}`, mainCfg, tmpDir.URI)})
1007+
1008+
// first module
1009+
ls.CallAndExpectResponse(t, &langserver.CallRequest{
1010+
Method: "textDocument/completion",
1011+
ReqParams: fmt.Sprintf(`{
1012+
"textDocument": {
1013+
"uri": "%s/main.tf"
1014+
},
1015+
"position": {
1016+
"character": 0,
1017+
"line": 2
1018+
}
1019+
}`, tmpDir.URI)}, `{
1020+
"jsonrpc": "2.0",
1021+
"id": 3,
1022+
"result": {
1023+
"isIncomplete": false,
1024+
"items": [
1025+
{
1026+
"label": "alpha-var",
1027+
"labelDetails": {},
1028+
"kind": 10,
1029+
"detail": "required, string",
1030+
"insertTextFormat": 1,
1031+
"textEdit": {
1032+
"range": {
1033+
"start": {
1034+
"line": 2,
1035+
"character": 0
1036+
},
1037+
"end": {
1038+
"line": 2,
1039+
"character": 0
1040+
}
1041+
},
1042+
"newText": "alpha-var"
1043+
}
1044+
},
1045+
{
1046+
"label": "providers",
1047+
"labelDetails": {},
1048+
"kind": 10,
1049+
"detail": "optional, map of provider references",
1050+
"documentation": "Explicit mapping of providers which the module uses",
1051+
"insertTextFormat": 1,
1052+
"textEdit": {
1053+
"range": {
1054+
"start": {
1055+
"line": 2,
1056+
"character": 0
1057+
},
1058+
"end": {
1059+
"line": 2,
1060+
"character": 0
1061+
}
1062+
},
1063+
"newText": "providers"
1064+
}
1065+
},
1066+
{
1067+
"label": "version",
1068+
"labelDetails": {},
1069+
"kind": 10,
1070+
"detail": "optional, string",
1071+
"documentation": "Constraint to set the version of the module, e.g. ~\u003e 1.0. Only applicable to modules in a module registry.",
1072+
"insertTextFormat": 1,
1073+
"textEdit": {
1074+
"range": {
1075+
"start": {
1076+
"line": 2,
1077+
"character": 0
1078+
},
1079+
"end": {
1080+
"line": 2,
1081+
"character": 0
1082+
}
1083+
},
1084+
"newText": "version"
1085+
}
1086+
}
1087+
]
1088+
}
1089+
}`)
1090+
// second module
1091+
ls.CallAndExpectResponse(t, &langserver.CallRequest{
1092+
Method: "textDocument/completion",
1093+
ReqParams: fmt.Sprintf(`{
1094+
"textDocument": {
1095+
"uri": "%s/main.tf"
1096+
},
1097+
"position": {
1098+
"character": 0,
1099+
"line": 6
1100+
}
1101+
}`, tmpDir.URI)}, `{
1102+
"jsonrpc": "2.0",
1103+
"id": 4,
1104+
"result": {
1105+
"isIncomplete": false,
1106+
"items": [
1107+
{
1108+
"label": "beta-var",
1109+
"labelDetails": {},
1110+
"kind": 10,
1111+
"detail": "required, number",
1112+
"insertTextFormat": 1,
1113+
"textEdit": {
1114+
"range": {
1115+
"start": {
1116+
"line": 6,
1117+
"character": 0
1118+
},
1119+
"end": {
1120+
"line": 6,
1121+
"character": 0
1122+
}
1123+
},
1124+
"newText": "beta-var"
1125+
}
1126+
},
1127+
{
1128+
"label": "providers",
1129+
"labelDetails": {},
1130+
"kind": 10,
1131+
"detail": "optional, map of provider references",
1132+
"documentation": "Explicit mapping of providers which the module uses",
1133+
"insertTextFormat": 1,
1134+
"textEdit": {
1135+
"range": {
1136+
"start": {
1137+
"line": 6,
1138+
"character": 0
1139+
},
1140+
"end": {
1141+
"line": 6,
1142+
"character": 0
1143+
}
1144+
},
1145+
"newText": "providers"
1146+
}
1147+
},
1148+
{
1149+
"label": "version",
1150+
"labelDetails": {},
1151+
"kind": 10,
1152+
"detail": "optional, string",
1153+
"documentation": "Constraint to set the version of the module, e.g. ~\u003e 1.0. Only applicable to modules in a module registry.",
1154+
"insertTextFormat": 1,
1155+
"textEdit": {
1156+
"range": {
1157+
"start": {
1158+
"line": 6,
1159+
"character": 0
1160+
},
1161+
"end": {
1162+
"line": 6,
1163+
"character": 0
1164+
}
1165+
},
1166+
"newText": "version"
1167+
}
1168+
}
1169+
]
1170+
}
1171+
}`)
1172+
// outputs
1173+
ls.CallAndExpectResponse(t, &langserver.CallRequest{
1174+
Method: "textDocument/completion",
1175+
ReqParams: fmt.Sprintf(`{
1176+
"textDocument": {
1177+
"uri": "%s/main.tf"
1178+
},
1179+
"position": {
1180+
"character": 17,
1181+
"line": 10
1182+
}
1183+
}`, tmpDir.URI)}, `{
1184+
"jsonrpc": "2.0",
1185+
"id": 5,
1186+
"result": {
1187+
"isIncomplete": false,
1188+
"items": [
1189+
{
1190+
"label": "module.alpha",
1191+
"labelDetails": {},
1192+
"kind": 6,
1193+
"detail": "object",
1194+
"insertTextFormat": 1,
1195+
"textEdit": {
1196+
"range": {
1197+
"start": {
1198+
"line": 10,
1199+
"character": 10
1200+
},
1201+
"end": {
1202+
"line": 10,
1203+
"character": 17
1204+
}
1205+
},
1206+
"newText": "module.alpha"
1207+
}
1208+
},
1209+
{
1210+
"label": "module.beta",
1211+
"labelDetails": {},
1212+
"kind": 6,
1213+
"detail": "object",
1214+
"insertTextFormat": 1,
1215+
"textEdit": {
1216+
"range": {
1217+
"start": {
1218+
"line": 10,
1219+
"character": 10
1220+
},
1221+
"end": {
1222+
"line": 10,
1223+
"character": 17
1224+
}
1225+
},
1226+
"newText": "module.beta"
1227+
}
1228+
}
1229+
]
1230+
}
1231+
}`)
1232+
}
1233+
8851234
func tfExecutor(t *testing.T, workdir, tfVersion string) exec.TerraformExecutor {
8861235
ctx := context.Background()
8871236
installDir := filepath.Join(t.TempDir(), "hcinstall")

internal/terraform/module/watcher.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -373,19 +373,21 @@ func decodeCalledModulesFunc(fs ReadOnlyFS, modStore *state.ModuleStore, schemaR
373373
modStore.Add(mc.Path)
374374

375375
mcHandle := document.DirHandleFromPath(mc.Path)
376+
// copy path for queued jobs below
377+
mcPath := mc.Path
376378

377379
id, err := jobStore.EnqueueJob(job.Job{
378380
Dir: mcHandle,
379381
Func: func(ctx context.Context) error {
380-
return ParseModuleConfiguration(fs, modStore, mc.Path)
382+
return ParseModuleConfiguration(fs, modStore, mcPath)
381383
},
382384
Type: op.OpTypeParseModuleConfiguration.String(),
383385
Defer: func(ctx context.Context, jobErr error) (ids job.IDs) {
384386
id, err := jobStore.EnqueueJob(job.Job{
385387
Dir: mcHandle,
386388
Type: op.OpTypeLoadModuleMetadata.String(),
387389
Func: func(ctx context.Context) error {
388-
return LoadModuleMetadata(modStore, mc.Path)
390+
return LoadModuleMetadata(modStore, mcPath)
389391
},
390392
})
391393
if err != nil {
@@ -407,14 +409,14 @@ func decodeCalledModulesFunc(fs ReadOnlyFS, modStore *state.ModuleStore, schemaR
407409
id, err = jobStore.EnqueueJob(job.Job{
408410
Dir: mcHandle,
409411
Func: func(ctx context.Context) error {
410-
return ParseVariables(fs, modStore, mc.Path)
412+
return ParseVariables(fs, modStore, mcPath)
411413
},
412414
Type: op.OpTypeParseVariables.String(),
413415
Defer: func(ctx context.Context, jobErr error) (ids job.IDs) {
414416
id, err = jobStore.EnqueueJob(job.Job{
415417
Dir: mcHandle,
416418
Func: func(ctx context.Context) error {
417-
return DecodeVarsReferences(ctx, modStore, schemaReader, mc.Path)
419+
return DecodeVarsReferences(ctx, modStore, schemaReader, mcPath)
418420
},
419421
Type: op.OpTypeDecodeVarsReferences.String(),
420422
})

0 commit comments

Comments
 (0)