1package hclsyntax
2
3import (
4	"fmt"
5	"strings"
6
7	"github.com/hashicorp/hcl2/hcl"
8)
9
10// AsHCLBlock returns the block data expressed as a *hcl.Block.
11func (b *Block) AsHCLBlock() *hcl.Block {
12	if b == nil {
13		return nil
14	}
15
16	lastHeaderRange := b.TypeRange
17	if len(b.LabelRanges) > 0 {
18		lastHeaderRange = b.LabelRanges[len(b.LabelRanges)-1]
19	}
20
21	return &hcl.Block{
22		Type:   b.Type,
23		Labels: b.Labels,
24		Body:   b.Body,
25
26		DefRange:    hcl.RangeBetween(b.TypeRange, lastHeaderRange),
27		TypeRange:   b.TypeRange,
28		LabelRanges: b.LabelRanges,
29	}
30}
31
32// Body is the implementation of hcl.Body for the HCL native syntax.
33type Body struct {
34	Attributes Attributes
35	Blocks     Blocks
36
37	// These are used with PartialContent to produce a "remaining items"
38	// body to return. They are nil on all bodies fresh out of the parser.
39	hiddenAttrs  map[string]struct{}
40	hiddenBlocks map[string]struct{}
41
42	SrcRange hcl.Range
43	EndRange hcl.Range // Final token of the body, for reporting missing items
44}
45
46// Assert that *Body implements hcl.Body
47var assertBodyImplBody hcl.Body = &Body{}
48
49func (b *Body) walkChildNodes(w internalWalkFunc) {
50	w(b.Attributes)
51	w(b.Blocks)
52}
53
54func (b *Body) Range() hcl.Range {
55	return b.SrcRange
56}
57
58func (b *Body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
59	content, remainHCL, diags := b.PartialContent(schema)
60
61	// No we'll see if anything actually remains, to produce errors about
62	// extraneous items.
63	remain := remainHCL.(*Body)
64
65	for name, attr := range b.Attributes {
66		if _, hidden := remain.hiddenAttrs[name]; !hidden {
67			var suggestions []string
68			for _, attrS := range schema.Attributes {
69				if _, defined := content.Attributes[attrS.Name]; defined {
70					continue
71				}
72				suggestions = append(suggestions, attrS.Name)
73			}
74			suggestion := nameSuggestion(name, suggestions)
75			if suggestion != "" {
76				suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
77			} else {
78				// Is there a block of the same name?
79				for _, blockS := range schema.Blocks {
80					if blockS.Type == name {
81						suggestion = fmt.Sprintf(" Did you mean to define a block of type %q?", name)
82						break
83					}
84				}
85			}
86
87			diags = append(diags, &hcl.Diagnostic{
88				Severity: hcl.DiagError,
89				Summary:  "Unsupported argument",
90				Detail:   fmt.Sprintf("An argument named %q is not expected here.%s", name, suggestion),
91				Subject:  &attr.NameRange,
92			})
93		}
94	}
95
96	for _, block := range b.Blocks {
97		blockTy := block.Type
98		if _, hidden := remain.hiddenBlocks[blockTy]; !hidden {
99			var suggestions []string
100			for _, blockS := range schema.Blocks {
101				suggestions = append(suggestions, blockS.Type)
102			}
103			suggestion := nameSuggestion(blockTy, suggestions)
104			if suggestion != "" {
105				suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
106			} else {
107				// Is there an attribute of the same name?
108				for _, attrS := range schema.Attributes {
109					if attrS.Name == blockTy {
110						suggestion = fmt.Sprintf(" Did you mean to define argument %q? If so, use the equals sign to assign it a value.", blockTy)
111						break
112					}
113				}
114			}
115
116			diags = append(diags, &hcl.Diagnostic{
117				Severity: hcl.DiagError,
118				Summary:  "Unsupported block type",
119				Detail:   fmt.Sprintf("Blocks of type %q are not expected here.%s", blockTy, suggestion),
120				Subject:  &block.TypeRange,
121			})
122		}
123	}
124
125	return content, diags
126}
127
128func (b *Body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
129	attrs := make(hcl.Attributes)
130	var blocks hcl.Blocks
131	var diags hcl.Diagnostics
132	hiddenAttrs := make(map[string]struct{})
133	hiddenBlocks := make(map[string]struct{})
134
135	if b.hiddenAttrs != nil {
136		for k, v := range b.hiddenAttrs {
137			hiddenAttrs[k] = v
138		}
139	}
140	if b.hiddenBlocks != nil {
141		for k, v := range b.hiddenBlocks {
142			hiddenBlocks[k] = v
143		}
144	}
145
146	for _, attrS := range schema.Attributes {
147		name := attrS.Name
148		attr, exists := b.Attributes[name]
149		_, hidden := hiddenAttrs[name]
150		if hidden || !exists {
151			if attrS.Required {
152				diags = append(diags, &hcl.Diagnostic{
153					Severity: hcl.DiagError,
154					Summary:  "Missing required argument",
155					Detail:   fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name),
156					Subject:  b.MissingItemRange().Ptr(),
157				})
158			}
159			continue
160		}
161
162		hiddenAttrs[name] = struct{}{}
163		attrs[name] = attr.AsHCLAttribute()
164	}
165
166	blocksWanted := make(map[string]hcl.BlockHeaderSchema)
167	for _, blockS := range schema.Blocks {
168		blocksWanted[blockS.Type] = blockS
169	}
170
171	for _, block := range b.Blocks {
172		if _, hidden := hiddenBlocks[block.Type]; hidden {
173			continue
174		}
175		blockS, wanted := blocksWanted[block.Type]
176		if !wanted {
177			continue
178		}
179
180		if len(block.Labels) > len(blockS.LabelNames) {
181			name := block.Type
182			if len(blockS.LabelNames) == 0 {
183				diags = append(diags, &hcl.Diagnostic{
184					Severity: hcl.DiagError,
185					Summary:  fmt.Sprintf("Extraneous label for %s", name),
186					Detail: fmt.Sprintf(
187						"No labels are expected for %s blocks.", name,
188					),
189					Subject: block.LabelRanges[0].Ptr(),
190					Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
191				})
192			} else {
193				diags = append(diags, &hcl.Diagnostic{
194					Severity: hcl.DiagError,
195					Summary:  fmt.Sprintf("Extraneous label for %s", name),
196					Detail: fmt.Sprintf(
197						"Only %d labels (%s) are expected for %s blocks.",
198						len(blockS.LabelNames), strings.Join(blockS.LabelNames, ", "), name,
199					),
200					Subject: block.LabelRanges[len(blockS.LabelNames)].Ptr(),
201					Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
202				})
203			}
204			continue
205		}
206
207		if len(block.Labels) < len(blockS.LabelNames) {
208			name := block.Type
209			diags = append(diags, &hcl.Diagnostic{
210				Severity: hcl.DiagError,
211				Summary:  fmt.Sprintf("Missing %s for %s", blockS.LabelNames[len(block.Labels)], name),
212				Detail: fmt.Sprintf(
213					"All %s blocks must have %d labels (%s).",
214					name, len(blockS.LabelNames), strings.Join(blockS.LabelNames, ", "),
215				),
216				Subject: &block.OpenBraceRange,
217				Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
218			})
219			continue
220		}
221
222		blocks = append(blocks, block.AsHCLBlock())
223	}
224
225	// We hide blocks only after we've processed all of them, since otherwise
226	// we can't process more than one of the same type.
227	for _, blockS := range schema.Blocks {
228		hiddenBlocks[blockS.Type] = struct{}{}
229	}
230
231	remain := &Body{
232		Attributes: b.Attributes,
233		Blocks:     b.Blocks,
234
235		hiddenAttrs:  hiddenAttrs,
236		hiddenBlocks: hiddenBlocks,
237
238		SrcRange: b.SrcRange,
239		EndRange: b.EndRange,
240	}
241
242	return &hcl.BodyContent{
243		Attributes: attrs,
244		Blocks:     blocks,
245
246		MissingItemRange: b.MissingItemRange(),
247	}, remain, diags
248}
249
250func (b *Body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
251	attrs := make(hcl.Attributes)
252	var diags hcl.Diagnostics
253
254	if len(b.Blocks) > 0 {
255		example := b.Blocks[0]
256		diags = append(diags, &hcl.Diagnostic{
257			Severity: hcl.DiagError,
258			Summary:  fmt.Sprintf("Unexpected %q block", example.Type),
259			Detail:   "Blocks are not allowed here.",
260			Subject:  &example.TypeRange,
261		})
262		// we will continue processing anyway, and return the attributes
263		// we are able to find so that certain analyses can still be done
264		// in the face of errors.
265	}
266
267	if b.Attributes == nil {
268		return attrs, diags
269	}
270
271	for name, attr := range b.Attributes {
272		if _, hidden := b.hiddenAttrs[name]; hidden {
273			continue
274		}
275		attrs[name] = attr.AsHCLAttribute()
276	}
277
278	return attrs, diags
279}
280
281func (b *Body) MissingItemRange() hcl.Range {
282	return hcl.Range{
283		Filename: b.SrcRange.Filename,
284		Start:    b.SrcRange.Start,
285		End:      b.SrcRange.Start,
286	}
287}
288
289// Attributes is the collection of attribute definitions within a body.
290type Attributes map[string]*Attribute
291
292func (a Attributes) walkChildNodes(w internalWalkFunc) {
293	for _, attr := range a {
294		w(attr)
295	}
296}
297
298// Range returns the range of some arbitrary point within the set of
299// attributes, or an invalid range if there are no attributes.
300//
301// This is provided only to complete the Node interface, but has no practical
302// use.
303func (a Attributes) Range() hcl.Range {
304	// An attributes doesn't really have a useful range to report, since
305	// it's just a grouping construct. So we'll arbitrarily take the
306	// range of one of the attributes, or produce an invalid range if we have
307	// none. In practice, there's little reason to ask for the range of
308	// an Attributes.
309	for _, attr := range a {
310		return attr.Range()
311	}
312	return hcl.Range{
313		Filename: "<unknown>",
314	}
315}
316
317// Attribute represents a single attribute definition within a body.
318type Attribute struct {
319	Name string
320	Expr Expression
321
322	SrcRange    hcl.Range
323	NameRange   hcl.Range
324	EqualsRange hcl.Range
325}
326
327func (a *Attribute) walkChildNodes(w internalWalkFunc) {
328	w(a.Expr)
329}
330
331func (a *Attribute) Range() hcl.Range {
332	return a.SrcRange
333}
334
335// AsHCLAttribute returns the block data expressed as a *hcl.Attribute.
336func (a *Attribute) AsHCLAttribute() *hcl.Attribute {
337	if a == nil {
338		return nil
339	}
340	return &hcl.Attribute{
341		Name: a.Name,
342		Expr: a.Expr,
343
344		Range:     a.SrcRange,
345		NameRange: a.NameRange,
346	}
347}
348
349// Blocks is the list of nested blocks within a body.
350type Blocks []*Block
351
352func (bs Blocks) walkChildNodes(w internalWalkFunc) {
353	for _, block := range bs {
354		w(block)
355	}
356}
357
358// Range returns the range of some arbitrary point within the list of
359// blocks, or an invalid range if there are no blocks.
360//
361// This is provided only to complete the Node interface, but has no practical
362// use.
363func (bs Blocks) Range() hcl.Range {
364	if len(bs) > 0 {
365		return bs[0].Range()
366	}
367	return hcl.Range{
368		Filename: "<unknown>",
369	}
370}
371
372// Block represents a nested block structure
373type Block struct {
374	Type   string
375	Labels []string
376	Body   *Body
377
378	TypeRange       hcl.Range
379	LabelRanges     []hcl.Range
380	OpenBraceRange  hcl.Range
381	CloseBraceRange hcl.Range
382}
383
384func (b *Block) walkChildNodes(w internalWalkFunc) {
385	w(b.Body)
386}
387
388func (b *Block) Range() hcl.Range {
389	return hcl.RangeBetween(b.TypeRange, b.CloseBraceRange)
390}
391
392func (b *Block) DefRange() hcl.Range {
393	return hcl.RangeBetween(b.TypeRange, b.OpenBraceRange)
394}
395