Skip to content

Commit

Permalink
Add support for TraversalExpr
Browse files Browse the repository at this point in the history
  • Loading branch information
radeksimko committed May 5, 2021
1 parent dbb5507 commit 52408e2
Show file tree
Hide file tree
Showing 31 changed files with 3,419 additions and 141 deletions.
1 change: 1 addition & 0 deletions decoder/attribute_candidates.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func attributeSchemaToCandidate(name string, attr *schema.AttributeSchema, rng h
Snippet: snippetForAttribute(name, attr),
Range: rng,
},
TriggerSuggest: true,
}
}

Expand Down
6 changes: 4 additions & 2 deletions decoder/body_decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ func TestDecoder_CandidateAtPos_incompleteAttributes(t *testing.T) {
NewText: "attr1",
Snippet: "attr1 = ${1:1}",
},
Kind: lang.AttributeCandidateKind,
Kind: lang.AttributeCandidateKind,
TriggerSuggest: true,
},
},
IsComplete: false,
Expand Down Expand Up @@ -145,7 +146,8 @@ func TestDecoder_CandidateAtPos_computedAttributes(t *testing.T) {
NewText: "attr2",
Snippet: "attr2 = ${1:1}",
},
Kind: lang.AttributeCandidateKind,
Kind: lang.AttributeCandidateKind,
TriggerSuggest: true,
},
},
IsComplete: true,
Expand Down
53 changes: 47 additions & 6 deletions decoder/candidates.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,32 @@ func (d *Decoder) CandidatesAtPos(filename string, pos hcl.Pos) (lang.Candidates
return lang.ZeroCandidates(), &NoSchemaError{}
}

return d.candidatesAtPos(rootBody, d.rootSchema, pos)
outerBodyRng := rootBody.Range()
// Find outer block body range to allow filtering
// of references pointing back to the same block
outerBlock := rootBody.OutermostBlockAtPos(pos)
if outerBlock != nil {
ob := outerBlock.Body.(*hclsyntax.Body)
outerBodyRng = ob.Range()
}

return d.candidatesAtPos(rootBody, outerBodyRng, d.rootSchema, pos)
}

func (d *Decoder) candidatesAtPos(body *hclsyntax.Body, bodySchema *schema.BodySchema, pos hcl.Pos) (lang.Candidates, error) {
func (d *Decoder) candidatesAtPos(body *hclsyntax.Body, outerBodyRng hcl.Range, bodySchema *schema.BodySchema, pos hcl.Pos) (lang.Candidates, error) {
if bodySchema == nil {
return lang.ZeroCandidates(), nil
}

filename := body.Range().Filename

for _, attr := range body.Attributes {
if attr.Expr.Range().ContainsPos(pos) || attr.EqualsRange.End.Byte == pos.Byte {
if d.isPosInsideAttrExpr(attr, pos) {
if aSchema, ok := bodySchema.Attributes[attr.Name]; ok {
return d.attrValueCandidatesAtPos(attr, aSchema, pos)
return d.attrValueCandidatesAtPos(attr, aSchema, outerBodyRng, pos)
}
if bodySchema.AnyAttribute != nil {
return d.attrValueCandidatesAtPos(attr, bodySchema.AnyAttribute, pos)
return d.attrValueCandidatesAtPos(attr, bodySchema.AnyAttribute, outerBodyRng, pos)
}

return lang.ZeroCandidates(), nil
Expand Down Expand Up @@ -120,7 +129,7 @@ func (d *Decoder) candidatesAtPos(body *hclsyntax.Body, bodySchema *schema.BodyS
return lang.ZeroCandidates(), err
}

return d.candidatesAtPos(block.Body, mergedSchema, pos)
return d.candidatesAtPos(block.Body, outerBodyRng, mergedSchema, pos)
}
}
}
Expand All @@ -133,6 +142,38 @@ func (d *Decoder) candidatesAtPos(body *hclsyntax.Body, bodySchema *schema.BodyS
return d.bodySchemaCandidates(body, bodySchema, rng, rng), nil
}

func (d *Decoder) isPosInsideAttrExpr(attr *hclsyntax.Attribute, pos hcl.Pos) bool {
if attr.Expr.Range().ContainsPos(pos) {
return true
}

// edge case: near end (typically newline char)
if attr.Expr.Range().End.Byte == pos.Byte {
return true
}

// edge case: near the beginning (right after '=')
if attr.EqualsRange.End.Byte == pos.Byte {
return true
}

// edge case: end of incomplete traversal with '.' (which parser ignores)
endByte := attr.Expr.Range().End.Byte
if _, ok := attr.Expr.(*hclsyntax.ScopeTraversalExpr); ok && pos.Byte-endByte == 1 {
suspectedDotRng := hcl.Range{
Filename: attr.Expr.Range().Filename,
Start: attr.Expr.Range().End,
End: pos,
}
b, err := d.bytesFromRange(suspectedDotRng)
if err == nil && string(b) == "." {
return true
}
}

return false
}

func (d *Decoder) nameTokenRangeAtPos(filename string, pos hcl.Pos) (hcl.Range, error) {
rng := hcl.Range{
Filename: filename,
Expand Down
80 changes: 20 additions & 60 deletions decoder/candidates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,8 @@ resource "random_resource" "test" {
NewText: "one",
Snippet: `one = "${1:value}"`,
},
Kind: lang.AttributeCandidateKind,
Kind: lang.AttributeCandidateKind,
TriggerSuggest: true,
},
{
Label: "three",
Expand All @@ -909,7 +910,8 @@ resource "random_resource" "test" {
NewText: "three",
Snippet: "three = ${1:false}",
},
Kind: lang.AttributeCandidateKind,
Kind: lang.AttributeCandidateKind,
TriggerSuggest: true,
},
{
Label: "two",
Expand All @@ -923,7 +925,8 @@ resource "random_resource" "test" {
NewText: "two",
Snippet: "two = ${1:1}",
},
Kind: lang.AttributeCandidateKind,
Kind: lang.AttributeCandidateKind,
TriggerSuggest: true,
},
}),
},
Expand All @@ -943,7 +946,8 @@ resource "random_resource" "test" {
NewText: "one",
Snippet: `one = "${1:value}"`,
},
Kind: lang.AttributeCandidateKind,
Kind: lang.AttributeCandidateKind,
TriggerSuggest: true,
},
{
Label: "three",
Expand All @@ -957,7 +961,8 @@ resource "random_resource" "test" {
NewText: "three",
Snippet: "three = ${1:false}",
},
Kind: lang.AttributeCandidateKind,
Kind: lang.AttributeCandidateKind,
TriggerSuggest: true,
},
{
Label: "two",
Expand All @@ -971,7 +976,8 @@ resource "random_resource" "test" {
NewText: "two",
Snippet: "two = ${1:1}",
},
Kind: lang.AttributeCandidateKind,
Kind: lang.AttributeCandidateKind,
TriggerSuggest: true,
},
}),
},
Expand Down Expand Up @@ -1046,7 +1052,8 @@ func TestDecoder_CandidatesAtPos_AnyAttribute(t *testing.T) {
NewText: "name",
Snippet: "name = {\n source = \"${1:value}\"\n version = \"${2:value}\"\n}",
},
Kind: lang.AttributeCandidateKind,
Kind: lang.AttributeCandidateKind,
TriggerSuggest: true,
},
})

Expand Down Expand Up @@ -1118,7 +1125,8 @@ func TestDecoder_CandidatesAtPos_multipleTypes(t *testing.T) {
NewText: "for_each",
Snippet: "for_each = [ ${1} ]",
},
Kind: lang.AttributeCandidateKind,
Kind: lang.AttributeCandidateKind,
TriggerSuggest: true,
},
})

Expand Down Expand Up @@ -1202,32 +1210,8 @@ resource "any" "ref" {
NewText: "count",
Snippet: "count = ${1:1}",
},
Kind: lang.AttributeCandidateKind,
},
}),
},
{
"new block or attribute inside a block",
`
resource "any" "ref" {
co
}
`,
hcl.Pos{Line: 3, Column: 5, Byte: 28},
lang.CompleteCandidates([]lang.Candidate{
{
Label: "count",
Detail: "Optional, number",
TextEdit: lang.TextEdit{
Range: hcl.Range{
Filename: "test.tf",
Start: hcl.Pos{Line: 3, Column: 3, Byte: 26},
End: hcl.Pos{Line: 3, Column: 5, Byte: 28},
},
NewText: "count",
Snippet: "count = ${1:1}",
},
Kind: lang.AttributeCandidateKind,
Kind: lang.AttributeCandidateKind,
TriggerSuggest: true,
},
}),
},
Expand Down Expand Up @@ -1342,32 +1326,8 @@ resource "any" "ref" {
NewText: "count",
Snippet: "count = ${1:1}",
},
Kind: lang.AttributeCandidateKind,
},
}),
},
{
"new block or attribute inside a block",
`
resource "any" "ref" {
co
}
`,
hcl.Pos{Line: 3, Column: 5, Byte: 28},
lang.CompleteCandidates([]lang.Candidate{
{
Label: "count",
Detail: "Optional, number",
TextEdit: lang.TextEdit{
Range: hcl.Range{
Filename: "test.tf",
Start: hcl.Pos{Line: 3, Column: 3, Byte: 26},
End: hcl.Pos{Line: 3, Column: 5, Byte: 28},
},
NewText: "count",
Snippet: "count = ${1:1}",
},
Kind: lang.AttributeCandidateKind,
Kind: lang.AttributeCandidateKind,
TriggerSuggest: true,
},
}),
},
Expand Down
14 changes: 12 additions & 2 deletions decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package decoder

import (
"fmt"
"sort"
"sync"

"github.com/hashicorp/hcl-lang/lang"
Expand All @@ -15,6 +16,7 @@ type Decoder struct {
files map[string]*hcl.File
filesMu *sync.RWMutex

refReader ReferenceReader
rootSchema *schema.BodySchema
rootSchemaMu *sync.RWMutex
maxCandidates uint
Expand All @@ -28,6 +30,8 @@ type Decoder struct {
useUtmContent bool
}

type ReferenceReader func() lang.References

// NewDecoder creates a new Decoder
//
// Decoder is safe for use without any schema, but configuration files are loaded
Expand All @@ -49,10 +53,13 @@ func NewDecoder() *Decoder {
func (d *Decoder) SetSchema(schema *schema.BodySchema) {
d.rootSchemaMu.Lock()
defer d.rootSchemaMu.Unlock()

d.rootSchema = schema
}

func (d *Decoder) SetReferenceReader(f ReferenceReader) {
d.refReader = f
}

func (d *Decoder) SetUtmSource(src string) {
d.utmSource = src
}
Expand Down Expand Up @@ -94,6 +101,9 @@ func (p *Decoder) Filenames() []string {
for filename := range p.files {
files = append(files, filename)
}

sort.Strings(files)

return files
}

Expand Down Expand Up @@ -276,7 +286,7 @@ func traversalToAddress(traversal hcl.Traversal) (lang.Address, error) {
Key: t.Key,
})
default:
return addr, fmt.Errorf("invalid traversal: %T", tr)
return addr, fmt.Errorf("invalid traversal: %#v", tr)
}
}
return addr, nil
Expand Down
6 changes: 6 additions & 0 deletions decoder/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ func (*NoSchemaError) Error() string {
return fmt.Sprintf("no schema available")
}

type NoReferenceFound struct{}

func (*NoReferenceFound) Error() string {
return fmt.Sprintf("no matching reference found")
}

type ConstraintMismatch struct {
Expr hcl.Expression
}
Expand Down
Loading

0 comments on commit 52408e2

Please sign in to comment.