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,5 +1,7 @@
use std::borrow::Cow;
use std::fmt::{Formatter, Write};
use std::io::BufReader;
use std::ops::Range;
use std::path::Path;
use std::{fmt, fs};
@ -8,8 +10,10 @@ use similar::TextDiff;
use crate::normalizer::Normalizer;
use ruff_formatter::FormatOptions;
use ruff_python_ast::comparable::ComparableMod;
use ruff_python_formatter::{format_module_source, PreviewMode, PyFormatOptions};
use ruff_python_formatter::{format_module_source, format_range, PreviewMode, PyFormatOptions};
use ruff_python_parser::{parse, AsMode};
use ruff_source_file::{LineIndex, OneIndexed};
use ruff_text_size::{TextRange, TextSize};
mod normalizer;
@ -28,12 +32,65 @@ fn black_compatibility() {
PyFormatOptions::from_extension(input_path)
};
let printed = format_module_source(&content, options.clone()).unwrap_or_else(|err| {
panic!(
"Formatting of {} to succeed but encountered error {err}",
input_path.display()
)
});
let first_line = content.lines().next().unwrap_or_default();
let formatted_code = if first_line.starts_with("# flags:")
&& first_line.contains("--line-ranges=")
{
let line_index = LineIndex::from_source_text(&content);
let ranges = first_line
.split_ascii_whitespace()
.filter_map(|chunk| {
let (_, lines) = chunk.split_once("--line-ranges=")?;
let (lower, upper) = lines.split_once('-')?;
let lower = lower
.parse::<OneIndexed>()
.expect("Expected a valid line number");
let upper = upper
.parse::<OneIndexed>()
.expect("Expected a valid line number");
let range_start = line_index.line_start(lower, &content);
let range_end = line_index.line_end(upper, &content);
Some(TextRange::new(range_start, range_end))
})
.rev();
let mut formatted_code = content.clone();
for range in ranges {
let formatted =
format_range(&content, range, options.clone()).unwrap_or_else(|err| {
panic!(
"Range-formatting of {} to succeed but encountered error {err}",
input_path.display()
)
});
let range = formatted.source_range();
formatted_code.replace_range(Range::<usize>::from(range), formatted.as_code());
}
// We can't do stability checks for range formatting because we don't know the updated rangs.
formatted_code
} else {
let printed = format_module_source(&content, options.clone()).unwrap_or_else(|err| {
panic!(
"Formatting of {} to succeed but encountered error {err}",
input_path.display()
)
});
let formatted_code = printed.into_code();
ensure_stability_when_formatting_twice(&formatted_code, &options, input_path);
formatted_code
};
let extension = input_path
.extension()
@ -43,10 +100,7 @@ fn black_compatibility() {
let expected_output = fs::read_to_string(&expected_path)
.unwrap_or_else(|_| panic!("Expected Black output file '{expected_path:?}' to exist"));
let formatted_code = printed.as_code();
ensure_unchanged_ast(&content, formatted_code, &options, input_path);
ensure_stability_when_formatting_twice(formatted_code, &options, input_path);
ensure_unchanged_ast(&content, &formatted_code, &options, input_path);
if formatted_code == expected_output {
// Black and Ruff formatting matches. Delete any existing snapshot files because the Black output
@ -88,7 +142,7 @@ fn black_compatibility() {
write!(snapshot, "{}", Header::new("Black Differences")).unwrap();
let diff = TextDiff::from_lines(expected_output.as_str(), formatted_code)
let diff = TextDiff::from_lines(expected_output.as_str(), &formatted_code)
.unified_diff()
.header("Black", "Ruff")
.to_string();
@ -124,12 +178,7 @@ fn format() {
let content = fs::read_to_string(input_path).unwrap();
let options = PyFormatOptions::from_extension(input_path);
let printed =
format_module_source(&content, options.clone()).expect("Formatting to succeed");
let formatted_code = printed.as_code();
ensure_unchanged_ast(&content, formatted_code, &options, input_path);
ensure_stability_when_formatting_twice(formatted_code, &options, input_path);
let formatted_code = format_file(&content, &options, input_path);
let mut snapshot = format!("## Input\n{}", CodeFrame::new("python", &content));
@ -142,12 +191,7 @@ fn format() {
writeln!(snapshot, "## Outputs").unwrap();
for (i, options) in options.into_iter().enumerate() {
let printed =
format_module_source(&content, options.clone()).expect("Formatting to succeed");
let formatted_code = printed.as_code();
ensure_unchanged_ast(&content, formatted_code, &options, input_path);
ensure_stability_when_formatting_twice(formatted_code, &options, input_path);
let formatted_code = format_file(&content, &options, input_path);
writeln!(
snapshot,
@ -164,16 +208,7 @@ fn format() {
// We want to capture the differences in the preview style in our fixtures
let options_preview = options.with_preview(PreviewMode::Enabled);
let printed_preview = format_module_source(&content, options_preview.clone())
.expect("Formatting to succeed");
let formatted_preview = printed_preview.as_code();
ensure_unchanged_ast(&content, formatted_preview, &options_preview, input_path);
ensure_stability_when_formatting_twice(
formatted_preview,
&options_preview,
input_path,
);
let formatted_preview = format_file(&content, &options_preview, input_path);
if formatted_code != formatted_preview {
// Having both snapshots makes it hard to see the difference, so we're keeping only
@ -183,7 +218,7 @@ fn format() {
"#### Preview changes\n{}",
CodeFrame::new(
"diff",
TextDiff::from_lines(formatted_code, formatted_preview)
TextDiff::from_lines(&formatted_code, &formatted_preview)
.unified_diff()
.header("Stable", "Preview")
)
@ -194,12 +229,7 @@ fn format() {
} else {
// We want to capture the differences in the preview style in our fixtures
let options_preview = options.with_preview(PreviewMode::Enabled);
let printed_preview = format_module_source(&content, options_preview.clone())
.expect("Formatting to succeed");
let formatted_preview = printed_preview.as_code();
ensure_unchanged_ast(&content, formatted_preview, &options_preview, input_path);
ensure_stability_when_formatting_twice(formatted_preview, &options_preview, input_path);
let formatted_preview = format_file(&content, &options_preview, input_path);
if formatted_code == formatted_preview {
writeln!(
@ -217,7 +247,7 @@ fn format() {
CodeFrame::new("python", &formatted_code),
CodeFrame::new(
"diff",
TextDiff::from_lines(formatted_code, formatted_preview)
TextDiff::from_lines(&formatted_code, &formatted_preview)
.unified_diff()
.header("Stable", "Preview")
)
@ -242,6 +272,66 @@ fn format() {
);
}
fn format_file(source: &str, options: &PyFormatOptions, input_path: &Path) -> String {
let (unformatted, formatted_code) = if source.contains("<RANGE_START>") {
let mut content = source.to_string();
let without_markers = content
.replace("<RANGE_START>", "")
.replace("<RANGE_END>", "");
while let Some(range_start_marker) = content.find("<RANGE_START>") {
// Remove the start marker
content.replace_range(
range_start_marker..range_start_marker + "<RANGE_START>".len(),
"",
);
let range_end_marker = content[range_start_marker..]
.find("<RANGE_END>")
.expect("Matching <RANGE_END> marker for <RANGE_START> to exist")
+ range_start_marker;
content.replace_range(range_end_marker..range_end_marker + "<RANGE_END>".len(), "");
// Replace all other markers to get a valid Python input
let format_input = content
.replace("<RANGE_START>", "")
.replace("<RANGE_END>", "");
let range = TextRange::new(
TextSize::try_from(range_start_marker).unwrap(),
TextSize::try_from(range_end_marker).unwrap(),
);
let formatted =
format_range(&format_input, range, options.clone()).unwrap_or_else(|err| {
panic!(
"Range-formatting of {} to succeed but encountered error {err}",
input_path.display()
)
});
content.replace_range(
Range::<usize>::from(formatted.source_range()),
formatted.as_code(),
);
}
(Cow::Owned(without_markers), content)
} else {
let printed = format_module_source(source, options.clone()).expect("Formatting to succeed");
let formatted_code = printed.into_code();
ensure_stability_when_formatting_twice(&formatted_code, options, input_path);
(Cow::Borrowed(source), formatted_code)
};
ensure_unchanged_ast(&unformatted, &formatted_code, options, input_path);
formatted_code
}
/// Format another time and make sure that there are no changes anymore
fn ensure_stability_when_formatting_twice(
formatted_code: &str,

View file

@ -1,319 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/line_ranges_basic.py
---
## Input
```python
# flags: --line-ranges=5-6
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
def foo1(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
def foo2(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
def foo3(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
def foo4(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
# Adding some unformated code covering a wide range of syntaxes.
if True:
# Incorrectly indented prefix comments.
pass
import typing
from typing import (
Any ,
)
class MyClass( object): # Trailing comment with extra leading space.
#NOTE: The following indentation is incorrect:
@decor( 1 * 3 )
def my_func( arg):
pass
try: # Trailing comment with extra leading space.
for i in range(10): # Trailing comment with extra leading space.
while condition:
if something:
then_something( )
elif something_else:
then_something_else( )
except ValueError as e:
unformatted( )
finally:
unformatted( )
async def test_async_unformatted( ): # Trailing comment with extra leading space.
async for i in some_iter( unformatted ): # Trailing comment with extra leading space.
await asyncio.sleep( 1 )
async with some_context( unformatted ):
print( "unformatted" )
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -1,7 +1,18 @@
# flags: --line-ranges=5-6
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
-def foo1(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
+def foo1(
+ parameter_1,
+ parameter_2,
+ parameter_3,
+ parameter_4,
+ parameter_5,
+ parameter_6,
+ parameter_7,
+):
+ pass
+
+
def foo2(
parameter_1,
parameter_2,
@@ -26,38 +37,52 @@
pass
-def foo4(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
+def foo4(
+ parameter_1,
+ parameter_2,
+ parameter_3,
+ parameter_4,
+ parameter_5,
+ parameter_6,
+ parameter_7,
+):
+ pass
+
# Adding some unformated code covering a wide range of syntaxes.
if True:
- # Incorrectly indented prefix comments.
- pass
+ # Incorrectly indented prefix comments.
+ pass
-import typing
-from typing import (
- Any ,
- )
-class MyClass( object): # Trailing comment with extra leading space.
- #NOTE: The following indentation is incorrect:
- @decor( 1 * 3 )
- def my_func( arg):
- pass
+import typing
+from typing import (
+ Any,
+)
+
+
+class MyClass(object): # Trailing comment with extra leading space.
+ # NOTE: The following indentation is incorrect:
+ @decor(1 * 3)
+ def my_func(arg):
+ pass
-try: # Trailing comment with extra leading space.
- for i in range(10): # Trailing comment with extra leading space.
- while condition:
- if something:
- then_something( )
- elif something_else:
- then_something_else( )
-except ValueError as e:
- unformatted( )
+
+try: # Trailing comment with extra leading space.
+ for i in range(10): # Trailing comment with extra leading space.
+ while condition:
+ if something:
+ then_something()
+ elif something_else:
+ then_something_else()
+except ValueError as e:
+ unformatted()
finally:
- unformatted( )
+ unformatted()
+
-async def test_async_unformatted( ): # Trailing comment with extra leading space.
- async for i in some_iter( unformatted ): # Trailing comment with extra leading space.
- await asyncio.sleep( 1 )
- async with some_context( unformatted ):
- print( "unformatted" )
+async def test_async_unformatted(): # Trailing comment with extra leading space.
+ async for i in some_iter(unformatted): # Trailing comment with extra leading space.
+ await asyncio.sleep(1)
+ async with some_context(unformatted):
+ print("unformatted")
```
## Ruff Output
```python
# flags: --line-ranges=5-6
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
def foo1(
parameter_1,
parameter_2,
parameter_3,
parameter_4,
parameter_5,
parameter_6,
parameter_7,
):
pass
def foo2(
parameter_1,
parameter_2,
parameter_3,
parameter_4,
parameter_5,
parameter_6,
parameter_7,
):
pass
def foo3(
parameter_1,
parameter_2,
parameter_3,
parameter_4,
parameter_5,
parameter_6,
parameter_7,
):
pass
def foo4(
parameter_1,
parameter_2,
parameter_3,
parameter_4,
parameter_5,
parameter_6,
parameter_7,
):
pass
# Adding some unformated code covering a wide range of syntaxes.
if True:
# Incorrectly indented prefix comments.
pass
import typing
from typing import (
Any,
)
class MyClass(object): # Trailing comment with extra leading space.
# NOTE: The following indentation is incorrect:
@decor(1 * 3)
def my_func(arg):
pass
try: # Trailing comment with extra leading space.
for i in range(10): # Trailing comment with extra leading space.
while condition:
if something:
then_something()
elif something_else:
then_something_else()
except ValueError as e:
unformatted()
finally:
unformatted()
async def test_async_unformatted(): # Trailing comment with extra leading space.
async for i in some_iter(unformatted): # Trailing comment with extra leading space.
await asyncio.sleep(1)
async with some_context(unformatted):
print("unformatted")
```
## Black Output
```python
# flags: --line-ranges=5-6
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
def foo1(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
def foo2(
parameter_1,
parameter_2,
parameter_3,
parameter_4,
parameter_5,
parameter_6,
parameter_7,
):
pass
def foo3(
parameter_1,
parameter_2,
parameter_3,
parameter_4,
parameter_5,
parameter_6,
parameter_7,
):
pass
def foo4(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
# Adding some unformated code covering a wide range of syntaxes.
if True:
# Incorrectly indented prefix comments.
pass
import typing
from typing import (
Any ,
)
class MyClass( object): # Trailing comment with extra leading space.
#NOTE: The following indentation is incorrect:
@decor( 1 * 3 )
def my_func( arg):
pass
try: # Trailing comment with extra leading space.
for i in range(10): # Trailing comment with extra leading space.
while condition:
if something:
then_something( )
elif something_else:
then_something_else( )
except ValueError as e:
unformatted( )
finally:
unformatted( )
async def test_async_unformatted( ): # Trailing comment with extra leading space.
async for i in some_iter( unformatted ): # Trailing comment with extra leading space.
await asyncio.sleep( 1 )
async with some_context( unformatted ):
print( "unformatted" )
```

View file

@ -25,16 +25,14 @@ print ( "format me" )
```diff
--- Black
+++ Ruff
@@ -6,7 +6,8 @@
# This can be fixed in the future if we use a better diffing algorithm, or make Black
# perform formatting in a single pass.
-print ( "format me" )
@@ -9,5 +9,5 @@
print ( "format me" )
print("format me")
print("format me")
print("format me")
print("format me")
+print("format me")
-print("format me")
-print("format me")
+print ( "format me" )
+print ( "format me" )
```
## Ruff Output
@ -48,11 +46,11 @@ print ( "format me" )
# This can be fixed in the future if we use a better diffing algorithm, or make Black
# perform formatting in a single pass.
print ( "format me" )
print("format me")
print("format me")
print("format me")
print("format me")
print("format me")
print ( "format me" )
print ( "format me" )
```
## Black Output

View file

@ -1,111 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/line_ranges_fmt_off.py
---
## Input
```python
# flags: --line-ranges=7-7 --line-ranges=17-23
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
# fmt: off
import os
def myfunc( ): # Intentionally unformatted.
pass
# fmt: on
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
# fmt: off
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
# fmt: on
def myfunc( ): # This will be reformatted.
print( {"this will be reformatted"} )
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -9,8 +9,10 @@
# fmt: on
-def myfunc( ): # This will not be reformatted.
- print( {"also won't be reformatted"} )
+def myfunc(): # This will not be reformatted.
+ print({"also won't be reformatted"})
+
+
# fmt: off
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
```
## Ruff Output
```python
# flags: --line-ranges=7-7 --line-ranges=17-23
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
# fmt: off
import os
def myfunc( ): # Intentionally unformatted.
pass
# fmt: on
def myfunc(): # This will not be reformatted.
print({"also won't be reformatted"})
# fmt: off
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
# fmt: on
def myfunc(): # This will be reformatted.
print({"this will be reformatted"})
```
## Black Output
```python
# flags: --line-ranges=7-7 --line-ranges=17-23
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
# fmt: off
import os
def myfunc( ): # Intentionally unformatted.
pass
# fmt: on
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
# fmt: off
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
# fmt: on
def myfunc(): # This will be reformatted.
print({"this will be reformatted"})
```

View file

@ -33,12 +33,7 @@ class MyClass:
```diff
--- Black
+++ Ruff
@@ -4,12 +4,11 @@
# Regression test for an edge case involving decorators and fmt: off/on.
class MyClass:
-
# fmt: off
@@ -9,7 +9,7 @@
@decorator ( )
# fmt: on
def method():
@ -47,7 +42,7 @@ class MyClass:
@decor(
a=1,
@@ -18,4 +17,4 @@
@@ -18,4 +18,4 @@
# fmt: on
)
def func():
@ -64,6 +59,7 @@ class MyClass:
# Regression test for an edge case involving decorators and fmt: off/on.
class MyClass:
# fmt: off
@decorator ( )
# fmt: on

View file

@ -1,93 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/line_ranges_fmt_off_overlap.py
---
## Input
```python
# flags: --line-ranges=11-17
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
# fmt: off
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
# fmt: on
def myfunc( ): # This will be reformatted.
print( {"this will be reformatted"} )
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -3,8 +3,10 @@
# flag above as it's formatting specifically these lines.
-def myfunc( ): # This will not be reformatted.
- print( {"also won't be reformatted"} )
+def myfunc(): # This will not be reformatted.
+ print({"also won't be reformatted"})
+
+
# fmt: off
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
```
## Ruff Output
```python
# flags: --line-ranges=11-17
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
def myfunc(): # This will not be reformatted.
print({"also won't be reformatted"})
# fmt: off
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
# fmt: on
def myfunc(): # This will be reformatted.
print({"this will be reformatted"})
```
## Black Output
```python
# flags: --line-ranges=11-17
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
# fmt: off
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
def myfunc( ): # This will not be reformatted.
print( {"also won't be reformatted"} )
# fmt: on
def myfunc(): # This will be reformatted.
print({"this will be reformatted"})
```

View file

@ -1,69 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/line_ranges_indentation.py
---
## Input
```python
# flags: --line-ranges=5-5
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
if cond1:
print("first")
if cond2:
print("second")
else:
print("else")
if another_cond:
print("will not be changed")
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -9,4 +9,4 @@
print("else")
if another_cond:
- print("will not be changed")
+ print("will not be changed")
```
## Ruff Output
```python
# flags: --line-ranges=5-5
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
if cond1:
print("first")
if cond2:
print("second")
else:
print("else")
if another_cond:
print("will not be changed")
```
## Black Output
```python
# flags: --line-ranges=5-5
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
if cond1:
print("first")
if cond2:
print("second")
else:
print("else")
if another_cond:
print("will not be changed")
```

View file

@ -1,74 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/line_ranges_two_passes.py
---
## Input
```python
# flags: --line-ranges=9-11
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
# This is a specific case for Black's two-pass formatting behavior in `format_str`.
# The second pass must respect the line ranges before the first pass.
def restrict_to_this_line(arg1,
arg2,
arg3):
print ( "This should not be formatted." )
print ( "Note that in the second pass, the original line range 9-11 will cover these print lines.")
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -7,5 +7,7 @@
def restrict_to_this_line(arg1, arg2, arg3):
- print ( "This should not be formatted." )
- print ( "Note that in the second pass, the original line range 9-11 will cover these print lines.")
+ print("This should not be formatted.")
+ print(
+ "Note that in the second pass, the original line range 9-11 will cover these print lines."
+ )
```
## Ruff Output
```python
# flags: --line-ranges=9-11
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
# This is a specific case for Black's two-pass formatting behavior in `format_str`.
# The second pass must respect the line ranges before the first pass.
def restrict_to_this_line(arg1, arg2, arg3):
print("This should not be formatted.")
print(
"Note that in the second pass, the original line range 9-11 will cover these print lines."
)
```
## Black Output
```python
# flags: --line-ranges=9-11
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.
# This is a specific case for Black's two-pass formatting behavior in `format_str`.
# The second pass must respect the line ranges before the first pass.
def restrict_to_this_line(arg1, arg2, arg3):
print ( "This should not be formatted." )
print ( "Note that in the second pass, the original line range 9-11 will cover these print lines.")
```

View file

@ -0,0 +1,53 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/ancestory.py
---
## Input
```python
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")
```
## Output
```python
def test ():
if True:
print(1 + 2)
else:
print(3 + 4)
print(" Do not format this")
def test_empty_lines ():
if True:
print(1 + 2)
else:
print(3 + 4)
print(" Do not format this")
```

View file

@ -0,0 +1,101 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/clause_header.py
---
## Input
```python
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" )
```
## Output
```python
def test(a, b, c: str, d):
print ( "Don't format the body when only making changes to the clause header")
print( "Should not get formatted")
class Test(OtherClass): # comment
# Should not get formatted
def __init__( self):
print("hello")
print( "dont' format this")
def test2(a, b, c: str, d):
print ( "Don't format the body when only making changes to the clause header")
print( "Should not get formatted")
def test3(a, b, c: str, d): # fmt: skip
print ( "Don't format the body when only making changes to the clause header")
def test4(a):
print("Format this")
if True:
print("and this")
print("Not this" )
if a + b: # trailing clause header comment
print("Not formatted" )
if b + c: # trailing clause header comment
print("Not formatted" )
```

View file

@ -0,0 +1,26 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/comment_only_range.py
---
## Input
```python
def test ():
<RANGE_START># Some leading comment
# that spans multiple lines
<RANGE_END>
print("Do not format this" )
```
## Output
```python
def test ():
# Some leading comment
# that spans multiple lines
print("Do not format this" )
```

View file

@ -0,0 +1,59 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/decorators.py
---
## Input
```python
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" )
```
## Output
```python
def test():
print("before" )
@decorator(aa)
def func():
print("Do not format this" )
@decorator(a)
def test(a):
print( "body")
print("after" )
@decorator(a)
def test( a):
print( "body")
print("after" )
```

View file

@ -0,0 +1,391 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/docstring_code_examples.py
---
## Input
```python
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)
```
## Outputs
### Output 1
```
indent-style = space
line-width = 88
indent-width = 4
quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = Py38
source_type = Python
```
```python
def doctest_simple ():
"""
Do cool stuff.
>>> cool_stuff(1)
2
"""
pass
def doctest_only ():
"""
Do cool stuff.
>>> def cool_stuff(x):
... print(f"hi {x}")
hi 2
"""
pass
def in_doctest ():
"""
Do cool stuff.
>>> cool_stuff(x)
>>> cool_stuff(y)
2
"""
pass
def suppressed_doctest ():
"""
Do cool stuff.
>>> cool_stuff( x )
>>> cool_stuff( y )
2
""" # fmt: skip
pass
def fmt_off_doctest ():
# fmt: off
"""
Do cool stuff.
>>> cool_stuff( x )
>>> cool_stuff( y )
2
"""
# fmt: on
pass
if True:
def doctest_long_lines():
"""
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
)
if True:
def doctest_long_lines():
"""
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)
```
### Output 2
```
indent-style = space
line-width = 88
indent-width = 4
quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = 88
preview = Disabled
target_version = Py38
source_type = Python
```
```python
def doctest_simple ():
"""
Do cool stuff.
>>> cool_stuff(1)
2
"""
pass
def doctest_only ():
"""
Do cool stuff.
>>> def cool_stuff(x):
... print(f"hi {x}")
hi 2
"""
pass
def in_doctest ():
"""
Do cool stuff.
>>> cool_stuff(x)
>>> cool_stuff(y)
2
"""
pass
def suppressed_doctest ():
"""
Do cool stuff.
>>> cool_stuff( x )
>>> cool_stuff( y )
2
""" # fmt: skip
pass
def fmt_off_doctest ():
# fmt: off
"""
Do cool stuff.
>>> cool_stuff( x )
>>> cool_stuff( y )
2
"""
# fmt: on
pass
if True:
def doctest_long_lines():
"""
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
)
if True:
def doctest_long_lines():
"""
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)
```

View file

@ -0,0 +1,14 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/empty_file.py
---
## Input
```python
<RANGE_START><RANGE_END>```
## Output
```python
```

View file

@ -0,0 +1,18 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/empty_range.py
---
## Input
```python
def test():
<RANGE_START><RANGE_END>print( "test" )
```
## Output
```python
def test():
print( "test" )
```

View file

@ -0,0 +1,62 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/fmt_on_off.py
---
## Input
```python
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
```
## Output
```python
class MyClass:
# Range that falls entirely in a suppressed range
# fmt: off
def method( self ):
print ( "str" )
# 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
def other( self):
print ( "str" )
# fmt: on
def formatted(self):
pass
def outside_formatting_range (self): pass
```

View file

@ -0,0 +1,310 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/indent.py
---
## Input
```python
# 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" )
```
## Outputs
### Output 1
```
indent-style = space
line-width = 88
indent-width = 4
quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = Py38
source_type = Python
```
```python
# Formats the entire function with tab or 4 space indentation
# because the statement indentations don't match the preferred indentation.
def test ():
print("before")
1 + 2
if True:
pass
print("Done")
print("formatted")
print("not formatted" )
def test2 ():
print("before")
1 + 2
(3 + 2)
print("Done")
print("formatted")
print("not formatted" )
def test3 ():
print("before")
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")
def test4 ():
print("before")
1 + 2
"""A Multiline string
that uses the same indentation as the formatted code will. This should not be dedented."""
print("Done")
def test5 ():
print("before")
if True:
print("Format to fix indentation")
print(1 + 2)
else:
print(3 + 4)
print("Format to fix indentation")
pass
def test6 ():
print("Format")
print(3 + 4)
print("Format to fix indentation" )
```
### Output 2
```
indent-style = tab
line-width = 88
indent-width = 4
quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = Py38
source_type = Python
```
```python
# Formats the entire function with tab or 4 space indentation
# because the statement indentations don't match the preferred indentation.
def test ():
print("before")
1 + 2
if True:
pass
print("Done")
print("formatted")
print("not formatted" )
def test2 ():
print("before")
1 + 2
(3 + 2)
print("Done")
print("formatted")
print("not formatted" )
def test3 ():
print("before")
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")
def test4 ():
print("before")
1 + 2
"""A Multiline string
that uses the same indentation as the formatted code will. This should not be dedented."""
print("Done")
def test5 ():
print("before")
if True:
print("Format to fix indentation")
print(1 + 2)
else:
print(3 + 4)
print("Format to fix indentation")
pass
def test6 ():
print("Format")
print(3 + 4)
print("Format to fix indentation")
```
### Output 3
```
indent-style = space
line-width = 88
indent-width = 2
quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Disabled
target_version = Py38
source_type = Python
```
```python
# Formats the entire function with tab or 4 space indentation
# because the statement indentations don't match the preferred indentation.
def test ():
print("before" )
1 + 2
if True:
pass
print("Done")
print("formatted" )
print("not formatted" )
def test2 ():
print("before" )
1 + 2
(3 + 2)
print("Done")
print("formatted" )
print("not formatted" )
def test3 ():
print("before" )
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")
def test4 ():
print("before" )
1 + 2
"""A Multiline string
that uses the same indentation as the formatted code will. This should not be dedented."""
print("Done")
def test5 ():
print("before" )
if True:
print("Format to fix indentation")
print(1 + 2)
else:
print(3 + 4)
print("Format to fix indentation")
pass
def test6 ():
print("Format")
print(3 + 4)
print("Format to fix indentation")
```

View file

@ -0,0 +1,82 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/leading_comments.py
---
## Input
```python
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" )
```
## Output
```python
def test ():
print( "hello" )
# leading comment
1 + 2
print("world")
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
# leading comment 2
1 + 2
print("world")
print( "unformatted" )
def test3 ():
print("hello")
# leading comment 1
# leading comment 2
1 + 2
print("world")
print( "unformatted" )
```

View file

@ -0,0 +1,46 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/leading_trailing_comments.py
---
## Input
```python
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" )
```
## Output
```python
def test ():
# Leading comments before the statements that should be formatted
# Don't duplicate the comments
print("format this") # 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
print("format this")
# here's some trailing comment as well
print("Do not format this 2" )
```

View file

@ -0,0 +1,34 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/module.py
---
## Input
```python
print("Before range start" )
<RANGE_START>
if a + b :
print("formatted" )
print("still in range" )
<RANGE_END>
print("After range end" )
```
## Output
```python
print("Before range start" )
if a + b:
print("formatted")
print("still in range")
print("After range end" )
```

View file

@ -0,0 +1,30 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/range_narrowing.py
---
## Input
```python
def test ():
<RANGE_START>if True:
print( "format")
elif False:
print ( "and this")<RANGE_END>
print("not this" )
print("nor this" )
```
## Output
```python
def test ():
if True:
print("format")
elif False:
print("and this")
print("not this" )
print("nor this" )
```

View file

@ -0,0 +1,139 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/regressions.py
---
## Input
```python
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
```
## Output
```python
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:
return event_cls
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:
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,
]
print("Don't format this" )
# The user removed an argument from a call. Ruff should reformat the entire call
call(a, b, c, d)
print("Don't format this" )
#-----------------------------------------------------------------------------
# The user adds a new comment at the end:
#
#-----------------------------------------------------------------------------
print("Don't format this" )
def convert_str(value: str) -> str: # Trailing comment
"""Return a string as-is."""
return value # Trailing comment
def test ():
pass
```

View file

@ -0,0 +1,61 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/same_line_body.py
---
## Input
```python
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>
```
## Output
```python
def test(a):
print("body")
def test2( a):
print("body")
def test3( a):
print("body")
print("more")
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:
print("aaaa long body, should wrap or be intented")
# This print statement is too-long even when intented. It should be wrapped
if aaaaaaaaaaaa + bbbbbbbbbbbbbb + cccccccccccccccccc + ddd:
print(
"aaaa long body, should wrap or be intented",
"more content to make it exceed the 88 chars limit",
)
```

View file

@ -0,0 +1,61 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/stub.pyi
---
## Input
```python
# 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>
```
## Outputs
### Output 1
```
indent-style = space
line-width = 88
indent-width = 4
quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Enabled
target_version = Py38
source_type = Stub
```
```python
# Don't collapse the ellipsis if only formatting the ellipsis line.
class Test:
...
class Test2:
pass
class Test3: ...
class Test4:
# leading comment
...
# trailing comment
class Test4: ...
```

View file

@ -0,0 +1,74 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/trailing_comments.py
---
## Input
```python
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" )
```
## Output
```python
def test1 ():
print("hello" )
1 + 2 # trailing comment
print ("world" )
def test2 ():
print("hello" )
# FIXME: For some reason the trailing comment here gets not formatted
# but is correctly formatted above
1 + 2 # trailing comment
print ("world" )
def test3 ():
print("hellO" )
1 + 2 # trailing comment
# trailing section comment
def test3 ():
print("hellO" )
1 + 2 # trailing comment
print("more") # trailing comment 2
# trailing section comment
print( "world" )
```

View file

@ -0,0 +1,25 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/whitespace_only_range.py
---
## Input
```python
def test():
pass <RANGE_START>
<RANGE_END>
def test_formatted(): pass
```
## Output
```python
def test():
pass
def test_formatted(): pass
```