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

Support for nested expressions - 1st stage #177

Merged
merged 16 commits into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from 12 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
23 changes: 20 additions & 3 deletions decoder/attribute_candidates.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ import (
)

func attributeSchemaToCandidate(name string, attr *schema.AttributeSchema, rng hcl.Range) lang.Candidate {
var snippet string
var triggerSuggest bool
if attr.Constraint != nil {
cData := attr.Constraint.EmptyCompletionData(1)
snippet = fmt.Sprintf("%s = %s", name, cData.Snippet)
triggerSuggest = cData.TriggerSuggest
} else {
snippet = snippetForAttribute(name, attr)
triggerSuggest = triggerSuggestForExprConstraints(attr.Expr)
}

return lang.Candidate{
Label: name,
Detail: detailForAttribute(attr),
Expand All @@ -20,10 +31,10 @@ func attributeSchemaToCandidate(name string, attr *schema.AttributeSchema, rng h
Kind: lang.AttributeCandidateKind,
TextEdit: lang.TextEdit{
NewText: name,
Snippet: snippetForAttribute(name, attr),
Snippet: snippet,
Range: rng,
},
TriggerSuggest: triggerSuggestForExprConstraints(attr.Expr),
TriggerSuggest: triggerSuggest,
}
}

Expand All @@ -40,7 +51,13 @@ func detailForAttribute(attr *schema.AttributeSchema) string {
details = append(details, "sensitive")
}

friendlyName := attr.Expr.FriendlyName()
var friendlyName string
if attr.Constraint != nil {
friendlyName = attr.Constraint.FriendlyName()
} else {
friendlyName = attr.Expr.FriendlyName()
}

if friendlyName != "" {
details = append(details, friendlyName)
}
Expand Down
188 changes: 172 additions & 16 deletions decoder/attribute_candidates_test.go
Original file line number Diff line number Diff line change
@@ -1,56 +1,120 @@
package decoder

import (
"context"
"fmt"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl-lang/lang"
"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty/cty"
)

func TestSnippetForAttribute(t *testing.T) {
testCases := []struct {
testName string
attrName string
attrSchema *schema.AttributeSchema
expectedSnippet string
testName string
attrName string
attrSchema *schema.AttributeSchema
expectedCandidates lang.Candidates
}{
{
"primitive type",
"primitive",
&schema.AttributeSchema{
Expr: schema.LiteralTypeOnly(cty.String),
},
`primitive = "${1:value}"`,
lang.CompleteCandidates([]lang.Candidate{
{
Label: "primitive",
Detail: "string",
TextEdit: lang.TextEdit{
Range: hcl.Range{
Filename: "test.tf",
Start: hcl.InitialPos,
End: hcl.InitialPos,
},
NewText: "primitive",
Snippet: `primitive = "${1:value}"`,
},
Kind: lang.AttributeCandidateKind,
},
}),
},
{
"map of strings",
"mymap",
&schema.AttributeSchema{
Expr: schema.LiteralTypeOnly(cty.Map(cty.String)),
},
`mymap = {
lang.CompleteCandidates([]lang.Candidate{
{
Label: "mymap",
Detail: "map of string",
TextEdit: lang.TextEdit{
Range: hcl.Range{
Filename: "test.tf",
Start: hcl.InitialPos,
End: hcl.InitialPos,
},
NewText: "mymap",
Snippet: `mymap = {
"${1:key}" = "${2:value}"
}`,
},
Kind: lang.AttributeCandidateKind,
},
}),
},
{
"map of numbers",
"mymap",
&schema.AttributeSchema{
Expr: schema.LiteralTypeOnly(cty.Map(cty.Number)),
},
`mymap = {
lang.CompleteCandidates([]lang.Candidate{
{
Label: "mymap",
Detail: "map of number",
TextEdit: lang.TextEdit{
Range: hcl.Range{
Filename: "test.tf",
Start: hcl.InitialPos,
End: hcl.InitialPos,
},
NewText: "mymap",
Snippet: `mymap = {
"${1:key}" = ${2:1}
}`,
},
Kind: lang.AttributeCandidateKind,
},
}),
},
{
"list of numbers",
"mylist",
&schema.AttributeSchema{
Expr: schema.LiteralTypeOnly(cty.List(cty.Number)),
},
`mylist = [ ${1:1} ]`,
lang.CompleteCandidates([]lang.Candidate{
{
Label: "mylist",
Detail: "list of number",
TextEdit: lang.TextEdit{
Range: hcl.Range{
Filename: "test.tf",
Start: hcl.InitialPos,
End: hcl.InitialPos,
},
NewText: "mylist",
Snippet: `mylist = [ ${1:1} ]`,
},
Kind: lang.AttributeCandidateKind,
},
}),
},
{
"list of objects",
Expand All @@ -61,18 +125,48 @@ func TestSnippetForAttribute(t *testing.T) {
"second": cty.Number,
}))),
},
`mylistobj = [ {
lang.CompleteCandidates([]lang.Candidate{
{
Label: "mylistobj",
Detail: "list of object",
TextEdit: lang.TextEdit{
Range: hcl.Range{
Filename: "test.tf",
Start: hcl.InitialPos,
End: hcl.InitialPos,
},
NewText: "mylistobj",
Snippet: `mylistobj = [ {
first = "${1:value}"
second = ${2:1}
} ]`,
},
Kind: lang.AttributeCandidateKind,
},
}),
},
{
"set of numbers",
"myset",
&schema.AttributeSchema{
Expr: schema.LiteralTypeOnly(cty.Set(cty.Number)),
},
`myset = [ ${1:1} ]`,
lang.CompleteCandidates([]lang.Candidate{
{
Label: "myset",
Detail: "set of number",
TextEdit: lang.TextEdit{
Range: hcl.Range{
Filename: "test.tf",
Start: hcl.InitialPos,
End: hcl.InitialPos,
},
NewText: "myset",
Snippet: `myset = [ ${1:1} ]`,
},
Kind: lang.AttributeCandidateKind,
},
}),
},
{
"object",
Expand All @@ -84,19 +178,49 @@ func TestSnippetForAttribute(t *testing.T) {
"keybool": cty.Bool,
})),
},
`myobj = {
lang.CompleteCandidates([]lang.Candidate{
{
Label: "myobj",
Detail: "object",
TextEdit: lang.TextEdit{
Range: hcl.Range{
Filename: "test.tf",
Start: hcl.InitialPos,
End: hcl.InitialPos,
},
NewText: "myobj",
Snippet: `myobj = {
keybool = ${1:false}
keynum = ${2:1}
keystr = "${3:value}"
}`,
},
Kind: lang.AttributeCandidateKind,
},
}),
},
{
"unknown type",
"mynil",
&schema.AttributeSchema{
Expr: schema.LiteralTypeOnly(cty.DynamicPseudoType),
},
`mynil = ${1}`,
lang.CompleteCandidates([]lang.Candidate{
{
Label: "mynil",
Detail: "any type",
TextEdit: lang.TextEdit{
Range: hcl.Range{
Filename: "test.tf",
Start: hcl.InitialPos,
End: hcl.InitialPos,
},
NewText: "mynil",
Snippet: `mynil = ${1}`,
},
Kind: lang.AttributeCandidateKind,
},
}),
},
{
"nested object",
Expand All @@ -110,21 +234,53 @@ func TestSnippetForAttribute(t *testing.T) {
}),
})),
},
`myobj = {
lang.CompleteCandidates([]lang.Candidate{
{
Label: "myobj",
Detail: "object",
TextEdit: lang.TextEdit{
Range: hcl.Range{
Filename: "test.tf",
Start: hcl.InitialPos,
End: hcl.InitialPos,
},
NewText: "myobj",
Snippet: `myobj = {
another = {
nested_number = ${1:1}
nestedstr = "${2:value}"
}
keystr = "${3:value}"
}`,
},
Kind: lang.AttributeCandidateKind,
},
}),
},
}

for i, tc := range testCases {
t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) {
snippet := snippetForAttribute(tc.attrName, tc.attrSchema)
if diff := cmp.Diff(tc.expectedSnippet, snippet); diff != "" {
t.Fatalf("unexpected snippet: %s", diff)
f, _ := hclsyntax.ParseConfig([]byte("\n"), "test.tf", hcl.InitialPos)
d := testPathDecoder(t, &PathContext{
Schema: &schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
tc.attrName: tc.attrSchema,
},
},
Files: map[string]*hcl.File{
"test.tf": f,
},
})

ctx := context.Background()
candidates, err := d.CandidatesAtPos(ctx, "test.tf", hcl.InitialPos)
if err != nil {
t.Fatal(err)
}

if diff := cmp.Diff(tc.expectedCandidates, candidates); diff != "" {
t.Fatalf("unexpected candidates: %s", diff)
}
})
}
Expand Down
40 changes: 40 additions & 0 deletions decoder/expr_keyword.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package decoder

import (
"context"

"github.com/hashicorp/hcl-lang/lang"
"github.com/hashicorp/hcl-lang/reference"
"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
)

type Keyword struct {
expr hcl.Expression
cons schema.Keyword
}

func (kw Keyword) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candidate {
// TODO
return nil
}

func (kw Keyword) HoverAtPos(ctx context.Context, pos hcl.Pos) *lang.HoverData {
// TODO
return nil
}

func (kw Keyword) SemanticTokens(ctx context.Context) []lang.SemanticToken {
// TODO
return nil
}

func (kw Keyword) ReferenceOrigins(allowSelfRefs bool) reference.Origins {
// TODO
return nil
}

func (kw Keyword) ReferenceTargets(attrAddr *schema.AttributeAddrSchema) reference.Targets {
// TODO
return nil
}
Loading