Skip to content

Commit 990d1a1

Browse files
authored
Merge pull request #587 from Shopify/ar/nilable-location
Allow nilable Spoom::Location lines and columns attributes
2 parents 28a57d5 + c60632f commit 990d1a1

File tree

2 files changed

+157
-43
lines changed

2 files changed

+157
-43
lines changed

lib/spoom/location.rb

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,42 +15,73 @@ class << self
1515
sig { params(location_string: String).returns(Location) }
1616
def from_string(location_string)
1717
file, rest = location_string.split(":", 2)
18-
raise LocationError, "Invalid location string: #{location_string}" unless file && rest
18+
raise LocationError, "Invalid location string `#{location_string}`: missing file name" unless file
19+
20+
return new(file) if rest.nil?
1921

2022
start_line, rest = rest.split(":", 2)
21-
raise LocationError, "Invalid location string: #{location_string}" unless start_line && rest
23+
if rest.nil?
24+
start_line, end_line = T.must(start_line).split("-", 2)
25+
raise LocationError, "Invalid location string `#{location_string}`: missing end line" unless end_line
26+
27+
return new(file, start_line: start_line.to_i, end_line: end_line.to_i) if end_line
28+
end
2229

2330
start_column, rest = rest.split("-", 2)
24-
raise LocationError, "Invalid location string: #{location_string}" unless start_column && rest
31+
raise LocationError, "Invalid location string `#{location_string}`: missing end line and column" if rest.nil?
2532

2633
end_line, end_column = rest.split(":", 2)
27-
raise LocationError, "Invalid location string: #{location_string}" unless end_line && end_column
28-
29-
new(file, start_line.to_i, start_column.to_i, end_line.to_i, end_column.to_i)
34+
raise LocationError,
35+
"Invalid location string `#{location_string}`: missing end column" unless end_line && end_column
36+
37+
new(
38+
file,
39+
start_line: start_line.to_i,
40+
start_column: start_column.to_i,
41+
end_line: end_line.to_i,
42+
end_column: end_column.to_i,
43+
)
3044
end
3145

3246
sig { params(file: String, location: Prism::Location).returns(Location) }
3347
def from_prism(file, location)
34-
new(file, location.start_line, location.start_column, location.end_line, location.end_column)
48+
new(
49+
file,
50+
start_line: location.start_line,
51+
start_column: location.start_column,
52+
end_line: location.end_line,
53+
end_column: location.end_column,
54+
)
3555
end
3656
end
3757

3858
sig { returns(String) }
3959
attr_reader :file
4060

41-
sig { returns(Integer) }
61+
sig { returns(T.nilable(Integer)) }
4262
attr_reader :start_line, :start_column, :end_line, :end_column
4363

4464
sig do
4565
params(
4666
file: String,
47-
start_line: Integer,
48-
start_column: Integer,
49-
end_line: Integer,
50-
end_column: Integer,
67+
start_line: T.nilable(Integer),
68+
start_column: T.nilable(Integer),
69+
end_line: T.nilable(Integer),
70+
end_column: T.nilable(Integer),
5171
).void
5272
end
53-
def initialize(file, start_line, start_column, end_line, end_column)
73+
def initialize(file, start_line: nil, start_column: nil, end_line: nil, end_column: nil)
74+
raise LocationError,
75+
"Invalid location: end line is required if start line is provided" if start_line && !end_line
76+
raise LocationError,
77+
"Invalid location: start line is required if end line is provided" if !start_line && end_line
78+
raise LocationError,
79+
"Invalid location: end column is required if start column is provided" if start_column && !end_column
80+
raise LocationError,
81+
"Invalid location: start column is required if end column is provided" if !start_column && end_column
82+
raise LocationError,
83+
"Invalid location: lines are required if columns are provided" if start_column && !start_line
84+
5485
@file = file
5586
@start_line = start_line
5687
@start_column = start_column
@@ -61,10 +92,12 @@ def initialize(file, start_line, start_column, end_line, end_column)
6192
sig { params(other: Location).returns(T::Boolean) }
6293
def include?(other)
6394
return false unless @file == other.file
64-
return false if @start_line > other.start_line
65-
return false if @start_line == other.start_line && @start_column > other.start_column
66-
return false if @end_line < other.end_line
67-
return false if @end_line == other.end_line && @end_column < other.end_column
95+
return false if (@start_line || -Float::INFINITY) > (other.start_line || -Float::INFINITY)
96+
return false if @start_line == other.start_line &&
97+
(@start_column || -Float::INFINITY) > (other.start_column || -Float::INFINITY)
98+
return false if (@end_line || Float::INFINITY) < (other.end_line || Float::INFINITY)
99+
return false if @end_line == other.end_line &&
100+
(@end_column || Float::INFINITY) < (other.end_column || Float::INFINITY)
68101

