Skip to content

Commit

Permalink
decoder: Plumb schema.Constraint through & introduce Expression
Browse files Browse the repository at this point in the history
  • Loading branch information
radeksimko committed Jan 5, 2023
1 parent fd648ad commit fbf7e72
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 68 deletions.
23 changes: 20 additions & 3 deletions decoder/attribute_candidates.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ import (
)

func attributeSchemaToCandidate(name string, attr *schema.AttributeSchema, rng hcl.Range) lang.Candidate {
var snippet string
var triggerSuggest bool
if attr.Constraint != nil {
cData := attr.Constraint.EmptyCompletionData(1)
snippet = fmt.Sprintf("%s = %s", name, cData.Snippet)
triggerSuggest = cData.TriggerSuggest
} else {
snippet = snippetForAttribute(name, attr)
triggerSuggest = triggerSuggestForExprConstraints(attr.Expr)
}

return lang.Candidate{
Label: name,
Detail: detailForAttribute(attr),
Expand All @@ -20,10 +31,10 @@ func attributeSchemaToCandidate(name string, attr *schema.AttributeSchema, rng h
Kind: lang.AttributeCandidateKind,
TextEdit: lang.TextEdit{
NewText: name,
Snippet: snippetForAttribute(name, attr),
Snippet: snippet,
Range: rng,
},
TriggerSuggest: triggerSuggestForExprConstraints(attr.Expr),
TriggerSuggest: triggerSuggest,
}
}

Expand All @@ -40,7 +51,13 @@ func detailForAttribute(attr *schema.AttributeSchema) string {
details = append(details, "sensitive")
}

friendlyName := attr.Expr.FriendlyName()
var friendlyName string
if attr.Constraint != nil {
friendlyName = attr.Constraint.FriendlyName()
} else {
friendlyName = attr.Expr.FriendlyName()
}

if friendlyName != "" {
details = append(details, friendlyName)
}
Expand Down
25 changes: 25 additions & 0 deletions decoder/expression.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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"
)

// Expression represents an expression capable of providing
// various LSP features for given hcl.Expression and schema.Constraint.
type Expression interface {
CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candidate
HoverAtPos(ctx context.Context, pos hcl.Pos) *lang.HoverData
SemanticTokens(ctx context.Context) []lang.SemanticToken
ReferenceOrigins(allowSelfRefs bool) reference.Origins
ReferenceTargets(attrAddr *schema.AttributeAddrSchema) reference.Targets
}

func NewExpression(expr hcl.Expression, cons schema.Constraint) Expression {
// TODO
return nil
}
56 changes: 39 additions & 17 deletions decoder/expression_candidates.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,42 @@ import (
)

