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 12, 2021
1 parent dd44a50 commit 9372020
Show file tree
Hide file tree
Showing 15 changed files with 188 additions and 15 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-20210512111541-102ac67216b9
github.com/hashicorp/hcl/v2 v2.10.0
github.com/hashicorp/terraform-exec v0.13.3
github.com/hashicorp/terraform-json v0.11.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-20210512111618-5c20c7ab2df3
github.com/mh-cbon/go-fmt-fail v0.0.0-20160815164508-67765b3fbcb5
github.com/mitchellh/cli v1.1.2
github.com/mitchellh/copystructure v1.2.0
Expand Down
12 changes: 4 additions & 8 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-20210512111541-102ac67216b9 h1:hNUJeCGZSe3lB79pKcanxItpukbjiQ47HBfKhDlMPkg=
github.com/hashicorp/hcl-lang v0.0.0-20210512111541-102ac67216b9/go.mod h1:7BtIzYAy75UR501SFkNjL98xMZbvn5Vc7bj+dOgcH70=
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 @@ -207,8 +206,8 @@ github.com/hashicorp/terraform-json v0.11.0 h1:4zDqqW2F3kOysORIaYKFGgWDYIRA3hwqx
github.com/hashicorp/terraform-json v0.11.0/go.mod h1:pmbq9o4EuL43db5+0ogX10Yofv1nozM+wskr/bGFJpI=
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-20210512111618-5c20c7ab2df3 h1:1FLawNTRHVoasrYlIQXcwFWI1v+t97BA99diSKf4470=
github.com/hashicorp/terraform-schema v0.0.0-20210512111618-5c20c7ab2df3/go.mod h1:x9bL0iUl55qobaaZU0k3OaIriyqjYtHzZLUz4ZbyzAc=
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 @@ -266,7 +265,6 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT
github.com/mitchellh/cli v1.1.2 h1:PvH+lL2B7IQ101xQL63Of8yFS2y+aDlsFcsqNc+u/Kw=
github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.1.2/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
Expand All @@ -284,7 +282,6 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down Expand Up @@ -383,7 +380,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
4 changes: 3 additions & 1 deletion internal/lsp/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ 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

var cmd *lsp.Command
if candidate.TriggerSuggest {
if candidate.TriggerSuggest && snippetSupport {
cmd = &lsp.Command{
Command: "editor.action.triggerSuggest",
Title: "Suggest",
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 9372020

Please sign in to comment.