]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/hcl2/hcl/json/parser.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / hcl2 / hcl / json / parser.go
CommitLineData
15c0b25d
AP
1package json
2
3import (
4 "encoding/json"
5 "fmt"
15c0b25d
AP
6
7 "github.com/hashicorp/hcl2/hcl"
107c1cdb 8 "github.com/zclconf/go-cty/cty"
15c0b25d
AP
9)
10
11func parseFileContent(buf []byte, filename string) (node, hcl.Diagnostics) {
12 tokens := scan(buf, pos{
13 Filename: filename,
14 Pos: hcl.Pos{
15 Byte: 0,
16 Line: 1,
17 Column: 1,
18 },
19 })
20 p := newPeeker(tokens)
21 node, diags := parseValue(p)
22 if len(diags) == 0 && p.Peek().Type != tokenEOF {
23 diags = diags.Append(&hcl.Diagnostic{
24 Severity: hcl.DiagError,
25 Summary: "Extraneous data after value",
26 Detail: "Extra characters appear after the JSON value.",
27 Subject: p.Peek().Range.Ptr(),
28 })
29 }
30 return node, diags
31}
32
33func parseValue(p *peeker) (node, hcl.Diagnostics) {
34 tok := p.Peek()
35
36 wrapInvalid := func(n node, diags hcl.Diagnostics) (node, hcl.Diagnostics) {
37 if n != nil {
38 return n, diags
39 }
40 return invalidVal{tok.Range}, diags
41 }
42
43 switch tok.Type {
44 case tokenBraceO:
45 return wrapInvalid(parseObject(p))
46 case tokenBrackO:
47 return wrapInvalid(parseArray(p))
48 case tokenNumber:
49 return wrapInvalid(parseNumber(p))
50 case tokenString:
51 return wrapInvalid(parseString(p))
52 case tokenKeyword:
53 return wrapInvalid(parseKeyword(p))
54 case tokenBraceC:
55 return wrapInvalid(nil, hcl.Diagnostics{
56 {
57 Severity: hcl.DiagError,
107c1cdb 58 Summary: "Missing JSON value",
15c0b25d
AP
59 Detail: "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.",
60 Subject: &tok.Range,
61 },
62 })
63 case tokenBrackC:
64 return wrapInvalid(nil, hcl.Diagnostics{
65 {
66 Severity: hcl.DiagError,
67 Summary: "Missing array element value",
68 Detail: "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.",
69 Subject: &tok.Range,
70 },
71 })
72 case tokenEOF:
73 return wrapInvalid(nil, hcl.Diagnostics{
74 {
75 Severity: hcl.DiagError,
76 Summary: "Missing value",
77 Detail: "The JSON data ends prematurely.",
78 Subject: &tok.Range,
79 },
80 })
81 default:
82 return wrapInvalid(nil, hcl.Diagnostics{
83 {
84 Severity: hcl.DiagError,
85 Summary: "Invalid start of value",
86 Detail: "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.",
87 Subject: &tok.Range,
88 },
89 })
90 }
91}
92
93func tokenCanStartValue(tok token) bool {
94 switch tok.Type {
95 case tokenBraceO, tokenBrackO, tokenNumber, tokenString, tokenKeyword:
96 return true
97 default:
98 return false
99 }
100}
101
102func parseObject(p *peeker) (node, hcl.Diagnostics) {
103 var diags hcl.Diagnostics
104
105 open := p.Read()
106 attrs := []*objectAttr{}
107
108 // recover is used to shift the peeker to what seems to be the end of
109 // our object, so that when we encounter an error we leave the peeker
110 // at a reasonable point in the token stream to continue parsing.
111 recover := func(tok token) {
112 open := 1
113 for {
114 switch tok.Type {
115 case tokenBraceO:
116 open++
117 case tokenBraceC:
118 open--
119 if open <= 1 {
120 return
121 }
122 case tokenEOF:
123 // Ran out of source before we were able to recover,
124 // so we'll bail here and let the caller deal with it.
125 return
126 }
127 tok = p.Read()
128 }
129 }
130
131Token:
132 for {
133 if p.Peek().Type == tokenBraceC {
134 break Token
135 }
136
137 keyNode, keyDiags := parseValue(p)
138 diags = diags.Extend(keyDiags)
139 if keyNode == nil {
140 return nil, diags
141 }
142
143 keyStrNode, ok := keyNode.(*stringVal)
144 if !ok {
145 return nil, diags.Append(&hcl.Diagnostic{
146 Severity: hcl.DiagError,
107c1cdb
ND
147 Summary: "Invalid object property name",
148 Detail: "A JSON object property name must be a string",
15c0b25d
AP
149 Subject: keyNode.StartRange().Ptr(),
150 })
151 }
152
153 key := keyStrNode.Value
154
155 colon := p.Read()
156 if colon.Type != tokenColon {
157 recover(colon)
158
159 if colon.Type == tokenBraceC || colon.Type == tokenComma {
160 // Catch common mistake of using braces instead of brackets
161 // for an object.
162 return nil, diags.Append(&hcl.Diagnostic{
163 Severity: hcl.DiagError,
164 Summary: "Missing object value",
165 Detail: "A JSON object attribute must have a value, introduced by a colon.",
166 Subject: &colon.Range,
167 })
168 }
169
170 if colon.Type == tokenEquals {
171 // Possible confusion with native HCL syntax.
172 return nil, diags.Append(&hcl.Diagnostic{
173 Severity: hcl.DiagError,
107c1cdb 174 Summary: "Missing property value colon",
15c0b25d
AP
175 Detail: "JSON uses a colon as its name/value delimiter, not an equals sign.",
176 Subject: &colon.Range,
177 })
178 }
179
180 return nil, diags.Append(&hcl.Diagnostic{
181 Severity: hcl.DiagError,
107c1cdb
ND
182 Summary: "Missing property value colon",
183 Detail: "A colon must appear between an object property's name and its value.",
15c0b25d
AP
184 Subject: &colon.Range,
185 })
186 }
187
188 valNode, valDiags := parseValue(p)
189 diags = diags.Extend(valDiags)
190 if valNode == nil {
191 return nil, diags
192 }
193
194 attrs = append(attrs, &objectAttr{
195 Name: key,
196 Value: valNode,
197 NameRange: keyStrNode.SrcRange,
198 })
199
200 switch p.Peek().Type {
201 case tokenComma:
202 comma := p.Read()
203 if p.Peek().Type == tokenBraceC {
204 // Special error message for this common mistake
205 return nil, diags.Append(&hcl.Diagnostic{
206 Severity: hcl.DiagError,
207 Summary: "Trailing comma in object",
107c1cdb 208 Detail: "JSON does not permit a trailing comma after the final property in an object.",
15c0b25d
AP
209 Subject: &comma.Range,
210 })
211 }
212 continue Token
213 case tokenEOF:
214 return nil, diags.Append(&hcl.Diagnostic{
215 Severity: hcl.DiagError,
216 Summary: "Unclosed object",
217 Detail: "No closing brace was found for this JSON object.",
218 Subject: &open.Range,
219 })
220 case tokenBrackC:
221 // Consume the bracket anyway, so that we don't return with the peeker
222 // at a strange place.
223 p.Read()
224 return nil, diags.Append(&hcl.Diagnostic{
225 Severity: hcl.DiagError,
226 Summary: "Mismatched braces",
227 Detail: "A JSON object must be closed with a brace, not a bracket.",
228 Subject: p.Peek().Range.Ptr(),
229 })
230 case tokenBraceC:
231 break Token
232 default:
233 recover(p.Read())
234 return nil, diags.Append(&hcl.Diagnostic{
235 Severity: hcl.DiagError,
236 Summary: "Missing attribute seperator comma",
107c1cdb 237 Detail: "A comma must appear between each property definition in an object.",
15c0b25d
AP
238 Subject: p.Peek().Range.Ptr(),
239 })
240 }
241
242 }
243
244 close := p.Read()
245 return &objectVal{
246 Attrs: attrs,
247 SrcRange: hcl.RangeBetween(open.Range, close.Range),
248 OpenRange: open.Range,
249 CloseRange: close.Range,
250 }, diags
251}
252
253func parseArray(p *peeker) (node, hcl.Diagnostics) {
254 var diags hcl.Diagnostics
255
256 open := p.Read()
257 vals := []node{}
258
259 // recover is used to shift the peeker to what seems to be the end of
260 // our array, so that when we encounter an error we leave the peeker
261 // at a reasonable point in the token stream to continue parsing.
262 recover := func(tok token) {
263 open := 1
264 for {
265 switch tok.Type {
266 case tokenBrackO:
267 open++
268 case tokenBrackC:
269 open--
270 if open <= 1 {
271 return
272 }
273 case tokenEOF:
274 // Ran out of source before we were able to recover,
275 // so we'll bail here and let the caller deal with it.
276 return
277 }
278 tok = p.Read()
279 }
280 }
281
282Token:
283 for {
284 if p.Peek().Type == tokenBrackC {
285 break Token
286 }
287
288 valNode, valDiags := parseValue(p)
289 diags = diags.Extend(valDiags)
290 if valNode == nil {
291 return nil, diags
292 }
293
294 vals = append(vals, valNode)
295
296 switch p.Peek().Type {
297 case tokenComma:
298 comma := p.Read()
299 if p.Peek().Type == tokenBrackC {
300 // Special error message for this common mistake
301 return nil, diags.Append(&hcl.Diagnostic{
302 Severity: hcl.DiagError,
303 Summary: "Trailing comma in array",
107c1cdb 304 Detail: "JSON does not permit a trailing comma after the final value in an array.",
15c0b25d
AP
305 Subject: &comma.Range,
306 })
307 }
308 continue Token
309 case tokenColon:
310 recover(p.Read())
311 return nil, diags.Append(&hcl.Diagnostic{
312 Severity: hcl.DiagError,
313 Summary: "Invalid array value",
314 Detail: "A colon is not used to introduce values in a JSON array.",
315 Subject: p.Peek().Range.Ptr(),
316 })
317 case tokenEOF:
318 recover(p.Read())
319 return nil, diags.Append(&hcl.Diagnostic{
320 Severity: hcl.DiagError,
321 Summary: "Unclosed object",
322 Detail: "No closing bracket was found for this JSON array.",
323 Subject: &open.Range,
324 })
325 case tokenBraceC:
326 recover(p.Read())
327 return nil, diags.Append(&hcl.Diagnostic{
328 Severity: hcl.DiagError,
329 Summary: "Mismatched brackets",
330 Detail: "A JSON array must be closed with a bracket, not a brace.",
331 Subject: p.Peek().Range.Ptr(),
332 })
333 case tokenBrackC:
334 break Token
335 default:
336 recover(p.Read())
337 return nil, diags.Append(&hcl.Diagnostic{
338 Severity: hcl.DiagError,
339 Summary: "Missing attribute seperator comma",
340 Detail: "A comma must appear between each value in an array.",
341 Subject: p.Peek().Range.Ptr(),
342 })
343 }
344
345 }
346
347 close := p.Read()
348 return &arrayVal{
349 Values: vals,
350 SrcRange: hcl.RangeBetween(open.Range, close.Range),
351 OpenRange: open.Range,
352 }, diags
353}
354
355func parseNumber(p *peeker) (node, hcl.Diagnostics) {
356 tok := p.Read()
357
358 // Use encoding/json to validate the number syntax.
359 // TODO: Do this more directly to produce better diagnostics.
360 var num json.Number
361 err := json.Unmarshal(tok.Bytes, &num)
362 if err != nil {
363 return nil, hcl.Diagnostics{
364 {
365 Severity: hcl.DiagError,
366 Summary: "Invalid JSON number",
367 Detail: fmt.Sprintf("There is a syntax error in the given JSON number."),
368 Subject: &tok.Range,
369 },
370 }
371 }
372
107c1cdb
ND
373 // We want to guarantee that we parse numbers the same way as cty (and thus
374 // native syntax HCL) would here, so we'll use the cty parser even though
375 // in most other cases we don't actually introduce cty concepts until
376 // decoding time. We'll unwrap the parsed float immediately afterwards, so
377 // the cty value is just a temporary helper.
378 nv, err := cty.ParseNumberVal(string(num))
15c0b25d
AP
379 if err != nil {
380 // Should never happen if above passed, since JSON numbers are a subset
107c1cdb 381 // of what cty can parse...
15c0b25d
AP
382 return nil, hcl.Diagnostics{
383 {
384 Severity: hcl.DiagError,
385 Summary: "Invalid JSON number",
386 Detail: fmt.Sprintf("There is a syntax error in the given JSON number."),
387 Subject: &tok.Range,
388 },
389 }
390 }
391
392 return &numberVal{
107c1cdb 393 Value: nv.AsBigFloat(),
15c0b25d
AP
394 SrcRange: tok.Range,
395 }, nil
396}
397
398func parseString(p *peeker) (node, hcl.Diagnostics) {
399 tok := p.Read()
400 var str string
401 err := json.Unmarshal(tok.Bytes, &str)
402
403 if err != nil {
404 var errRange hcl.Range
405 if serr, ok := err.(*json.SyntaxError); ok {
406 errOfs := serr.Offset
407 errPos := tok.Range.Start
408 errPos.Byte += int(errOfs)
409
410 // TODO: Use the byte offset to properly count unicode
411 // characters for the column, and mark the whole of the
412 // character that was wrong as part of our range.
413 errPos.Column += int(errOfs)
414
415 errEndPos := errPos
416 errEndPos.Byte++
417 errEndPos.Column++
418
419 errRange = hcl.Range{
420 Filename: tok.Range.Filename,
421 Start: errPos,
422 End: errEndPos,
423 }
424 } else {
425 errRange = tok.Range
426 }
427
428 var contextRange *hcl.Range
429 if errRange != tok.Range {
430 contextRange = &tok.Range
431 }
432
433 // FIXME: Eventually we should parse strings directly here so
434 // we can produce a more useful error message in the face fo things
435 // such as invalid escapes, etc.
436 return nil, hcl.Diagnostics{
437 {
438 Severity: hcl.DiagError,
439 Summary: "Invalid JSON string",
440 Detail: fmt.Sprintf("There is a syntax error in the given JSON string."),
441 Subject: &errRange,
442 Context: contextRange,
443 },
444 }
445 }
446
447 return &stringVal{
448 Value: str,
449 SrcRange: tok.Range,
450 }, nil
451}
452
453func parseKeyword(p *peeker) (node, hcl.Diagnostics) {
454 tok := p.Read()
455 s := string(tok.Bytes)
456
457 switch s {
458 case "true":
459 return &booleanVal{
460 Value: true,
461 SrcRange: tok.Range,
462 }, nil
463 case "false":
464 return &booleanVal{
465 Value: false,
466 SrcRange: tok.Range,
467 }, nil
468 case "null":
469 return &nullVal{
470 SrcRange: tok.Range,
471 }, nil
472 case "undefined", "NaN", "Infinity":
473 return nil, hcl.Diagnostics{
474 {
475 Severity: hcl.DiagError,
476 Summary: "Invalid JSON keyword",
477 Detail: fmt.Sprintf("The JavaScript identifier %q cannot be used in JSON.", s),
478 Subject: &tok.Range,
479 },
480 }
481 default:
482 var dym string
483 if suggest := keywordSuggestion(s); suggest != "" {
484 dym = fmt.Sprintf(" Did you mean %q?", suggest)
485 }
486
487 return nil, hcl.Diagnostics{
488 {
489 Severity: hcl.DiagError,
490 Summary: "Invalid JSON keyword",
491 Detail: fmt.Sprintf("%q is not a valid JSON keyword.%s", s, dym),
492 Subject: &tok.Range,
493 },
494 }
495 }
496}