Skip to content

Commit

Permalink
Add support for traversals/references
Browse files Browse the repository at this point in the history
  • Loading branch information
radeksimko committed May 5, 2021
1 parent 64ec698 commit 04eb478
Show file tree
Hide file tree
Showing 15 changed files with 187 additions and 12 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ require (
github.com/hashicorp/go-memdb v1.3.2
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-version v1.3.0
github.com/hashicorp/hcl-lang v0.0.0-20210419185146-8556dd730bc7
github.com/hashicorp/hcl-lang v0.0.0-20210505191011-f735b3e2b548
github.com/hashicorp/hcl/v2 v2.10.0
github.com/hashicorp/terraform-exec v0.13.3
github.com/hashicorp/terraform-json v0.10.0
github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896
github.com/hashicorp/terraform-schema v0.0.0-20210428174709-ad0461b43827
github.com/hashicorp/terraform-schema v0.0.0-20210505192708-22623ce70271
github.com/mh-cbon/go-fmt-fail v0.0.0-20160815164508-67765b3fbcb5
github.com/mitchellh/cli v1.1.2
github.com/mitchellh/copystructure v1.1.2
Expand Down
10 changes: 4 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,9 @@ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl-lang v0.0.0-20210419185146-8556dd730bc7 h1:6ajS9kFs/7UnRcy3dHThUnmicqU7VjGUn0RLOT6Ewq0=
github.com/hashicorp/hcl-lang v0.0.0-20210419185146-8556dd730bc7/go.mod h1:VRVfqufUmJSpWsoWDtYV/BejqCV+NNyS9V9vR0dcivs=
github.com/hashicorp/hcl-lang v0.0.0-20210505191011-f735b3e2b548 h1:UJcMJkdwVuDg7RSqrP5gqS2fPAT5WOENDMqzIDc0uxU=
github.com/hashicorp/hcl-lang v0.0.0-20210505191011-f735b3e2b548/go.mod h1:B3OEgaMz80/e+zVmTlJ8t2VL+k57up43LhGOcmOL5V4=
github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90=
github.com/hashicorp/hcl/v2 v2.9.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
github.com/hashicorp/hcl/v2 v2.10.0 h1:1S1UnuhDGlv3gRFV4+0EdwB+znNP5HmcGbIqwnSCByg=
github.com/hashicorp/hcl/v2 v2.10.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
Expand All @@ -206,8 +205,8 @@ github.com/hashicorp/terraform-json v0.10.0 h1:9syPD/Y5t+3uFjG8AiWVPu1bklJD8QB8i
github.com/hashicorp/terraform-json v0.10.0/go.mod h1:3defM4kkMfttwiE7VakJDwCd4R+umhSQnvJwORXbprE=
github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 h1:1FGtlkJw87UsTMg5s8jrekrHmUPUJaMcu6ELiVhQrNw=
github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896/go.mod h1:bzBPnUIkI0RxauU8Dqo+2KrZZ28Cf48s8V6IHt3p4co=
github.com/hashicorp/terraform-schema v0.0.0-20210428174709-ad0461b43827 h1:wf2hrzKEi5WOZkp5U0GGLKsslbFrGob2GJ35rjwMiO0=
github.com/hashicorp/terraform-schema v0.0.0-20210428174709-ad0461b43827/go.mod h1:K3XjJOEolIgMNwNb/cVbSSO8nBxvO/JLDAuTOLbT7XQ=
github.com/hashicorp/terraform-schema v0.0.0-20210505192708-22623ce70271 h1:5PqDECk03F5c/HFIMuU1DSsmw/Umf/4JVGDYiYmgXdA=
github.com/hashicorp/terraform-schema v0.0.0-20210505192708-22623ce70271/go.mod h1:ceB2RvqFSFhsBU+VMq7qeFU+0x/RxPqN0tF8mKx0tv4=
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.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
Expand Down Expand Up @@ -379,7 +378,6 @@ github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLE
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
github.com/zclconf/go-cty v1.8.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
github.com/zclconf/go-cty v1.8.2/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
github.com/zclconf/go-cty v1.8.3 h1:48gwZXrdSADU2UW9eZKHprxAI7APZGW9XmExpJpSjT0=
github.com/zclconf/go-cty v1.8.3/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
Expand Down
6 changes: 6 additions & 0 deletions internal/decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ import (
"fmt"

"github.com/hashicorp/hcl-lang/decoder"
"github.com/hashicorp/hcl-lang/lang"
lsctx "github.com/hashicorp/terraform-ls/internal/context"
"github.com/hashicorp/terraform-ls/internal/terraform/module"
)

func DecoderForModule(ctx context.Context, mod module.Module) (*decoder.Decoder, error) {
d := decoder.NewDecoder()

d.SetReferenceReader(func() lang.References {
return mod.References
})

d.SetUtmSource("terraform-ls")
d.UseUtmContent(true)

Expand Down
2 changes: 1 addition & 1 deletion internal/langserver/handlers/complete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestCompletion_withValidData(t *testing.T) {
ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{
TerraformCalls: &exec.TerraformMockCalls{
PerWorkDir: map[string][]*mock.Call{
tmpDir.Dir(): []*mock.Call{
tmpDir.Dir(): {
{
Method: "Version",
Repeatability: 1,
Expand Down
4 changes: 4 additions & 0 deletions internal/langserver/handlers/did_change.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ func TextDocumentDidChange(ctx context.Context, params lsp.DidChangeTextDocument
if err != nil {
return err
}
err = modMgr.EnqueueModuleOpWait(mod.Path, op.OpTypeDecodeReferences)
if err != nil {
return err
}

diags, err := lsctx.Diagnostics(ctx)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/langserver/handlers/did_open.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe
// TODO: Do this only if we can verify the file differs?
modMgr.EnqueueModuleOpWait(mod.Path, op.OpTypeParseConfiguration)
modMgr.EnqueueModuleOpWait(mod.Path, op.OpTypeLoadModuleMetadata)
modMgr.EnqueueModuleOpWait(mod.Path, op.OpTypeDecodeReferences)

if mod.TerraformVersionState == op.OpStateUnknown {
modMgr.EnqueueModuleOp(mod.Path, op.OpTypeGetTerraformVersion)
Expand Down
2 changes: 2 additions & 0 deletions internal/lsp/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ func toCompletionItem(candidate lang.Candidate, caps lsp.CompletionClientCapabil
kind = lsp.EnumCompletion
case lang.MapCandidateKind, lang.ObjectCandidateKind:
kind = lsp.StructCompletion
case lang.TraversalCandidateKind:
kind = lsp.VariableCompletion
}

// TODO: Omit item which uses kind unsupported by the client
Expand Down
4 changes: 4 additions & 0 deletions internal/lsp/token_encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ func (te *TokenEncoder) encodeTokenOfIndex(i int) []float64 {
tokenType = TokenTypeParameter
case lang.TokenMapKey:
tokenType = TokenTypeParameter
case lang.TokenKeyword:
tokenType = TokenTypeVariable
case lang.TokenTraversalStep:
tokenType = TokenTypeVariable

default:
return []float64{}
Expand Down
1 change: 1 addition & 0 deletions internal/lsp/token_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ var (
TokenTypeKeyword,
TokenTypeNumber,
TokenTypeParameter,
TokenTypeVariable,
}
serverTokenModifiers = TokenModifiers{
TokenModifierDeprecated,
Expand Down
49 changes: 49 additions & 0 deletions internal/state/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package state
import (
"github.com/hashicorp/go-memdb"
"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl-lang/lang"
"github.com/hashicorp/hcl/v2"
tfaddr "github.com/hashicorp/terraform-registry-address"
tfmod "github.com/hashicorp/terraform-schema/module"
Expand Down Expand Up @@ -32,6 +33,10 @@ type Module struct {
ProviderSchemaErr error
ProviderSchemaState op.OpState

References lang.References
ReferencesErr error
ReferencesState op.OpState

ParsedFiles map[string]*hcl.File
ParsingErr error
ParsingState op.OpState
Expand All @@ -49,6 +54,7 @@ func newModule(modPath string) *Module {
ModManifestState: op.OpStateUnknown,
TerraformVersionState: op.OpStateUnknown,
ProviderSchemaState: op.OpStateUnknown,
ReferencesState: op.OpStateUnknown,
ParsingState: op.OpStateUnknown,
MetaState: op.OpStateUnknown,
}
Expand Down Expand Up @@ -400,3 +406,46 @@ func (s *ModuleStore) UpdateDiagnostics(path string, diags map[string]hcl.Diagno
txn.Commit()
return nil
}

func (s *ModuleStore) SetReferencesState(path string, state op.OpState) error {
txn := s.db.Txn(true)
defer txn.Abort()

mod, err := moduleByPath(txn, path)
if err != nil {
return err
}

mod.ReferencesState = state
err = txn.Insert(s.tableName, mod)
if err != nil {
return err
}

txn.Commit()
return nil
}

func (s *ModuleStore) UpdateReferences(path string, refs lang.References, rErr error) error {
txn := s.db.Txn(true)
txn.Defer(func() {
s.SetReferencesState(path, op.OpStateLoaded)
})
defer txn.Abort()

mod, err := moduleByPath(txn, path)
if err != nil {
return err
}

mod.References = refs
mod.ReferencesErr = rErr

err = txn.Insert(s.tableName, mod)
if err != nil {
return err
}

txn.Commit()
return nil
}
12 changes: 12 additions & 0 deletions internal/terraform/module/module_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,12 @@ func (ml *moduleLoader) executeModuleOp(ctx context.Context, modOp ModuleOperati
ml.logger.Printf("failed to load module metadata: %s", err)
}
return
case op.OpTypeDecodeReferences:
err := DecodeReferences(ml.modStore, ml.schemaStore, modOp.ModulePath)
if err != nil {
ml.logger.Printf("failed to decode references: %s", err)
}
return
}

ml.logger.Printf("%s: unknown operation (%#v) for module operation",
Expand Down Expand Up @@ -226,6 +232,12 @@ func (ml *moduleLoader) EnqueueModuleOp(modOp ModuleOperation) error {
return nil
}
ml.modStore.SetMetaState(modOp.ModulePath, op.OpStateQueued)
case op.OpTypeDecodeReferences:
if mod.MetaState == op.OpStateQueued {
// avoid enqueuing duplicate operation
return nil
}
ml.modStore.SetReferencesState(modOp.ModulePath, op.OpStateQueued)
}

ml.queue.PushOp(modOp)
Expand Down
6 changes: 5 additions & 1 deletion internal/terraform/module/module_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ func (mm *moduleManager) SchemaForModule(modPath string) (*schema.BodySchema, er
return nil, err
}

return schemaForModule(mod, mm.schemaStore)
}

func schemaForModule(mod *state.Module, schemaReader state.SchemaReader) (*schema.BodySchema, error) {
var coreSchema *schema.BodySchema
coreRequirements := make(version.Constraints, 0)
if mod.TerraformVersion != nil {
Expand All @@ -120,7 +124,7 @@ func (mm *moduleManager) SchemaForModule(modPath string) (*schema.BodySchema, er
}

sm := tfschema.NewSchemaMerger(coreSchema)
sm.SetSchemaReader(mm.schemaStore)
sm.SetSchemaReader(schemaReader)

meta := &tfmodule.Meta{
Path: mod.Path,
Expand Down
92 changes: 92 additions & 0 deletions internal/terraform/module/module_ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"strings"

"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl-lang/decoder"
"github.com/hashicorp/hcl-lang/lang"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform-ls/internal/filesystem"
Expand All @@ -17,6 +19,7 @@ import (
"github.com/hashicorp/terraform-schema/earlydecoder"
"github.com/hashicorp/terraform-schema/module"
tfschema "github.com/hashicorp/terraform-schema/schema"
"github.com/zclconf/go-cty/cty"
)

type ModuleOperation struct {
Expand Down Expand Up @@ -276,3 +279,92 @@ func LoadModuleMetadata(modStore *state.ModuleStore, modPath string) error {
}
return mErr
}

func DecodeReferences(modStore *state.ModuleStore, schemaReader state.SchemaReader, modPath string) error {
err := modStore.SetReferencesState(modPath, op.OpStateLoading)
if err != nil {
return err
}

mod, err := modStore.ModuleByPath(modPath)
if err != nil {
return err
}

d := decoder.NewDecoder()
for name, f := range mod.ParsedFiles {
err := d.LoadFile(name, f)
if err != nil {
return fmt.Errorf("failed to load a file: %w", err)
}
}

fullSchema, schemaErr := schemaForModule(mod, schemaReader)
if schemaErr != nil {
sErr := modStore.UpdateReferences(modPath, lang.References{}, schemaErr)
if sErr != nil {
return sErr
}
return schemaErr
}
d.SetSchema(fullSchema)

refs, rErr := d.DecodeReferences()

bRefs := builtinReferences(modPath)
refs = append(refs, bRefs...)

sErr := modStore.UpdateReferences(modPath, refs, rErr)
if sErr != nil {
return sErr
}

return rErr
}

var builtinScopeId = lang.ScopeId("builtin")

func builtinReferences(modPath string) lang.References {
return lang.References{
{
Addr: lang.Address{
lang.RootStep{Name: "path"},
lang.AttrStep{Name: "module"},
},
ScopeId: builtinScopeId,
Type: cty.String,
Description: lang.Markdown("The filesystem path of the module where the expression is placed\n\n" +
modPath),
},
{
Addr: lang.Address{
lang.RootStep{Name: "path"},
lang.AttrStep{Name: "root"},
},
ScopeId: builtinScopeId,
Type: cty.String,
Description: lang.Markdown("The filesystem path of the root module of the configuration"),
},
{
Addr: lang.Address{
lang.RootStep{Name: "path"},
lang.AttrStep{Name: "cwd"},
},
ScopeId: builtinScopeId,
Type: cty.String,
Description: lang.Markdown("The filesystem path of the current working directory.\n\n" +
"In normal use of Terraform this is the same as `path.root`, " +
"but some advanced uses of Terraform run it from a directory " +
"other than the root module directory, causing these paths to be different."),
},
{
Addr: lang.Address{
lang.RootStep{Name: "terraform"},
lang.AttrStep{Name: "workspace"},
},
ScopeId: builtinScopeId,
Type: cty.String,
Description: lang.Markdown("The name of the currently selected workspace"),
},
}
}
5 changes: 3 additions & 2 deletions internal/terraform/module/operation/op_type_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions internal/terraform/module/operation/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ const (
OpTypeParseConfiguration
OpTypeParseModuleManifest
OpTypeLoadModuleMetadata
OpTypeDecodeReferences
)

0 comments on commit 04eb478

Please sign in to comment.