diff --git a/hclpack/didyoumean.go b/hclpack/didyoumean.go deleted file mode 100644 index ec4aa79..0000000 --- a/hclpack/didyoumean.go +++ /dev/null @@ -1,24 +0,0 @@ -package hclpack - -import ( - "github.com/agext/levenshtein" -) - -// nameSuggestion tries to find a name from the given slice of suggested names -// that is close to the given name and returns it if found. If no suggestion -// is close enough, returns the empty string. -// -// The suggestions are tried in order, so earlier suggestions take precedence -// if the given string is similar to two or more suggestions. -// -// This function is intended to be used with a relatively-small number of -// suggestions. It's not optimized for hundreds or thousands of them. -func nameSuggestion(given string, suggestions []string) string { - for _, suggestion := range suggestions { - dist := levenshtein.Distance(given, suggestion, nil) - if dist < 3 { // threshold determined experimentally - return suggestion - } - } - return "" -} diff --git a/hclpack/doc.go b/hclpack/doc.go deleted file mode 100644 index 5e7f76b..0000000 --- a/hclpack/doc.go +++ /dev/null @@ -1,14 +0,0 @@ -// Package hclpack provides a straightforward representation of HCL block/body -// structure that can be easily serialized and deserialized for compact -// transmission (e.g. over a network) without transmitting the full source code. -// -// Expressions are retained in native syntax source form so that their -// evaluation can be delayed until a package structure is decoded by some -// other system that has enough information to populate the evaluation context. -// -// Packed structures retain source location information but do not retain -// actual source code. To make sense of source locations returned in diagnostics -// and via other APIs the caller must somehow gain access to the original source -// code that the packed representation was built from, which is a problem that -// must be solved somehow by the calling application. -package hclpack diff --git a/hclpack/example_test.go b/hclpack/example_test.go deleted file mode 100644 index 6031ad0..0000000 --- a/hclpack/example_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package hclpack_test - -import ( - "bytes" - "encoding/json" - "fmt" - "os" - - "github.com/hashicorp/hcl2/hcl" - "github.com/hashicorp/hcl2/hclpack" -) - -func Example_marshalJSON() { - src := ` - service "example" { - priority = 2 - platform { - os = "linux" - arch = "amd64" - } - process "web" { - exec = ["./webapp"] - } - process "worker" { - exec = ["./worker"] - } - } - ` - - body, diags := hclpack.PackNativeFile([]byte(src), "example.svc", hcl.Pos{Line: 1, Column: 1}) - if diags.HasErrors() { - fmt.Fprintf(os.Stderr, "Failed to parse: %s", diags.Error()) - return - } - - jb, err := body.MarshalJSON() - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to marshal: %s", err) - return - } - - // Normally the compact form is best, but we'll indent just for the sake - // of this example so the result is readable. - var buf bytes.Buffer - json.Indent(&buf, jb, "", " ") - os.Stdout.Write(buf.Bytes()) - - // Output: - // { - // "r": { - // "b": [ - // { - // "h": [ - // "service", - // "example" - // ], - // "b": { - // "a": { - // "priority": { - // "s": "2", - // "r": "ChAKDA4QDhA" - // } - // }, - // "b": [ - // { - // "h": [ - // "platform" - // ], - // "b": { - // "a": { - // "arch": { - // "s": "\"amd64\"", - // "r": "IiwiJCYsKCo" - // }, - // "os": { - // "s": "\"linux\"", - // "r": "FiAWGBogHB4" - // } - // }, - // "r": "Li4" - // }, - // "r": "EhQSFA" - // }, - // { - // "h": [ - // "process", - // "web" - // ], - // "b": { - // "a": { - // "exec": { - // "s": "[\"./webapp\"]", - // "r": "OEA4OjxAPD4" - // } - // }, - // "r": "QkI" - // }, - // "r": "MDYwMjQ2" - // }, - // { - // "h": [ - // "process", - // "worker" - // ], - // "b": { - // "a": { - // "exec": { - // "s": "[\"./worker\"]", - // "r": "TFRMTlBUUFI" - // } - // }, - // "r": "VlY" - // }, - // "r": "REpERkhK" - // } - // ], - // "r": "WFg" - // }, - // "r": "AggCBAYI" - // } - // ], - // "r": "Wlo" - // }, - // "s": [ - // "example.svc" - // ], - // "p": "BAQEAA4OAAICABISAggMABAQAAYGAAICAggIABAQAgYKAAQEAAoKAAICAAoKAAICAgYGAAgIAAYGAAICAAoKAAICAgoKAggIAA4OAAICAAoKAgwQAAgIAAYGAAICABYWAgoKAggIAA4OAAICABAQAgwQAAgIAAYGAAICABYWAgoKAgYGAgQE" - // } -} diff --git a/hclpack/expression.go b/hclpack/expression.go deleted file mode 100644 index 8fa238b..0000000 --- a/hclpack/expression.go +++ /dev/null @@ -1,160 +0,0 @@ -package hclpack - -import ( - "fmt" - - "github.com/zclconf/go-cty/cty" - ctyjson "github.com/zclconf/go-cty/cty/json" - - "github.com/hashicorp/hcl2/hcl" - "github.com/hashicorp/hcl2/hcl/hclsyntax" -) - -// Expression is an implementation of hcl.Expression in terms of some raw -// expression source code. The methods of this type will first parse the -// source code and then pass the call through to the real expression that -// is produced. -type Expression struct { - // Source is the raw source code of the expression, which should be parsed - // as the syntax specified by SourceType. - Source []byte - SourceType ExprSourceType - - // Range_ and StartRange_ describe the physical extents of the expression - // in the original source code. SourceRange_ is its entire range while - // StartRange is just the tokens that introduce the expression type. For - // simple expression types, SourceRange and StartRange are identical. - Range_, StartRange_ hcl.Range -} - -var _ hcl.Expression = (*Expression)(nil) - -// Value implements the Value method of hcl.Expression but with the additional -// step of first parsing the expression source code. This implementation is -// unusual in that it can potentially return syntax errors, whereas other -// Value implementations usually work with already-parsed expressions. -func (e *Expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { - expr, diags := e.Parse() - if diags.HasErrors() { - return cty.DynamicVal, diags - } - - val, moreDiags := expr.Value(ctx) - diags = append(diags, moreDiags...) - return val, diags -} - -// Variables implements the Variables method of hcl.Expression but with the -// additional step of first parsing the expression source code. -// -// Since this method cannot return errors, it will return a nil slice if -// parsing fails, indicating that no variables are present. This is okay in -// practice because a subsequent call to Value would fail with syntax errors -// regardless of what variables are in the context. -func (e *Expression) Variables() []hcl.Traversal { - expr, diags := e.Parse() - if diags.HasErrors() { - return nil - } - return expr.Variables() -} - -// UnwrapExpression parses and returns the underlying expression, if possible. -// -// This is essentially the same as Parse but without the ability to return an -// error; it is here only to support the static analysis facilities in the -// main HCL package (ExprList, ExprMap, etc). If any error is encountered -// during parsing, the result is a static expression that always returns -// cty.DynamicVal. -// -// This function does not impose any further conversions on the underlying -// expression, so the result may still not be suitable for the static analysis -// functions, depending on the source type of the expression and thus what -// type of physical expression it becomes after decoding. -func (e *Expression) UnwrapExpression() hcl.Expression { - expr, diags := e.Parse() - if diags.HasErrors() { - return hcl.StaticExpr(cty.DynamicVal, e.Range_) - } - return expr -} - -func (e *Expression) Range() hcl.Range { - return e.Range_ -} - -func (e *Expression) StartRange() hcl.Range { - return e.StartRange_ -} - -// Parse attempts to parse the source code of the receiving expression using -// its indicated source type, returning the expression if possible and any -// diagnostics produced during parsing. -func (e *Expression) Parse() (hcl.Expression, hcl.Diagnostics) { - switch e.SourceType { - case ExprNative: - return hclsyntax.ParseExpression(e.Source, e.Range_.Filename, e.Range_.Start) - case ExprTemplate: - return hclsyntax.ParseTemplate(e.Source, e.Range_.Filename, e.Range_.Start) - case ExprLiteralJSON: - ty, err := ctyjson.ImpliedType(e.Source) - if err != nil { - return nil, hcl.Diagnostics{ - { - Severity: hcl.DiagError, - Summary: "Invalid JSON value", - Detail: fmt.Sprintf("The JSON representation of this expression is invalid: %s.", err), - Subject: &e.Range_, - }, - } - } - val, err := ctyjson.Unmarshal(e.Source, ty) - if err != nil { - return nil, hcl.Diagnostics{ - { - Severity: hcl.DiagError, - Summary: "Invalid JSON value", - Detail: fmt.Sprintf("The JSON representation of this expression is invalid: %s.", err), - Subject: &e.Range_, - }, - } - } - return hcl.StaticExpr(val, e.Range_), nil - default: - // This should never happen for a valid Expression. - return nil, hcl.Diagnostics{ - { - Severity: hcl.DiagError, - Summary: "Invalid expression source type", - Detail: fmt.Sprintf("Packed version of this expression has an invalid source type %s. This is always a bug.", e.SourceType), - Subject: &e.Range_, - }, - } - } -} - -func (e *Expression) addRanges(rngs map[hcl.Range]struct{}) { - rngs[e.Range_] = struct{}{} - rngs[e.StartRange_] = struct{}{} -} - -// ExprSourceType defines the syntax type used for an expression's source code, -// which is then used to select a suitable parser for it when evaluating. -type ExprSourceType rune - -//go:generate stringer -type ExprSourceType - -const ( - // ExprNative indicates that an expression must be parsed as native - // expression syntax, with hclsyntax.ParseExpression. - ExprNative ExprSourceType = 'N' - - // ExprTemplate indicates that an expression must be parsed as native - // template syntax, with hclsyntax.ParseTemplate. - ExprTemplate ExprSourceType = 'T' - - // ExprLiteralJSON indicates that an expression must be parsed as JSON and - // treated literally, using cty/json. This can be used when populating - // literal attribute values from a non-HCL source. - ExprLiteralJSON ExprSourceType = 'L' -) diff --git a/hclpack/expression_test.go b/hclpack/expression_test.go deleted file mode 100644 index 07048a8..0000000 --- a/hclpack/expression_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package hclpack - -import ( - "testing" - - "github.com/hashicorp/hcl2/hcl" - "github.com/zclconf/go-cty/cty" -) - -func TestExpressionValue(t *testing.T) { - tests := map[string]struct { - Expr *Expression - Ctx *hcl.EvalContext - Want cty.Value - }{ - "simple literal expr": { - &Expression{ - Source: []byte(`"hello"`), - SourceType: ExprNative, - }, - nil, - cty.StringVal("hello"), - }, - "simple literal template": { - &Expression{ - Source: []byte(`hello ${5}`), - SourceType: ExprTemplate, - }, - nil, - cty.StringVal("hello 5"), - }, - "expr with variable": { - &Expression{ - Source: []byte(`foo`), - SourceType: ExprNative, - }, - &hcl.EvalContext{ - Variables: map[string]cty.Value{ - "foo": cty.StringVal("bar"), - }, - }, - cty.StringVal("bar"), - }, - "template with variable": { - &Expression{ - Source: []byte(`foo ${foo}`), - SourceType: ExprTemplate, - }, - &hcl.EvalContext{ - Variables: map[string]cty.Value{ - "foo": cty.StringVal("bar"), - }, - }, - cty.StringVal("foo bar"), - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - got, diags := test.Expr.Value(test.Ctx) - for _, diag := range diags { - t.Errorf("unexpected diagnostic: %s", diag.Error()) - } - - if !test.Want.RawEquals(got) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} diff --git a/hclpack/exprsourcetype_string.go b/hclpack/exprsourcetype_string.go deleted file mode 100644 index 5052841..0000000 --- a/hclpack/exprsourcetype_string.go +++ /dev/null @@ -1,24 +0,0 @@ -// Code generated by "stringer -type ExprSourceType"; DO NOT EDIT. - -package hclpack - -import "strconv" - -const ( - _ExprSourceType_name_0 = "ExprLiteralJSON" - _ExprSourceType_name_1 = "ExprNative" - _ExprSourceType_name_2 = "ExprTemplate" -) - -func (i ExprSourceType) String() string { - switch { - case i == 76: - return _ExprSourceType_name_0 - case i == 78: - return _ExprSourceType_name_1 - case i == 84: - return _ExprSourceType_name_2 - default: - return "ExprSourceType(" + strconv.FormatInt(int64(i), 10) + ")" - } -} diff --git a/hclpack/json_marshal.go b/hclpack/json_marshal.go deleted file mode 100644 index 765eb76..0000000 --- a/hclpack/json_marshal.go +++ /dev/null @@ -1,211 +0,0 @@ -package hclpack - -import ( - "encoding/json" - - "github.com/hashicorp/hcl2/hcl" -) - -// MarshalJSON is an implementation of Marshaler from encoding/json, allowing -// bodies to be included in other types that are JSON-marshalable. -// -// The result of MarshalJSON is optimized for compactness rather than easy -// human consumption/editing. Use UnmarshalJSON to decode it. -func (b *Body) MarshalJSON() ([]byte, error) { - rngs := make(map[hcl.Range]struct{}) - b.addRanges(rngs) - - fns, posList, posMap := packPositions(rngs) - - head := jsonHeader{ - Body: b.forJSON(posMap), - Sources: fns, - Pos: posList, - } - - return json.Marshal(&head) -} - -func (b *Body) forJSON(pos map[string]map[hcl.Pos]posOfs) bodyJSON { - var ret bodyJSON - - if len(b.Attributes) > 0 { - ret.Attrs = make(map[string]attrJSON, len(b.Attributes)) - for name, attr := range b.Attributes { - ret.Attrs[name] = attr.forJSON(pos) - } - } - if len(b.ChildBlocks) > 0 { - ret.Blocks = make([]blockJSON, len(b.ChildBlocks)) - for i, block := range b.ChildBlocks { - ret.Blocks[i] = block.forJSON(pos) - } - } - ret.Ranges = make(rangesPacked, 1) - ret.Ranges[0] = packRange(b.MissingItemRange_, pos) - - return ret -} - -func (a *Attribute) forJSON(pos map[string]map[hcl.Pos]posOfs) attrJSON { - var ret attrJSON - - ret.Source = string(a.Expr.Source) - switch a.Expr.SourceType { - case ExprNative: - ret.Syntax = 0 - case ExprTemplate: - ret.Syntax = 1 - case ExprLiteralJSON: - ret.Syntax = 2 - } - ret.Ranges = make(rangesPacked, 4) - ret.Ranges[0] = packRange(a.Range, pos) - ret.Ranges[1] = packRange(a.NameRange, pos) - ret.Ranges[2] = packRange(a.Expr.Range_, pos) - ret.Ranges[3] = packRange(a.Expr.StartRange_, pos) - - return ret -} - -func (b *Block) forJSON(pos map[string]map[hcl.Pos]posOfs) blockJSON { - var ret blockJSON - - ret.Header = make([]string, len(b.Labels)+1) - ret.Header[0] = b.Type - copy(ret.Header[1:], b.Labels) - ret.Body = b.Body.forJSON(pos) - ret.Ranges = make(rangesPacked, 2+len(b.LabelRanges)) - ret.Ranges[0] = packRange(b.DefRange, pos) - ret.Ranges[1] = packRange(b.TypeRange, pos) - for i, rng := range b.LabelRanges { - ret.Ranges[i+2] = packRange(rng, pos) - } - - return ret -} - -// UnmarshalJSON is an implementation of Unmarshaler from encoding/json, -// allowing bodies to be included in other types that are JSON-unmarshalable. -func (b *Body) UnmarshalJSON(data []byte) error { - var head jsonHeader - err := json.Unmarshal(data, &head) - if err != nil { - return err - } - - fns := head.Sources - positions := head.Pos.Unpack() - - *b = head.Body.decode(fns, positions) - - return nil -} - -type jsonHeader struct { - Body bodyJSON `json:"r"` - - Sources []string `json:"s,omitempty"` - Pos positionsPacked `json:"p,omitempty"` -} - -type bodyJSON struct { - // Files are the source filenames that were involved in - Attrs map[string]attrJSON `json:"a,omitempty"` - Blocks []blockJSON `json:"b,omitempty"` - - // Ranges contains the MissingItemRange - Ranges rangesPacked `json:"r,omitempty"` -} - -func (bj *bodyJSON) decode(fns []string, positions []position) Body { - var ret Body - - if len(bj.Attrs) > 0 { - ret.Attributes = make(map[string]Attribute, len(bj.Attrs)) - for name, aj := range bj.Attrs { - ret.Attributes[name] = aj.decode(fns, positions) - } - } - - if len(bj.Blocks) > 0 { - ret.ChildBlocks = make([]Block, len(bj.Blocks)) - for i, blj := range bj.Blocks { - ret.ChildBlocks[i] = blj.decode(fns, positions) - } - } - - ret.MissingItemRange_ = bj.Ranges.UnpackIdx(fns, positions, 0) - - return ret -} - -type attrJSON struct { - // To keep things compact, in the JSON encoding we flatten the - // expression down into the attribute object, since overhead - // for attributes adds up in a complex config. - Source string `json:"s"` - Syntax int `json:"t,omitempty"` // omitted for 0=native - - // Ranges contains the Range, NameRange, Expr.Range, Expr.StartRange - Ranges rangesPacked `json:"r,omitempty"` -} - -func (aj *attrJSON) decode(fns []string, positions []position) Attribute { - var ret Attribute - - ret.Expr.Source = []byte(aj.Source) - switch aj.Syntax { - case 0: - ret.Expr.SourceType = ExprNative - case 1: - ret.Expr.SourceType = ExprTemplate - case 2: - ret.Expr.SourceType = ExprLiteralJSON - } - - ret.Range = aj.Ranges.UnpackIdx(fns, positions, 0) - ret.NameRange = aj.Ranges.UnpackIdx(fns, positions, 1) - ret.Expr.Range_ = aj.Ranges.UnpackIdx(fns, positions, 2) - ret.Expr.StartRange_ = aj.Ranges.UnpackIdx(fns, positions, 3) - if ret.Expr.StartRange_ == (hcl.Range{}) { - // If the start range wasn't present then we'll just use the Range - ret.Expr.StartRange_ = ret.Expr.Range_ - } - - return ret -} - -type blockJSON struct { - // Header is the type followed by any labels. We flatten this here - // to keep the JSON encoding compact. - Header []string `json:"h"` - Body bodyJSON `json:"b,omitempty"` - - // Ranges contains the DefRange followed by the TypeRange and then - // each of the label ranges in turn. - Ranges rangesPacked `json:"r,omitempty"` -} - -func (blj *blockJSON) decode(fns []string, positions []position) Block { - var ret Block - - if len(blj.Header) > 0 { // If the header is invalid then we'll end up with an empty type - ret.Type = blj.Header[0] - } - if len(blj.Header) > 1 { - ret.Labels = blj.Header[1:] - } - ret.Body = blj.Body.decode(fns, positions) - - ret.DefRange = blj.Ranges.UnpackIdx(fns, positions, 0) - ret.TypeRange = blj.Ranges.UnpackIdx(fns, positions, 1) - if len(ret.Labels) > 0 { - ret.LabelRanges = make([]hcl.Range, len(ret.Labels)) - for i := range ret.Labels { - ret.LabelRanges[i] = blj.Ranges.UnpackIdx(fns, positions, i+2) - } - } - - return ret -} diff --git a/hclpack/json_marshal_test.go b/hclpack/json_marshal_test.go deleted file mode 100644 index 279c612..0000000 --- a/hclpack/json_marshal_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package hclpack - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - - "github.com/hashicorp/hcl2/hcl" -) - -func TestJSONRoundTrip(t *testing.T) { - src := ` - service "example" { - priority = 2 - platform { - os = "linux" - arch = "amd64" - } - process "web" { - exec = ["./webapp"] - } - process "worker" { - exec = ["./worker"] - } - } - ` - - startBody, diags := PackNativeFile([]byte(src), "example.svc", hcl.Pos{Line: 1, Column: 1}) - if diags.HasErrors() { - t.Fatalf("Failed to parse: %s", diags.Error()) - } - - jb, err := startBody.MarshalJSON() - if err != nil { - t.Fatalf("Failed to marshal: %s", err) - } - - endBody := &Body{} - err = endBody.UnmarshalJSON(jb) - if err != nil { - t.Fatalf("Failed to unmarshal: %s", err) - } - - if !cmp.Equal(startBody, endBody) { - t.Errorf("incorrect result\n%s", cmp.Diff(startBody, endBody)) - } -} diff --git a/hclpack/pack_native.go b/hclpack/pack_native.go deleted file mode 100644 index 05f67a6..0000000 --- a/hclpack/pack_native.go +++ /dev/null @@ -1,58 +0,0 @@ -package hclpack - -import ( - "github.com/hashicorp/hcl2/hcl" - "github.com/hashicorp/hcl2/hcl/hclsyntax" -) - -// PackNativeFile parses the given source code as HCL native syntax and packs -// it into a hclpack Body ready to be marshalled. -// -// If the given source code contains syntax errors then error diagnostics will -// be returned. A non-nil body might still be returned in this case, which -// allows a cautious caller to still do certain analyses on the result. -func PackNativeFile(src []byte, filename string, start hcl.Pos) (*Body, hcl.Diagnostics) { - f, diags := hclsyntax.ParseConfig(src, filename, start) - rootBody := f.Body.(*hclsyntax.Body) - return packNativeBody(rootBody, src), diags -} - -func packNativeBody(body *hclsyntax.Body, src []byte) *Body { - ret := &Body{} - for name, attr := range body.Attributes { - exprRng := attr.Expr.Range() - exprStartRng := attr.Expr.StartRange() - exprSrc := exprRng.SliceBytes(src) - ret.setAttribute(name, Attribute{ - Expr: Expression{ - Source: exprSrc, - SourceType: ExprNative, - - Range_: exprRng, - StartRange_: exprStartRng, - }, - Range: attr.Range(), - NameRange: attr.NameRange, - }) - } - - for _, block := range body.Blocks { - childBody := packNativeBody(block.Body, src) - defRange := block.TypeRange - if len(block.LabelRanges) > 0 { - defRange = hcl.RangeBetween(defRange, block.LabelRanges[len(block.LabelRanges)-1]) - } - ret.appendBlock(Block{ - Type: block.Type, - Labels: block.Labels, - Body: *childBody, - TypeRange: block.TypeRange, - DefRange: defRange, - LabelRanges: block.LabelRanges, - }) - } - - ret.MissingItemRange_ = body.EndRange - - return ret -} diff --git a/hclpack/pack_native_test.go b/hclpack/pack_native_test.go deleted file mode 100644 index 651b98f..0000000 --- a/hclpack/pack_native_test.go +++ /dev/null @@ -1,166 +0,0 @@ -package hclpack - -import ( - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - - "github.com/hashicorp/hcl2/hcl" -) - -func TestPackNativeFile(t *testing.T) { - src := ` -foo = "bar" -baz = "boz" - -child { - a = b + c -} - -another_child "foo" "bar" {} -` - - got, diags := PackNativeFile([]byte(src), "", hcl.Pos{Line: 1, Column: 1}) - for _, diag := range diags { - t.Errorf("unexpected diagnostic: %s", diag.Error()) - } - - want := &Body{ - Attributes: map[string]Attribute{ - "baz": { - Expr: Expression{ - Source: []byte(`"boz"`), - SourceType: ExprNative, - Range_: hcl.Range{ - Start: hcl.Pos{Line: 3, Column: 7, Byte: 19}, - End: hcl.Pos{Line: 3, Column: 12, Byte: 24}, - }, - StartRange_: hcl.Range{ - Start: hcl.Pos{Line: 3, Column: 8, Byte: 20}, - End: hcl.Pos{Line: 3, Column: 11, Byte: 23}, - }, - }, - Range: hcl.Range{ - Start: hcl.Pos{Line: 3, Column: 1, Byte: 13}, - End: hcl.Pos{Line: 3, Column: 12, Byte: 24}, - }, - NameRange: hcl.Range{ - Start: hcl.Pos{Line: 3, Column: 1, Byte: 13}, - End: hcl.Pos{Line: 3, Column: 4, Byte: 16}, - }, - }, - "foo": { - Expr: Expression{ - Source: []byte(`"bar"`), - SourceType: ExprNative, - Range_: hcl.Range{ - Start: hcl.Pos{Line: 2, Column: 7, Byte: 7}, - End: hcl.Pos{Line: 2, Column: 12, Byte: 12}, - }, - StartRange_: hcl.Range{ - Start: hcl.Pos{Line: 2, Column: 8, Byte: 8}, - End: hcl.Pos{Line: 2, Column: 11, Byte: 11}, - }, - }, - Range: hcl.Range{ - Start: hcl.Pos{Line: 2, Column: 1, Byte: 1}, - End: hcl.Pos{Line: 2, Column: 12, Byte: 12}, - }, - NameRange: hcl.Range{ - Start: hcl.Pos{Line: 2, Column: 1, Byte: 1}, - End: hcl.Pos{Line: 2, Column: 4, Byte: 4}, - }, - }, - }, - ChildBlocks: []Block{ - { - Type: "child", - Body: Body{ - Attributes: map[string]Attribute{ - "a": { - Expr: Expression{ - Source: []byte(`b + c`), - SourceType: ExprNative, - Range_: hcl.Range{ - Start: hcl.Pos{Line: 6, Column: 7, Byte: 40}, - End: hcl.Pos{Line: 6, Column: 12, Byte: 45}, - }, - StartRange_: hcl.Range{ - Start: hcl.Pos{Line: 6, Column: 7, Byte: 40}, - End: hcl.Pos{Line: 6, Column: 8, Byte: 41}, - }, - }, - Range: hcl.Range{ - Start: hcl.Pos{Line: 6, Column: 3, Byte: 36}, - End: hcl.Pos{Line: 6, Column: 12, Byte: 45}, - }, - NameRange: hcl.Range{ - Start: hcl.Pos{Line: 6, Column: 3, Byte: 36}, - End: hcl.Pos{Line: 6, Column: 4, Byte: 37}, - }, - }, - }, - MissingItemRange_: hcl.Range{ - Start: hcl.Pos{Line: 7, Column: 2, Byte: 47}, - End: hcl.Pos{Line: 7, Column: 2, Byte: 47}, - }, - }, - DefRange: hcl.Range{ - Start: hcl.Pos{Line: 5, Column: 1, Byte: 26}, - End: hcl.Pos{Line: 5, Column: 6, Byte: 31}, - }, - TypeRange: hcl.Range{ - Start: hcl.Pos{Line: 5, Column: 1, Byte: 26}, - End: hcl.Pos{Line: 5, Column: 6, Byte: 31}, - }, - }, - { - Type: "another_child", - Labels: []string{"foo", "bar"}, - Body: Body{ - MissingItemRange_: hcl.Range{ - Start: hcl.Pos{Line: 9, Column: 29, Byte: 77}, - End: hcl.Pos{Line: 9, Column: 29, Byte: 77}, - }, - }, - DefRange: hcl.Range{ - Start: hcl.Pos{Line: 9, Column: 1, Byte: 49}, - End: hcl.Pos{Line: 9, Column: 26, Byte: 74}, - }, - TypeRange: hcl.Range{ - Start: hcl.Pos{Line: 9, Column: 1, Byte: 49}, - End: hcl.Pos{Line: 9, Column: 14, Byte: 62}, - }, - LabelRanges: []hcl.Range{ - hcl.Range{ - Start: hcl.Pos{Line: 9, Column: 15, Byte: 63}, - End: hcl.Pos{Line: 9, Column: 20, Byte: 68}, - }, - hcl.Range{ - Start: hcl.Pos{Line: 9, Column: 21, Byte: 69}, - End: hcl.Pos{Line: 9, Column: 26, Byte: 74}, - }, - }, - }, - }, - MissingItemRange_: hcl.Range{ - Start: hcl.Pos{Line: 10, Column: 1, Byte: 78}, - End: hcl.Pos{Line: 10, Column: 1, Byte: 78}, - }, - } - - if !cmp.Equal(want, got) { - bytesAsString := func(s []byte) string { - return string(s) - } - posAsString := func(pos hcl.Pos) string { - return fmt.Sprintf("%#v", pos) - } - t.Errorf("wrong result\n%s", cmp.Diff( - want, got, - cmp.Transformer("bytesAsString", bytesAsString), - cmp.Transformer("posAsString", posAsString), - )) - } -} diff --git a/hclpack/positions_packed.go b/hclpack/positions_packed.go deleted file mode 100644 index 29c7144..0000000 --- a/hclpack/positions_packed.go +++ /dev/null @@ -1,326 +0,0 @@ -package hclpack - -import ( - "encoding/base64" - "sort" - - "github.com/hashicorp/hcl2/hcl" -) - -// positionsPacked is a delta-based representation of source positions -// that implements encoding.TextMarshaler and encoding.TextUnmarshaler using -// a compact variable-length quantity encoding to mimimize the overhead of -// storing source positions. -// -// Serializations of the other types in this package can refer to positions -// in a positionsPacked by index. -type positionsPacked []positionPacked - -func (pp positionsPacked) MarshalBinary() ([]byte, error) { - lenInt := len(pp) * 4 // each positionPacked contains four ints, but we don't include the fileidx - - // guess avg of ~1.25 bytes per int, in which case we'll avoid further allocation - buf := newVLQBuf(lenInt + (lenInt / 4)) - var lastFileIdx int - for _, ppr := range pp { - // Rather than writing out the same file index over and over, we instead - // insert a ; delimiter each time it increases. Since it's common for - // for a body to be entirely in one file, this can lead to considerable - // savings in that case. - delims := ppr.FileIdx - lastFileIdx - lastFileIdx = ppr.FileIdx - for i := 0; i < delims; i++ { - buf = buf.AppendRawByte(';') - } - buf = buf.AppendInt(ppr.LineDelta) - buf = buf.AppendInt(ppr.ColumnDelta) - buf = buf.AppendInt(ppr.ByteDelta) - } - - return buf.Bytes(), nil -} - -func (pp positionsPacked) MarshalText() ([]byte, error) { - raw, err := pp.MarshalBinary() - if err != nil { - return nil, err - } - - l := base64.RawStdEncoding.EncodedLen(len(raw)) - ret := make([]byte, l) - base64.RawStdEncoding.Encode(ret, raw) - return ret, nil -} - -func (pp *positionsPacked) UnmarshalBinary(data []byte) error { - buf := vlqBuf(data) - var ret positionsPacked - fileIdx := 0 - for len(buf) > 0 { - if buf[0] == ';' { - // Starting a new file, then. - fileIdx++ - buf = buf[1:] - continue - } - - var ppr positionPacked - var err error - ppr.FileIdx = fileIdx - ppr.LineDelta, buf, err = buf.ReadInt() - if err != nil { - return err - } - ppr.ColumnDelta, buf, err = buf.ReadInt() - if err != nil { - return err - } - ppr.ByteDelta, buf, err = buf.ReadInt() - if err != nil { - return err - } - ret = append(ret, ppr) - } - *pp = ret - return nil -} - -func (pp *positionsPacked) UnmarshalText(data []byte) error { - maxL := base64.RawStdEncoding.DecodedLen(len(data)) - into := make([]byte, maxL) - realL, err := base64.RawStdEncoding.Decode(into, data) - if err != nil { - return err - } - return pp.UnmarshalBinary(into[:realL]) -} - -type position struct { - FileIdx int - Pos hcl.Pos -} - -func (pp positionsPacked) Unpack() []position { - ret := make([]position, len(pp)) - var accPos hcl.Pos - var accFileIdx int - - for i, relPos := range pp { - if relPos.FileIdx != accFileIdx { - accPos = hcl.Pos{} // reset base position for each new file - accFileIdx = pp[i].FileIdx - } - if relPos.LineDelta > 0 { - accPos.Column = 0 // reset column position for each new line - } - accPos.Line += relPos.LineDelta - accPos.Column += relPos.ColumnDelta - accPos.Byte += relPos.ByteDelta - ret[i] = position{ - FileIdx: relPos.FileIdx, - Pos: accPos, - } - } - - return ret -} - -type positionPacked struct { - FileIdx int - LineDelta, ColumnDelta, ByteDelta int -} - -func (pp positionsPacked) Len() int { - return len(pp) -} - -func (pp positionsPacked) Less(i, j int) bool { - return pp[i].FileIdx < pp[j].FileIdx -} - -func (pp positionsPacked) Swap(i, j int) { - pp[i], pp[j] = pp[j], pp[i] -} - -// posOfs is an index into a positionsPacked. The zero value of this type -// represents the absense of a position. -type posOfs int - -func newPosOffs(idx int) posOfs { - return posOfs(idx + 1) -} - -func (o posOfs) Index() int { - return int(o - 1) -} - -// rangePacked is a range represented as two indexes into a positionsPacked. -// This implements encoding.TextMarshaler and encoding.TextUnmarshaler using -// a compact variable-length quantity encoding. -type rangePacked struct { - Start posOfs - End posOfs -} - -func packRange(rng hcl.Range, pos map[string]map[hcl.Pos]posOfs) rangePacked { - return rangePacked{ - Start: pos[rng.Filename][rng.Start], - End: pos[rng.Filename][rng.End], - } -} - -func (rp rangePacked) Unpack(fns []string, poss []position) hcl.Range { - startIdx := rp.Start.Index() - endIdx := rp.End.Index() - if startIdx < 0 && startIdx >= len(poss) { - return hcl.Range{} // out of bounds, so invalid - } - if endIdx < 0 && endIdx >= len(poss) { - return hcl.Range{} // out of bounds, so invalid - } - startPos := poss[startIdx] - endPos := poss[endIdx] - fnIdx := startPos.FileIdx - var fn string - if fnIdx >= 0 && fnIdx < len(fns) { - fn = fns[fnIdx] - } - return hcl.Range{ - Filename: fn, - Start: startPos.Pos, - End: endPos.Pos, - } -} - -// rangesPacked represents a sequence of ranges, packed compactly into a single -// string during marshaling. -type rangesPacked []rangePacked - -func (rp rangesPacked) MarshalBinary() ([]byte, error) { - lenInt := len(rp) * 2 // each positionPacked contains two ints - - // guess avg of ~1.25 bytes per int, in which case we'll avoid further allocation - buf := newVLQBuf(lenInt + (lenInt / 4)) - for _, rpr := range rp { - buf = buf.AppendInt(int(rpr.Start)) // intentionally storing these as 1-based offsets - buf = buf.AppendInt(int(rpr.End)) - } - - return buf.Bytes(), nil -} - -func (rp rangesPacked) MarshalText() ([]byte, error) { - raw, err := rp.MarshalBinary() - if err != nil { - return nil, err - } - - l := base64.RawStdEncoding.EncodedLen(len(raw)) - ret := make([]byte, l) - base64.RawStdEncoding.Encode(ret, raw) - return ret, nil -} - -func (rp *rangesPacked) UnmarshalBinary(data []byte) error { - buf := vlqBuf(data) - var ret rangesPacked - for len(buf) > 0 { - var startInt, endInt int - var err error - startInt, buf, err = buf.ReadInt() - if err != nil { - return err - } - endInt, buf, err = buf.ReadInt() - if err != nil { - return err - } - ret = append(ret, rangePacked{ - Start: posOfs(startInt), // these are stored as 1-based offsets, so safe to convert directly - End: posOfs(endInt), - }) - } - *rp = ret - return nil -} - -func (rp *rangesPacked) UnmarshalText(data []byte) error { - maxL := base64.RawStdEncoding.DecodedLen(len(data)) - into := make([]byte, maxL) - realL, err := base64.RawStdEncoding.Decode(into, data) - if err != nil { - return err - } - return rp.UnmarshalBinary(into[:realL]) -} - -func (rps rangesPacked) UnpackIdx(fns []string, poss []position, idx int) hcl.Range { - if idx < 0 || idx >= len(rps) { - return hcl.Range{} // out of bounds, so invalid - } - return rps[idx].Unpack(fns, poss) -} - -// packPositions will find the distinct positions from the given ranges -// and then pack them into a positionsPacked, along with a lookup table to find -// the encoded offset of each distinct position. -func packPositions(rngs map[hcl.Range]struct{}) (fns []string, poss positionsPacked, posMap map[string]map[hcl.Pos]posOfs) { - const noOfs = posOfs(0) - - posByFile := make(map[string][]hcl.Pos) - for rng := range rngs { - fn := rng.Filename - posByFile[fn] = append(posByFile[fn], rng.Start) - posByFile[fn] = append(posByFile[fn], rng.End) - } - fns = make([]string, 0, len(posByFile)) - for fn := range posByFile { - fns = append(fns, fn) - } - sort.Strings(fns) - - var retPos positionsPacked - posMap = make(map[string]map[hcl.Pos]posOfs) - for fileIdx, fn := range fns { - poss := posByFile[fn] - sort.Sort(sortPositions(poss)) - var prev hcl.Pos - for _, pos := range poss { - if _, exists := posMap[fn][pos]; exists { - continue - } - ofs := newPosOffs(len(retPos)) - if pos.Line != prev.Line { - // Column indices start from zero for each new line. - prev.Column = 0 - } - retPos = append(retPos, positionPacked{ - FileIdx: fileIdx, - LineDelta: pos.Line - prev.Line, - ColumnDelta: pos.Column - prev.Column, - ByteDelta: pos.Byte - prev.Byte, - }) - if posMap[fn] == nil { - posMap[fn] = make(map[hcl.Pos]posOfs) - } - posMap[fn][pos] = ofs - prev = pos - } - } - - return fns, retPos, posMap -} - -type sortPositions []hcl.Pos - -func (sp sortPositions) Len() int { - return len(sp) -} - -func (sp sortPositions) Less(i, j int) bool { - return sp[i].Byte < sp[j].Byte -} - -func (sp sortPositions) Swap(i, j int) { - sp[i], sp[j] = sp[j], sp[i] -} diff --git a/hclpack/positions_packed_test.go b/hclpack/positions_packed_test.go deleted file mode 100644 index fbb2ca4..0000000 --- a/hclpack/positions_packed_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package hclpack - -import ( - "testing" - - "github.com/google/go-cmp/cmp" -) - -func TestBinaryRoundTrip(t *testing.T) { - startPacked := positionsPacked{ - {FileIdx: 0, LineDelta: 1, ColumnDelta: 2, ByteDelta: 3}, - {FileIdx: 1, LineDelta: 2, ColumnDelta: 3, ByteDelta: 4}, - {FileIdx: 2, LineDelta: 3, ColumnDelta: 4, ByteDelta: 5}, - } - - b, err := startPacked.MarshalBinary() - if err != nil { - t.Fatalf("Failed to marshal: %s", err) - } - - var endPacked positionsPacked - err = endPacked.UnmarshalBinary(b) - if err != nil { - t.Fatalf("Failed to unmarshal: %s", err) - } - - if !cmp.Equal(startPacked, endPacked) { - t.Errorf("Incorrect result\n%s", cmp.Diff(startPacked, endPacked)) - } -} diff --git a/hclpack/structure.go b/hclpack/structure.go deleted file mode 100644 index 488a99e..0000000 --- a/hclpack/structure.go +++ /dev/null @@ -1,297 +0,0 @@ -package hclpack - -import ( - "fmt" - - "github.com/hashicorp/hcl2/hcl" -) - -// Body is an implementation of hcl.Body. -type Body struct { - Attributes map[string]Attribute - ChildBlocks []Block - - MissingItemRange_ hcl.Range -} - -var _ hcl.Body = (*Body)(nil) - -// Content is an implementation of the method of the same name on hcl.Body. -// -// When Content is called directly on a hclpack.Body, all child block bodies -// are guaranteed to be of type *hclpack.Body, so callers can type-assert -// to obtain a child Body in order to serialize it separately if needed. -func (b *Body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) { - return b.content(schema, nil) -} - -// PartialContent is an implementation of the method of the same name on hcl.Body. -// -// The returned "remain" body may share some backing objects with the receiver, -// so neither the receiver nor the returned remain body, or any descendent -// objects within them, may be mutated after this method is used. -// -// When Content is called directly on a hclpack.Body, all child block bodies -// and the returned "remain" body are guaranteed to be of type *hclpack.Body, -// so callers can type-assert to obtain a child Body in order to serialize it -// separately if needed. -func (b *Body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) { - remain := &Body{ - MissingItemRange_: b.MissingItemRange_, - } - content, diags := b.content(schema, remain) - return content, remain, diags -} - -func (b *Body) content(schema *hcl.BodySchema, remain *Body) (*hcl.BodyContent, hcl.Diagnostics) { - if b == nil { - b = &Body{} // We'll treat a nil body like an empty one, for convenience - } - var diags hcl.Diagnostics - - var attrs map[string]*hcl.Attribute - var attrUsed map[string]struct{} - if len(b.Attributes) > 0 { - attrs = make(map[string]*hcl.Attribute, len(b.Attributes)) - attrUsed = make(map[string]struct{}, len(b.Attributes)) - } - for _, attrS := range schema.Attributes { - name := attrS.Name - attr, exists := b.Attributes[name] - if !exists { - if attrS.Required { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Missing required argument", - Detail: fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name), - Subject: &b.MissingItemRange_, - }) - } - continue - } - - attrs[name] = attr.asHCLAttribute(name) - attrUsed[name] = struct{}{} - } - - for name, attr := range b.Attributes { - if _, used := attrUsed[name]; used { - continue - } - if remain != nil { - remain.setAttribute(name, attr) - continue - } - var suggestions []string - for _, attrS := range schema.Attributes { - if _, defined := attrs[name]; defined { - continue - } - suggestions = append(suggestions, attrS.Name) - } - suggestion := nameSuggestion(name, suggestions) - if suggestion != "" { - suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) - } else { - // Is there a block of the same name? - for _, blockS := range schema.Blocks { - if blockS.Type == name { - suggestion = fmt.Sprintf(" Did you mean to define a block of type %q?", name) - break - } - } - } - - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Unsupported argument", - Detail: fmt.Sprintf("An argument named %q is not expected here.%s", name, suggestion), - Subject: attr.NameRange.Ptr(), - }) - } - - blocksWanted := make(map[string]hcl.BlockHeaderSchema) - for _, blockS := range schema.Blocks { - blocksWanted[blockS.Type] = blockS - } - - var blocks []*hcl.Block - for _, block := range b.ChildBlocks { - // Redeclare block on stack so the pointer to the body is set on the - // correct block. https://github.com/hashicorp/hcl2/issues/72 - block := block - - blockTy := block.Type - blockS, wanted := blocksWanted[blockTy] - if !wanted { - if remain != nil { - remain.appendBlock(block) - continue - } - var suggestions []string - for _, blockS := range schema.Blocks { - suggestions = append(suggestions, blockS.Type) - } - suggestion := nameSuggestion(blockTy, suggestions) - if suggestion != "" { - suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) - } else { - // Is there an attribute of the same name? - for _, attrS := range schema.Attributes { - if attrS.Name == blockTy { - suggestion = fmt.Sprintf(" Did you mean to define argument %q?", blockTy) - break - } - } - } - - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Unsupported block type", - Detail: fmt.Sprintf("Blocks of type %q are not expected here.%s", blockTy, suggestion), - Subject: &block.TypeRange, - }) - continue - } - - if len(block.Labels) != len(blockS.LabelNames) { - if len(blockS.LabelNames) == 0 { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: fmt.Sprintf("Extraneous label for %s", blockTy), - Detail: fmt.Sprintf( - "No labels are expected for %s blocks.", blockTy, - ), - Subject: &block.DefRange, - Context: &block.DefRange, - }) - } else { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: fmt.Sprintf("Wrong label count for %s", blockTy), - Detail: fmt.Sprintf( - "%s blocks expect %d label(s), but got %d.", - blockTy, len(blockS.LabelNames), len(block.Labels), - ), - Subject: &block.DefRange, - Context: &block.DefRange, - }) - } - continue - } - - blocks = append(blocks, block.asHCLBlock()) - } - - return &hcl.BodyContent{ - Attributes: attrs, - Blocks: blocks, - MissingItemRange: b.MissingItemRange_, - }, diags -} - -// JustAttributes is an implementation of the method of the same name on hcl.Body. -func (b *Body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) { - var diags hcl.Diagnostics - if len(b.ChildBlocks) > 0 { - for _, block := range b.ChildBlocks { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: fmt.Sprintf("Unexpected %s block", block.Type), - Detail: "Blocks are not allowed here.", - Context: &block.TypeRange, - }) - } - // We'll continue processing anyway, and return any attributes we find - // so that the caller can do careful partial analysis. - } - - if len(b.Attributes) == 0 { - return nil, diags - } - - ret := make(hcl.Attributes, len(b.Attributes)) - for n, a := range b.Attributes { - ret[n] = a.asHCLAttribute(n) - } - return ret, diags -} - -// MissingItemRange is an implementation of the method of the same name on hcl.Body. -func (b *Body) MissingItemRange() hcl.Range { - return b.MissingItemRange_ -} - -func (b *Body) setAttribute(name string, attr Attribute) { - if b.Attributes == nil { - b.Attributes = make(map[string]Attribute) - } - b.Attributes[name] = attr -} - -func (b *Body) appendBlock(block Block) { - b.ChildBlocks = append(b.ChildBlocks, block) -} - -func (b *Body) addRanges(rngs map[hcl.Range]struct{}) { - rngs[b.MissingItemRange_] = struct{}{} - for _, attr := range b.Attributes { - attr.addRanges(rngs) - } - for _, block := range b.ChildBlocks { - block.addRanges(rngs) - } -} - -// Block represents a nested block within a body. -type Block struct { - Type string - Labels []string - Body Body - - DefRange, TypeRange hcl.Range - LabelRanges []hcl.Range -} - -func (b *Block) asHCLBlock() *hcl.Block { - return &hcl.Block{ - Type: b.Type, - Labels: b.Labels, - Body: &b.Body, - - TypeRange: b.TypeRange, - DefRange: b.DefRange, - LabelRanges: b.LabelRanges, - } -} - -func (b *Block) addRanges(rngs map[hcl.Range]struct{}) { - rngs[b.DefRange] = struct{}{} - rngs[b.TypeRange] = struct{}{} - for _, rng := range b.LabelRanges { - rngs[rng] = struct{}{} - } - b.Body.addRanges(rngs) -} - -// Attribute represents an attribute definition within a body. -type Attribute struct { - Expr Expression - - Range, NameRange hcl.Range -} - -func (a *Attribute) asHCLAttribute(name string) *hcl.Attribute { - return &hcl.Attribute{ - Name: name, - Expr: &a.Expr, - Range: a.Range, - NameRange: a.NameRange, - } -} - -func (a *Attribute) addRanges(rngs map[hcl.Range]struct{}) { - rngs[a.Range] = struct{}{} - rngs[a.NameRange] = struct{}{} - a.Expr.addRanges(rngs) -} diff --git a/hclpack/structure_test.go b/hclpack/structure_test.go deleted file mode 100644 index e99bd2b..0000000 --- a/hclpack/structure_test.go +++ /dev/null @@ -1,206 +0,0 @@ -package hclpack - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - - "github.com/hashicorp/hcl2/hcl" -) - -func TestBodyContent(t *testing.T) { - tests := map[string]struct { - Body *Body - Schema *hcl.BodySchema - Want *hcl.BodyContent - }{ - "empty": { - &Body{}, - &hcl.BodySchema{}, - &hcl.BodyContent{}, - }, - "nil": { - nil, - &hcl.BodySchema{}, - &hcl.BodyContent{}, - }, - "attribute": { - &Body{ - Attributes: map[string]Attribute{ - "foo": { - Expr: Expression{ - Source: []byte(`"hello"`), - SourceType: ExprNative, - }, - }, - }, - }, - &hcl.BodySchema{ - Attributes: []hcl.AttributeSchema{ - {Name: "foo", Required: true}, - {Name: "bar", Required: false}, - }, - }, - &hcl.BodyContent{ - Attributes: hcl.Attributes{ - "foo": { - Name: "foo", - Expr: &Expression{ - Source: []byte(`"hello"`), - SourceType: ExprNative, - }, - }, - }, - }, - }, - "block": { - &Body{ - ChildBlocks: []Block{ - { - Type: "foo", - }, - }, - }, - &hcl.BodySchema{ - Blocks: []hcl.BlockHeaderSchema{ - {Type: "foo"}, - }, - }, - &hcl.BodyContent{ - Blocks: hcl.Blocks{ - { - Type: "foo", - Body: &Body{}, - }, - }, - }, - }, - "block attributes": { - &Body{ - ChildBlocks: []Block{ - { - Type: "foo", - Body: Body{ - Attributes: map[string]Attribute{ - "bar": { - Expr: Expression{ - Source: []byte(`"hello"`), - SourceType: ExprNative, - }, - }, - }, - }, - }, - { - Type: "foo", - Body: Body{ - Attributes: map[string]Attribute{ - "bar": { - Expr: Expression{ - Source: []byte(`"world"`), - SourceType: ExprNative, - }, - }, - }, - }, - }, - }, - }, - &hcl.BodySchema{ - Blocks: []hcl.BlockHeaderSchema{ - {Type: "foo"}, - }, - }, - &hcl.BodyContent{ - Blocks: hcl.Blocks{ - { - Type: "foo", - Body: &Body{ - Attributes: map[string]Attribute{ - "bar": { - Expr: Expression{ - Source: []byte(`"hello"`), - SourceType: ExprNative, - }, - }, - }, - }, - }, - { - Type: "foo", - Body: &Body{ - Attributes: map[string]Attribute{ - "bar": { - Expr: Expression{ - Source: []byte(`"world"`), - SourceType: ExprNative, - }, - }, - }, - }, - }, - }, - }, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - got, diags := test.Body.Content(test.Schema) - for _, diag := range diags { - t.Errorf("unexpected diagnostic: %s", diag.Error()) - } - - if !cmp.Equal(test.Want, got) { - bytesAsString := func(s []byte) string { - return string(s) - } - t.Errorf("wrong result\n%s", cmp.Diff( - test.Want, got, - cmp.Transformer("bytesAsString", bytesAsString), - )) - } - }) - } - -} - -func TestBodyPartialContent(t *testing.T) { - tests := map[string]struct { - Body *Body - Schema *hcl.BodySchema - WantRemain hcl.Body - }{ - "missing range": { - &Body{ - MissingItemRange_: hcl.Range{ - Filename: "file.hcl", - Start: hcl.Pos{Line: 3, Column: 2}, - End: hcl.Pos{Line: 3, Column: 2}, - }, - }, - &hcl.BodySchema{}, - &Body{ - MissingItemRange_: hcl.Range{ - Filename: "file.hcl", - Start: hcl.Pos{Line: 3, Column: 2}, - End: hcl.Pos{Line: 3, Column: 2}, - }, - }, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - _, gotRemain, diags := test.Body.PartialContent(test.Schema) - for _, diag := range diags { - t.Errorf("unexpected diagnostic: %s", diag.Error()) - } - - if !cmp.Equal(test.WantRemain, gotRemain) { - t.Errorf("wrong remaining result\n%s", cmp.Diff(test.WantRemain, gotRemain)) - } - }) - } - -} diff --git a/hclpack/vlq.go b/hclpack/vlq.go deleted file mode 100644 index 2f4a2bf..0000000 --- a/hclpack/vlq.go +++ /dev/null @@ -1,50 +0,0 @@ -package hclpack - -import ( - "errors" - - "github.com/bsm/go-vlq" -) - -type vlqBuf []byte - -var vlqSpace [vlq.MaxLen64]byte - -func newVLQBuf(byteCap int) vlqBuf { - return make(vlqBuf, 0, byteCap) -} - -func (b vlqBuf) AppendInt(i int) vlqBuf { - spc := cap(b) - len(b) - if spc < len(vlqSpace) { - b = append(b, vlqSpace[:]...) - b = b[:len(b)-len(vlqSpace)] - } - into := b[len(b):cap(b)] - l := vlq.PutInt(into, int64(i)) - b = b[:len(b)+l] - return b -} - -func (b vlqBuf) ReadInt() (int, vlqBuf, error) { - v, adv := vlq.Int([]byte(b)) - if adv <= 0 { - if adv == 0 { - return 0, b, errors.New("missing expected VLQ value") - } else { - return 0, b, errors.New("invalid VLQ value") - } - } - if int64(int(v)) != v { - return 0, b, errors.New("VLQ value too big for integer on this platform") - } - return int(v), b[adv:], nil -} - -func (b vlqBuf) AppendRawByte(by byte) vlqBuf { - return append(b, by) -} - -func (b vlqBuf) Bytes() []byte { - return []byte(b) -}