69102
true
70103
end
@@ -73,13 +106,34 @@ def include?(other)
73106
def <=>(other)
74107
return unless Location === other
75108

76-
[file, start_line, start_column, end_line, end_column] <=>
77-
[other.file, other.start_line, other.start_column, other.end_line, other.end_column]
109+
comparison_array_self = [
110+
@file,
111+
@start_line || -Float::INFINITY,
112+
@start_column || -Float::INFINITY,
113+
@end_line || Float::INFINITY,
114+
@end_column || Float::INFINITY,
115+
]
116+
117+
comparison_array_other = [
118+
other.file,
119+
other.start_line || -Float::INFINITY,
120+
other.start_column || -Float::INFINITY,
121+
other.end_line || Float::INFINITY,
122+
other.end_column || Float::INFINITY,
123+
]
124+
125+
comparison_array_self <=> comparison_array_other
78126
end
79127

80128
sig { returns(String) }
81129
def to_s
82-
"#{@file}:#{@start_line}:#{@start_column}-#{@end_line}:#{@end_column}"
130+
if @start_line && @start_column
131+
"#{@file}:#{@start_line}:#{@start_column}-#{@end_line}:#{@end_column}"
132+
elsif @start_line
133+
"#{@file}:#{@start_line}-#{@end_line}"
134+
else
135+
@file
136+
end
83137
end
84138
end
85139
end

test/spoom/location_test.rb

Lines changed: 83 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,25 @@ module Spoom
77
module Sorbet
88
class LocationTest < Minitest::Test
99
def test_from_string
10-
location = Location.from_string("foo.rb:1:2-3:4")
11-
assert_equal("foo.rb", location.file)
12-
assert_equal(1, location.start_line)
13-
assert_equal(2, location.start_column)
14-
assert_equal(3, location.end_line)
15-
assert_equal(4, location.end_column)
10+
location1 = Location.from_string("foo.rb:1:2-3:4")
11+
assert_equal("foo.rb", location1.file)
12+
assert_equal(1, location1.start_line)
13+
assert_equal(2, location1.start_column)
14+
assert_equal(3, location1.end_line)
15+
assert_equal(4, location1.end_column)
16+
17+
location2 = Location.from_string("foo.rb:1-3")
18+
assert_equal(1, location2.start_line)
19+
assert_equal(3, location2.end_line)
20+
assert_nil(location2.start_column)
21+
assert_nil(location2.end_column)
22+
23+
location3 = Location.from_string("foo.rb")
24+
assert_equal("foo.rb", location3.file)
25+
assert_nil(location3.start_line)
26+
assert_nil(location3.start_column)
27+
assert_nil(location3.end_line)
28+
assert_nil(location3.end_column)
1629
end
1730

1831
def test_raises_if_location_string_has_missing_components
@@ -27,65 +40,112 @@ def test_raises_if_location_string_has_missing_components
2740
assert_raises(Location::LocationError) do
2841
Location.from_string("foo.rb:1")
2942
end
43+
end
44+
45+
def test_raises_if_initialize_has_missing_attributes
46+
assert_raises(Location::LocationError) do
47+
Location.new("foo.rb", start_line: 1, start_column: 2, end_line: 3)
48+
end
49+
50+
assert_raises(Location::LocationError) do
51+
Location.new("foo.rb", start_line: 1, start_column: 2, end_column: 3)
52+
end
53+
54+
assert_raises(Location::LocationError) do
55+
Location.new("foo.rb", start_line: 1, end_column: 2, end_line: 3)
56+
end
3057

3158
assert_raises(Location::LocationError) do
32-
Location.from_string("foo.rb")
59+
Location.new("foo.rb", start_column: 1, end_column: 2, end_line: 3)
60+
end
61+
62+
assert_raises(Location::LocationError) do
63+
Location.new("foo.rb", start_line: 1, start_column: 2)
64+
end
65+
66+
assert_raises(Location::LocationError) do
67+
Location.new("foo.rb", start_line: 1)
3368
end
3469
end
3570

3671
def test_include
37-
location1 = Location.new("foo.rb", 1, 2, 3, 4)
38-
location2 = Location.new("foo.rb", 1, 2, 3, 4)
72+
location1 = Location.new("foo.rb", start_line: 1, start_column: 2, end_line: 3, end_column: 4)
73+
location2 = Location.new("foo.rb", start_line: 1, start_column: 2, end_line: 3, end_column: 4)
3974
assert(location1.include?(location2))
4075

