Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions internal/hcl/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,11 @@ func IsNoBlockFoundErr(err error) bool {
_, ok := err.(*NoBlockFoundErr)
return ok
}

type NoTokenFoundErr struct {
AtPos hcllib.Pos
}

func (e *NoTokenFoundErr) Error() string {
return fmt.Sprintf("no token found at %#v", e.AtPos)
}
76 changes: 56 additions & 20 deletions internal/hcl/hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ import (
"github.com/hashicorp/terraform-ls/internal/filesystem"
)

type File interface {
BlockTokensAtPosition(filesystem.FilePosition) (hclsyntax.Tokens, hcllib.Pos, error)
}

type file struct {
filename string
content []byte
Expand All @@ -24,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 @@ -59,46 +85,56 @@ func (f *file) parse() (*parsedFile, error) {
return f.pf, nil
}

func (f *file) BlockTokensAtPosition(filePos filesystem.FilePosition) (hclsyntax.Tokens, hcllib.Pos, error) {
pos := filePos.Position()

b, err := f.blockAtPosition(pos)
if err != nil {
return hclsyntax.Tokens{}, pos, err
func (f *file) PosInBlock(pos hcl.Pos) bool {
_, err := f.BlockAtPosition(pos)
if IsNoBlockFoundErr(err) {
return false
}

return b, pos, nil
return true
}

func (f *file) blockAtPosition(pos hcllib.Pos) (hclsyntax.Tokens, error) {
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 hclsyntax.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 {
wholeRange := hcllib.RangeBetween(block.TypeRange, block.CloseBraceRange)
if wholeRange.ContainsPos(pos) {
return definitionTokens(tokensInRange(f.pf.Tokens, block.Range())), nil
if block.Range().ContainsPos(pos) {
dt := definitionTokens(tokensInRange(pf.Tokens, block.Range()))
return &parsedBlock{dt}, nil
}
}

return nil, &NoBlockFoundErr{pos}
}

func (f *file) TokenAtPosition(pos hcllib.Pos) (hclsyntax.Token, error) {
pf, _ := f.parse()

for _, t := range pf.Tokens {
if rangeContainsOffset(t.Range, pos.Byte) {
return t, nil
}
}

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

func tokensInRange(tokens hclsyntax.Tokens, rng hcllib.Range) hclsyntax.Tokens {
var ts hclsyntax.Tokens

Expand Down
17 changes: 6 additions & 11 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,14 +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)
fp := &testPosition{
FileHandler: fsFile,
pos: tc.pos,
}
f := NewTestFile([]byte(tc.content))

tokens, _, err := f.BlockTokensAtPosition(fp)
tBlock, err := f.BlockAtPosition(tc.pos)
if err != nil {
if tc.expectedErr == nil {
t.Fatal(err)
Expand All @@ -258,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
}
10 changes: 8 additions & 2 deletions internal/lsp/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ func CompletionItem(candidate lang.CompletionCandidate, pos hcl.Pos, snippetSupp
}

if snippetSupport {
pos, newText := candidate.Snippet(pos)
return lsp.CompletionItem{
Label: candidate.Label(),
Kind: lsp.CIKField,
Expand All @@ -47,7 +46,7 @@ func CompletionItem(candidate lang.CompletionCandidate, pos hcl.Pos, snippetSupp
Start: lsp.Position{Line: pos.Line - 1, Character: pos.Column - 1},
End: lsp.Position{Line: pos.Line - 1, Character: pos.Column - 1},
},
NewText: newText,
NewText: candidate.Snippet(),
},
}
}
Expand All @@ -58,5 +57,12 @@ func CompletionItem(candidate lang.CompletionCandidate, pos hcl.Pos, snippetSupp
InsertTextFormat: lsp.ITFPlainText,
Detail: candidate.Detail(),
Documentation: doc,
TextEdit: &lsp.TextEdit{
Range: lsp.Range{
Start: lsp.Position{Line: pos.Line - 1, Character: pos.Column - 1},
End: lsp.Position{Line: pos.Line - 1, Character: pos.Column - 1},
},
NewText: candidate.PlainText(),
},
}
}
Loading