Add support for basic Constant::Str formatting (#3173)

This PR enables us to apply the proper quotation marks, including support for escapes. There are some significant TODOs, especially around implicit concatenations like:

```py
(
  "abc"
  "def"
)
```

Which are represented as a single AST node, which requires us to tokenize _within_ the formatter to identify all the individual string parts.
This commit is contained in:
Charlie Marsh 2023-02-23 11:23:10 -05:00 committed by GitHub
parent 095f005bf4
commit f967f344fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 426 additions and 706 deletions

1
Cargo.lock generated
View file

@ -2110,6 +2110,7 @@ dependencies = [
"insta", "insta",
"once_cell", "once_cell",
"ruff_formatter", "ruff_formatter",
"ruff_python",
"ruff_rustpython", "ruff_rustpython",
"ruff_testing_macros", "ruff_testing_macros",
"ruff_text_size", "ruff_text_size",

View file

@ -1,13 +0,0 @@
/// See: <https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals>
pub const TRIPLE_QUOTE_PREFIXES: &[&str] = &[
"u\"\"\"", "u'''", "r\"\"\"", "r'''", "U\"\"\"", "U'''", "R\"\"\"", "R'''", "\"\"\"", "'''",
];
pub const SINGLE_QUOTE_PREFIXES: &[&str] = &[
"u\"", "u'", "r\"", "r'", "u\"", "u'", "r\"", "r'", "U\"", "U'", "R\"", "R'", "\"", "'",
];
pub const TRIPLE_QUOTE_SUFFIXES: &[&str] = &["\"\"\"", "'''"];
pub const SINGLE_QUOTE_SUFFIXES: &[&str] = &["\"", "'"];

View file

@ -1,4 +1,3 @@
pub mod constants;
pub mod definition; pub mod definition;
pub mod extraction; pub mod extraction;
pub mod google; pub mod google;

View file

@ -1,13 +1,15 @@
use crate::docstrings::constants; use ruff_python::string::{
SINGLE_QUOTE_PREFIXES, SINGLE_QUOTE_SUFFIXES, TRIPLE_QUOTE_PREFIXES, TRIPLE_QUOTE_SUFFIXES,
};
/// Strip the leading and trailing quotes from a docstring. /// Strip the leading and trailing quotes from a docstring.
pub fn raw_contents(contents: &str) -> &str { pub fn raw_contents(contents: &str) -> &str {
for pattern in constants::TRIPLE_QUOTE_PREFIXES { for pattern in TRIPLE_QUOTE_PREFIXES {
if contents.starts_with(pattern) { if contents.starts_with(pattern) {
return &contents[pattern.len()..contents.len() - 3]; return &contents[pattern.len()..contents.len() - 3];
} }
} }
for pattern in constants::SINGLE_QUOTE_PREFIXES { for pattern in SINGLE_QUOTE_PREFIXES {
if contents.starts_with(pattern) { if contents.starts_with(pattern) {
return &contents[pattern.len()..contents.len() - 1]; return &contents[pattern.len()..contents.len() - 1];
} }
@ -18,10 +20,7 @@ pub fn raw_contents(contents: &str) -> &str {
/// Return the leading quote string for a docstring (e.g., `"""`). /// Return the leading quote string for a docstring (e.g., `"""`).
pub fn leading_quote(content: &str) -> Option<&str> { pub fn leading_quote(content: &str) -> Option<&str> {
if let Some(first_line) = content.lines().next() { if let Some(first_line) = content.lines().next() {
for pattern in constants::TRIPLE_QUOTE_PREFIXES for pattern in TRIPLE_QUOTE_PREFIXES.iter().chain(SINGLE_QUOTE_PREFIXES) {
.iter()
.chain(constants::SINGLE_QUOTE_PREFIXES)
{
if first_line.starts_with(pattern) { if first_line.starts_with(pattern) {
return Some(pattern); return Some(pattern);
} }
@ -32,9 +31,9 @@ pub fn leading_quote(content: &str) -> Option<&str> {
/// Return the trailing quote string for a docstring (e.g., `"""`). /// Return the trailing quote string for a docstring (e.g., `"""`).
pub fn trailing_quote(content: &str) -> Option<&&str> { pub fn trailing_quote(content: &str) -> Option<&&str> {
constants::TRIPLE_QUOTE_SUFFIXES TRIPLE_QUOTE_SUFFIXES
.iter() .iter()
.chain(constants::SINGLE_QUOTE_SUFFIXES) .chain(SINGLE_QUOTE_SUFFIXES)
.find(|&pattern| content.ends_with(pattern)) .find(|&pattern| content.ends_with(pattern))
} }

View file

@ -1,9 +1,9 @@
use ruff_macros::{define_violation, derive_message_formats}; use ruff_macros::{define_violation, derive_message_formats};
use ruff_python::string::TRIPLE_QUOTE_PREFIXES;
use crate::ast::types::Range; use crate::ast::types::Range;
use crate::ast::whitespace::LinesWithTrailingNewline; use crate::ast::whitespace::LinesWithTrailingNewline;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::docstrings::constants;
use crate::docstrings::definition::{DefinitionKind, Docstring}; use crate::docstrings::definition::{DefinitionKind, Docstring};
use crate::fix::Fix; use crate::fix::Fix;
use crate::message::Location; use crate::message::Location;
@ -54,7 +54,7 @@ pub fn multi_line_summary_start(checker: &mut Checker, docstring: &Docstring) {
{ {
return; return;
}; };
if constants::TRIPLE_QUOTE_PREFIXES.contains(&first_line) { if TRIPLE_QUOTE_PREFIXES.contains(&first_line) {
if checker if checker
.settings .settings
.rules .rules

View file

@ -1,6 +1,16 @@
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
/// See: <https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals>
pub const TRIPLE_QUOTE_PREFIXES: &[&str] = &[
"u\"\"\"", "u'''", "r\"\"\"", "r'''", "U\"\"\"", "U'''", "R\"\"\"", "R'''", "\"\"\"", "'''",
];
pub const SINGLE_QUOTE_PREFIXES: &[&str] = &[
"u\"", "u'", "r\"", "r'", "u\"", "u'", "r\"", "r'", "U\"", "U'", "R\"", "R'", "\"", "'",
];
pub const TRIPLE_QUOTE_SUFFIXES: &[&str] = &["\"\"\"", "'''"];
pub const SINGLE_QUOTE_SUFFIXES: &[&str] = &["\"", "'"];
pub static STRING_QUOTE_PREFIX_REGEX: Lazy<Regex> = pub static STRING_QUOTE_PREFIX_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"^(?i)[urb]*['"](?P<raw>.*)['"]$"#).unwrap()); Lazy::new(|| Regex::new(r#"^(?i)[urb]*['"](?P<raw>.*)['"]$"#).unwrap());

View file

@ -7,6 +7,7 @@ rust-version = { workspace = true }
[dependencies] [dependencies]
ruff_formatter = { path = "../ruff_formatter" } ruff_formatter = { path = "../ruff_formatter" }
ruff_python = { path = "../ruff_python" }
ruff_rustpython = { path = "../ruff_rustpython" } ruff_rustpython = { path = "../ruff_rustpython" }
ruff_text_size = { path = "../ruff_text_size" } ruff_text_size = { path = "../ruff_text_size" }

View file

@ -0,0 +1,23 @@
use ruff_python::string::{
SINGLE_QUOTE_PREFIXES, SINGLE_QUOTE_SUFFIXES, TRIPLE_QUOTE_PREFIXES, TRIPLE_QUOTE_SUFFIXES,
};
/// Return the leading quote string for a docstring (e.g., `"""`).
pub fn leading_quote(content: &str) -> Option<&str> {
if let Some(first_line) = content.lines().next() {
for pattern in TRIPLE_QUOTE_PREFIXES.iter().chain(SINGLE_QUOTE_PREFIXES) {
if first_line.starts_with(pattern) {
return Some(pattern);
}
}
}
None
}
/// Return the trailing quote string for a docstring (e.g., `"""`).
pub fn trailing_quote(content: &str) -> Option<&&str> {
TRIPLE_QUOTE_SUFFIXES
.iter()
.chain(SINGLE_QUOTE_SUFFIXES)
.find(|&pattern| content.ends_with(pattern))
}

View file

@ -1,3 +1,4 @@
pub mod helpers;
pub mod locator; pub mod locator;
pub mod types; pub mod types;
pub mod visitor; pub mod visitor;

View file

@ -13,6 +13,7 @@ use crate::cst::{
Arguments, Boolop, Cmpop, Comprehension, Expr, ExprKind, Keyword, Operator, Unaryop, Arguments, Boolop, Cmpop, Comprehension, Expr, ExprKind, Keyword, Operator, Unaryop,
}; };
use crate::format::helpers::{is_self_closing, is_simple_power, is_simple_slice}; use crate::format::helpers::{is_self_closing, is_simple_power, is_simple_slice};
use crate::format::strings::string_literal;
use crate::shared_traits::AsFormat; use crate::shared_traits::AsFormat;
use crate::trivia::{Parenthesize, Relationship, TriviaKind}; use crate::trivia::{Parenthesize, Relationship, TriviaKind};
@ -128,8 +129,6 @@ fn format_tuple(
write!( write!(
f, f,
[soft_block_indent(&format_with(|f| { [soft_block_indent(&format_with(|f| {
// TODO(charlie): If the magic trailing comma isn't present, and the
// tuple is _already_ expanded, we're not supposed to add this.
let magic_trailing_comma = expr let magic_trailing_comma = expr
.trivia .trivia
.iter() .iter()
@ -641,10 +640,21 @@ fn format_joined_str(
fn format_constant( fn format_constant(
f: &mut Formatter<ASTFormatContext<'_>>, f: &mut Formatter<ASTFormatContext<'_>>,
expr: &Expr, expr: &Expr,
_constant: &Constant, constant: &Constant,
_kind: Option<&str>, _kind: Option<&str>,
) -> FormatResult<()> { ) -> FormatResult<()> {
write!(f, [literal(Range::from_located(expr))])?; match constant {
Constant::None => write!(f, [text("None")])?,
Constant::Bool(value) => {
if *value {
write!(f, [text("True")])?;
} else {
write!(f, [text("False")])?;
}
}
Constant::Str(_) => write!(f, [string_literal(expr)])?,
_ => write!(f, [literal(Range::from_located(expr))])?,
}
Ok(()) Ok(())
} }

View file

@ -10,5 +10,6 @@ mod expr;
mod helpers; mod helpers;
mod operator; mod operator;
mod stmt; mod stmt;
mod strings;
mod unaryop; mod unaryop;
mod withitem; mod withitem;

View file

@ -0,0 +1,240 @@
use rustpython_parser::{Mode, Tok};
use ruff_formatter::prelude::*;
use ruff_formatter::{write, Format};
use ruff_text_size::TextSize;
use crate::context::ASTFormatContext;
use crate::core::helpers::{leading_quote, trailing_quote};
use crate::core::types::Range;
use crate::cst::Expr;
use crate::trivia::Parenthesize;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct StringLiteralPart {
range: Range,
}
impl Format<ASTFormatContext<'_>> for StringLiteralPart {
fn fmt(&self, f: &mut Formatter<ASTFormatContext<'_>>) -> FormatResult<()> {
let (source, start, end) = f.context().locator().slice(self.range);
// Extract leading and trailing quotes.
let content = &source[start..end];
let leading_quote = leading_quote(content).unwrap();
let trailing_quote = trailing_quote(content).unwrap();
let body = &content[leading_quote.len()..content.len() - trailing_quote.len()];
// Determine the correct quote style.
// TODO(charlie): Make this parameterizable.
let mut squotes: usize = 0;
let mut dquotes: usize = 0;
for char in body.chars() {
if char == '\'' {
squotes += 1;
} else if char == '"' {
dquotes += 1;
}
}
let mut is_raw = false;
if leading_quote.contains('r') {
is_raw = true;
f.write_element(FormatElement::StaticText { text: "r" })?;
} else if leading_quote.contains('R') {
is_raw = true;
f.write_element(FormatElement::StaticText { text: "R" })?;
}
if trailing_quote.len() == 1 {
// Single-quoted string.
if dquotes == 0 || squotes > 0 {
// If the body doesn't contain any double quotes, or it contains both single and
// double quotes, use double quotes.
f.write_element(FormatElement::StaticText { text: "\"" })?;
f.write_element(FormatElement::DynamicText {
text: if is_raw {
body.into()
} else {
double_escape(body).into()
},
source_position: TextSize::default(),
})?;
f.write_element(FormatElement::StaticText { text: "\"" })?;
Ok(())
} else {
f.write_element(FormatElement::StaticText { text: "'" })?;
f.write_element(FormatElement::DynamicText {
text: if is_raw {
body.into()
} else {
single_escape(body).into()
},
source_position: TextSize::default(),
})?;
f.write_element(FormatElement::StaticText { text: "'" })?;
Ok(())
}
} else if trailing_quote.len() == 3 {
// Triple-quoted string.
if body.starts_with("\"\"\"") || body.ends_with('"') {
// We only need to use single quotes if the string body starts with three or more
// double quotes, or ends with a double quote. Converting to double quotes in those
// cases would cause a syntax error.
f.write_element(FormatElement::StaticText { text: "'''" })?;
f.write_element(FormatElement::DynamicText {
text: body.to_string().into_boxed_str(),
source_position: TextSize::default(),
})?;
f.write_element(FormatElement::StaticText { text: "'''" })?;
Ok(())
} else {
f.write_element(FormatElement::StaticText { text: "\"\"\"" })?;
f.write_element(FormatElement::DynamicText {
text: body.to_string().into_boxed_str(),
source_position: TextSize::default(),
})?;
f.write_element(FormatElement::StaticText { text: "\"\"\"" })?;
Ok(())
}
} else {
unreachable!("Invalid quote length: {}", trailing_quote.len());
}
}
}
#[inline]
pub const fn string_literal_part(range: Range) -> StringLiteralPart {
StringLiteralPart { range }
}
#[derive(Debug, Copy, Clone)]
pub struct StringLiteral<'a> {
expr: &'a Expr,
}
impl Format<ASTFormatContext<'_>> for StringLiteral<'_> {
fn fmt(&self, f: &mut Formatter<ASTFormatContext<'_>>) -> FormatResult<()> {
let expr = self.expr;
// TODO(charlie): This tokenization needs to happen earlier, so that we can attach
// comments to individual string literals.
let (source, start, end) = f.context().locator().slice(Range::from_located(expr));
let elts =
rustpython_parser::lexer::lex_located(&source[start..end], Mode::Module, expr.location)
.flatten()
.filter_map(|(start, tok, end)| {
if matches!(tok, Tok::String { .. }) {
Some(Range::new(start, end))
} else {
None
}
})
.collect::<Vec<_>>();
write!(
f,
[group(&format_with(|f| {
if matches!(expr.parentheses, Parenthesize::IfExpanded) {
write!(f, [if_group_breaks(&text("("))])?;
}
for (i, elt) in elts.iter().enumerate() {
write!(f, [string_literal_part(*elt)])?;
if i < elts.len() - 1 {
write!(f, [soft_line_break_or_space()])?;
}
}
if matches!(expr.parentheses, Parenthesize::IfExpanded) {
write!(f, [if_group_breaks(&text(")"))])?;
}
Ok(())
}))]
)?;
Ok(())
}
}
#[inline]
pub const fn string_literal(expr: &Expr) -> StringLiteral {
StringLiteral { expr }
}
/// Escape a string body to be used in a string literal with double quotes.
fn double_escape(text: &str) -> String {
let mut escaped = String::with_capacity(text.len());
let mut chars = text.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\\' {
let Some(next) = chars.peek() else {
break;
};
if *next == '\'' {
chars.next();
escaped.push('\'');
} else if *next == '"' {
chars.next();
escaped.push('"');
} else if *next == '\\' {
chars.next();
escaped.push('\\');
escaped.push(ch);
} else {
escaped.push(ch);
}
} else if ch == '"' {
escaped.push('\\');
escaped.push('"');
} else {
escaped.push(ch);
}
}
escaped
}
/// Escape a string body to be used in a string literal with single quotes.
fn single_escape(text: &str) -> String {
let mut escaped = String::with_capacity(text.len());
let mut chars = text.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\\' {
let Some(next) = chars.peek() else {
break;
};
if *next == '"' {
chars.next();
escaped.push('"');
} else if *next == '\'' {
chars.next();
escaped.push('\'');
} else if *next == '\\' {
chars.next();
escaped.push('\\');
escaped.push(ch);
} else {
escaped.push(ch);
}
} else if ch == '\'' {
escaped.push('\\');
escaped.push('\'');
} else {
escaped.push(ch);
}
}
escaped
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_double_escape() {
assert_eq!(double_escape(r#"It\'s mine"#), r#"It's mine"#);
assert_eq!(double_escape(r#"It\'s "mine""#), r#"It's \"mine\""#);
assert_eq!(double_escape(r#"It\\'s mine"#), r#"It\\'s mine"#);
}
#[test]
fn test_single_escape() {
assert_eq!(single_escape(r#"It's \"mine\""#), r#"It\'s "mine""#);
}
}

View file

@ -1,49 +0,0 @@
---
source: crates/ruff_python_formatter/src/lib.rs
expression: snapshot
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/bracketmatch.py
---
## Input
```py
for ((x in {}) or {})['a'] in x:
pass
pem_spam = lambda l, spam = {
"x": 3
}: not spam.get(l.strip())
lambda x=lambda y={1: 3}: y['x':lambda y: {1: 2}]: x
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -1,4 +1,4 @@
-for ((x in {}) or {})["a"] in x:
+for ((x in {}) or {})['a'] in x:
pass
pem_spam = lambda l, spam={"x": 3}: not spam.get(l.strip())
-lambda x=lambda y={1: 3}: y["x" : lambda y: {1: 2}]: x
+lambda x=lambda y={1: 3}: y['x' : lambda y: {1: 2}]: x
```
## Ruff Output
```py
for ((x in {}) or {})['a'] in x:
pass
pem_spam = lambda l, spam={"x": 3}: not spam.get(l.strip())
lambda x=lambda y={1: 3}: y['x' : lambda y: {1: 2}]: x
```
## Black Output
```py
for ((x in {}) or {})["a"] in x:
pass
pem_spam = lambda l, spam={"x": 3}: not spam.get(l.strip())
lambda x=lambda y={1: 3}: y["x" : lambda y: {1: 2}]: x
```

View file

@ -1,319 +0,0 @@
---
source: crates/ruff_python_formatter/src/lib.rs
expression: snapshot
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/collections.py
---
## Input
```py
import core, time, a
from . import A, B, C
# keeps existing trailing comma
from foo import (
bar,
)
# also keeps existing structure
from foo import (
baz,
qux,
)
# `as` works as well
from foo import (
xyzzy as magic,
)
a = {1,2,3,}
b = {
1,2,
3}
c = {
1,
2,
3,
}
x = 1,
y = narf(),
nested = {(1,2,3),(4,5,6),}
nested_no_trailing_comma = {(1,2,3),(4,5,6)}
nested_long_lines = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccccccccccccccc", (1, 2, 3), "dddddddddddddddddddddddddddddddddddddddd"]
{"oneple": (1,),}
{"oneple": (1,)}
['ls', 'lsoneple/%s' % (foo,)]
x = {"oneple": (1,)}
y = {"oneple": (1,),}
assert False, ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa wraps %s" % bar)
# looping over a 1-tuple should also not get wrapped
for x in (1,):
pass
for (x,) in (1,), (2,), (3,):
pass
[1, 2, 3,]
division_result_tuple = (6/2,)
print("foo %r", (foo.bar,))
if True:
IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = (
Config.IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING
| {pylons.controllers.WSGIController}
)
if True:
ec2client.get_waiter('instance_stopped').wait(
InstanceIds=[instance.id],
WaiterConfig={
'Delay': 5,
})
ec2client.get_waiter("instance_stopped").wait(
InstanceIds=[instance.id],
WaiterConfig={"Delay": 5,},
)
ec2client.get_waiter("instance_stopped").wait(
InstanceIds=[instance.id], WaiterConfig={"Delay": 5,},
)
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -47,7 +47,7 @@
"oneple": (1,),
}
{"oneple": (1,)}
-["ls", "lsoneple/%s" % (foo,)]
+['ls', 'lsoneple/%s' % (foo,)]
x = {"oneple": (1,)}
y = {
"oneple": (1,),
@@ -79,10 +79,10 @@
)
if True:
- ec2client.get_waiter("instance_stopped").wait(
+ ec2client.get_waiter('instance_stopped').wait(
InstanceIds=[instance.id],
WaiterConfig={
- "Delay": 5,
+ 'Delay': 5,
},
)
ec2client.get_waiter("instance_stopped").wait(
```
## Ruff Output
```py
import core, time, a
from . import A, B, C
# keeps existing trailing comma
from foo import (
bar,
)
# also keeps existing structure
from foo import (
baz,
qux,
)
# `as` works as well
from foo import (
xyzzy as magic,
)
a = {
1,
2,
3,
}
b = {1, 2, 3}
c = {
1,
2,
3,
}
x = (1,)
y = (narf(),)
nested = {
(1, 2, 3),
(4, 5, 6),
}
nested_no_trailing_comma = {(1, 2, 3), (4, 5, 6)}
nested_long_lines = [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"cccccccccccccccccccccccccccccccccccccccc",
(1, 2, 3),
"dddddddddddddddddddddddddddddddddddddddd",
]
{
"oneple": (1,),
}
{"oneple": (1,)}
['ls', 'lsoneple/%s' % (foo,)]
x = {"oneple": (1,)}
y = {
"oneple": (1,),
}
assert False, (
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa wraps %s"
% bar
)
# looping over a 1-tuple should also not get wrapped
for x in (1,):
pass
for (x,) in (1,), (2,), (3,):
pass
[
1,
2,
3,
]
division_result_tuple = (6 / 2,)
print("foo %r", (foo.bar,))
if True:
IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = (
Config.IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING
| {pylons.controllers.WSGIController}
)
if True:
ec2client.get_waiter('instance_stopped').wait(
InstanceIds=[instance.id],
WaiterConfig={
'Delay': 5,
},
)
ec2client.get_waiter("instance_stopped").wait(
InstanceIds=[instance.id],
WaiterConfig={
"Delay": 5,
},
)
ec2client.get_waiter("instance_stopped").wait(
InstanceIds=[instance.id],
WaiterConfig={
"Delay": 5,
},
)
```
## Black Output
```py
import core, time, a
from . import A, B, C
# keeps existing trailing comma
from foo import (
bar,
)
# also keeps existing structure
from foo import (
baz,
qux,
)
# `as` works as well
from foo import (
xyzzy as magic,
)
a = {
1,
2,
3,
}
b = {1, 2, 3}
c = {
1,
2,
3,
}
x = (1,)
y = (narf(),)
nested = {
(1, 2, 3),
(4, 5, 6),
}
nested_no_trailing_comma = {(1, 2, 3), (4, 5, 6)}
nested_long_lines = [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"cccccccccccccccccccccccccccccccccccccccc",
(1, 2, 3),
"dddddddddddddddddddddddddddddddddddddddd",
]
{
"oneple": (1,),
}
{"oneple": (1,)}
["ls", "lsoneple/%s" % (foo,)]
x = {"oneple": (1,)}
y = {
"oneple": (1,),
}
assert False, (
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa wraps %s"
% bar
)
# looping over a 1-tuple should also not get wrapped
for x in (1,):
pass
for (x,) in (1,), (2,), (3,):
pass
[
1,
2,
3,
]
division_result_tuple = (6 / 2,)
print("foo %r", (foo.bar,))
if True:
IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = (
Config.IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING
| {pylons.controllers.WSGIController}
)
if True:
ec2client.get_waiter("instance_stopped").wait(
InstanceIds=[instance.id],
WaiterConfig={
"Delay": 5,
},
)
ec2client.get_waiter("instance_stopped").wait(
InstanceIds=[instance.id],
WaiterConfig={
"Delay": 5,
},
)
ec2client.get_waiter("instance_stopped").wait(
InstanceIds=[instance.id],
WaiterConfig={
"Delay": 5,
},
)
```

View file

@ -178,7 +178,7 @@ instruction()#comment with bad spacing
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -1,31 +1,31 @@ @@ -1,8 +1,8 @@
from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import (
- MyLovelyCompanyTeamProjectComponent, # NOT DRY - MyLovelyCompanyTeamProjectComponent, # NOT DRY
+ MyLovelyCompanyTeamProjectComponent, + MyLovelyCompanyTeamProjectComponent,
@ -189,52 +189,25 @@ instruction()#comment with bad spacing
) )
# Please keep __all__ alphabetized within each category. # Please keep __all__ alphabetized within each category.
@@ -13,7 +13,7 @@
__all__ = [ "Callable",
# Super-special typing primitives. "ClassVar",
- "Any",
- "Callable",
- "ClassVar",
+ 'Any',
+ 'Callable',
+ 'ClassVar',
# ABCs (from collections.abc). # ABCs (from collections.abc).
- "AbstractSet", # collections.abc.Set. - "AbstractSet", # collections.abc.Set.
- "ByteString", + "AbstractSet",
- "Container", "ByteString",
+ 'AbstractSet', "Container",
+ 'ByteString',
+ 'Container',
# Concrete collection types. # Concrete collection types.
- "Counter", @@ -24,7 +24,7 @@
- "Deque", "List",
- "Dict", "Set",
- "DefaultDict", "FrozenSet",
- "List",
- "Set",
- "FrozenSet",
- "NamedTuple", # Not really a type. - "NamedTuple", # Not really a type.
- "Generator", + "NamedTuple",
+ 'Counter', "Generator",
+ 'Deque',
+ 'Dict',
+ 'DefaultDict',
+ 'List',
+ 'Set',
+ 'FrozenSet',
+ 'NamedTuple',
+ 'Generator',
] ]
not_shareables = [ @@ -54,32 +54,39 @@
@@ -48,38 +48,45 @@
SubBytes(b"spam"),
]
-if "PYTHON" in os.environ:
+if 'PYTHON' in os.environ:
add_compiler(compiler_from_env())
else:
# for compiler in compilers.values(): # for compiler in compilers.values():
# add_compiler(compiler) # add_compiler(compiler)
add_compiler(compilers[(7.0, 32)]) add_compiler(compilers[(7.0, 32)])
@ -284,7 +257,7 @@ instruction()#comment with bad spacing
): ):
pass pass
# no newline before or after # no newline before or after
@@ -103,47 +110,47 @@ @@ -103,42 +110,42 @@
############################################################################ ############################################################################
call2( call2(
@ -323,12 +296,11 @@ instruction()#comment with bad spacing
] ]
lcomp3 = [ lcomp3 = [
# This one is actually too long to fit in a single line. # This one is actually too long to fit in a single line.
- element.split("\n", 1)[0] element.split("\n", 1)[0]
- # yup - # yup
- for element in collection.select_elements() - for element in collection.select_elements()
- # right - # right
- if element is not None - if element is not None
+ element.split('\n', 1)[0]
+ for # yup + for # yup
+ element in collection.select_elements() + element in collection.select_elements()
+ if # right + if # right
@ -346,12 +318,6 @@ instruction()#comment with bad spacing
# let's return # let's return
return Node( return Node(
syms.simple_stmt,
- [Node(statement, result), Leaf(token.NEWLINE, "\n")], # FIXME: \r\n?
+ [Node(statement, result), Leaf(token.NEWLINE, '\n')], # FIXME: \r\n?
)
@@ -167,7 +174,7 @@ @@ -167,7 +174,7 @@
####################### #######################
@ -377,23 +343,23 @@ from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component
__all__ = [ __all__ = [
# Super-special typing primitives. # Super-special typing primitives.
'Any', "Any",
'Callable', "Callable",
'ClassVar', "ClassVar",
# ABCs (from collections.abc). # ABCs (from collections.abc).
'AbstractSet', "AbstractSet",
'ByteString', "ByteString",
'Container', "Container",
# Concrete collection types. # Concrete collection types.
'Counter', "Counter",
'Deque', "Deque",
'Dict', "Dict",
'DefaultDict', "DefaultDict",
'List', "List",
'Set', "Set",
'FrozenSet', "FrozenSet",
'NamedTuple', "NamedTuple",
'Generator', "Generator",
] ]
not_shareables = [ not_shareables = [
@ -416,7 +382,7 @@ not_shareables = [
SubBytes(b"spam"), SubBytes(b"spam"),
] ]
if 'PYTHON' in os.environ: if "PYTHON" in os.environ:
add_compiler(compiler_from_env()) add_compiler(compiler_from_env())
else: else:
# for compiler in compilers.values(): # for compiler in compilers.values():
@ -501,7 +467,7 @@ short
] ]
lcomp3 = [ lcomp3 = [
# This one is actually too long to fit in a single line. # This one is actually too long to fit in a single line.
element.split('\n', 1)[0] element.split("\n", 1)[0]
for # yup for # yup
element in collection.select_elements() element in collection.select_elements()
if # right if # right
@ -518,7 +484,7 @@ short
# let's return # let's return
return Node( return Node(
syms.simple_stmt, syms.simple_stmt,
[Node(statement, result), Leaf(token.NEWLINE, '\n')], # FIXME: \r\n? [Node(statement, result), Leaf(token.NEWLINE, "\n")], # FIXME: \r\n?
) )

View file

@ -105,20 +105,7 @@ def g():
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -3,9 +3,9 @@ @@ -25,23 +25,30 @@
# leading comment
def f():
- NO = ""
- SPACE = " "
- DOUBLESPACE = " "
+ NO = ''
+ SPACE = ' '
+ DOUBLESPACE = ' '
t = leaf.type
p = leaf.parent # trailing comment
@@ -25,35 +25,41 @@
return NO return NO
if prevp.type == token.EQUAL: if prevp.type == token.EQUAL:
@ -164,21 +151,14 @@ def g():
return NO return NO
############################################################################### @@ -49,7 +56,6 @@
# SECTION BECAUSE SECTIONS # SECTION BECAUSE SECTIONS
############################################################################### ###############################################################################
- -
def g(): def g():
- NO = "" NO = ""
- SPACE = " " SPACE = " "
- DOUBLESPACE = " "
+ NO = ''
+ SPACE = ' '
+ DOUBLESPACE = ' '
t = leaf.type
p = leaf.parent
@@ -67,7 +73,7 @@ @@ -67,7 +73,7 @@
return DOUBLESPACE return DOUBLESPACE
@ -221,9 +201,9 @@ def g():
# leading comment # leading comment
def f(): def f():
NO = '' NO = ""
SPACE = ' ' SPACE = " "
DOUBLESPACE = ' ' DOUBLESPACE = " "
t = leaf.type t = leaf.type
p = leaf.parent # trailing comment p = leaf.parent # trailing comment
@ -275,9 +255,9 @@ def f():
############################################################################### ###############################################################################
def g(): def g():
NO = '' NO = ""
SPACE = ' ' SPACE = " "
DOUBLESPACE = ' ' DOUBLESPACE = " "
t = leaf.type t = leaf.type
p = leaf.parent p = leaf.parent

View file

@ -268,22 +268,16 @@ last_call()
--- Black --- Black
+++ Ruff +++ Ruff
@@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
-"some_string"
-b"\\xa3"
+... +...
+'some_string' "some_string"
-b"\\xa3"
+b'\\xa3' +b'\\xa3'
Name Name
None None
True True
@@ -35,10 +36,11 @@ @@ -38,7 +39,8 @@
lambda arg: None lambda a, b, c=True, *, d=(1 << v2), e="str": a
lambda a=True: a lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b
lambda a, b, c=True: a
-lambda a, b, c=True, *, d=(1 << v2), e="str": a
-lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b
+lambda a, b, c=True, *, d=(1 << v2), e='str': a
+lambda a, b, c=True, *vararg, d=(v1 << 2), e='str', **kwargs: a + b
manylambdas = lambda x=lambda y=lambda z=1: z: y(): x() manylambdas = lambda x=lambda y=lambda z=1: z: y(): x()
-foo = lambda port_id, ignore_missing: { -foo = lambda port_id, ignore_missing: {
+foo = lambda port_id, +foo = lambda port_id,
@ -291,38 +285,16 @@ last_call()
"port1": port1_resource, "port1": port1_resource,
"port2": port2_resource, "port2": port2_resource,
}[port_id] }[port_id]
@@ -52,11 +54,11 @@ @@ -56,7 +58,7 @@
if (1 if super_long_test_name else 2) {"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}}
else (str or bytes or None)
)
-{"2.7": dead, "3.7": (long_live or die_hard)}
-{"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}}
+{'2.7': dead, '3.7': (long_live or die_hard)}
+{'2.7': dead, '3.7': (long_live or die_hard), **{'3.6': verygood}}
{**a, **b, **c} {**a, **b, **c}
-{"2.7", "3.6", "3.7", "3.8", "3.9", ("4.0" if gilectomy else "3.10")} {"2.7", "3.6", "3.7", "3.8", "3.9", ("4.0" if gilectomy else "3.10")}
-({"a": "b"}, (True or False), (+value), "string", b"bytes") or None -({"a": "b"}, (True or False), (+value), "string", b"bytes") or None
+{'2.7', '3.6', '3.7', '3.8', '3.9', ('4.0' if gilectomy else '3.10')} +({"a": "b"}, (True or False), (+value), "string", b'bytes') or None
+({'a': 'b'}, (True or False), (+value), 'string', b'bytes') or None
() ()
(1,) (1,)
(1, 2) (1, 2)
@@ -88,32 +90,33 @@ @@ -100,7 +102,8 @@
]
{i for i in (1, 2, 3)}
{(i**2) for i in (1, 2, 3)}
-{(i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))}
+{(i**2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))}
{((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)}
[i for i in (1, 2, 3)]
[(i**2) for i in (1, 2, 3)]
-[(i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))]
+[(i**2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))]
[((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)]
{i: 0 for i in (1, 2, 3)}
-{i: j for i, j in ((1, "a"), (2, "b"), (3, "c"))}
+{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))}
{a: b * 2 for a, b in dictionary.items()}
{a: b * -2 for a, b in dictionary.items()} {a: b * -2 for a, b in dictionary.items()}
{ {
k: v k: v
@ -332,23 +304,6 @@ last_call()
} }
Python3 > Python2 > COBOL Python3 > Python2 > COBOL
Life is Life Life is Life
call()
call(arg)
-call(kwarg="hey")
-call(arg, kwarg="hey")
-call(arg, another, kwarg="hey", **kwargs)
+call(kwarg='hey')
+call(arg, kwarg='hey')
+call(arg, another, kwarg='hey', **kwargs)
call(
this_is_a_very_long_variable_which_will_force_a_delimiter_split,
arg,
another,
- kwarg="hey",
+ kwarg='hey',
**kwargs,
) # note: no trailing comma pre-3.6
call(*gidgets[:2])
@@ -122,8 +125,8 @@ @@ -122,8 +125,8 @@
call(b, **self.screen_kwargs) call(b, **self.screen_kwargs)
lukasz.langa.pl lukasz.langa.pl
@ -367,19 +322,19 @@ last_call()
-xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore -xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore
- sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) - sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
+xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = ( +xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = (
+ classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore
+)
+xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = (
+ classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore + classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore
) )
-xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore -xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore
- sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) - sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
+xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = ( +xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = (
+ classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore + classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__))
) )
-xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( -xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(
- sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) - sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
-) # type: ignore -) # type: ignore
+xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = (
+ classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__))
+)
slice[0] slice[0]
slice[0:1] slice[0:1]
slice[0:1:2] slice[0:1:2]
@ -405,46 +360,25 @@ last_call()
numpy[:, (0, 1, 2, 5)] numpy[:, (0, 1, 2, 5)]
numpy[0, [0]] numpy[0, [0]]
numpy[:, [i]] numpy[:, [i]]
@@ -172,17 +175,17 @@ @@ -172,7 +175,7 @@
numpy[-(c + 1) :, d] numpy[-(c + 1) :, d]
numpy[:, l[-2]] numpy[:, l[-2]]
numpy[:, ::-1] numpy[:, ::-1]
-numpy[np.newaxis, :] -numpy[np.newaxis, :]
+numpy[np.newaxis, ::] +numpy[np.newaxis, ::]
(str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None) (str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None)
-{"2.7": dead, "3.7": long_live or die_hard} {"2.7": dead, "3.7": long_live or die_hard}
-{"2.7", "3.6", "3.7", "3.8", "3.9", "4.0" if gilectomy else "3.10"} {"2.7", "3.6", "3.7", "3.8", "3.9", "4.0" if gilectomy else "3.10"}
+{'2.7': dead, '3.7': long_live or die_hard}
+{'2.7', '3.6', '3.7', '3.8', '3.9', '4.0' if gilectomy else '3.10'}
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C]
(SomeName)
SomeName
(Good, Bad, Ugly)
(i for i in (1, 2, 3))
((i**2) for i in (1, 2, 3))
-((i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c")))
+((i**2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c')))
(((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3))
(*starred,)
{
@@ -201,30 +204,26 @@ @@ -201,30 +204,26 @@
e = (1,).count(1) e = (1,).count(1)
f = 1, *range(10) f = 1, *range(10)
g = 1, *"ten" g = 1, *"ten"
-what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set( -what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set(
- vars_to_remove - vars_to_remove
+what_is_up_with_those_new_coord_names = ( -)
+ (coord_names
+ + set(vars_to_create))
+ + set(vars_to_remove)
)
-what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set( -what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set(
- vars_to_remove - vars_to_remove
+what_is_up_with_those_new_coord_names = ( -)
+ (coord_names
+ | set(vars_to_create))
+ - set(vars_to_remove)
)
-result = ( -result = (
- session.query(models.Customer.id) - session.query(models.Customer.id)
- .filter( - .filter(
@ -452,7 +386,11 @@ last_call()
- ) - )
- .order_by(models.Customer.id.asc()) - .order_by(models.Customer.id.asc())
- .all() - .all()
-) +what_is_up_with_those_new_coord_names = (
+ (coord_names
+ + set(vars_to_create))
+ + set(vars_to_remove)
)
-result = ( -result = (
- session.query(models.Customer.id) - session.query(models.Customer.id)
- .filter( - .filter(
@ -462,7 +400,11 @@ last_call()
- models.Customer.id.asc(), - models.Customer.id.asc(),
- ) - )
- .all() - .all()
-) +what_is_up_with_those_new_coord_names = (
+ (coord_names
+ | set(vars_to_create))
+ - set(vars_to_remove)
)
+result = session.query(models.Customer.id).filter( +result = session.query(models.Customer.id).filter(
+ models.Customer.account_id == account_id, + models.Customer.account_id == account_id,
+ models.Customer.email == email_address, + models.Customer.email == email_address,
@ -489,15 +431,6 @@ last_call()
assert parens is TooMany assert parens is TooMany
for (x,) in (1,), (2,), (3,): for (x,) in (1,), (2,), (3,):
... ...
@@ -272,7 +271,7 @@
addr_proto,
addr_canonname,
addr_sockaddr,
-) in socket.getaddrinfo("google.com", "http"):
+) in socket.getaddrinfo('google.com', 'http'):
pass
a = (
aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp
@@ -327,13 +326,18 @@ @@ -327,13 +326,18 @@
): ):
return True return True
@ -536,7 +469,7 @@ last_call()
```py ```py
... ...
'some_string' "some_string"
b'\\xa3' b'\\xa3'
Name Name
None None
@ -573,8 +506,8 @@ flags & ~select.EPOLLIN and waiters.write_task is not None
lambda arg: None lambda arg: None
lambda a=True: a lambda a=True: a
lambda a, b, c=True: a lambda a, b, c=True: a
lambda a, b, c=True, *, d=(1 << v2), e='str': a lambda a, b, c=True, *, d=(1 << v2), e="str": a
lambda a, b, c=True, *vararg, d=(v1 << 2), e='str', **kwargs: a + b lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b
manylambdas = lambda x=lambda y=lambda z=1: z: y(): x() manylambdas = lambda x=lambda y=lambda z=1: z: y(): x()
foo = lambda port_id, foo = lambda port_id,
ignore_missing,: { ignore_missing,: {
@ -591,11 +524,11 @@ str or None if (1 if True else 2) else str or bytes or None
if (1 if super_long_test_name else 2) if (1 if super_long_test_name else 2)
else (str or bytes or None) else (str or bytes or None)
) )
{'2.7': dead, '3.7': (long_live or die_hard)} {"2.7": dead, "3.7": (long_live or die_hard)}
{'2.7': dead, '3.7': (long_live or die_hard), **{'3.6': verygood}} {"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}}
{**a, **b, **c} {**a, **b, **c}
{'2.7', '3.6', '3.7', '3.8', '3.9', ('4.0' if gilectomy else '3.10')} {"2.7", "3.6", "3.7", "3.8", "3.9", ("4.0" if gilectomy else "3.10")}
({'a': 'b'}, (True or False), (+value), 'string', b'bytes') or None ({"a": "b"}, (True or False), (+value), "string", b'bytes') or None
() ()
(1,) (1,)
(1, 2) (1, 2)
@ -627,14 +560,14 @@ str or None if (1 if True else 2) else str or bytes or None
] ]
{i for i in (1, 2, 3)} {i for i in (1, 2, 3)}
{(i**2) for i in (1, 2, 3)} {(i**2) for i in (1, 2, 3)}
{(i**2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))} {(i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))}
{((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)} {((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)}
[i for i in (1, 2, 3)] [i for i in (1, 2, 3)]
[(i**2) for i in (1, 2, 3)] [(i**2) for i in (1, 2, 3)]
[(i**2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))] [(i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))]
[((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)] [((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)]
{i: 0 for i in (1, 2, 3)} {i: 0 for i in (1, 2, 3)}
{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))} {i: j for i, j in ((1, "a"), (2, "b"), (3, "c"))}
{a: b * 2 for a, b in dictionary.items()} {a: b * 2 for a, b in dictionary.items()}
{a: b * -2 for a, b in dictionary.items()} {a: b * -2 for a, b in dictionary.items()}
{ {
@ -646,14 +579,14 @@ Python3 > Python2 > COBOL
Life is Life Life is Life
call() call()
call(arg) call(arg)
call(kwarg='hey') call(kwarg="hey")
call(arg, kwarg='hey') call(arg, kwarg="hey")
call(arg, another, kwarg='hey', **kwargs) call(arg, another, kwarg="hey", **kwargs)
call( call(
this_is_a_very_long_variable_which_will_force_a_delimiter_split, this_is_a_very_long_variable_which_will_force_a_delimiter_split,
arg, arg,
another, another,
kwarg='hey', kwarg="hey",
**kwargs, **kwargs,
) # note: no trailing comma pre-3.6 ) # note: no trailing comma pre-3.6
call(*gidgets[:2]) call(*gidgets[:2])
@ -714,15 +647,15 @@ numpy[:, l[-2]]
numpy[:, ::-1] numpy[:, ::-1]
numpy[np.newaxis, ::] numpy[np.newaxis, ::]
(str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None) (str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None)
{'2.7': dead, '3.7': long_live or die_hard} {"2.7": dead, "3.7": long_live or die_hard}
{'2.7', '3.6', '3.7', '3.8', '3.9', '4.0' if gilectomy else '3.10'} {"2.7", "3.6", "3.7", "3.8", "3.9", "4.0" if gilectomy else "3.10"}
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C]
(SomeName) (SomeName)
SomeName SomeName
(Good, Bad, Ugly) (Good, Bad, Ugly)
(i for i in (1, 2, 3)) (i for i in (1, 2, 3))
((i**2) for i in (1, 2, 3)) ((i**2) for i in (1, 2, 3))
((i**2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))) ((i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c")))
(((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)) (((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3))
(*starred,) (*starred,)
{ {
@ -808,7 +741,7 @@ for (
addr_proto, addr_proto,
addr_canonname, addr_canonname,
addr_sockaddr, addr_sockaddr,
) in socket.getaddrinfo('google.com', 'http'): ) in socket.getaddrinfo("google.com", "http"):
pass pass
a = ( a = (
aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp

View file

@ -67,11 +67,11 @@ def test_calculate_fades():
- -
- # Test don't manage the volume - # Test don't manage the volume
+@pytest.mark.parametrize( +@pytest.mark.parametrize(
+ 'test', + "test",
[ [
- ('stuff', 'in') - ('stuff', 'in')
+ # Test don't manage the volume + # Test don't manage the volume
+ [('stuff', 'in')], + [("stuff", "in")],
], ],
-]) -])
+) +)
@ -123,10 +123,10 @@ TmEx = 2
# Position, Volume, State, TmSt/TmEx/None, [call, [arg1...]] # Position, Volume, State, TmSt/TmEx/None, [call, [arg1...]]
@pytest.mark.parametrize( @pytest.mark.parametrize(
'test', "test",
[ [
# Test don't manage the volume # Test don't manage the volume
[('stuff', 'in')], [("stuff", "in")],
], ],
) )
def test_fader(test): def test_fader(test):

View file

@ -97,17 +97,20 @@ elif unformatted:
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -5,8 +5,7 @@ @@ -3,10 +3,8 @@
entry_points={
# fmt: off
"console_scripts": [ "console_scripts": [
"foo-bar" - "foo-bar"
"=foo.bar.:main", - "=foo.bar.:main",
- # fmt: on - # fmt: on
- ] # Includes an formatted indentation. - ] # Includes an formatted indentation.
+ "foo-bar" "=foo.bar.:main",
+ ], + ],
}, },
) )
@@ -18,8 +17,8 @@ @@ -18,8 +16,8 @@
"ls", "ls",
"-la", "-la",
] ]
@ -118,7 +121,7 @@ elif unformatted:
check=True, check=True,
) )
@@ -27,9 +26,8 @@ @@ -27,9 +25,8 @@
# Regression test for https://github.com/psf/black/issues/3026. # Regression test for https://github.com/psf/black/issues/3026.
def test_func(): def test_func():
# yapf: disable # yapf: disable
@ -129,7 +132,7 @@ elif unformatted:
elif b: elif b:
return True return True
@@ -39,10 +37,10 @@ @@ -39,10 +36,10 @@
# Regression test for https://github.com/psf/black/issues/2567. # Regression test for https://github.com/psf/black/issues/2567.
if True: if True:
# fmt: off # fmt: off
@ -144,7 +147,7 @@ elif unformatted:
else: else:
print("This will be formatted") print("This will be formatted")
@@ -52,14 +50,11 @@ @@ -52,14 +49,11 @@
async def call(param): async def call(param):
if param: if param:
# fmt: off # fmt: off
@ -162,7 +165,7 @@ elif unformatted:
print("This will be formatted") print("This will be formatted")
@@ -68,20 +63,21 @@ @@ -68,20 +62,21 @@
class Named(t.Protocol): class Named(t.Protocol):
# fmt: off # fmt: off
@property @property
@ -198,8 +201,7 @@ setup(
entry_points={ entry_points={
# fmt: off # fmt: off
"console_scripts": [ "console_scripts": [
"foo-bar" "foo-bar" "=foo.bar.:main",
"=foo.bar.:main",
], ],
}, },
) )

View file

@ -117,32 +117,7 @@ def __await__(): return (yield)
def func_no_args(): def func_no_args():
@@ -27,7 +27,7 @@ @@ -64,19 +64,14 @@
async def coroutine(arg, exec=False):
"Single-line docstring. Multiline is harder to reformat."
async with some_connection() as conn:
- await conn.do_what_i_mean("SELECT bobby, tables FROM xkcd", timeout=2)
+ await conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2)
await asyncio.sleep(1)
@@ -44,7 +44,7 @@
return text[number:-1]
-def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""):
+def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r''):
offset = attr.ib(default=attr.Factory(lambda: _r.uniform(10000, 200000)))
assert task._cancel_stack[: len(old_stack)] == old_stack
@@ -58,25 +58,20 @@
f: int = -1,
g: int = 1 if False else 2,
h: str = "",
- i: str = r"",
+ i: str = r'',
):
...
def spaces2(result=_core.Value(None)): def spaces2(result=_core.Value(None)):
@ -218,7 +193,7 @@ def func_no_args():
async def coroutine(arg, exec=False): async def coroutine(arg, exec=False):
"Single-line docstring. Multiline is harder to reformat." "Single-line docstring. Multiline is harder to reformat."
async with some_connection() as conn: async with some_connection() as conn:
await conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2) await conn.do_what_i_mean("SELECT bobby, tables FROM xkcd", timeout=2)
await asyncio.sleep(1) await asyncio.sleep(1)
@ -235,7 +210,7 @@ def function_signature_stress_test(
return text[number:-1] return text[number:-1]
def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r''): def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""):
offset = attr.ib(default=attr.Factory(lambda: _r.uniform(10000, 200000))) offset = attr.ib(default=attr.Factory(lambda: _r.uniform(10000, 200000)))
assert task._cancel_stack[: len(old_stack)] == old_stack assert task._cancel_stack[: len(old_stack)] == old_stack
@ -249,7 +224,7 @@ def spaces_types(
f: int = -1, f: int = -1,
g: int = 1 if False else 2, g: int = 1 if False else 2,
h: str = "", h: str = "",
i: str = r'', i: str = r"",
): ):
... ...

View file

@ -74,35 +74,6 @@ some_module.some_function(
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -2,7 +2,7 @@
a,
):
d = {
- "key": "value",
+ 'key': 'value',
}
tup = (1,)
@@ -12,8 +12,8 @@
b,
):
d = {
- "key": "value",
- "key2": "value2",
+ 'key': 'value',
+ 'key2': 'value2',
}
tup = (
1,
@@ -26,7 +26,7 @@
):
call(
arg={
- "explode": "this",
+ 'explode': 'this',
}
)
call2(
@@ -52,53 +52,52 @@ @@ -52,53 +52,52 @@
pass pass
@ -184,7 +155,7 @@ def f(
a, a,
): ):
d = { d = {
'key': 'value', "key": "value",
} }
tup = (1,) tup = (1,)
@ -194,8 +165,8 @@ def f2(
b, b,
): ):
d = { d = {
'key': 'value', "key": "value",
'key2': 'value2', "key2": "value2",
} }
tup = ( tup = (
1, 1,
@ -208,7 +179,7 @@ def f(
): ):
call( call(
arg={ arg={
'explode': 'this', "explode": "this",
} }
) )
call2( call2(

View file

@ -39,10 +39,9 @@ def docstring_multiline():
name = "Łukasz" name = "Łukasz"
-(f"hello {name}", f"hello {name}") -(f"hello {name}", f"hello {name}")
-(b"", b"") -(b"", b"")
-("", "")
+(f"hello {name}", F"hello {name}") +(f"hello {name}", F"hello {name}")
+(b"", B"") +(b"", B"")
+(u"", U"") ("", "")
(r"", R"") (r"", R"")
-(rf"", rf"", Rf"", Rf"", rf"", rf"", Rf"", Rf"") -(rf"", rf"", Rf"", Rf"", rf"", rf"", Rf"", Rf"")
@ -62,7 +61,7 @@ def docstring_multiline():
name = "Łukasz" name = "Łukasz"
(f"hello {name}", F"hello {name}") (f"hello {name}", F"hello {name}")
(b"", B"") (b"", B"")
(u"", U"") ("", "")
(r"", R"") (r"", R"")
(rf"", fr"", Rf"", fR"", rF"", Fr"", RF"", FR"") (rf"", fr"", Rf"", fR"", rF"", Fr"", RF"", FR"")

View file

@ -38,7 +38,7 @@ class A:
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -1,15 +1,14 @@ @@ -1,7 +1,6 @@
-if e1234123412341234.winerror not in ( -if e1234123412341234.winerror not in (
- _winapi.ERROR_SEM_TIMEOUT, - _winapi.ERROR_SEM_TIMEOUT,
- _winapi.ERROR_PIPE_BUSY, - _winapi.ERROR_PIPE_BUSY,
@ -49,23 +49,13 @@ class A:
pass pass
if x: if x:
if y:
new_id = (
max(
- Vegetable.objects.order_by("-id")[0].id,
- Mineral.objects.order_by("-id")[0].id,
+ Vegetable.objects.order_by('-id')[0].id,
+ Mineral.objects.order_by('-id')[0].id,
)
+ 1
)
@@ -21,14 +20,20 @@ @@ -21,14 +20,20 @@
"Your password must contain at least %(min_length)d character.", "Your password must contain at least %(min_length)d character.",
"Your password must contain at least %(min_length)d characters.", "Your password must contain at least %(min_length)d characters.",
self.min_length, self.min_length,
- ) % {"min_length": self.min_length} - ) % {"min_length": self.min_length}
+ ) + )
+ % {'min_length': self.min_length} + % {"min_length": self.min_length}
class A: class A:
@ -100,8 +90,8 @@ if x:
if y: if y:
new_id = ( new_id = (
max( max(
Vegetable.objects.order_by('-id')[0].id, Vegetable.objects.order_by("-id")[0].id,
Mineral.objects.order_by('-id')[0].id, Mineral.objects.order_by("-id")[0].id,
) )
+ 1 + 1
) )
@ -114,7 +104,7 @@ class X:
"Your password must contain at least %(min_length)d characters.", "Your password must contain at least %(min_length)d characters.",
self.min_length, self.min_length,
) )
% {'min_length': self.min_length} % {"min_length": self.min_length}
class A: class A:

View file

@ -23,7 +23,7 @@ if (e123456.get_tk_patchlevel() >= (8, 6, 0, 'final') or
- 8, - 8,
-) <= get_tk_patchlevel() < (8, 6): -) <= get_tk_patchlevel() < (8, 6):
+if ( +if (
+ e123456.get_tk_patchlevel() >= (8, 6, 0, 'final') + e123456.get_tk_patchlevel() >= (8, 6, 0, "final")
+ or (8, 5, 8) <= get_tk_patchlevel() < (8, 6) + or (8, 5, 8) <= get_tk_patchlevel() < (8, 6)
+): +):
pass pass
@ -33,7 +33,7 @@ if (e123456.get_tk_patchlevel() >= (8, 6, 0, 'final') or
```py ```py
if ( if (
e123456.get_tk_patchlevel() >= (8, 6, 0, 'final') e123456.get_tk_patchlevel() >= (8, 6, 0, "final")
or (8, 5, 8) <= get_tk_patchlevel() < (8, 6) or (8, 5, 8) <= get_tk_patchlevel() < (8, 6)
): ):
pass pass