Skip to content

Commit

Permalink
(TF-18664) Add DecodeReferenceOrigins and DecodeReferenceTargets jobs (
Browse files Browse the repository at this point in the history
…#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.
  • Loading branch information
jpogran committed Aug 30, 2024
1 parent 7d287cc commit 168d44e
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 15 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/ENHANCEMENTS-20240805-140526.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: ENHANCEMENTS
body: Add DecodeReferenceOrigins and DecodeReferenceTargets jobs
time: 2024-08-05T14:05:26.030294-04:00
custom:
Issue: "1786"
Repository: terraform-ls
26 changes: 26 additions & 0 deletions internal/features/stacks/decoder/path_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@ func stackPathContext(record *state.StackRecord, stateReader CombinedReader) (*d
}

// TODO: Add reference origins and targets if needed
for _, origin := range record.RefOrigins {
if ast.IsStackFilename(origin.OriginRange().Filename) {
pathCtx.ReferenceOrigins = append(pathCtx.ReferenceOrigins, origin)
}
}

for _, target := range record.RefTargets {
if target.RangePtr != nil && ast.IsStackFilename(target.RangePtr.Filename) {
pathCtx.ReferenceTargets = append(pathCtx.ReferenceTargets, target)
} else if target.RangePtr == nil {
pathCtx.ReferenceTargets = append(pathCtx.ReferenceTargets, target)
}
}

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

// TODO: Add reference origins and targets if needed
for _, origin := range record.RefOrigins {
if ast.IsDeployFilename(origin.OriginRange().Filename) {
pathCtx.ReferenceOrigins = append(pathCtx.ReferenceOrigins, origin)
}
}

for _, target := range record.RefTargets {
if target.RangePtr != nil && ast.IsDeployFilename(target.RangePtr.Filename) {
pathCtx.ReferenceTargets = append(pathCtx.ReferenceTargets, target)
} else if target.RangePtr == nil {
pathCtx.ReferenceTargets = append(pathCtx.ReferenceTargets, target)
}
}

for name, f := range record.ParsedFiles {
if _, ok := name.(ast.DeployFilename); ok {
Expand Down
56 changes: 43 additions & 13 deletions internal/features/stacks/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,13 @@ func (f *StacksFeature) decodeStack(ctx context.Context, dir document.DirHandle,
}
ids = append(ids, parseId)

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

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

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

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

refTargetsId, err := f.stateStore.JobStore.EnqueueJob(ctx, job.Job{
Dir: dir,
Func: func(ctx context.Context) error {
return jobs.DecodeReferenceTargets(ctx, f.store, f.moduleFeature, path)
},
Type: operation.OpTypeDecodeReferenceTargets.String(),
DependsOn: append(componentIds, eSchemaId),
IgnoreState: ignoreState,
})
if err != nil {
return deferIds, err
}
deferIds = append(deferIds, refTargetsId)

refOriginsId, err := f.stateStore.JobStore.EnqueueJob(ctx, job.Job{
Dir: dir,
Func: func(ctx context.Context) error {
return jobs.DecodeReferenceOrigins(ctx, f.store, f.moduleFeature, path)
},
Type: operation.OpTypeDecodeReferenceOrigins.String(),
DependsOn: append(componentIds, eSchemaId),
IgnoreState: ignoreState,
})
if err != nil {
return deferIds, err
}
deferIds = append(deferIds, refOriginsId)

if validationOptions.EnableEnhancedValidation {
validationId, err := f.stateStore.JobStore.EnqueueJob(ctx, job.Job{
_, err := f.stateStore.JobStore.EnqueueJob(ctx, job.Job{
Dir: dir,
Func: func(ctx context.Context) error {
return jobs.SchemaStackValidation(ctx, f.store, f.moduleFeature, dir.Path())
},
Type: operation.OpTypeSchemaStackValidation.String(),
DependsOn: deferIds,
DependsOn: job.IDs{refOriginsId, refTargetsId},
IgnoreState: ignoreState,
})
if err != nil {
return deferIds, err
return ids, err
}
deferIds = append(deferIds, validationId)
}

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

// TODO: Implement the following functions where appropriate to stacks
// Future: decodeDeclaredModuleCalls(ctx, dir, ignoreState)
// Future: DecodeReferenceTargets(ctx, f.Store, f.rootFeature, path)
// Future: DecodeReferenceOrigins(ctx, f.Store, f.rootFeature, path)

return ids, nil
}

Expand Down
143 changes: 143 additions & 0 deletions internal/features/stacks/jobs/references.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package jobs

import (
"context"

"github.com/hashicorp/hcl-lang/decoder"
"github.com/hashicorp/hcl-lang/lang"
"github.com/hashicorp/hcl-lang/reference"
idecoder "github.com/hashicorp/terraform-ls/internal/decoder"
"github.com/hashicorp/terraform-ls/internal/document"
sdecoder "github.com/hashicorp/terraform-ls/internal/features/stacks/decoder"
"github.com/hashicorp/terraform-ls/internal/features/stacks/state"
"github.com/hashicorp/terraform-ls/internal/job"
ilsp "github.com/hashicorp/terraform-ls/internal/lsp"
"github.com/hashicorp/terraform-ls/internal/terraform/module/operation"
)

// DecodeReferenceTargets collects reference targets,
// using previously parsed AST (via [ParseStackConfiguration]),
// core schema of appropriate version (as obtained via [GetTerraformVersion])
// and provider schemas ([PreloadEmbeddedSchema] or [ObtainSchema]).
//
// For example it tells us that variable block between certain LOC
// can be referred to as var.foobar. This is useful e.g. during completion,
// go-to-definition or go-to-references.
func DecodeReferenceTargets(ctx context.Context, stackStore *state.StackStore, moduleReader sdecoder.ModuleReader, stackPath string) error {
mod, err := stackStore.StackRecordByPath(stackPath)
if err != nil {
return err
}

// TODO: Avoid collection if upstream jobs reported no changes

// Avoid collection if it is already in progress or already done
if mod.RefTargetsState != operation.OpStateUnknown && !job.IgnoreState(ctx) {
return job.StateNotChangedErr{Dir: document.DirHandleFromPath(stackPath)}
}

err = stackStore.SetReferenceTargetsState(stackPath, operation.OpStateLoading)
if err != nil {
return err
}

d := decoder.NewDecoder(&sdecoder.PathReader{
StateReader: stackStore,
ModuleReader: moduleReader,
})
d.SetContext(idecoder.DecoderContext(ctx))

stackDecoder, err := d.Path(lang.Path{
Path: stackPath,
LanguageID: ilsp.Stacks.String(),
})
if err != nil {
return err
}
stackTargets, rErr := stackDecoder.CollectReferenceTargets()

deployDecoder, err := d.Path(lang.Path{
Path: stackPath,
LanguageID: ilsp.Deploy.String(),
})
if err != nil {
return err
}
deployTargets, rErr := deployDecoder.CollectReferenceTargets()

targets := make(reference.Targets, 0)
targets = append(targets, stackTargets...)
targets = append(targets, deployTargets...)

sErr := stackStore.UpdateReferenceTargets(stackPath, targets, rErr)
if sErr != nil {
return sErr
}

return rErr
}

// DecodeReferenceOrigins collects reference origins,
// using previously parsed AST (via [ParseStackConfiguration]),
// core schema of appropriate version (as obtained via [GetTerraformVersion])
// and provider schemas ([PreloadEmbeddedSchema] or [ObtainSchema]).
//
// For example it tells us that there is a reference address var.foobar
// at a particular LOC. This can be later matched with targets
// (as obtained via [DecodeReferenceTargets]) during hover or go-to-definition.
func DecodeReferenceOrigins(ctx context.Context, stackStore *state.StackStore, moduleReader sdecoder.ModuleReader, stackPath string) error {
mod, err := stackStore.StackRecordByPath(stackPath)
if err != nil {
return err
}

// TODO: Avoid collection if upstream jobs reported no changes

// Avoid collection if it is already in progress or already done
if mod.RefOriginsState != operation.OpStateUnknown && !job.IgnoreState(ctx) {
return job.StateNotChangedErr{Dir: document.DirHandleFromPath(stackPath)}
}

err = stackStore.SetReferenceOriginsState(stackPath, operation.OpStateLoading)
if err != nil {
return err
}

d := decoder.NewDecoder(&sdecoder.PathReader{
StateReader: stackStore,
ModuleReader: moduleReader,
})
d.SetContext(idecoder.DecoderContext(ctx))

stackDecoder, err := d.Path(lang.Path{
Path: stackPath,
LanguageID: ilsp.Stacks.String(),
})
if err != nil {
return err
}
stackOrigins, rErr := stackDecoder.CollectReferenceOrigins()

deployDecoder, err := d.Path(lang.Path{
Path: stackPath,
LanguageID: ilsp.Deploy.String(),
})
if err != nil {
return err
}
deployOrigins, rErr := deployDecoder.CollectReferenceOrigins()

origins := make(reference.Origins, 0)
origins = append(origins, stackOrigins...)
origins = append(origins, deployOrigins...)

sErr := stackStore.UpdateReferenceOrigins(stackPath, origins, rErr)
if sErr != nil {
return sErr
}

return rErr
}
25 changes: 23 additions & 2 deletions internal/features/stacks/state/stack_record.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package state

import (
"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl-lang/reference"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform-ls/internal/features/stacks/ast"
globalAst "github.com/hashicorp/terraform-ls/internal/terraform/ast"
Expand Down Expand Up @@ -34,6 +35,14 @@ type StackRecord struct {
RequiredTerraformVersion *version.Version
RequiredTerraformVersionErr error
RequiredTerraformVersionState operation.OpState

RefTargets reference.Targets
RefTargetsErr error
RefTargetsState operation.OpState

RefOrigins reference.Origins
RefOriginsErr error
RefOriginsState operation.OpState
}

func (m *StackRecord) Path() string {
Expand All @@ -59,6 +68,14 @@ func (m *StackRecord) Copy() *StackRecord {
RequiredTerraformVersion: m.RequiredTerraformVersion,
RequiredTerraformVersionErr: m.RequiredTerraformVersionErr,
RequiredTerraformVersionState: m.RequiredTerraformVersionState,

RefTargets: m.RefTargets.Copy(),
RefTargetsErr: m.RefTargetsErr,
RefTargetsState: m.RefTargetsState,

RefOrigins: m.RefOrigins.Copy(),
RefOriginsErr: m.RefOriginsErr,
RefOriginsState: m.RefOriginsState,
}

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

func newStack(modPath string) *StackRecord {
func newStack(stackPath string) *StackRecord {
return &StackRecord{
path: modPath,
path: stackPath,
PreloadEmbeddedSchemaState: operation.OpStateUnknown,
RefOriginsState: operation.OpStateUnknown,
RefTargetsState: operation.OpStateUnknown,
MetaState: operation.OpStateUnknown,
DiagnosticsState: globalAst.DiagnosticSourceState{
globalAst.HCLParsingSource: operation.OpStateUnknown,
globalAst.SchemaValidationSource: operation.OpStateUnknown,
Expand Down
Loading

0 comments on commit 168d44e

Please sign in to comment.