Skip to content

Commit

Permalink
decoder: Implement completion for LiteralType
Browse files Browse the repository at this point in the history
  • Loading branch information
radeksimko committed Mar 15, 2023
1 parent 6de5fd7 commit cfdd98f
Show file tree
Hide file tree
Showing 4 changed files with 1,309 additions and 6 deletions.
5 changes: 1 addition & 4 deletions decoder/expr_literal_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,8 @@ import (
type LiteralType struct {
expr hcl.Expression
cons schema.LiteralType
}

func (lt LiteralType) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candidate {
// TODO
return nil
pathCtx *PathContext
}

func (lt LiteralType) HoverAtPos(ctx context.Context, pos hcl.Pos) *lang.HoverData {
Expand Down
204 changes: 204 additions & 0 deletions decoder/expr_literal_type_completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package decoder

import (
"context"
"strings"

"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 (lt LiteralType) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candidate {
typ := lt.cons.Type

if isEmptyExpression(lt.expr) {
editRange := hcl.Range{
Filename: lt.expr.Range().Filename,
Start: pos,
End: pos,
}

if typ.IsPrimitiveType() {
if typ == cty.Bool {
return boolLiteralCandidates("", editRange)
}
return []lang.Candidate{}
}

if typ == cty.DynamicPseudoType {
return []lang.Candidate{}
}

return []lang.Candidate{
{
Label: labelForLiteralType(typ),
Detail: typ.FriendlyName(),
Kind: candidateKindForType(typ),
TextEdit: lang.TextEdit{
Range: editRange,
NewText: newTextForLiteralType(typ),
Snippet: snippetForLiteralType(1, typ),
},
},
}
}

if typ == cty.Bool {
return lt.completeBoolAtPos(ctx, typ, pos)
}

if typ.IsListType() {
expr, ok := lt.expr.(*hclsyntax.TupleConsExpr)
if !ok {
return []lang.Candidate{}
}

cons := schema.List{
Elem: schema.LiteralType{
Type: typ.ElementType(),
},
}

return newExpression(lt.pathCtx, expr, cons).CompletionAtPos(ctx, pos)
}

if typ.IsSetType() {
expr, ok := lt.expr.(*hclsyntax.TupleConsExpr)
if !ok {
return []lang.Candidate{}
}

cons := schema.Set{
Elem: schema.LiteralType{
Type: typ.ElementType(),
},
}

return newExpression(lt.pathCtx, expr, cons).CompletionAtPos(ctx, pos)
}

if typ.IsTupleType() {
expr, ok := lt.expr.(*hclsyntax.TupleConsExpr)
if !ok {
return []lang.Candidate{}
}

elemTypes := typ.TupleElementTypes()
cons := schema.Tuple{
Elems: make([]schema.Constraint, len(elemTypes)),
}
for i, elemType := range elemTypes {
cons.Elems[i] = schema.LiteralType{
Type: elemType,
}
}

return newExpression(lt.pathCtx, expr, cons).CompletionAtPos(ctx, pos)
}

if typ.IsMapType() {
expr, ok := lt.expr.(*hclsyntax.ObjectConsExpr)
if !ok {
return []lang.Candidate{}
}

cons := schema.Map{
Elem: schema.LiteralType{
Type: typ.ElementType(),
},
}
return newExpression(lt.pathCtx, expr, cons).CompletionAtPos(ctx, pos)
}

if typ.IsObjectType() {
expr, ok := lt.expr.(*hclsyntax.ObjectConsExpr)
if !ok {
return []lang.Candidate{}
}

cons := schema.Object{
Attributes: ctyObjectToObjectAttributes(typ),
}
return newExpression(lt.pathCtx, expr, cons).CompletionAtPos(ctx, pos)
}

return nil
}

func (lt LiteralType) completeBoolAtPos(ctx context.Context, typ cty.Type, pos hcl.Pos) []lang.Candidate {
switch eType := lt.expr.(type) {

case *hclsyntax.ScopeTraversalExpr:
prefixLen := pos.Byte - eType.Range().Start.Byte
prefix := eType.Traversal.RootName()[0:prefixLen]
return boolLiteralCandidates(prefix, eType.Range())

case *hclsyntax.LiteralValueExpr:
if eType.Val.Type() == cty.Bool {
value := "false"
if eType.Val.True() {
value = "true"
}
prefixLen := pos.Byte - eType.Range().Start.Byte
prefix := value[0:prefixLen]
return boolLiteralCandidates(prefix, eType.Range())
}
}

return []lang.Candidate{}
}

func boolLiteralCandidates(prefix string, editRange hcl.Range) []lang.Candidate {
candidates := make([]lang.Candidate, 0)

if strings.HasPrefix("false", prefix) {
candidates = append(candidates, lang.Candidate{
Label: "false",
Detail: cty.Bool.FriendlyNameForConstraint(),
Kind: lang.BoolCandidateKind,
TextEdit: lang.TextEdit{
NewText: "false",
Snippet: "false",
Range: editRange,
},
})
}
if strings.HasPrefix("true", prefix) {
candidates = append(candidates, lang.Candidate{
Label: "true",
Detail: cty.Bool.FriendlyNameForConstraint(),
Kind: lang.BoolCandidateKind,
TextEdit: lang.TextEdit{
NewText: "true",
Snippet: "true",
Range: editRange,
},
})
}

return candidates
}

func ctyObjectToObjectAttributes(objType cty.Type) schema.ObjectAttributes {
attrTypes := objType.AttributeTypes()
objAttributes := make(schema.ObjectAttributes, len(attrTypes))

for name, attrType := range attrTypes {
aSchema := &schema.AttributeSchema{
Constraint: schema.LiteralType{
Type: attrType,
},
}
if objType.AttributeOptional(name) {
aSchema.IsOptional = true
} else {
aSchema.IsRequired = true
}
objAttributes[name] = aSchema
}

return objAttributes
}
Loading

0 comments on commit cfdd98f

Please sign in to comment.