Skip to content

Commit fa7a9bb

Browse files
committed
(TF-18664) Add DecodeReferenceOrigins and DecodeReferenceTargets jobs (#1786)
* Add DecodeReferenceOrigins and DecodeReferenceTargets jobs This commit adds two new jobs, DecodeReferenceOrigins and DecodeReferenceTargets, and their supporting plumbing to the stacks feature. These jobs are responsible for collecting reference origins and targets, respectively. Reference origins and targets are used to determine where a reference is defined and where it is used. This information is useful for features like go-to-definition and go-to-references.
1 parent ac936de commit fa7a9bb

File tree

6 files changed

+328
-15
lines changed

6 files changed

+328
-15
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: ENHANCEMENTS
2+
body: Add DecodeReferenceOrigins and DecodeReferenceTargets jobs
3+
time: 2024-08-05T14:05:26.030294-04:00
4+
custom:
5+
Issue: "1786"
6+
Repository: terraform-ls

internal/features/stacks/decoder/path_reader.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,19 @@ func stackPathContext(record *state.StackRecord, stateReader CombinedReader) (*d
106106
}
107107

108108
// TODO: Add reference origins and targets if needed
109+
for _, origin := range record.RefOrigins {
110+
if ast.IsStackFilename(origin.OriginRange().Filename) {
111+
pathCtx.ReferenceOrigins = append(pathCtx.ReferenceOrigins, origin)
112+
}
113+
}
114+
115+
for _, target := range record.RefTargets {
116+
if target.RangePtr != nil && ast.IsStackFilename(target.RangePtr.Filename) {
117+
pathCtx.ReferenceTargets = append(pathCtx.ReferenceTargets, target)
118+
} else if target.RangePtr == nil {
119+
pathCtx.ReferenceTargets = append(pathCtx.ReferenceTargets, target)
120+
}
121+
}
109122

110123
for name, f := range record.ParsedFiles {
111124
if _, ok := name.(ast.StackFilename); ok {
@@ -153,6 +166,19 @@ func deployPathContext(record *state.StackRecord) (*decoder.PathContext, error)
153166
}
154167

155168
// TODO: Add reference origins and targets if needed
169+
for _, origin := range record.RefOrigins {
170+
if ast.IsDeployFilename(origin.OriginRange().Filename) {
171+
pathCtx.ReferenceOrigins = append(pathCtx.ReferenceOrigins, origin)
172+
}
173+
}
174+
175+
for _, target := range record.RefTargets {
176+
if target.RangePtr != nil && ast.IsDeployFilename(target.RangePtr.Filename) {
177+
pathCtx.ReferenceTargets = append(pathCtx.ReferenceTargets, target)
178+
} else if target.RangePtr == nil {
179+
pathCtx.ReferenceTargets = append(pathCtx.ReferenceTargets, target)
180+
}
181+
}
156182

157183
for name, f := range record.ParsedFiles {
158184
if _, ok := name.(ast.DeployFilename); ok {

internal/features/stacks/events.go

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,13 @@ func (f *StacksFeature) decodeStack(ctx context.Context, dir document.DirHandle,
192192
}
193193
ids = append(ids, parseId)
194194

195-
// this needs to be here because the setting context
196-
// is not available in the validate job
195+
// Changes to a setting currently requires a LS restart, so the LS
196+
// setting context cannot change during the execution of a job. That's
197+
// why we can extract it here and use it in Defer.
198+
// See https://github.com/hashicorp/terraform-ls/issues/1008
199+
// We can safely ignore the error here. If we can't get the options from
200+
// the context, validationOptions.EnableEnhancedValidation will be false
201+
// by default. So we don't run the validation jobs.
197202
validationOptions, _ := lsctx.ValidationOptions(ctx)
198203

199204
metaId, err := f.stateStore.JobStore.EnqueueJob(ctx, job.Job{
@@ -211,10 +216,13 @@ func (f *StacksFeature) decodeStack(ctx context.Context, dir document.DirHandle,
211216
f.logger.Printf("loading module metadata returned error: %s", jobErr)
212217
}
213218

214-
spawnedIds, err := loadStackComponentSources(ctx, f.store, f.bus, path)
215-
deferIds = append(deferIds, spawnedIds...)
219+
componentIds, err := loadStackComponentSources(ctx, f.store, f.bus, path)
220+
deferIds = append(deferIds, componentIds...)
216221
if err != nil {
217222
f.logger.Printf("loading stack component sources returned error: %s", err)
223+
// We log the error but still continue scheduling other jobs
224+
// which are still valuable for the rest of the configuration
225+
// even if they may not have the data for module calls.
218226
}
219227

220228
// while we now have the job ids in here, depending on the metaId job is not enough
@@ -238,20 +246,47 @@ func (f *StacksFeature) decodeStack(ctx context.Context, dir document.DirHandle,
238246
}
239247
deferIds = append(deferIds, eSchemaId)
240248

249+
refTargetsId, err := f.stateStore.JobStore.EnqueueJob(ctx, job.Job{
250+
Dir: dir,
251+
Func: func(ctx context.Context) error {
252+
return jobs.DecodeReferenceTargets(ctx, f.store, f.moduleFeature, path)
253+
},
254+
Type: operation.OpTypeDecodeReferenceTargets.String(),
255+
DependsOn: append(componentIds, eSchemaId),
256+
IgnoreState: ignoreState,
257+
})
258+
if err != nil {
259+
return deferIds, err
260+
}
261+
deferIds = append(deferIds, refTargetsId)
262+
263+
refOriginsId, err := f.stateStore.JobStore.EnqueueJob(ctx, job.Job{
264+
Dir: dir,
265+
Func: func(ctx context.Context) error {
266+
return jobs.DecodeReferenceOrigins(ctx, f.store, f.moduleFeature, path)
267+
},
268+
Type: operation.OpTypeDecodeReferenceOrigins.String(),
269+
DependsOn: append(componentIds, eSchemaId),
270+
IgnoreState: ignoreState,
271+
})
272+
if err != nil {
273+
return deferIds, err
274+
}
275+
deferIds = append(deferIds, refOriginsId)
276+
241277
if validationOptions.EnableEnhancedValidation {
242-
validationId, err := f.stateStore.JobStore.EnqueueJob(ctx, job.Job{
278+
_, err := f.stateStore.JobStore.EnqueueJob(ctx, job.Job{
243279
Dir: dir,
244280
Func: func(ctx context.Context) error {
245281
return jobs.SchemaStackValidation(ctx, f.store, f.moduleFeature, dir.Path())
246282
},
247283
Type: operation.OpTypeSchemaStackValidation.String(),
248-
DependsOn: deferIds,
284+
DependsOn: job.IDs{refOriginsId, refTargetsId},
249285
IgnoreState: ignoreState,
250286
})
251287
if err != nil {
252-
return deferIds, err
288+
return ids, err
253289
}
254-
deferIds = append(deferIds, validationId)
255290
}
256291

257292
return deferIds, nil
@@ -262,11 +297,6 @@ func (f *StacksFeature) decodeStack(ctx context.Context, dir document.DirHandle,
262297
}
263298
ids = append(ids, metaId)
264299

265-
// TODO: Implement the following functions where appropriate to stacks
266-
// Future: decodeDeclaredModuleCalls(ctx, dir, ignoreState)
267-
// Future: DecodeReferenceTargets(ctx, f.Store, f.rootFeature, path)
268-
// Future: DecodeReferenceOrigins(ctx, f.Store, f.rootFeature, path)
269-
270300
return ids, nil
271301
}
272302

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package jobs
5+
6+
import (
7+
"context"
8+
9+
"github.com/hashicorp/hcl-lang/decoder"
10+
"github.com/hashicorp/hcl-lang/lang"
11+
"github.com/hashicorp/hcl-lang/reference"
12+
idecoder "github.com/hashicorp/terraform-ls/internal/decoder"
13+
"github.com/hashicorp/terraform-ls/internal/document"
14+
sdecoder "github.com/hashicorp/terraform-ls/internal/features/stacks/decoder"
15+
"github.com/hashicorp/terraform-ls/internal/features/stacks/state"
16+
"github.com/hashicorp/terraform-ls/internal/job"
17+
ilsp "github.com/hashicorp/terraform-ls/internal/lsp"
18+
"github.com/hashicorp/terraform-ls/internal/terraform/module/operation"
19+
)
20+
21+
// DecodeReferenceTargets collects reference targets,
22+
// using previously parsed AST (via [ParseStackConfiguration]),
23+
// core schema of appropriate version (as obtained via [GetTerraformVersion])
24+
// and provider schemas ([PreloadEmbeddedSchema] or [ObtainSchema]).
25+
//
26+
// For example it tells us that variable block between certain LOC
27+
// can be referred to as var.foobar. This is useful e.g. during completion,
28+
// go-to-definition or go-to-references.
29+
func DecodeReferenceTargets(ctx context.Context, stackStore *state.StackStore, moduleReader sdecoder.ModuleReader, stackPath string) error {
30+
mod, err := stackStore.StackRecordByPath(stackPath)
31+
if err != nil {
32+
return err
33+
}
34+
35+
// TODO: Avoid collection if upstream jobs reported no changes
36+
37+
// Avoid collection if it is already in progress or already done
38+
if mod.RefTargetsState != operation.OpStateUnknown && !job.IgnoreState(ctx) {
39+
return job.StateNotChangedErr{Dir: document.DirHandleFromPath(stackPath)}
40+
}
41+
42+
err = stackStore.SetReferenceTargetsState(stackPath, operation.OpStateLoading)
43+
if err != nil {
44+
return err
45+
}
46+
47+
d := decoder.NewDecoder(&sdecoder.PathReader{
48+
StateReader: stackStore,
49+
ModuleReader: moduleReader,
50+
})
51+
d.SetContext(idecoder.DecoderContext(ctx))
52+
53+
stackDecoder, err := d.Path(lang.Path{
54+
Path: stackPath,
55+
LanguageID: ilsp.Stacks.String(),
56+
})
57+
if err != nil {
58+
return err
59+
}
60+
stackTargets, rErr := stackDecoder.CollectReferenceTargets()
61+
62+
deployDecoder, err := d.Path(lang.Path{
63+
Path: stackPath,
64+
LanguageID: ilsp.Deploy.String(),
65+
})
66+
if err != nil {
67+
return err
68+
}
69+
deployTargets, rErr := deployDecoder.CollectReferenceTargets()
70+
71+
targets := make(reference.Targets, 0)
72+
targets = append(targets, stackTargets...)
73+
targets = append(targets, deployTargets...)
74+
75+
sErr := stackStore.UpdateReferenceTargets(stackPath, targets, rErr)
76+
if sErr != nil {
77+
return sErr
78+
}
79+
80+
return rErr
81+
}
82+
83+
// DecodeReferenceOrigins collects reference origins,
84+
// using previously parsed AST (via [ParseStackConfiguration]),
85+
// core schema of appropriate version (as obtained via [GetTerraformVersion])
86+
// and provider schemas ([PreloadEmbeddedSchema] or [ObtainSchema]).
87+
//
88+
// For example it tells us that there is a reference address var.foobar
89+
// at a particular LOC. This can be later matched with targets
90+
// (as obtained via [DecodeReferenceTargets]) during hover or go-to-definition.
91+
func DecodeReferenceOrigins(ctx context.Context, stackStore *state.StackStore, moduleReader sdecoder.ModuleReader, stackPath string) error {
92+
mod, err := stackStore.StackRecordByPath(stackPath)
93+
if err != nil {
94+
return err
95+
}
96+
97+
// TODO: Avoid collection if upstream jobs reported no changes
98+
99+
// Avoid collection if it is already in progress or already done
100+
if mod.RefOriginsState != operation.OpStateUnknown && !job.IgnoreState(ctx) {
101+
return job.StateNotChangedErr{Dir: document.DirHandleFromPath(stackPath)}
102+
}
103+
104+
err = stackStore.SetReferenceOriginsState(stackPath, operation.OpStateLoading)
105+
if err != nil {
106+
return err
107+
}
108+
109+
d := decoder.NewDecoder(&sdecoder.PathReader{
110+
StateReader: stackStore,
111+
ModuleReader: moduleReader,
112+
})
113+
d.SetContext(idecoder.DecoderContext(ctx))
114+
115+
stackDecoder, err := d.Path(lang.Path{
116+
Path: stackPath,
117+
LanguageID: ilsp.Stacks.String(),
118+
})
119+
if err != nil {
120+
return err
121+
}
122+
stackOrigins, rErr := stackDecoder.CollectReferenceOrigins()
123+
124+
deployDecoder, err := d.Path(lang.Path{
125+
Path: stackPath,
126+
LanguageID: ilsp.Deploy.String(),
127+
})
128+
if err != nil {
129+
return err
130+
}
131+
deployOrigins, rErr := deployDecoder.CollectReferenceOrigins()
132+
133+
origins := make(reference.Origins, 0)
134+
origins = append(origins, stackOrigins...)
135+
origins = append(origins, deployOrigins...)
136+
137+
sErr := stackStore.UpdateReferenceOrigins(stackPath, origins, rErr)
138+
if sErr != nil {
139+
return sErr
140+
}
141+
142+
return rErr
143+
}

internal/features/stacks/state/stack_record.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package state
55

66
import (
77
"github.com/hashicorp/go-version"
8+
"github.com/hashicorp/hcl-lang/reference"
89
"github.com/hashicorp/hcl/v2"
910
"github.com/hashicorp/terraform-ls/internal/features/stacks/ast"
1011
globalAst "github.com/hashicorp/terraform-ls/internal/terraform/ast"
@@ -34,6 +35,14 @@ type StackRecord struct {
3435
RequiredTerraformVersion *version.Version
3536
RequiredTerraformVersionErr error
3637
RequiredTerraformVersionState operation.OpState
38+
39+
RefTargets reference.Targets
40+
RefTargetsErr error
41+
RefTargetsState operation.OpState
42+
43+
RefOrigins reference.Origins
44+
RefOriginsErr error
45+
RefOriginsState operation.OpState
3746
}
3847

3948
func (m *StackRecord) Path() string {
@@ -59,6 +68,14 @@ func (m *StackRecord) Copy() *StackRecord {
5968
RequiredTerraformVersion: m.RequiredTerraformVersion,
6069
RequiredTerraformVersionErr: m.RequiredTerraformVersionErr,
6170
RequiredTerraformVersionState: m.RequiredTerraformVersionState,
71+
72+
RefTargets: m.RefTargets.Copy(),
73+
RefTargetsErr: m.RefTargetsErr,
74+
RefTargetsState: m.RefTargetsState,
75+
76+
RefOrigins: m.RefOrigins.Copy(),
77+
RefOriginsErr: m.RefOriginsErr,
78+
RefOriginsState: m.RefOriginsState,
6279
}
6380

6481
if m.ParsedFiles != nil {
@@ -85,9 +102,13 @@ func (m *StackRecord) Copy() *StackRecord {
85102
return newRecord
86103
}
87104

88-
func newStack(modPath string) *StackRecord {
105+
func newStack(stackPath string) *StackRecord {
89106
return &StackRecord{
90-
path: modPath,
107+
path: stackPath,
108+
PreloadEmbeddedSchemaState: operation.OpStateUnknown,
109+
RefOriginsState: operation.OpStateUnknown,
110+
RefTargetsState: operation.OpStateUnknown,
111+
MetaState: operation.OpStateUnknown,
91112
DiagnosticsState: globalAst.DiagnosticSourceState{
92113
globalAst.HCLParsingSource: operation.OpStateUnknown,
93114
globalAst.SchemaValidationSource: operation.OpStateUnknown,

0 commit comments

Comments
 (0)