github.com/hashicorp/hcl/v2@v2.20.0/json/parser.go (about)

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