Skip to content

Commit

Permalink
fix: avoid crashes on unknown keys (#29)
Browse files Browse the repository at this point in the history
* Add tests for unknown keys

* fix: avoid crashes on unknown keys
  • Loading branch information
radeksimko authored Apr 8, 2021
1 parent 9bb6847 commit 98c28fb
Show file tree
Hide file tree
Showing 9 changed files with 484 additions and 17 deletions.
4 changes: 3 additions & 1 deletion decoder/expression_candidates.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ func constraintsAtPos(expr hcl.Expression, constraints ExprConstraints, pos hcl.
undeclaredAttributes := oe.Attributes
for _, item := range eType.Items {
key, _ := item.KeyExpr.Value(nil)
if !key.IsWhollyKnown() || key.Type() != cty.String {
if key.IsNull() || !key.IsWhollyKnown() || key.Type() != cty.String {
// skip items keys that can't be interpolated
// without further context
continue
}
attr, ok := oe.Attributes[key.AsString()]
Expand Down
51 changes: 51 additions & 0 deletions decoder/expression_candidates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,57 @@ func TestDecoder_CandidateAtPos_expressions(t *testing.T) {
},
}),
},
{
"object as expression - attribute key unknown",
map[string]*schema.AttributeSchema{
"attr": {
Expr: schema.ExprConstraints{
schema.ObjectExpr{
Attributes: schema.ObjectExprAttributes{
"first": schema.ObjectAttribute{
Expr: schema.LiteralTypeOnly(cty.String),
},
"second": schema.ObjectAttribute{
Expr: schema.LiteralTypeOnly(cty.Number),
},
},
},
},
},
},
`attr = {
first = "blah"
var.test = "foo"
"${var.env}.${another}" = "prod"
}
`,
hcl.Pos{Line: 5, Column: 3, Byte: 82},
lang.CompleteCandidates([]lang.Candidate{
{
Label: "second",
Detail: "number",
TextEdit: lang.TextEdit{
Range: hcl.Range{
Filename: "test.tf",
Start: hcl.Pos{
Line: 5,
Column: 3,
Byte: 82,
},
End: hcl.Pos{
Line: 5,
Column: 3,
Byte: 82,
},
},
NewText: "second = 1",
Snippet: "second = ${1:1}",
},
Kind: lang.AttributeCandidateKind,
},
}),
},
{
"list as value",
map[string]*schema.AttributeSchema{
Expand Down
4 changes: 3 additions & 1 deletion decoder/expression_constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ func (ec ExprConstraints) LiteralValueOfObjectConsExpr(expr *hclsyntax.ObjectCon
exprValues := make(map[string]cty.Value)
for _, item := range expr.Items {
key, _ := item.KeyExpr.Value(nil)
if !key.IsWhollyKnown() || key.Type() != cty.String {
if key.IsNull() || !key.IsWhollyKnown() || key.Type() != cty.String {
// Avoid building incomplete object with keys
// that can't be interpolated without further context
return schema.LiteralValue{}, false
}

Expand Down
4 changes: 3 additions & 1 deletion decoder/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,9 @@ func hoverDataForExpr(expr hcl.Expression, constraints ExprConstraints, pos hcl.
func hoverDataForObjectExpr(objExpr *hclsyntax.ObjectConsExpr, oe schema.ObjectExpr, pos hcl.Pos) (*lang.HoverData, error) {
for _, item := range objExpr.Items {
key, _ := item.KeyExpr.Value(nil)
if !key.IsWhollyKnown() || key.Type() != cty.String {
if key.IsNull() || !key.IsWhollyKnown() || key.Type() != cty.String {
// skip items keys that can't be interpolated
// without further context
continue
}
attr, ok := oe.Attributes[key.AsString()]
Expand Down
97 changes: 97 additions & 0 deletions decoder/hover_expressions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,52 @@ _object_`),
},
nil,
},
{
"object as type with unknown key",
map[string]*schema.AttributeSchema{
"litobj": {Expr: schema.LiteralTypeOnly(cty.Object(map[string]cty.Type{
"source": cty.String,
"bool": cty.Bool,
"notbool": cty.String,
"nested_map": cty.Map(cty.String),
"nested_obj": cty.Object(map[string]cty.Type{}),
}))},
},
`litobj = {
"${var.src}" = "blah"
"${var.env}.${another}" = "prod"
"different" = 42
"bool" = true
"notbool" = "test"
}`,
hcl.Pos{Line: 4, Column: 12, Byte: 65},
&lang.HoverData{
Content: lang.Markdown("```" + `
{
bool = bool
nested_map = map of string
nested_obj = object
notbool = string
source = string
}
` + "```" + `
_object_`),
Range: hcl.Range{
Filename: "test.tf",
Start: hcl.Pos{
Line: 1,
Column: 10,
Byte: 9,
},
End: hcl.Pos{
Line: 7,
Column: 4,
Byte: 139,
},
},
},
nil,
},
{
"object as expression",
map[string]*schema.AttributeSchema{
Expand Down Expand Up @@ -303,6 +349,57 @@ _object_`),
},
nil,
},
{
"object as expression with unknown key",
map[string]*schema.AttributeSchema{
"obj": {Expr: schema.ExprConstraints{
schema.ObjectExpr{
Attributes: schema.ObjectExprAttributes{
"source": schema.ObjectAttribute{
Expr: schema.LiteralTypeOnly(cty.String),
},
"bool": schema.ObjectAttribute{
Expr: schema.LiteralTypeOnly(cty.Bool),
},
"notbool": schema.ObjectAttribute{
Expr: schema.LiteralTypeOnly(cty.String),
},
"nested_map": schema.ObjectAttribute{
Expr: schema.LiteralTypeOnly(cty.Map(cty.String)),
},
"nested_obj": schema.ObjectAttribute{
Expr: schema.LiteralTypeOnly(cty.Object(map[string]cty.Type{})),
},
},
},
}},
},
`obj = {
var.src = "blah"
"${var.env}.${another}" = "prod"
different = 42
bool = true
notbool = "test"
}`,
hcl.Pos{Line: 1, Column: 3, Byte: 2},
&lang.HoverData{
Content: lang.Markdown(`**obj** _object_`),
Range: hcl.Range{
Filename: "test.tf",
Start: hcl.Pos{
Line: 1,
Column: 1,
Byte: 0,
},
End: hcl.Pos{
Line: 7,
Column: 2,
Byte: 123,
},
},
},
nil,
},
{
"object as expression - expression",
map[string]*schema.AttributeSchema{
Expand Down
32 changes: 19 additions & 13 deletions decoder/semantic_tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,9 @@ func tokensForExpression(expr hclsyntax.Expression, constraints ExprConstraints)
if ok {
for _, item := range eType.Items {
key, _ := item.KeyExpr.Value(nil)
if !key.IsWhollyKnown() || key.Type() != cty.String {
if key.IsNull() || !key.IsWhollyKnown() || key.Type() != cty.String {
// skip items keys that can't be interpolated
// without further context
continue
}
attr, ok := oe.Attributes[key.AsString()]
Expand Down Expand Up @@ -299,19 +301,23 @@ func tokensForObjectConsExpr(expr *hclsyntax.ObjectConsExpr, exprType cty.Type)
attrTypes := exprType.AttributeTypes()
for _, item := range expr.Items {
key, _ := item.KeyExpr.Value(nil)
if key.IsWhollyKnown() && key.Type() == cty.String {
valType, ok := attrTypes[key.AsString()]
if !ok {
// unknown attribute
continue
}
tokens = append(tokens, lang.SemanticToken{
Type: lang.TokenObjectKey,
Modifiers: []lang.SemanticTokenModifier{},
Range: item.KeyExpr.Range(),
})
tokens = append(tokens, tokenForTypedExpression(item.ValueExpr, valType)...)
if key.IsNull() || !key.IsWhollyKnown() || key.Type() != cty.String {
// skip items keys that can't be interpolated
// without further context
continue
}

valType, ok := attrTypes[key.AsString()]
if !ok {
// unknown attribute
continue
}
tokens = append(tokens, lang.SemanticToken{
Type: lang.TokenObjectKey,
Modifiers: []lang.SemanticTokenModifier{},
Range: item.KeyExpr.Range(),
})
tokens = append(tokens, tokenForTypedExpression(item.ValueExpr, valType)...)
}
}
if exprType.IsMapType() {
Expand Down
Loading

0 comments on commit 98c28fb

Please sign in to comment.