From 1365a2cfe54286b6496fd3565a26b2ae5a537ce3 Mon Sep 17 00:00:00 2001
From: Martin Atkins <mart@degeneration.co.uk>
Date: Sun, 14 Jan 2018 11:33:23 -0800
Subject: [PATCH] hcl: RangeOver function

This is a generalization of RangeBetween that finds a single range that
covers the full extent of both given ranges, possibly also including some
additional content between the ranges if they do not overlap.
---
 hcl/pos.go      |  34 +++++++++++
 hcl/pos_test.go | 150 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 184 insertions(+)

diff --git a/hcl/pos.go b/hcl/pos.go
index 2ac16c7..39c183f 100644
--- a/hcl/pos.go
+++ b/hcl/pos.go
@@ -60,6 +60,40 @@ func RangeBetween(start, end Range) Range {
 	}
 }
 
+// RangeOver returns a new range that covers both of the given ranges and
+// possibly additional content between them if the two ranges do not overlap.
+//
+// If either range is empty then it is ignored. The result is empty if both
+// given ranges are empty.
+//
+// The result is meaningless if the two ranges to not belong to the same
+// source file.
+func RangeOver(a, b Range) Range {
+	if a.Empty() {
+		return b
+	}
+	if b.Empty() {
+		return a
+	}
+
+	var start, end Pos
+	if a.Start.Byte < b.Start.Byte {
+		start = a.Start
+	} else {
+		start = b.Start
+	}
+	if a.End.Byte > b.End.Byte {
+		end = a.End
+	} else {
+		end = b.End
+	}
+	return Range{
+		Filename: a.Filename,
+		Start:    start,
+		End:      end,
+	}
+}
+
 // ContainsOffset returns true if and only if the given byte offset is within
 // the receiving Range.
 func (r Range) ContainsOffset(offset int) bool {
diff --git a/hcl/pos_test.go b/hcl/pos_test.go
index 05703c9..32ad116 100644
--- a/hcl/pos_test.go
+++ b/hcl/pos_test.go
@@ -7,6 +7,156 @@ import (
 	"testing"
 )
 
+func TestRangeOver(t *testing.T) {
+	tests := []struct {
+		A    Range
+		B    Range
+		Want 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{ //  ####
+				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: 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: 5, Line: 1, Column: 6},
+			},
+		},
+		{
+			Range{ //   ####
+				Start: Pos{Byte: 2, Line: 1, Column: 3},
+				End:   Pos{Byte: 6, Line: 1, Column: 7},
+			},
+			Range{ //  ####
+				Start: Pos{Byte: 1, Line: 1, Column: 2},
+				End:   Pos{Byte: 5, Line: 1, Column: 6},
+			},
+			Range{ //  #####
+				Start: Pos{Byte: 1, Line: 1, Column: 2},
+				End:   Pos{Byte: 6, Line: 1, Column: 7},
+			},
+		},
+		{
+			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: 5, Line: 1, Column: 6},
+			},
+		},
+		{
+			Range{ //  ###
+				Start: Pos{Byte: 1, Line: 1, Column: 2},
+				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: 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: 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: 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: 5, Line: 1, Column: 6},
+			},
+			Range{ //  ####
+				Start: Pos{Byte: 2, Line: 1, Column: 3},
+				End:   Pos{Byte: 5, Line: 1, Column: 6},
+			},
+			Range{ //  ####
+				Start: Pos{Byte: 2, Line: 1, Column: 3},
+				End:   Pos{Byte: 5, Line: 1, Column: 6},
+			},
+		},
+		{
+			Range{ // ##
+				Start: Pos{Byte: 0, Line: 1, Column: 1},
+				End:   Pos{Byte: 2, Line: 1, Column: 3},
+			},
+			Range{ //     ##
+				Start: Pos{Byte: 4, Line: 1, Column: 5},
+				End:   Pos{Byte: 6, Line: 1, Column: 7},
+			},
+			Range{ // ######
+				Start: Pos{Byte: 0, Line: 1, Column: 1},
+				End:   Pos{Byte: 6, Line: 1, Column: 7},
+			},
+		},
+		{
+			Range{ //     ##
+				Start: Pos{Byte: 4, Line: 1, Column: 5},
+				End:   Pos{Byte: 6, Line: 1, Column: 7},
+			},
+			Range{ // ##
+				Start: Pos{Byte: 0, Line: 1, Column: 1},
+				End:   Pos{Byte: 2, Line: 1, Column: 3},
+			},
+			Range{ // ######
+				Start: Pos{Byte: 0, Line: 1, Column: 1},
+				End:   Pos{Byte: 6, Line: 1, Column: 7},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(fmt.Sprintf("%s<=>%s", test.A, test.B), func(t *testing.T) {
+			got := RangeOver(test.A, test.B)
+			if !reflect.DeepEqual(got, test.Want) {
+				t.Errorf(
+					"wrong result\nA   : %-10s %s\nB   : %-10s %s\ngot : %-10s %s\nwant: %-10s %s",
+					visRangeOffsets(test.A), test.A,
+					visRangeOffsets(test.B), test.B,
+					visRangeOffsets(got), got,
+					visRangeOffsets(test.Want), test.Want,
+				)
+			}
+		})
+	}
+}
+
 func TestPosOverlap(t *testing.T) {
 	tests := []struct {
 		A    Range