mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-12 15:45:07 +00:00
Range formatting API (#9635)
This commit is contained in:
parent
6bb126415d
commit
ce14f4dea5
65 changed files with 3273 additions and 762 deletions
|
@ -1,3 +1,7 @@
|
|||
[mixed_space_and_tab.py]
|
||||
generated_code = true
|
||||
ij_formatter_enabled = false
|
||||
|
||||
["range_formatting/*.py"]
|
||||
generated_code = true
|
||||
ij_formatter_enabled = false
|
20
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/ancestory.py
vendored
Normal file
20
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/ancestory.py
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
def test ():
|
||||
if True:
|
||||
print(<RANGE_START>1 + 2)
|
||||
|
||||
else:
|
||||
print(3 + 4)<RANGE_END>
|
||||
|
||||
print(" Do not format this")
|
||||
|
||||
|
||||
|
||||
def test_empty_lines ():
|
||||
if True:
|
||||
print(<RANGE_START>1 + 2)
|
||||
|
||||
|
||||
else:
|
||||
print(3 + 4)<RANGE_END>
|
||||
|
||||
print(" Do not format this")
|
44
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/clause_header.py
vendored
Normal file
44
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/clause_header.py
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
def test(<RANGE_START>a, b, c: str<RANGE_END>, d):
|
||||
print ( "Don't format the body when only making changes to the clause header")
|
||||
|
||||
|
||||
print( "Should not get formatted")
|
||||
|
||||
|
||||
class <RANGE_START> Test(OtherClass<RANGE_END>)\
|
||||
: # comment
|
||||
|
||||
# Should not get formatted
|
||||
def __init__( self):
|
||||
print("hello")
|
||||
|
||||
print( "dont' format this")
|
||||
|
||||
|
||||
def test2(<RANGE_START>a, b, c: str, d):<RANGE_END>
|
||||
print ( "Don't format the body when only making changes to the clause header")
|
||||
|
||||
|
||||
print( "Should not get formatted")
|
||||
|
||||
|
||||
def test3(<RANGE_START>a, b, c: str, d):<RANGE_END> # fmt: skip
|
||||
print ( "Don't format the body when only making changes to the clause header")
|
||||
|
||||
|
||||
|
||||
def test4(<RANGE_START> a):
|
||||
print("Format this" )
|
||||
|
||||
if True:
|
||||
print( "and this")<RANGE_END>
|
||||
|
||||
print("Not this" )
|
||||
|
||||
|
||||
<RANGE_START>if a + b : # trailing clause header comment<RANGE_END>
|
||||
print("Not formatted" )
|
||||
|
||||
|
||||
<RANGE_START>if b + c :<RANGE_END> # trailing clause header comment
|
||||
print("Not formatted" )
|
|
@ -0,0 +1,6 @@
|
|||
def test ():
|
||||
<RANGE_START># Some leading comment
|
||||
# that spans multiple lines
|
||||
<RANGE_END>
|
||||
print("Do not format this" )
|
||||
|
23
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/decorators.py
vendored
Normal file
23
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/decorators.py
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
def test():
|
||||
|
||||
print("before" )
|
||||
|
||||
@<RANGE_START> decorator( aa)
|
||||
|
||||
<RANGE_END>def func ():
|
||||
print("Do not format this" )
|
||||
|
||||
|
||||
<RANGE_START>@ decorator( a)
|
||||
def test( a):<RANGE_END>
|
||||
print( "body")
|
||||
|
||||
print("after" )
|
||||
|
||||
|
||||
<RANGE_START>@ decorator( a)<RANGE_END>
|
||||
def test( a):
|
||||
print( "body")
|
||||
|
||||
print("after" )
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
[
|
||||
{
|
||||
"docstring_code": "enabled",
|
||||
"indent_style": "space",
|
||||
"indent_width": 4
|
||||
},
|
||||
{
|
||||
"docstring_code": "enabled",
|
||||
"docstring_code_line_width": 88,
|
||||
"indent_style": "space",
|
||||
"indent_width": 4
|
||||
}
|
||||
]
|
|
@ -0,0 +1,103 @@
|
|||
def doctest_simple ():
|
||||
<RANGE_START>"""
|
||||
Do cool stuff.
|
||||
|
||||
>>> cool_stuff( 1 )
|
||||
2
|
||||
"""
|
||||
pass<RANGE_END>
|
||||
|
||||
|
||||
def doctest_only ():
|
||||
<RANGE_START>"""
|
||||
Do cool stuff.
|
||||
|
||||
>>> def cool_stuff( x ):
|
||||
... print( f"hi {x}" );
|
||||
hi 2
|
||||
"""<RANGE_END>
|
||||
pass
|
||||
|
||||
|
||||
def in_doctest ():
|
||||
"""
|
||||
Do cool stuff.
|
||||
<RANGE_START>
|
||||
>>> cool_stuff( x )
|
||||
>>> cool_stuff( y )
|
||||
2<RANGE_END>
|
||||
"""
|
||||
pass
|
||||
|
||||
def suppressed_doctest ():
|
||||
"""
|
||||
Do cool stuff.
|
||||
<RANGE_START>
|
||||
>>> cool_stuff( x )
|
||||
>>> cool_stuff( y )
|
||||
2<RANGE_END>
|
||||
""" # fmt: skip
|
||||
pass
|
||||
|
||||
|
||||
def fmt_off_doctest ():
|
||||
# fmt: off
|
||||
"""
|
||||
Do cool stuff.
|
||||
<RANGE_START>
|
||||
>>> cool_stuff( x )
|
||||
>>> cool_stuff( y )
|
||||
2<RANGE_END>
|
||||
"""
|
||||
# fmt: on
|
||||
pass
|
||||
|
||||
|
||||
|
||||
if True:
|
||||
def doctest_long_lines():
|
||||
<RANGE_START>
|
||||
'''
|
||||
This won't get wrapped even though it exceeds our configured
|
||||
line width because it doesn't exceed the line width within this
|
||||
docstring. e.g, the `f` in `foo` is treated as the first column.
|
||||
>>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey)
|
||||
|
||||
But this one is long enough to get wrapped.
|
||||
>>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard)
|
||||
|
||||
This one doesn't get wrapped with an indent width of 4 even with `dynamic` line width
|
||||
>>> a = this_is_a_long_line(lion, giraffe, hippo, zebra, lemur, penguin, monkey)
|
||||
|
||||
This one gets wrapped with `dynamic` line width and an indent width of 4 because it exceeds the width by 1
|
||||
>>> ab = this_is_a_long_line(lion, giraffe, hippo, zebra, lemur, penguin, monkey)
|
||||
'''
|
||||
# This demonstrates a normal line that will get wrapped but won't
|
||||
# get wrapped in the docstring above because of how the line-width
|
||||
# setting gets reset at the first column in each code snippet.
|
||||
foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey)
|
||||
<RANGE_END>
|
||||
|
||||
|
||||
if True:
|
||||
def doctest_long_lines():
|
||||
<RANGE_START>'''
|
||||
This won't get wrapped even though it exceeds our configured
|
||||
line width because it doesn't exceed the line width within this
|
||||
docstring. e.g, the `f` in `foo` is treated as the first column.
|
||||
>>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey)
|
||||
|
||||
But this one is long enough to get wrapped.
|
||||
>>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard)
|
||||
|
||||
This one doesn't get wrapped with an indent width of 4 even with `dynamic` line width
|
||||
>>> a = this_is_a_long_line(lion, giraffe, hippo, zebra, lemur, penguin, monkey)
|
||||
|
||||
This one gets wrapped with `dynamic` line width and an indent width of 4 because it exceeds the width by 1
|
||||
>>> ab = this_is_a_long_line(lion, giraffe, hippo, zebra, lemur, penguin, monkey)
|
||||
'''<RANGE_END>
|
||||
# This demonstrates a normal line that will get wrapped but won't
|
||||
# get wrapped in the docstring above because of how the line-width
|
||||
# setting gets reset at the first column in each code snippet.
|
||||
foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey)
|
||||
|
1
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/empty_file.py
vendored
Normal file
1
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/empty_file.py
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<RANGE_START><RANGE_END>
|
2
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/empty_range.py
vendored
Normal file
2
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/empty_range.py
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
def test():
|
||||
<RANGE_START><RANGE_END>print( "test" )
|
24
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/fmt_on_off.py
vendored
Normal file
24
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/fmt_on_off.py
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
class MyClass:
|
||||
|
||||
# Range that falls entirely in a suppressed range
|
||||
# fmt: off<RANGE_START>
|
||||
def method( self ):
|
||||
print ( "str" )
|
||||
<RANGE_END># fmt: on
|
||||
|
||||
# This should net get formatted because it isn't in a formatting range.
|
||||
def not_in_formatting_range ( self): ...
|
||||
|
||||
|
||||
# Range that starts in a suppressed range and ends in a formatting range
|
||||
# fmt: off<RANGE_START>
|
||||
def other( self):
|
||||
print ( "str" )
|
||||
|
||||
# fmt: on
|
||||
|
||||
def formatted ( self):
|
||||
pass
|
||||
<RANGE_END>
|
||||
def outside_formatting_range (self): pass
|
||||
|
12
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/indent.options.json
vendored
Normal file
12
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/indent.options.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
[
|
||||
{
|
||||
"indent_style": "space"
|
||||
},
|
||||
{
|
||||
"indent_style": "tab"
|
||||
},
|
||||
{
|
||||
"indent_style": "space",
|
||||
"indent_width": 2
|
||||
}
|
||||
]
|
63
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/indent.py
vendored
Normal file
63
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/indent.py
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
# Formats the entire function with tab or 4 space indentation
|
||||
# because the statement indentations don't match the preferred indentation.
|
||||
def test ():
|
||||
print("before" )
|
||||
<RANGE_START>1 + 2
|
||||
if True:
|
||||
pass
|
||||
print("Done" )<RANGE_END>
|
||||
|
||||
print("formatted" )
|
||||
|
||||
print("not formatted" )
|
||||
|
||||
def test2 ():
|
||||
print("before" )
|
||||
<RANGE_START>1 + 2
|
||||
(
|
||||
3 + 2
|
||||
)
|
||||
print("Done" )<RANGE_END>
|
||||
|
||||
print("formatted" )
|
||||
|
||||
print("not formatted" )
|
||||
|
||||
def test3 ():
|
||||
print("before" )
|
||||
<RANGE_START>1 + 2
|
||||
"""A Multiline string
|
||||
that starts at the beginning of the line and we need to preserve the leading spaces"""
|
||||
|
||||
"""A Multiline string
|
||||
that has some indentation on the second line and we need to preserve the leading spaces"""
|
||||
|
||||
print("Done" )<RANGE_END>
|
||||
|
||||
|
||||
def test4 ():
|
||||
print("before" )
|
||||
<RANGE_START>1 + 2
|
||||
"""A Multiline string
|
||||
that uses the same indentation as the formatted code will. This should not be dedented."""
|
||||
|
||||
print("Done" )<RANGE_END>
|
||||
|
||||
def test5 ():
|
||||
print("before" )
|
||||
if True:
|
||||
print("Format to fix indentation" )
|
||||
print(<RANGE_START>1 + 2)
|
||||
|
||||
else:
|
||||
print(3 + 4)<RANGE_END>
|
||||
print("Format to fix indentation" )
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def test6 ():
|
||||
<RANGE_START>
|
||||
print("Format" )
|
||||
print(3 + 4)<RANGE_END>
|
||||
print("Format to fix indentation" )
|
34
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/leading_comments.py
vendored
Normal file
34
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/leading_comments.py
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
def test ():
|
||||
print( "hello" )
|
||||
<RANGE_START># leading comment
|
||||
1 + 2
|
||||
|
||||
print( "world" )<RANGE_END>
|
||||
|
||||
print( "unformatted" )
|
||||
|
||||
|
||||
print( "Hy" )
|
||||
|
||||
|
||||
def test2 ():
|
||||
print( "hello" )
|
||||
# Leading comments don't get formatted. That's why Ruff won't fixup
|
||||
# the indentation here. That's something we might want to explore in the future
|
||||
# leading comment 1<RANGE_START>
|
||||
# leading comment 2
|
||||
1 + 2
|
||||
|
||||
print( "world" )<RANGE_END>
|
||||
|
||||
print( "unformatted" )
|
||||
|
||||
def test3 ():
|
||||
<RANGE_START>print( "hello" )
|
||||
# leading comment 1
|
||||
# leading comment 2
|
||||
1 + 2
|
||||
|
||||
print( "world" )<RANGE_END>
|
||||
|
||||
print( "unformatted" )
|
|
@ -0,0 +1,16 @@
|
|||
def test ():
|
||||
# Leading comments before the statements that should be formatted
|
||||
# Don't duplicate the comments
|
||||
<RANGE_START>print( "format this")<RANGE_END> # trailing end of line comment
|
||||
# here's some trailing comment as well
|
||||
|
||||
print("Do not format this" )
|
||||
|
||||
def test ():
|
||||
# Leading comments before the statements that should be formatted
|
||||
# Don't duplicate the comments
|
||||
<RANGE_START>print( "format this")
|
||||
# here's some trailing comment as well
|
||||
<RANGE_END>
|
||||
|
||||
print("Do not format this 2" )
|
10
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/module.py
vendored
Normal file
10
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/module.py
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
print("Before range start" )
|
||||
|
||||
<RANGE_START>
|
||||
if a + b :
|
||||
print("formatted" )
|
||||
|
||||
print("still in range" )
|
||||
<RANGE_END>
|
||||
|
||||
print("After range end" )
|
|
@ -0,0 +1,8 @@
|
|||
def test ():
|
||||
<RANGE_START>if True:
|
||||
print( "format")
|
||||
elif False:
|
||||
print ( "and this")<RANGE_END>
|
||||
print("not this" )
|
||||
|
||||
print("nor this" )
|
69
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/regressions.py
vendored
Normal file
69
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/regressions.py
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
class Event:
|
||||
event_name: ClassVar[str]
|
||||
|
||||
@staticmethod
|
||||
def cls_for(event_name: str) -> type[Event]:
|
||||
event_cls = _CONCRETE_EVENT_CLASSES.get(event_name)
|
||||
if event_cls is not <RANGE_START>None:
|
||||
return event_cls<RANGE_END>
|
||||
else:
|
||||
raise ValueError(f"unknown event name '{event_name}'")
|
||||
|
||||
class Event:
|
||||
event_name: ClassVar[str]
|
||||
|
||||
@staticmethod
|
||||
def cls_for(event_name: str) -> type[Event]:
|
||||
event_cls = _CONCRETE_EVENT_CLASSES.get(event_name)
|
||||
if event_cls is not None:
|
||||
<RANGE_START>
|
||||
|
||||
<RANGE_END>
|
||||
return event_cls
|
||||
else:
|
||||
raise ValueError(f"unknown event name '{event_name}'")
|
||||
|
||||
|
||||
# The user starts adding items to a list and then hits save.
|
||||
# Ruff should trim the empty lines
|
||||
a = [
|
||||
1,
|
||||
2,
|
||||
3,<RANGE_START>
|
||||
|
||||
|
||||
<RANGE_END>
|
||||
]
|
||||
|
||||
print("Don't format this" )
|
||||
|
||||
|
||||
# The user removed an argument from a call. Ruff should reformat the entire call
|
||||
call(
|
||||
a,
|
||||
<RANGE_START>
|
||||
<RANGE_END>b,
|
||||
c,
|
||||
d
|
||||
)
|
||||
|
||||
print("Don't format this" )
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# The user adds a new comment at the end:
|
||||
<RANGE_START># <RANGE_END>
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
print("Don't format this" )
|
||||
|
||||
|
||||
def convert_str(value: str) -> str: # Trailing comment
|
||||
"""Return a string as-is."""
|
||||
|
||||
<RANGE_START>
|
||||
|
||||
return value # Trailing comment
|
||||
<RANGE_END>
|
||||
def test ():
|
||||
pass
|
19
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/same_line_body.py
vendored
Normal file
19
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/same_line_body.py
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
def test(<RANGE_START>a ): <RANGE_END>print("body" )
|
||||
|
||||
|
||||
def test2( a): <RANGE_START>print("body" )<RANGE_END>
|
||||
|
||||
|
||||
def test3( a): <RANGE_START>print("body" )
|
||||
|
||||
print("more" )<RANGE_END>
|
||||
print("after" )
|
||||
|
||||
|
||||
# The if header and the print statement together are longer than 100 characters.
|
||||
# The print statement should either be wrapped to fit at the end of the if statement, or be converted to a
|
||||
# suite body
|
||||
if aaaaaaaaaaaa + bbbbbbbbbbbbbb + cccccccccccccccccc + ddd: <RANGE_START>print("aaaa long body, should wrap or be intented" )<RANGE_END>
|
||||
|
||||
# This print statement is too-long even when intented. It should be wrapped
|
||||
if aaaaaaaaaaaa + bbbbbbbbbbbbbb + cccccccccccccccccc + ddd: <RANGE_START>print("aaaa long body, should wrap or be intented", "more content to make it exceed the 88 chars limit")<RANGE_END>
|
6
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/stub.options.json
vendored
Normal file
6
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/stub.options.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
[
|
||||
{
|
||||
"preview": "enabled",
|
||||
"source_type": "Stub"
|
||||
}
|
||||
]
|
16
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/stub.pyi
vendored
Normal file
16
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/stub.pyi
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Don't collapse the ellipsis if only formatting the ellipsis line.
|
||||
class Test:
|
||||
<RANGE_START>...<RANGE_END>
|
||||
|
||||
class Test2: <RANGE_START>pass<RANGE_END>
|
||||
|
||||
class Test3: <RANGE_START>...<RANGE_END>
|
||||
|
||||
class Test4:
|
||||
# leading comment
|
||||
<RANGE_START>...<RANGE_END>
|
||||
# trailing comment
|
||||
|
||||
|
||||
class Test4:
|
||||
<RANGE_START> ...<RANGE_END>
|
30
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/trailing_comments.py
vendored
Normal file
30
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/trailing_comments.py
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
def test1 ():
|
||||
print("hello" )
|
||||
|
||||
<RANGE_START>1 + 2<RANGE_END> # trailing comment
|
||||
print ("world" )
|
||||
|
||||
def test2 ():
|
||||
print("hello" )
|
||||
# FIXME: For some reason the trailing comment here gets not formatted
|
||||
# but is correctly formatted above
|
||||
<RANGE_START>1 + 2 # trailing comment<RANGE_END>
|
||||
print ("world" )
|
||||
|
||||
def test3 ():
|
||||
print("hellO" )
|
||||
|
||||
<RANGE_START>1 + 2 # trailing comment
|
||||
|
||||
# trailing section comment
|
||||
<RANGE_END>
|
||||
|
||||
def test3 ():
|
||||
print("hellO" )
|
||||
|
||||
<RANGE_START>1 + 2 # trailing comment
|
||||
print("more" ) # trailing comment 2
|
||||
# trailing section comment
|
||||
<RANGE_END>
|
||||
|
||||
print( "world" )
|
|
@ -0,0 +1,6 @@
|
|||
def test():
|
||||
pass <RANGE_START>
|
||||
|
||||
<RANGE_END>
|
||||
|
||||
def test_formatted(): pass
|
Loading…
Add table
Add a link
Reference in a new issue