Preserve generator parentheses in single argument call expressions (#7226)

This commit is contained in:
Micha Reiser 2023-09-08 10:53:34 +02:00 committed by GitHub
parent e376c3ff7e
commit a352f2f092
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 67 additions and 14 deletions

View file

@ -16,6 +16,11 @@ f((1) for _ in (a))
# combination of the two above # combination of the two above
f(((1) for _ in (a))) f(((1) for _ in (a)))
bases = tuple(
(base._meta.label_lower if hasattr(base, "_meta") else base)
for base in flattened_bases
)
# black keeps these atm, but intends to remove them in the future: # black keeps these atm, but intends to remove them in the future:
# https://github.com/psf/black/issues/2943 # https://github.com/psf/black/issues/2943

View file

@ -1,9 +1,10 @@
use ruff_formatter::{format_args, write, Buffer, FormatResult, FormatRuleWithOptions}; use ruff_formatter::{format_args, write, FormatRuleWithOptions};
use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::node::AnyNodeRef;
use ruff_python_ast::ExprGeneratorExp; use ruff_python_ast::ExprGeneratorExp;
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
use ruff_text_size::{Ranged, TextRange};
use crate::comments::SourceComment; use crate::comments::SourceComment;
use crate::expression::parentheses::{parenthesized, NeedsParentheses, OptionalParentheses}; use crate::expression::parentheses::{parenthesized, NeedsParentheses, OptionalParentheses};
use crate::prelude::*; use crate::prelude::*;
@ -12,12 +13,13 @@ pub enum GeneratorExpParentheses {
#[default] #[default]
Default, Default,
/// Skip parens if the generator is the only argument to a function and doesn't contain any /// Skips the parentheses if they aren't present in the source code. Used when formatting call expressions
/// dangling comments. For example: /// because the parentheses are optional if the generator is the **only** argument:
///
/// ```python /// ```python
/// all(x for y in z)` /// all(x for y in z)`
/// ``` /// ```
StripIfOnlyFunctionArg, Preserve,
} }
impl FormatRuleWithOptions<ExprGeneratorExp, PyFormatContext<'_>> for FormatExprGeneratorExp { impl FormatRuleWithOptions<ExprGeneratorExp, PyFormatContext<'_>> for FormatExprGeneratorExp {
@ -51,8 +53,9 @@ impl FormatNodeRule<ExprGeneratorExp> for FormatExprGeneratorExp {
let comments = f.context().comments().clone(); let comments = f.context().comments().clone();
let dangling = comments.dangling(item); let dangling = comments.dangling(item);
if self.parentheses == GeneratorExpParentheses::StripIfOnlyFunctionArg if self.parentheses == GeneratorExpParentheses::Preserve
&& dangling.is_empty() && dangling.is_empty()
&& !is_generator_parenthesized(item, f.context().source())
{ {
write!( write!(
f, f,
@ -94,3 +97,36 @@ impl NeedsParentheses for ExprGeneratorExp {
OptionalParentheses::Never OptionalParentheses::Never
} }
} }
fn is_generator_parenthesized(generator: &ExprGeneratorExp, source: &str) -> bool {
// / Count the number of open parentheses between the start of the tuple and the first element.
let open_parentheses_count = SimpleTokenizer::new(
source,
TextRange::new(generator.start(), generator.elt.start()),
)
.skip_trivia()
.filter(|token| token.kind() == SimpleTokenKind::LParen)
.count();
if open_parentheses_count == 0 {
return false;
}
// Count the number of parentheses between the end of the first element and its trailing comma.
let close_parentheses_count = SimpleTokenizer::new(
source,
TextRange::new(
generator.elt.end(),
generator
.generators
.first()
.map_or(generator.end(), Ranged::start),
),
)
.skip_trivia()
.filter(|token| token.kind() == SimpleTokenKind::RParen)
.count();
// If the number of open parentheses is greater than the number of close parentheses, the generator
// is parenthesized.
open_parentheses_count > close_parentheses_count
}

View file

@ -37,7 +37,7 @@ impl FormatNodeRule<Arguments> for FormatArguments {
generator_exp, generator_exp,
&generator_exp &generator_exp
.format() .format()
.with_options(GeneratorExpParentheses::StripIfOnlyFunctionArg), .with_options(GeneratorExpParentheses::Preserve),
), ),
other => { other => {
let parentheses = let parentheses =

View file

@ -386,10 +386,10 @@ threshold_date = datetime.datetime.now() - datetime.timedelta( # comment
) )
# Parenthesized and opening-parenthesis comments # Parenthesized and opening-parenthesis comments
func(x for x in y) func((x for x in y))
func( # outer comment func( # outer comment
x for x in y (x for x in y)
) )
func( func(
@ -399,9 +399,11 @@ func(
) )
func( func(
# inner comment (
x # inner comment
for x in y x
for x in y
)
) )
func( # outer comment func( # outer comment

View file

@ -22,6 +22,11 @@ f((1) for _ in (a))
# combination of the two above # combination of the two above
f(((1) for _ in (a))) f(((1) for _ in (a)))
bases = tuple(
(base._meta.label_lower if hasattr(base, "_meta") else base)
for base in flattened_bases
)
# black keeps these atm, but intends to remove them in the future: # black keeps these atm, but intends to remove them in the future:
# https://github.com/psf/black/issues/2943 # https://github.com/psf/black/issues/2943
@ -67,13 +72,18 @@ sum((a for b in c), start=0)
# black keeps these atm, but intends to remove them in the future: # black keeps these atm, but intends to remove them in the future:
# https://github.com/psf/black/issues/2943 # https://github.com/psf/black/issues/2943
f(1 for _ in a) f((1 for _ in a))
# make sure source parenthesis detection isn't fooled by these # make sure source parenthesis detection isn't fooled by these
f((1) for _ in (a)) f((1) for _ in (a))
# combination of the two above # combination of the two above
f((1) for _ in (a)) f(((1) for _ in (a)))
bases = tuple(
(base._meta.label_lower if hasattr(base, "_meta") else base)
for base in flattened_bases
)
# black keeps these atm, but intends to remove them in the future: # black keeps these atm, but intends to remove them in the future: