-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
decoder: Implement hover for TypeDeclaration
- Loading branch information
1 parent
ec6416f
commit e2ef0d3
Showing
3 changed files
with
377 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
package decoder | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/hashicorp/hcl-lang/lang" | ||
"github.com/hashicorp/hcl/v2" | ||
"github.com/hashicorp/hcl/v2/ext/typeexpr" | ||
"github.com/hashicorp/hcl/v2/hclsyntax" | ||
) | ||
|
||
func (td TypeDeclaration) HoverAtPos(ctx context.Context, pos hcl.Pos) *lang.HoverData { | ||
switch eType := td.expr.(type) { | ||
case *hclsyntax.ScopeTraversalExpr: | ||
if len(eType.Traversal) != 1 { | ||
return nil | ||
} | ||
|
||
if eType.Range().ContainsPos(pos) { | ||
typ, _ := typeexpr.TypeConstraint(eType) | ||
content, err := hoverContentForType(typ, 0) | ||
if err != nil { | ||
return nil | ||
} | ||
return &lang.HoverData{ | ||
Content: lang.Markdown(content), | ||
Range: eType.Range(), | ||
} | ||
} | ||
case *hclsyntax.FunctionCallExpr: | ||
// position in complex type name | ||
if eType.NameRange.ContainsPos(pos) { | ||
typ, diags := typeexpr.TypeConstraint(eType) | ||
if len(diags) > 0 { | ||
return nil | ||
} | ||
|
||
content, err := hoverContentForType(typ, 0) | ||
if err != nil { | ||
return nil | ||
} | ||
return &lang.HoverData{ | ||
Content: lang.Markdown(content), | ||
Range: eType.Range(), | ||
} | ||
} | ||
|
||
// position inside paranthesis | ||
if hcl.RangeBetween(eType.OpenParenRange, eType.CloseParenRange).ContainsPos(pos) { | ||
if isSingleArgTypeName(eType.Name) { | ||
if len(eType.Args) == 0 { | ||
return nil | ||
} | ||
|
||
if len(eType.Args) == 1 && eType.Args[0].Range().ContainsPos(pos) { | ||
cons := TypeDeclaration{ | ||
expr: eType.Args[0], | ||
pathCtx: td.pathCtx, | ||
} | ||
return cons.HoverAtPos(ctx, pos) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
if eType.Name == "object" { | ||
if len(eType.Args) != 1 { | ||
return nil | ||
} | ||
objExpr, ok := eType.Args[0].(*hclsyntax.ObjectConsExpr) | ||
if !ok { | ||
return nil | ||
} | ||
if !objExpr.Range().ContainsPos(pos) { | ||
return nil | ||
} | ||
|
||
// account for position on {} braces | ||
closeRange := hcl.Range{ | ||
Filename: objExpr.Range().Filename, | ||
Start: hcl.Pos{ | ||
Line: objExpr.Range().End.Line, | ||
Column: objExpr.Range().End.Column - 1, | ||
Byte: objExpr.Range().End.Byte - 1, | ||
}, | ||
End: objExpr.Range().End, | ||
} | ||
if objExpr.OpenRange.ContainsPos(pos) || closeRange.ContainsPos(pos) { | ||
typ, diags := typeexpr.TypeConstraint(eType) | ||
if len(diags) > 0 { | ||
return nil | ||
} | ||
content, err := hoverContentForType(typ, 0) | ||
if err != nil { | ||
return nil | ||
} | ||
return &lang.HoverData{ | ||
Content: lang.Markdown(content), | ||
Range: eType.Range(), | ||
} | ||
} | ||
|
||
cons := TypeDeclaration{ | ||
expr: objExpr, | ||
pathCtx: td.pathCtx, | ||
insideObject: true, | ||
} | ||
return cons.HoverAtPos(ctx, pos) | ||
} | ||
|
||
if eType.Name == "tuple" { | ||
if len(eType.Args) != 1 { | ||
return nil | ||
} | ||
tupleExpr, ok := eType.Args[0].(*hclsyntax.TupleConsExpr) | ||
if !ok { | ||
return nil | ||
} | ||
if !tupleExpr.Range().ContainsPos(pos) { | ||
return nil | ||
} | ||
|
||
// account for position on [] brackets | ||
closeRange := hcl.Range{ | ||
Filename: tupleExpr.Range().Filename, | ||
Start: hcl.Pos{ | ||
Line: tupleExpr.Range().End.Line, | ||
Column: tupleExpr.Range().End.Column - 1, | ||
Byte: tupleExpr.Range().End.Byte - 1, | ||
}, | ||
End: tupleExpr.Range().End, | ||
} | ||
if tupleExpr.OpenRange.ContainsPos(pos) || closeRange.ContainsPos(pos) { | ||
typ, diags := typeexpr.TypeConstraint(eType) | ||
if len(diags) > 0 { | ||
return nil | ||
} | ||
content, err := hoverContentForType(typ, 0) | ||
if err != nil { | ||
return nil | ||
} | ||
return &lang.HoverData{ | ||
Content: lang.Markdown(content), | ||
Range: eType.Range(), | ||
} | ||
} | ||
|
||
for _, expr := range tupleExpr.Exprs { | ||
if expr.Range().ContainsPos(pos) { | ||
cons := TypeDeclaration{ | ||
expr: expr, | ||
pathCtx: td.pathCtx, | ||
} | ||
return cons.HoverAtPos(ctx, pos) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
case *hclsyntax.ObjectConsExpr: | ||
if !td.insideObject { | ||
// reject hover in bare object notation w/out object() | ||
return nil | ||
} | ||
|
||
if len(eType.Items) == 0 { | ||
return nil | ||
} | ||
|
||
for _, item := range eType.Items { | ||
if item.KeyExpr.Range().ContainsPos(pos) { | ||
val, _ := item.KeyExpr.Value(nil) | ||
typ, _ := typeexpr.TypeConstraint(item.ValueExpr) | ||
return &lang.HoverData{ | ||
Content: lang.Markdown(fmt.Sprintf("`%s` = _%s_", val.AsString(), typ.FriendlyNameForConstraint())), | ||
Range: hcl.RangeBetween(item.KeyExpr.Range(), item.ValueExpr.Range()), | ||
} | ||
} | ||
if item.ValueExpr.Range().ContainsPos(pos) { | ||
cons := TypeDeclaration{ | ||
expr: item.ValueExpr, | ||
pathCtx: td.pathCtx, | ||
} | ||
return cons.HoverAtPos(ctx, pos) | ||
} | ||
} | ||
return nil | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
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" | ||
) | ||
|
||
func TestHoverAtPos_exprTypeDeclaration(t *testing.T) { | ||
testCases := []struct { | ||
testName string | ||
attrSchema map[string]*schema.AttributeSchema | ||
cfg string | ||
pos hcl.Pos | ||
expectedHoverData *lang.HoverData | ||
}{ | ||
{ | ||
"primitive type", | ||
map[string]*schema.AttributeSchema{ | ||
"attr": { | ||
Constraint: schema.TypeDeclaration{}, | ||
}, | ||
}, | ||
`attr = string`, | ||
hcl.Pos{Line: 1, Column: 11, Byte: 10}, | ||
&lang.HoverData{ | ||
Content: lang.Markdown(`_string_`), | ||
Range: hcl.Range{ | ||
Filename: "test.tf", | ||
Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, | ||
End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
"list type on list", | ||
map[string]*schema.AttributeSchema{ | ||
"attr": { | ||
Constraint: schema.TypeDeclaration{}, | ||
}, | ||
}, | ||
`attr = list(string)`, | ||
hcl.Pos{Line: 1, Column: 10, Byte: 9}, | ||
&lang.HoverData{ | ||
Content: lang.Markdown(`_list of string_`), | ||
Range: hcl.Range{ | ||
Filename: "test.tf", | ||
Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, | ||
End: hcl.Pos{Line: 1, Column: 20, Byte: 19}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
"list type on element type", | ||
map[string]*schema.AttributeSchema{ | ||
"attr": { | ||
Constraint: schema.TypeDeclaration{}, | ||
}, | ||
}, | ||
`attr = list(string)`, | ||
hcl.Pos{Line: 1, Column: 16, Byte: 15}, | ||
&lang.HoverData{ | ||
Content: lang.Markdown(`_string_`), | ||
Range: hcl.Range{ | ||
Filename: "test.tf", | ||
Start: hcl.Pos{Line: 1, Column: 13, Byte: 12}, | ||
End: hcl.Pos{Line: 1, Column: 19, Byte: 18}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
"tuple type on tuple", | ||
map[string]*schema.AttributeSchema{ | ||
"attr": { | ||
Constraint: schema.TypeDeclaration{}, | ||
}, | ||
}, | ||
`attr = tuple([string])`, | ||
hcl.Pos{Line: 1, Column: 10, Byte: 9}, | ||
&lang.HoverData{ | ||
Content: lang.Markdown(`_tuple_`), | ||
Range: hcl.Range{ | ||
Filename: "test.tf", | ||
Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, | ||
End: hcl.Pos{Line: 1, Column: 23, Byte: 22}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
"object type on object", | ||
map[string]*schema.AttributeSchema{ | ||
"attr": { | ||
Constraint: schema.TypeDeclaration{}, | ||
}, | ||
}, | ||
`attr = object({ | ||
foo = string | ||
}) | ||
`, | ||
hcl.Pos{Line: 1, Column: 11, Byte: 10}, | ||
&lang.HoverData{ | ||
Content: lang.Markdown("```\n{\n foo = string\n}\n```\n_object_"), | ||
Range: hcl.Range{ | ||
Filename: "test.tf", | ||
Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, | ||
End: hcl.Pos{Line: 3, Column: 3, Byte: 33}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
"object type on attribute name", | ||
map[string]*schema.AttributeSchema{ | ||
"attr": { | ||
Constraint: schema.TypeDeclaration{}, | ||
}, | ||
}, | ||
`attr = object({ | ||
foo = string | ||
}) | ||
`, | ||
hcl.Pos{Line: 2, Column: 5, Byte: 20}, | ||
&lang.HoverData{ | ||
Content: lang.Markdown("`foo` = _string_"), | ||
Range: hcl.Range{ | ||
Filename: "test.tf", | ||
Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, | ||
End: hcl.Pos{Line: 2, Column: 15, Byte: 30}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
"object type on attribute value", | ||
map[string]*schema.AttributeSchema{ | ||
"attr": { | ||
Constraint: schema.TypeDeclaration{}, | ||
}, | ||
}, | ||
`attr = object({ | ||
foo = string | ||
}) | ||
`, | ||
hcl.Pos{Line: 2, Column: 11, Byte: 26}, | ||
&lang.HoverData{ | ||
Content: lang.Markdown("_string_"), | ||
Range: hcl.Range{ | ||
Filename: "test.tf", | ||
Start: hcl.Pos{Line: 2, Column: 9, Byte: 24}, | ||
End: hcl.Pos{Line: 2, Column: 15, Byte: 30}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
for i, tc := range testCases { | ||
t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { | ||
bodySchema := &schema.BodySchema{ | ||
Attributes: tc.attrSchema, | ||
} | ||
|
||
f, _ := hclsyntax.ParseConfig([]byte(tc.cfg), "test.tf", hcl.InitialPos) | ||
d := testPathDecoder(t, &PathContext{ | ||
Schema: bodySchema, | ||
Files: map[string]*hcl.File{ | ||
"test.tf": f, | ||
}, | ||
}) | ||
|
||
ctx := context.Background() | ||
hoverData, err := d.HoverAtPos(ctx, "test.tf", tc.pos) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if diff := cmp.Diff(tc.expectedHoverData, hoverData); diff != "" { | ||
t.Fatalf("unexpected hover data: %s", diff) | ||
} | ||
}) | ||
} | ||
} |