Range formatting API (#9635)

This commit is contained in:
Micha Reiser 2024-01-31 11:13:37 +01:00 committed by GitHub
parent 6bb126415d
commit ce14f4dea5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 3273 additions and 762 deletions

View file

@ -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

View 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")

View 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" )

View file

@ -0,0 +1,6 @@
def test ():
<RANGE_START># Some leading comment
# that spans multiple lines
<RANGE_END>
print("Do not format this" )

View 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" )

View file

@ -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
}
]

View file

@ -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)

View file

@ -0,0 +1 @@
<RANGE_START><RANGE_END>

View file

@ -0,0 +1,2 @@
def test():
<RANGE_START><RANGE_END>print( "test" )

View 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

View file

@ -0,0 +1,12 @@
[
{
"indent_style": "space"
},
{
"indent_style": "tab"
},
{
"indent_style": "space",
"indent_width": 2
}
]

View 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" )

View 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" )

View file

@ -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" )

View 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" )

View file

@ -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" )

View 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

View 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>

View file

@ -0,0 +1,6 @@
[
{
"preview": "enabled",
"source_type": "Stub"
}
]

View 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>

View 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" )

View file

@ -0,0 +1,6 @@
def test():
pass <RANGE_START>
<RANGE_END>
def test_formatted(): pass