From 08344a41598396919f9b00a28bb9ddac2ebf80c0 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 22 Aug 2018 13:09:44 -0700 Subject: [PATCH] govendor fetch github.com/hashicorp/hcl2/... This is a general catchup of some developments in the HCL2 codebase, but in particular includes: - Recording expression and evalcontext as part of diagnostics, so that variable value information can be included alongside diagnostic snippets. - hcldec supports decoding blocks into tuple and object values as well as list and map values, which then allows cty.DynamicPseudoType nested attributes to work properly. --- .../hcl2/ext/dynblock/expand_spec.go | 50 +- .../hashicorp/hcl2/hcl/diagnostic.go | 31 +- .../hashicorp/hcl2/hcl/diagnostic_text.go | 143 +++++ .../hcl2/hcl/hclsyntax/diagnostics.go | 23 + .../hcl2/hcl/hclsyntax/expression.go | 292 ++++++---- .../hcl2/hcl/hclsyntax/expression_ops.go | 56 +- .../hcl2/hcl/hclsyntax/expression_template.go | 20 +- .../hashicorp/hcl2/hcl/hclsyntax/structure.go | 7 + .../hcl2/hcl/hclsyntax/structure_at_pos.go | 118 ++++ .../hashicorp/hcl2/hcl/json/structure.go | 33 +- vendor/github.com/hashicorp/hcl2/hcl/pos.go | 10 + .../hashicorp/hcl2/hcl/structure_at_pos.go | 117 ++++ .../github.com/hashicorp/hcl2/hcldec/spec.go | 529 +++++++++++++++++- .../github.com/hashicorp/hcl2/hclwrite/ast.go | 201 ++----- .../hashicorp/hcl2/hclwrite/ast_body.go | 137 +++++ .../hashicorp/hcl2/hclwrite/ast_expression.go | 82 +++ .../hashicorp/hcl2/hclwrite/node.go | 231 ++++++++ .../hashicorp/hcl2/hclwrite/parser.go | 325 ++++++----- .../hashicorp/hcl2/hclwrite/public.go | 16 +- .../hashicorp/hcl2/hclwrite/tokens.go | 104 +--- vendor/vendor.json | 56 +- 21 files changed, 2002 insertions(+), 579 deletions(-) create mode 100644 vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/diagnostics.go create mode 100644 vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/structure_at_pos.go create mode 100644 vendor/github.com/hashicorp/hcl2/hcl/structure_at_pos.go create mode 100644 vendor/github.com/hashicorp/hcl2/hclwrite/ast_body.go create mode 100644 vendor/github.com/hashicorp/hcl2/hclwrite/ast_expression.go create mode 100644 vendor/github.com/hashicorp/hcl2/hclwrite/node.go diff --git a/vendor/github.com/hashicorp/hcl2/ext/dynblock/expand_spec.go b/vendor/github.com/hashicorp/hcl2/ext/dynblock/expand_spec.go index fa91c9f3ae..be76cd04dc 100644 --- a/vendor/github.com/hashicorp/hcl2/ext/dynblock/expand_spec.go +++ b/vendor/github.com/hashicorp/hcl2/ext/dynblock/expand_spec.go @@ -43,19 +43,23 @@ func (b *expandBody) decodeSpec(blockS *hcl.BlockHeaderSchema, rawSpec *hcl.Bloc if !eachVal.CanIterateElements() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid dynamic for_each value", - Detail: fmt.Sprintf("Cannot use a value of type %s in for_each. An iterable collection is required.", eachVal.Type()), - Subject: eachAttr.Expr.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Invalid dynamic for_each value", + Detail: fmt.Sprintf("Cannot use a value of type %s in for_each. An iterable collection is required.", eachVal.Type()), + Subject: eachAttr.Expr.Range().Ptr(), + Expression: eachAttr.Expr, + EvalContext: b.forEachCtx, }) return nil, diags } if eachVal.IsNull() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid dynamic for_each value", - Detail: "Cannot use a null value in for_each.", - Subject: eachAttr.Expr.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Invalid dynamic for_each value", + Detail: "Cannot use a null value in for_each.", + Subject: eachAttr.Expr.Range().Ptr(), + Expression: eachAttr.Expr, + EvalContext: b.forEachCtx, }) return nil, diags } @@ -159,28 +163,34 @@ func (s *expandSpec) newBlock(i *iteration, ctx *hcl.EvalContext) (*hcl.Block, h labelVal, convErr = convert.Convert(labelVal, cty.String) if convErr != nil { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid dynamic block label", - Detail: fmt.Sprintf("Cannot use this value as a dynamic block label: %s.", convErr), - Subject: labelExpr.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Invalid dynamic block label", + Detail: fmt.Sprintf("Cannot use this value as a dynamic block label: %s.", convErr), + Subject: labelExpr.Range().Ptr(), + Expression: labelExpr, + EvalContext: lCtx, }) return nil, diags } if labelVal.IsNull() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid dynamic block label", - Detail: "Cannot use a null value as a dynamic block label.", - Subject: labelExpr.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Invalid dynamic block label", + Detail: "Cannot use a null value as a dynamic block label.", + Subject: labelExpr.Range().Ptr(), + Expression: labelExpr, + EvalContext: lCtx, }) return nil, diags } if !labelVal.IsKnown() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid dynamic block label", - Detail: "This value is not yet known. Dynamic block labels must be immediately-known values.", - Subject: labelExpr.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Invalid dynamic block label", + Detail: "This value is not yet known. Dynamic block labels must be immediately-known values.", + Subject: labelExpr.Range().Ptr(), + Expression: labelExpr, + EvalContext: lCtx, }) return nil, diags } diff --git a/vendor/github.com/hashicorp/hcl2/hcl/diagnostic.go b/vendor/github.com/hashicorp/hcl2/hcl/diagnostic.go index f1e00569f1..c320961e11 100644 --- a/vendor/github.com/hashicorp/hcl2/hcl/diagnostic.go +++ b/vendor/github.com/hashicorp/hcl2/hcl/diagnostic.go @@ -26,14 +26,43 @@ const ( type Diagnostic struct { Severity DiagnosticSeverity - // Summary and detail contain the English-language description of the + // Summary and Detail contain the English-language description of the // problem. Summary is a terse description of the general problem and // detail is a more elaborate, often-multi-sentence description of // the probem and what might be done to solve it. Summary string Detail string + + // Subject and Context are both source ranges relating to the diagnostic. + // + // Subject is a tight range referring to exactly the construct that + // is problematic, while Context is an optional broader range (which should + // fully contain Subject) that ought to be shown around Subject when + // generating isolated source-code snippets in diagnostic messages. + // If Context is nil, the Subject is also the Context. + // + // Some diagnostics have no source ranges at all. If Context is set then + // Subject should always also be set. Subject *Range Context *Range + + // For diagnostics that occur when evaluating an expression, Expression + // may refer to that expression and EvalContext may point to the + // EvalContext that was active when evaluating it. This may allow for the + // inclusion of additional useful information when rendering a diagnostic + // message to the user. + // + // It is not always possible to select a single EvalContext for a + // diagnostic, and so in some cases this field may be nil even when an + // expression causes a problem. + // + // EvalContexts form a tree, so the given EvalContext may refer to a parent + // which in turn refers to another parent, etc. For a full picture of all + // of the active variables and functions the caller must walk up this + // chain, preferring definitions that are "closer" to the expression in + // case of colliding names. + Expression Expression + EvalContext *EvalContext } // Diagnostics is a list of Diagnostic instances. diff --git a/vendor/github.com/hashicorp/hcl2/hcl/diagnostic_text.go b/vendor/github.com/hashicorp/hcl2/hcl/diagnostic_text.go index dfa473add8..0b4a2629b9 100644 --- a/vendor/github.com/hashicorp/hcl2/hcl/diagnostic_text.go +++ b/vendor/github.com/hashicorp/hcl2/hcl/diagnostic_text.go @@ -2,11 +2,14 @@ package hcl import ( "bufio" + "bytes" "errors" "fmt" "io" + "sort" wordwrap "github.com/mitchellh/go-wordwrap" + "github.com/zclconf/go-cty/cty" ) type diagnosticTextWriter struct { @@ -133,6 +136,62 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error { w.wr.Write([]byte{'\n'}) } + + if diag.Expression != nil && diag.EvalContext != nil { + // We will attempt to render the values for any variables + // referenced in the given expression as additional context, for + // situations where the same expression is evaluated multiple + // times in different scopes. + expr := diag.Expression + ctx := diag.EvalContext + + vars := expr.Variables() + stmts := make([]string, 0, len(vars)) + seen := make(map[string]struct{}, len(vars)) + for _, traversal := range vars { + val, diags := traversal.TraverseAbs(ctx) + if diags.HasErrors() { + // Skip anything that generates errors, since we probably + // already have the same error in our diagnostics set + // already. + continue + } + + traversalStr := w.traversalStr(traversal) + if _, exists := seen[traversalStr]; exists { + continue // don't show duplicates when the same variable is referenced multiple times + } + switch { + case !val.IsKnown(): + // Can't say anything about this yet, then. + continue + case val.IsNull(): + stmts = append(stmts, fmt.Sprintf("%s set to null", traversalStr)) + default: + stmts = append(stmts, fmt.Sprintf("%s as %s", traversalStr, w.valueStr(val))) + } + seen[traversalStr] = struct{}{} + } + + sort.Strings(stmts) // FIXME: Should maybe use a traversal-aware sort that can sort numeric indexes properly? + last := len(stmts) - 1 + + for i, stmt := range stmts { + switch i { + case 0: + w.wr.Write([]byte{'w', 'i', 't', 'h', ' '}) + default: + w.wr.Write([]byte{' ', ' ', ' ', ' ', ' '}) + } + w.wr.Write([]byte(stmt)) + switch i { + case last: + w.wr.Write([]byte{'.', '\n', '\n'}) + default: + w.wr.Write([]byte{',', '\n'}) + } + } + } } if diag.Detail != "" { @@ -156,6 +215,90 @@ func (w *diagnosticTextWriter) WriteDiagnostics(diags Diagnostics) error { return nil } +func (w *diagnosticTextWriter) traversalStr(traversal Traversal) string { + // This is a specialized subset of traversal rendering tailored to + // producing helpful contextual messages in diagnostics. It is not + // comprehensive nor intended to be used for other purposes. + + var buf bytes.Buffer + for _, step := range traversal { + switch tStep := step.(type) { + case TraverseRoot: + buf.WriteString(tStep.Name) + case TraverseAttr: + buf.WriteByte('.') + buf.WriteString(tStep.Name) + case TraverseIndex: + buf.WriteByte('[') + if keyTy := tStep.Key.Type(); keyTy.IsPrimitiveType() { + buf.WriteString(w.valueStr(tStep.Key)) + } else { + // We'll just use a placeholder for more complex values, + // since otherwise our result could grow ridiculously long. + buf.WriteString("...") + } + buf.WriteByte(']') + } + } + return buf.String() +} + +func (w *diagnosticTextWriter) valueStr(val cty.Value) string { + // This is a specialized subset of value rendering tailored to producing + // helpful but concise messages in diagnostics. It is not comprehensive + // nor intended to be used for other purposes. + + ty := val.Type() + switch { + case val.IsNull(): + return "null" + case !val.IsKnown(): + // Should never happen here because we should filter before we get + // in here, but we'll do something reasonable rather than panic. + return "(not yet known)" + case ty == cty.Bool: + if val.True() { + return "true" + } + return "false" + case ty == cty.Number: + bf := val.AsBigFloat() + return bf.Text('g', 10) + case ty == cty.String: + // Go string syntax is not exactly the same as HCL native string syntax, + // but we'll accept the minor edge-cases where this is different here + // for now, just to get something reasonable here. + return fmt.Sprintf("%q", val.AsString()) + case ty.IsCollectionType() || ty.IsTupleType(): + l := val.LengthInt() + switch l { + case 0: + return "empty " + ty.FriendlyName() + case 1: + return ty.FriendlyName() + " with 1 element" + default: + return fmt.Sprintf("%s with %d elements", ty.FriendlyName(), l) + } + case ty.IsObjectType(): + atys := ty.AttributeTypes() + l := len(atys) + switch l { + case 0: + return "object with no attributes" + case 1: + var name string + for k := range atys { + name = k + } + return fmt.Sprintf("object with 1 attribute %q", name) + default: + return fmt.Sprintf("object with %d attributes", l) + } + default: + return ty.FriendlyName() + } +} + func contextString(file *File, offset int) string { type contextStringer interface { ContextString(offset int) string diff --git a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/diagnostics.go b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/diagnostics.go new file mode 100644 index 0000000000..94eaf58929 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/diagnostics.go @@ -0,0 +1,23 @@ +package hclsyntax + +import ( + "github.com/hashicorp/hcl2/hcl" +) + +// setDiagEvalContext is an internal helper that will impose a particular +// EvalContext on a set of diagnostics in-place, for any diagnostic that +// does not already have an EvalContext set. +// +// We generally expect diagnostics to be immutable, but this is safe to use +// on any Diagnostics where none of the contained Diagnostic objects have yet +// been seen by a caller. Its purpose is to apply additional context to a +// set of diagnostics produced by a "deeper" component as the stack unwinds +// during expression evaluation. +func setDiagEvalContext(diags hcl.Diagnostics, expr hcl.Expression, ctx *hcl.EvalContext) { + for _, diag := range diags { + if diag.Expression == nil { + diag.Expression = expr + diag.EvalContext = ctx + } + } +} diff --git a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/expression.go b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/expression.go index e54806e3c4..500eba3c70 100644 --- a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/expression.go +++ b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/expression.go @@ -105,7 +105,9 @@ func (e *ScopeTraversalExpr) walkChildNodes(w internalWalkFunc) { } func (e *ScopeTraversalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { - return e.Traversal.TraverseAbs(ctx) + val, diags := e.Traversal.TraverseAbs(ctx) + setDiagEvalContext(diags, e, ctx) + return val, diags } func (e *ScopeTraversalExpr) Range() hcl.Range { @@ -136,6 +138,7 @@ func (e *RelativeTraversalExpr) walkChildNodes(w internalWalkFunc) { func (e *RelativeTraversalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { src, diags := e.Source.Value(ctx) ret, travDiags := e.Traversal.TraverseRel(src) + setDiagEvalContext(travDiags, e, ctx) diags = append(diags, travDiags...) return ret, diags } @@ -207,10 +210,12 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti if !hasNonNilMap { return cty.DynamicVal, hcl.Diagnostics{ { - Severity: hcl.DiagError, - Summary: "Function calls not allowed", - Detail: "Functions may not be called here.", - Subject: e.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Function calls not allowed", + Detail: "Functions may not be called here.", + Subject: e.Range().Ptr(), + Expression: e, + EvalContext: ctx, }, } } @@ -226,11 +231,13 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti return cty.DynamicVal, hcl.Diagnostics{ { - Severity: hcl.DiagError, - Summary: "Call to unknown function", - Detail: fmt.Sprintf("There is no function named %q.%s", e.Name, suggestion), - Subject: &e.NameRange, - Context: e.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Call to unknown function", + Detail: fmt.Sprintf("There is no function named %q.%s", e.Name, suggestion), + Subject: &e.NameRange, + Context: e.Range().Ptr(), + Expression: e, + EvalContext: ctx, }, } } @@ -255,11 +262,13 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti case expandVal.Type().IsTupleType() || expandVal.Type().IsListType() || expandVal.Type().IsSetType(): if expandVal.IsNull() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid expanding argument value", - Detail: "The expanding argument (indicated by ...) must not be null.", - Context: expandExpr.Range().Ptr(), - Subject: e.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Invalid expanding argument value", + Detail: "The expanding argument (indicated by ...) must not be null.", + Subject: expandExpr.Range().Ptr(), + Context: e.Range().Ptr(), + Expression: expandExpr, + EvalContext: ctx, }) return cty.DynamicVal, diags } @@ -280,11 +289,13 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti args = newArgs default: diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid expanding argument value", - Detail: "The expanding argument (indicated by ...) must be of a tuple, list, or set type.", - Context: expandExpr.Range().Ptr(), - Subject: e.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Invalid expanding argument value", + Detail: "The expanding argument (indicated by ...) must be of a tuple, list, or set type.", + Subject: expandExpr.Range().Ptr(), + Context: e.Range().Ptr(), + Expression: expandExpr, + EvalContext: ctx, }) return cty.DynamicVal, diags } @@ -304,8 +315,10 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti "Function %q expects%s %d argument(s). Missing value for %q.", e.Name, qual, len(params), missing.Name, ), - Subject: &e.CloseParenRange, - Context: e.Range().Ptr(), + Subject: &e.CloseParenRange, + Context: e.Range().Ptr(), + Expression: e, + EvalContext: ctx, }, } } @@ -319,8 +332,10 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti "Function %q expects only %d argument(s).", e.Name, len(params), ), - Subject: args[len(params)].StartRange().Ptr(), - Context: e.Range().Ptr(), + Subject: args[len(params)].StartRange().Ptr(), + Context: e.Range().Ptr(), + Expression: e, + EvalContext: ctx, }, } } @@ -350,8 +365,10 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti "Invalid value for %q parameter: %s.", param.Name, err, ), - Subject: argExpr.StartRange().Ptr(), - Context: e.Range().Ptr(), + Subject: argExpr.StartRange().Ptr(), + Context: e.Range().Ptr(), + Expression: argExpr, + EvalContext: ctx, }) } @@ -387,8 +404,10 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti "Invalid value for %q parameter: %s.", param.Name, err, ), - Subject: argExpr.StartRange().Ptr(), - Context: e.Range().Ptr(), + Subject: argExpr.StartRange().Ptr(), + Context: e.Range().Ptr(), + Expression: argExpr, + EvalContext: ctx, }) default: @@ -399,8 +418,10 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti "Call to function %q failed: %s.", e.Name, err, ), - Subject: e.StartRange().Ptr(), - Context: e.Range().Ptr(), + Subject: e.StartRange().Ptr(), + Context: e.Range().Ptr(), + Expression: e, + EvalContext: ctx, }) } @@ -465,10 +486,12 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic // "These expressions are object and object respectively" if the // object types don't exactly match. "The true and false result expressions must have consistent types. The given expressions are %s and %s, respectively.", - trueResult.Type(), falseResult.Type(), + trueResult.Type().FriendlyName(), falseResult.Type().FriendlyName(), ), - Subject: hcl.RangeBetween(e.TrueResult.Range(), e.FalseResult.Range()).Ptr(), - Context: &e.SrcRange, + Subject: hcl.RangeBetween(e.TrueResult.Range(), e.FalseResult.Range()).Ptr(), + Context: &e.SrcRange, + Expression: e, + EvalContext: ctx, }, } } @@ -477,11 +500,13 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic diags = append(diags, condDiags...) if condResult.IsNull() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Null condition", - Detail: "The condition value is null. Conditions must either be true or false.", - Subject: e.Condition.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Null condition", + Detail: "The condition value is null. Conditions must either be true or false.", + Subject: e.Condition.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.Condition, + EvalContext: ctx, }) return cty.UnknownVal(resultType), diags } @@ -491,11 +516,13 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic condResult, err := convert.Convert(condResult, cty.Bool) if err != nil { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Incorrect condition type", - Detail: fmt.Sprintf("The condition expression must be of type bool."), - Subject: e.Condition.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Incorrect condition type", + Detail: fmt.Sprintf("The condition expression must be of type bool."), + Subject: e.Condition.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.Condition, + EvalContext: ctx, }) return cty.UnknownVal(resultType), diags } @@ -514,8 +541,10 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic "The true result value has the wrong type: %s.", err.Error(), ), - Subject: e.TrueResult.Range().Ptr(), - Context: &e.SrcRange, + Subject: e.TrueResult.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.TrueResult, + EvalContext: ctx, }) trueResult = cty.UnknownVal(resultType) } @@ -535,8 +564,10 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic "The false result value has the wrong type: %s.", err.Error(), ), - Subject: e.TrueResult.Range().Ptr(), - Context: &e.SrcRange, + Subject: e.FalseResult.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.FalseResult, + EvalContext: ctx, }) falseResult = cty.UnknownVal(resultType) } @@ -573,7 +604,9 @@ func (e *IndexExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { diags = append(diags, collDiags...) diags = append(diags, keyDiags...) - return hcl.Index(coll, key, &e.SrcRange) + val, diags := hcl.Index(coll, key, &e.SrcRange) + setDiagEvalContext(diags, e, ctx) + return val, diags } func (e *IndexExpr) Range() hcl.Range { @@ -676,10 +709,12 @@ func (e *ObjectConsExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics if key.IsNull() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Null value as key", - Detail: "Can't use a null value as a key.", - Subject: item.ValueExpr.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Null value as key", + Detail: "Can't use a null value as a key.", + Subject: item.ValueExpr.Range().Ptr(), + Expression: item.KeyExpr, + EvalContext: ctx, }) known = false continue @@ -689,10 +724,12 @@ func (e *ObjectConsExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics key, err = convert.Convert(key, cty.String) if err != nil { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Incorrect key type", - Detail: fmt.Sprintf("Can't use this value as a key: %s.", err.Error()), - Subject: item.ValueExpr.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Incorrect key type", + Detail: fmt.Sprintf("Can't use this value as a key: %s.", err.Error()), + Subject: item.ValueExpr.Range().Ptr(), + Expression: item.ValueExpr, + EvalContext: ctx, }) known = false continue @@ -819,11 +856,13 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { if collVal.IsNull() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Iteration over null value", - Detail: "A null value cannot be used as the collection in a 'for' expression.", - Subject: e.CollExpr.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Iteration over null value", + Detail: "A null value cannot be used as the collection in a 'for' expression.", + Subject: e.CollExpr.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.CollExpr, + EvalContext: ctx, }) return cty.DynamicVal, diags } @@ -838,8 +877,10 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { "A value of type %s cannot be used as the collection in a 'for' expression.", collVal.Type().FriendlyName(), ), - Subject: e.CollExpr.Range().Ptr(), - Context: &e.SrcRange, + Subject: e.CollExpr.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.CollExpr, + EvalContext: ctx, }) return cty.DynamicVal, diags } @@ -847,14 +888,13 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { return cty.DynamicVal, diags } - childCtx := ctx.NewChild() - childCtx.Variables = map[string]cty.Value{} - // Before we start we'll do an early check to see if any CondExpr we've // been given is of the wrong type. This isn't 100% reliable (it may // be DynamicVal until real values are given) but it should catch some // straightforward cases and prevent a barrage of repeated errors. if e.CondExpr != nil { + childCtx := ctx.NewChild() + childCtx.Variables = map[string]cty.Value{} if e.KeyVar != "" { childCtx.Variables[e.KeyVar] = cty.DynamicVal } @@ -864,22 +904,26 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { diags = append(diags, condDiags...) if result.IsNull() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Condition is null", - Detail: "The value of the 'if' clause must not be null.", - Subject: e.CondExpr.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Condition is null", + Detail: "The value of the 'if' clause must not be null.", + Subject: e.CondExpr.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.CondExpr, + EvalContext: ctx, }) return cty.DynamicVal, diags } _, err := convert.Convert(result, cty.Bool) if err != nil { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid 'for' condition", - Detail: fmt.Sprintf("The 'if' clause value is invalid: %s.", err.Error()), - Subject: e.CondExpr.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Invalid 'for' condition", + Detail: fmt.Sprintf("The 'if' clause value is invalid: %s.", err.Error()), + Subject: e.CondExpr.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.CondExpr, + EvalContext: ctx, }) return cty.DynamicVal, diags } @@ -903,6 +947,8 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { known := true for it.Next() { k, v := it.Element() + childCtx := ctx.NewChild() + childCtx.Variables = map[string]cty.Value{} if e.KeyVar != "" { childCtx.Variables[e.KeyVar] = k } @@ -914,11 +960,13 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { if includeRaw.IsNull() { if known { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Condition is null", - Detail: "The value of the 'if' clause must not be null.", - Subject: e.CondExpr.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Invalid 'for' condition", + Detail: "The value of the 'if' clause must not be null.", + Subject: e.CondExpr.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.CondExpr, + EvalContext: childCtx, }) } known = false @@ -928,11 +976,13 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { if err != nil { if known { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid 'for' condition", - Detail: fmt.Sprintf("The 'if' clause value is invalid: %s.", err.Error()), - Subject: e.CondExpr.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Invalid 'for' condition", + Detail: fmt.Sprintf("The 'if' clause value is invalid: %s.", err.Error()), + Subject: e.CondExpr.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.CondExpr, + EvalContext: childCtx, }) } known = false @@ -954,11 +1004,13 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { if keyRaw.IsNull() { if known { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid object key", - Detail: "Key expression in 'for' expression must not produce a null value.", - Subject: e.KeyExpr.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Invalid object key", + Detail: "Key expression in 'for' expression must not produce a null value.", + Subject: e.KeyExpr.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.KeyExpr, + EvalContext: childCtx, }) } known = false @@ -973,11 +1025,13 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { if err != nil { if known { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid object key", - Detail: fmt.Sprintf("The key expression produced an invalid result: %s.", err.Error()), - Subject: e.KeyExpr.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Invalid object key", + Detail: fmt.Sprintf("The key expression produced an invalid result: %s.", err.Error()), + Subject: e.KeyExpr.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.KeyExpr, + EvalContext: childCtx, }) } known = false @@ -997,11 +1051,13 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { Severity: hcl.DiagError, Summary: "Duplicate object key", Detail: fmt.Sprintf( - "Two different items produced the key %q in this for expression. If duplicates are expected, use the ellipsis (...) after the value expression to enable grouping by key.", + "Two different items produced the key %q in this 'for' expression. If duplicates are expected, use the ellipsis (...) after the value expression to enable grouping by key.", k, ), - Subject: e.KeyExpr.Range().Ptr(), - Context: &e.SrcRange, + Subject: e.KeyExpr.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.KeyExpr, + EvalContext: childCtx, }) } else { vals[key.AsString()] = val @@ -1031,6 +1087,8 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { known := true for it.Next() { k, v := it.Element() + childCtx := ctx.NewChild() + childCtx.Variables = map[string]cty.Value{} if e.KeyVar != "" { childCtx.Variables[e.KeyVar] = k } @@ -1042,11 +1100,13 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { if includeRaw.IsNull() { if known { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Condition is null", - Detail: "The value of the 'if' clause must not be null.", - Subject: e.CondExpr.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Invalid 'for' condition", + Detail: "The value of the 'if' clause must not be null.", + Subject: e.CondExpr.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.CondExpr, + EvalContext: childCtx, }) } known = false @@ -1064,11 +1124,13 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { if err != nil { if known { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid 'for' condition", - Detail: fmt.Sprintf("The 'if' clause value is invalid: %s.", err.Error()), - Subject: e.CondExpr.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Invalid 'for' condition", + Detail: fmt.Sprintf("The 'if' clause value is invalid: %s.", err.Error()), + Subject: e.CondExpr.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.CondExpr, + EvalContext: childCtx, }) } known = false @@ -1154,11 +1216,13 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { if sourceVal.IsNull() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Splat of null value", - Detail: "Splat expressions (with the * symbol) cannot be applied to null values.", - Subject: e.Source.Range().Ptr(), - Context: hcl.RangeBetween(e.Source.Range(), e.MarkerRange).Ptr(), + Severity: hcl.DiagError, + Summary: "Splat of null value", + Detail: "Splat expressions (with the * symbol) cannot be applied to null values.", + Subject: e.Source.Range().Ptr(), + Context: hcl.RangeBetween(e.Source.Range(), e.MarkerRange).Ptr(), + Expression: e.Source, + EvalContext: ctx, }) return cty.DynamicVal, diags } diff --git a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/expression_ops.go b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/expression_ops.go index 9a5da043be..ee96a0d712 100644 --- a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/expression_ops.go +++ b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/expression_ops.go @@ -149,21 +149,25 @@ func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) lhsVal, err := convert.Convert(givenLHSVal, lhsParam.Type) if err != nil { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid operand", - Detail: fmt.Sprintf("Unsuitable value for left operand: %s.", err), - Subject: e.LHS.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Invalid operand", + Detail: fmt.Sprintf("Unsuitable value for left operand: %s.", err), + Subject: e.LHS.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.LHS, + EvalContext: ctx, }) } rhsVal, err := convert.Convert(givenRHSVal, rhsParam.Type) if err != nil { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid operand", - Detail: fmt.Sprintf("Unsuitable value for right operand: %s.", err), - Subject: e.RHS.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Invalid operand", + Detail: fmt.Sprintf("Unsuitable value for right operand: %s.", err), + Subject: e.RHS.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.RHS, + EvalContext: ctx, }) } @@ -178,10 +182,12 @@ func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) if err != nil { diags = append(diags, &hcl.Diagnostic{ // FIXME: This diagnostic is useless. - Severity: hcl.DiagError, - Summary: "Operation failed", - Detail: fmt.Sprintf("Error during operation: %s.", err), - Subject: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Operation failed", + Detail: fmt.Sprintf("Error during operation: %s.", err), + Subject: &e.SrcRange, + Expression: e, + EvalContext: ctx, }) return cty.UnknownVal(e.Op.Type), diags } @@ -219,11 +225,13 @@ func (e *UnaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { val, err := convert.Convert(givenVal, param.Type) if err != nil { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid operand", - Detail: fmt.Sprintf("Unsuitable value for unary operand: %s.", err), - Subject: e.Val.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Invalid operand", + Detail: fmt.Sprintf("Unsuitable value for unary operand: %s.", err), + Subject: e.Val.Range().Ptr(), + Context: &e.SrcRange, + Expression: e.Val, + EvalContext: ctx, }) } @@ -238,10 +246,12 @@ func (e *UnaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { if err != nil { diags = append(diags, &hcl.Diagnostic{ // FIXME: This diagnostic is useless. - Severity: hcl.DiagError, - Summary: "Operation failed", - Detail: fmt.Sprintf("Error during operation: %s.", err), - Subject: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Operation failed", + Detail: fmt.Sprintf("Error during operation: %s.", err), + Subject: &e.SrcRange, + Expression: e, + EvalContext: ctx, }) return cty.UnknownVal(e.Op.Type), diags } diff --git a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/expression_template.go b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/expression_template.go index a1c472754d..6aa6b60ff0 100644 --- a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/expression_template.go +++ b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/expression_template.go @@ -37,8 +37,10 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) Detail: fmt.Sprintf( "The expression result is null. Cannot include a null value in a string template.", ), - Subject: part.Range().Ptr(), - Context: &e.SrcRange, + Subject: part.Range().Ptr(), + Context: &e.SrcRange, + Expression: part, + EvalContext: ctx, }) continue } @@ -61,8 +63,10 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) "Cannot include the given value in a string template: %s.", err.Error(), ), - Subject: part.Range().Ptr(), - Context: &e.SrcRange, + Subject: part.Range().Ptr(), + Context: &e.SrcRange, + Expression: part, + EvalContext: ctx, }) continue } @@ -127,7 +131,9 @@ func (e *TemplateJoinExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti Detail: fmt.Sprintf( "An iteration result is null. Cannot include a null value in a string template.", ), - Subject: e.Range().Ptr(), + Subject: e.Range().Ptr(), + Expression: e, + EvalContext: ctx, }) continue } @@ -143,7 +149,9 @@ func (e *TemplateJoinExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti "Cannot include one of the interpolation results into the string template: %s.", err.Error(), ), - Subject: e.Range().Ptr(), + Subject: e.Range().Ptr(), + Expression: e, + EvalContext: ctx, }) continue } diff --git a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/structure.go b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/structure.go index d69f65b62e..089311df89 100644 --- a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/structure.go +++ b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/structure.go @@ -9,6 +9,10 @@ import ( // AsHCLBlock returns the block data expressed as a *hcl.Block. func (b *Block) AsHCLBlock() *hcl.Block { + if b == nil { + return nil + } + lastHeaderRange := b.TypeRange if len(b.LabelRanges) > 0 { lastHeaderRange = b.LabelRanges[len(b.LabelRanges)-1] @@ -326,6 +330,9 @@ func (a *Attribute) Range() hcl.Range { // AsHCLAttribute returns the block data expressed as a *hcl.Attribute. func (a *Attribute) AsHCLAttribute() *hcl.Attribute { + if a == nil { + return nil + } return &hcl.Attribute{ Name: a.Name, Expr: a.Expr, diff --git a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/structure_at_pos.go b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/structure_at_pos.go new file mode 100644 index 0000000000..d8f023ba05 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/structure_at_pos.go @@ -0,0 +1,118 @@ +package hclsyntax + +import ( + "github.com/hashicorp/hcl2/hcl" +) + +// ----------------------------------------------------------------------------- +// The methods in this file are all optional extension methods that serve to +// implement the methods of the same name on *hcl.File when its root body +// is provided by this package. +// ----------------------------------------------------------------------------- + +// BlocksAtPos implements the method of the same name for an *hcl.File that +// is backed by a *Body. +func (b *Body) BlocksAtPos(pos hcl.Pos) []*hcl.Block { + list, _ := b.blocksAtPos(pos, true) + return list +} + +// InnermostBlockAtPos implements the method of the same name for an *hcl.File +// that is backed by a *Body. +func (b *Body) InnermostBlockAtPos(pos hcl.Pos) *hcl.Block { + _, innermost := b.blocksAtPos(pos, false) + return innermost.AsHCLBlock() +} + +// OutermostBlockAtPos implements the method of the same name for an *hcl.File +// that is backed by a *Body. +func (b *Body) OutermostBlockAtPos(pos hcl.Pos) *hcl.Block { + return b.outermostBlockAtPos(pos).AsHCLBlock() +} + +// blocksAtPos is the internal engine of both BlocksAtPos and +// InnermostBlockAtPos, which both need to do the same logic but return a +// differently-shaped result. +// +// list is nil if makeList is false, avoiding an allocation. Innermost is +// always set, and if the returned list is non-nil it will always match the +// final element from that list. +func (b *Body) blocksAtPos(pos hcl.Pos, makeList bool) (list []*hcl.Block, innermost *Block) { + current := b + +Blocks: + for current != nil { + for _, block := range current.Blocks { + wholeRange := hcl.RangeBetween(block.TypeRange, block.CloseBraceRange) + if wholeRange.ContainsPos(pos) { + innermost = block + if makeList { + list = append(list, innermost.AsHCLBlock()) + } + current = block.Body + continue Blocks + } + } + + // If we fall out here then none of the current body's nested blocks + // contain the position we are looking for, and so we're done. + break + } + + return +} + +// outermostBlockAtPos is the internal version of OutermostBlockAtPos that +// returns a hclsyntax.Block rather than an hcl.Block, allowing for further +// analysis if necessary. +func (b *Body) outermostBlockAtPos(pos hcl.Pos) *Block { + // This is similar to blocksAtPos, but simpler because we know it only + // ever needs to search the first level of nested blocks. + + for _, block := range b.Blocks { + wholeRange := hcl.RangeBetween(block.TypeRange, block.CloseBraceRange) + if wholeRange.ContainsPos(pos) { + return block + } + } + + return nil +} + +// AttributeAtPos implements the method of the same name for an *hcl.File +// that is backed by a *Body. +func (b *Body) AttributeAtPos(pos hcl.Pos) *hcl.Attribute { + return b.attributeAtPos(pos).AsHCLAttribute() +} + +// attributeAtPos is the internal version of AttributeAtPos that returns a +// hclsyntax.Block rather than an hcl.Block, allowing for further analysis if +// necessary. +func (b *Body) attributeAtPos(pos hcl.Pos) *Attribute { + searchBody := b + _, block := b.blocksAtPos(pos, false) + if block != nil { + searchBody = block.Body + } + + for _, attr := range searchBody.Attributes { + if attr.SrcRange.ContainsPos(pos) { + return attr + } + } + + return nil +} + +// OutermostExprAtPos implements the method of the same name for an *hcl.File +// that is backed by a *Body. +func (b *Body) OutermostExprAtPos(pos hcl.Pos) hcl.Expression { + attr := b.attributeAtPos(pos) + if attr == nil { + return nil + } + if !attr.Expr.Range().ContainsPos(pos) { + return nil + } + return attr.Expr +} diff --git a/vendor/github.com/hashicorp/hcl2/hcl/json/structure.go b/vendor/github.com/hashicorp/hcl2/hcl/json/structure.go index 9e0c92a83b..1e9d4b5735 100644 --- a/vendor/github.com/hashicorp/hcl2/hcl/json/structure.go +++ b/vendor/github.com/hashicorp/hcl2/hcl/json/structure.go @@ -432,7 +432,8 @@ func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { Value: jsonAttr.Name, SrcRange: jsonAttr.NameRange, }}).Value(ctx) - val, valDiags := (&expression{src: jsonAttr.Value}).Value(ctx) + valExpr := &expression{src: jsonAttr.Value} + val, valDiags := valExpr.Value(ctx) diags = append(diags, nameDiags...) diags = append(diags, valDiags...) @@ -440,19 +441,23 @@ func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { name, err = convert.Convert(name, cty.String) if err != nil { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid object key expression", - Detail: fmt.Sprintf("Cannot use this expression as an object key: %s.", err), - Subject: &jsonAttr.NameRange, + Severity: hcl.DiagError, + Summary: "Invalid object key expression", + Detail: fmt.Sprintf("Cannot use this expression as an object key: %s.", err), + Subject: &jsonAttr.NameRange, + Expression: valExpr, + EvalContext: ctx, }) continue } if name.IsNull() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid object key expression", - Detail: "Cannot use null value as an object key.", - Subject: &jsonAttr.NameRange, + Severity: hcl.DiagError, + Summary: "Invalid object key expression", + Detail: "Cannot use null value as an object key.", + Subject: &jsonAttr.NameRange, + Expression: valExpr, + EvalContext: ctx, }) continue } @@ -471,10 +476,12 @@ func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { nameStr := name.AsString() if _, defined := attrs[nameStr]; defined { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Duplicate object attribute", - Detail: fmt.Sprintf("An attribute named %q was already defined at %s.", nameStr, attrRanges[nameStr]), - Subject: &jsonAttr.NameRange, + Severity: hcl.DiagError, + Summary: "Duplicate object attribute", + Detail: fmt.Sprintf("An attribute named %q was already defined at %s.", nameStr, attrRanges[nameStr]), + Subject: &jsonAttr.NameRange, + Expression: e, + EvalContext: ctx, }) continue } diff --git a/vendor/github.com/hashicorp/hcl2/hcl/pos.go b/vendor/github.com/hashicorp/hcl2/hcl/pos.go index 1a4b329dcf..6b7ec1d347 100644 --- a/vendor/github.com/hashicorp/hcl2/hcl/pos.go +++ b/vendor/github.com/hashicorp/hcl2/hcl/pos.go @@ -94,6 +94,16 @@ func RangeOver(a, b Range) Range { } } +// ContainsPos returns true if and only if the given position is contained within +// the receiving range. +// +// In the unlikely case that the line/column information disagree with the byte +// offset information in the given position or receiving range, the byte +// offsets are given priority. +func (r Range) ContainsPos(pos Pos) bool { + return r.ContainsOffset(pos.Byte) +} + // ContainsOffset returns true if and only if the given byte offset is within // the receiving Range. func (r Range) ContainsOffset(offset int) bool { diff --git a/vendor/github.com/hashicorp/hcl2/hcl/structure_at_pos.go b/vendor/github.com/hashicorp/hcl2/hcl/structure_at_pos.go new file mode 100644 index 0000000000..8521814e5f --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/hcl/structure_at_pos.go @@ -0,0 +1,117 @@ +package hcl + +// ----------------------------------------------------------------------------- +// The methods in this file all have the general pattern of making a best-effort +// to find one or more constructs that contain a given source position. +// +// These all operate by delegating to an optional method of the same name and +// signature on the file's root body, allowing each syntax to potentially +// provide its own implementations of these. For syntaxes that don't implement +// them, the result is always nil. +// ----------------------------------------------------------------------------- + +// BlocksAtPos attempts to find all of the blocks that contain the given +// position, ordered so that the outermost block is first and the innermost +// block is last. This is a best-effort method that may not be able to produce +// a complete result for all positions or for all HCL syntaxes. +// +// If the returned slice is non-empty, the first element is guaranteed to +// represent the same block as would be the result of OutermostBlockAtPos and +// the last element the result of InnermostBlockAtPos. However, the +// implementation may return two different objects describing the same block, +// so comparison by pointer identity is not possible. +// +// The result is nil if no blocks at all contain the given position. +func (f *File) BlocksAtPos(pos Pos) []*Block { + // The root body of the file must implement this interface in order + // to support BlocksAtPos. + type Interface interface { + BlocksAtPos(pos Pos) []*Block + } + + impl, ok := f.Body.(Interface) + if !ok { + return nil + } + return impl.BlocksAtPos(pos) +} + +// OutermostBlockAtPos attempts to find a top-level block in the receiving file +// that contains the given position. This is a best-effort method that may not +// be able to produce a result for all positions or for all HCL syntaxes. +// +// The result is nil if no single block could be selected for any reason. +func (f *File) OutermostBlockAtPos(pos Pos) *Block { + // The root body of the file must implement this interface in order + // to support OutermostBlockAtPos. + type Interface interface { + OutermostBlockAtPos(pos Pos) *Block + } + + impl, ok := f.Body.(Interface) + if !ok { + return nil + } + return impl.OutermostBlockAtPos(pos) +} + +// InnermostBlockAtPos attempts to find the most deeply-nested block in the +// receiving file that contains the given position. This is a best-effort +// method that may not be able to produce a result for all positions or for +// all HCL syntaxes. +// +// The result is nil if no single block could be selected for any reason. +func (f *File) InnermostBlockAtPos(pos Pos) *Block { + // The root body of the file must implement this interface in order + // to support InnermostBlockAtPos. + type Interface interface { + InnermostBlockAtPos(pos Pos) *Block + } + + impl, ok := f.Body.(Interface) + if !ok { + return nil + } + return impl.InnermostBlockAtPos(pos) +} + +// OutermostExprAtPos attempts to find an expression in the receiving file +// that contains the given position. This is a best-effort method that may not +// be able to produce a result for all positions or for all HCL syntaxes. +// +// Since expressions are often nested inside one another, this method returns +// the outermost "root" expression that is not contained by any other. +// +// The result is nil if no single expression could be selected for any reason. +func (f *File) OutermostExprAtPos(pos Pos) Expression { + // The root body of the file must implement this interface in order + // to support OutermostExprAtPos. + type Interface interface { + OutermostExprAtPos(pos Pos) Expression + } + + impl, ok := f.Body.(Interface) + if !ok { + return nil + } + return impl.OutermostExprAtPos(pos) +} + +// AttributeAtPos attempts to find an attribute definition in the receiving +// file that contains the given position. This is a best-effort method that may +// not be able to produce a result for all positions or for all HCL syntaxes. +// +// The result is nil if no single attribute could be selected for any reason. +func (f *File) AttributeAtPos(pos Pos) *Attribute { + // The root body of the file must implement this interface in order + // to support OutermostExprAtPos. + type Interface interface { + AttributeAtPos(pos Pos) *Attribute + } + + impl, ok := f.Body.(Interface) + if !ok { + return nil + } + return impl.AttributeAtPos(pos) +} diff --git a/vendor/github.com/hashicorp/hcl2/hcldec/spec.go b/vendor/github.com/hashicorp/hcl2/hcldec/spec.go index 0d1288ce0b..f9da7f65bc 100644 --- a/vendor/github.com/hashicorp/hcl2/hcldec/spec.go +++ b/vendor/github.com/hashicorp/hcl2/hcldec/spec.go @@ -3,6 +3,7 @@ package hcldec import ( "bytes" "fmt" + "sort" "github.com/hashicorp/hcl2/hcl" "github.com/zclconf/go-cty/cty" @@ -477,6 +478,44 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, blockLabels []blockLabe if len(elems) == 0 { ret = cty.ListValEmpty(s.Nested.impliedType()) } else { + // Since our target is a list, all of the decoded elements must have the + // same type or cty.ListVal will panic below. Different types can arise + // if there is an attribute spec of type cty.DynamicPseudoType in the + // nested spec; all given values must be convertable to a single type + // in order for the result to be considered valid. + etys := make([]cty.Type, len(elems)) + for i, v := range elems { + etys[i] = v.Type() + } + ety, convs := convert.UnifyUnsafe(etys) + if ety == cty.NilType { + // FIXME: This is a pretty terrible error message. + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName), + Detail: "Corresponding attributes in all blocks of this type must be the same.", + Subject: &sourceRanges[0], + }) + return cty.DynamicVal, diags + } + for i, v := range elems { + if convs[i] != nil { + newV, err := convs[i](v) + if err != nil { + // FIXME: This is a pretty terrible error message. + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName), + Detail: fmt.Sprintf("Block with index %d has inconsistent argument types: %s.", i, err), + Subject: &sourceRanges[i], + }) + // Bail early here so we won't panic below in cty.ListVal + return cty.DynamicVal, diags + } + elems[i] = newV + } + } + ret = cty.ListVal(elems) } @@ -508,6 +547,127 @@ func (s *BlockListSpec) sourceRange(content *hcl.BodyContent, blockLabels []bloc return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) } +// A BlockTupleSpec is a Spec that produces a cty tuple of the results of +// decoding all of the nested blocks of a given type, using a nested spec. +// +// This is similar to BlockListSpec, but it permits the nested blocks to have +// different result types in situations where cty.DynamicPseudoType attributes +// are present. +type BlockTupleSpec struct { + TypeName string + Nested Spec + MinItems int + MaxItems int +} + +func (s *BlockTupleSpec) visitSameBodyChildren(cb visitFunc) { + // leaf node ("Nested" does not use the same body) +} + +// blockSpec implementation +func (s *BlockTupleSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema { + return []hcl.BlockHeaderSchema{ + { + Type: s.TypeName, + LabelNames: findLabelSpecs(s.Nested), + }, + } +} + +// blockSpec implementation +func (s *BlockTupleSpec) nestedSpec() Spec { + return s.Nested +} + +// specNeedingVariables implementation +func (s *BlockTupleSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { + var ret []hcl.Traversal + + for _, childBlock := range content.Blocks { + if childBlock.Type != s.TypeName { + continue + } + + ret = append(ret, Variables(childBlock.Body, s.Nested)...) + } + + return ret +} + +func (s *BlockTupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { + var diags hcl.Diagnostics + + if s.Nested == nil { + panic("BlockListSpec with no Nested Spec") + } + + var elems []cty.Value + var sourceRanges []hcl.Range + for _, childBlock := range content.Blocks { + if childBlock.Type != s.TypeName { + continue + } + + val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false) + diags = append(diags, childDiags...) + elems = append(elems, val) + sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)) + } + + if len(elems) < s.MinItems { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Insufficient %s blocks", s.TypeName), + Detail: fmt.Sprintf("At least %d %q blocks are required.", s.MinItems, s.TypeName), + Subject: &content.MissingItemRange, + }) + } else if s.MaxItems > 0 && len(elems) > s.MaxItems { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Too many %s blocks", s.TypeName), + Detail: fmt.Sprintf("No more than %d %q blocks are allowed", s.MaxItems, s.TypeName), + Subject: &sourceRanges[s.MaxItems], + }) + } + + var ret cty.Value + + if len(elems) == 0 { + ret = cty.EmptyTupleVal + } else { + ret = cty.TupleVal(elems) + } + + return ret, diags +} + +func (s *BlockTupleSpec) impliedType() cty.Type { + // We can't predict our type, because we don't know how many blocks + // there will be until we decode. + return cty.DynamicPseudoType +} + +func (s *BlockTupleSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { + // We return the source range of the _first_ block of the given type, + // since they are not guaranteed to form a contiguous range. + + var childBlock *hcl.Block + for _, candidate := range content.Blocks { + if candidate.Type != s.TypeName { + continue + } + + childBlock = candidate + break + } + + if childBlock == nil { + return content.MissingItemRange + } + + return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) +} + // A BlockSetSpec is a Spec that produces a cty set of the results of // decoding all of the nested blocks of a given type, using a nested spec. type BlockSetSpec struct { @@ -592,6 +752,44 @@ func (s *BlockSetSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel if len(elems) == 0 { ret = cty.SetValEmpty(s.Nested.impliedType()) } else { + // Since our target is a set, all of the decoded elements must have the + // same type or cty.SetVal will panic below. Different types can arise + // if there is an attribute spec of type cty.DynamicPseudoType in the + // nested spec; all given values must be convertable to a single type + // in order for the result to be considered valid. + etys := make([]cty.Type, len(elems)) + for i, v := range elems { + etys[i] = v.Type() + } + ety, convs := convert.UnifyUnsafe(etys) + if ety == cty.NilType { + // FIXME: This is a pretty terrible error message. + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName), + Detail: "Corresponding attributes in all blocks of this type must be the same.", + Subject: &sourceRanges[0], + }) + return cty.DynamicVal, diags + } + for i, v := range elems { + if convs[i] != nil { + newV, err := convs[i](v) + if err != nil { + // FIXME: This is a pretty terrible error message. + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName), + Detail: fmt.Sprintf("Block with index %d has inconsistent argument types: %s.", i, err), + Subject: &sourceRanges[i], + }) + // Bail early here so we won't panic below in cty.ListVal + return cty.DynamicVal, diags + } + elems[i] = newV + } + } + ret = cty.SetVal(elems) } @@ -672,7 +870,10 @@ func (s *BlockMapSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel var diags hcl.Diagnostics if s.Nested == nil { - panic("BlockSetSpec with no Nested Spec") + panic("BlockMapSpec with no Nested Spec") + } + if ImpliedType(s).HasDynamicTypes() { + panic("cty.DynamicPseudoType attributes may not be used inside a BlockMapSpec") } elems := map[string]interface{}{} @@ -765,6 +966,307 @@ func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, blockLabels []block return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) } +// A BlockObjectSpec is a Spec that produces a cty object of the results of +// decoding all of the nested blocks of a given type, using a nested spec. +// +// One level of object structure is created for each of the given label names. +// There must be at least one given label name. +// +// This is similar to BlockMapSpec, but it permits the nested blocks to have +// different result types in situations where cty.DynamicPseudoType attributes +// are present. +type BlockObjectSpec struct { + TypeName string + LabelNames []string + Nested Spec +} + +func (s *BlockObjectSpec) visitSameBodyChildren(cb visitFunc) { + // leaf node ("Nested" does not use the same body) +} + +// blockSpec implementation +func (s *BlockObjectSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema { + return []hcl.BlockHeaderSchema{ + { + Type: s.TypeName, + LabelNames: append(s.LabelNames, findLabelSpecs(s.Nested)...), + }, + } +} + +// blockSpec implementation +func (s *BlockObjectSpec) nestedSpec() Spec { + return s.Nested +} + +// specNeedingVariables implementation +func (s *BlockObjectSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { + var ret []hcl.Traversal + + for _, childBlock := range content.Blocks { + if childBlock.Type != s.TypeName { + continue + } + + ret = append(ret, Variables(childBlock.Body, s.Nested)...) + } + + return ret +} + +func (s *BlockObjectSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { + var diags hcl.Diagnostics + + if s.Nested == nil { + panic("BlockObjectSpec with no Nested Spec") + } + + elems := map[string]interface{}{} + for _, childBlock := range content.Blocks { + if childBlock.Type != s.TypeName { + continue + } + + childLabels := labelsForBlock(childBlock) + val, _, childDiags := decode(childBlock.Body, childLabels[len(s.LabelNames):], ctx, s.Nested, false) + targetMap := elems + for _, key := range childBlock.Labels[:len(s.LabelNames)-1] { + if _, exists := targetMap[key]; !exists { + targetMap[key] = make(map[string]interface{}) + } + targetMap = targetMap[key].(map[string]interface{}) + } + + diags = append(diags, childDiags...) + + key := childBlock.Labels[len(s.LabelNames)-1] + if _, exists := targetMap[key]; exists { + labelsBuf := bytes.Buffer{} + for _, label := range childBlock.Labels { + fmt.Fprintf(&labelsBuf, " %q", label) + } + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Duplicate %s block", s.TypeName), + Detail: fmt.Sprintf( + "A block for %s%s was already defined. The %s labels must be unique.", + s.TypeName, labelsBuf.String(), s.TypeName, + ), + Subject: &childBlock.DefRange, + }) + continue + } + + targetMap[key] = val + } + + if len(elems) == 0 { + return cty.EmptyObjectVal, diags + } + + var ctyObj func(map[string]interface{}, int) cty.Value + ctyObj = func(raw map[string]interface{}, depth int) cty.Value { + vals := make(map[string]cty.Value, len(raw)) + if depth == 1 { + for k, v := range raw { + vals[k] = v.(cty.Value) + } + } else { + for k, v := range raw { + vals[k] = ctyObj(v.(map[string]interface{}), depth-1) + } + } + return cty.ObjectVal(vals) + } + + return ctyObj(elems, len(s.LabelNames)), diags +} + +func (s *BlockObjectSpec) impliedType() cty.Type { + // We can't predict our type, since we don't know how many blocks are + // present and what labels they have until we decode. + return cty.DynamicPseudoType +} + +func (s *BlockObjectSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { + // We return the source range of the _first_ block of the given type, + // since they are not guaranteed to form a contiguous range. + + var childBlock *hcl.Block + for _, candidate := range content.Blocks { + if candidate.Type != s.TypeName { + continue + } + + childBlock = candidate + break + } + + if childBlock == nil { + return content.MissingItemRange + } + + return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) +} + +// A BlockAttrsSpec is a Spec that interprets a single block as if it were +// a map of some element type. That is, each attribute within the block +// becomes a key in the resulting map and the attribute's value becomes the +// element value, after conversion to the given element type. The resulting +// value is a cty.Map of the given element type. +// +// This spec imposes a validation constraint that there be exactly one block +// of the given type name and that this block may contain only attributes. The +// block does not accept any labels. +// +// This is an alternative to an AttrSpec of a map type for situations where +// block syntax is desired. Note that block syntax does not permit dynamic +// keys, construction of the result via a "for" expression, etc. In most cases +// an AttrSpec is preferred if the desired result is a map whose keys are +// chosen by the user rather than by schema. +type BlockAttrsSpec struct { + TypeName string + ElementType cty.Type + Required bool +} + +func (s *BlockAttrsSpec) visitSameBodyChildren(cb visitFunc) { + // leaf node +} + +// blockSpec implementation +func (s *BlockAttrsSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema { + return []hcl.BlockHeaderSchema{ + { + Type: s.TypeName, + LabelNames: nil, + }, + } +} + +// blockSpec implementation +func (s *BlockAttrsSpec) nestedSpec() Spec { + // This is an odd case: we aren't actually going to apply a nested spec + // in this case, since we're going to interpret the body directly as + // attributes, but we need to return something non-nil so that the + // decoder will recognize this as a block spec. We won't actually be + // using this for anything at decode time. + return noopSpec{} +} + +// specNeedingVariables implementation +func (s *BlockAttrsSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { + + block, _ := s.findBlock(content) + if block == nil { + return nil + } + + var vars []hcl.Traversal + + attrs, diags := block.Body.JustAttributes() + if diags.HasErrors() { + return nil + } + + for _, attr := range attrs { + vars = append(vars, attr.Expr.Variables()...) + } + + // We'll return the variables references in source order so that any + // error messages that result are also in source order. + sort.Slice(vars, func(i, j int) bool { + return vars[i].SourceRange().Start.Byte < vars[j].SourceRange().Start.Byte + }) + + return vars +} + +func (s *BlockAttrsSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { + var diags hcl.Diagnostics + + block, other := s.findBlock(content) + if block == nil { + if s.Required { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Missing %s block", s.TypeName), + Detail: fmt.Sprintf( + "A block of type %q is required here.", s.TypeName, + ), + Subject: &content.MissingItemRange, + }) + } + return cty.NullVal(cty.Map(s.ElementType)), diags + } + if other != nil { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Duplicate %s block", s.TypeName), + Detail: fmt.Sprintf( + "Only one block of type %q is allowed. Previous definition was at %s.", + s.TypeName, block.DefRange.String(), + ), + Subject: &other.DefRange, + }) + } + + attrs, attrDiags := block.Body.JustAttributes() + diags = append(diags, attrDiags...) + + if len(attrs) == 0 { + return cty.MapValEmpty(s.ElementType), diags + } + + vals := make(map[string]cty.Value, len(attrs)) + for name, attr := range attrs { + attrVal, attrDiags := attr.Expr.Value(ctx) + diags = append(diags, attrDiags...) + + attrVal, err := convert.Convert(attrVal, s.ElementType) + if err != nil { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid attribute value", + Detail: fmt.Sprintf("Invalid value for attribute of %q block: %s.", s.TypeName, err), + Subject: attr.Expr.Range().Ptr(), + }) + attrVal = cty.UnknownVal(s.ElementType) + } + + vals[name] = attrVal + } + + return cty.MapVal(vals), diags +} + +func (s *BlockAttrsSpec) impliedType() cty.Type { + return cty.Map(s.ElementType) +} + +func (s *BlockAttrsSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { + block, _ := s.findBlock(content) + if block == nil { + return content.MissingItemRange + } + return block.DefRange +} + +func (s *BlockAttrsSpec) findBlock(content *hcl.BodyContent) (block *hcl.Block, other *hcl.Block) { + for _, candidate := range content.Blocks { + if candidate.Type != s.TypeName { + continue + } + if block != nil { + return block, candidate + } + block = candidate + } + + return block, nil +} + // A BlockLabelSpec is a Spec that returns a cty.String representing the // label of the block its given body belongs to, if indeed its given body // belongs to a block. It is a programming error to use this in a non-block @@ -1038,3 +1540,28 @@ func (s *TransformFuncSpec) sourceRange(content *hcl.BodyContent, blockLabels [] // not super-accurate, because there's nothing better to return. return s.Wrapped.sourceRange(content, blockLabels) } + +// noopSpec is a placeholder spec that does nothing, used in situations where +// a non-nil placeholder spec is required. It is not exported because there is +// no reason to use it directly; it is always an implementation detail only. +type noopSpec struct { +} + +func (s noopSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { + return cty.NullVal(cty.DynamicPseudoType), nil +} + +func (s noopSpec) impliedType() cty.Type { + return cty.DynamicPseudoType +} + +func (s noopSpec) visitSameBodyChildren(cb visitFunc) { + // nothing to do +} + +func (s noopSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { + // No useful range for a noopSpec, and nobody should be calling this anyway. + return hcl.Range{ + Filename: "noopSpec", + } +} diff --git a/vendor/github.com/hashicorp/hcl2/hclwrite/ast.go b/vendor/github.com/hashicorp/hcl2/hclwrite/ast.go index 38ec090886..8bf7331b79 100644 --- a/vendor/github.com/hashicorp/hcl2/hclwrite/ast.go +++ b/vendor/github.com/hashicorp/hcl2/hclwrite/ast.go @@ -3,29 +3,25 @@ package hclwrite import ( "bytes" "io" - - "github.com/hashicorp/hcl2/hcl" - "github.com/zclconf/go-cty/cty" ) -type Node interface { - walkChildNodes(w internalWalkFunc) - Tokens() *TokenSeq -} - -type internalWalkFunc func(Node) - type File struct { - Name string - SrcBytes []byte + inTree - Body *Body - AllTokens *TokenSeq + srcBytes []byte + body *node +} + +// Body returns the root body of the file, which contains the top-level +// attributes and blocks. +func (f *File) Body() *Body { + return f.body.content.(*Body) } // WriteTo writes the tokens underlying the receiving file to the given writer. func (f *File) WriteTo(wr io.Writer) (int, error) { - return f.AllTokens.WriteTo(wr) + tokens := f.inTree.children.BuildTokens(nil) + return tokens.WriteTo(wr) } // Bytes returns a buffer containing the source code resulting from the @@ -37,169 +33,74 @@ func (f *File) Bytes() []byte { return buf.Bytes() } -// Format makes in-place modifications to the tokens underlying the receiving -// file in order to change the whitespace to be in canonical form. -func (f *File) Format() { - format(f.Body.AllTokens.Tokens()) -} - -type Body struct { - // Items may contain Attribute, Block and Unstructured instances. - // Items and AllTokens should be updated only by methods of this type, - // since they must be kept synchronized for correct operation. - Items []Node - AllTokens *TokenSeq - - // IndentLevel is the number of spaces that should appear at the start - // of lines added within this body. - IndentLevel int -} - -func (n *Body) walkChildNodes(w internalWalkFunc) { - for _, item := range n.Items { - w(item) - } -} - -func (n *Body) Tokens() *TokenSeq { - return n.AllTokens -} +type comments struct { + leafNode -func (n *Body) AppendItem(node Node) { - n.Items = append(n.Items, node) - n.AppendUnstructuredTokens(node.Tokens()) + parent *node + tokens Tokens } -func (n *Body) AppendUnstructuredTokens(seq *TokenSeq) { - if n.AllTokens == nil { - new := make(TokenSeq, 0, 1) - n.AllTokens = &new +func newComments(tokens Tokens) *comments { + return &comments{ + tokens: tokens, } - *(n.AllTokens) = append(*(n.AllTokens), seq) -} - -// FindAttribute returns the first attribute item from the body that has the -// given name, or returns nil if there is currently no matching attribute. -// -// A valid AST has only one definition of each attribute, but that constraint -// is not enforced in the hclwrite AST, so a tree that has been mutated by -// other calls may contain additional matching attributes that cannot be seen -// by this method. -func (n *Body) FindAttribute(name string) *Attribute { - nameBytes := []byte(name) - for _, item := range n.Items { - if attr, ok := item.(*Attribute); ok { - if attr.NameTokens.IsIdent(nameBytes) { - return attr - } - } - } - return nil -} - -// SetAttributeValue either replaces the expression of an existing attribute -// of the given name or adds a new attribute definition to the end of the block. -// -// The value is given as a cty.Value, and must therefore be a literal. To set -// a variable reference or other traversal, use SetAttributeTraversal. -// -// The return value is the attribute that was either modified in-place or -// created. -func (n *Body) SetAttributeValue(name string, val cty.Value) *Attribute { - panic("Body.SetAttributeValue not yet implemented") } -// SetAttributeTraversal either replaces the expression of an existing attribute -// of the given name or adds a new attribute definition to the end of the block. -// -// The new expression is given as a hcl.Traversal, which must be an absolute -// traversal. To set a literal value, use SetAttributeValue. -// -// The return value is the attribute that was either modified in-place or -// created. -func (n *Body) SetAttributeTraversal(name string, traversal hcl.Traversal) *Attribute { - panic("Body.SetAttributeTraversal not yet implemented") +func (c *comments) BuildTokens(to Tokens) Tokens { + return c.tokens.BuildTokens(to) } -type Attribute struct { - AllTokens *TokenSeq - - LeadCommentTokens *TokenSeq - NameTokens *TokenSeq - EqualsTokens *TokenSeq - Expr *Expression - LineCommentTokens *TokenSeq - EOLTokens *TokenSeq -} +type identifier struct { + leafNode -func (a *Attribute) walkChildNodes(w internalWalkFunc) { - w(a.Expr) + parent *node + token *Token } -func (n *Attribute) Tokens() *TokenSeq { - return n.AllTokens +func newIdentifier(token *Token) *identifier { + return &identifier{ + token: token, + } } -type Block struct { - AllTokens *TokenSeq - - LeadCommentTokens *TokenSeq - TypeTokens *TokenSeq - LabelTokens []*TokenSeq - LabelTokensFlat *TokenSeq - OBraceTokens *TokenSeq - Body *Body - CBraceTokens *TokenSeq - EOLTokens *TokenSeq +func (i *identifier) BuildTokens(to Tokens) Tokens { + return append(to, i.token) } -func (n *Block) walkChildNodes(w internalWalkFunc) { - w(n.Body) +func (i *identifier) hasName(name string) bool { + return name == string(i.token.Bytes) } -func (n *Block) Tokens() *TokenSeq { - return n.AllTokens -} +type number struct { + leafNode -type Expression struct { - AllTokens *TokenSeq - AbsTraversals []*Traversal + parent *node + token *Token } -func (n *Expression) walkChildNodes(w internalWalkFunc) { - for _, name := range n.AbsTraversals { - w(name) +func newNumber(token *Token) *number { + return &number{ + token: token, } } -func (n *Expression) Tokens() *TokenSeq { - return n.AllTokens +func (n *number) BuildTokens(to Tokens) Tokens { + return append(to, n.token) } -type Traversal struct { - AllTokens *TokenSeq - Steps []*Traverser -} - -func (n *Traversal) walkChildNodes(w internalWalkFunc) { - for _, step := range n.Steps { - w(step) - } -} +type quoted struct { + leafNode -func (n *Traversal) Tokens() *TokenSeq { - return n.AllTokens + parent *node + tokens Tokens } -type Traverser struct { - AllTokens *TokenSeq - Logical hcl.Traverser -} - -func (n *Traverser) Tokens() *TokenSeq { - return n.AllTokens +func newQuoted(tokens Tokens) *quoted { + return "ed{ + tokens: tokens, + } } -func (n *Traverser) walkChildNodes(w internalWalkFunc) { - // No child nodes for a traversal step +func (q *quoted) BuildTokens(to Tokens) Tokens { + return q.tokens.BuildTokens(to) } diff --git a/vendor/github.com/hashicorp/hcl2/hclwrite/ast_body.go b/vendor/github.com/hashicorp/hcl2/hclwrite/ast_body.go new file mode 100644 index 0000000000..b0c257c39c --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/hclwrite/ast_body.go @@ -0,0 +1,137 @@ +package hclwrite + +import ( + "github.com/hashicorp/hcl2/hcl" + "github.com/hashicorp/hcl2/hcl/hclsyntax" + "github.com/zclconf/go-cty/cty" +) + +type Body struct { + inTree + + items nodeSet + + // indentLevel is the number of spaces that should appear at the start + // of lines added within this body. + indentLevel int +} + +func (b *Body) appendItem(c nodeContent) *node { + nn := b.children.Append(c) + b.items.Add(nn) + return nn +} + +func (b *Body) appendItemNode(nn *node) *node { + nn.assertUnattached() + b.children.AppendNode(nn) + b.items.Add(nn) + return nn +} + +func (b *Body) AppendUnstructuredTokens(ts Tokens) { + b.inTree.children.Append(ts) +} + +// GetAttribute returns the attribute from the body that has the given name, +// or returns nil if there is currently no matching attribute. +func (b *Body) GetAttribute(name string) *Attribute { + for n := range b.items { + if attr, isAttr := n.content.(*Attribute); isAttr { + nameObj := attr.name.content.(*identifier) + if nameObj.hasName(name) { + // We've found it! + return attr + } + } + } + + return nil +} + +// SetAttributeValue either replaces the expression of an existing attribute +// of the given name or adds a new attribute definition to the end of the block. +// +// The value is given as a cty.Value, and must therefore be a literal. To set +// a variable reference or other traversal, use SetAttributeTraversal. +// +// The return value is the attribute that was either modified in-place or +// created. +func (b *Body) SetAttributeValue(name string, val cty.Value) *Attribute { + attr := b.GetAttribute(name) + expr := NewExpressionLiteral(val) + if attr != nil { + attr.expr = attr.expr.ReplaceWith(expr) + } else { + attr := newAttribute() + attr.init(name, expr) + b.appendItem(attr) + } + return attr +} + +// SetAttributeTraversal either replaces the expression of an existing attribute +// of the given name or adds a new attribute definition to the end of the block. +// +// The new expression is given as a hcl.Traversal, which must be an absolute +// traversal. To set a literal value, use SetAttributeValue. +// +// The return value is the attribute that was either modified in-place or +// created. +func (b *Body) SetAttributeTraversal(name string, traversal hcl.Traversal) *Attribute { + panic("Body.SetAttributeTraversal not yet implemented") +} + +type Attribute struct { + inTree + + leadComments *node + name *node + expr *node + lineComments *node +} + +func newAttribute() *Attribute { + return &Attribute{ + inTree: newInTree(), + } +} + +func (a *Attribute) init(name string, expr *Expression) { + expr.assertUnattached() + + nameTok := newIdentToken(name) + nameObj := newIdentifier(nameTok) + a.leadComments = a.children.Append(newComments(nil)) + a.name = a.children.Append(nameObj) + a.children.AppendUnstructuredTokens(Tokens{ + { + Type: hclsyntax.TokenEqual, + Bytes: []byte{'='}, + }, + }) + a.expr = a.children.Append(expr) + a.expr.list = a.children + a.lineComments = a.children.Append(newComments(nil)) + a.children.AppendUnstructuredTokens(Tokens{ + { + Type: hclsyntax.TokenNewline, + Bytes: []byte{'\n'}, + }, + }) +} + +func (a *Attribute) Expr() *Expression { + return a.expr.content.(*Expression) +} + +type Block struct { + inTree + + leadComments *node + typeName *node + labels nodeSet + open *node + body *node + close *node +} diff --git a/vendor/github.com/hashicorp/hcl2/hclwrite/ast_expression.go b/vendor/github.com/hashicorp/hcl2/hclwrite/ast_expression.go new file mode 100644 index 0000000000..cdd6e48f24 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/hclwrite/ast_expression.go @@ -0,0 +1,82 @@ +package hclwrite + +import ( + "github.com/hashicorp/hcl2/hcl" + "github.com/zclconf/go-cty/cty" +) + +type Expression struct { + inTree + + absTraversals nodeSet +} + +func newExpression() *Expression { + return &Expression{ + inTree: newInTree(), + absTraversals: newNodeSet(), + } +} + +// NewExpressionLiteral constructs an an expression that represents the given +// literal value. +// +// Since an unknown value cannot be represented in source code, this function +// will panic if the given value is unknown or contains a nested unknown value. +// Use val.IsWhollyKnown before calling to be sure. +// +// HCL native syntax does not directly represent lists, maps, and sets, and +// instead relies on the automatic conversions to those collection types from +// either list or tuple constructor syntax. Therefore converting collection +// values to source code and re-reading them will lose type information, and +// the reader must provide a suitable type at decode time to recover the +// original value. +func NewExpressionLiteral(val cty.Value) *Expression { + toks := TokensForValue(val) + expr := newExpression() + expr.children.AppendUnstructuredTokens(toks) + return expr +} + +// NewExpressionAbsTraversal constructs an expression that represents the +// given traversal, which must be absolute or this function will panic. +func NewExpressionAbsTraversal(traversal hcl.Traversal) *Expression { + panic("NewExpressionAbsTraversal not yet implemented") +} + +type Traversal struct { + inTree + + steps nodeSet +} + +func newTraversal() *Traversal { + return &Traversal{ + inTree: newInTree(), + steps: newNodeSet(), + } +} + +type TraverseName struct { + inTree + + name *node +} + +func newTraverseName() *TraverseName { + return &TraverseName{ + inTree: newInTree(), + } +} + +type TraverseIndex struct { + inTree + + key *node +} + +func newTraverseIndex() *TraverseIndex { + return &TraverseIndex{ + inTree: newInTree(), + } +} diff --git a/vendor/github.com/hashicorp/hcl2/hclwrite/node.go b/vendor/github.com/hashicorp/hcl2/hclwrite/node.go new file mode 100644 index 0000000000..674b830d23 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/hclwrite/node.go @@ -0,0 +1,231 @@ +package hclwrite + +import ( + "fmt" + + "github.com/google/go-cmp/cmp" +) + +// node represents a node in the AST. +type node struct { + content nodeContent + + list *nodes + before, after *node +} + +func newNode(c nodeContent) *node { + return &node{ + content: c, + } +} + +func (n *node) Equal(other *node) bool { + return cmp.Equal(n.content, other.content) +} + +func (n *node) BuildTokens(to Tokens) Tokens { + return n.content.BuildTokens(to) +} + +// Detach removes the receiver from the list it currently belongs to. If the +// node is not currently in a list, this is a no-op. +func (n *node) Detach() { + if n.list == nil { + return + } + if n.before != nil { + n.before.after = n.after + } + if n.after != nil { + n.after.before = n.before + } + if n.list.first == n { + n.list.first = n.after + } + if n.list.last == n { + n.list.last = n.before + } + n.list = nil + n.before = nil + n.after = nil +} + +// ReplaceWith removes the receiver from the list it currently belongs to and +// inserts a new node with the given content in its place. If the node is not +// currently in a list, this function will panic. +// +// The return value is the newly-constructed node, containing the given content. +// After this function returns, the reciever is no longer attached to a list. +func (n *node) ReplaceWith(c nodeContent) *node { + if n.list == nil { + panic("can't replace node that is not in a list") + } + + before := n.before + after := n.after + list := n.list + n.before, n.after, n.list = nil, nil, nil + + nn := newNode(c) + nn.before = before + nn.after = after + nn.list = list + if before != nil { + before.after = nn + } + if after != nil { + after.before = nn + } + return nn +} + +func (n *node) assertUnattached() { + if n.list != nil { + panic(fmt.Sprintf("attempt to attach already-attached node %#v", n)) + } +} + +// nodeContent is the interface type implemented by all AST content types. +type nodeContent interface { + walkChildNodes(w internalWalkFunc) + BuildTokens(to Tokens) Tokens +} + +// nodes is a list of nodes. +type nodes struct { + first, last *node +} + +func (ns *nodes) BuildTokens(to Tokens) Tokens { + for n := ns.first; n != nil; n = n.after { + to = n.BuildTokens(to) + } + return to +} + +func (ns *nodes) Append(c nodeContent) *node { + n := &node{ + content: c, + } + ns.AppendNode(n) + n.list = ns + return n +} + +func (ns *nodes) AppendNode(n *node) { + if ns.last != nil { + n.before = ns.last + ns.last.after = n + } + n.list = ns + ns.last = n + if ns.first == nil { + ns.first = n + } +} + +func (ns *nodes) AppendUnstructuredTokens(tokens Tokens) *node { + if len(tokens) == 0 { + return nil + } + n := newNode(tokens) + ns.AppendNode(n) + n.list = ns + return n +} + +// nodeSet is an unordered set of nodes. It is used to describe a set of nodes +// that all belong to the same list that have some role or characteristic +// in common. +type nodeSet map[*node]struct{} + +func newNodeSet() nodeSet { + return make(nodeSet) +} + +func (ns nodeSet) Has(n *node) bool { + if ns == nil { + return false + } + _, exists := ns[n] + return exists +} + +func (ns nodeSet) Add(n *node) { + ns[n] = struct{}{} +} + +func (ns nodeSet) Remove(n *node) { + delete(ns, n) +} + +func (ns nodeSet) List() []*node { + if len(ns) == 0 { + return nil + } + + ret := make([]*node, 0, len(ns)) + + // Determine which list we are working with. We assume here that all of + // the nodes belong to the same list, since that is part of the contract + // for nodeSet. + var list *nodes + for n := range ns { + list = n.list + break + } + + // We recover the order by iterating over the whole list. This is not + // the most efficient way to do it, but our node lists should always be + // small so not worth making things more complex. + for n := list.first; n != nil; n = n.after { + if ns.Has(n) { + ret = append(ret, n) + } + } + return ret +} + +type internalWalkFunc func(*node) + +// inTree can be embedded into a content struct that has child nodes to get +// a standard implementation of the NodeContent interface and a record of +// a potential parent node. +type inTree struct { + parent *node + children *nodes +} + +func newInTree() inTree { + return inTree{ + children: &nodes{}, + } +} + +func (it *inTree) assertUnattached() { + if it.parent != nil { + panic(fmt.Sprintf("node is already attached to %T", it.parent.content)) + } +} + +func (it *inTree) walkChildNodes(w internalWalkFunc) { + for n := it.children.first; n != nil; n = n.after { + w(n) + } +} + +func (it *inTree) BuildTokens(to Tokens) Tokens { + for n := it.children.first; n != nil; n = n.after { + to = n.BuildTokens(to) + } + return to +} + +// leafNode can be embedded into a content struct to give it a do-nothing +// implementation of walkChildNodes +type leafNode struct { +} + +func (n *leafNode) walkChildNodes(w internalWalkFunc) { +} diff --git a/vendor/github.com/hashicorp/hcl2/hclwrite/parser.go b/vendor/github.com/hashicorp/hcl2/hclwrite/parser.go index 52891c8b09..75afe2cf9d 100644 --- a/vendor/github.com/hashicorp/hcl2/hclwrite/parser.go +++ b/vendor/github.com/hashicorp/hcl2/hclwrite/parser.go @@ -1,10 +1,12 @@ package hclwrite import ( + "fmt" "sort" "github.com/hashicorp/hcl2/hcl" "github.com/hashicorp/hcl2/hcl/hclsyntax" + "github.com/zclconf/go-cty/cty" ) // Our "parser" here is actually not doing any parsing of its own. Instead, @@ -49,18 +51,19 @@ func parse(src []byte, filename string, start hcl.Pos) (*File, hcl.Diagnostics) } before, root, after := parseBody(file.Body.(*hclsyntax.Body), from) + ret := &File{ + inTree: newInTree(), - return &File{ - Name: filename, - SrcBytes: src, - - Body: root, - AllTokens: &TokenSeq{ - before.Seq(), - root.AllTokens, - after.Seq(), - }, - }, nil + srcBytes: src, + body: root, + } + + nodes := ret.inTree.children + nodes.Append(before.Tokens()) + nodes.AppendNode(root) + nodes.Append(after.Tokens()) + + return ret, diags } type inputTokens struct { @@ -76,6 +79,23 @@ func (it inputTokens) Partition(rng hcl.Range) (before, within, after inputToken return } +func (it inputTokens) PartitionType(ty hclsyntax.TokenType) (before, within, after inputTokens) { + for i, t := range it.writerTokens { + if t.Type == ty { + return it.Slice(0, i), it.Slice(i, i+1), it.Slice(i+1, len(it.nativeTokens)) + } + } + panic(fmt.Sprintf("didn't find any token of type %s", ty)) +} + +func (it inputTokens) PartitionTypeSingle(ty hclsyntax.TokenType) (before inputTokens, found *Token, after inputTokens) { + before, within, after := it.PartitionType(ty) + if within.Len() != 1 { + panic("PartitionType found more than one token") + } + return before, within.Tokens()[0], after +} + // PartitionIncludeComments is like Partition except the returned "within" // range includes any lead and line comments associated with the range. func (it inputTokens) PartitionIncludingComments(rng hcl.Range) (before, within, after inputTokens) { @@ -133,8 +153,8 @@ func (it inputTokens) Len() int { return len(it.nativeTokens) } -func (it inputTokens) Seq() *TokenSeq { - return &TokenSeq{it.writerTokens} +func (it inputTokens) Tokens() Tokens { + return it.writerTokens } func (it inputTokens) Types() []hclsyntax.TokenType { @@ -148,7 +168,7 @@ func (it inputTokens) Types() []hclsyntax.TokenType { // parseBody locates the given body within the given input tokens and returns // the resulting *Body object as well as the tokens that appeared before and // after it. -func parseBody(nativeBody *hclsyntax.Body, from inputTokens) (inputTokens, *Body, inputTokens) { +func parseBody(nativeBody *hclsyntax.Body, from inputTokens) (inputTokens, *node, inputTokens) { before, within, after := from.PartitionIncludingComments(nativeBody.SrcRange) // The main AST doesn't retain the original source ordering of the @@ -164,7 +184,10 @@ func parseBody(nativeBody *hclsyntax.Body, from inputTokens) (inputTokens, *Body sort.Sort(nativeNodeSorter{nativeItems}) body := &Body{ - IndentLevel: 0, // TODO: deal with this + inTree: newInTree(), + + indentLevel: 0, // TODO: deal with this + items: newNodeSet(), } remain := within @@ -172,24 +195,24 @@ func parseBody(nativeBody *hclsyntax.Body, from inputTokens) (inputTokens, *Body beforeItem, item, afterItem := parseBodyItem(nativeItem, remain) if beforeItem.Len() > 0 { - body.AppendUnstructuredTokens(beforeItem.Seq()) + body.AppendUnstructuredTokens(beforeItem.Tokens()) } - body.AppendItem(item) + body.appendItemNode(item) remain = afterItem } if remain.Len() > 0 { - body.AppendUnstructuredTokens(remain.Seq()) + body.AppendUnstructuredTokens(remain.Tokens()) } - return before, body, after + return before, newNode(body), after } -func parseBodyItem(nativeItem hclsyntax.Node, from inputTokens) (inputTokens, Node, inputTokens) { +func parseBodyItem(nativeItem hclsyntax.Node, from inputTokens) (inputTokens, *node, inputTokens) { before, leadComments, within, lineComments, newline, after := from.PartitionBlockItem(nativeItem.Range()) - var item Node + var item *node switch tItem := nativeItem.(type) { case *hclsyntax.Attribute: @@ -204,90 +227,96 @@ func parseBodyItem(nativeItem hclsyntax.Node, from inputTokens) (inputTokens, No return before, item, after } -func parseAttribute(nativeAttr *hclsyntax.Attribute, from, leadComments, lineComments, newline inputTokens) *Attribute { - var allTokens TokenSeq - attr := &Attribute{} +func parseAttribute(nativeAttr *hclsyntax.Attribute, from, leadComments, lineComments, newline inputTokens) *node { + attr := &Attribute{ + inTree: newInTree(), + } + children := attr.inTree.children - if leadComments.Len() > 0 { - attr.LeadCommentTokens = leadComments.Seq() - allTokens = append(allTokens, attr.LeadCommentTokens) + { + cn := newNode(newComments(leadComments.Tokens())) + attr.leadComments = cn + children.AppendNode(cn) } before, nameTokens, from := from.Partition(nativeAttr.NameRange) - if before.Len() > 0 { - allTokens = append(allTokens, before.Seq()) + { + children.AppendUnstructuredTokens(before.Tokens()) + if nameTokens.Len() != 1 { + // Should never happen with valid input + panic("attribute name is not exactly one token") + } + token := nameTokens.Tokens()[0] + in := newNode(newIdentifier(token)) + attr.name = in + children.AppendNode(in) } - attr.NameTokens = nameTokens.Seq() - allTokens = append(allTokens, attr.NameTokens) before, equalsTokens, from := from.Partition(nativeAttr.EqualsRange) - if before.Len() > 0 { - allTokens = append(allTokens, before.Seq()) - } - attr.EqualsTokens = equalsTokens.Seq() - allTokens = append(allTokens, attr.EqualsTokens) + children.AppendUnstructuredTokens(before.Tokens()) + children.AppendUnstructuredTokens(equalsTokens.Tokens()) before, exprTokens, from := from.Partition(nativeAttr.Expr.Range()) - if before.Len() > 0 { - allTokens = append(allTokens, before.Seq()) + { + children.AppendUnstructuredTokens(before.Tokens()) + exprNode := parseExpression(nativeAttr.Expr, exprTokens) + attr.expr = exprNode + children.AppendNode(exprNode) } - attr.Expr = parseExpression(nativeAttr.Expr, exprTokens) - allTokens = append(allTokens, attr.Expr.AllTokens) - if lineComments.Len() > 0 { - attr.LineCommentTokens = lineComments.Seq() - allTokens = append(allTokens, attr.LineCommentTokens) + { + cn := newNode(newComments(lineComments.Tokens())) + attr.lineComments = cn + children.AppendNode(cn) } - if newline.Len() > 0 { - attr.EOLTokens = newline.Seq() - allTokens = append(allTokens, attr.EOLTokens) - } + children.AppendUnstructuredTokens(newline.Tokens()) // Collect any stragglers, though there shouldn't be any - if from.Len() > 0 { - allTokens = append(allTokens, from.Seq()) - } - - attr.AllTokens = &allTokens + children.AppendUnstructuredTokens(from.Tokens()) - return attr + return newNode(attr) } -func parseBlock(nativeBlock *hclsyntax.Block, from, leadComments, lineComments, newline inputTokens) *Block { - var allTokens TokenSeq - block := &Block{} +func parseBlock(nativeBlock *hclsyntax.Block, from, leadComments, lineComments, newline inputTokens) *node { + block := &Block{ + inTree: newInTree(), + labels: newNodeSet(), + } + children := block.inTree.children - if leadComments.Len() > 0 { - block.LeadCommentTokens = leadComments.Seq() - allTokens = append(allTokens, block.LeadCommentTokens) + { + cn := newNode(newComments(leadComments.Tokens())) + block.leadComments = cn + children.AppendNode(cn) } before, typeTokens, from := from.Partition(nativeBlock.TypeRange) - if before.Len() > 0 { - allTokens = append(allTokens, before.Seq()) + { + children.AppendUnstructuredTokens(before.Tokens()) + if typeTokens.Len() != 1 { + // Should never happen with valid input + panic("block type name is not exactly one token") + } + token := typeTokens.Tokens()[0] + in := newNode(newIdentifier(token)) + block.typeName = in + children.AppendNode(in) } - block.TypeTokens = typeTokens.Seq() - allTokens = append(allTokens, block.TypeTokens) for _, rng := range nativeBlock.LabelRanges { var labelTokens inputTokens before, labelTokens, from = from.Partition(rng) - if before.Len() > 0 { - allTokens = append(allTokens, before.Seq()) - } - seq := labelTokens.Seq() - block.LabelTokens = append(block.LabelTokens, seq) - *(block.LabelTokensFlat) = append(*(block.LabelTokensFlat), seq) - allTokens = append(allTokens, seq) + children.AppendUnstructuredTokens(before.Tokens()) + tokens := labelTokens.Tokens() + ln := newNode(newQuoted(tokens)) + block.labels.Add(ln) + children.AppendNode(ln) } before, oBrace, from := from.Partition(nativeBlock.OpenBraceRange) - if before.Len() > 0 { - allTokens = append(allTokens, before.Seq()) - } - block.OBraceTokens = oBrace.Seq() - allTokens = append(allTokens, block.OBraceTokens) + children.AppendUnstructuredTokens(before.Tokens()) + children.AppendUnstructuredTokens(oBrace.Tokens()) // We go a bit out of order here: we go hunting for the closing brace // so that we have a delimited body, but then we'll deal with the body @@ -295,87 +324,109 @@ func parseBlock(nativeBlock *hclsyntax.Block, from, leadComments, lineComments, // that appear after it. bodyTokens, cBrace, from := from.Partition(nativeBlock.CloseBraceRange) before, body, after := parseBody(nativeBlock.Body, bodyTokens) + children.AppendUnstructuredTokens(before.Tokens()) + block.body = body + children.AppendNode(body) + children.AppendUnstructuredTokens(after.Tokens()) - if before.Len() > 0 { - allTokens = append(allTokens, before.Seq()) - } - block.Body = body - allTokens = append(allTokens, body.AllTokens) - if after.Len() > 0 { - allTokens = append(allTokens, after.Seq()) - } - - block.CBraceTokens = cBrace.Seq() - allTokens = append(allTokens, block.CBraceTokens) + children.AppendUnstructuredTokens(cBrace.Tokens()) // stragglers - if after.Len() > 0 { - allTokens = append(allTokens, from.Seq()) - } + children.AppendUnstructuredTokens(from.Tokens()) if lineComments.Len() > 0 { // blocks don't actually have line comments, so we'll just treat // them as extra stragglers - allTokens = append(allTokens, lineComments.Seq()) - } - if newline.Len() > 0 { - block.EOLTokens = newline.Seq() - allTokens = append(allTokens, block.EOLTokens) + children.AppendUnstructuredTokens(lineComments.Tokens()) } + children.AppendUnstructuredTokens(newline.Tokens()) - block.AllTokens = &allTokens - return block + return newNode(block) } -func parseExpression(nativeExpr hclsyntax.Expression, from inputTokens) *Expression { - var allTokens TokenSeq - nativeVars := nativeExpr.Variables() - var absTraversals []*Traversal - for _, nativeTraversal := range nativeVars { - var traversalTokens TokenSeq - var before, traversalFrom inputTokens - before, traversalFrom, from = from.Partition(nativeTraversal.SourceRange()) - if before.Len() > 0 { - allTokens = append(allTokens, before.Seq()) - } - - var steps []*Traverser +func parseExpression(nativeExpr hclsyntax.Expression, from inputTokens) *node { + expr := newExpression() + children := expr.inTree.children - for _, nativeStep := range nativeTraversal { - var stepFrom inputTokens - before, stepFrom, traversalFrom = traversalFrom.Partition(nativeStep.SourceRange()) - stepTokens := stepFrom.Seq() - if before.Len() > 0 { - traversalTokens = append(traversalTokens, before.Seq()) - } - traversalTokens = append(traversalTokens, stepTokens) - step := &Traverser{ - AllTokens: stepTokens, - Logical: nativeStep, - } - steps = append(steps, step) - } - // Attach any straggler that don't belong to a step to the traversal itself. - if traversalFrom.Len() > 0 { - traversalTokens = append(traversalTokens, traversalFrom.Seq()) - } - allTokens = append(allTokens, &traversalTokens) + nativeVars := nativeExpr.Variables() - absTraversals = append(absTraversals, &Traversal{ - AllTokens: &traversalTokens, - Steps: steps, - }) + for _, nativeTraversal := range nativeVars { + before, traversal, after := parseTraversal(nativeTraversal, from) + children.AppendUnstructuredTokens(before.Tokens()) + children.AppendNode(traversal) + expr.absTraversals.Add(traversal) + from = after } // Attach any stragglers that don't belong to a traversal to the expression // itself. In an expression with no traversals at all, this is just the // entirety of "from". - if from.Len() > 0 { - allTokens = append(allTokens, from.Seq()) + children.AppendUnstructuredTokens(from.Tokens()) + + return newNode(expr) +} + +func parseTraversal(nativeTraversal hcl.Traversal, from inputTokens) (before inputTokens, n *node, after inputTokens) { + traversal := newTraversal() + children := traversal.inTree.children + before, from, after = from.Partition(nativeTraversal.SourceRange()) + + stepAfter := from + for _, nativeStep := range nativeTraversal { + before, step, after := parseTraversalStep(nativeStep, stepAfter) + children.AppendUnstructuredTokens(before.Tokens()) + children.AppendNode(step) + stepAfter = after } - return &Expression{ - AllTokens: &allTokens, - AbsTraversals: absTraversals, + return before, newNode(traversal), after +} + +func parseTraversalStep(nativeStep hcl.Traverser, from inputTokens) (before inputTokens, n *node, after inputTokens) { + var children *nodes + switch tNativeStep := nativeStep.(type) { + + case hcl.TraverseRoot, hcl.TraverseAttr: + step := newTraverseName() + children = step.inTree.children + before, from, after = from.Partition(nativeStep.SourceRange()) + inBefore, token, inAfter := from.PartitionTypeSingle(hclsyntax.TokenIdent) + name := newIdentifier(token) + children.AppendUnstructuredTokens(inBefore.Tokens()) + step.name = children.Append(name) + children.AppendUnstructuredTokens(inAfter.Tokens()) + return before, newNode(step), after + + case hcl.TraverseIndex: + step := newTraverseIndex() + children = step.inTree.children + before, from, after = from.Partition(nativeStep.SourceRange()) + + var inBefore, oBrack, keyTokens, cBrack inputTokens + inBefore, oBrack, from = from.PartitionType(hclsyntax.TokenOBrack) + children.AppendUnstructuredTokens(inBefore.Tokens()) + children.AppendUnstructuredTokens(oBrack.Tokens()) + keyTokens, cBrack, from = from.PartitionType(hclsyntax.TokenCBrack) + + keyVal := tNativeStep.Key + switch keyVal.Type() { + case cty.String: + key := newQuoted(keyTokens.Tokens()) + step.key = children.Append(key) + case cty.Number: + valBefore, valToken, valAfter := keyTokens.PartitionTypeSingle(hclsyntax.TokenNumberLit) + children.AppendUnstructuredTokens(valBefore.Tokens()) + key := newNumber(valToken) + step.key = children.Append(key) + children.AppendUnstructuredTokens(valAfter.Tokens()) + } + + children.AppendUnstructuredTokens(cBrack.Tokens()) + children.AppendUnstructuredTokens(from.Tokens()) + + return before, newNode(step), after + default: + panic(fmt.Sprintf("unsupported traversal step type %T", nativeStep)) } + } // writerTokens takes a sequence of tokens as produced by the main hclsyntax diff --git a/vendor/github.com/hashicorp/hcl2/hclwrite/public.go b/vendor/github.com/hashicorp/hcl2/hclwrite/public.go index d9b0dd5aa9..b4d7f15b1d 100644 --- a/vendor/github.com/hashicorp/hcl2/hclwrite/public.go +++ b/vendor/github.com/hashicorp/hcl2/hclwrite/public.go @@ -6,6 +6,20 @@ import ( "github.com/hashicorp/hcl2/hcl" ) +// NewFile creates a new file object that is empty and ready to have constructs +// added t it. +func NewFile() *File { + body := &Body{ + inTree: newInTree(), + indentLevel: 0, + } + file := &File{ + inTree: newInTree(), + } + file.body = file.inTree.children.Append(body) + return file +} + // ParseConfig interprets the given source bytes into a *hclwrite.File. The // resulting AST can be used to perform surgical edits on the source code // before turning it back into bytes again. @@ -25,6 +39,6 @@ func Format(src []byte) []byte { tokens := lexConfig(src) format(tokens) buf := &bytes.Buffer{} - (&TokenSeq{tokens}).WriteTo(buf) + tokens.WriteTo(buf) return buf.Bytes() } diff --git a/vendor/github.com/hashicorp/hcl2/hclwrite/tokens.go b/vendor/github.com/hashicorp/hcl2/hclwrite/tokens.go index 8fb87f2529..0b2034a1ba 100644 --- a/vendor/github.com/hashicorp/hcl2/hclwrite/tokens.go +++ b/vendor/github.com/hashicorp/hcl2/hclwrite/tokens.go @@ -8,19 +8,6 @@ import ( "github.com/hashicorp/hcl2/hcl/hclsyntax" ) -// TokenGen is an abstract type that can append tokens to a list. It is the -// low-level foundation underlying the hclwrite AST; the AST provides a -// convenient abstraction over raw token sequences to facilitate common tasks, -// but it's also possible to directly manipulate the tree of token generators -// to make changes that the AST API doesn't directly allow. -type TokenGen interface { - EachToken(TokenCallback) -} - -// TokenCallback is used with TokenGen implementations to specify the action -// that is to be taken for each token in the flattened token sequence. -type TokenCallback func(*Token) - // Token is a single sequence of bytes annotated with a type. It is similar // in purpose to hclsyntax.Token, but discards the source position information // since that is not useful in code generation. @@ -38,17 +25,16 @@ type Token struct { // Tokens is a flat list of tokens. type Tokens []*Token -func (ts Tokens) WriteTo(wr io.Writer) (int, error) { - seq := &TokenSeq{ts} - return seq.WriteTo(wr) -} - func (ts Tokens) Bytes() []byte { buf := &bytes.Buffer{} ts.WriteTo(buf) return buf.Bytes() } +func (ts Tokens) testValue() string { + return string(ts.Bytes()) +} + // Columns returns the number of columns (grapheme clusters) the token sequence // occupies. The result is not meaningful if there are newline or single-line // comment tokens in the sequence. @@ -62,43 +48,10 @@ func (ts Tokens) Columns() int { return ret } -// TokenSeq combines zero or more TokenGens together to produce a flat sequence -// of tokens from a tree of TokenGens. -type TokenSeq []TokenGen - -func (t *Token) EachToken(cb TokenCallback) { - cb(t) -} - -func (ts Tokens) EachToken(cb TokenCallback) { - for _, t := range ts { - cb(t) - } -} - -func (ts *TokenSeq) EachToken(cb TokenCallback) { - if ts == nil { - return - } - for _, gen := range *ts { - gen.EachToken(cb) - } -} - -// Tokens returns the flat list of tokens represented by the receiving -// token sequence. -func (ts *TokenSeq) Tokens() Tokens { - var tokens Tokens - ts.EachToken(func(token *Token) { - tokens = append(tokens, token) - }) - return tokens -} - // WriteTo takes an io.Writer and writes the bytes for each token to it, // along with the spacing that separates each token. In other words, this // allows serializing the tokens to a file or other such byte stream. -func (ts *TokenSeq) WriteTo(wr io.Writer) (int, error) { +func (ts Tokens) WriteTo(wr io.Writer) (int, error) { // We know we're going to be writing a lot of small chunks of repeated // space characters, so we'll prepare a buffer of these that we can // easily pass to wr.Write without any further allocation. @@ -109,9 +62,9 @@ func (ts *TokenSeq) WriteTo(wr io.Writer) (int, error) { var n int var err error - ts.EachToken(func(token *Token) { + for _, token := range ts { if err != nil { - return + return n, err } for spacesBefore := token.SpacesBefore; spacesBefore > 0; spacesBefore -= len(spaces) { @@ -123,48 +76,29 @@ func (ts *TokenSeq) WriteTo(wr io.Writer) (int, error) { thisN, err = wr.Write(spaces[:thisChunk]) n += thisN if err != nil { - return + return n, err } } var thisN int thisN, err = wr.Write(token.Bytes) n += thisN - }) + } return n, err } -// SoloToken returns the single token represented by the receiving sequence, -// or nil if the sequence does not represent exactly one token. -func (ts *TokenSeq) SoloToken() *Token { - var ret *Token - found := false - ts.EachToken(func(tok *Token) { - if ret == nil && !found { - ret = tok - found = true - } else if ret != nil && found { - ret = nil - } - }) - return ret +func (ts Tokens) walkChildNodes(w internalWalkFunc) { + // Unstructured tokens have no child nodes } -// IsIdent returns true if and only if the token sequence represents a single -// ident token whose name matches the given string. -func (ts *TokenSeq) IsIdent(name []byte) bool { - tok := ts.SoloToken() - if tok == nil { - return false - } - if tok.Type != hclsyntax.TokenIdent { - return false - } - return bytes.Equal(tok.Bytes, name) +func (ts Tokens) BuildTokens(to Tokens) Tokens { + return append(to, ts...) } -// TokenSeqEmpty is a TokenSeq that contains no tokens. It can be used anywhere, -// but its primary purpose is to be assigned as a replacement for a non-empty -// TokenSeq when eliminating a section of an input file. -var TokenSeqEmpty = TokenSeq([]TokenGen(nil)) +func newIdentToken(name string) *Token { + return &Token{ + Type: hclsyntax.TokenIdent, + Bytes: []byte(name), + } +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 9944915638..a5c69ba841 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1759,70 +1759,70 @@ "revisionTime": "2017-05-04T19:02:34Z" }, { - "checksumSHA1": "dJPromzLdd492RQjE/09klKRXGs=", + "checksumSHA1": "8q8+Dn1uRDlN4OZ5kqQnUMgSPRk=", "path": "github.com/hashicorp/hcl2/ext/dynblock", - "revision": "41cff854d8157be197e6b4698a8d9570ced9476b", - "revisionTime": "2018-07-18T22:41:35Z" + "revision": "ed8144cda141c1f06f8ab122baff783b645cfe52", + "revisionTime": "2018-08-22T19:31:30Z" }, { "checksumSHA1": "IAfC0Xri1iCRgbbiDBgs6ue8/Ic=", "path": "github.com/hashicorp/hcl2/ext/typeexpr", - "revision": "41cff854d8157be197e6b4698a8d9570ced9476b", - "revisionTime": "2018-07-18T22:41:35Z" + "revision": "ed8144cda141c1f06f8ab122baff783b645cfe52", + "revisionTime": "2018-08-22T19:31:30Z" }, { "checksumSHA1": "BRJaQcKriVKEirVC7YxBxPufQF0=", "path": "github.com/hashicorp/hcl2/gohcl", - "revision": "41cff854d8157be197e6b4698a8d9570ced9476b", - "revisionTime": "2018-07-18T22:41:35Z" + "revision": "ed8144cda141c1f06f8ab122baff783b645cfe52", + "revisionTime": "2018-08-22T19:31:30Z" }, { - "checksumSHA1": "3ypdUCoJwZt+XRMV/6FoRhywz8A=", + "checksumSHA1": "LotrMqeWeTv/rNOGUHRs9iVBjoQ=", "path": "github.com/hashicorp/hcl2/hcl", - "revision": "41cff854d8157be197e6b4698a8d9570ced9476b", - "revisionTime": "2018-07-18T22:41:35Z" + "revision": "ed8144cda141c1f06f8ab122baff783b645cfe52", + "revisionTime": "2018-08-22T19:31:30Z" }, { - "checksumSHA1": "o6XGTmFfazaLQiSVs5cnaNvJhYQ=", + "checksumSHA1": "RNoOVGaFtYqaPMyARZuHc2OejDs=", "path": "github.com/hashicorp/hcl2/hcl/hclsyntax", - "revision": "41cff854d8157be197e6b4698a8d9570ced9476b", - "revisionTime": "2018-07-18T22:41:35Z" + "revision": "ed8144cda141c1f06f8ab122baff783b645cfe52", + "revisionTime": "2018-08-22T19:31:30Z" }, { - "checksumSHA1": "Cuhv6UBgimVhWWwYm8v7Moisrhg=", + "checksumSHA1": "4Cr8I/nepYf4eRCl5hiazPf+afs=", "path": "github.com/hashicorp/hcl2/hcl/json", - "revision": "41cff854d8157be197e6b4698a8d9570ced9476b", - "revisionTime": "2018-07-18T22:41:35Z" + "revision": "ed8144cda141c1f06f8ab122baff783b645cfe52", + "revisionTime": "2018-08-22T19:31:30Z" }, { - "checksumSHA1": "iIVMnRuvfOy/tJ1zE9rVcjD/01A=", + "checksumSHA1": "6JRj4T/iQxIe/CoKXHDjPuupmL8=", "path": "github.com/hashicorp/hcl2/hcldec", - "revision": "41cff854d8157be197e6b4698a8d9570ced9476b", - "revisionTime": "2018-07-18T22:41:35Z" + "revision": "ed8144cda141c1f06f8ab122baff783b645cfe52", + "revisionTime": "2018-08-22T19:31:30Z" }, { "checksumSHA1": "sySYF9Ew71VS/LfrG+s/0jK+1VQ=", "path": "github.com/hashicorp/hcl2/hcled", - "revision": "41cff854d8157be197e6b4698a8d9570ced9476b", - "revisionTime": "2018-07-18T22:41:35Z" + "revision": "ed8144cda141c1f06f8ab122baff783b645cfe52", + "revisionTime": "2018-08-22T19:31:30Z" }, { "checksumSHA1": "IzmftuG99BqNhbFGhxZaGwtiMtM=", "path": "github.com/hashicorp/hcl2/hclparse", - "revision": "41cff854d8157be197e6b4698a8d9570ced9476b", - "revisionTime": "2018-07-18T22:41:35Z" + "revision": "ed8144cda141c1f06f8ab122baff783b645cfe52", + "revisionTime": "2018-08-22T19:31:30Z" }, { "checksumSHA1": "UQzPVMlOo/3CJQnShbnNVcTF4EA=", "path": "github.com/hashicorp/hcl2/hcltest", - "revision": "41cff854d8157be197e6b4698a8d9570ced9476b", - "revisionTime": "2018-07-18T22:41:35Z" + "revision": "ed8144cda141c1f06f8ab122baff783b645cfe52", + "revisionTime": "2018-08-22T19:31:30Z" }, { - "checksumSHA1": "v2ADtHrkDgQdqsroh9SJRsfANTk=", + "checksumSHA1": "+B/DKtBk5ag/s10xr8X1IepziY4=", "path": "github.com/hashicorp/hcl2/hclwrite", - "revision": "41cff854d8157be197e6b4698a8d9570ced9476b", - "revisionTime": "2018-07-18T22:41:35Z" + "revision": "ed8144cda141c1f06f8ab122baff783b645cfe52", + "revisionTime": "2018-08-22T19:31:30Z" }, { "checksumSHA1": "M09yxoBoCEtG7EcHR8aEWLzMMJc=",