diff --git a/go.mod b/go.mod index 0b1cb5a22..be7d078f5 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ 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-20211014152429-0bfbdcca0902 + github.com/hashicorp/hcl-lang v0.0.0-20211028105221-b5fa8357ac20 github.com/hashicorp/hcl/v2 v2.10.1 github.com/hashicorp/terraform-exec v0.15.0 github.com/hashicorp/terraform-json v0.13.0 @@ -33,5 +33,5 @@ require ( github.com/vektra/mockery/v2 v2.9.4 github.com/zclconf/go-cty v1.9.1 github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b - golang.org/x/tools v0.0.0-20200323144430-8dcfad9e016e + golang.org/x/tools v0.1.7 ) diff --git a/go.sum b/go.sum index 201438c00..9dfd4e97d 100644 --- a/go.sum +++ b/go.sum @@ -191,8 +191,10 @@ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG 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-20210803155453-7c098e4940bc/go.mod h1:xzXU6Fn+TWVaZUFxV8CyAsObi2oMgSEFAmLvCx2ArzM= -github.com/hashicorp/hcl-lang v0.0.0-20211014152429-0bfbdcca0902 h1:FxmNMZrjISkvGoXdoySct4dnk40KwhspPj9LvAaPPJo= -github.com/hashicorp/hcl-lang v0.0.0-20211014152429-0bfbdcca0902/go.mod h1:D7lBT7dekCcgbxzIHHBFvaRm42u5jY0pDoiC2J6A2KM= +github.com/hashicorp/hcl-lang v0.0.0-20211028091655-f66b386dae6e h1:6zbCXhmOFq8btKEaIcg+8VGNpqxf42YaXqO9qzxkwt0= +github.com/hashicorp/hcl-lang v0.0.0-20211028091655-f66b386dae6e/go.mod h1:NQq9vfyCPpRTPS4L5xeJGxp32qqp83UkDAO37NyBGF8= +github.com/hashicorp/hcl-lang v0.0.0-20211028105221-b5fa8357ac20 h1:YnIiYleElIvfMaHyAPSRCJcjg7Xi8A9tcK4ylhcI5hY= +github.com/hashicorp/hcl-lang v0.0.0-20211028105221-b5fa8357ac20/go.mod h1:NQq9vfyCPpRTPS4L5xeJGxp32qqp83UkDAO37NyBGF8= github.com/hashicorp/hcl/v2 v2.10.1 h1:h4Xx4fsrRE26ohAk/1iGF/JBqRQbyUqu5Lvj60U54ys= github.com/hashicorp/hcl/v2 v2.10.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -379,6 +381,7 @@ github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6e github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= 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= @@ -429,8 +432,9 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -452,8 +456,9 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210326060303-6b1517762897 h1:KrsHThm5nFk34YtATK1LsThyGhGbGe1olrte/HInHvs= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -485,17 +490,20 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -517,8 +525,9 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200323144430-8dcfad9e016e h1:ssd5ulOvVWlh4kDSUF2SqzmMeWfjmwDXM+uGw/aQjRE= golang.org/x/tools v0.0.0-20200323144430-8dcfad9e016e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/cmd/completion_command.go b/internal/cmd/completion_command.go index 47bf555fe..2a6b167cb 100644 --- a/internal/cmd/completion_command.go +++ b/internal/cmd/completion_command.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" + "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/terraform-ls/internal/decoder" "github.com/hashicorp/terraform-ls/internal/filesystem" "github.com/hashicorp/terraform-ls/internal/logging" @@ -87,7 +88,7 @@ func (c *CompletionCommand) Run(args []string) int { fs.SetLogger(logger) fs.CreateAndOpenDocument(fh, "terraform", content) - file, err := fs.GetDocument(fh) + doc, err := fs.GetDocument(fh) if err != nil { c.Ui.Error(err.Error()) return 1 @@ -98,7 +99,7 @@ func (c *CompletionCommand) Run(args []string) int { URI: fh.DocumentURI(), }, Position: lspPos, - }, file) + }, doc) if err != nil { c.Ui.Error(err.Error()) return 1 @@ -112,29 +113,27 @@ func (c *CompletionCommand) Run(args []string) int { } modMgr := module.NewSyncModuleManager(ctx, fs, ss.Modules, ss.ProviderSchemas) - mod, err := modMgr.AddModule(fh.Dir()) + _, err = modMgr.AddModule(fh.Dir()) if err != nil { c.Ui.Error(err.Error()) return 1 } - schema, err := modMgr.SchemaForModule(file.Dir()) - - if err != nil { - c.Ui.Error(fmt.Sprintf("failed to find schema: %s", err.Error())) - return 1 - } + pos := fPos.Position() - d, err := decoder.DecoderForModule(ctx, mod) + d, err := decoder.NewDecoder(ctx, &decoder.PathReader{ + ModuleReader: ss.Modules, + SchemaReader: ss.ProviderSchemas, + }).Path(lang.Path{ + Path: doc.Dir(), + LanguageID: doc.LanguageID(), + }) if err != nil { - c.Ui.Error(fmt.Sprintf("failed to find decoder: %s", err.Error())) + c.Ui.Error(err.Error()) return 1 } - d.SetSchema(schema) - - pos := fPos.Position() - candidates, err := d.CandidatesAtPos(file.Filename(), pos) + candidates, err := d.CandidatesAtPos(doc.Filename(), pos) if err != nil { c.Ui.Error(fmt.Sprintf("failed to find candidates: %s", err.Error())) return 1 diff --git a/internal/codelens/reference_count.go b/internal/codelens/reference_count.go new file mode 100644 index 000000000..ede2f50fa --- /dev/null +++ b/internal/codelens/reference_count.go @@ -0,0 +1,115 @@ +package codelens + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/hashicorp/hcl-lang/decoder" + "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/reference" + "github.com/hashicorp/hcl/v2" + ilsp "github.com/hashicorp/terraform-ls/internal/lsp" + lsp "github.com/hashicorp/terraform-ls/internal/protocol" +) + +func ReferenceCount(showReferencesCmdId string) lang.CodeLensFunc { + return func(ctx context.Context, path lang.Path, file string) ([]lang.CodeLens, error) { + lenses := make([]lang.CodeLens, 0) + + pathCtx, err := decoder.PathCtx(ctx) + if err != nil { + return nil, err + } + + refTargets := pathCtx.ReferenceTargets.OutermostInFile(file) + if err != nil { + return nil, err + } + + // There can be two targets pointing to the same range + // e.g. when a block is targetable as type-less reference + // and as an object, which is important in most contexts + // but not here, where we present it to the user. + dedupedTargets := make(map[hcl.Range]reference.Targets, 0) + for _, refTarget := range refTargets { + rng := *refTarget.RangePtr + if _, ok := dedupedTargets[rng]; !ok { + dedupedTargets[rng] = make(reference.Targets, 0) + } + dedupedTargets[rng] = append(dedupedTargets[rng], refTarget) + } + + for rng, refTargets := range dedupedTargets { + originCount := 0 + var defRange *hcl.Range + for _, refTarget := range refTargets { + if refTarget.DefRangePtr != nil { + defRange = refTarget.DefRangePtr + } + + originCount += len(pathCtx.ReferenceOrigins.Targeting(refTarget)) + } + + if originCount == 0 { + continue + } + + var hclPos hcl.Pos + if defRange != nil { + hclPos = posMiddleOfRange(defRange) + } else { + hclPos = posMiddleOfRange(&rng) + } + + lenses = append(lenses, lang.CodeLens{ + Range: rng, + Command: lang.Command{ + Title: getTitle("reference", "references", originCount), + ID: showReferencesCmdId, + Arguments: []lang.CommandArgument{ + Position(ilsp.HCLPosToLSP(hclPos)), + ReferenceContext(lsp.ReferenceContext{}), + }, + }, + }) + } + return lenses, nil + } +} + +type Position lsp.Position + +func (p Position) MarshalJSON() ([]byte, error) { + return json.Marshal(lsp.Position(p)) +} + +type ReferenceContext lsp.ReferenceContext + +func (rc ReferenceContext) MarshalJSON() ([]byte, error) { + return json.Marshal(lsp.ReferenceContext(rc)) +} + +func posMiddleOfRange(rng *hcl.Range) hcl.Pos { + col := rng.Start.Column + byte := rng.Start.Byte + + if rng.Start.Line == rng.End.Line && rng.End.Column > rng.Start.Column { + charsFromStart := (rng.End.Column - rng.Start.Column) / 2 + col += charsFromStart + byte += charsFromStart + } + + return hcl.Pos{ + Line: rng.Start.Line, + Column: col, + Byte: byte, + } +} + +func getTitle(singular, plural string, n int) string { + if n > 1 || n == 0 { + return fmt.Sprintf("%d %s", n, plural) + } + return fmt.Sprintf("%d %s", n, singular) +} diff --git a/internal/context/context.go b/internal/context/context.go index fd2f9687d..1dabe3690 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -21,9 +21,6 @@ func (k *contextKey) String() string { var ( ctxDs = &contextKey{"document storage"} - ctxClientCapsSetter = &contextKey{"client capabilities setter"} - ctxClientCaps = &contextKey{"client capabilities"} - ctxClientName = &contextKey{"client name"} ctxTfExecPath = &contextKey{"terraform executable path"} ctxTfExecLogPath = &contextKey{"terraform executor log path"} ctxTfExecTimeout = &contextKey{"terraform execution timeout"} @@ -56,55 +53,6 @@ func DocumentStorage(ctx context.Context) (filesystem.DocumentStorage, error) { return fs, nil } -func WithClientCapabilitiesSetter(ctx context.Context, caps *lsp.ClientCapabilities) context.Context { - return context.WithValue(ctx, ctxClientCapsSetter, caps) -} - -func SetClientCapabilities(ctx context.Context, caps *lsp.ClientCapabilities) error { - cc, ok := ctx.Value(ctxClientCapsSetter).(*lsp.ClientCapabilities) - if !ok { - return missingContextErr(ctxClientCapsSetter) - } - - *cc = *caps - return nil -} - -func WithClientCapabilities(ctx context.Context, caps *lsp.ClientCapabilities) context.Context { - return context.WithValue(ctx, ctxClientCaps, caps) -} - -func ClientCapabilities(ctx context.Context) (lsp.ClientCapabilities, error) { - caps, ok := ctx.Value(ctxClientCaps).(*lsp.ClientCapabilities) - if !ok { - return lsp.ClientCapabilities{}, missingContextErr(ctxClientCaps) - } - - return *caps, nil -} - -func WithClientName(ctx context.Context, namePtr *string) context.Context { - return context.WithValue(ctx, ctxClientName, namePtr) -} - -func ClientName(ctx context.Context) (string, bool) { - name, ok := ctx.Value(ctxClientName).(*string) - if !ok { - return "", false - } - return *name, true -} - -func SetClientName(ctx context.Context, name string) error { - namePtr, ok := ctx.Value(ctxClientName).(*string) - if !ok { - return missingContextErr(ctxClientName) - } - - *namePtr = name - return nil -} - func WithTerraformExecLogPath(ctx context.Context, path string) context.Context { return context.WithValue(ctx, ctxTfExecLogPath, path) } diff --git a/internal/decoder/context.go b/internal/decoder/context.go new file mode 100644 index 000000000..52a3bc817 --- /dev/null +++ b/internal/decoder/context.go @@ -0,0 +1,21 @@ +package decoder + +import ( + "context" + + ilsp "github.com/hashicorp/terraform-ls/internal/lsp" +) + +type languageIdCtxKey struct{} + +func WithLanguageId(ctx context.Context, langId ilsp.LanguageID) context.Context { + return context.WithValue(ctx, languageIdCtxKey{}, langId) +} + +func LanguageId(ctx context.Context) (ilsp.LanguageID, bool) { + id, ok := ctx.Value(languageIdCtxKey{}).(ilsp.LanguageID) + if !ok { + return "", false + } + return id, true +} diff --git a/internal/decoder/decoder.go b/internal/decoder/decoder.go index 5cb6d3481..538d635fb 100644 --- a/internal/decoder/decoder.go +++ b/internal/decoder/decoder.go @@ -4,52 +4,76 @@ import ( "context" "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/ast" - "github.com/hashicorp/terraform-ls/internal/terraform/module" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/terraform-ls/internal/codelens" + ilsp "github.com/hashicorp/terraform-ls/internal/lsp" + lsp "github.com/hashicorp/terraform-ls/internal/protocol" + "github.com/hashicorp/terraform-ls/internal/state" + tfschema "github.com/hashicorp/terraform-schema/schema" ) -func DecoderForModule(ctx context.Context, mod module.Module) (*decoder.Decoder, error) { - d := decoder.NewDecoder() +func NewDecoder(ctx context.Context, pathReader decoder.PathReader) *decoder.Decoder { + d := decoder.NewDecoder(pathReader) + d.SetContext(decoderContext(ctx)) + return d +} - d.SetReferenceTargetReader(func() lang.ReferenceTargets { - return mod.RefTargets - }) +func modulePathContext(mod *state.Module, schemaReader state.SchemaReader, modReader ModuleReader) (*decoder.PathContext, error) { + schema, err := schemaForModule(mod, schemaReader, modReader) + if err != nil { + return nil, err + } - d.SetReferenceOriginReader(func() lang.ReferenceOrigins { - return mod.RefOrigins - }) + pathCtx := &decoder.PathContext{ + Schema: schema, + ReferenceOrigins: mod.RefOrigins, + ReferenceTargets: mod.RefTargets, + Files: make(map[string]*hcl.File, 0), + } - d.SetUtmSource("terraform-ls") - d.UseUtmContent(true) + for name, f := range mod.ParsedModuleFiles { + pathCtx.Files[name.String()] = f + } - clientName, ok := lsctx.ClientName(ctx) - if ok { - d.SetUtmMedium(clientName) + return pathCtx, nil +} + +func varsPathContext(mod *state.Module) (*decoder.PathContext, error) { + schema, err := tfschema.SchemaForVariables(mod.Meta.Variables) + if err != nil { + return nil, err } - for name, f := range mod.ParsedModuleFiles { - err := d.LoadFile(name.String(), f) - if err != nil { - // skip unreadable files - continue - } + pathCtx := &decoder.PathContext{ + Schema: schema, + ReferenceOrigins: mod.RefOrigins, + ReferenceTargets: mod.RefTargets, + Files: make(map[string]*hcl.File, 0), } - return d, nil + for name, f := range mod.ParsedVarsFiles { + pathCtx.Files[name.String()] = f + } + return pathCtx, nil } -func DecoderForVariables(varsFiles ast.VarsFiles) (*decoder.Decoder, error) { - d := decoder.NewDecoder() +func decoderContext(ctx context.Context) decoder.DecoderContext { + dCtx := decoder.DecoderContext{ + UtmSource: "terraform-ls", + UseUtmContent: true, + } - for name, f := range varsFiles { - err := d.LoadFile(name.String(), f) - if err != nil { - // skip unreadable files - continue + cc, err := ilsp.ClientCapabilities(ctx) + if err == nil { + cmdId, ok := lsp.ExperimentalClientCapabilities(cc.Experimental).ShowReferencesCommandId() + if ok { + dCtx.CodeLenses = append(dCtx.CodeLenses, codelens.ReferenceCount(cmdId)) } } - return d, nil + clientName, ok := ilsp.ClientName(ctx) + if ok { + dCtx.UtmMedium = clientName + } + return dCtx } diff --git a/internal/decoder/module_schema.go b/internal/decoder/module_schema.go new file mode 100644 index 000000000..d5f7703c4 --- /dev/null +++ b/internal/decoder/module_schema.go @@ -0,0 +1,42 @@ +package decoder + +import ( + "github.com/hashicorp/go-version" + "github.com/hashicorp/hcl-lang/schema" + "github.com/hashicorp/terraform-ls/internal/state" + tfmodule "github.com/hashicorp/terraform-schema/module" + tfschema "github.com/hashicorp/terraform-schema/schema" +) + +func schemaForModule(mod *state.Module, schemaReader state.SchemaReader, modReader state.ModuleCallReader) (*schema.BodySchema, error) { + var coreSchema *schema.BodySchema + coreRequirements := make(version.Constraints, 0) + if mod.TerraformVersion != nil { + var err error + coreSchema, err = tfschema.CoreModuleSchemaForVersion(mod.TerraformVersion) + if err != nil { + return nil, err + } + coreRequirements, err = version.NewConstraint(mod.TerraformVersion.String()) + if err != nil { + return nil, err + } + } else { + coreSchema = tfschema.UniversalCoreModuleSchema() + } + + sm := tfschema.NewSchemaMerger(coreSchema) + sm.SetSchemaReader(schemaReader) + sm.SetTerraformVersion(mod.TerraformVersion) + sm.SetModuleReader(modReader) + + meta := &tfmodule.Meta{ + Path: mod.Path, + CoreRequirements: coreRequirements, + ProviderRequirements: mod.Meta.ProviderRequirements, + ProviderReferences: mod.Meta.ProviderReferences, + Variables: mod.Meta.Variables, + } + + return sm.SchemaForModule(meta) +} diff --git a/internal/decoder/path_reader.go b/internal/decoder/path_reader.go new file mode 100644 index 000000000..ef35749b1 --- /dev/null +++ b/internal/decoder/path_reader.go @@ -0,0 +1,76 @@ +package decoder + +import ( + "context" + "fmt" + + "github.com/hashicorp/hcl-lang/decoder" + "github.com/hashicorp/hcl-lang/lang" + ilsp "github.com/hashicorp/terraform-ls/internal/lsp" + "github.com/hashicorp/terraform-ls/internal/state" + tfmod "github.com/hashicorp/terraform-schema/module" +) + +type ModuleReader interface { + ModuleByPath(modPath string) (*state.Module, error) + List() ([]*state.Module, error) + ModuleCalls(modPath string) ([]tfmod.ModuleCall, error) + ModuleMeta(modPath string) (*tfmod.Meta, error) +} + +type PathReader struct { + ModuleReader ModuleReader + SchemaReader state.SchemaReader +} + +var _ decoder.PathReader = &PathReader{} + +func (mr *PathReader) Paths(ctx context.Context) []lang.Path { + paths := make([]lang.Path, 0) + + modList, err := mr.ModuleReader.List() + if err != nil { + return paths + } + + langId, hasLang := LanguageId(ctx) + + for _, mod := range modList { + if hasLang { + paths = append(paths, lang.Path{ + Path: mod.Path, + LanguageID: langId.String(), + }) + continue + } + + paths = append(paths, lang.Path{ + Path: mod.Path, + LanguageID: ilsp.Terraform.String(), + }) + if len(mod.ParsedVarsFiles) > 0 { + paths = append(paths, lang.Path{ + Path: mod.Path, + LanguageID: ilsp.Tfvars.String(), + }) + } + } + + return paths +} + +func (mr *PathReader) PathContext(path lang.Path) (*decoder.PathContext, error) { + mod, err := mr.ModuleReader.ModuleByPath(path.Path) + if err != nil { + return nil, err + } + + switch path.LanguageID { + case ilsp.Terraform.String(): + return modulePathContext(mod, mr.SchemaReader, mr.ModuleReader) + case ilsp.Tfvars.String(): + return varsPathContext(mod) + } + + return nil, fmt.Errorf("unknown language ID: %q", path.LanguageID) +} diff --git a/internal/langserver/handlers/code_lens.go b/internal/langserver/handlers/code_lens.go index 7ea7f0b2b..a173f120c 100644 --- a/internal/langserver/handlers/code_lens.go +++ b/internal/langserver/handlers/code_lens.go @@ -2,18 +2,13 @@ package handlers import ( "context" - "encoding/json" - "fmt" - "github.com/hashicorp/hcl-lang/lang" - "github.com/hashicorp/hcl/v2" lsctx "github.com/hashicorp/terraform-ls/internal/context" - "github.com/hashicorp/terraform-ls/internal/filesystem" ilsp "github.com/hashicorp/terraform-ls/internal/lsp" lsp "github.com/hashicorp/terraform-ls/internal/protocol" ) -func (h *logHandler) TextDocumentCodeLens(ctx context.Context, params lsp.CodeLensParams) ([]lsp.CodeLens, error) { +func (svc *service) TextDocumentCodeLens(ctx context.Context, params lsp.CodeLensParams) ([]lsp.CodeLens, error) { list := make([]lsp.CodeLens, 0) fs, err := lsctx.DocumentStorage(ctx) @@ -22,140 +17,33 @@ func (h *logHandler) TextDocumentCodeLens(ctx context.Context, params lsp.CodeLe } fh := ilsp.FileHandlerFromDocumentURI(params.TextDocument.URI) - file, err := fs.GetDocument(fh) + doc, err := fs.GetDocument(fh) if err != nil { return list, err } - list = append(list, h.referenceCountCodeLens(ctx, file)...) - - return list, nil -} - -func (h *logHandler) referenceCountCodeLens(ctx context.Context, doc filesystem.Document) []lsp.CodeLens { - list := make([]lsp.CodeLens, 0) - - cc, err := lsctx.ClientCapabilities(ctx) - if err != nil { - return list - } - - showReferencesCmd, ok := lsp.ExperimentalClientCapabilities(cc.Experimental).ShowReferencesCommandId() - if !ok { - return list - } - - mf, err := lsctx.ModuleFinder(ctx) - if err != nil { - return list - } - - mod, err := mf.ModuleByPath(doc.Dir()) - if err != nil { - return list - } - - schema, err := schemaForDocument(mf, doc) - if err != nil { - return list - } - - d, err := decoderForDocument(ctx, mod, doc.LanguageID()) - if err != nil { - return list - } - d.SetSchema(schema) - - refTargets, err := d.ReferenceTargetsInFile(doc.Filename()) + d, err := svc.decoderForDocument(ctx, doc) if err != nil { - return list + return nil, err } - refContext := lsp.ReferenceContext{} - refContextBytes, err := json.Marshal(refContext) + lenses, err := d.CodeLensesForFile(ctx, doc.Filename()) if err != nil { - return list - } - - // There can be two targets pointing to the same range - // e.g. when a block is targettable as type-less reference - // and as an object, which is important in most contexts - // but not here, where we present it to the user. - dedupedTargets := make(map[hcl.Range]lang.ReferenceTargets, 0) - for _, refTarget := range refTargets { - rng := *refTarget.RangePtr - if _, ok := dedupedTargets[rng]; !ok { - dedupedTargets[rng] = make(lang.ReferenceTargets, 0) - } - dedupedTargets[rng] = append(dedupedTargets[rng], refTarget) + return nil, err } - for rng, refTargets := range dedupedTargets { - originCount := 0 - var defRange *hcl.Range - for _, refTarget := range refTargets { - if refTarget.DefRangePtr != nil { - defRange = refTarget.DefRangePtr - } - - origins, err := d.ReferenceOriginsTargeting(refTarget) - if err != nil { - continue - } - originCount += len(origins) - } - - if originCount == 0 { - continue - } - - var pos hcl.Pos - if defRange != nil { - pos = posMiddleOfRange(defRange) - } else { - pos = posMiddleOfRange(&rng) - } - - posBytes, err := json.Marshal(ilsp.HCLPosToLSP(pos)) + for _, lens := range lenses { + cmd, err := ilsp.Command(lens.Command) if err != nil { - return list + svc.logger.Printf("skipping code lens %#v: %s", lens.Command, err) + continue } list = append(list, lsp.CodeLens{ - Range: ilsp.HCLRangeToLSP(rng), - Command: lsp.Command{ - Title: getTitle("reference", "references", originCount), - Command: showReferencesCmd, - Arguments: []json.RawMessage{ - json.RawMessage(posBytes), - json.RawMessage(refContextBytes), - }, - }, + Range: ilsp.HCLRangeToLSP(lens.Range), + Command: cmd, }) } - return list -} - -func posMiddleOfRange(rng *hcl.Range) hcl.Pos { - col := rng.Start.Column - byte := rng.Start.Byte - - if rng.Start.Line == rng.End.Line && rng.End.Column > rng.Start.Column { - charsFromStart := (rng.End.Column - rng.Start.Column) / 2 - col += charsFromStart - byte += charsFromStart - } - return hcl.Pos{ - Line: rng.Start.Line, - Column: col, - Byte: byte, - } -} - -func getTitle(singular, plural string, n int) string { - if n > 1 || n == 0 { - return fmt.Sprintf("%d %s", n, plural) - } - return fmt.Sprintf("%d %s", n, singular) + return list, nil } diff --git a/internal/langserver/handlers/complete.go b/internal/langserver/handlers/complete.go index 3d1bc280f..e86010d82 100644 --- a/internal/langserver/handlers/complete.go +++ b/internal/langserver/handlers/complete.go @@ -8,7 +8,7 @@ import ( lsp "github.com/hashicorp/terraform-ls/internal/protocol" ) -func (h *logHandler) TextDocumentComplete(ctx context.Context, params lsp.CompletionParams) (lsp.CompletionList, error) { +func (svc *service) TextDocumentComplete(ctx context.Context, params lsp.CompletionParams) (lsp.CompletionList, error) { var list lsp.CompletionList fs, err := lsctx.DocumentStorage(ctx) @@ -16,37 +16,21 @@ func (h *logHandler) TextDocumentComplete(ctx context.Context, params lsp.Comple return list, err } - cc, err := lsctx.ClientCapabilities(ctx) + cc, err := ilsp.ClientCapabilities(ctx) if err != nil { return list, err } - mf, err := lsctx.ModuleFinder(ctx) + doc, err := fs.GetDocument(ilsp.FileHandlerFromDocumentURI(params.TextDocument.URI)) if err != nil { return list, err } - file, err := fs.GetDocument(ilsp.FileHandlerFromDocumentURI(params.TextDocument.URI)) + d, err := svc.decoderForDocument(ctx, doc) if err != nil { return list, err } - mod, err := mf.ModuleByPath(file.Dir()) - if err != nil { - return list, err - } - - schema, err := schemaForDocument(mf, file) - if err != nil { - return list, err - } - - d, err := decoderForDocument(ctx, mod, file.LanguageID()) - if err != nil { - return list, err - } - d.SetSchema(schema) - expFeatures, err := lsctx.ExperimentalFeatures(ctx) if err != nil { return list, err @@ -54,13 +38,13 @@ func (h *logHandler) TextDocumentComplete(ctx context.Context, params lsp.Comple d.PrefillRequiredFields = expFeatures.PrefillRequiredFields - fPos, err := ilsp.FilePositionFromDocumentPosition(params.TextDocumentPositionParams, file) + fPos, err := ilsp.FilePositionFromDocumentPosition(params.TextDocumentPositionParams, doc) if err != nil { return list, err } - h.logger.Printf("Looking for candidates at %q -> %#v", file.Filename(), fPos.Position()) - candidates, err := d.CandidatesAtPos(file.Filename(), fPos.Position()) - h.logger.Printf("received candidates: %#v", candidates) + svc.logger.Printf("Looking for candidates at %q -> %#v", doc.Filename(), fPos.Position()) + candidates, err := d.CandidatesAtPos(doc.Filename(), fPos.Position()) + svc.logger.Printf("received candidates: %#v", candidates) return ilsp.ToCompletionList(candidates, cc.TextDocument), err } diff --git a/internal/langserver/handlers/document_link.go b/internal/langserver/handlers/document_link.go index f223191e8..87cf48b20 100644 --- a/internal/langserver/handlers/document_link.go +++ b/internal/langserver/handlers/document_link.go @@ -4,53 +4,36 @@ import ( "context" lsctx "github.com/hashicorp/terraform-ls/internal/context" - "github.com/hashicorp/terraform-ls/internal/decoder" ilsp "github.com/hashicorp/terraform-ls/internal/lsp" lsp "github.com/hashicorp/terraform-ls/internal/protocol" ) -func (h *logHandler) TextDocumentLink(ctx context.Context, params lsp.DocumentLinkParams) ([]lsp.DocumentLink, error) { +func (svc *service) TextDocumentLink(ctx context.Context, params lsp.DocumentLinkParams) ([]lsp.DocumentLink, error) { fs, err := lsctx.DocumentStorage(ctx) if err != nil { return nil, err } - cc, err := lsctx.ClientCapabilities(ctx) + cc, err := ilsp.ClientCapabilities(ctx) if err != nil { return nil, err } - mf, err := lsctx.ModuleFinder(ctx) + doc, err := fs.GetDocument(ilsp.FileHandlerFromDocumentURI(params.TextDocument.URI)) if err != nil { return nil, err } - file, err := fs.GetDocument(ilsp.FileHandlerFromDocumentURI(params.TextDocument.URI)) - if err != nil { - return nil, err - } - - if file.LanguageID() != ilsp.Terraform.String() { + if doc.LanguageID() != ilsp.Terraform.String() { return nil, nil } - mod, err := mf.ModuleByPath(file.Dir()) - if err != nil { - return nil, err - } - - schema, err := mf.SchemaForModule(file.Dir()) - if err != nil { - return nil, err - } - - d, err := decoder.DecoderForModule(ctx, mod) + d, err := svc.decoderForDocument(ctx, doc) if err != nil { return nil, err } - d.SetSchema(schema) - links, err := d.LinksInFile(file.Filename()) + links, err := d.LinksInFile(doc.Filename()) if err != nil { return nil, err } diff --git a/internal/langserver/handlers/go_to_ref_target.go b/internal/langserver/handlers/go_to_ref_target.go index 6bd1ed0df..d6f91333a 100644 --- a/internal/langserver/handlers/go_to_ref_target.go +++ b/internal/langserver/handlers/go_to_ref_target.go @@ -8,8 +8,8 @@ import ( lsp "github.com/hashicorp/terraform-ls/internal/protocol" ) -func (h *logHandler) GoToReferenceTarget(ctx context.Context, params lsp.TextDocumentPositionParams) (interface{}, error) { - cc, err := lsctx.ClientCapabilities(ctx) +func (svc *service) GoToReferenceTarget(ctx context.Context, params lsp.TextDocumentPositionParams) (interface{}, error) { + cc, err := ilsp.ClientCapabilities(ctx) if err != nil { return nil, err } @@ -19,51 +19,25 @@ func (h *logHandler) GoToReferenceTarget(ctx context.Context, params lsp.TextDoc return nil, err } - mf, err := lsctx.ModuleFinder(ctx) + doc, err := fs.GetDocument(ilsp.FileHandlerFromDocumentURI(params.TextDocument.URI)) if err != nil { return nil, err } - file, err := fs.GetDocument(ilsp.FileHandlerFromDocumentURI(params.TextDocument.URI)) + d, err := svc.decoderForDocument(ctx, doc) if err != nil { return nil, err } - mod, err := mf.ModuleByPath(file.Dir()) + fPos, err := ilsp.FilePositionFromDocumentPosition(params, doc) if err != nil { return nil, err } - schema, err := schemaForDocument(mf, file) + target, err := d.ReferenceTargetForOriginAtPos(doc.Filename(), fPos.Position()) if err != nil { return nil, err } - d, err := decoderForDocument(ctx, mod, file.LanguageID()) - if err != nil { - return nil, err - } - d.SetSchema(schema) - - fPos, err := ilsp.FilePositionFromDocumentPosition(params, file) - if err != nil { - return nil, err - } - - h.logger.Printf("Looking for ref origin at %q -> %#v", file.Filename(), fPos.Position()) - origin, err := d.ReferenceOriginAtPos(file.Filename(), fPos.Position()) - if err != nil { - return nil, err - } - if origin == nil { - return nil, nil - } - h.logger.Printf("found origin: %#v", origin) - - target, err := d.ReferenceTargetForOrigin(*origin) - if err != nil { - return nil, err - } - - return ilsp.ReferenceToLocationLink(mod.Path, *origin, target, cc.TextDocument.Declaration.LinkSupport), nil + return ilsp.RefTargetToLocationLink(target, cc.TextDocument.Declaration.LinkSupport), nil } diff --git a/internal/langserver/handlers/hover.go b/internal/langserver/handlers/hover.go index 629fec4b3..a163e0f7a 100644 --- a/internal/langserver/handlers/hover.go +++ b/internal/langserver/handlers/hover.go @@ -8,51 +8,35 @@ import ( lsp "github.com/hashicorp/terraform-ls/internal/protocol" ) -func (h *logHandler) TextDocumentHover(ctx context.Context, params lsp.TextDocumentPositionParams) (*lsp.Hover, error) { +func (svc *service) TextDocumentHover(ctx context.Context, params lsp.TextDocumentPositionParams) (*lsp.Hover, error) { fs, err := lsctx.DocumentStorage(ctx) if err != nil { return nil, err } - cc, err := lsctx.ClientCapabilities(ctx) + cc, err := ilsp.ClientCapabilities(ctx) if err != nil { return nil, err } - mf, err := lsctx.ModuleFinder(ctx) + doc, err := fs.GetDocument(ilsp.FileHandlerFromDocumentURI(params.TextDocument.URI)) if err != nil { return nil, err } - file, err := fs.GetDocument(ilsp.FileHandlerFromDocumentURI(params.TextDocument.URI)) + d, err := svc.decoderForDocument(ctx, doc) if err != nil { return nil, err } - mod, err := mf.ModuleByPath(file.Dir()) + fPos, err := ilsp.FilePositionFromDocumentPosition(params, doc) if err != nil { return nil, err } - schema, err := schemaForDocument(mf, file) - if err != nil { - return nil, err - } - - d, err := decoderForDocument(ctx, mod, file.LanguageID()) - if err != nil { - return nil, err - } - d.SetSchema(schema) - - fPos, err := ilsp.FilePositionFromDocumentPosition(params, file) - if err != nil { - return nil, err - } - - h.logger.Printf("Looking for hover data at %q -> %#v", file.Filename(), fPos.Position()) - hoverData, err := d.HoverAtPos(file.Filename(), fPos.Position()) - h.logger.Printf("received hover data: %#v", hoverData) + svc.logger.Printf("Looking for hover data at %q -> %#v", doc.Filename(), fPos.Position()) + hoverData, err := d.HoverAtPos(doc.Filename(), fPos.Position()) + svc.logger.Printf("received hover data: %#v", hoverData) if err != nil { return nil, err } diff --git a/internal/langserver/handlers/initialize.go b/internal/langserver/handlers/initialize.go index 8de7fe125..1ec095554 100644 --- a/internal/langserver/handlers/initialize.go +++ b/internal/langserver/handlers/initialize.go @@ -68,7 +68,7 @@ func (svc *service) Initialize(ctx context.Context, params lsp.InitializeParams) } if params.ClientInfo.Name != "" { - err = lsctx.SetClientName(ctx, params.ClientInfo.Name) + err = ilsp.SetClientName(ctx, params.ClientInfo.Name) if err != nil { return serverCaps, err } @@ -82,7 +82,7 @@ func (svc *service) Initialize(ctx context.Context, params lsp.InitializeParams) } } - err = lsctx.SetClientCapabilities(ctx, &clientCaps) + err = ilsp.SetClientCapabilities(ctx, &clientCaps) if err != nil { return serverCaps, err } diff --git a/internal/langserver/handlers/references.go b/internal/langserver/handlers/references.go index 08ce40522..ffaf7fa62 100644 --- a/internal/langserver/handlers/references.go +++ b/internal/langserver/handlers/references.go @@ -3,13 +3,12 @@ package handlers import ( "context" - "github.com/hashicorp/hcl-lang/lang" lsctx "github.com/hashicorp/terraform-ls/internal/context" ilsp "github.com/hashicorp/terraform-ls/internal/lsp" lsp "github.com/hashicorp/terraform-ls/internal/protocol" ) -func (h *logHandler) References(ctx context.Context, params lsp.ReferenceParams) ([]lsp.Location, error) { +func (svc *service) References(ctx context.Context, params lsp.ReferenceParams) ([]lsp.Location, error) { list := make([]lsp.Location, 0) fs, err := lsctx.DocumentStorage(ctx) @@ -17,57 +16,22 @@ func (h *logHandler) References(ctx context.Context, params lsp.ReferenceParams) return list, err } - mf, err := lsctx.ModuleFinder(ctx) + doc, err := fs.GetDocument(ilsp.FileHandlerFromDocumentURI(params.TextDocument.URI)) if err != nil { return list, err } - file, err := fs.GetDocument(ilsp.FileHandlerFromDocumentURI(params.TextDocument.URI)) + d, err := svc.decoderForDocument(ctx, doc) if err != nil { return list, err } - mod, err := mf.ModuleByPath(file.Dir()) + fPos, err := ilsp.FilePositionFromDocumentPosition(params.TextDocumentPositionParams, doc) if err != nil { return list, err } - schema, err := schemaForDocument(mf, file) - if err != nil { - return list, err - } - - d, err := decoderForDocument(ctx, mod, file.LanguageID()) - if err != nil { - return list, err - } - d.SetSchema(schema) - - fPos, err := ilsp.FilePositionFromDocumentPosition(params.TextDocumentPositionParams, file) - if err != nil { - return list, err - } - - refTargets, err := d.InnermostReferenceTargetsAtPos(fPos.Filename(), fPos.Position()) - if err != nil { - return list, err - } - if len(refTargets) == 0 { - // this position is not addressable - h.logger.Printf("position is not addressable: %s - %#v", fPos.Filename(), fPos.Position()) - return list, nil - } - - h.logger.Printf("finding origins for inner-most targets: %#v", refTargets) - - origins := make(lang.ReferenceOrigins, 0) - for _, refTarget := range refTargets { - refOrigins, err := d.ReferenceOriginsTargeting(refTarget) - if err != nil { - return list, err - } - origins = append(origins, refOrigins...) - } + origins := d.ReferenceOriginsTargetingPos(doc.Filename(), fPos.Position()) - return ilsp.RefOriginsToLocations(mod.Path, origins), nil + return ilsp.RefOriginsToLocations(origins), nil } diff --git a/internal/langserver/handlers/semantic_tokens.go b/internal/langserver/handlers/semantic_tokens.go index ebc04222b..7ede9662b 100644 --- a/internal/langserver/handlers/semantic_tokens.go +++ b/internal/langserver/handlers/semantic_tokens.go @@ -2,7 +2,6 @@ package handlers import ( "context" - "fmt" "github.com/creachadair/jrpc2/code" lsctx "github.com/hashicorp/terraform-ls/internal/context" @@ -10,10 +9,10 @@ import ( lsp "github.com/hashicorp/terraform-ls/internal/protocol" ) -func (lh *logHandler) TextDocumentSemanticTokensFull(ctx context.Context, params lsp.SemanticTokensParams) (lsp.SemanticTokens, error) { +func (svc *service) TextDocumentSemanticTokensFull(ctx context.Context, params lsp.SemanticTokensParams) (lsp.SemanticTokens, error) { tks := lsp.SemanticTokens{} - cc, err := lsctx.ClientCapabilities(ctx) + cc, err := ilsp.ClientCapabilities(ctx) if err != nil { return tks, err } @@ -25,7 +24,7 @@ func (lh *logHandler) TextDocumentSemanticTokensFull(ctx context.Context, params // This would indicate a buggy client which sent a request // it didn't claim to support, so we just strictly follow // the protocol here and avoid serving buggy clients. - lh.logger.Printf("semantic tokens full request support not announced by client") + svc.logger.Printf("semantic tokens full request support not announced by client") return tks, code.MethodNotFound.Err() } @@ -34,32 +33,16 @@ func (lh *logHandler) TextDocumentSemanticTokensFull(ctx context.Context, params return tks, err } - mf, err := lsctx.ModuleFinder(ctx) - if err != nil { - return tks, err - } - fh := ilsp.FileHandlerFromDocumentURI(params.TextDocument.URI) doc, err := ds.GetDocument(fh) if err != nil { return tks, err } - mod, err := mf.ModuleByPath(doc.Dir()) - if err != nil { - return tks, fmt.Errorf("finding compatible decoder failed: %w", err) - } - - schema, err := schemaForDocument(mf, doc) - if err != nil { - return tks, err - } - - d, err := decoderForDocument(ctx, mod, doc.LanguageID()) + d, err := svc.decoderForDocument(ctx, doc) if err != nil { return tks, err } - d.SetSchema(schema) tokens, err := d.SemanticTokensInFile(doc.Filename()) if err != nil { diff --git a/internal/langserver/handlers/service.go b/internal/langserver/handlers/service.go index e9ef163b6..e13d2cf95 100644 --- a/internal/langserver/handlers/service.go +++ b/internal/langserver/handlers/service.go @@ -12,7 +12,7 @@ import ( "github.com/creachadair/jrpc2/code" rpch "github.com/creachadair/jrpc2/handler" "github.com/hashicorp/hcl-lang/decoder" - "github.com/hashicorp/hcl-lang/schema" + "github.com/hashicorp/hcl-lang/lang" lsctx "github.com/hashicorp/terraform-ls/internal/context" idecoder "github.com/hashicorp/terraform-ls/internal/decoder" "github.com/hashicorp/terraform-ls/internal/filesystem" @@ -108,10 +108,10 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { return nil, err } - ctx = lsctx.WithClientCapabilitiesSetter(ctx, cc) + ctx = ilsp.WithClientCapabilitiesSetter(ctx, cc) ctx = lsctx.WithRootDirectory(ctx, &rootDir) ctx = lsctx.WithCommandPrefix(ctx, &commandPrefix) - ctx = lsctx.WithClientName(ctx, &clientName) + ctx = ilsp.ContextWithClientName(ctx, &clientName) ctx = lsctx.WithExperimentalFeatures(ctx, &expFeatures) version, ok := lsctx.LanguageServerVersion(svc.srvCtx) @@ -166,10 +166,9 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { } ctx = lsctx.WithDocumentStorage(ctx, svc.fs) - ctx = lsctx.WithClientCapabilities(ctx, cc) - ctx = lsctx.WithModuleFinder(ctx, svc.modMgr) + ctx = ilsp.WithClientCapabilities(ctx, cc) - return handle(ctx, req, lh.TextDocumentSymbol) + return handle(ctx, req, svc.TextDocumentSymbol) }, "textDocument/documentLink": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { err := session.CheckInitializationIsConfirmed() @@ -178,11 +177,10 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { } ctx = lsctx.WithDocumentStorage(ctx, svc.fs) - ctx = lsctx.WithClientCapabilities(ctx, cc) - ctx = lsctx.WithClientName(ctx, &clientName) - ctx = lsctx.WithModuleFinder(ctx, svc.modMgr) + ctx = ilsp.WithClientCapabilities(ctx, cc) + ctx = ilsp.ContextWithClientName(ctx, &clientName) - return handle(ctx, req, lh.TextDocumentLink) + return handle(ctx, req, svc.TextDocumentLink) }, "textDocument/declaration": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { err := session.CheckInitializationIsConfirmed() @@ -191,10 +189,9 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { } ctx = lsctx.WithDocumentStorage(ctx, svc.fs) - ctx = lsctx.WithClientCapabilities(ctx, cc) - ctx = lsctx.WithModuleFinder(ctx, svc.modMgr) + ctx = ilsp.WithClientCapabilities(ctx, cc) - return handle(ctx, req, lh.GoToReferenceTarget) + return handle(ctx, req, svc.GoToReferenceTarget) }, "textDocument/definition": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { err := session.CheckInitializationIsConfirmed() @@ -203,10 +200,9 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { } ctx = lsctx.WithDocumentStorage(ctx, svc.fs) - ctx = lsctx.WithClientCapabilities(ctx, cc) - ctx = lsctx.WithModuleFinder(ctx, svc.modMgr) + ctx = ilsp.WithClientCapabilities(ctx, cc) - return handle(ctx, req, lh.GoToReferenceTarget) + return handle(ctx, req, svc.GoToReferenceTarget) }, "textDocument/completion": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { err := session.CheckInitializationIsConfirmed() @@ -215,11 +211,10 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { } ctx = lsctx.WithDocumentStorage(ctx, svc.fs) - ctx = lsctx.WithClientCapabilities(ctx, cc) - ctx = lsctx.WithModuleFinder(ctx, svc.modMgr) + ctx = ilsp.WithClientCapabilities(ctx, cc) ctx = lsctx.WithExperimentalFeatures(ctx, &expFeatures) - return handle(ctx, req, lh.TextDocumentComplete) + return handle(ctx, req, svc.TextDocumentComplete) }, "textDocument/hover": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { err := session.CheckInitializationIsConfirmed() @@ -228,11 +223,10 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { } ctx = lsctx.WithDocumentStorage(ctx, svc.fs) - ctx = lsctx.WithClientCapabilities(ctx, cc) - ctx = lsctx.WithClientName(ctx, &clientName) - ctx = lsctx.WithModuleFinder(ctx, svc.modMgr) + ctx = ilsp.WithClientCapabilities(ctx, cc) + ctx = ilsp.ContextWithClientName(ctx, &clientName) - return handle(ctx, req, lh.TextDocumentHover) + return handle(ctx, req, svc.TextDocumentHover) }, "textDocument/codeAction": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { err := session.CheckInitializationIsConfirmed() @@ -240,7 +234,7 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { return nil, err } - ctx = lsctx.WithClientCapabilities(ctx, cc) + ctx = ilsp.WithClientCapabilities(ctx, cc) ctx = lsctx.WithDocumentStorage(ctx, svc.fs) ctx = exec.WithExecutorOpts(ctx, svc.tfExecOpts) ctx = exec.WithExecutorFactory(ctx, svc.tfExecFactory) @@ -253,11 +247,10 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { return nil, err } - ctx = lsctx.WithClientCapabilities(ctx, cc) + ctx = ilsp.WithClientCapabilities(ctx, cc) ctx = lsctx.WithDocumentStorage(ctx, svc.fs) - ctx = lsctx.WithModuleFinder(ctx, svc.modMgr) - return handle(ctx, req, lh.TextDocumentCodeLens) + return handle(ctx, req, svc.TextDocumentCodeLens) }, "textDocument/formatting": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { err := session.CheckInitializationIsConfirmed() @@ -278,10 +271,9 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { } ctx = lsctx.WithDocumentStorage(ctx, svc.fs) - ctx = lsctx.WithClientCapabilities(ctx, cc) - ctx = lsctx.WithModuleFinder(ctx, svc.modMgr) + ctx = ilsp.WithClientCapabilities(ctx, cc) - return handle(ctx, req, lh.TextDocumentSemanticTokensFull) + return handle(ctx, req, svc.TextDocumentSemanticTokensFull) }, "textDocument/didSave": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { err := session.CheckInitializationIsConfirmed() @@ -314,9 +306,8 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { } ctx = lsctx.WithDocumentStorage(ctx, svc.fs) - ctx = lsctx.WithModuleFinder(ctx, svc.modMgr) - return handle(ctx, req, lh.References) + return handle(ctx, req, svc.References) }, "workspace/executeCommand": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { err := session.CheckInitializationIsConfirmed() @@ -343,10 +334,9 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { } ctx = lsctx.WithDocumentStorage(ctx, svc.fs) - ctx = lsctx.WithClientCapabilities(ctx, cc) - ctx = lsctx.WithModuleFinder(ctx, svc.modMgr) + ctx = ilsp.WithClientCapabilities(ctx, cc) - return handle(ctx, req, lh.WorkspaceSymbol) + return handle(ctx, req, svc.WorkspaceSymbol) }, "shutdown": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { err := session.Shutdown(req) @@ -444,6 +434,9 @@ func (svc *service) configureSessionDependencies(cfgOpts *settings.Options) erro } store.SetLogger(svc.logger) + svc.modStore = store.Modules + svc.schemaStore = store.ProviderSchemas + err = schemas.PreloadSchemasToStore(store.ProviderSchemas) if err != nil { return err @@ -526,16 +519,13 @@ func handle(ctx context.Context, req *jrpc2.Request, fn interface{}) (interface{ return result, err } -func schemaForDocument(mf module.ModuleFinder, doc filesystem.Document) (*schema.BodySchema, error) { - if doc.LanguageID() == ilsp.Tfvars.String() { - return mf.SchemaForVariables(doc.Dir()) - } - return mf.SchemaForModule(doc.Dir()) -} - -func decoderForDocument(ctx context.Context, mod module.Module, languageID string) (*decoder.Decoder, error) { - if languageID == ilsp.Tfvars.String() { - return idecoder.DecoderForVariables(mod.ParsedVarsFiles) - } - return idecoder.DecoderForModule(ctx, mod) +func (svc *service) decoderForDocument(ctx context.Context, doc filesystem.Document) (*decoder.PathDecoder, error) { + // TODO: long-lived decoder instance + return idecoder.NewDecoder(ctx, &idecoder.PathReader{ + ModuleReader: svc.modStore, + SchemaReader: svc.schemaStore, + }).Path(lang.Path{ + Path: doc.Dir(), + LanguageID: doc.LanguageID(), + }) } diff --git a/internal/langserver/handlers/symbols.go b/internal/langserver/handlers/symbols.go index 9d592d21b..a87df97d9 100644 --- a/internal/langserver/handlers/symbols.go +++ b/internal/langserver/handlers/symbols.go @@ -8,7 +8,7 @@ import ( lsp "github.com/hashicorp/terraform-ls/internal/protocol" ) -func (h *logHandler) TextDocumentSymbol(ctx context.Context, params lsp.DocumentSymbolParams) ([]lsp.DocumentSymbol, error) { +func (svc *service) TextDocumentSymbol(ctx context.Context, params lsp.DocumentSymbolParams) ([]lsp.DocumentSymbol, error) { var symbols []lsp.DocumentSymbol fs, err := lsctx.DocumentStorage(ctx) @@ -16,32 +16,22 @@ func (h *logHandler) TextDocumentSymbol(ctx context.Context, params lsp.Document return symbols, err } - cc, err := lsctx.ClientCapabilities(ctx) + cc, err := ilsp.ClientCapabilities(ctx) if err != nil { return symbols, err } - mf, err := lsctx.ModuleFinder(ctx) + doc, err := fs.GetDocument(ilsp.FileHandlerFromDocumentURI(params.TextDocument.URI)) if err != nil { return symbols, err } - file, err := fs.GetDocument(ilsp.FileHandlerFromDocumentURI(params.TextDocument.URI)) + d, err := svc.decoderForDocument(ctx, doc) if err != nil { return symbols, err } - mod, err := mf.ModuleByPath(file.Dir()) - if err != nil { - return symbols, err - } - - d, err := decoderForDocument(ctx, mod, file.LanguageID()) - if err != nil { - return symbols, err - } - - sbs, err := d.SymbolsInFile(file.Filename()) + sbs, err := d.SymbolsInFile(doc.Filename()) if err != nil { return symbols, err } diff --git a/internal/langserver/handlers/workspace_symbol.go b/internal/langserver/handlers/workspace_symbol.go index 86a9fbfba..e336ee9b2 100644 --- a/internal/langserver/handlers/workspace_symbol.go +++ b/internal/langserver/handlers/workspace_symbol.go @@ -3,47 +3,26 @@ package handlers import ( "context" - lsctx "github.com/hashicorp/terraform-ls/internal/context" "github.com/hashicorp/terraform-ls/internal/decoder" ilsp "github.com/hashicorp/terraform-ls/internal/lsp" lsp "github.com/hashicorp/terraform-ls/internal/protocol" ) -func (h *logHandler) WorkspaceSymbol(ctx context.Context, params lsp.WorkspaceSymbolParams) ([]lsp.SymbolInformation, error) { - var symbols []lsp.SymbolInformation - - mf, err := lsctx.ModuleFinder(ctx) - if err != nil { - return symbols, err - } - - cc, err := lsctx.ClientCapabilities(ctx) +func (svc *service) WorkspaceSymbol(ctx context.Context, params lsp.WorkspaceSymbolParams) ([]lsp.SymbolInformation, error) { + cc, err := ilsp.ClientCapabilities(ctx) if err != nil { return nil, err } - modules, err := mf.ListModules() + d := decoder.NewDecoder(ctx, &decoder.PathReader{ + ModuleReader: svc.modStore, + SchemaReader: svc.schemaStore, + }) + + symbols, err := d.Symbols(ctx, params.Query) if err != nil { return nil, err } - for _, mod := range modules { - d, err := decoder.DecoderForModule(ctx, mod) - if err != nil { - return symbols, err - } - - schema, _ := mf.SchemaForModule(mod.Path) - d.SetSchema(schema) - - modSymbols, err := d.Symbols(params.Query) - if err != nil { - continue - } - - symbols = append(symbols, ilsp.SymbolInformation(mod.Path, modSymbols, - cc.Workspace.Symbol)...) - } - - return symbols, nil + return ilsp.WorkspaceSymbols(symbols, cc.Workspace.Symbol), nil } diff --git a/internal/lsp/client_capabilities.go b/internal/lsp/client_capabilities.go new file mode 100644 index 000000000..9551fdb64 --- /dev/null +++ b/internal/lsp/client_capabilities.go @@ -0,0 +1,38 @@ +package lsp + +import ( + "context" + "errors" + + lsp "github.com/hashicorp/terraform-ls/internal/protocol" +) + +type clientCapsSetterCtxKey struct{} +type clientCapsCtxKey struct{} + +func WithClientCapabilitiesSetter(ctx context.Context, caps *lsp.ClientCapabilities) context.Context { + return context.WithValue(ctx, clientCapsSetterCtxKey{}, caps) +} + +func SetClientCapabilities(ctx context.Context, caps *lsp.ClientCapabilities) error { + cc, ok := ctx.Value(clientCapsSetterCtxKey{}).(*lsp.ClientCapabilities) + if !ok { + return errors.New("TODO") + } + + *cc = *caps + return nil +} + +func WithClientCapabilities(ctx context.Context, caps *lsp.ClientCapabilities) context.Context { + return context.WithValue(ctx, clientCapsCtxKey{}, caps) +} + +func ClientCapabilities(ctx context.Context) (lsp.ClientCapabilities, error) { + caps, ok := ctx.Value(clientCapsCtxKey{}).(*lsp.ClientCapabilities) + if !ok { + return lsp.ClientCapabilities{}, errors.New("TODO") + } + + return *caps, nil +} diff --git a/internal/lsp/client_name.go b/internal/lsp/client_name.go new file mode 100644 index 000000000..ac6a54c81 --- /dev/null +++ b/internal/lsp/client_name.go @@ -0,0 +1,30 @@ +package lsp + +import ( + "context" + "fmt" +) + +type clientNameCtxKey struct{} + +func ContextWithClientName(ctx context.Context, namePtr *string) context.Context { + return context.WithValue(ctx, clientNameCtxKey{}, namePtr) +} + +func ClientName(ctx context.Context) (string, bool) { + name, ok := ctx.Value(clientNameCtxKey{}).(*string) + if !ok { + return "", false + } + return *name, true +} + +func SetClientName(ctx context.Context, name string) error { + namePtr, ok := ctx.Value(clientNameCtxKey{}).(*string) + if !ok { + return fmt.Errorf("missing context: client name") + } + + *namePtr = name + return nil +} diff --git a/internal/lsp/command.go b/internal/lsp/command.go new file mode 100644 index 000000000..bc3391d69 --- /dev/null +++ b/internal/lsp/command.go @@ -0,0 +1,23 @@ +package lsp + +import ( + "github.com/hashicorp/hcl-lang/lang" + lsp "github.com/hashicorp/terraform-ls/internal/protocol" +) + +func Command(cmd lang.Command) (lsp.Command, error) { + lspCmd := lsp.Command{ + Title: cmd.Title, + Command: cmd.ID, + } + + for _, arg := range cmd.Arguments { + jsonArg, err := arg.MarshalJSON() + if err != nil { + return lspCmd, err + } + lspCmd.Arguments = append(lspCmd.Arguments, jsonArg) + } + + return lspCmd, nil +} diff --git a/internal/lsp/location_links.go b/internal/lsp/location_links.go index d678d9bc9..674ec9af6 100644 --- a/internal/lsp/location_links.go +++ b/internal/lsp/location_links.go @@ -3,31 +3,30 @@ package lsp import ( "path/filepath" - "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/decoder" lsp "github.com/hashicorp/terraform-ls/internal/protocol" "github.com/hashicorp/terraform-ls/internal/uri" ) -func ReferenceToLocationLink(targetModPath string, origin lang.ReferenceOrigin, - target *lang.ReferenceTarget, linkSupport bool) interface{} { - - if target == nil || target.RangePtr == nil { - return nil - } - - targetUri := uri.FromPath(filepath.Join(targetModPath, target.RangePtr.Filename)) +func RefTargetToLocationLink(target *decoder.ReferenceTarget, linkSupport bool) interface{} { + targetUri := uri.FromPath(filepath.Join(target.Path.Path, target.Range.Filename)) if linkSupport { - return lsp.LocationLink{ - OriginSelectionRange: HCLRangeToLSP(origin.Range), + locLink := lsp.LocationLink{ + OriginSelectionRange: HCLRangeToLSP(target.OriginRange), TargetURI: lsp.DocumentURI(targetUri), - TargetRange: HCLRangeToLSP(*target.RangePtr), - TargetSelectionRange: HCLRangeToLSP(*target.RangePtr), + TargetRange: HCLRangeToLSP(target.Range), } + + if target.DefRangePtr != nil { + locLink.TargetSelectionRange = HCLRangeToLSP(*target.DefRangePtr) + } + + return locLink } return lsp.Location{ URI: lsp.DocumentURI(targetUri), - Range: HCLRangeToLSP(*target.RangePtr), + Range: HCLRangeToLSP(target.Range), } } diff --git a/internal/lsp/locations.go b/internal/lsp/locations.go index a6b4e46d2..7c602c49c 100644 --- a/internal/lsp/locations.go +++ b/internal/lsp/locations.go @@ -3,16 +3,16 @@ package lsp import ( "path/filepath" - "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/decoder" lsp "github.com/hashicorp/terraform-ls/internal/protocol" "github.com/hashicorp/terraform-ls/internal/uri" ) -func RefOriginsToLocations(originModPath string, origins lang.ReferenceOrigins) []lsp.Location { +func RefOriginsToLocations(origins decoder.ReferenceOrigins) []lsp.Location { locations := make([]lsp.Location, len(origins)) for i, origin := range origins { - originUri := uri.FromPath(filepath.Join(originModPath, origin.Range.Filename)) + originUri := uri.FromPath(filepath.Join(origin.Path.Path, origin.Range.Filename)) locations[i] = lsp.Location{ URI: lsp.DocumentURI(originUri), Range: HCLRangeToLSP(origin.Range), diff --git a/internal/lsp/symbols.go b/internal/lsp/symbols.go index 2b977c1e3..b2280bc9c 100644 --- a/internal/lsp/symbols.go +++ b/internal/lsp/symbols.go @@ -10,7 +10,7 @@ import ( "github.com/zclconf/go-cty/cty" ) -func SymbolInformation(dirPath string, sbs []decoder.Symbol, caps *lsp.WorkspaceSymbolClientCapabilities) []lsp.SymbolInformation { +func WorkspaceSymbols(sbs []decoder.Symbol, caps *lsp.WorkspaceSymbolClientCapabilities) []lsp.SymbolInformation { symbols := make([]lsp.SymbolInformation, len(sbs)) for i, s := range sbs { kind, ok := symbolKind(s, caps.SymbolKind.ValueSet) @@ -19,7 +19,7 @@ func SymbolInformation(dirPath string, sbs []decoder.Symbol, caps *lsp.Workspace continue } - path := filepath.Join(dirPath, s.Range().Filename) + path := filepath.Join(s.Path().Path, s.Range().Filename) symbols[i] = lsp.SymbolInformation{ Name: s.Name(), Kind: kind, diff --git a/internal/state/module.go b/internal/state/module.go index 284bf9853..6e18a6788 100644 --- a/internal/state/module.go +++ b/internal/state/module.go @@ -5,7 +5,7 @@ import ( "github.com/hashicorp/go-memdb" "github.com/hashicorp/go-version" - "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/reference" "github.com/hashicorp/hcl/v2" tfaddr "github.com/hashicorp/terraform-registry-address" tfmod "github.com/hashicorp/terraform-schema/module" @@ -75,11 +75,11 @@ type Module struct { ProviderSchemaErr error ProviderSchemaState op.OpState - RefTargets lang.ReferenceTargets + RefTargets reference.Targets RefTargetsErr error RefTargetsState op.OpState - RefOrigins lang.ReferenceOrigins + RefOrigins reference.Origins RefOriginsErr error RefOriginsState op.OpState @@ -662,7 +662,7 @@ func (s *ModuleStore) SetReferenceTargetsState(path string, state op.OpState) er return nil } -func (s *ModuleStore) UpdateReferenceTargets(path string, refs lang.ReferenceTargets, rErr error) error { +func (s *ModuleStore) UpdateReferenceTargets(path string, refs reference.Targets, rErr error) error { txn := s.db.Txn(true) txn.Defer(func() { s.SetReferenceTargetsState(path, op.OpStateLoaded) @@ -705,7 +705,7 @@ func (s *ModuleStore) SetReferenceOriginsState(path string, state op.OpState) er return nil } -func (s *ModuleStore) UpdateReferenceOrigins(path string, origins lang.ReferenceOrigins, roErr error) error { +func (s *ModuleStore) UpdateReferenceOrigins(path string, origins reference.Origins, roErr error) error { txn := s.db.Txn(true) txn.Defer(func() { s.SetReferenceOriginsState(path, op.OpStateLoaded) diff --git a/internal/terraform/module/builtin_references.go b/internal/terraform/module/builtin_references.go index 964d96c65..928727617 100644 --- a/internal/terraform/module/builtin_references.go +++ b/internal/terraform/module/builtin_references.go @@ -2,13 +2,14 @@ package module import ( "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/reference" "github.com/zclconf/go-cty/cty" ) var builtinScopeId = lang.ScopeId("builtin") -func builtinReferences(modPath string) lang.ReferenceTargets { - return lang.ReferenceTargets{ +func builtinReferences(modPath string) reference.Targets { + return reference.Targets{ { Addr: lang.Address{ lang.RootStep{Name: "path"}, diff --git a/internal/terraform/module/module_loader.go b/internal/terraform/module/module_loader.go index b74bfd630..428a44c12 100644 --- a/internal/terraform/module/module_loader.go +++ b/internal/terraform/module/module_loader.go @@ -183,12 +183,12 @@ func (ml *moduleLoader) executeModuleOp(ctx context.Context, modOp ModuleOperati ml.logger.Printf("failed to load module metadata: %s", opErr) } case op.OpTypeDecodeReferenceTargets: - opErr = DecodeReferenceTargets(ml.modStore, ml.schemaStore, modOp.ModulePath) + opErr = DecodeReferenceTargets(ctx, ml.modStore, ml.schemaStore, modOp.ModulePath) if opErr != nil { ml.logger.Printf("failed to decode reference targets: %s", opErr) } case op.OpTypeDecodeReferenceOrigins: - opErr = DecodeReferenceOrigins(ml.modStore, ml.schemaStore, modOp.ModulePath) + opErr = DecodeReferenceOrigins(ctx, ml.modStore, ml.schemaStore, modOp.ModulePath) if opErr != nil { ml.logger.Printf("failed to decode reference origins: %s", opErr) } diff --git a/internal/terraform/module/module_manager.go b/internal/terraform/module/module_manager.go index fbbffd312..f3645fe20 100644 --- a/internal/terraform/module/module_manager.go +++ b/internal/terraform/module/module_manager.go @@ -5,14 +5,10 @@ import ( "log" "path/filepath" - "github.com/hashicorp/go-version" - "github.com/hashicorp/hcl-lang/schema" - "github.com/hashicorp/terraform-ls/internal/filesystem" "github.com/hashicorp/terraform-ls/internal/state" op "github.com/hashicorp/terraform-ls/internal/terraform/module/operation" tfmodule "github.com/hashicorp/terraform-schema/module" - tfschema "github.com/hashicorp/terraform-schema/schema" ) type moduleManager struct { @@ -104,58 +100,6 @@ func (mm *moduleManager) EnqueueModuleOp(modPath string, opType op.OpType, defer return nil } -func (mm *moduleManager) SchemaForModule(modPath string) (*schema.BodySchema, error) { - mod, err := mm.ModuleByPath(modPath) - if err != nil { - return nil, err - } - - return schemaForModule(mod, mm.schemaStore, mm.moduleStore) -} - -func (mm *moduleManager) SchemaForVariables(modPath string) (*schema.BodySchema, error) { - mod, err := mm.ModuleByPath(modPath) - - if err != nil { - return nil, err - } - - return tfschema.SchemaForVariables(mod.Meta.Variables) -} - -func schemaForModule(mod *state.Module, schemaReader state.SchemaReader, modReader state.ModuleCallReader) (*schema.BodySchema, error) { - var coreSchema *schema.BodySchema - coreRequirements := make(version.Constraints, 0) - if mod.TerraformVersion != nil { - var err error - coreSchema, err = tfschema.CoreModuleSchemaForVersion(mod.TerraformVersion) - if err != nil { - return nil, err - } - coreRequirements, err = version.NewConstraint(mod.TerraformVersion.String()) - if err != nil { - return nil, err - } - } else { - coreSchema = tfschema.UniversalCoreModuleSchema() - } - - sm := tfschema.NewSchemaMerger(coreSchema) - sm.SetSchemaReader(schemaReader) - sm.SetTerraformVersion(mod.TerraformVersion) - sm.SetModuleReader(modReader) - - meta := &tfmodule.Meta{ - Path: mod.Path, - CoreRequirements: coreRequirements, - ProviderRequirements: mod.Meta.ProviderRequirements, - ProviderReferences: mod.Meta.ProviderReferences, - Variables: mod.Meta.Variables, - } - - return sm.SchemaForModule(meta) -} - func (mm *moduleManager) CallersOfModule(modPath string) ([]Module, error) { modules := make([]Module, 0) callers, err := mm.moduleStore.CallersOfModule(modPath) diff --git a/internal/terraform/module/module_manager_test.go b/internal/terraform/module/module_manager_test.go index 097d90af1..3233b7f86 100644 --- a/internal/terraform/module/module_manager_test.go +++ b/internal/terraform/module/module_manager_test.go @@ -10,14 +10,9 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/hcl-lang/lang" - "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/terraform-ls/internal/filesystem" "github.com/hashicorp/terraform-ls/internal/state" "github.com/hashicorp/terraform-ls/internal/terraform/exec" - tfmodule "github.com/hashicorp/terraform-schema/module" - "github.com/zclconf/go-cty-debug/ctydebug" - "github.com/zclconf/go-cty/cty" ) func TestModuleManager_ModuleCandidatesByPath(t *testing.T) { @@ -378,35 +373,6 @@ func schemaSourcesPaths(t *testing.T, srcs []SchemaSource) []string { return paths } -func TestSchemaForModule_uninitialized(t *testing.T) { - mmock := NewModuleManagerMock(nil) - - ctx := context.Background() - fs := filesystem.NewFilesystem() - ss, err := state.NewStateStore() - if err != nil { - t.Fatal(err) - } - mm := mmock(ctx, fs, ss.Modules, ss.ProviderSchemas) - t.Cleanup(mm.CancelLoading) - - testData, err := filepath.Abs("testdata") - if err != nil { - t.Fatal(err) - } - path := filepath.Join(testData, "uninitialized-root") - - _, err = mm.AddModule(path) - if err != nil { - t.Fatal(err) - } - - _, err = mm.SchemaForModule(path) - if err != nil { - t.Fatal(err) - } -} - func testLogger() *log.Logger { if testing.Verbose() { return log.New(os.Stdout, "", log.LstdFlags|log.Lshortfile) @@ -414,88 +380,3 @@ func testLogger() *log.Logger { return log.New(ioutil.Discard, "", 0) } - -func TestSchemaForVariables(t *testing.T) { - mmock := NewModuleManagerMock(nil) - ctx := context.Background() - fs := filesystem.NewFilesystem() - ss, err := state.NewStateStore() - if err != nil { - t.Fatal(err) - } - mm := mmock(ctx, fs, ss.Modules, ss.ProviderSchemas) - t.Cleanup(mm.CancelLoading) - - testData, err := filepath.Abs("testdata") - if err != nil { - t.Fatal(err) - } - path := filepath.Join(testData, "testdata-path") - - mod, err := mm.AddModule(path) - if err != nil { - t.Fatal(err) - } - - mod.Meta.Variables = map[string]tfmodule.Variable{ - "name": { - Description: "name of the module", - Type: cty.String, - }, - } - expectedSchema := &schema.BodySchema{Attributes: map[string]*schema.AttributeSchema{ - "name": { - Description: lang.MarkupContent{ - Value: "name of the module", - Kind: lang.PlainTextKind, - }, - IsRequired: true, - Expr: schema.LiteralTypeOnly(cty.String), - }, - }} - - actualSchema, err := mm.SchemaForVariables(path) - if err != nil { - t.Fatal(err) - } - - diff := cmp.Diff(expectedSchema, actualSchema, ctydebug.CmpOptions) - if diff != "" { - t.Fatalf("unexpected schema: %s", diff) - } -} - -func TestSchemaForModule(t *testing.T) { - mmock := NewModuleManagerMock(nil) - ctx := context.Background() - fs := filesystem.NewFilesystem() - ss, err := state.NewStateStore() - if err != nil { - t.Fatal(err) - } - mm := mmock(ctx, fs, ss.Modules, ss.ProviderSchemas) - t.Cleanup(mm.CancelLoading) - testData, err := filepath.Abs("testdata") - if err != nil { - t.Fatal(err) - } - path := filepath.Join(testData, "testdata-path") - _, err = mm.AddModule(path) - if err != nil { - t.Fatal(err) - } - - actualSchema, err := mm.SchemaForModule(path) - if err != nil { - t.Fatal(err) - } - - if actualSchema.Attributes != nil { - t.Fatalf("unexpected attributes in schema") - } - for _, key := range []string{"resource", "data", "provider"} { - if val, ok := actualSchema.Blocks[key]; !ok || val == nil { - t.Fatalf("missing %s block in schema", key) - } - } -} diff --git a/internal/terraform/module/module_ops.go b/internal/terraform/module/module_ops.go index 90a3cc25f..44bc3f8a0 100644 --- a/internal/terraform/module/module_ops.go +++ b/internal/terraform/module/module_ops.go @@ -5,9 +5,10 @@ import ( "fmt" "github.com/hashicorp/go-version" - "github.com/hashicorp/hcl-lang/decoder" "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/terraform-ls/internal/decoder" "github.com/hashicorp/terraform-ls/internal/filesystem" + ilsp "github.com/hashicorp/terraform-ls/internal/lsp" "github.com/hashicorp/terraform-ls/internal/state" "github.com/hashicorp/terraform-ls/internal/terraform/datadir" op "github.com/hashicorp/terraform-ls/internal/terraform/module/operation" @@ -252,39 +253,25 @@ func LoadModuleMetadata(modStore *state.ModuleStore, modPath string) error { return mErr } -func DecodeReferenceTargets(modStore *state.ModuleStore, schemaReader state.SchemaReader, modPath string) error { +func DecodeReferenceTargets(ctx context.Context, modStore *state.ModuleStore, schemaReader state.SchemaReader, modPath string) error { err := modStore.SetReferenceTargetsState(modPath, op.OpStateLoading) if err != nil { return err } - mod, err := modStore.ModuleByPath(modPath) + d, err := decoder.NewDecoder(ctx, &decoder.PathReader{ + ModuleReader: modStore, + SchemaReader: schemaReader, + }).Path(lang.Path{ + Path: modPath, + LanguageID: ilsp.Terraform.String(), + }) if err != nil { return err } - - d := decoder.NewDecoder() - for name, f := range mod.ParsedModuleFiles.AsMap() { - err := d.LoadFile(name, f) - if err != nil { - return fmt.Errorf("failed to load a file: %w", err) - } - } - - fullSchema, schemaErr := schemaForModule(mod, schemaReader, modStore) - if schemaErr != nil { - sErr := modStore.UpdateReferenceTargets(modPath, lang.ReferenceTargets{}, schemaErr) - if sErr != nil { - return sErr - } - return schemaErr - } - d.SetSchema(fullSchema) - targets, rErr := d.CollectReferenceTargets() - bRefs := builtinReferences(modPath) - targets = append(targets, bRefs...) + targets = append(targets, builtinReferences(modPath)...) sErr := modStore.UpdateReferenceTargets(modPath, targets, rErr) if sErr != nil { @@ -294,35 +281,22 @@ func DecodeReferenceTargets(modStore *state.ModuleStore, schemaReader state.Sche return rErr } -func DecodeReferenceOrigins(modStore *state.ModuleStore, schemaReader state.SchemaReader, modPath string) error { +func DecodeReferenceOrigins(ctx context.Context, modStore *state.ModuleStore, schemaReader state.SchemaReader, modPath string) error { err := modStore.SetReferenceOriginsState(modPath, op.OpStateLoading) if err != nil { return err } - mod, err := modStore.ModuleByPath(modPath) + d, err := decoder.NewDecoder(ctx, &decoder.PathReader{ + ModuleReader: modStore, + SchemaReader: schemaReader, + }).Path(lang.Path{ + Path: modPath, + LanguageID: ilsp.Terraform.String(), + }) if err != nil { return err } - - d := decoder.NewDecoder() - for name, f := range mod.ParsedModuleFiles.AsMap() { - err := d.LoadFile(name, f) - if err != nil { - return fmt.Errorf("failed to load a file: %w", err) - } - } - - fullSchema, schemaErr := schemaForModule(mod, schemaReader, modStore) - if schemaErr != nil { - sErr := modStore.UpdateReferenceOrigins(modPath, lang.ReferenceOrigins{}, schemaErr) - if sErr != nil { - return sErr - } - return schemaErr - } - d.SetSchema(fullSchema) - origins, rErr := d.CollectReferenceOrigins() sErr := modStore.UpdateReferenceOrigins(modPath, origins, rErr) diff --git a/internal/terraform/module/types.go b/internal/terraform/module/types.go index c466683db..156521f0f 100644 --- a/internal/terraform/module/types.go +++ b/internal/terraform/module/types.go @@ -4,7 +4,6 @@ import ( "context" "log" - "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/terraform-ls/internal/filesystem" "github.com/hashicorp/terraform-ls/internal/state" op "github.com/hashicorp/terraform-ls/internal/terraform/module/operation" @@ -22,8 +21,6 @@ type SchemaSource struct { type ModuleFinder interface { ModuleByPath(path string) (Module, error) - SchemaForModule(path string) (*schema.BodySchema, error) - SchemaForVariables(path string) (*schema.BodySchema, error) SchemaSourcesForModule(path string) ([]SchemaSource, error) ListModules() ([]Module, error) ModuleCalls(modPath string) ([]tfmodule.ModuleCall, error)