diff --git a/decoder/expr_type_declaration.go b/decoder/expr_type_declaration.go index af9474a9..ffbd9997 100644 --- a/decoder/expr_type_declaration.go +++ b/decoder/expr_type_declaration.go @@ -1,9 +1,6 @@ package decoder import ( - "context" - - "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2" ) @@ -14,16 +11,6 @@ type TypeDeclaration struct { pathCtx *PathContext } -func (td TypeDeclaration) HoverAtPos(ctx context.Context, pos hcl.Pos) *lang.HoverData { - // TODO - return nil -} - -func (td TypeDeclaration) SemanticTokens(ctx context.Context) []lang.SemanticToken { - // TODO - return nil -} - func isTypeNameWithElementOnly(name string) bool { return name == "list" || name == "set" || name == "map" } diff --git a/decoder/expr_type_declaration_hover.go b/decoder/expr_type_declaration_hover.go new file mode 100644 index 00000000..683c69eb --- /dev/null +++ b/decoder/expr_type_declaration_hover.go @@ -0,0 +1,189 @@ +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 isTypeNameWithElementOnly(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" { + return td.objectHoverAtPos(ctx, eType, pos) + } + + if eType.Name == "tuple" { + return td.tupleHoverAtPos(ctx, eType, pos) + } + } + } + return nil +} + +func (td TypeDeclaration) objectHoverAtPos(ctx context.Context, funcExpr *hclsyntax.FunctionCallExpr, pos hcl.Pos) *lang.HoverData { + if len(funcExpr.Args) != 1 { + return nil + } + objExpr, ok := funcExpr.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(funcExpr) + if len(diags) > 0 { + return nil + } + content, err := hoverContentForType(typ, 0) + if err != nil { + return nil + } + return &lang.HoverData{ + Content: lang.Markdown(content), + Range: objExpr.Range(), + } + } + + for _, item := range objExpr.Items { + if item.KeyExpr.Range().ContainsPos(pos) { + rawKey, _, ok := rawObjectKey(item.KeyExpr) + if !ok { + // un-decodable key expression + return nil + } + + typ, _ := typeexpr.TypeConstraint(item.ValueExpr) + return &lang.HoverData{ + Content: lang.Markdown(fmt.Sprintf("`%s` = _%s_", rawKey, 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 +} + +func (td TypeDeclaration) tupleHoverAtPos(ctx context.Context, funcExpr *hclsyntax.FunctionCallExpr, pos hcl.Pos) *lang.HoverData { + if len(funcExpr.Args) != 1 { + return nil + } + tupleExpr, ok := funcExpr.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(funcExpr) + if len(diags) > 0 { + return nil + } + content, err := hoverContentForType(typ, 0) + if err != nil { + return nil + } + return &lang.HoverData{ + Content: lang.Markdown(content), + Range: funcExpr.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 +} diff --git a/decoder/expr_type_declaration_hover_test.go b/decoder/expr_type_declaration_hover_test.go new file mode 100644 index 00000000..15523237 --- /dev/null +++ b/decoder/expr_type_declaration_hover_test.go @@ -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) + } + }) + } +} diff --git a/decoder/expr_type_declaration_semtok.go b/decoder/expr_type_declaration_semtok.go new file mode 100644 index 00000000..f54004f3 --- /dev/null +++ b/decoder/expr_type_declaration_semtok.go @@ -0,0 +1,130 @@ +package decoder + +import ( + "context" + + "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl/v2/hclsyntax" +) + +func (td TypeDeclaration) SemanticTokens(ctx context.Context) []lang.SemanticToken { + switch eType := td.expr.(type) { + case *hclsyntax.ScopeTraversalExpr: + if len(eType.Traversal) != 1 { + return []lang.SemanticToken{} + } + + if isPrimitiveTypeDeclaration(eType.Traversal.RootName()) { + return []lang.SemanticToken{ + { + Type: lang.TokenTypePrimitive, + Modifiers: []lang.SemanticTokenModifier{}, + Range: eType.Range(), + }, + } + } + case *hclsyntax.FunctionCallExpr: + if isTypeNameWithElementOnly(eType.Name) { + tokens := make([]lang.SemanticToken, 0) + + tokens = append(tokens, lang.SemanticToken{ + Type: lang.TokenTypeCapsule, + Modifiers: []lang.SemanticTokenModifier{}, + Range: eType.NameRange, + }) + + if len(eType.Args) == 0 { + return tokens + } + + if len(eType.Args) == 1 { + cons := TypeDeclaration{ + expr: eType.Args[0], + pathCtx: td.pathCtx, + } + tokens = append(tokens, cons.SemanticTokens(ctx)...) + + return tokens + } + + return []lang.SemanticToken{} + } + + if eType.Name == "object" { + return td.objectSemanticTokens(ctx, eType) + } + + if eType.Name == "tuple" { + return td.tupleSemanticTokens(ctx, eType) + } + } + return nil +} + +func (td TypeDeclaration) objectSemanticTokens(ctx context.Context, funcExpr *hclsyntax.FunctionCallExpr) []lang.SemanticToken { + tokens := make([]lang.SemanticToken, 0) + tokens = append(tokens, lang.SemanticToken{ + Type: lang.TokenTypeCapsule, + Modifiers: []lang.SemanticTokenModifier{}, + Range: funcExpr.NameRange, + }) + + if len(funcExpr.Args) != 1 { + return tokens + } + + objExpr, ok := funcExpr.Args[0].(*hclsyntax.ObjectConsExpr) + if !ok { + return []lang.SemanticToken{} + } + + for _, item := range objExpr.Items { + _, _, ok := rawObjectKey(item.KeyExpr) + if !ok { + // avoid reporting un-decodable key + return tokens + } + + tokens = append(tokens, lang.SemanticToken{ + Type: lang.TokenAttrName, + Modifiers: []lang.SemanticTokenModifier{}, + Range: item.KeyExpr.Range(), + }) + + cons := TypeDeclaration{ + expr: item.ValueExpr, + pathCtx: td.pathCtx, + } + tokens = append(tokens, cons.SemanticTokens(ctx)...) + } + + return tokens +} + +func (td TypeDeclaration) tupleSemanticTokens(ctx context.Context, funcExpr *hclsyntax.FunctionCallExpr) []lang.SemanticToken { + tokens := make([]lang.SemanticToken, 0) + tokens = append(tokens, lang.SemanticToken{ + Type: lang.TokenTypeCapsule, + Modifiers: []lang.SemanticTokenModifier{}, + Range: funcExpr.NameRange, + }) + + if len(funcExpr.Args) != 1 { + return tokens + } + + tupleExpr, ok := funcExpr.Args[0].(*hclsyntax.TupleConsExpr) + if !ok { + return []lang.SemanticToken{} + } + + for _, expr := range tupleExpr.Exprs { + cons := TypeDeclaration{ + expr: expr, + pathCtx: td.pathCtx, + } + tokens = append(tokens, cons.SemanticTokens(ctx)...) + } + + return tokens +} diff --git a/decoder/expr_type_declaration_semtok_test.go b/decoder/expr_type_declaration_semtok_test.go new file mode 100644 index 00000000..89270a21 --- /dev/null +++ b/decoder/expr_type_declaration_semtok_test.go @@ -0,0 +1,393 @@ +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 TestSemanticTokens_exprTypeDeclaration(t *testing.T) { + testCases := []struct { + testName string + attrSchema map[string]*schema.AttributeSchema + cfg string + expectedTokens []lang.SemanticToken + }{ + { + "primitive type", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.TypeDeclaration{}, + }, + }, + `attr = string`, + []lang.SemanticToken{ + { + Type: lang.TokenAttrName, + 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}, + }, + }, + { + Type: lang.TokenTypePrimitive, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + }, + }, + }, + { + "invalid primitive type", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.TypeDeclaration{}, + }, + }, + `attr = foobar`, + []lang.SemanticToken{ + { + Type: lang.TokenAttrName, + 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}, + }, + }, + }, + }, + { + "single-argument complex type", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.TypeDeclaration{}, + }, + }, + `attr = list(string)`, + []lang.SemanticToken{ + { + Type: lang.TokenAttrName, + 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}, + }, + }, + { + Type: lang.TokenTypeCapsule, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + }, + }, + { + Type: lang.TokenTypePrimitive, + Modifiers: lang.SemanticTokenModifiers{}, + 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", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.TypeDeclaration{}, + }, + }, + `attr = tuple([string, bool, number])`, + []lang.SemanticToken{ + { + Type: lang.TokenAttrName, + 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}, + }, + }, + { + Type: lang.TokenTypeCapsule, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 13, Byte: 12}, + }, + }, + { + Type: lang.TokenTypePrimitive, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: hcl.Pos{Line: 1, Column: 21, Byte: 20}, + }, + }, + { + Type: lang.TokenTypePrimitive, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 23, Byte: 22}, + End: hcl.Pos{Line: 1, Column: 27, Byte: 26}, + }, + }, + { + Type: lang.TokenTypePrimitive, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 29, Byte: 28}, + End: hcl.Pos{Line: 1, Column: 35, Byte: 34}, + }, + }, + }, + }, + { + "object", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.TypeDeclaration{}, + }, + }, + `attr = object({ + foo = string + bar = number +})`, + []lang.SemanticToken{ + { + Type: lang.TokenAttrName, + 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}, + }, + }, + { + Type: lang.TokenTypeCapsule, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + }, + { + Type: lang.TokenAttrName, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 2, Column: 6, Byte: 21}, + }, + }, + { + Type: lang.TokenTypePrimitive, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 9, Byte: 24}, + End: hcl.Pos{Line: 2, Column: 15, Byte: 30}, + }, + }, + { + Type: lang.TokenAttrName, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 3, Column: 3, Byte: 33}, + End: hcl.Pos{Line: 3, Column: 6, Byte: 36}, + }, + }, + { + Type: lang.TokenTypePrimitive, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 3, Column: 9, Byte: 39}, + End: hcl.Pos{Line: 3, Column: 15, Byte: 45}, + }, + }, + }, + }, + { + "object with complex types", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.TypeDeclaration{}, + }, + }, + `attr = object({ + foo = list(string) + bar = tuple([bool, string]) + baz = object({ + paw = string + }) +})`, + []lang.SemanticToken{ + { + Type: lang.TokenAttrName, + 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}, + }, + }, + { + Type: lang.TokenTypeCapsule, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + }, + }, + { + Type: lang.TokenAttrName, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 3, Byte: 18}, + End: hcl.Pos{Line: 2, Column: 6, Byte: 21}, + }, + }, + { + Type: lang.TokenTypeCapsule, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 2, Column: 9, Byte: 24}, + End: hcl.Pos{Line: 2, Column: 13, Byte: 28}, + }, + }, + { + Type: lang.TokenTypePrimitive, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Start: hcl.Pos{Line: 2, Column: 14, Byte: 29}, + End: hcl.Pos{Line: 2, Column: 20, Byte: 35}, + Filename: "test.tf", + }, + }, + { + Type: lang.TokenAttrName, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 3, Byte: 39}, + End: hcl.Pos{Line: 3, Column: 6, Byte: 42}, + Filename: "test.tf", + }, + }, + { + Type: lang.TokenTypeCapsule, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 9, Byte: 45}, + End: hcl.Pos{Line: 3, Column: 14, Byte: 50}, + Filename: "test.tf", + }, + }, + { + Type: lang.TokenTypePrimitive, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Start: hcl.Pos{Line: 3, Column: 16, Byte: 52}, + End: hcl.Pos{Line: 3, Column: 20, Byte: 56}, + Filename: "test.tf", + }, + }, + { + Type: lang.TokenTypePrimitive, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 3, Column: 22, Byte: 58}, + End: hcl.Pos{Line: 3, Column: 28, Byte: 64}, + }, + }, + { + Type: lang.TokenAttrName, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 4, Column: 3, Byte: 69}, + End: hcl.Pos{Line: 4, Column: 6, Byte: 72}, + }, + }, + { + Type: lang.TokenTypeCapsule, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 4, Column: 9, Byte: 75}, + End: hcl.Pos{Line: 4, Column: 15, Byte: 81}, + }, + }, + { + Type: lang.TokenAttrName, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 5, Column: 5, Byte: 88}, + End: hcl.Pos{Line: 5, Column: 8, Byte: 91}, + }, + }, + { + Type: lang.TokenTypePrimitive, + Modifiers: lang.SemanticTokenModifiers{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 5, Column: 11, Byte: 94}, + End: hcl.Pos{Line: 5, Column: 17, Byte: 100}, + }, + }, + }, + }, + } + 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.SemanticTokensInFile(ctx, "test.tf") + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectedTokens, hoverData); diff != "" { + t.Fatalf("unexpected tokens: %s", diff) + } + }) + } +}