41-
location3 = Location.new("foo.rb", 1, 2, 3, 5)
76+
location3 = Location.new("foo.rb", start_line: 1, start_column: 2, end_line: 3, end_column: 5)
4277
refute(location1.include?(location3))
4378
assert(location3.include?(location1))
4479

45-
location4 = Location.new("foo.rb", 1, 2, 4, 4)
80+
location4 = Location.new("foo.rb", start_line: 1, start_column: 2, end_line: 4, end_column: 4)
4681
refute(location1.include?(location4))
4782
assert(location4.include?(location1))
4883

49-
location5 = Location.new("foo.rb", 1, 3, 3, 4)
84+
location5 = Location.new("foo.rb", start_line: 1, start_column: 3, end_line: 3, end_column: 4)
5085
assert(location1.include?(location5))
5186
refute(location5.include?(location1))
5287

53-
location6 = Location.new("foo.rb", 2, 2, 3, 4)
88+
location6 = Location.new("foo.rb", start_line: 2, start_column: 2, end_line: 3, end_column: 4)
5489
assert(location1.include?(location6))
5590
refute(location6.include?(location1))
5691

57-
location7 = Location.new("bar.rb", 1, 2, 3, 4)
92+
location7 = Location.new("bar.rb", start_line: 1, start_column: 2, end_line: 3, end_column: 4)
5893
refute(location1.include?(location7))
5994
refute(location7.include?(location1))
95+
96+
location8 = Location.new("foo.rb")
97+
location9 = Location.new("foo.rb")
98+
assert(location8.include?(location9))
99+
assert(location9.include?(location8))
100+
101+
assert(location8.include?(location1))
102+
refute(location1.include?(location8))
103+
104+
location10 = Location.new("foo.rb", start_line: 1, end_line: 3)
105+
assert(location10.include?(location1))
106+
refute(location1.include?(location10))
60107
end
61108

62109
def test_comparison
63-
location1 = Location.new("foo.rb", 1, 2, 3, 4)
64-
location2 = Location.new("foo.rb", 1, 2, 3, 4)
110+
location1 = Location.new("foo.rb", start_line: 1, start_column: 2, end_line: 3, end_column: 4)
111+
location2 = Location.new("foo.rb", start_line: 1, start_column: 2, end_line: 3, end_column: 4)
65112
assert_equal(0, location1 <=> location2)
66113

67-
location3 = Location.new("foo.rb", 1, 2, 3, 5)
114+
location3 = Location.new("foo.rb", start_line: 1, start_column: 2, end_line: 3, end_column: 5)
68115
assert_equal(-1, location1 <=> location3)
69116

70-
location4 = Location.new("foo.rb", 1, 2, 4, 4)
117+
location4 = Location.new("foo.rb", start_line: 1, start_column: 2, end_line: 4, end_column: 4)
71118
assert_equal(-1, location1 <=> location4)
72119

73-
location5 = Location.new("foo.rb", 1, 3, 3, 4)
120+
location5 = Location.new("foo.rb", start_line: 1, start_column: 3, end_line: 3, end_column: 4)
74121
assert_equal(-1, location1 <=> location5)
75122

76-
location6 = Location.new("foo.rb", 11, 2, 3, 4)
123+
location6 = Location.new("foo.rb", start_line: 11, start_column: 2, end_line: 3, end_column: 4)
77124
assert_equal(-1, location1 <=> location6)
78125

79-
location7 = Location.new("bar.rb", 1, 2, 3, 4)
126+
location7 = Location.new("bar.rb", start_line: 1, start_column: 2, end_line: 3, end_column: 4)
80127
assert_equal(1, location1 <=> location7)
81128

82129
not_a_location = 42
83130
assert_nil(location1 <=> not_a_location)
131+
132+
location8 = Location.new("foo.rb")
133+
location9 = Location.new("foo.rb")
134+
assert_equal(0, location8 <=> location9)
135+
136+
location10 = Location.new("foo.rb", start_line: 1, end_line: 3)
137+
assert_equal(-1, location8 <=> location10)
84138
end
85139

86140
def test_to_s
87-
location = Location.new("foo.rb", 1, 2, 3, 4)
88-
assert_equal("foo.rb:1:2-3:4", location.to_s)
141+
location1 = Location.new("foo.rb", start_line: 1, start_column: 2, end_line: 3, end_column: 4)
142+
assert_equal("foo.rb:1:2-3:4", location1.to_s)
143+
144+
location2 = Location.new("foo.rb", start_line: 1, end_line: 3)
145+
assert_equal("foo.rb:1-3", location2.to_s)
146+
147+
location3 = Location.new("foo.rb")
148+
assert_equal("foo.rb", location3.to_s)
89149
end
90150
end
91151
end

0 commit comments

Comments
 (0)