Skip to content

Commit

Permalink
refactoring: Decouple generic HCL parsing logic
Browse files Browse the repository at this point in the history
This also helps keeping CompletionCandidate immutable
and allows passing around already "trimmed" candidates
as opposed to letting the lsp package handle the trimming.
  • Loading branch information
radeksimko committed Jun 2, 2020
1 parent 010655f commit e96b87e
Show file tree
Hide file tree
Showing 21 changed files with 311 additions and 287 deletions.
61 changes: 48 additions & 13 deletions internal/hcl/hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ import (
"github.com/hashicorp/terraform-ls/internal/filesystem"
)

type File interface {
BlockTokensAtPosition(hcl.Pos) (hclsyntax.Tokens, error)
TokenAtPosition(hcl.Pos) (hclsyntax.Token, error)
}

type file struct {
filename string
content []byte
Expand All @@ -25,13 +20,43 @@ type parsedFile struct {
Tokens hclsyntax.Tokens
}

func NewFile(f filesystem.File) File {
type parsedBlock struct {
tokens hclsyntax.Tokens
}

func (pb *parsedBlock) Tokens() hclsyntax.Tokens {
return pb.tokens
}

func (pb *parsedBlock) TokenAtPosition(pos hcl.Pos) (hclsyntax.Token, error) {
for _, t := range pb.tokens {
if rangeContainsOffset(t.Range, pos.Byte) {
return t, nil
}
}

return hclsyntax.Token{}, &NoTokenFoundErr{pos}
}

func NewFile(f filesystem.File) TokenizedFile {
return &file{
filename: f.Filename(),
content: []byte(f.Text()),
}
}

func NewTestFile(b []byte) TokenizedFile {
return &file{
filename: "/test.tf",
content: b,
}
}

func NewTestBlock(b []byte) (TokenizedBlock, error) {
f := NewTestFile(b)
return f.BlockAtPosition(hcllib.InitialPos)
}

func (f *file) parse() (*parsedFile, error) {
if f.pf != nil {
return f.pf, nil
Expand Down Expand Up @@ -60,32 +85,42 @@ func (f *file) parse() (*parsedFile, error) {
return f.pf, nil
}

func (f *file) BlockTokensAtPosition(pos hcllib.Pos) (hclsyntax.Tokens, error) {
func (f *file) PosInBlock(pos hcl.Pos) bool {
_, err := f.BlockAtPosition(pos)
if IsNoBlockFoundErr(err) {
return false
}

return true
}

func (f *file) BlockAtPosition(pos hcllib.Pos) (TokenizedBlock, error) {
pf, _ := f.parse()

body, ok := pf.Body.(*hclsyntax.Body)
if !ok {
return hclsyntax.Tokens{}, fmt.Errorf("unexpected body type (%T)", body)
return nil, fmt.Errorf("unexpected body type (%T)", body)
}
if body.SrcRange.Empty() && pos != hcllib.InitialPos {
return hclsyntax.Tokens{}, &InvalidHclPosErr{pos, body.SrcRange}
return nil, &InvalidHclPosErr{pos, body.SrcRange}
}
if !body.SrcRange.Empty() {
if posIsEqual(body.SrcRange.End, pos) {
return pf.Tokens, &NoBlockFoundErr{pos}
return nil, &NoBlockFoundErr{pos}
}
if !body.SrcRange.ContainsPos(pos) {
return hclsyntax.Tokens{}, &InvalidHclPosErr{pos, body.SrcRange}
return nil, &InvalidHclPosErr{pos, body.SrcRange}
}
}

for _, block := range body.Blocks {
if block.Range().ContainsPos(pos) {
return definitionTokens(tokensInRange(pf.Tokens, block.Range())), nil
dt := definitionTokens(tokensInRange(pf.Tokens, block.Range()))
return &parsedBlock{dt}, nil
}
}

return pf.Tokens, &NoBlockFoundErr{pos}
return nil, &NoBlockFoundErr{pos}
}

func (f *file) TokenAtPosition(pos hcllib.Pos) (hclsyntax.Token, error) {
Expand Down
13 changes: 6 additions & 7 deletions internal/hcl/hcl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func TestFile_BlockAtPosition(t *testing.T) {
},
&InvalidHclPosErr{
Pos: hcl.Pos{Line: -42, Column: -3, Byte: -46},
InRange: hcl.Range{Filename: "test.tf", Start: hcl.InitialPos, End: hcl.InitialPos},
InRange: hcl.Range{Filename: "/test.tf", Start: hcl.InitialPos, End: hcl.InitialPos},
},
nil,
},
Expand All @@ -143,7 +143,7 @@ func TestFile_BlockAtPosition(t *testing.T) {
},
&InvalidHclPosErr{
Pos: hcl.Pos{Line: 42, Column: 3, Byte: 46},
InRange: hcl.Range{Filename: "test.tf", Start: hcl.InitialPos, End: hcl.InitialPos},
InRange: hcl.Range{Filename: "/test.tf", Start: hcl.InitialPos, End: hcl.InitialPos},
},
nil,
},
Expand All @@ -161,7 +161,7 @@ func TestFile_BlockAtPosition(t *testing.T) {
&InvalidHclPosErr{
Pos: hcl.Pos{Line: 42, Column: 3, Byte: 46},
InRange: hcl.Range{
Filename: "test.tf",
Filename: "/test.tf",
Start: hcl.InitialPos,
End: hcl.Pos{Column: 1, Line: 4, Byte: 20},
},
Expand Down Expand Up @@ -237,10 +237,9 @@ provider "aws" {

for i, tc := range testCases {
t.Run(fmt.Sprintf("%d-%s", i+1, tc.name), func(t *testing.T) {
fsFile := filesystem.NewFile("test.tf", []byte(tc.content))
f := NewFile(fsFile)
f := NewTestFile([]byte(tc.content))

tokens, err := f.BlockTokensAtPosition(tc.pos)
tBlock, err := f.BlockAtPosition(tc.pos)
if err != nil {
if tc.expectedErr == nil {
t.Fatal(err)
Expand All @@ -254,7 +253,7 @@ provider "aws" {
t.Fatalf("Expected error: %s", tc.expectedErr)
}

if diff := cmp.Diff(hclsyntax.Tokens(tc.expectedTokens), tokens, opts...); diff != "" {
if diff := cmp.Diff(hclsyntax.Tokens(tc.expectedTokens), tBlock.Tokens(), opts...); diff != "" {
t.Fatalf("Unexpected token difference: %s", diff)
}

Expand Down
17 changes: 17 additions & 0 deletions internal/hcl/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package hcl

import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)

type TokenizedFile interface {
BlockAtPosition(hcl.Pos) (TokenizedBlock, error)
TokenAtPosition(hcl.Pos) (hclsyntax.Token, error)
PosInBlock(hcl.Pos) bool
}

type TokenizedBlock interface {
TokenAtPosition(hcl.Pos) (hclsyntax.Token, error)
Tokens() hclsyntax.Tokens
}
18 changes: 9 additions & 9 deletions internal/lsp/completion.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package lsp

import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform-ls/internal/terraform/lang"
lsp "github.com/sourcegraph/go-lsp"
)

func CompletionList(candidates lang.CompletionCandidates, caps lsp.TextDocumentClientCapabilities) lsp.CompletionList {
func CompletionList(candidates lang.CompletionCandidates, pos hcl.Pos, caps lsp.TextDocumentClientCapabilities) lsp.CompletionList {
snippetSupport := caps.Completion.CompletionItem.SnippetSupport
list := lsp.CompletionList{}

Expand All @@ -18,13 +19,13 @@ func CompletionList(candidates lang.CompletionCandidates, caps lsp.TextDocumentC
list.IsIncomplete = !candidates.IsComplete()
list.Items = make([]lsp.CompletionItem, len(cList))
for i, c := range cList {
list.Items[i] = CompletionItem(c, snippetSupport)
list.Items[i] = CompletionItem(c, pos, snippetSupport)
}

return list
}

func CompletionItem(candidate lang.CompletionCandidate, snippetSupport bool) lsp.CompletionItem {
func CompletionItem(candidate lang.CompletionCandidate, pos hcl.Pos, snippetSupport bool) lsp.CompletionItem {
// TODO: deprecated / tags?

doc := ""
Expand All @@ -33,7 +34,6 @@ func CompletionItem(candidate lang.CompletionCandidate, snippetSupport bool) lsp
doc = c.Value()
}

r := candidate.PrefixRange()
if snippetSupport {
return lsp.CompletionItem{
Label: candidate.Label(),
Expand All @@ -43,8 +43,8 @@ func CompletionItem(candidate lang.CompletionCandidate, snippetSupport bool) lsp
Documentation: doc,
TextEdit: &lsp.TextEdit{
Range: lsp.Range{
Start: lsp.Position{Line: r.Start.Line - 1, Character: r.Start.Column - 1},
End: lsp.Position{Line: r.End.Line - 1, Character: r.End.Column - 1},
Start: lsp.Position{Line: pos.Line - 1, Character: pos.Column - 1},
End: lsp.Position{Line: pos.Line - 1, Character: pos.Column - 1},
},
NewText: candidate.Snippet(),
},
Expand All @@ -59,10 +59,10 @@ func CompletionItem(candidate lang.CompletionCandidate, snippetSupport bool) lsp
Documentation: doc,
TextEdit: &lsp.TextEdit{
Range: lsp.Range{
Start: lsp.Position{Line: r.Start.Line - 1, Character: r.Start.Column - 1},
End: lsp.Position{Line: r.End.Line - 1, Character: r.End.Column - 1},
Start: lsp.Position{Line: pos.Line - 1, Character: pos.Column - 1},
End: lsp.Position{Line: pos.Line - 1, Character: pos.Column - 1},
},
NewText: candidate.Label(),
NewText: candidate.PlainText(),
},
}
}
Loading

0 comments on commit e96b87e

Please sign in to comment.