Skip to content

Commit 7b5a657

Browse files
authoredMar 15, 2024··
Fix --line-ranges behavior when ranges are at EOF (#4273)
Fixes #4264
1 parent 1abcffc commit 7b5a657

File tree

6 files changed

+148
-2
lines changed

6 files changed

+148
-2
lines changed
 

‎CHANGES.md

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
of Black would incorrectly format the contents of certain unusual f-strings containing
1616
nested strings with the same quote type. Now, Black will crash on such strings until
1717
support for the new f-string syntax is implemented. (#4270)
18+
- Fixed a bug where line-ranges exceeding the last code line would not work as expected
19+
(#4273)
1820

1921
### Preview style
2022

‎src/black/__init__.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,12 @@
8484
parse_ast,
8585
stringify_ast,
8686
)
87-
from black.ranges import adjusted_lines, convert_unchanged_lines, parse_line_ranges
87+
from black.ranges import (
88+
adjusted_lines,
89+
convert_unchanged_lines,
90+
parse_line_ranges,
91+
sanitized_lines,
92+
)
8893
from black.report import Changed, NothingChanged, Report
8994
from black.trans import iter_fexpr_spans
9095
from blib2to3.pgen2 import token
@@ -1220,6 +1225,10 @@ def f(
12201225
hey
12211226
12221227
"""
1228+
if lines:
1229+
lines = sanitized_lines(lines, src_contents)
1230+
if not lines:
1231+
return src_contents # Nothing to format
12231232
dst_contents = _format_str_once(src_contents, mode=mode, lines=lines)
12241233
# Forced second pass to work around optional trailing commas (becoming
12251234
# forced trailing commas on pass 2) interacting differently with optional

‎src/black/ranges.py

+28
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,34 @@ def is_valid_line_range(lines: Tuple[int, int]) -> bool:
4545
return not lines or lines[0] <= lines[1]
4646

4747

48+
def sanitized_lines(
49+
lines: Collection[Tuple[int, int]], src_contents: str
50+
) -> Collection[Tuple[int, int]]:
51+
"""Returns the valid line ranges for the given source.
52+
53+
This removes ranges that are entirely outside the valid lines.
54+
55+
Other ranges are normalized so that the start values are at least 1 and the
56+
end values are at most the (1-based) index of the last source line.
57+
"""
58+
if not src_contents:
59+
return []
60+
good_lines = []
61+
src_line_count = src_contents.count("\n")
62+
if not src_contents.endswith("\n"):
63+
src_line_count += 1
64+
for start, end in lines:
65+
if start > src_line_count:
66+
continue
67+
# line-ranges are 1-based
68+
start = max(start, 1)
69+
if end < start:
70+
continue
71+
end = min(end, src_line_count)
72+
good_lines.append((start, end))
73+
return good_lines
74+
75+
4876
def adjusted_lines(
4977
lines: Collection[Tuple[int, int]],
5078
original_source: str,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# flags: --line-ranges=6-1000
2+
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
3+
# flag above as it's formatting specifically these lines.
4+
def foo1(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
5+
def foo2(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
6+
def foo3(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
7+
def foo4(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
8+
9+
# output
10+
# flags: --line-ranges=6-1000
11+
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
12+
# flag above as it's formatting specifically these lines.
13+
def foo1(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
14+
def foo2(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
15+
def foo3(
16+
parameter_1,
17+
parameter_2,
18+
parameter_3,
19+
parameter_4,
20+
parameter_5,
21+
parameter_6,
22+
parameter_7,
23+
):
24+
pass
25+
26+
27+
def foo4(
28+
parameter_1,
29+
parameter_2,
30+
parameter_3,
31+
parameter_4,
32+
parameter_5,
33+
parameter_6,
34+
parameter_7,
35+
):
36+
pass
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# flags: --line-ranges=5000-6000
2+
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
3+
# flag above as it's formatting specifically these lines, in this case none.
4+
def foo1(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
5+
def foo2(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
6+
def foo3(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
7+
def foo4(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass

‎tests/test_ranges.py

+65-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import pytest
66

7-
from black.ranges import adjusted_lines
7+
from black.ranges import adjusted_lines, sanitized_lines
88

99

1010
@pytest.mark.parametrize(
@@ -183,3 +183,67 @@ def test_diffs(lines: List[Tuple[int, int]], adjusted: List[Tuple[int, int]]) ->
183183
12. # last line changed
184184
"""
185185
assert adjusted == adjusted_lines(lines, original_source, modified_source)
186+
187+
188+
@pytest.mark.parametrize(
189+
"lines,sanitized",
190+
[
191+
(
192+
[(1, 4)],
193+
[(1, 4)],
194+
),
195+
(
196+
[(2, 3)],
197+
[(2, 3)],
198+
),
199+
(
200+
[(2, 10)],
201+
[(2, 4)],
202+
),
203+
(
204+
[(0, 3)],
205+
[(1, 3)],
206+
),
207+
(
208+
[(0, 10)],
209+
[(1, 4)],
210+
),
211+
(
212+
[(-2, 3)],
213+
[(1, 3)],
214+
),
215+
(
216+
[(0, 0)],
217+
[],
218+
),
219+
(
220+
[(-2, -1)],
221+
[],
222+
),
223+
(
224+
[(-1, 0)],
225+
[],
226+
),
227+
(
228+
[(3, 1), (1, 3), (5, 6)],
229+
[(1, 3)],
230+
),
231+
],
232+
)
233+
def test_sanitize(
234+
lines: List[Tuple[int, int]], sanitized: List[Tuple[int, int]]
235+
) -> None:
236+
source = """\
237+
1. import re
238+
2. def func(arg1,
239+
3. arg2, arg3):
240+
4. pass
241+
"""
242+
assert sanitized == sanitized_lines(lines, source)
243+
244+
source_no_trailing_nl = """\
245+
1. import re
246+
2. def func(arg1,
247+
3. arg2, arg3):
248+
4. pass"""
249+
assert sanitized == sanitized_lines(lines, source_no_trailing_nl)

0 commit comments

Comments
 (0)
Please sign in to comment.