Skip to content

Commit 0fca7da

Browse files
committed
go: add Compare/Contains methods on *Occurrence
1 parent 0571eeb commit 0fca7da

4 files changed

Lines changed: 63 additions & 31 deletions

File tree

bindings/go/scip/flatten.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,7 @@ func FlattenOccurrences(occurrences []*Occurrence) []*Occurrence {
6464
for _, occurrence := range occurrences[1:] {
6565
top := flattened[len(flattened)-1]
6666

67-
if !occurrenceRangesEqual(top, occurrence) {
68-
flattened = append(flattened, occurrence)
69-
continue
70-
}
71-
if top.Symbol != occurrence.Symbol {
67+
if top.Compare(occurrence) != 0 {
7268
flattened = append(flattened, occurrence)
7369
continue
7470
}

bindings/go/scip/occurrence_range.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package scip
22

3+
import "strings"
4+
35
// SourceRange returns the source range of this occurrence and whether one is
46
// set. `typed_range` takes precedence over the deprecated `range` field.
57
// Malformed deprecated ranges (length != 3 or 4) are reported as missing; use
@@ -50,6 +52,28 @@ func (occ *Occurrence) SetEnclosingSourceRange(r Range) {
5052
occ.TypedEnclosingRange = &Occurrence_MultiLineEnclosingRange{MultiLineEnclosingRange: r.ToMultiLineRange()}
5153
}
5254

55+
// Compare orders occurrences in the canonical SCIP ordering: ascending by
56+
// source range, with symbol name as a tiebreaker. Returns -1, 0, or +1.
57+
//
58+
// Occurrences missing a source range compare as if their range were the
59+
// zero range; such occurrences are illegal per the SCIP spec and should be
60+
// surfaced via `scip lint`.
61+
func (occ *Occurrence) Compare(other *Occurrence) int {
62+
r1, _ := occ.SourceRange()
63+
r2, _ := other.SourceRange()
64+
if c := r1.CompareStrict(r2); c != 0 {
65+
return c
66+
}
67+
return strings.Compare(occ.Symbol, other.Symbol)
68+
}
69+
70+
// Contains reports whether the source range of this occurrence contains the
71+
// given position. Returns false if the occurrence has no range.
72+
func (occ *Occurrence) Contains(pos Position) bool {
73+
r, ok := occ.SourceRange()
74+
return ok && r.Contains(pos)
75+
}
76+
5377
// ToRange returns this single-line range as a Range.
5478
func (sr *SingleLineRange) ToRange() Range {
5579
return Range{

bindings/go/scip/occurrence_range_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,37 @@ func TestOccurrence_SetEnclosingSourceRange(t *testing.T) {
139139
require.True(t, ok)
140140
require.Equal(t, &MultiLineRange{StartLine: 1, StartCharacter: 0, EndLine: 5, EndCharacter: 1}, tr.MultiLineEnclosingRange)
141141
}
142+
143+
func TestOccurrence_Compare(t *testing.T) {
144+
mkOcc := func(r []int32, sym string) *Occurrence {
145+
return &Occurrence{Range: r, Symbol: sym}
146+
}
147+
tests := []struct {
148+
name string
149+
a, b *Occurrence
150+
want int
151+
}{
152+
{"equal", mkOcc([]int32{1, 0, 5}, "x"), mkOcc([]int32{1, 0, 5}, "x"), 0},
153+
{"earlier range", mkOcc([]int32{0, 0, 5}, "x"), mkOcc([]int32{1, 0, 5}, "x"), -1},
154+
{"later range", mkOcc([]int32{2, 0, 5}, "x"), mkOcc([]int32{1, 0, 5}, "x"), 1},
155+
{"same range, earlier symbol", mkOcc([]int32{1, 0, 5}, "a"), mkOcc([]int32{1, 0, 5}, "b"), -1},
156+
{"same range, later symbol", mkOcc([]int32{1, 0, 5}, "b"), mkOcc([]int32{1, 0, 5}, "a"), 1},
157+
}
158+
for _, tc := range tests {
159+
t.Run(tc.name, func(t *testing.T) {
160+
require.Equal(t, tc.want, tc.a.Compare(tc.b))
161+
})
162+
}
163+
}
164+
165+
func TestOccurrence_Contains(t *testing.T) {
166+
occ := &Occurrence{Range: []int32{2, 5, 10}}
167+
require.True(t, occ.Contains(Position{2, 5}))
168+
require.True(t, occ.Contains(Position{2, 9}))
169+
require.False(t, occ.Contains(Position{2, 4}))
170+
require.False(t, occ.Contains(Position{2, 10}))
171+
require.False(t, occ.Contains(Position{3, 0}))
172+
173+
empty := &Occurrence{}
174+
require.False(t, empty.Contains(Position{0, 0}))
175+
}

bindings/go/scip/sort.go

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,13 @@ func FindOccurrences(occurrences []*Occurrence, targetLine, targetCharacter int3
4949
var filtered []*Occurrence
5050
pos := Position{targetLine, targetCharacter}
5151
for _, occurrence := range occurrences {
52-
if r, _ := occurrence.SourceRange(); r.Contains(pos) {
52+
if occurrence.Contains(pos) {
5353
filtered = append(filtered, occurrence)
5454
}
5555
}
5656

57-
sort.Slice(filtered, func(i, j int) bool {
58-
// Ordered so that the least precise (largest) range comes last
59-
ri, _ := filtered[i].SourceRange()
60-
rj, _ := filtered[j].SourceRange()
61-
return ri.CompareStrict(rj) > 0
62-
})
63-
57+
// Ordered so that the least precise (largest) range comes last.
58+
slices.SortFunc(filtered, func(a, b *Occurrence) int { return b.Compare(a) })
6459
return filtered
6560
}
6661

@@ -69,27 +64,10 @@ func FindOccurrences(occurrences []*Occurrence, targetLine, targetCharacter int3
6964
// come before the enclosed. If there are multiple occurrences with the exact same range, then the
7065
// occurrences are sorted by symbol name.
7166
func SortOccurrences(occurrences []*Occurrence) []*Occurrence {
72-
sort.Slice(occurrences, func(i, j int) bool {
73-
r1, _ := occurrences[i].SourceRange()
74-
r2, _ := occurrences[j].SourceRange()
75-
if ret := r1.CompareStrict(r2); ret != 0 {
76-
return ret < 0
77-
}
78-
return occurrences[i].Symbol < occurrences[j].Symbol
79-
})
80-
67+
slices.SortFunc(occurrences, (*Occurrence).Compare)
8168
return occurrences
8269
}
8370

84-
// occurrenceRangesEqual compares the source ranges of two occurrences for
85-
// equality, normalizing across the deprecated `repeated int32` and the typed
86-
// `typed_range` encodings.
87-
func occurrenceRangesEqual(a, b *Occurrence) bool {
88-
ra, _ := a.SourceRange()
89-
rb, _ := b.SourceRange()
90-
return ra.CompareStrict(rb) == 0
91-
}
92-
9371
// SortRanges sorts the given range slice (in-place) and returns it (for convenience). Ranges are
9472
// sorted in ascending order of starting position, where enclosing ranges come before the enclosed.
9573
func SortRanges(ranges []Range) []Range {

0 commit comments

Comments
 (0)