Skip to content

Commit

Permalink
Add DecodeReferenceOrigins and DecodeReferenceTargets jobs
Browse files Browse the repository at this point in the history
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 5, 2024
1 parent 5e25bd3 commit 35d10f3
Show file tree
Hide file tree
Showing 6 changed files with 330 additions and 20 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
63 changes: 45 additions & 18 deletions internal/features/stacks/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,6 @@ 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
validationOptions, _ := lsctx.ValidationOptions(ctx)

metaId, err := f.stateStore.JobStore.EnqueueJob(ctx, job.Job{
Dir: dir,
Func: func(ctx context.Context) error {
Expand Down Expand Up @@ -238,21 +234,33 @@ func (f *StacksFeature) decodeStack(ctx context.Context, dir document.DirHandle,
}
deferIds = append(deferIds, eSchemaId)

if validationOptions.EnableEnhancedValidation {
validationId, 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,
IgnoreState: ignoreState,
})
if err != nil {
return deferIds, err
}
deferIds = append(deferIds, validationId)
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: job.IDs{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: job.IDs{eSchemaId},
IgnoreState: ignoreState,
})
if err != nil {
return deferIds, err
}
deferIds = append(deferIds, refOriginsId)

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

validationOptions, err := lsctx.ValidationOptions(ctx)
if err != nil {
return ids, err
}
if validationOptions.EnableEnhancedValidation {
_, 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: ids,
IgnoreState: ignoreState,
})
if err != nil {
return ids, err
}
}

// TODO: Implement the following functions where appropriate to stacks
// Future: decodeDeclaredModuleCalls(ctx, dir, ignoreState)
// Future: DecodeReferenceTargets(ctx, f.Store, f.rootFeature, path)
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 35d10f3

Please sign in to comment.