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

decoder: Add support for LiteralType as Constraint #186

Merged
merged 8 commits into from
Mar 20, 2023
29 changes: 1 addition & 28 deletions decoder/expr_literal_type.go
Original file line number Diff line number Diff line change
@@ -1,40 +1,13 @@
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 LiteralType struct {
expr hcl.Expression
cons schema.LiteralType
}

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

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

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

func (lt LiteralType) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) reference.Origins {
// TODO
return nil
}

func (lt LiteralType) ReferenceTargets(ctx context.Context, targetCtx *TargetContext) reference.Targets {
// TODO
return nil
pathCtx *PathContext
}
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
radeksimko marked this conversation as resolved.
Show resolved Hide resolved

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, 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 []lang.Candidate{}
}

func (lt LiteralType) completeBoolAtPos(ctx context.Context, 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