From 683155cd29b5db0fb14b9821f763666baacf2652 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 22 Mar 2021 16:02:44 +0000 Subject: [PATCH] Introduce ListExpr, SetExpr and TupleExpr --- decoder/expression_candidates.go | 135 +++++++- decoder/expression_candidates_test.go | 393 +++++++++++++++++++++- decoder/expression_constraints.go | 27 ++ decoder/hover.go | 190 ++++++++--- decoder/hover_expressions_test.go | 465 +++++++++++++++++++++++++- decoder/semantic_tokens.go | 33 +- decoder/semantic_tokens_expr_test.go | 205 ++++++++++++ schema/expressions.go | 58 +++- 8 files changed, 1423 insertions(+), 83 deletions(-) diff --git a/decoder/expression_candidates.go b/decoder/expression_candidates.go index f2824a94..363ace51 100644 --- a/decoder/expression_candidates.go +++ b/decoder/expression_candidates.go @@ -34,9 +34,8 @@ func constraintsAtPos(expr hcl.Expression, constraints ExprConstraints, pos hcl. } } case *hclsyntax.TupleConsExpr: - tc, ok := constraints.TupleConsExpr() rng := eType.Range() - insideBracketsRng := hcl.Range{ + tupleConsBody := hcl.Range{ Start: hcl.Pos{ Line: rng.Start.Line, Column: rng.Start.Column + 1, @@ -49,13 +48,40 @@ func constraintsAtPos(expr hcl.Expression, constraints ExprConstraints, pos hcl. }, Filename: rng.Filename, } - if ok && len(eType.Exprs) == 0 && insideBracketsRng.ContainsPos(pos) { + + tc, ok := constraints.TupleConsExpr() + if ok && len(eType.Exprs) == 0 && tupleConsBody.ContainsPos(pos) { return ExprConstraints(tc.AnyElem), hcl.Range{ Start: pos, End: pos, Filename: eType.Range().Filename, } } + + se, ok := constraints.SetExpr() + if ok && len(eType.Exprs) == 0 && tupleConsBody.ContainsPos(pos) { + return ExprConstraints(se.Elem), hcl.Range{ + Start: pos, + End: pos, + Filename: eType.Range().Filename, + } + } + le, ok := constraints.ListExpr() + if ok && len(eType.Exprs) == 0 && tupleConsBody.ContainsPos(pos) { + return ExprConstraints(le.Elem), hcl.Range{ + Start: pos, + End: pos, + Filename: eType.Range().Filename, + } + } + te, ok := constraints.TupleExpr() + if ok && len(eType.Exprs) == 0 && tupleConsBody.ContainsPos(pos) { + return ExprConstraints(te.Elems[0]), hcl.Range{ + Start: pos, + End: pos, + Filename: eType.Range().Filename, + } + } case *hclsyntax.ObjectConsExpr: oe, ok := constraints.ObjectExpr() if ok { @@ -138,6 +164,45 @@ func constraintToCandidates(constraint schema.ExprConstraint, editRng hcl.Range) }, TriggerSuggest: len(c.AnyElem) > 0, }) + case schema.ListExpr: + candidates = append(candidates, lang.Candidate{ + Label: fmt.Sprintf(`[%s]`, labelForConstraints(c.Elem)), + Detail: c.FriendlyName(), + Description: c.Description, + Kind: lang.ListCandidateKind, + TextEdit: lang.TextEdit{ + NewText: `[ ]`, + Snippet: `[ ${0} ]`, + Range: editRng, + }, + TriggerSuggest: len(c.Elem) > 0, + }) + case schema.SetExpr: + candidates = append(candidates, lang.Candidate{ + Label: fmt.Sprintf(`[%s]`, labelForConstraints(c.Elem)), + Detail: c.FriendlyName(), + Description: c.Description, + Kind: lang.SetCandidateKind, + TextEdit: lang.TextEdit{ + NewText: `[ ]`, + Snippet: `[ ${0} ]`, + Range: editRng, + }, + TriggerSuggest: len(c.Elem) > 0, + }) + case schema.TupleExpr: + candidates = append(candidates, lang.Candidate{ + Label: fmt.Sprintf(`[%s]`, labelForConstraints(c.Elems[0])), + Detail: c.FriendlyName(), + Description: c.Description, + Kind: lang.TupleCandidateKind, + TextEdit: lang.TextEdit{ + NewText: `[ ]`, + Snippet: `[ ${0} ]`, + Range: editRng, + }, + TriggerSuggest: len(c.Elems) > 0, + }) case schema.MapExpr: candidates = append(candidates, lang.Candidate{ Label: fmt.Sprintf(`{ key =%s}`, labelForConstraints(c.Elem)), @@ -171,10 +236,11 @@ func constraintToCandidates(constraint schema.ExprConstraint, editRng hcl.Range) for _, name := range attrNames { attr := c[name] candidates = append(candidates, lang.Candidate{ - Label: name, - Detail: attr.Expr.FriendlyName(), - Description: attr.Description, - Kind: lang.AttributeCandidateKind, + Label: name, + Detail: detailForAttribute(attr), + IsDeprecated: attr.IsDeprecated, + Description: attr.Description, + Kind: lang.AttributeCandidateKind, TextEdit: lang.TextEdit{ NewText: fmt.Sprintf("%s = %s", name, newTextForConstraints(attr.Expr, true)), Snippet: fmt.Sprintf("%s = %s", name, snippetForConstraints(1, attr.Expr, true)), @@ -201,6 +267,21 @@ func newTextForConstraints(cons schema.ExprConstraints, isNested bool) string { return "[ ]" } return fmt.Sprintf("[\n %s\n]", newTextForConstraints(c.AnyElem, true)) + case schema.ListExpr: + if isNested { + return "[ ]" + } + return fmt.Sprintf("[\n %s\n]", newTextForConstraints(c.Elem, true)) + case schema.SetExpr: + if isNested { + return "[ ]" + } + return fmt.Sprintf("[\n %s\n]", newTextForConstraints(c.Elem, true)) + case schema.TupleExpr: + if isNested { + return "[ ]" + } + return fmt.Sprintf("[\n %s\n]", newTextForConstraints(c.Elems[0], true)) case schema.MapExpr: return fmt.Sprintf("{\n %s\n}", newTextForConstraints(c.Elem, true)) case schema.ObjectExpr: @@ -224,6 +305,21 @@ func snippetForConstraints(placeholder uint, cons schema.ExprConstraints, isNest return fmt.Sprintf("[ ${%d} ]", placeholder+1) } return fmt.Sprintf("[\n %s\n]", snippetForConstraints(placeholder+1, c.AnyElem, true)) + case schema.ListExpr: + if isNested { + return fmt.Sprintf("[ ${%d} ]", placeholder+1) + } + return fmt.Sprintf("[\n %s\n]", snippetForConstraints(placeholder+1, c.Elem, true)) + case schema.SetExpr: + if isNested { + return fmt.Sprintf("[ ${%d} ]", placeholder+1) + } + return fmt.Sprintf("[\n %s\n]", snippetForConstraints(placeholder+1, c.Elem, true)) + case schema.TupleExpr: + if isNested { + return fmt.Sprintf("[ ${%d} ]", placeholder+1) + } + return fmt.Sprintf("[\n %s\n]", snippetForConstraints(placeholder+1, c.Elems[0], true)) case schema.MapExpr: return fmt.Sprintf("{\n %s\n}", snippetForConstraints(placeholder+1, c.Elem, true)) case schema.ObjectExpr: @@ -253,6 +349,12 @@ func labelForConstraints(cons schema.ExprConstraints) string { labels += c.FriendlyName() case schema.TupleConsExpr: labels += fmt.Sprintf("[%s]", labelForConstraints(c.AnyElem)) + case schema.ListExpr: + labels += fmt.Sprintf("[%s]", labelForConstraints(c.Elem)) + case schema.SetExpr: + labels += fmt.Sprintf("[%s]", labelForConstraints(c.Elem)) + case schema.TupleExpr: + labels += fmt.Sprintf("[%s]", labelForConstraints(c.Elems[0])) } labelsAdded++ } @@ -381,6 +483,25 @@ func snippetForExprContraints(placeholder uint, ec schema.ExprConstraints) strin return "[ ${0} ]" } return "[\n ${0}\n]" + case schema.ListExpr: + ec := ExprConstraints(et.Elem) + if ec.HasKeywordsOnly() { + return "[ ${0} ]" + } + return "[\n ${0}\n]" + case schema.SetExpr: + ec := ExprConstraints(et.Elem) + if ec.HasKeywordsOnly() { + return "[ ${0} ]" + } + return "[\n ${0}\n]" + case schema.TupleExpr: + // TODO: multiple constraints? + ec := ExprConstraints(et.Elems[0]) + if ec.HasKeywordsOnly() { + return "[ ${0} ]" + } + return "[\n ${0}\n]" case schema.MapExpr: return fmt.Sprintf("{\n ${%d:name} = %s\n }", placeholder, diff --git a/decoder/expression_candidates_test.go b/decoder/expression_candidates_test.go index 7ae63dd2..38182de3 100644 --- a/decoder/expression_candidates_test.go +++ b/decoder/expression_candidates_test.go @@ -131,10 +131,10 @@ func TestDecoder_CandidateAtPos_expressions(t *testing.T) { Expr: schema.ExprConstraints{ schema.ObjectExpr{ Attributes: schema.ObjectExprAttributes{ - "first": schema.ObjectAttribute{ + "first": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.String), }, - "second": schema.ObjectAttribute{ + "second": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.Number), }, }, @@ -178,10 +178,10 @@ func TestDecoder_CandidateAtPos_expressions(t *testing.T) { Expr: schema.ExprConstraints{ schema.ObjectExpr{ Attributes: schema.ObjectExprAttributes{ - "first": schema.ObjectAttribute{ + "first": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.String), }, - "second": schema.ObjectAttribute{ + "second": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.Number), }, }, @@ -248,10 +248,10 @@ func TestDecoder_CandidateAtPos_expressions(t *testing.T) { Expr: schema.ExprConstraints{ schema.ObjectExpr{ Attributes: schema.ObjectExprAttributes{ - "first": schema.ObjectAttribute{ + "first": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.String), }, - "second": schema.ObjectAttribute{ + "second": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.Number), }, }, @@ -709,6 +709,387 @@ func TestDecoder_CandidateAtPos_expressions(t *testing.T) { hcl.Pos{Line: 1, Column: 10, Byte: 9}, lang.ZeroCandidates(), }, + { + "attribute as list expression", + map[string]*schema.AttributeSchema{ + "attr": { + Expr: schema.ExprConstraints{ + schema.ListExpr{ + Elem: schema.LiteralTypeOnly(cty.String), + }, + }, + }, + }, + ` +`, + hcl.Pos{Line: 1, Column: 1, Byte: 0}, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "attr", + Detail: "list", + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + }, + NewText: "attr", + Snippet: "attr = [\n ${0}\n]", + }, + Kind: lang.AttributeCandidateKind, + }, + }), + }, + { + "list expression", + map[string]*schema.AttributeSchema{ + "attr": { + Expr: schema.ExprConstraints{ + schema.ListExpr{ + Elem: schema.LiteralTypeOnly(cty.String), + }, + }, + }, + }, + `attr = +`, + hcl.Pos{Line: 1, Column: 8, Byte: 7}, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "[ string ]", + Detail: "list", + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + }, + NewText: "[ ]", + Snippet: "[ ${0} ]", + }, + Kind: lang.ListCandidateKind, + TriggerSuggest: true, + }, + }), + }, + { + "list expression inside", + map[string]*schema.AttributeSchema{ + "attr": { + Expr: schema.ExprConstraints{ + schema.ListExpr{ + Elem: schema.LiteralTypeOnly(cty.Bool), + }, + }, + }, + }, + `attr = [ ] +`, + hcl.Pos{Line: 1, Column: 10, Byte: 9}, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "true", + Detail: "bool", + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + End: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + }, + NewText: "true", + Snippet: "${1:true}", + }, + Kind: lang.BoolCandidateKind, + }, + { + Label: "false", + Detail: "bool", + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + End: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + }, + NewText: "false", + Snippet: "${1:false}", + }, + Kind: lang.BoolCandidateKind, + }, + }), + }, + { + "attribute as set expression", + map[string]*schema.AttributeSchema{ + "attr": { + Expr: schema.ExprConstraints{ + schema.SetExpr{ + Elem: schema.LiteralTypeOnly(cty.String), + }, + }, + }, + }, + ` +`, + hcl.Pos{Line: 1, Column: 1, Byte: 0}, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "attr", + Detail: "set", + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + }, + NewText: "attr", + Snippet: "attr = [\n ${0}\n]", + }, + Kind: lang.AttributeCandidateKind, + }, + }), + }, + { + "set expression", + map[string]*schema.AttributeSchema{ + "attr": { + Expr: schema.ExprConstraints{ + schema.SetExpr{ + Elem: schema.LiteralTypeOnly(cty.String), + }, + }, + }, + }, + `attr = +`, + hcl.Pos{Line: 1, Column: 8, Byte: 7}, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "[ string ]", + Detail: "set", + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + }, + NewText: "[ ]", + Snippet: "[ ${0} ]", + }, + Kind: lang.SetCandidateKind, + TriggerSuggest: true, + }, + }), + }, + { + "set expression inside", + map[string]*schema.AttributeSchema{ + "attr": { + Expr: schema.ExprConstraints{ + schema.SetExpr{ + Elem: schema.LiteralTypeOnly(cty.Bool), + }, + }, + }, + }, + `attr = [ ] +`, + hcl.Pos{Line: 1, Column: 10, Byte: 9}, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "true", + Detail: "bool", + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + End: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + }, + NewText: "true", + Snippet: "${1:true}", + }, + Kind: lang.BoolCandidateKind, + }, + { + Label: "false", + Detail: "bool", + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + End: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + }, + NewText: "false", + Snippet: "${1:false}", + }, + Kind: lang.BoolCandidateKind, + }, + }), + }, + { + "attribute as tuple expression", + map[string]*schema.AttributeSchema{ + "attr": { + Expr: schema.ExprConstraints{ + schema.TupleExpr{ + Elems: []schema.ExprConstraints{ + schema.LiteralTypeOnly(cty.String), + schema.LiteralTypeOnly(cty.Number), + }, + }, + }, + }, + }, + ` +`, + hcl.Pos{Line: 1, Column: 1, Byte: 0}, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "attr", + Detail: "tuple", + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + }, + NewText: "attr", + Snippet: "attr = [\n ${0}\n]", + }, + Kind: lang.AttributeCandidateKind, + }, + }), + }, + { + "tuple expression", + map[string]*schema.AttributeSchema{ + "attr": { + Expr: schema.ExprConstraints{ + schema.TupleExpr{ + Elems: []schema.ExprConstraints{ + schema.LiteralTypeOnly(cty.String), + schema.LiteralTypeOnly(cty.Number), + }, + }, + }, + }, + }, + `attr = +`, + hcl.Pos{Line: 1, Column: 8, Byte: 7}, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "[ string ]", + Detail: "tuple", + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + }, + NewText: "[ ]", + Snippet: "[ ${0} ]", + }, + Kind: lang.TupleCandidateKind, + TriggerSuggest: true, + }, + }), + }, + { + "tuple expression inside", + map[string]*schema.AttributeSchema{ + "attr": { + Expr: schema.ExprConstraints{ + schema.TupleExpr{ + Elems: []schema.ExprConstraints{ + schema.LiteralTypeOnly(cty.Bool), + schema.LiteralTypeOnly(cty.Number), + }, + }, + }, + }, + }, + `attr = [ ] +`, + hcl.Pos{Line: 1, Column: 10, Byte: 9}, + lang.CompleteCandidates([]lang.Candidate{ + { + Label: "true", + Detail: "bool", + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + End: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + }, + NewText: "true", + Snippet: "${1:true}", + }, + Kind: lang.BoolCandidateKind, + }, + { + Label: "false", + Detail: "bool", + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + End: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + }, + NewText: "false", + Snippet: "${1:false}", + }, + Kind: lang.BoolCandidateKind, + }, + }), + }, { "keyword", map[string]*schema.AttributeSchema{ diff --git a/decoder/expression_constraints.go b/decoder/expression_constraints.go index 25f87819..63e7d857 100644 --- a/decoder/expression_constraints.go +++ b/decoder/expression_constraints.go @@ -56,6 +56,33 @@ func (ec ExprConstraints) TupleConsExpr() (schema.TupleConsExpr, bool) { return schema.TupleConsExpr{}, false } +func (ec ExprConstraints) SetExpr() (schema.SetExpr, bool) { + for _, c := range ec { + if se, ok := c.(schema.SetExpr); ok { + return se, ok + } + } + return schema.SetExpr{}, false +} + +func (ec ExprConstraints) ListExpr() (schema.ListExpr, bool) { + for _, c := range ec { + if le, ok := c.(schema.ListExpr); ok { + return le, ok + } + } + return schema.ListExpr{}, false +} + +func (ec ExprConstraints) TupleExpr() (schema.TupleExpr, bool) { + for _, c := range ec { + if te, ok := c.(schema.TupleExpr); ok { + return te, ok + } + } + return schema.TupleExpr{}, false +} + func (ec ExprConstraints) HasLiteralTypeOf(exprType cty.Type) bool { for _, c := range ec { if lt, ok := c.(schema.LiteralTypeExpr); ok && lt.Type.Equals(exprType) { diff --git a/decoder/hover.go b/decoder/hover.go index c9c685f0..8b3fe888 100644 --- a/decoder/hover.go +++ b/decoder/hover.go @@ -68,7 +68,7 @@ func (d *Decoder) hoverAtPos(body *hclsyntax.Body, bodySchema *schema.BodySchema if attr.Expr.Range().ContainsPos(pos) { exprCons := ExprConstraints(aSchema.Expr) - data, err := hoverDataForExpr(attr.Expr, exprCons, pos) + data, err := hoverDataForExpr(attr.Expr, exprCons, 0, pos) if err != nil { return nil, &PositionalError{ Filename: filename, @@ -210,11 +210,17 @@ func hoverContentForBlock(bType string, schema *schema.BlockSchema) lang.MarkupC } } -func hoverDataForExpr(expr hcl.Expression, constraints ExprConstraints, pos hcl.Pos) (*lang.HoverData, error) { +func hoverDataForExpr(expr hcl.Expression, constraints ExprConstraints, nestingLvl int, pos hcl.Pos) (*lang.HoverData, error) { switch e := expr.(type) { case *hclsyntax.ScopeTraversalExpr: kw, ok := constraints.KeywordExpr() if ok && len(e.Traversal) == 1 { + if nestingLvl > 0 { + return &lang.HoverData{ + Content: lang.Markdown(kw.FriendlyName()), + Range: expr.Range(), + }, nil + } return &lang.HoverData{ Content: lang.Markdown(fmt.Sprintf("`%s` _%s_", kw.Keyword, kw.FriendlyName())), Range: expr.Range(), @@ -222,7 +228,7 @@ func hoverDataForExpr(expr hcl.Expression, constraints ExprConstraints, pos hcl. } case *hclsyntax.TemplateExpr: if e.IsStringLiteral() { - data, err := hoverDataForExpr(e.Parts[0], constraints, pos) + data, err := hoverDataForExpr(e.Parts[0], constraints, nestingLvl, pos) if err != nil { return nil, err } @@ -256,7 +262,7 @@ func hoverDataForExpr(expr hcl.Expression, constraints ExprConstraints, pos hcl. } } case *hclsyntax.TemplateWrapExpr: - data, err := hoverDataForExpr(e.Wrapped, constraints, pos) + data, err := hoverDataForExpr(e.Wrapped, constraints, nestingLvl, pos) if err != nil { return nil, err } @@ -277,10 +283,61 @@ func hoverDataForExpr(expr hcl.Expression, constraints ExprConstraints, pos hcl. Range: expr.Range(), }, nil } - + se, ok := constraints.SetExpr() + if ok { + for _, elemExpr := range e.Exprs { + if elemExpr.Range().ContainsPos(pos) { + return hoverDataForExpr(elemExpr, ExprConstraints(se.Elem), nestingLvl, pos) + } + } + content := fmt.Sprintf("_%s_", se.FriendlyName()) + if se.Description.Value != "" { + content += "\n\n" + se.Description.Value + } + return &lang.HoverData{ + Content: lang.Markdown(content), + Range: expr.Range(), + }, nil + } + le, ok := constraints.ListExpr() + if ok { + for _, elemExpr := range e.Exprs { + if elemExpr.Range().ContainsPos(pos) { + return hoverDataForExpr(elemExpr, ExprConstraints(le.Elem), nestingLvl, pos) + } + } + content := fmt.Sprintf("_%s_", le.FriendlyName()) + if le.Description.Value != "" { + content += "\n\n" + le.Description.Value + } + return &lang.HoverData{ + Content: lang.Markdown(content), + Range: expr.Range(), + }, nil + } + te, ok := constraints.TupleExpr() + if ok { + for i, elemExpr := range e.Exprs { + if elemExpr.Range().ContainsPos(pos) { + if i >= len(te.Elems) { + return nil, &ConstraintMismatch{elemExpr} + } + ec := ExprConstraints(te.Elems[i]) + return hoverDataForExpr(elemExpr, ec, nestingLvl, pos) + } + } + content := fmt.Sprintf("_%s_", te.FriendlyName()) + if te.Description.Value != "" { + content += "\n\n" + te.Description.Value + } + return &lang.HoverData{ + Content: lang.Markdown(content), + Range: expr.Range(), + }, nil + } lt, ok := constraints.LiteralTypeOfTupleExpr() if ok { - content, err := hoverContentForType(lt.Type) + content, err := hoverContentForType(lt.Type, nestingLvl) if err != nil { return nil, err } @@ -289,9 +346,9 @@ func hoverDataForExpr(expr hcl.Expression, constraints ExprConstraints, pos hcl. Range: expr.Range(), }, nil } - litVal, ok := constraints.LiteralValueOfTupleExpr(e) + lv, ok := constraints.LiteralValueOfTupleExpr(e) if ok { - content, err := hoverContentForValue(litVal.Val, 0) + content, err := hoverContentForValue(lv.Val, nestingLvl) if err != nil { return nil, err } @@ -303,13 +360,16 @@ func hoverDataForExpr(expr hcl.Expression, constraints ExprConstraints, pos hcl. case *hclsyntax.ObjectConsExpr: objExpr, ok := constraints.ObjectExpr() if ok { - return hoverDataForObjectExpr(e, objExpr, pos) + return hoverDataForObjectExpr(e, objExpr, nestingLvl, pos) } mapExpr, ok := constraints.MapExpr() if ok { - content := fmt.Sprintf("_%s_", mapExpr.FriendlyName()) - if mapExpr.Description.Value != "" { - content += "\n\n" + mapExpr.Description.Value + content := mapExpr.FriendlyName() + if nestingLvl == 0 { + content = fmt.Sprintf("_%s_", mapExpr.FriendlyName()) + if mapExpr.Description.Value != "" { + content += "\n\n" + mapExpr.Description.Value + } } return &lang.HoverData{ Content: lang.Markdown(content), @@ -318,7 +378,7 @@ func hoverDataForExpr(expr hcl.Expression, constraints ExprConstraints, pos hcl. } lt, ok := constraints.LiteralTypeOfObjectConsExpr() if ok { - content, err := hoverContentForType(lt.Type) + content, err := hoverContentForType(lt.Type, nestingLvl) if err != nil { return nil, err } @@ -329,7 +389,7 @@ func hoverDataForExpr(expr hcl.Expression, constraints ExprConstraints, pos hcl. } litVal, ok := constraints.LiteralValueOfObjectConsExpr(e) if ok { - content, err := hoverContentForValue(litVal.Val, 0) + content, err := hoverContentForValue(litVal.Val, nestingLvl) if err != nil { return nil, err } @@ -340,9 +400,19 @@ func hoverDataForExpr(expr hcl.Expression, constraints ExprConstraints, pos hcl. } case *hclsyntax.LiteralValueExpr: if constraints.HasLiteralTypeOf(e.Val.Type()) { - content, err := hoverContentForValue(e.Val, 0) - if err != nil { - return nil, err + content := "" + if nestingLvl == 0 { + valContent, err := hoverContentForValue(e.Val, nestingLvl) + if err != nil { + return nil, err + } + content = valContent + } else { + typeContent, err := hoverContentForType(e.Val.Type(), nestingLvl) + if err != nil { + return nil, err + } + content = typeContent } return &lang.HoverData{ Content: lang.Markdown(content), @@ -351,7 +421,7 @@ func hoverDataForExpr(expr hcl.Expression, constraints ExprConstraints, pos hcl. } lv, ok := constraints.LiteralValueOf(e.Val) if ok { - content, err := hoverContentForValue(lv.Val, 0) + content, err := hoverContentForValue(lv.Val, nestingLvl) if err != nil { return nil, err } @@ -366,7 +436,8 @@ func hoverDataForExpr(expr hcl.Expression, constraints ExprConstraints, pos hcl. return nil, fmt.Errorf("unsupported expression (%T)", expr) } -func hoverDataForObjectExpr(objExpr *hclsyntax.ObjectConsExpr, oe schema.ObjectExpr, pos hcl.Pos) (*lang.HoverData, error) { +func hoverDataForObjectExpr(objExpr *hclsyntax.ObjectConsExpr, oe schema.ObjectExpr, nestingLvl int, pos hcl.Pos) (*lang.HoverData, error) { + declaredAttributes := make(map[string]hclsyntax.Expression, 0) for _, item := range objExpr.Items { key, _ := item.KeyExpr.Value(nil) if !key.IsWhollyKnown() || key.Type() != cty.String { @@ -379,27 +450,28 @@ func hoverDataForObjectExpr(objExpr *hclsyntax.ObjectConsExpr, oe schema.ObjectE } if item.ValueExpr.Range().ContainsPos(pos) { - return hoverDataForExpr(item.ValueExpr, ExprConstraints(attr.Expr), pos) + return hoverDataForExpr(item.ValueExpr, ExprConstraints(attr.Expr), nestingLvl+1, pos) } itemRng := hcl.RangeBetween(item.KeyExpr.Range(), item.ValueExpr.Range()) if itemRng.ContainsPos(pos) { - content := fmt.Sprintf(`**%s** _%s_`, key.AsString(), attr.FriendlyName()) - if attr.Description.Value != "" { - content += fmt.Sprintf("\n\n%s", attr.Description.Value) - } - + content := hoverContentForAttribute(key.AsString(), attr) return &lang.HoverData{ - Content: lang.Markdown(content), + Content: content, Range: itemRng, }, nil } + + declaredAttributes[key.AsString()] = item.ValueExpr } if len(oe.Attributes) == 0 { - content := fmt.Sprintf("_%s_", oe.FriendlyName()) - if oe.Description.Value != "" { - content += "\n\n" + oe.Description.Value + content := oe.FriendlyName() + if nestingLvl == 0 { + content := fmt.Sprintf("_%s_", oe.FriendlyName()) + if oe.Description.Value != "" { + content += "\n\n" + oe.Description.Value + } } return &lang.HoverData{ Content: lang.Markdown(content), @@ -408,13 +480,32 @@ func hoverDataForObjectExpr(objExpr *hclsyntax.ObjectConsExpr, oe schema.ObjectE } attrNames := sortedObjectExprAttrNames(oe.Attributes) - content := "```\n{\n" + content := "" + if nestingLvl == 0 { + content += "```\n" + } + content += "{\n" + insideNesting := strings.Repeat(" ", nestingLvl+1) for _, name := range attrNames { - content += fmt.Sprintf(" %s = %s\n", name, oe.Attributes[name].FriendlyName()) + ec := oe.Attributes[name].Expr + attrData := ec.FriendlyName() + + if attrExpr, ok := declaredAttributes[name]; ok { + data, err := hoverDataForExpr(attrExpr, ExprConstraints(ec), nestingLvl+1, pos) + if err == nil && data.Content.Value != "" { + attrData = data.Content.Value + } + } + + content += fmt.Sprintf("%s%s = %s\n", insideNesting, name, attrData) } - content += fmt.Sprintf("}\n```\n_%s_", oe.FriendlyName()) - if oe.Description.Value != "" { - content += "\n\n" + oe.Description.Value + endBraceNesting := strings.Repeat(" ", nestingLvl) + content += fmt.Sprintf("%s}", endBraceNesting) + if nestingLvl == 0 { + content += fmt.Sprintf("\n```\n_%s_", oe.FriendlyName()) + if oe.Description.Value != "" { + content += "\n\n" + oe.Description.Value + } } return &lang.HoverData{ Content: lang.Markdown(content), @@ -583,8 +674,11 @@ func hoverContentForValue(val cty.Value, nestingLvl int) (string, error) { return "", fmt.Errorf("unsupported type: %q", attrType.FriendlyName()) } -func hoverContentForType(attrType cty.Type) (string, error) { +func hoverContentForType(attrType cty.Type, nestingLvl int) (string, error) { if attrType.IsPrimitiveType() { + if nestingLvl > 0 { + return attrType.FriendlyName(), nil + } return fmt.Sprintf(`_%s_`, attrType.FriendlyName()), nil } @@ -593,18 +687,36 @@ func hoverContentForType(attrType cty.Type) (string, error) { if len(attrNames) == 0 { return attrType.FriendlyName(), nil } - value := "```\n{\n" + value := "" + if nestingLvl == 0 { + value += "```\n" + } + value += "{\n" + insideNesting := strings.Repeat(" ", nestingLvl+1) for _, name := range attrNames { valType := attrType.AttributeType(name) - value += fmt.Sprintf(" %s = %s\n", name, - valType.FriendlyName()) + valData := valType.FriendlyNameForConstraint() + + data, err := hoverContentForType(valType, nestingLvl+1) + if err == nil { + valData = data + } + + value += fmt.Sprintf("%s%s = %s\n", insideNesting, name, valData) + } + endBraceNesting := strings.Repeat(" ", nestingLvl) + value += fmt.Sprintf("%s}", endBraceNesting) + if nestingLvl == 0 { + value += "\n```\n_object_" } - value += "}\n```\n_object_" return value, nil } if attrType.IsMapType() || attrType.IsListType() || attrType.IsSetType() || attrType.IsTupleType() { + if nestingLvl > 0 { + return attrType.FriendlyName(), nil + } value := fmt.Sprintf(`_%s_`, attrType.FriendlyName()) return value, nil } diff --git a/decoder/hover_expressions_test.go b/decoder/hover_expressions_test.go index 50ea8382..04c54563 100644 --- a/decoder/hover_expressions_test.go +++ b/decoder/hover_expressions_test.go @@ -216,7 +216,10 @@ EOT "bool": cty.Bool, "notbool": cty.String, "nested_map": cty.Map(cty.String), - "nested_obj": cty.Object(map[string]cty.Type{}), + "nested_obj": cty.Object(map[string]cty.Type{ + "one": cty.String, + "two": cty.Number, + }), }))}, }, `litobj = { @@ -231,7 +234,10 @@ EOT { bool = bool nested_map = map of string - nested_obj = object + nested_obj = { + one = string + two = number + } notbool = string source = string } @@ -253,25 +259,267 @@ _object_`), }, nil, }, + { + "list of object expressions", + map[string]*schema.AttributeSchema{ + "objects": {Expr: schema.ExprConstraints{ + schema.ListExpr{ + Elem: schema.ExprConstraints{ + schema.ObjectExpr{ + Attributes: schema.ObjectExprAttributes{ + "source": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.String), + }, + "bool": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.Bool), + }, + "notbool": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.String), + }, + "nested_map": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.Map(cty.String)), + }, + "nested_obj": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.Object(map[string]cty.Type{})), + }, + }, + }, + }, + }, + }}, + }, + `objects = [ + { + source = "blah" + different = 42 + bool = true + notbool = "test" + } +]`, + hcl.Pos{Line: 1, Column: 3, Byte: 2}, + &lang.HoverData{ + Content: lang.Markdown(`**objects** _list_`), + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 1, + Byte: 0, + }, + End: hcl.Pos{ + Line: 8, + Column: 2, + Byte: 117, + }, + }, + }, + nil, + }, + { + "list of object expressions element", + map[string]*schema.AttributeSchema{ + "objects": {Expr: schema.ExprConstraints{ + schema.ListExpr{ + Elem: schema.ExprConstraints{ + schema.ObjectExpr{ + Attributes: schema.ObjectExprAttributes{ + "source": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.String), + }, + "bool": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.Bool), + }, + "notbool": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.String), + }, + "nested_map": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.Map(cty.String)), + }, + "nested_obj": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.Object(map[string]cty.Type{})), + }, + }, + }, + }, + }, + }}, + }, + `objects = [ + { + source = "blah" + different = 42 + bool = true + notbool = "test" + } +]`, + hcl.Pos{Line: 3, Column: 12, Byte: 29}, + &lang.HoverData{ + Content: lang.Markdown(`**source** _string_`), + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 3, + Column: 9, + Byte: 26, + }, + End: hcl.Pos{ + Line: 3, + Column: 24, + Byte: 41, + }, + }, + }, + nil, + }, + { + "nested object expression", + map[string]*schema.AttributeSchema{ + "object": {Expr: schema.ExprConstraints{ + schema.ObjectExpr{ + Attributes: schema.ObjectExprAttributes{ + "nested": &schema.AttributeSchema{ + Expr: schema.ExprConstraints{ + schema.ObjectExpr{ + Attributes: schema.ObjectExprAttributes{ + "source": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.String), + }, + "bool": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.Bool), + }, + "notbool": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.String), + }, + "nested_map": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.Map(cty.String)), + }, + "nested_obj": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.Object(map[string]cty.Type{})), + }, + }, + }, + }, + }, + }, + }, + }}, + }, + `object = { + nested = { + source = "blah" + different = 42 + bool = true + notbool = "test" + } +}`, + hcl.Pos{Line: 1, Column: 10, Byte: 9}, + &lang.HoverData{ + Content: lang.Markdown("```" + ` +{ + nested = { + bool = bool + nested_map = map of string + nested_obj = object + notbool = string + source = string + } +} +` + "```\n_object_"), + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + End: hcl.Pos{ + Line: 8, + Column: 2, + Byte: 125, + }, + }, + }, + nil, + }, + { + "nested object expression inside", + map[string]*schema.AttributeSchema{ + "object": {Expr: schema.ExprConstraints{ + schema.ObjectExpr{ + Attributes: schema.ObjectExprAttributes{ + "nested": &schema.AttributeSchema{ + Expr: schema.ExprConstraints{ + schema.ObjectExpr{ + Attributes: schema.ObjectExprAttributes{ + "source": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.String), + }, + "bool": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.Bool), + }, + "notbool": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.String), + }, + "nested_map": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.Map(cty.String)), + }, + "nested_obj": &schema.AttributeSchema{ + Expr: schema.LiteralTypeOnly(cty.Object(map[string]cty.Type{})), + }, + }, + }, + }, + }, + }, + }, + }}, + }, + `object = { + nested = { + source = "blah" + different = 42 + bool = true + notbool = "test" + } +}`, + hcl.Pos{Line: 3, Column: 12, Byte: 37}, + &lang.HoverData{ + Content: lang.Markdown(`**source** _string_`), + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 3, + Column: 9, + Byte: 34, + }, + End: hcl.Pos{ + Line: 3, + Column: 24, + Byte: 49, + }, + }, + }, + nil, + }, { "object as expression", map[string]*schema.AttributeSchema{ "obj": {Expr: schema.ExprConstraints{ schema.ObjectExpr{ Attributes: schema.ObjectExprAttributes{ - "source": schema.ObjectAttribute{ + "source": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.String), }, - "bool": schema.ObjectAttribute{ + "bool": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.Bool), }, - "notbool": schema.ObjectAttribute{ + "notbool": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.String), }, - "nested_map": schema.ObjectAttribute{ + "nested_map": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.Map(cty.String)), }, - "nested_obj": schema.ObjectAttribute{ + "nested_obj": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.Object(map[string]cty.Type{})), }, }, @@ -309,19 +557,19 @@ _object_`), "obj": {Expr: schema.ExprConstraints{ schema.ObjectExpr{ Attributes: schema.ObjectExprAttributes{ - "source": schema.ObjectAttribute{ + "source": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.String), }, - "bool": schema.ObjectAttribute{ + "bool": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.Bool), }, - "notbool": schema.ObjectAttribute{ + "notbool": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.String), }, - "nested_map": schema.ObjectAttribute{ + "nested_map": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.Map(cty.String)), }, - "nested_obj": schema.ObjectAttribute{ + "nested_obj": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.Object(map[string]cty.Type{})), }, }, @@ -368,19 +616,19 @@ _object_`), "obj": {Expr: schema.ExprConstraints{ schema.ObjectExpr{ Attributes: schema.ObjectExprAttributes{ - "source": schema.ObjectAttribute{ + "source": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.String), }, - "bool": schema.ObjectAttribute{ + "bool": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.Bool), }, - "notbool": schema.ObjectAttribute{ + "notbool": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.String), }, - "nested_map": schema.ObjectAttribute{ + "nested_map": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.Map(cty.String)), }, - "nested_obj": schema.ObjectAttribute{ + "nested_obj": &schema.AttributeSchema{ Expr: schema.LiteralTypeOnly(cty.Object(map[string]cty.Type{})), }, }, @@ -594,7 +842,7 @@ _object_`), "tuplecons": {Expr: schema.ExprConstraints{ schema.TupleConsExpr{ Name: "special tuple", - AnyElem: schema.LiteralTypeOnly(cty.List(cty.String)), + AnyElem: schema.LiteralTypeOnly(cty.String), }, }}, }, @@ -618,6 +866,187 @@ _object_`), }, nil, }, + { + "list expression", + map[string]*schema.AttributeSchema{ + "list": {Expr: schema.ExprConstraints{ + schema.ListExpr{ + Description: lang.Markdown("Special list"), + Elem: schema.LiteralTypeOnly(cty.String), + }, + }}, + }, + `list = [ "one", "two" ]`, + hcl.Pos{Line: 1, Column: 8, Byte: 7}, + &lang.HoverData{ + Content: lang.Markdown("_list_\n\nSpecial list"), + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 8, + Byte: 7, + }, + End: hcl.Pos{ + Line: 1, + Column: 24, + Byte: 23, + }, + }, + }, + nil, + }, + { + "list expression element", + map[string]*schema.AttributeSchema{ + "list": {Expr: schema.ExprConstraints{ + schema.ListExpr{ + Elem: schema.LiteralTypeOnly(cty.String), + }, + }}, + }, + `list = [ "one", "two" ]`, + hcl.Pos{Line: 1, Column: 12, Byte: 11}, + &lang.HoverData{ + Content: lang.Markdown("`\"one\"` _string_"), + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + End: hcl.Pos{ + Line: 1, + Column: 15, + Byte: 14, + }, + }, + }, + nil, + }, + { + "set expression", + map[string]*schema.AttributeSchema{ + "set": {Expr: schema.ExprConstraints{ + schema.SetExpr{ + Description: lang.Markdown("Special set"), + Elem: schema.LiteralTypeOnly(cty.String), + }, + }}, + }, + `set = [ "one", "two" ]`, + hcl.Pos{Line: 1, Column: 7, Byte: 6}, + &lang.HoverData{ + Content: lang.Markdown("_set_\n\nSpecial set"), + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 7, + Byte: 6, + }, + End: hcl.Pos{ + Line: 1, + Column: 23, + Byte: 22, + }, + }, + }, + nil, + }, + { + "set expression element", + map[string]*schema.AttributeSchema{ + "set": {Expr: schema.ExprConstraints{ + schema.SetExpr{ + Elem: schema.LiteralTypeOnly(cty.String), + }, + }}, + }, + `set = [ "one", "two" ]`, + hcl.Pos{Line: 1, Column: 12, Byte: 11}, + &lang.HoverData{ + Content: lang.Markdown("`\"one\"` _string_"), + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 9, + Byte: 8, + }, + End: hcl.Pos{ + Line: 1, + Column: 14, + Byte: 13, + }, + }, + }, + nil, + }, + { + "tuple expression", + map[string]*schema.AttributeSchema{ + "tup": {Expr: schema.ExprConstraints{ + schema.TupleExpr{ + Description: lang.Markdown("Special tuple"), + Elems: []schema.ExprConstraints{ + schema.LiteralTypeOnly(cty.String), + }, + }, + }}, + }, + `tup = [ "one", "two" ]`, + hcl.Pos{Line: 1, Column: 7, Byte: 6}, + &lang.HoverData{ + Content: lang.Markdown("_tuple_\n\nSpecial tuple"), + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 7, + Byte: 6, + }, + End: hcl.Pos{ + Line: 1, + Column: 23, + Byte: 22, + }, + }, + }, + nil, + }, + { + "tuple expression element", + map[string]*schema.AttributeSchema{ + "tup": {Expr: schema.ExprConstraints{ + schema.TupleExpr{ + Elems: []schema.ExprConstraints{ + schema.LiteralTypeOnly(cty.String), + }, + }, + }}, + }, + `tup = [ "one", "two" ]`, + hcl.Pos{Line: 1, Column: 12, Byte: 11}, + &lang.HoverData{ + Content: lang.Markdown("`\"one\"` _string_"), + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 9, + Byte: 8, + }, + End: hcl.Pos{ + Line: 1, + Column: 14, + Byte: 13, + }, + }, + }, + nil, + }, { "object as value", map[string]*schema.AttributeSchema{ diff --git a/decoder/semantic_tokens.go b/decoder/semantic_tokens.go index d8e78b24..29e67031 100644 --- a/decoder/semantic_tokens.go +++ b/decoder/semantic_tokens.go @@ -159,8 +159,35 @@ func tokensForExpression(expr hclsyntax.Expression, constraints ExprConstraints) case *hclsyntax.TupleConsExpr: tc, ok := constraints.TupleConsExpr() if ok { + ec := ExprConstraints(tc.AnyElem) for _, expr := range eType.Exprs { - ec := ExprConstraints(tc.AnyElem) + tokens = append(tokens, tokensForExpression(expr, ec)...) + } + return tokens + } + se, ok := constraints.SetExpr() + if ok { + ec := ExprConstraints(se.Elem) + for _, expr := range eType.Exprs { + tokens = append(tokens, tokensForExpression(expr, ec)...) + } + return tokens + } + le, ok := constraints.ListExpr() + if ok { + ec := ExprConstraints(le.Elem) + for _, expr := range eType.Exprs { + tokens = append(tokens, tokensForExpression(expr, ec)...) + } + return tokens + } + te, ok := constraints.TupleExpr() + if ok { + for i, expr := range eType.Exprs { + if i >= len(te.Elems) { + break + } + ec := ExprConstraints(te.Elems[i]) tokens = append(tokens, tokensForExpression(expr, ec)...) } return tokens @@ -169,9 +196,9 @@ func tokensForExpression(expr hclsyntax.Expression, constraints ExprConstraints) if ok { return tokensForTupleConsExpr(eType, lt.Type) } - litVal, ok := constraints.LiteralValueOfTupleExpr(eType) + lv, ok := constraints.LiteralValueOfTupleExpr(eType) if ok { - return tokensForTupleConsExpr(eType, litVal.Val.Type()) + return tokensForTupleConsExpr(eType, lv.Val.Type()) } case *hclsyntax.ObjectConsExpr: oe, ok := constraints.ObjectExpr() diff --git a/decoder/semantic_tokens_expr_test.go b/decoder/semantic_tokens_expr_test.go index dea13b6f..95004f60 100644 --- a/decoder/semantic_tokens_expr_test.go +++ b/decoder/semantic_tokens_expr_test.go @@ -825,6 +825,211 @@ EOT }, }, }, + { + "list expression", + map[string]*schema.AttributeSchema{ + "attr": { + Expr: schema.ExprConstraints{ + schema.ListExpr{ + Elem: schema.LiteralTypeOnly(cty.String), + }, + }, + }, + }, + `attr = [ "one", 42, "two" ] +`, + []lang.SemanticToken{ + { // attr + Type: lang.TokenAttrName, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 1, + Byte: 0, + }, + End: hcl.Pos{ + Line: 1, + Column: 5, + Byte: 4, + }, + }, + }, + { // "one" + Type: lang.TokenString, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + End: hcl.Pos{ + Line: 1, + Column: 15, + Byte: 14, + }, + }, + }, + { // "two" + Type: lang.TokenString, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 21, + Byte: 20, + }, + End: hcl.Pos{ + Line: 1, + Column: 26, + Byte: 25, + }, + }, + }, + }, + }, + { + "set expression", + map[string]*schema.AttributeSchema{ + "attr": { + Expr: schema.ExprConstraints{ + schema.SetExpr{ + Elem: schema.LiteralTypeOnly(cty.String), + }, + }, + }, + }, + `attr = [ "one", 42, "two" ] +`, + []lang.SemanticToken{ + { // attr + Type: lang.TokenAttrName, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 1, + Byte: 0, + }, + End: hcl.Pos{ + Line: 1, + Column: 5, + Byte: 4, + }, + }, + }, + { // "one" + Type: lang.TokenString, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + End: hcl.Pos{ + Line: 1, + Column: 15, + Byte: 14, + }, + }, + }, + { // "two" + Type: lang.TokenString, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 21, + Byte: 20, + }, + End: hcl.Pos{ + Line: 1, + Column: 26, + Byte: 25, + }, + }, + }, + }, + }, + { + "tuple expression", + map[string]*schema.AttributeSchema{ + "attr": { + Expr: schema.ExprConstraints{ + schema.TupleExpr{ + Elems: []schema.ExprConstraints{ + schema.LiteralTypeOnly(cty.String), + schema.LiteralTypeOnly(cty.Number), + schema.LiteralTypeOnly(cty.Bool), + }, + }, + }, + }, + }, + `attr = [ "one", 42, "two" ] +`, + []lang.SemanticToken{ + { // attr + Type: lang.TokenAttrName, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 1, + Byte: 0, + }, + End: hcl.Pos{ + Line: 1, + Column: 5, + Byte: 4, + }, + }, + }, + { // "one" + Type: lang.TokenString, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 10, + Byte: 9, + }, + End: hcl.Pos{ + Line: 1, + Column: 15, + Byte: 14, + }, + }, + }, + { // 42 + Type: lang.TokenNumber, + Modifiers: []lang.SemanticTokenModifier{}, + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{ + Line: 1, + Column: 17, + Byte: 16, + }, + End: hcl.Pos{ + Line: 1, + Column: 19, + Byte: 18, + }, + }, + }, + }, + }, { "tuple as list", map[string]*schema.AttributeSchema{ diff --git a/schema/expressions.go b/schema/expressions.go index 5ec6733b..906643a8 100644 --- a/schema/expressions.go +++ b/schema/expressions.go @@ -64,6 +64,8 @@ func (lv LiteralValue) FriendlyName() string { return lv.Val.Type().FriendlyNameForConstraint() } +// TODO: Consider removing TupleConsExpr +// in favour of ListExpr, SetExpr and TupleExpr type TupleConsExpr struct { AnyElem ExprConstraints Name string @@ -81,10 +83,55 @@ func (tc TupleConsExpr) FriendlyName() string { return tc.Name } +type ListExpr struct { + Elem ExprConstraints + Description lang.MarkupContent + MinItems uint64 + MaxItems uint64 +} + +func (ListExpr) isExprConstraintImpl() exprConstrSigil { + return exprConstrSigil{} +} + +func (ListExpr) FriendlyName() string { + return "list" +} + +type SetExpr struct { + Elem ExprConstraints + Description lang.MarkupContent + MinItems uint64 + MaxItems uint64 +} + +func (SetExpr) isExprConstraintImpl() exprConstrSigil { + return exprConstrSigil{} +} + +func (SetExpr) FriendlyName() string { + return "set" +} + +type TupleExpr struct { + Elems []ExprConstraints + Description lang.MarkupContent +} + +func (TupleExpr) isExprConstraintImpl() exprConstrSigil { + return exprConstrSigil{} +} + +func (le TupleExpr) FriendlyName() string { + return "tuple" +} + type MapExpr struct { Elem ExprConstraints Name string Description lang.MarkupContent + MinItems uint64 + MaxItems uint64 } func (MapExpr) isExprConstraintImpl() exprConstrSigil { @@ -115,7 +162,7 @@ func (oe ObjectExpr) FriendlyName() string { return oe.Name } -type ObjectExprAttributes map[string]ObjectAttribute +type ObjectExprAttributes map[string]*AttributeSchema func (ObjectExprAttributes) isExprConstraintImpl() exprConstrSigil { return exprConstrSigil{} @@ -125,15 +172,6 @@ func (oe ObjectExprAttributes) FriendlyName() string { return "attributes" } -type ObjectAttribute struct { - Expr ExprConstraints - Description lang.MarkupContent -} - -func (oa ObjectAttribute) FriendlyName() string { - return oa.Expr.FriendlyName() -} - type KeywordExpr struct { Keyword string Name string