Repair reversed D&D evidence ranges

This commit is contained in:
2026-08-28 13:45:21 +00:00
parent a2610757cd
commit b178f1c684
36 changed files with 226 additions and 61 deletions

View File

@@ -63,6 +63,29 @@ func (o SourceRefOrder) EarliestValid(refs []source.SourceRef) (int, bool) {
return earliest, found
}
// OrderEndpoints returns an owned copy of refs with each resolvable range
// ordered by document position. References outside the indexed source, or
// whose endpoints cannot both be resolved, remain unchanged so validators can
// diagnose them.
func (o SourceRefOrder) OrderEndpoints(refs []source.SourceRef) []source.SourceRef {
if refs == nil {
return nil
}
ordered := append([]source.SourceRef{}, refs...)
for index := range ordered {
ref := &ordered[index]
if ref.SourceID != o.sourceID || ref.StartUnitID <= 0 || ref.EndUnitID <= 0 {
continue
}
start, startOK := o.index.Position(ref.StartUnitID)
end, endOK := o.index.Position(ref.EndUnitID)
if startOK && endOK && start > end {
ref.StartUnitID, ref.EndUnitID = ref.EndUnitID, ref.StartUnitID
}
}
return ordered
}
// Canonicalize returns an owned, stable-sorted, exactly de-duplicated copy of
// refs. It deliberately preserves invalid references for diagnostics.
func (o SourceRefOrder) Canonicalize(refs []source.SourceRef) []source.SourceRef {

View File

@@ -58,6 +58,38 @@ func TestSourceRefOrderCanonicalizePreservesRepresentationAndOwnership(t *testin
}
}
func TestSourceRefOrderOrdersOnlyResolvableCurrentSourceEndpoints(t *testing.T) {
doc := unitRefSourceDocument(30, 10, 20)
order := NewSourceRefOrder(doc)
input := []source.SourceRef{
{SourceID: doc.ID, StartUnitID: 30, EndUnitID: 10},
{SourceID: doc.ID, StartUnitID: 20, EndUnitID: 30},
{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10},
{SourceID: "other", StartUnitID: 20, EndUnitID: 30},
{SourceID: doc.ID, StartUnitID: 999, EndUnitID: 30},
{SourceID: doc.ID, StartUnitID: 20, EndUnitID: 0},
}
want := []source.SourceRef{
{SourceID: doc.ID, StartUnitID: 30, EndUnitID: 10},
{SourceID: doc.ID, StartUnitID: 30, EndUnitID: 20},
{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10},
{SourceID: "other", StartUnitID: 20, EndUnitID: 30},
{SourceID: doc.ID, StartUnitID: 999, EndUnitID: 30},
{SourceID: doc.ID, StartUnitID: 20, EndUnitID: 0},
}
got := order.OrderEndpoints(input)
if !reflect.DeepEqual(got, want) {
t.Fatalf("OrderEndpoints() = %#v, want %#v", got, want)
}
got[0].StartUnitID = 999
if input[0].StartUnitID != 30 {
t.Fatal("OrderEndpoints() output aliases input")
}
if got := order.OrderEndpoints(nil); got != nil {
t.Fatalf("OrderEndpoints(nil) = %#v, want nil", got)
}
}
func TestSourceRefOrderSnapshotAndEarliestValid(t *testing.T) {
doc := unitRefSourceDocument(30, 10, 20)
sourceID := doc.ID