diff --git a/hcl/pos.go b/hcl/pos.go index 39c183f..1a4b329 100644 --- a/hcl/pos.go +++ b/hcl/pos.go @@ -231,3 +231,32 @@ func (r Range) Overlap(other Range) Range { End: end, } } + +// PartitionAround finds the portion of the given range that overlaps with +// the reciever and returns three ranges: the portion of the reciever that +// precedes the overlap, the overlap itself, and then the portion of the +// reciever that comes after the overlap. +// +// If the two ranges do not overlap then all three returned ranges are empty. +// +// If the given range aligns with or extends beyond either extent of the +// reciever then the corresponding outer range will be empty. +func (r Range) PartitionAround(other Range) (before, overlap, after Range) { + overlap = r.Overlap(other) + if overlap.Empty() { + return overlap, overlap, overlap + } + + before = Range{ + Filename: r.Filename, + Start: r.Start, + End: overlap.Start, + } + after = Range{ + Filename: r.Filename, + Start: overlap.End, + End: r.End, + } + + return before, overlap, after +} diff --git a/hcl/pos_test.go b/hcl/pos_test.go index 32ad116..cfa4137 100644 --- a/hcl/pos_test.go +++ b/hcl/pos_test.go @@ -307,6 +307,138 @@ func TestPosOverlap(t *testing.T) { } } +func TestRangePartitionAround(t *testing.T) { + tests := []struct { + Outer Range + Inner Range + WantBefore Range + WantOverlap Range + WantAfter Range + }{ + { + Range{ // ## + Start: Pos{Byte: 2, Line: 1, Column: 3}, + End: Pos{Byte: 4, Line: 1, Column: 5}, + }, + Range{ // #### + Start: Pos{Byte: 1, Line: 1, Column: 2}, + End: Pos{Byte: 5, Line: 1, Column: 6}, + }, + Range{ // (empty) + Start: Pos{Byte: 2, Line: 1, Column: 3}, + End: Pos{Byte: 2, Line: 1, Column: 3}, + }, + Range{ // ## + Start: Pos{Byte: 2, Line: 1, Column: 3}, + End: Pos{Byte: 4, Line: 1, Column: 5}, + }, + Range{ // (empty) + Start: Pos{Byte: 4, Line: 1, Column: 5}, + End: Pos{Byte: 4, Line: 1, Column: 5}, + }, + }, + { + Range{ // #### + Start: Pos{Byte: 0, Line: 1, Column: 1}, + End: Pos{Byte: 4, Line: 1, Column: 5}, + }, + Range{ // #### + Start: Pos{Byte: 1, Line: 1, Column: 2}, + End: Pos{Byte: 5, Line: 1, Column: 6}, + }, + Range{ // # + Start: Pos{Byte: 0, Line: 1, Column: 1}, + End: Pos{Byte: 1, Line: 1, Column: 2}, + }, + Range{ // ### + Start: Pos{Byte: 1, Line: 1, Column: 2}, + End: Pos{Byte: 4, Line: 1, Column: 5}, + }, + Range{ // (empty) + Start: Pos{Byte: 4, Line: 1, Column: 5}, + End: Pos{Byte: 4, Line: 1, Column: 5}, + }, + }, + { + Range{ // #### + Start: Pos{Byte: 2, Line: 1, Column: 3}, + End: Pos{Byte: 5, Line: 1, Column: 6}, + }, + Range{ // #### + Start: Pos{Byte: 1, Line: 1, Column: 2}, + End: Pos{Byte: 5, Line: 1, Column: 6}, + }, + Range{ // (empty) + Start: Pos{Byte: 2, Line: 1, Column: 3}, + End: Pos{Byte: 2, Line: 1, Column: 3}, + }, + Range{ // ### + Start: Pos{Byte: 2, Line: 1, Column: 3}, + End: Pos{Byte: 5, Line: 1, Column: 6}, + }, + Range{ // # + Start: Pos{Byte: 5, Line: 1, Column: 6}, + End: Pos{Byte: 5, Line: 1, Column: 6}, + }, + }, + { + Range{ // #### + Start: Pos{Byte: 1, Line: 1, Column: 2}, + End: Pos{Byte: 5, Line: 1, Column: 6}, + }, + Range{ // ## + Start: Pos{Byte: 2, Line: 1, Column: 3}, + End: Pos{Byte: 4, Line: 1, Column: 5}, + }, + Range{ // # + Start: Pos{Byte: 1, Line: 1, Column: 2}, + End: Pos{Byte: 2, Line: 1, Column: 3}, + }, + Range{ // ## + Start: Pos{Byte: 2, Line: 1, Column: 3}, + End: Pos{Byte: 4, Line: 1, Column: 5}, + }, + Range{ // # + Start: Pos{Byte: 4, Line: 1, Column: 5}, + End: Pos{Byte: 5, Line: 1, Column: 6}, + }, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%s around %s", test.Outer, test.Inner), func(t *testing.T) { + gotBefore, gotOverlap, gotAfter := test.Outer.PartitionAround(test.Inner) + if !reflect.DeepEqual(gotBefore, test.WantBefore) { + t.Errorf( + "wrong before\nA : %-10s %s\nB : %-10s %s\ngot : %-10s %s\nwant: %-10s %s", + visRangeOffsets(test.Outer), test.Outer, + visRangeOffsets(test.Inner), test.Inner, + visRangeOffsets(gotBefore), gotBefore, + visRangeOffsets(test.WantBefore), test.WantBefore, + ) + } + if !reflect.DeepEqual(gotOverlap, test.WantOverlap) { + t.Errorf( + "wrong overlap\nA : %-10s %s\nB : %-10s %s\ngot : %-10s %s\nwant: %-10s %s", + visRangeOffsets(test.Outer), test.Outer, + visRangeOffsets(test.Inner), test.Inner, + visRangeOffsets(gotOverlap), gotOverlap, + visRangeOffsets(test.WantOverlap), test.WantOverlap, + ) + } + if !reflect.DeepEqual(gotAfter, test.WantAfter) { + t.Errorf( + "wrong after\nA : %-10s %s\nB : %-10s %s\ngot : %-10s %s\nwant: %-10s %s", + visRangeOffsets(test.Outer), test.Outer, + visRangeOffsets(test.Inner), test.Inner, + visRangeOffsets(gotAfter), gotAfter, + visRangeOffsets(test.WantAfter), test.WantAfter, + ) + } + }) + } +} + // visRangeOffsets is a helper that produces a visual representation of the // start and end byte offsets of the given range, which can then be stacked // with the same for other ranges to more easily see how the ranges relate