7 "github.com/hashicorp/hcl2/hcl"
8 "github.com/zclconf/go-cty/cty"
11 func parseFileContent(buf []byte, filename string) (node, hcl.Diagnostics) {
12 tokens := scan(buf, pos{
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(),
33 func parseValue(p *peeker) (node, hcl.Diagnostics) {
36 wrapInvalid := func(n node, diags hcl.Diagnostics) (node, hcl.Diagnostics) {
40 return invalidVal{tok.Range}, diags
45 return wrapInvalid(parseObject(p))
47 return wrapInvalid(parseArray(p))
49 return wrapInvalid(parseNumber(p))
51 return wrapInvalid(parseString(p))
53 return wrapInvalid(parseKeyword(p))
55 return wrapInvalid(nil, hcl.Diagnostics{
57 Severity: hcl.DiagError,
58 Summary: "Missing JSON value",
59 Detail: "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.",
64 return wrapInvalid(nil, hcl.Diagnostics{
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.",
73 return wrapInvalid(nil, hcl.Diagnostics{
75 Severity: hcl.DiagError,
76 Summary: "Missing value",
77 Detail: "The JSON data ends prematurely.",
82 return wrapInvalid(nil, hcl.Diagnostics{
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.",
93 func tokenCanStartValue(tok token) bool {
95 case tokenBraceO, tokenBrackO, tokenNumber, tokenString, tokenKeyword:
102 func parseObject(p *peeker) (node, hcl.Diagnostics) {
103 var diags hcl.Diagnostics
106 attrs := []*objectAttr{}
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) {
123 // Ran out of source before we were able to recover,
124 // so we'll bail here and let the caller deal with it.
133 if p.Peek().Type == tokenBraceC {
137 keyNode, keyDiags := parseValue(p)
138 diags = diags.Extend(keyDiags)
143 keyStrNode, ok := keyNode.(*stringVal)
145 return nil, diags.Append(&hcl.Diagnostic{
146 Severity: hcl.DiagError,
147 Summary: "Invalid object property name",
148 Detail: "A JSON object property name must be a string",
149 Subject: keyNode.StartRange().Ptr(),
153 key := keyStrNode.Value
156 if colon.Type != tokenColon {
159 if colon.Type == tokenBraceC || colon.Type == tokenComma {
160 // Catch common mistake of using braces instead of brackets
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,
170 if colon.Type == tokenEquals {
171 // Possible confusion with native HCL syntax.
172 return nil, diags.Append(&hcl.Diagnostic{
173 Severity: hcl.DiagError,
174 Summary: "Missing property value colon",
175 Detail: "JSON uses a colon as its name/value delimiter, not an equals sign.",
176 Subject: &colon.Range,
180 return nil, diags.Append(&hcl.Diagnostic{
181 Severity: hcl.DiagError,
182 Summary: "Missing property value colon",
183 Detail: "A colon must appear between an object property's name and its value.",
184 Subject: &colon.Range,
188 valNode, valDiags := parseValue(p)
189 diags = diags.Extend(valDiags)
194 attrs = append(attrs, &objectAttr{
197 NameRange: keyStrNode.SrcRange,
200 switch p.Peek().Type {
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",
208 Detail: "JSON does not permit a trailing comma after the final property in an object.",
209 Subject: &comma.Range,
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,
221 // Consume the bracket anyway, so that we don't return with the peeker
222 // at a strange place.
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(),
234 return nil, diags.Append(&hcl.Diagnostic{
235 Severity: hcl.DiagError,
236 Summary: "Missing attribute seperator comma",
237 Detail: "A comma must appear between each property definition in an object.",
238 Subject: p.Peek().Range.Ptr(),
247 SrcRange: hcl.RangeBetween(open.Range, close.Range),
248 OpenRange: open.Range,
249 CloseRange: close.Range,
253 func parseArray(p *peeker) (node, hcl.Diagnostics) {
254 var diags hcl.Diagnostics
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) {
274 // Ran out of source before we were able to recover,
275 // so we'll bail here and let the caller deal with it.
284 if p.Peek().Type == tokenBrackC {
288 valNode, valDiags := parseValue(p)
289 diags = diags.Extend(valDiags)
294 vals = append(vals, valNode)
296 switch p.Peek().Type {
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",
304 Detail: "JSON does not permit a trailing comma after the final value in an array.",
305 Subject: &comma.Range,
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(),
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,
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(),
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(),
350 SrcRange: hcl.RangeBetween(open.Range, close.Range),
351 OpenRange: open.Range,
355 func parseNumber(p *peeker) (node, hcl.Diagnostics) {
358 // Use encoding/json to validate the number syntax.
359 // TODO: Do this more directly to produce better diagnostics.
361 err := json.Unmarshal(tok.Bytes, &num)
363 return nil, hcl.Diagnostics{
365 Severity: hcl.DiagError,
366 Summary: "Invalid JSON number",
367 Detail: fmt.Sprintf("There is a syntax error in the given JSON number."),
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))
380 // Should never happen if above passed, since JSON numbers are a subset
381 // of what cty can parse...
382 return nil, hcl.Diagnostics{
384 Severity: hcl.DiagError,
385 Summary: "Invalid JSON number",
386 Detail: fmt.Sprintf("There is a syntax error in the given JSON number."),
393 Value: nv.AsBigFloat(),
398 func parseString(p *peeker) (node, hcl.Diagnostics) {
401 err := json.Unmarshal(tok.Bytes, &str)
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)
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)
419 errRange = hcl.Range{
420 Filename: tok.Range.Filename,
428 var contextRange *hcl.Range
429 if errRange != tok.Range {
430 contextRange = &tok.Range
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{
438 Severity: hcl.DiagError,
439 Summary: "Invalid JSON string",
440 Detail: fmt.Sprintf("There is a syntax error in the given JSON string."),
442 Context: contextRange,
453 func parseKeyword(p *peeker) (node, hcl.Diagnostics) {
455 s := string(tok.Bytes)
472 case "undefined", "NaN", "Infinity":
473 return nil, hcl.Diagnostics{
475 Severity: hcl.DiagError,
476 Summary: "Invalid JSON keyword",
477 Detail: fmt.Sprintf("The JavaScript identifier %q cannot be used in JSON.", s),
483 if suggest := keywordSuggestion(s); suggest != "" {
484 dym = fmt.Sprintf(" Did you mean %q?", suggest)
487 return nil, hcl.Diagnostics{
489 Severity: hcl.DiagError,
490 Summary: "Invalid JSON keyword",
491 Detail: fmt.Sprintf("%q is not a valid JSON keyword.%s", s, dym),