mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-23 13:36:52 +00:00
Remove parentheses around multiple exception types on Python 3.14+ (#20768)
Summary -- This PR implements the black preview style from https://github.com/psf/black/pull/4720. As of Python 3.14, you're allowed to omit the parentheses around groups of exceptions, as long as there's no `as` binding: **3.13** ```pycon Python 3.13.4 (main, Jun 4 2025, 17:37:06) [Clang 20.1.4 ] on linux Type "help", "copyright", "credits" or "license" for more information. >>> try: ... ... except (Exception, BaseException): ... ... Ellipsis >>> try: ... ... except Exception, BaseException: ... ... File "<python-input-1>", line 2 except Exception, BaseException: ... ^^^^^^^^^^^^^^^^^^^^^^^^ SyntaxError: multiple exception types must be parenthesized ``` **3.14** ```pycon Python 3.14.0rc2 (main, Sep 2 2025, 14:20:56) [Clang 20.1.4 ] on linux Type "help", "copyright", "credits" or "license" for more information. >>> try: ... ... except Exception, BaseException: ... ... Ellipsis >>> try: ... ... except (Exception, BaseException): ... ... Ellipsis >>> try: ... ... except Exception, BaseException as e: ... ... File "<python-input-2>", line 2 except Exception, BaseException as e: ... ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SyntaxError: multiple exception types must be parenthesized when using 'as' ``` I think this ended up being pretty straightforward, at least once Micha showed me where to start :) Test Plan -- New tests At first I thought we were deviating from black in how we handle comments within the exception type tuple, but I think this applies to how we format all tuples, not specifically with the new preview style.
This commit is contained in:
parent
1ed9b215b9
commit
591e9bbccb
8 changed files with 497 additions and 450 deletions
|
|
@ -6,7 +6,7 @@ use anyhow::{Context, Result};
|
|||
use clap::{Parser, ValueEnum, command};
|
||||
|
||||
use ruff_formatter::SourceCode;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_ast::{PySourceType, PythonVersion};
|
||||
use ruff_python_parser::{ParseOptions, parse};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_text_size::Ranged;
|
||||
|
|
@ -42,13 +42,19 @@ pub struct Cli {
|
|||
pub print_comments: bool,
|
||||
#[clap(long, short = 'C')]
|
||||
pub skip_magic_trailing_comma: bool,
|
||||
#[clap(long)]
|
||||
pub target_version: PythonVersion,
|
||||
}
|
||||
|
||||
pub fn format_and_debug_print(source: &str, cli: &Cli, source_path: &Path) -> Result<String> {
|
||||
let source_type = PySourceType::from(source_path);
|
||||
|
||||
// Parse the AST.
|
||||
let parsed = parse(source, ParseOptions::from(source_type)).context("Syntax error in input")?;
|
||||
let parsed = parse(
|
||||
source,
|
||||
ParseOptions::from(source_type).with_target_version(cli.target_version),
|
||||
)
|
||||
.context("Syntax error in input")?;
|
||||
|
||||
let options = PyFormatOptions::from_extension(source_path)
|
||||
.with_preview(if cli.preview {
|
||||
|
|
@ -60,7 +66,8 @@ pub fn format_and_debug_print(source: &str, cli: &Cli, source_path: &Path) -> Re
|
|||
MagicTrailingComma::Ignore
|
||||
} else {
|
||||
MagicTrailingComma::Respect
|
||||
});
|
||||
})
|
||||
.with_target_version(cli.target_version);
|
||||
|
||||
let source_code = SourceCode::new(source);
|
||||
let comment_ranges = CommentRanges::from(parsed.tokens());
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ pub enum TupleParentheses {
|
|||
///
|
||||
/// ```python
|
||||
/// return len(self.nodeseeeeeeeee), sum(
|
||||
// len(node.parents) for node in self.node_map.values()
|
||||
// )
|
||||
/// len(node.parents) for node in self.node_map.values()
|
||||
/// )
|
||||
/// ```
|
||||
OptionalParentheses,
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
use ruff_formatter::FormatRuleWithOptions;
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::ExceptHandlerExceptHandler;
|
||||
use ruff_python_ast::{ExceptHandlerExceptHandler, Expr, PythonVersion};
|
||||
|
||||
use crate::expression::expr_tuple::TupleParentheses;
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_remove_parens_around_except_types_enabled;
|
||||
use crate::statement::clause::{ClauseHeader, clause_body, clause_header};
|
||||
use crate::statement::suite::SuiteKind;
|
||||
|
||||
|
|
@ -57,7 +59,7 @@ impl FormatNodeRule<ExceptHandlerExceptHandler> for FormatExceptHandlerExceptHan
|
|||
clause_header(
|
||||
ClauseHeader::ExceptHandler(item),
|
||||
dangling_comments,
|
||||
&format_with(|f| {
|
||||
&format_with(|f: &mut PyFormatter| {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
|
|
@ -69,21 +71,50 @@ impl FormatNodeRule<ExceptHandlerExceptHandler> for FormatExceptHandlerExceptHan
|
|||
]
|
||||
)?;
|
||||
|
||||
if let Some(type_) = type_ {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
space(),
|
||||
maybe_parenthesize_expression(
|
||||
type_,
|
||||
item,
|
||||
Parenthesize::IfBreaks
|
||||
match type_.as_deref() {
|
||||
// For tuples of exception types without an `as` name and on 3.14+, the
|
||||
// parentheses are optional.
|
||||
//
|
||||
// ```py
|
||||
// try:
|
||||
// ...
|
||||
// except BaseException, Exception: # Ok
|
||||
// ...
|
||||
// ```
|
||||
Some(Expr::Tuple(tuple))
|
||||
if f.options().target_version() >= PythonVersion::PY314
|
||||
&& is_remove_parens_around_except_types_enabled(
|
||||
f.context(),
|
||||
)
|
||||
]
|
||||
)?;
|
||||
if let Some(name) = name {
|
||||
write!(f, [space(), token("as"), space(), name.format()])?;
|
||||
&& name.is_none() =>
|
||||
{
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
space(),
|
||||
tuple
|
||||
.format()
|
||||
.with_options(TupleParentheses::NeverPreserve)
|
||||
]
|
||||
)?;
|
||||
}
|
||||
Some(type_) => {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
space(),
|
||||
maybe_parenthesize_expression(
|
||||
type_,
|
||||
item,
|
||||
Parenthesize::IfBreaks
|
||||
)
|
||||
]
|
||||
)?;
|
||||
if let Some(name) = name {
|
||||
write!(f, [space(), token("as"), space(), name.format()])?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -27,3 +27,12 @@ pub(crate) const fn is_blank_line_before_decorated_class_in_stub_enabled(
|
|||
) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
||||
/// Returns `true` if the
|
||||
/// [`remove_parens_around_except_types`](https://github.com/astral-sh/ruff/pull/20768) preview
|
||||
/// style is enabled.
|
||||
pub(crate) const fn is_remove_parens_around_except_types_enabled(
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue