Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provider-Defined functions pre release #378

Closed
wants to merge 7 commits into from
Closed
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
11 changes: 10 additions & 1 deletion decoder/candidates.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ func (d *PathDecoder) completionAtPos(ctx context.Context, body *hclsyntax.Body,
filename := body.Range().Filename

for _, attr := range body.Attributes {
if d.isPosInsideAttrExpr(attr, pos) {
// TODO: handle nil Expr in all nested calls instead which allows us
// to recover incomplete calls to provider defined functions (which have no expression
// as they are deemed invalid by hcl while they are not completed yet)
if attr.Expr != nil && d.isPosInsideAttrExpr(attr, pos) {
if bodySchema.Extensions != nil && bodySchema.Extensions.SelfRefs {
ctx = schema.WithActiveSelfRefs(ctx)
}
Expand All @@ -82,6 +85,12 @@ func (d *PathDecoder) completionAtPos(ctx context.Context, body *hclsyntax.Body,
if attr.EqualsRange.ContainsPos(pos) {
return lang.ZeroCandidates(), nil
}

// There is a partial (aka invalid) namespaced function call in an attribute
// We abort here as we don't want to complete body schema candidates
if attr.Expr == nil {
return lang.ZeroCandidates(), nil
}
}

rng := hcl.Range{
Expand Down
34 changes: 34 additions & 0 deletions decoder/candidates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1325,6 +1325,40 @@ resource "random_resource" "test" {
}
}

func TestDecoder_CompletionAtPos_nil_expr(t *testing.T) {
ctx := context.Background()

// provider:: is not a traversal expression, so hcl will return a nil expression which needs to be
// handled gracefully
f, _ := hclsyntax.ParseConfig([]byte(`attr = provider::`), "test.tf", hcl.InitialPos)

d := testPathDecoder(t, &PathContext{
Schema: &schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"attr": {Constraint: schema.AnyExpression{OfType: cty.DynamicPseudoType}},
},
},
Files: map[string]*hcl.File{
"test.tf": f,
},
})

pos := hcl.Pos{Line: 1, Column: 18, Byte: 17}

candidates, err := d.CompletionAtPos(ctx, "test.tf", pos)
if err != nil {
t.Fatal(err)
}

expectedCandidates := lang.CompleteCandidates([]lang.Candidate{})

diff := cmp.Diff(expectedCandidates, candidates, ctydebug.CmpOptions)
if diff != "" {
t.Fatalf("unexpected schema for %s: %s", stringPos(pos), diff)
}

}

func TestDecoder_CompletionAtPos_AnyAttribute(t *testing.T) {
ctx := context.Background()
providersSchema := &schema.BlockSchema{
Expand Down
2 changes: 1 addition & 1 deletion decoder/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (d *PathDecoder) hoverAtPos(ctx context.Context, body *hclsyntax.Body, body
}, nil
}

if attr.Expr.Range().ContainsPos(pos) {
if attr.Expr != nil && attr.Expr.Range().ContainsPos(pos) {
return d.newExpression(attr.Expr, aSchema.Constraint).HoverAtPos(ctx, pos), nil
}
}
Expand Down
29 changes: 29 additions & 0 deletions decoder/hover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,35 @@ func TestDecoder_HoverAtPos_basic(t *testing.T) {
}
}

func TestDecoder_HoverAtPos_nil_expr(t *testing.T) {
// provider:: is not a traversal expression, so hcl will return a nil expression which needs to be
// handled gracefully
f, _ := hclsyntax.ParseConfig([]byte(`attr = provider::`), "test.tf", hcl.InitialPos)

d := testPathDecoder(t, &PathContext{
Schema: &schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"attr": {Constraint: schema.AnyExpression{OfType: cty.DynamicPseudoType}},
},
},
Files: map[string]*hcl.File{
"test.tf": f,
},
})

ctx := context.Background()
_, err := d.HoverAtPos(ctx, "test.tf", hcl.Pos{Line: 1, Column: 16, Byte: 15})

if err == nil {
t.Fatal("expected error")
}

positionalErr := &PositionalError{}
if !errors.As(err, &positionalErr) {
t.Fatal("expected PositionalError for invalid expression")
}
}

func TestDecoder_HoverAtPos_URL(t *testing.T) {
resourceLabelSchema := []*schema.LabelSchema{
{Name: "type", IsDepKey: true},
Expand Down
5 changes: 5 additions & 0 deletions decoder/reference_origins.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ func (d *PathDecoder) referenceOriginsInBody(body hcl.Body, bodySchema *schema.B
}
}

// skip if the attribute Expr is nil as all following origins are based on the expression
if attr.Expr == nil {
continue
}

if aSchema.IsDepKey && bodySchema.Targets != nil {
origins = append(origins, reference.DirectOrigin{
Range: attr.Expr.Range(),
Expand Down
28 changes: 28 additions & 0 deletions decoder/reference_origins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl-lang/lang"
"github.com/hashicorp/hcl-lang/reference"
"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty-debug/ctydebug"
"github.com/zclconf/go-cty/cty"
)
Expand Down Expand Up @@ -763,3 +765,29 @@ func TestReferenceOriginsTargetingPos(t *testing.T) {
})
}
}

func TestCollectReferenceOrigins_nil_expr(t *testing.T) {
// provider:: is not a traversal expression, so hcl will return a nil expression which needs to be
// handled gracefully
f, _ := hclsyntax.ParseConfig([]byte(`attr = provider::`), "test.tf", hcl.InitialPos)

d := testPathDecoder(t, &PathContext{
Schema: &schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"attr": {Constraint: schema.AnyExpression{OfType: cty.DynamicPseudoType}},
},
},
Files: map[string]*hcl.File{
"test.tf": f,
},
})

targets, err := d.CollectReferenceOrigins()
if err != nil {
t.Fatal("unexpected error when collecting reference origins while there was no expr in one of them")
}

if len(targets) != 0 {
t.Fatalf("expected no targets, got %d", len(targets))
}
}
5 changes: 5 additions & 0 deletions decoder/reference_targets.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,11 @@ func (d *PathDecoder) decodeReferenceTargetsForAttribute(attr *hcl.Attribute, at

ctx := context.Background()

// Early return as we don't attempt to recover reference targets for invalid expressions
if attr.Expr == nil {
return refs
}

expr := d.newExpression(attr.Expr, attrSchema.Constraint)
if eType, ok := expr.(ReferenceTargetsExpression); ok {
var targetCtx *TargetContext
Expand Down
27 changes: 27 additions & 0 deletions decoder/reference_targets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl-lang/lang"
"github.com/hashicorp/hcl-lang/reference"
"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty-debug/ctydebug"
"github.com/zclconf/go-cty/cty"
)
Expand Down Expand Up @@ -352,3 +354,28 @@ func TestReferenceTargetForOriginAtPos(t *testing.T) {
})
}
}

func TestCollectReferenceTargets_nil_expr(t *testing.T) {
// provider:: is not a traversal expression, so hcl will return a nil expression which needs to be
// handled gracefully
f, _ := hclsyntax.ParseConfig([]byte(`attr = provider::`), "test.tf", hcl.InitialPos)

d := testPathDecoder(t, &PathContext{
Schema: &schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"attr": {Constraint: schema.AnyExpression{OfType: cty.DynamicPseudoType}},
},
},
Files: map[string]*hcl.File{
"test.tf": f,
},
})
targets, err := d.CollectReferenceTargets()
if err != nil {
t.Fatal("unexpected error when collecting reference targets while there was no expr in one of them")
}

if len(targets) != 0 {
t.Fatalf("expected no targets, got %d", len(targets))
}
}
5 changes: 4 additions & 1 deletion decoder/semantic_tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ func (d *PathDecoder) tokensForBody(ctx context.Context, body *hclsyntax.Body, b
Range: attr.NameRange,
})

tokens = append(tokens, d.newExpression(attr.Expr, attrSchema.Constraint).SemanticTokens(ctx)...)
// Invalid expressions may be nil, and we don't want to panic further down the line
if attr.Expr != nil {
tokens = append(tokens, d.newExpression(attr.Expr, attrSchema.Constraint).SemanticTokens(ctx)...)
}
}

for _, block := range body.Blocks {
Expand Down
49 changes: 49 additions & 0 deletions decoder/semantic_tokens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,55 @@ resource "vault_auth_backend" "blah" {
}
}

func TestDecoder_SemanticTokensInFile_nil_expr(t *testing.T) {
// provider:: is not a traversal expression, so hcl will return a nil expression which needs to be
// handled gracefully
f, _ := hclsyntax.ParseConfig([]byte(`attr = provider::`), "test.tf", hcl.InitialPos)

d := testPathDecoder(t, &PathContext{
Schema: &schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"attr": {Constraint: schema.AnyExpression{OfType: cty.DynamicPseudoType}},
},
},
Files: map[string]*hcl.File{
"test.tf": f,
},
})

ctx := context.Background()

tokens, err := d.SemanticTokensInFile(ctx, "test.tf")
if err != nil {
t.Fatal(err)
}

expectedTokens := []lang.SemanticToken{
{
Type: "hcl-attrName",
Modifiers: lang.SemanticTokenModifiers{},
Range: hcl.Range{
Filename: "test.tf",
Start: hcl.Pos{
Line: 1,
Column: 1,
Byte: 0,
},
End: hcl.Pos{
Line: 1,
Column: 5,
Byte: 4,
},
},
},
}

diff := cmp.Diff(expectedTokens, tokens)
if diff != "" {
t.Fatalf("unexpected tokens: %s", diff)
}
}

func TestDecoder_SemanticTokensInFile_dependentSchema(t *testing.T) {
bodySchema := &schema.BodySchema{
Blocks: map[string]*schema.BlockSchema{
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.21.0
require (
github.com/google/go-cmp v0.6.0
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/hcl/v2 v2.19.1
github.com/hashicorp/hcl/v2 v2.19.2-0.20240226153113-c152f81a277f
github.com/mh-cbon/go-fmt-fail v0.0.0-20160815164508-67765b3fbcb5
github.com/zclconf/go-cty v1.14.2
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI=
github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
github.com/hashicorp/hcl/v2 v2.19.2-0.20240226153113-c152f81a277f h1:NhMIDxWIF21iTWPHNgMObGU4smTzfzwZbNxsqoCjzug=
github.com/hashicorp/hcl/v2 v2.19.2-0.20240226153113-c152f81a277f/go.mod h1:WmcD/Ym72MDOOx5F62Ly+leloeu6H7m0pG7VBiU6pQk=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
Expand Down
Loading