json: Add json.ParseWithStartPos function
This is like json.Parse but allows specifying a non-default start position, in case the caller is parsing a fragment from a larger JSON document.
This commit is contained in:
parent
b41530cb36
commit
e72341df8a
@ -8,15 +8,8 @@ import (
|
|||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseFileContent(buf []byte, filename string) (node, hcl.Diagnostics) {
|
func parseFileContent(buf []byte, filename string, start hcl.Pos) (node, hcl.Diagnostics) {
|
||||||
tokens := scan(buf, pos{
|
tokens := scan(buf, pos{Filename: filename, Pos: start})
|
||||||
Filename: filename,
|
|
||||||
Pos: hcl.Pos{
|
|
||||||
Byte: 0,
|
|
||||||
Line: 1,
|
|
||||||
Column: 1,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
p := newPeeker(tokens)
|
p := newPeeker(tokens)
|
||||||
node, diags := parseValue(p)
|
node, diags := parseValue(p)
|
||||||
if len(diags) == 0 && p.Peek().Type != tokenEOF {
|
if len(diags) == 0 && p.Peek().Type != tokenEOF {
|
||||||
|
@ -598,7 +598,49 @@ func TestParse(t *testing.T) {
|
|||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.Input, func(t *testing.T) {
|
t.Run(test.Input, func(t *testing.T) {
|
||||||
got, diag := parseFileContent([]byte(test.Input), "")
|
got, diag := parseFileContent([]byte(test.Input), "", hcl.Pos{Byte: 0, Line: 1, Column: 1})
|
||||||
|
|
||||||
|
if len(diag) != test.DiagCount {
|
||||||
|
t.Errorf("got %d diagnostics; want %d", len(diag), test.DiagCount)
|
||||||
|
for _, d := range diag {
|
||||||
|
t.Logf(" - %s", d.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := deep.Equal(got, test.Want); diff != nil {
|
||||||
|
for _, problem := range diff {
|
||||||
|
t.Error(problem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseWithPos(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Input string
|
||||||
|
StartPos hcl.Pos
|
||||||
|
Want node
|
||||||
|
DiagCount int
|
||||||
|
}{
|
||||||
|
// Simple, single-token constructs
|
||||||
|
{
|
||||||
|
`true`,
|
||||||
|
hcl.Pos{Byte: 0, Line: 3, Column: 10},
|
||||||
|
&booleanVal{
|
||||||
|
Value: true,
|
||||||
|
SrcRange: hcl.Range{
|
||||||
|
Start: hcl.Pos{Line: 3, Column: 10, Byte: 0},
|
||||||
|
End: hcl.Pos{Line: 3, Column: 14, Byte: 4},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.Input, func(t *testing.T) {
|
||||||
|
got, diag := parseFileContent([]byte(test.Input), "", test.StartPos)
|
||||||
|
|
||||||
if len(diag) != test.DiagCount {
|
if len(diag) != test.DiagCount {
|
||||||
t.Errorf("got %d diagnostics; want %d", len(diag), test.DiagCount)
|
t.Errorf("got %d diagnostics; want %d", len(diag), test.DiagCount)
|
||||||
|
@ -18,7 +18,16 @@ import (
|
|||||||
// from its HasErrors method. If HasErrors returns true, the file represents
|
// from its HasErrors method. If HasErrors returns true, the file represents
|
||||||
// the subset of data that was able to be parsed, which may be none.
|
// the subset of data that was able to be parsed, which may be none.
|
||||||
func Parse(src []byte, filename string) (*hcl.File, hcl.Diagnostics) {
|
func Parse(src []byte, filename string) (*hcl.File, hcl.Diagnostics) {
|
||||||
rootNode, diags := parseFileContent(src, filename)
|
return ParseWithStartPos(src, filename, hcl.Pos{Byte: 0, Line: 1, Column: 1})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseWithStartPos attempts to parse like json.Parse, but unlike json.Parse
|
||||||
|
// you can pass a start position of the given JSON as a hcl.Pos.
|
||||||
|
//
|
||||||
|
// In most cases json.Parse should be sufficient, but it can be useful for parsing
|
||||||
|
// a part of JSON with correct positions.
|
||||||
|
func ParseWithStartPos(src []byte, filename string, start hcl.Pos) (*hcl.File, hcl.Diagnostics) {
|
||||||
|
rootNode, diags := parseFileContent(src, filename, start)
|
||||||
|
|
||||||
switch rootNode.(type) {
|
switch rootNode.(type) {
|
||||||
case *objectVal, *arrayVal:
|
case *objectVal, *arrayVal:
|
||||||
|
@ -112,3 +112,73 @@ func TestParse_malformed(t *testing.T) {
|
|||||||
t.Errorf("got nil File; want actual file")
|
t.Errorf("got nil File; want actual file")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseWithStartPos(t *testing.T) {
|
||||||
|
src := `{
|
||||||
|
"foo": {
|
||||||
|
"bar": "baz"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
part := `{
|
||||||
|
"bar": "baz"
|
||||||
|
}`
|
||||||
|
|
||||||
|
file, diags := Parse([]byte(src), "")
|
||||||
|
partFile, partDiags := ParseWithStartPos([]byte(part), "", hcl.Pos{Byte: 0, Line: 2, Column: 10})
|
||||||
|
if len(diags) != 0 {
|
||||||
|
t.Errorf("got %d diagnostics on parse src; want 0", len(diags))
|
||||||
|
for _, diag := range diags {
|
||||||
|
t.Logf("- %s", diag.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(partDiags) != 0 {
|
||||||
|
t.Errorf("got %d diagnostics on parse part src; want 0", len(partDiags))
|
||||||
|
for _, diag := range partDiags {
|
||||||
|
t.Logf("- %s", diag.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if file == nil {
|
||||||
|
t.Errorf("got nil File; want actual file")
|
||||||
|
}
|
||||||
|
if file.Body == nil {
|
||||||
|
t.Fatalf("got nil Body; want actual body")
|
||||||
|
}
|
||||||
|
if partFile == nil {
|
||||||
|
t.Errorf("got nil part File; want actual file")
|
||||||
|
}
|
||||||
|
if partFile.Body == nil {
|
||||||
|
t.Fatalf("got nil part Body; want actual body")
|
||||||
|
}
|
||||||
|
|
||||||
|
content, diags := file.Body.Content(&hcl.BodySchema{
|
||||||
|
Blocks: []hcl.BlockHeaderSchema{{Type: "foo"}},
|
||||||
|
})
|
||||||
|
if len(diags) != 0 {
|
||||||
|
t.Errorf("got %d diagnostics on decode; want 0", len(diags))
|
||||||
|
for _, diag := range diags {
|
||||||
|
t.Logf("- %s", diag.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attrs, diags := content.Blocks[0].Body.JustAttributes()
|
||||||
|
if len(diags) != 0 {
|
||||||
|
t.Errorf("got %d diagnostics on decode; want 0", len(diags))
|
||||||
|
for _, diag := range diags {
|
||||||
|
t.Logf("- %s", diag.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
srcRange := attrs["bar"].Expr.Range()
|
||||||
|
|
||||||
|
partAttrs, diags := partFile.Body.JustAttributes()
|
||||||
|
if len(diags) != 0 {
|
||||||
|
t.Errorf("got %d diagnostics on decode; want 0", len(diags))
|
||||||
|
for _, diag := range diags {
|
||||||
|
t.Logf("- %s", diag.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
partRange := partAttrs["bar"].Expr.Range()
|
||||||
|
|
||||||
|
if srcRange.String() != partRange.String() {
|
||||||
|
t.Errorf("The two ranges did not match: src=%s, part=%s", srcRange, partRange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user