func (d *PathDecoder) attrValueCandidatesAtPos(ctx context.Context, attr *hclsyntax.Attribute, schema *schema.AttributeSchema, outerBodyRng hcl.Range, pos hcl.Pos) (lang.Candidates, error) {
constraints, editRng := constraintsAtPos(attr.Expr, ExprConstraints(schema.Expr), pos)
candidates := lang.NewCandidates()
candidates.IsComplete = true

if len(schema.CompletionHooks) > 0 {
candidates.IsComplete = false
candidates.List = append(candidates.List, d.candidatesFromHooks(ctx, attr, schema, outerBodyRng, pos)...)
}

count := len(candidates.List)
if len(constraints) > 0 && uint(count) < d.maxCandidates {
prefixRng := editRng
prefixRng.End = pos

for _, cons := range constraints {
if uint(count) >= d.maxCandidates {
return candidates, nil
if schema.Constraint != nil {
if uint(count) < d.maxCandidates {
expr := NewExpression(attr.Expr, schema.Constraint)
for _, candidate := range expr.CompletionAtPos(ctx, pos) {
if uint(count) >= d.maxCandidates {
return candidates, nil
}

candidates.List = append(candidates.List, candidate)
count++
}
}
} else {
constraints, editRng := constraintsAtPos(attr.Expr, ExprConstraints(schema.Expr), pos)

candidates.List = append(candidates.List, d.constraintToCandidates(ctx, cons, attr, outerBodyRng, prefixRng, editRng)...)
count++
if len(constraints) > 0 && uint(count) < d.maxCandidates {
prefixRng := editRng
prefixRng.End = pos

for _, cons := range constraints {
if uint(count) >= d.maxCandidates {
return candidates, nil
}

candidates.List = append(candidates.List, d.constraintToCandidates(ctx, cons, attr, outerBodyRng, prefixRng, editRng)...)
count++
}
}
}

Expand Down Expand Up @@ -235,13 +250,20 @@ func isMultilineTemplateExpr(expr hclsyntax.Expression) bool {
return t.Range().Start.Line != t.Range().End.Line
}

func (d *PathDecoder) candidatesFromHooks(ctx context.Context, attr *hclsyntax.Attribute, schema *schema.AttributeSchema, outerBodyRng hcl.Range, pos hcl.Pos) []lang.Candidate {
func (d *PathDecoder) candidatesFromHooks(ctx context.Context, attr *hclsyntax.Attribute, aSchema *schema.AttributeSchema, outerBodyRng hcl.Range, pos hcl.Pos) []lang.Candidate {
candidates := make([]lang.Candidate, 0)
expr := ExprConstraints(schema.Expr)
exprType, ok := expr.LiteralType()
if !ok && exprType != cty.String {
// Return early as we only support string values for now
return candidates
if aSchema.Constraint != nil {
if con, ok := aSchema.Constraint.(schema.Conformable); ok && con.Conforms(cty.String) {
// Return early as we only support string values for now
return candidates
}
} else {
expr := ExprConstraints(aSchema.Expr)
exprType, ok := expr.LiteralType()
if !ok && exprType != cty.String {
// Return early as we only support string values for now
return candidates
}
}

editRng := attr.Expr.Range()
Expand All @@ -264,7 +286,7 @@ func (d *PathDecoder) candidatesFromHooks(ctx context.Context, attr *hclsyntax.A
ctx = WithMaxCandidates(ctx, d.maxCandidates)

count := 0
for _, hook := range schema.CompletionHooks {
for _, hook := range aSchema.CompletionHooks {
if completionFunc, ok := d.decoderCtx.CompletionHooks[hook.Name]; ok {
res, _ := completionFunc(ctx, cty.StringVal(prefix))

Expand Down
4 changes: 4 additions & 0 deletions decoder/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ func (d *PathDecoder) hoverAtPos(ctx context.Context, body *hclsyntax.Body, body
}

if attr.Expr.Range().ContainsPos(pos) {
if aSchema.Constraint != nil {
return NewExpression(attr.Expr, aSchema.Constraint).HoverAtPos(ctx, pos), nil
}

exprCons := ExprConstraints(aSchema.Expr)
data, err := d.hoverDataForExpr(ctx, attr.Expr, exprCons, 0, pos)
if err != nil {
Expand Down
9 changes: 7 additions & 2 deletions decoder/label_candidates.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,13 @@ func requiredFieldsSnippet(bodySchema *schema.BodySchema, placeholder int, inden
continue
}

valueSnippet := snippetForExprContraint(uint(placeholder), attr.Expr)
snippetText += fmt.Sprintf("%s%s = %s", indent, attrName, valueSnippet)
var snippet string
if attr.Constraint != nil {
snippet = attr.Constraint.EmptyCompletionData(placeholder).Snippet
} else {
snippet = snippetForExprContraint(uint(placeholder), attr.Expr)
}
snippetText += fmt.Sprintf("%s%s = %s", indent, attrName, snippet)

// attrCount is used to tell if we are at the end of the list of attributes
// so we don't add a trailing newline. this will affect both attribute
Expand Down
18 changes: 11 additions & 7 deletions decoder/reference_origins.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,11 @@ func (d *PathDecoder) referenceOriginsInBody(body hcl.Body, bodySchema *schema.B
if bodySchema.Extensions != nil && bodySchema.Extensions.SelfRefs {
allowSelfRefs = true
}
origins = append(origins, d.findOriginsInExpression(attr.Expr, aSchema.Expr, allowSelfRefs)...)
if aSchema.Constraint != nil {
origins = append(origins, NewExpression(attr.Expr, aSchema.Constraint).ReferenceOrigins(allowSelfRefs)...)
} else {
origins = append(origins, d.legacyFindOriginsInExpression(attr.Expr, aSchema.Expr, allowSelfRefs)...)
}
}

for _, block := range content.Blocks {
Expand All @@ -192,23 +196,23 @@ func (d *PathDecoder) referenceOriginsInBody(body hcl.Body, bodySchema *schema.B
return origins, impliedOrigins
}

func (d *PathDecoder) findOriginsInExpression(expr hcl.Expression, ec schema.ExprConstraints, allowSelfRefs bool) reference.Origins {
func (d *PathDecoder) legacyFindOriginsInExpression(expr hcl.Expression, ec schema.ExprConstraints, allowSelfRefs bool) reference.Origins {
origins := make(reference.Origins, 0)

switch eType := expr.(type) {
case *hclsyntax.TupleConsExpr:
le, ok := ExprConstraints(ec).ListExpr()
if ok {
for _, elemExpr := range eType.ExprList() {
origins = append(origins, d.findOriginsInExpression(elemExpr, le.Elem, allowSelfRefs)...)
origins = append(origins, d.legacyFindOriginsInExpression(elemExpr, le.Elem, allowSelfRefs)...)
}
break
}

se, ok := ExprConstraints(ec).SetExpr()
if ok {
for _, elemExpr := range eType.ExprList() {
origins = append(origins, d.findOriginsInExpression(elemExpr, se.Elem, allowSelfRefs)...)
origins = append(origins, d.legacyFindOriginsInExpression(elemExpr, se.Elem, allowSelfRefs)...)
}
break
}
Expand All @@ -219,7 +223,7 @@ func (d *PathDecoder) findOriginsInExpression(expr hcl.Expression, ec schema.Exp
if len(tue.Elems) < i+1 {
break
}
origins = append(origins, d.findOriginsInExpression(elemExpr, tue.Elems[i], allowSelfRefs)...)
origins = append(origins, d.legacyFindOriginsInExpression(elemExpr, tue.Elems[i], allowSelfRefs)...)
}
}
case *hclsyntax.ObjectConsExpr:
Expand All @@ -239,14 +243,14 @@ func (d *PathDecoder) findOriginsInExpression(expr hcl.Expression, ec schema.Exp
continue
}

origins = append(origins, d.findOriginsInExpression(item.ValueExpr, attr.Expr, allowSelfRefs)...)
origins = append(origins, d.legacyFindOriginsInExpression(item.ValueExpr, attr.Expr, allowSelfRefs)...)
}
}

me, ok := ExprConstraints(ec).MapExpr()
if ok {
for _, item := range eType.Items {
origins = append(origins, d.findOriginsInExpression(item.ValueExpr, me.Elem, allowSelfRefs)...)
origins = append(origins, d.legacyFindOriginsInExpression(item.ValueExpr, me.Elem, allowSelfRefs)...)
}
}
case *hclsyntax.AnonSymbolExpr,
Expand Down
79 changes: 42 additions & 37 deletions decoder/reference_targets.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,55 +268,60 @@ func decodeTargetableBody(body hcl.Body, parentBlock *blockContent, tt *schema.T
func decodeReferenceTargetsForAttribute(attr *hcl.Attribute, attrSchema *schema.AttributeSchema) reference.Targets {
refs := make(reference.Targets, 0)

if attrSchema.Address != nil {
attrAddr, ok := resolveAttributeAddress(attr, attrSchema.Address.Steps)
if ok {
if attrSchema.Address.AsReference {
ref := reference.Target{
Addr: attrAddr,
ScopeId: attrSchema.Address.ScopeId,
DefRangePtr: &attr.NameRange,
RangePtr: attr.Range.Ptr(),
Name: attrSchema.Address.FriendlyName,
}
refs = append(refs, ref)
}

if attrSchema.Address.AsExprType {
t, ok := exprConstraintToDataType(attrSchema.Expr)
if ok {
if t == cty.DynamicPseudoType && attr.Expr != nil {
// attempt to make the type more specific
exprVal, diags := attr.Expr.Value(nil)
if !diags.HasErrors() {
t = exprVal.Type()
}
}

scopeId := attrSchema.Address.ScopeId

if attrSchema.Constraint != nil {
refs = append(refs, NewExpression(attr.Expr, attrSchema.Constraint).ReferenceTargets(attrSchema.Address)...)
} else {
if attrSchema.Address != nil {
attrAddr, ok := resolveAttributeAddress(attr, attrSchema.Address.Steps)
if ok {
if attrSchema.Address.AsReference {
ref := reference.Target{
Addr: attrAddr,
Type: t,
ScopeId: scopeId,
DefRangePtr: attr.NameRange.Ptr(),
ScopeId: attrSchema.Address.ScopeId,
DefRangePtr: &attr.NameRange,
RangePtr: attr.Range.Ptr(),
Name: attrSchema.Address.FriendlyName,
}
refs = append(refs, ref)
}

if attr.Expr != nil && !t.IsPrimitiveType() {
ref.NestedTargets = make(reference.Targets, 0)
ref.NestedTargets = append(ref.NestedTargets, decodeReferenceTargetsForComplexTypeExpr(attrAddr, attr.Expr, t, scopeId)...)
}
if attrSchema.Address.AsExprType {
t, ok := exprConstraintToDataType(attrSchema.Expr)
if ok {
if t == cty.DynamicPseudoType && attr.Expr != nil {
// attempt to make the type more specific
exprVal, diags := attr.Expr.Value(nil)
if !diags.HasErrors() {
t = exprVal.Type()
}
}

refs = append(refs, ref)
scopeId := attrSchema.Address.ScopeId

ref := reference.Target{
Addr: attrAddr,
Type: t,
ScopeId: scopeId,
DefRangePtr: attr.NameRange.Ptr(),
RangePtr: attr.Range.Ptr(),
Name: attrSchema.Address.FriendlyName,
}

if attr.Expr != nil && !t.IsPrimitiveType() {
ref.NestedTargets = make(reference.Targets, 0)
ref.NestedTargets = append(ref.NestedTargets, decodeReferenceTargetsForComplexTypeExpr(attrAddr, attr.Expr, t, scopeId)...)
}

refs = append(refs, ref)
}
}
}
}

ec := ExprConstraints(attrSchema.Expr)
refs = append(refs, referenceTargetsForExpr(attr.Expr, ec)...)
}

ec := ExprConstraints(attrSchema.Expr)
refs = append(refs, referenceTargetsForExpr(attr.Expr, ec)...)
return refs
}

Expand Down
8 changes: 6 additions & 2 deletions decoder/semantic_tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,12 @@ func (d *PathDecoder) tokensForBody(ctx context.Context, body *hclsyntax.Body, b
Range: attr.NameRange,
})

ec := ExprConstraints(attrSchema.Expr)
tokens = append(tokens, d.tokensForExpression(ctx, attr.Expr, ec)...)
if attrSchema.Constraint != nil {
tokens = append(tokens, NewExpression(attr.Expr, attrSchema.Constraint).SemanticTokens(ctx)...)
} else {
ec := ExprConstraints(attrSchema.Expr)
tokens = append(tokens, d.tokensForExpression(ctx, attr.Expr, ec)...)
}
}

for _, block := range body.Blocks {
Expand Down

0 comments on commit fbf7e72

Please sign in to comment.