6 "github.com/hashicorp/hcl2/hcl"
7 "github.com/hashicorp/hcl2/hcl/hclsyntax"
8 "github.com/zclconf/go-cty/cty"
9 "github.com/zclconf/go-cty/cty/convert"
12 // body is the implementation of "Body" used for files processed with the JSON
17 // If non-nil, the keys of this map cause the corresponding attributes to
18 // be treated as non-existing. This is used when Body.PartialContent is
19 // called, to produce the "remaining content" Body.
20 hiddenAttrs map[string]struct{}
23 // expression is the implementation of "Expression" used for files processed
24 // with the JSON parser.
25 type expression struct {
29 func (b *body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
30 content, newBody, diags := b.PartialContent(schema)
32 hiddenAttrs := newBody.(*body).hiddenAttrs
34 var nameSuggestions []string
35 for _, attrS := range schema.Attributes {
36 if _, ok := hiddenAttrs[attrS.Name]; !ok {
37 // Only suggest an attribute name if we didn't use it already.
38 nameSuggestions = append(nameSuggestions, attrS.Name)
41 for _, blockS := range schema.Blocks {
42 // Blocks can appear multiple times, so we'll suggest their type
43 // names regardless of whether they've already been used.
44 nameSuggestions = append(nameSuggestions, blockS.Type)
47 jsonAttrs, attrDiags := b.collectDeepAttrs(b.val, nil)
48 diags = append(diags, attrDiags...)
50 for _, attr := range jsonAttrs {
53 // Ignore "//" keys in objects representing bodies, to allow
54 // their use as comments.
58 if _, ok := hiddenAttrs[k]; !ok {
59 suggestion := nameSuggestion(k, nameSuggestions)
61 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
64 diags = append(diags, &hcl.Diagnostic{
65 Severity: hcl.DiagError,
66 Summary: "Extraneous JSON object property",
67 Detail: fmt.Sprintf("No argument or block type is named %q.%s", k, suggestion),
68 Subject: &attr.NameRange,
69 Context: attr.Range().Ptr(),
77 func (b *body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
78 var diags hcl.Diagnostics
80 jsonAttrs, attrDiags := b.collectDeepAttrs(b.val, nil)
81 diags = append(diags, attrDiags...)
83 usedNames := map[string]struct{}{}
84 if b.hiddenAttrs != nil {
85 for k := range b.hiddenAttrs {
86 usedNames[k] = struct{}{}
90 content := &hcl.BodyContent{
91 Attributes: map[string]*hcl.Attribute{},
94 MissingItemRange: b.MissingItemRange(),
97 // Create some more convenient data structures for our work below.
98 attrSchemas := map[string]hcl.AttributeSchema{}
99 blockSchemas := map[string]hcl.BlockHeaderSchema{}
100 for _, attrS := range schema.Attributes {
101 attrSchemas[attrS.Name] = attrS
103 for _, blockS := range schema.Blocks {
104 blockSchemas[blockS.Type] = blockS
107 for _, jsonAttr := range jsonAttrs {
108 attrName := jsonAttr.Name
109 if _, used := b.hiddenAttrs[attrName]; used {
113 if attrS, defined := attrSchemas[attrName]; defined {
114 if existing, exists := content.Attributes[attrName]; exists {
115 diags = append(diags, &hcl.Diagnostic{
116 Severity: hcl.DiagError,
117 Summary: "Duplicate argument",
118 Detail: fmt.Sprintf("The argument %q was already set at %s.", attrName, existing.Range),
119 Subject: &jsonAttr.NameRange,
120 Context: jsonAttr.Range().Ptr(),
125 content.Attributes[attrS.Name] = &hcl.Attribute{
127 Expr: &expression{src: jsonAttr.Value},
128 Range: hcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
129 NameRange: jsonAttr.NameRange,
131 usedNames[attrName] = struct{}{}
133 } else if blockS, defined := blockSchemas[attrName]; defined {
135 blockDiags := b.unpackBlock(bv, blockS.Type, &jsonAttr.NameRange, blockS.LabelNames, nil, nil, &content.Blocks)
136 diags = append(diags, blockDiags...)
137 usedNames[attrName] = struct{}{}
140 // We ignore anything that isn't defined because that's the
141 // PartialContent contract. The Content method will catch leftovers.
144 // Make sure we got all the required attributes.
145 for _, attrS := range schema.Attributes {
149 if _, defined := content.Attributes[attrS.Name]; !defined {
150 diags = append(diags, &hcl.Diagnostic{
151 Severity: hcl.DiagError,
152 Summary: "Missing required argument",
153 Detail: fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name),
154 Subject: b.MissingItemRange().Ptr(),
161 hiddenAttrs: usedNames,
164 return content, unusedBody, diags
167 // JustAttributes for JSON bodies interprets all properties of the wrapped
168 // JSON object as attributes and returns them.
169 func (b *body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
170 var diags hcl.Diagnostics
171 attrs := make(map[string]*hcl.Attribute)
173 obj, ok := b.val.(*objectVal)
175 diags = append(diags, &hcl.Diagnostic{
176 Severity: hcl.DiagError,
177 Summary: "Incorrect JSON value type",
178 Detail: "A JSON object is required here, setting the arguments for this block.",
179 Subject: b.val.StartRange().Ptr(),
184 for _, jsonAttr := range obj.Attrs {
185 name := jsonAttr.Name
187 // Ignore "//" keys in objects representing bodies, to allow
188 // their use as comments.
192 if _, hidden := b.hiddenAttrs[name]; hidden {
196 if existing, exists := attrs[name]; exists {
197 diags = append(diags, &hcl.Diagnostic{
198 Severity: hcl.DiagError,
199 Summary: "Duplicate attribute definition",
200 Detail: fmt.Sprintf("The argument %q was already set at %s.", name, existing.Range),
201 Subject: &jsonAttr.NameRange,
206 attrs[name] = &hcl.Attribute{
208 Expr: &expression{src: jsonAttr.Value},
209 Range: hcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
210 NameRange: jsonAttr.NameRange,
214 // No diagnostics possible here, since the parser already took care of
215 // finding duplicates and every JSON value can be a valid attribute value.
219 func (b *body) MissingItemRange() hcl.Range {
220 switch tv := b.val.(type) {
226 // Should not happen in correct operation, but might show up if the
227 // input is invalid and we are producing partial results.
228 return tv.StartRange()
232 func (b *body) unpackBlock(v node, typeName string, typeRange *hcl.Range, labelsLeft []string, labelsUsed []string, labelRanges []hcl.Range, blocks *hcl.Blocks) (diags hcl.Diagnostics) {
233 if len(labelsLeft) > 0 {
234 labelName := labelsLeft[0]
235 jsonAttrs, attrDiags := b.collectDeepAttrs(v, &labelName)
236 diags = append(diags, attrDiags...)
238 if len(jsonAttrs) == 0 {
239 diags = diags.Append(&hcl.Diagnostic{
240 Severity: hcl.DiagError,
241 Summary: "Missing block label",
242 Detail: fmt.Sprintf("At least one object property is required, whose name represents the %s block's %s.", typeName, labelName),
243 Subject: v.StartRange().Ptr(),
247 labelsUsed := append(labelsUsed, "")
248 labelRanges := append(labelRanges, hcl.Range{})
249 for _, p := range jsonAttrs {
251 labelsUsed[len(labelsUsed)-1] = pk
252 labelRanges[len(labelRanges)-1] = p.NameRange
253 diags = append(diags, b.unpackBlock(p.Value, typeName, typeRange, labelsLeft[1:], labelsUsed, labelRanges, blocks)...)
258 // By the time we get here, we've peeled off all the labels and we're ready
259 // to deal with the block's actual content.
261 // need to copy the label slices because their underlying arrays will
262 // continue to be mutated after we return.
263 labels := make([]string, len(labelsUsed))
264 copy(labels, labelsUsed)
265 labelR := make([]hcl.Range, len(labelRanges))
266 copy(labelR, labelRanges)
268 switch tv := v.(type) {
270 // There is no block content, e.g the value is null.
273 // Single instance of the block
274 *blocks = append(*blocks, &hcl.Block{
281 DefRange: tv.OpenRange,
282 TypeRange: *typeRange,
286 // Multiple instances of the block
287 for _, av := range tv.Values {
288 *blocks = append(*blocks, &hcl.Block{
292 val: av, // might be mistyped; we'll find out when content is requested for this body
295 DefRange: tv.OpenRange,
296 TypeRange: *typeRange,
301 diags = diags.Append(&hcl.Diagnostic{
302 Severity: hcl.DiagError,
303 Summary: "Incorrect JSON value type",
304 Detail: fmt.Sprintf("Either a JSON object or a JSON array is required, representing the contents of one or more %q blocks.", typeName),
305 Subject: v.StartRange().Ptr(),
311 // collectDeepAttrs takes either a single object or an array of objects and
312 // flattens it into a list of object attributes, collecting attributes from
313 // all of the objects in a given array.
315 // Ordering is preserved, so a list of objects that each have one property
316 // will result in those properties being returned in the same order as the
317 // objects appeared in the array.
319 // This is appropriate for use only for objects representing bodies or labels
322 // The labelName argument, if non-null, is used to tailor returned error
323 // messages to refer to block labels rather than attributes and child blocks.
324 // It has no other effect.
325 func (b *body) collectDeepAttrs(v node, labelName *string) ([]*objectAttr, hcl.Diagnostics) {
326 var diags hcl.Diagnostics
327 var attrs []*objectAttr
329 switch tv := v.(type) {
331 // If a value is null, then we don't return any attributes or return an error.
334 attrs = append(attrs, tv.Attrs...)
337 for _, ev := range tv.Values {
338 switch tev := ev.(type) {
340 attrs = append(attrs, tev.Attrs...)
342 if labelName != nil {
343 diags = append(diags, &hcl.Diagnostic{
344 Severity: hcl.DiagError,
345 Summary: "Incorrect JSON value type",
346 Detail: fmt.Sprintf("A JSON object is required here, to specify %s labels for this block.", *labelName),
347 Subject: ev.StartRange().Ptr(),
350 diags = append(diags, &hcl.Diagnostic{
351 Severity: hcl.DiagError,
352 Summary: "Incorrect JSON value type",
353 Detail: "A JSON object is required here, to define arguments and child blocks.",
354 Subject: ev.StartRange().Ptr(),
361 if labelName != nil {
362 diags = append(diags, &hcl.Diagnostic{
363 Severity: hcl.DiagError,
364 Summary: "Incorrect JSON value type",
365 Detail: fmt.Sprintf("Either a JSON object or JSON array of objects is required here, to specify %s labels for this block.", *labelName),
366 Subject: v.StartRange().Ptr(),
369 diags = append(diags, &hcl.Diagnostic{
370 Severity: hcl.DiagError,
371 Summary: "Incorrect JSON value type",
372 Detail: "Either a JSON object or JSON array of objects is required here, to define arguments and child blocks.",
373 Subject: v.StartRange().Ptr(),
381 func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
382 switch v := e.src.(type) {
385 // Parse string contents as a HCL native language expression.
386 // We only do this if we have a context, so passing a nil context
387 // is how the caller specifies that interpolations are not allowed
388 // and that the string should just be returned verbatim.
389 templateSrc := v.Value
390 expr, diags := hclsyntax.ParseTemplate(
394 // This won't produce _exactly_ the right result, since
395 // the hclsyntax parser can't "see" any escapes we removed
396 // while parsing JSON, but it's better than nothing.
398 Line: v.SrcRange.Start.Line,
400 // skip over the opening quote mark
401 Byte: v.SrcRange.Start.Byte + 1,
402 Column: v.SrcRange.Start.Column + 1,
405 if diags.HasErrors() {
406 return cty.DynamicVal, diags
408 val, evalDiags := expr.Value(ctx)
409 diags = append(diags, evalDiags...)
413 return cty.StringVal(v.Value), nil
415 return cty.NumberVal(v.Value), nil
417 return cty.BoolVal(v.Value), nil
419 vals := []cty.Value{}
420 for _, jsonVal := range v.Values {
421 val, _ := (&expression{src: jsonVal}).Value(ctx)
422 vals = append(vals, val)
424 return cty.TupleVal(vals), nil
426 var diags hcl.Diagnostics
427 attrs := map[string]cty.Value{}
428 attrRanges := map[string]hcl.Range{}
430 for _, jsonAttr := range v.Attrs {
431 // In this one context we allow keys to contain interpolation
432 // expressions too, assuming we're evaluating in interpolation
433 // mode. This achieves parity with the native syntax where
434 // object expressions can have dynamic keys, while block contents
436 name, nameDiags := (&expression{src: &stringVal{
437 Value: jsonAttr.Name,
438 SrcRange: jsonAttr.NameRange,
440 valExpr := &expression{src: jsonAttr.Value}
441 val, valDiags := valExpr.Value(ctx)
442 diags = append(diags, nameDiags...)
443 diags = append(diags, valDiags...)
446 name, err = convert.Convert(name, cty.String)
448 diags = append(diags, &hcl.Diagnostic{
449 Severity: hcl.DiagError,
450 Summary: "Invalid object key expression",
451 Detail: fmt.Sprintf("Cannot use this expression as an object key: %s.", err),
452 Subject: &jsonAttr.NameRange,
459 diags = append(diags, &hcl.Diagnostic{
460 Severity: hcl.DiagError,
461 Summary: "Invalid object key expression",
462 Detail: "Cannot use null value as an object key.",
463 Subject: &jsonAttr.NameRange,
470 // This is a bit of a weird case, since our usual rules require
471 // us to tolerate unknowns and just represent the result as
472 // best we can but if we don't know the key then we can't
473 // know the type of our object at all, and thus we must turn
474 // the whole thing into cty.DynamicVal. This is consistent with
475 // how this situation is handled in the native syntax.
476 // We'll keep iterating so we can collect other errors in
477 // subsequent attributes.
481 nameStr := name.AsString()
482 if _, defined := attrs[nameStr]; defined {
483 diags = append(diags, &hcl.Diagnostic{
484 Severity: hcl.DiagError,
485 Summary: "Duplicate object attribute",
486 Detail: fmt.Sprintf("An attribute named %q was already defined at %s.", nameStr, attrRanges[nameStr]),
487 Subject: &jsonAttr.NameRange,
494 attrRanges[nameStr] = jsonAttr.NameRange
497 // We encountered an unknown key somewhere along the way, so
498 // we can't know what our type will eventually be.
499 return cty.DynamicVal, diags
501 return cty.ObjectVal(attrs), diags
503 return cty.NullVal(cty.DynamicPseudoType), nil
505 // Default to DynamicVal so that ASTs containing invalid nodes can
506 // still be partially-evaluated.
507 return cty.DynamicVal, nil
511 func (e *expression) Variables() []hcl.Traversal {
512 var vars []hcl.Traversal
514 switch v := e.src.(type) {
516 templateSrc := v.Value
517 expr, diags := hclsyntax.ParseTemplate(
521 // This won't produce _exactly_ the right result, since
522 // the hclsyntax parser can't "see" any escapes we removed
523 // while parsing JSON, but it's better than nothing.
525 Line: v.SrcRange.Start.Line,
527 // skip over the opening quote mark
528 Byte: v.SrcRange.Start.Byte + 1,
529 Column: v.SrcRange.Start.Column + 1,
532 if diags.HasErrors() {
535 return expr.Variables()
538 for _, jsonVal := range v.Values {
539 vars = append(vars, (&expression{src: jsonVal}).Variables()...)
542 for _, jsonAttr := range v.Attrs {
543 keyExpr := &stringVal{ // we're going to treat key as an expression in this context
544 Value: jsonAttr.Name,
545 SrcRange: jsonAttr.NameRange,
547 vars = append(vars, (&expression{src: keyExpr}).Variables()...)
548 vars = append(vars, (&expression{src: jsonAttr.Value}).Variables()...)
555 func (e *expression) Range() hcl.Range {
559 func (e *expression) StartRange() hcl.Range {
560 return e.src.StartRange()
563 // Implementation for hcl.AbsTraversalForExpr.
564 func (e *expression) AsTraversal() hcl.Traversal {
565 // In JSON-based syntax a traversal is given as a string containing
566 // traversal syntax as defined by hclsyntax.ParseTraversalAbs.
568 switch v := e.src.(type) {
570 traversal, diags := hclsyntax.ParseTraversalAbs([]byte(v.Value), v.SrcRange.Filename, v.SrcRange.Start)
571 if diags.HasErrors() {
580 // Implementation for hcl.ExprCall.
581 func (e *expression) ExprCall() *hcl.StaticCall {
582 // In JSON-based syntax a static call is given as a string containing
583 // an expression in the native syntax that also supports ExprCall.
585 switch v := e.src.(type) {
587 expr, diags := hclsyntax.ParseExpression([]byte(v.Value), v.SrcRange.Filename, v.SrcRange.Start)
588 if diags.HasErrors() {
592 call, diags := hcl.ExprCall(expr)
593 if diags.HasErrors() {
603 // Implementation for hcl.ExprList.
604 func (e *expression) ExprList() []hcl.Expression {
605 switch v := e.src.(type) {
607 ret := make([]hcl.Expression, len(v.Values))
608 for i, node := range v.Values {
609 ret[i] = &expression{src: node}
617 // Implementation for hcl.ExprMap.
618 func (e *expression) ExprMap() []hcl.KeyValuePair {
619 switch v := e.src.(type) {
621 ret := make([]hcl.KeyValuePair, len(v.Attrs))
622 for i, jsonAttr := range v.Attrs {
623 ret[i] = hcl.KeyValuePair{
624 Key: &expression{src: &stringVal{
625 Value: jsonAttr.Name,
626 SrcRange: jsonAttr.NameRange,
628 Value: &expression{src: jsonAttr.Value},