]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
1 | package hclsyntax |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "strings" | |
6 | ||
7 | "github.com/hashicorp/hcl2/hcl" | |
8 | ) | |
9 | ||
10 | // AsHCLBlock returns the block data expressed as a *hcl.Block. | |
11 | func (b *Block) AsHCLBlock() *hcl.Block { | |
107c1cdb ND |
12 | if b == nil { |
13 | return nil | |
14 | } | |
15 | ||
15c0b25d AP |
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. | |
33 | type 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 | |
47 | var assertBodyImplBody hcl.Body = &Body{} | |
48 | ||
49 | func (b *Body) walkChildNodes(w internalWalkFunc) { | |
107c1cdb ND |
50 | w(b.Attributes) |
51 | w(b.Blocks) | |
15c0b25d AP |
52 | } |
53 | ||
54 | func (b *Body) Range() hcl.Range { | |
55 | return b.SrcRange | |
56 | } | |
57 | ||
58 | func (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, | |
107c1cdb ND |
89 | Summary: "Unsupported argument", |
90 | Detail: fmt.Sprintf("An argument named %q is not expected here.%s", name, suggestion), | |
15c0b25d AP |
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 { | |
107c1cdb | 110 | suggestion = fmt.Sprintf(" Did you mean to define argument %q? If so, use the equals sign to assign it a value.", blockTy) |
15c0b25d AP |
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 | ||
128 | func (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, | |
107c1cdb ND |
154 | Summary: "Missing required argument", |
155 | Detail: fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name), | |
15c0b25d AP |
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 | ||
250 | func (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, | |
107c1cdb | 258 | Summary: fmt.Sprintf("Unexpected %q block", example.Type), |
15c0b25d | 259 | Detail: "Blocks are not allowed here.", |
107c1cdb | 260 | Subject: &example.TypeRange, |
15c0b25d AP |
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 | ||
281 | func (b *Body) MissingItemRange() hcl.Range { | |
107c1cdb ND |
282 | return hcl.Range{ |
283 | Filename: b.SrcRange.Filename, | |
284 | Start: b.SrcRange.Start, | |
285 | End: b.SrcRange.Start, | |
286 | } | |
15c0b25d AP |
287 | } |
288 | ||
289 | // Attributes is the collection of attribute definitions within a body. | |
290 | type Attributes map[string]*Attribute | |
291 | ||
292 | func (a Attributes) walkChildNodes(w internalWalkFunc) { | |
107c1cdb ND |
293 | for _, attr := range a { |
294 | w(attr) | |
15c0b25d AP |
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. | |
303 | func (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. | |
318 | type Attribute struct { | |
319 | Name string | |
320 | Expr Expression | |
321 | ||
322 | SrcRange hcl.Range | |
323 | NameRange hcl.Range | |
324 | EqualsRange hcl.Range | |
325 | } | |
326 | ||
327 | func (a *Attribute) walkChildNodes(w internalWalkFunc) { | |
107c1cdb | 328 | w(a.Expr) |
15c0b25d AP |
329 | } |
330 | ||
331 | func (a *Attribute) Range() hcl.Range { | |
332 | return a.SrcRange | |
333 | } | |
334 | ||
335 | // AsHCLAttribute returns the block data expressed as a *hcl.Attribute. | |
336 | func (a *Attribute) AsHCLAttribute() *hcl.Attribute { | |
107c1cdb ND |
337 | if a == nil { |
338 | return nil | |
339 | } | |
15c0b25d AP |
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. | |
350 | type Blocks []*Block | |
351 | ||
352 | func (bs Blocks) walkChildNodes(w internalWalkFunc) { | |
107c1cdb ND |
353 | for _, block := range bs { |
354 | w(block) | |
15c0b25d AP |
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. | |
363 | func (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 | |
373 | type 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 | ||
384 | func (b *Block) walkChildNodes(w internalWalkFunc) { | |
107c1cdb | 385 | w(b.Body) |
15c0b25d AP |
386 | } |
387 | ||
388 | func (b *Block) Range() hcl.Range { | |
389 | return hcl.RangeBetween(b.TypeRange, b.CloseBraceRange) | |
390 | } | |
107c1cdb ND |
391 | |
392 | func (b *Block) DefRange() hcl.Range { | |
393 | return hcl.RangeBetween(b.TypeRange, b.OpenBraceRange) | |
394 | } |