Compare commits
4 Commits
v0.0.289
...
break-befo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4cfe1f2a7 | ||
|
|
f4c7bff36b | ||
|
|
56440ad835 | ||
|
|
179128dc54 |
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2304,6 +2304,7 @@ dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools",
|
||||
"memchr",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, Keyword, Stmt};
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Stmt};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_trivia::{
|
||||
@@ -92,10 +92,8 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
) -> Result<Edit> {
|
||||
// Partition into arguments before and after the argument to remove.
|
||||
let (before, after): (Vec<_>, Vec<_>) = arguments
|
||||
.args
|
||||
.iter()
|
||||
.map(Expr::range)
|
||||
.chain(arguments.keywords.iter().map(Keyword::range))
|
||||
.arguments_source_order()
|
||||
.map(|arg| arg.range())
|
||||
.filter(|range| argument.range() != *range)
|
||||
.partition(|range| range.start() < argument.start());
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ ruff_text_size = { path = "../ruff_text_size" }
|
||||
|
||||
bitflags = { workspace = true }
|
||||
is-macro = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
num-bigint = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
|
||||
@@ -207,6 +207,8 @@ pub fn any_over_expr(expr: &Expr, func: &dyn Fn(&Expr) -> bool) -> bool {
|
||||
range: _,
|
||||
}) => {
|
||||
any_over_expr(call_func, func)
|
||||
// Note that this is the evaluation order but not necessarily the declaration order
|
||||
// (e.g. for `f(*args, a=2, *args2, **kwargs)` it's not)
|
||||
|| args.iter().any(|expr| any_over_expr(expr, func))
|
||||
|| keywords
|
||||
.iter()
|
||||
@@ -347,6 +349,8 @@ pub fn any_over_stmt(stmt: &Stmt, func: &dyn Fn(&Expr) -> bool) -> bool {
|
||||
decorator_list,
|
||||
..
|
||||
}) => {
|
||||
// Note that e.g. `class A(*args, a=2, *args2, **kwargs): pass` is a valid class
|
||||
// definition
|
||||
arguments
|
||||
.as_deref()
|
||||
.is_some_and(|Arguments { args, keywords, .. }| {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::visitor::preorder::PreorderVisitor;
|
||||
use crate::{
|
||||
self as ast, Alias, Arguments, Comprehension, Decorator, ExceptHandler, Expr, Keyword,
|
||||
MatchCase, Mod, Parameter, ParameterWithDefault, Parameters, Pattern, PatternArguments,
|
||||
PatternKeyword, Stmt, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple,
|
||||
TypeParams, WithItem,
|
||||
self as ast, Alias, ArgOrKeyword, Arguments, Comprehension, Decorator, ExceptHandler, Expr,
|
||||
Keyword, MatchCase, Mod, Parameter, ParameterWithDefault, Parameters, Pattern,
|
||||
PatternArguments, PatternKeyword, Stmt, TypeParam, TypeParamParamSpec, TypeParamTypeVar,
|
||||
TypeParamTypeVarTuple, TypeParams, WithItem,
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use std::ptr::NonNull;
|
||||
@@ -3549,18 +3549,11 @@ impl AstNode for Arguments {
|
||||
where
|
||||
V: PreorderVisitor<'a> + ?Sized,
|
||||
{
|
||||
let ast::Arguments {
|
||||
range: _,
|
||||
args,
|
||||
keywords,
|
||||
} = self;
|
||||
|
||||
for arg in args {
|
||||
visitor.visit_expr(arg);
|
||||
}
|
||||
|
||||
for keyword in keywords {
|
||||
visitor.visit_keyword(keyword);
|
||||
for arg_or_keyword in self.arguments_source_order() {
|
||||
match arg_or_keyword {
|
||||
ArgOrKeyword::Arg(arg) => visitor.visit_expr(arg),
|
||||
ArgOrKeyword::Keyword(keyword) => visitor.visit_keyword(keyword),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![allow(clippy::derive_partial_eq_without_eq)]
|
||||
|
||||
use itertools::Itertools;
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Deref;
|
||||
@@ -2177,6 +2178,34 @@ pub struct Arguments {
|
||||
pub keywords: Vec<Keyword>,
|
||||
}
|
||||
|
||||
/// An entry in the argument list of a function call.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ArgOrKeyword<'a> {
|
||||
Arg(&'a Expr),
|
||||
Keyword(&'a Keyword),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Expr> for ArgOrKeyword<'a> {
|
||||
fn from(arg: &'a Expr) -> Self {
|
||||
Self::Arg(arg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Keyword> for ArgOrKeyword<'a> {
|
||||
fn from(keyword: &'a Keyword) -> Self {
|
||||
Self::Keyword(keyword)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for ArgOrKeyword<'_> {
|
||||
fn range(&self) -> TextRange {
|
||||
match self {
|
||||
Self::Arg(arg) => arg.range(),
|
||||
Self::Keyword(keyword) => keyword.range(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Arguments {
|
||||
/// Return the number of positional and keyword arguments.
|
||||
pub fn len(&self) -> usize {
|
||||
@@ -2212,6 +2241,46 @@ impl Arguments {
|
||||
.map(|keyword| &keyword.value)
|
||||
.or_else(|| self.find_positional(position))
|
||||
}
|
||||
|
||||
/// Return the positional and keyword arguments in the order of declaration.
|
||||
///
|
||||
/// Positional arguments are generally before keyword arguments, but star arguments are an
|
||||
/// exception:
|
||||
/// ```python
|
||||
/// class A(*args, a=2, *args2, **kwargs):
|
||||
/// pass
|
||||
///
|
||||
/// f(*args, a=2, *args2, **kwargs)
|
||||
/// ```
|
||||
/// where `*args` and `args2` are `args` while `a=1` and `kwargs` are `keywords`.
|
||||
///
|
||||
/// If you would just chain `args` and `keywords` the call would get reordered which we don't
|
||||
/// want. This function instead "merge sorts" them into the correct order.
|
||||
///
|
||||
/// Note that the order of evaluation is always first `args`, then `keywords`:
|
||||
/// ```python
|
||||
/// def f(*args, **kwargs):
|
||||
/// pass
|
||||
///
|
||||
/// def g(x):
|
||||
/// print(x)
|
||||
/// return x
|
||||
///
|
||||
///
|
||||
/// f(*g([1]), a=g(2), *g([3]), **g({"4": 5}))
|
||||
/// ```
|
||||
/// Output:
|
||||
/// ```text
|
||||
/// [1]
|
||||
/// [3]
|
||||
/// 2
|
||||
/// {'4': 5}
|
||||
/// ```
|
||||
pub fn arguments_source_order(&self) -> impl Iterator<Item = ArgOrKeyword<'_>> {
|
||||
let args = self.args.iter().map(ArgOrKeyword::Arg);
|
||||
let keywords = self.keywords.iter().map(ArgOrKeyword::Keyword);
|
||||
args.merge_by(keywords, |left, right| left.start() < right.start())
|
||||
}
|
||||
}
|
||||
|
||||
/// An AST node used to represent a sequence of type parameters.
|
||||
|
||||
@@ -573,6 +573,9 @@ pub fn walk_format_spec<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, format_spe
|
||||
}
|
||||
|
||||
pub fn walk_arguments<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, arguments: &'a Arguments) {
|
||||
// Note that the there might be keywords before the last arg, e.g. in
|
||||
// f(*args, a=2, *args2, **kwargs)`, but we follow Python in evaluating first `args` and then
|
||||
// `keywords`. See also [Arguments::arguments_source_order`].
|
||||
for arg in &arguments.args {
|
||||
visitor.visit_expr(arg);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use ruff_python_ast::{
|
||||
self as ast, Alias, BoolOp, CmpOp, Comprehension, Constant, ConversionFlag, DebugText,
|
||||
ExceptHandler, Expr, Identifier, MatchCase, Operator, Parameter, Parameters, Pattern, Stmt,
|
||||
Suite, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, WithItem,
|
||||
self as ast, Alias, ArgOrKeyword, BoolOp, CmpOp, Comprehension, Constant, ConversionFlag,
|
||||
DebugText, ExceptHandler, Expr, Identifier, MatchCase, Operator, Parameter, Parameters,
|
||||
Pattern, Stmt, Suite, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple,
|
||||
WithItem,
|
||||
};
|
||||
use ruff_python_ast::{ParameterWithDefault, TypeParams};
|
||||
use ruff_python_literal::escape::{AsciiEscape, Escape, UnicodeEscape};
|
||||
@@ -265,19 +266,23 @@ impl<'a> Generator<'a> {
|
||||
if let Some(arguments) = arguments {
|
||||
self.p("(");
|
||||
let mut first = true;
|
||||
for base in &arguments.args {
|
||||
self.p_delim(&mut first, ", ");
|
||||
self.unparse_expr(base, precedence::MAX);
|
||||
}
|
||||
for keyword in &arguments.keywords {
|
||||
self.p_delim(&mut first, ", ");
|
||||
if let Some(arg) = &keyword.arg {
|
||||
self.p_id(arg);
|
||||
self.p("=");
|
||||
} else {
|
||||
self.p("**");
|
||||
for arg_or_keyword in arguments.arguments_source_order() {
|
||||
match arg_or_keyword {
|
||||
ArgOrKeyword::Arg(arg) => {
|
||||
self.p_delim(&mut first, ", ");
|
||||
self.unparse_expr(arg, precedence::MAX);
|
||||
}
|
||||
ArgOrKeyword::Keyword(keyword) => {
|
||||
self.p_delim(&mut first, ", ");
|
||||
if let Some(arg) = &keyword.arg {
|
||||
self.p_id(arg);
|
||||
self.p("=");
|
||||
} else {
|
||||
self.p("**");
|
||||
}
|
||||
self.unparse_expr(&keyword.value, precedence::MAX);
|
||||
}
|
||||
}
|
||||
self.unparse_expr(&keyword.value, precedence::MAX);
|
||||
}
|
||||
self.p(")");
|
||||
}
|
||||
@@ -1045,19 +1050,24 @@ impl<'a> Generator<'a> {
|
||||
self.unparse_comp(generators);
|
||||
} else {
|
||||
let mut first = true;
|
||||
for arg in &arguments.args {
|
||||
self.p_delim(&mut first, ", ");
|
||||
self.unparse_expr(arg, precedence::COMMA);
|
||||
}
|
||||
for kw in &arguments.keywords {
|
||||
self.p_delim(&mut first, ", ");
|
||||
if let Some(arg) = &kw.arg {
|
||||
self.p_id(arg);
|
||||
self.p("=");
|
||||
self.unparse_expr(&kw.value, precedence::COMMA);
|
||||
} else {
|
||||
self.p("**");
|
||||
self.unparse_expr(&kw.value, precedence::MAX);
|
||||
|
||||
for arg_or_keyword in arguments.arguments_source_order() {
|
||||
match arg_or_keyword {
|
||||
ArgOrKeyword::Arg(arg) => {
|
||||
self.p_delim(&mut first, ", ");
|
||||
self.unparse_expr(arg, precedence::COMMA);
|
||||
}
|
||||
ArgOrKeyword::Keyword(keyword) => {
|
||||
self.p_delim(&mut first, ", ");
|
||||
if let Some(arg) = &keyword.arg {
|
||||
self.p_id(arg);
|
||||
self.p("=");
|
||||
self.unparse_expr(&keyword.value, precedence::COMMA);
|
||||
} else {
|
||||
self.p("**");
|
||||
self.unparse_expr(&keyword.value, precedence::MAX);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1649,6 +1659,11 @@ class Foo:
|
||||
assert_round_trip!(r#"type Foo[*Ts] = ..."#);
|
||||
assert_round_trip!(r#"type Foo[**P] = ..."#);
|
||||
assert_round_trip!(r#"type Foo[T, U, *Ts, **P] = ..."#);
|
||||
// https://github.com/astral-sh/ruff/issues/6498
|
||||
assert_round_trip!(r#"f(a=1, *args, **kwargs)"#);
|
||||
assert_round_trip!(r#"f(*args, a=1, **kwargs)"#);
|
||||
assert_round_trip!(r#"f(*args, a=1, *args2, **kwargs)"#);
|
||||
assert_round_trip!("class A(*args, a=2, *args2, **kwargs):\n pass");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
# Ruff Formatter
|
||||
|
||||
The Ruff formatter is an extremely fast Python code formatter that ships as part of the `ruff`
|
||||
CLI (as of Ruff v0.0.287).
|
||||
CLI (as of Ruff v0.0.289).
|
||||
|
||||
The formatter is currently in an **alpha** state. As such, it's not yet recommended for production
|
||||
use, but it _is_ ready for experimentation and testing. _We'd love to have your feedback._
|
||||
The formatter is currently in an **Alpha** state. The Alpha is primarily intended for
|
||||
experimentation: our focus is on collecting feedback that we can address prior to a production-ready
|
||||
Beta release later this year. (While we're using the formatter in production on our own projects,
|
||||
the CLI, configuration options, and code style may change arbitrarily between the Alpha and Beta.)
|
||||
|
||||
[_We'd love to hear your feedback._](https://github.com/astral-sh/ruff/discussions/7310)
|
||||
|
||||
## Goals
|
||||
|
||||
@@ -26,7 +30,7 @@ For details, see [Black compatibility](#black-compatibility).
|
||||
|
||||
## Getting started
|
||||
|
||||
The Ruff formatter shipped in an alpha state as part of Ruff v0.0.287.
|
||||
The Ruff formatter shipped in an Alpha state as part of Ruff v0.0.289.
|
||||
|
||||
### CLI
|
||||
|
||||
@@ -69,8 +73,7 @@ instead exiting with a non-zero status code if any files are not already formatt
|
||||
|
||||
### VS Code
|
||||
|
||||
As of `v2023.34.0`,
|
||||
the [Ruff VS Code extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff)
|
||||
As of `v2023.36.0`, the [Ruff VS Code extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff)
|
||||
ships with support for the Ruff formatter. To enable formatting capabilities, set the
|
||||
`ruff.enableExperimentalFormatter` setting to `true` in your `settings.json`, and mark the Ruff
|
||||
extension as your default Python formatter:
|
||||
|
||||
@@ -242,3 +242,26 @@ f(x=(
|
||||
# comment
|
||||
1
|
||||
))
|
||||
|
||||
args = [2]
|
||||
args2 = [3]
|
||||
kwargs = {"4": 5}
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/6498
|
||||
f(a=1, *args, **kwargs)
|
||||
f(*args, a=1, **kwargs)
|
||||
f(*args, a=1, *args2, **kwargs)
|
||||
f( # a
|
||||
* # b
|
||||
args
|
||||
# c
|
||||
, # d
|
||||
a=1,
|
||||
# e
|
||||
* # f
|
||||
args2
|
||||
# g
|
||||
** # h
|
||||
kwargs,
|
||||
)
|
||||
|
||||
|
||||
@@ -91,3 +91,18 @@ f = "f"[:,]
|
||||
g1 = "g"[(1):(2)]
|
||||
g2 = "g"[(1):(2):(3)]
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/7316
|
||||
section_header_data = byte_array[
|
||||
byte_begin_index
|
||||
+ byte_step_index * event_index : byte_begin_index
|
||||
+ byte_step_index * (event_index + 1)
|
||||
]
|
||||
|
||||
section_header_data2 = byte_array[
|
||||
byte_begin_index
|
||||
+ byte_step_index * event_index : byte_begin_index
|
||||
+ byte_step_index : section_size
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ pub(super) fn place_comment<'a>(
|
||||
/// ):
|
||||
/// ...
|
||||
/// ```
|
||||
/// The parentheses enclose `True`, but the range of `True`doesn't include the `# comment`.
|
||||
/// The parentheses enclose `True`, but the range of `True` doesn't include the `# comment`.
|
||||
///
|
||||
/// Default handling can get parenthesized comments wrong in a number of ways. For example, the
|
||||
/// comment here is marked (by default) as a trailing comment of `x`, when it should be a leading
|
||||
@@ -120,10 +120,8 @@ fn handle_parenthesized_comment<'a>(
|
||||
// For now, we _can_ assert, but to do so, we stop lexing when we hit a token that precedes an
|
||||
// identifier.
|
||||
if comment.line_position().is_end_of_line() {
|
||||
let tokenizer = SimpleTokenizer::new(
|
||||
locator.contents(),
|
||||
TextRange::new(preceding.end(), comment.start()),
|
||||
);
|
||||
let range = TextRange::new(preceding.end(), comment.start());
|
||||
let tokenizer = SimpleTokenizer::new(locator.contents(), range);
|
||||
if tokenizer
|
||||
.skip_trivia()
|
||||
.take_while(|token| {
|
||||
@@ -136,7 +134,7 @@ fn handle_parenthesized_comment<'a>(
|
||||
debug_assert!(
|
||||
!matches!(token.kind, SimpleTokenKind::Bogus),
|
||||
"Unexpected token between nodes: `{:?}`",
|
||||
locator.slice(TextRange::new(preceding.end(), comment.start()),)
|
||||
locator.slice(range)
|
||||
);
|
||||
|
||||
token.kind() == SimpleTokenKind::LParen
|
||||
@@ -145,10 +143,8 @@ fn handle_parenthesized_comment<'a>(
|
||||
return CommentPlacement::leading(following, comment);
|
||||
}
|
||||
} else {
|
||||
let tokenizer = SimpleTokenizer::new(
|
||||
locator.contents(),
|
||||
TextRange::new(comment.end(), following.start()),
|
||||
);
|
||||
let range = TextRange::new(comment.end(), following.start());
|
||||
let tokenizer = SimpleTokenizer::new(locator.contents(), range);
|
||||
if tokenizer
|
||||
.skip_trivia()
|
||||
.take_while(|token| {
|
||||
@@ -161,7 +157,7 @@ fn handle_parenthesized_comment<'a>(
|
||||
debug_assert!(
|
||||
!matches!(token.kind, SimpleTokenKind::Bogus),
|
||||
"Unexpected token between nodes: `{:?}`",
|
||||
locator.slice(TextRange::new(comment.end(), following.start()))
|
||||
locator.slice(range)
|
||||
);
|
||||
token.kind() == SimpleTokenKind::RParen
|
||||
})
|
||||
|
||||
@@ -91,7 +91,7 @@ impl FormatNodeRule<ExprSlice> for FormatExprSlice {
|
||||
if !all_simple && lower.is_some() {
|
||||
space().fmt(f)?;
|
||||
}
|
||||
token(":").fmt(f)?;
|
||||
write!(f, [soft_line_break(), token(":")])?;
|
||||
// No upper node, no need for a space, e.g. `x[a() :]`
|
||||
if !all_simple && upper.is_some() {
|
||||
space().fmt(f)?;
|
||||
@@ -125,7 +125,7 @@ impl FormatNodeRule<ExprSlice> for FormatExprSlice {
|
||||
if !all_simple && (upper.is_some() || step.is_none()) {
|
||||
space().fmt(f)?;
|
||||
}
|
||||
token(":").fmt(f)?;
|
||||
write!(f, [soft_line_break(), token(":")])?;
|
||||
// No step node, no need for a space
|
||||
if !all_simple && step.is_some() {
|
||||
space().fmt(f)?;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::node::AstNode;
|
||||
use ruff_python_ast::{Arguments, Expr};
|
||||
use ruff_python_ast::{ArgOrKeyword, Arguments, Expr};
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
@@ -14,6 +13,11 @@ pub struct FormatArguments;
|
||||
|
||||
impl FormatNodeRule<Arguments> for FormatArguments {
|
||||
fn fmt_fields(&self, item: &Arguments, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let Arguments {
|
||||
range,
|
||||
args,
|
||||
keywords,
|
||||
} = item;
|
||||
// We have a case with `f()` without any argument, which is a special case because we can
|
||||
// have a comment with no node attachment inside:
|
||||
// ```python
|
||||
@@ -21,7 +25,7 @@ impl FormatNodeRule<Arguments> for FormatArguments {
|
||||
// # This call has a dangling comment.
|
||||
// )
|
||||
// ```
|
||||
if item.args.is_empty() && item.keywords.is_empty() {
|
||||
if args.is_empty() && keywords.is_empty() {
|
||||
let comments = f.context().comments().clone();
|
||||
let dangling = comments.dangling(item);
|
||||
return write!(f, [empty_parenthesized("(", dangling, ")")]);
|
||||
@@ -29,9 +33,9 @@ impl FormatNodeRule<Arguments> for FormatArguments {
|
||||
|
||||
let all_arguments = format_with(|f: &mut PyFormatter| {
|
||||
let source = f.context().source();
|
||||
let mut joiner = f.join_comma_separated(item.end());
|
||||
match item.args.as_slice() {
|
||||
[arg] if item.keywords.is_empty() => {
|
||||
let mut joiner = f.join_comma_separated(range.end());
|
||||
match args.as_slice() {
|
||||
[arg] if keywords.is_empty() => {
|
||||
match arg {
|
||||
Expr::GeneratorExp(generator_exp) => joiner.entry(
|
||||
generator_exp,
|
||||
@@ -41,7 +45,7 @@ impl FormatNodeRule<Arguments> for FormatArguments {
|
||||
),
|
||||
other => {
|
||||
let parentheses =
|
||||
if is_single_argument_parenthesized(arg, item.end(), source) {
|
||||
if is_single_argument_parenthesized(arg, range.end(), source) {
|
||||
Parentheses::Always
|
||||
} else {
|
||||
// Note: no need to handle opening-parenthesis comments, since
|
||||
@@ -53,14 +57,17 @@ impl FormatNodeRule<Arguments> for FormatArguments {
|
||||
}
|
||||
};
|
||||
}
|
||||
args => {
|
||||
joiner
|
||||
.entries(
|
||||
// We have the parentheses from the call so the item never need any
|
||||
args.iter()
|
||||
.map(|arg| (arg, arg.format().with_options(Parentheses::Preserve))),
|
||||
)
|
||||
.nodes(item.keywords.iter());
|
||||
_ => {
|
||||
for arg_or_keyword in item.arguments_source_order() {
|
||||
match arg_or_keyword {
|
||||
ArgOrKeyword::Arg(arg) => {
|
||||
joiner.entry(arg, &arg.format());
|
||||
}
|
||||
ArgOrKeyword::Keyword(keyword) => {
|
||||
joiner.entry(keyword, &keyword.format());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +83,7 @@ impl FormatNodeRule<Arguments> for FormatArguments {
|
||||
// c,
|
||||
// )
|
||||
let comments = f.context().comments().clone();
|
||||
let dangling_comments = comments.dangling(item.as_any_node_ref());
|
||||
let dangling_comments = comments.dangling(item);
|
||||
|
||||
write!(
|
||||
f,
|
||||
|
||||
@@ -18,14 +18,9 @@ pub struct FormatStmtFunctionDef;
|
||||
impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
|
||||
fn fmt_fields(&self, item: &StmtFunctionDef, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let StmtFunctionDef {
|
||||
range: _,
|
||||
is_async,
|
||||
decorator_list,
|
||||
name,
|
||||
type_params,
|
||||
parameters,
|
||||
returns,
|
||||
body,
|
||||
..
|
||||
} = item;
|
||||
|
||||
let comments = f.context().comments().clone();
|
||||
@@ -47,101 +42,7 @@ impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
|
||||
clause_header(
|
||||
ClauseHeader::Function(item),
|
||||
trailing_definition_comments,
|
||||
&format_with(|f| {
|
||||
if *is_async {
|
||||
write!(f, [token("async"), space()])?;
|
||||
}
|
||||
|
||||
write!(f, [token("def"), space(), name.format()])?;
|
||||
|
||||
if let Some(type_params) = type_params.as_ref() {
|
||||
write!(f, [type_params.format()])?;
|
||||
}
|
||||
|
||||
let format_inner = format_with(|f: &mut PyFormatter| {
|
||||
write!(f, [parameters.format()])?;
|
||||
|
||||
if let Some(return_annotation) = returns.as_ref() {
|
||||
write!(f, [space(), token("->"), space()])?;
|
||||
|
||||
if return_annotation.is_tuple_expr() {
|
||||
let parentheses =
|
||||
if comments.has_leading(return_annotation.as_ref()) {
|
||||
Parentheses::Always
|
||||
} else {
|
||||
Parentheses::Never
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
[return_annotation.format().with_options(parentheses)]
|
||||
)?;
|
||||
} else if comments.has_trailing(return_annotation.as_ref()) {
|
||||
// Intentionally parenthesize any return annotations with trailing comments.
|
||||
// This avoids an instability in cases like:
|
||||
// ```python
|
||||
// def double(
|
||||
// a: int
|
||||
// ) -> (
|
||||
// int # Hello
|
||||
// ):
|
||||
// pass
|
||||
// ```
|
||||
// If we allow this to break, it will be formatted as follows:
|
||||
// ```python
|
||||
// def double(
|
||||
// a: int
|
||||
// ) -> int: # Hello
|
||||
// pass
|
||||
// ```
|
||||
// On subsequent formats, the `# Hello` will be interpreted as a dangling
|
||||
// comment on a function, yielding:
|
||||
// ```python
|
||||
// def double(a: int) -> int: # Hello
|
||||
// pass
|
||||
// ```
|
||||
// Ideally, we'd reach that final formatting in a single pass, but doing so
|
||||
// requires that the parent be aware of how the child is formatted, which
|
||||
// is challenging. As a compromise, we break those expressions to avoid an
|
||||
// instability.
|
||||
write!(
|
||||
f,
|
||||
[return_annotation
|
||||
.format()
|
||||
.with_options(Parentheses::Always)]
|
||||
)?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
[maybe_parenthesize_expression(
|
||||
return_annotation,
|
||||
item,
|
||||
if empty_parameters(parameters, f.context().source()) {
|
||||
// If the parameters are empty, add parentheses if the return annotation
|
||||
// breaks at all.
|
||||
Parenthesize::IfBreaksOrIfRequired
|
||||
} else {
|
||||
// Otherwise, use our normal rules for parentheses, which allows us to break
|
||||
// like:
|
||||
// ```python
|
||||
// def f(
|
||||
// x,
|
||||
// ) -> Tuple[
|
||||
// int,
|
||||
// int,
|
||||
// ]:
|
||||
// ...
|
||||
// ```
|
||||
Parenthesize::IfBreaks
|
||||
},
|
||||
)]
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
group(&format_inner).fmt(f)
|
||||
}),
|
||||
&format_with(|f| format_function_header(f, item)),
|
||||
),
|
||||
clause_body(body, trailing_definition_comments).with_kind(SuiteKind::Function),
|
||||
]
|
||||
@@ -176,6 +77,109 @@ impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_function_header(f: &mut PyFormatter, item: &StmtFunctionDef) -> FormatResult<()> {
|
||||
let StmtFunctionDef {
|
||||
range: _,
|
||||
is_async,
|
||||
decorator_list: _,
|
||||
name,
|
||||
type_params,
|
||||
parameters,
|
||||
returns,
|
||||
body: _,
|
||||
} = item;
|
||||
|
||||
let comments = f.context().comments().clone();
|
||||
|
||||
if *is_async {
|
||||
write!(f, [token("async"), space()])?;
|
||||
}
|
||||
|
||||
write!(f, [token("def"), space(), name.format()])?;
|
||||
|
||||
if let Some(type_params) = type_params.as_ref() {
|
||||
write!(f, [type_params.format()])?;
|
||||
}
|
||||
|
||||
let format_inner = format_with(|f: &mut PyFormatter| {
|
||||
write!(f, [parameters.format()])?;
|
||||
|
||||
if let Some(return_annotation) = returns.as_ref() {
|
||||
write!(f, [space(), token("->"), space()])?;
|
||||
|
||||
if return_annotation.is_tuple_expr() {
|
||||
let parentheses = if comments.has_leading(return_annotation.as_ref()) {
|
||||
Parentheses::Always
|
||||
} else {
|
||||
Parentheses::Never
|
||||
};
|
||||
write!(f, [return_annotation.format().with_options(parentheses)])?;
|
||||
} else if comments.has_trailing(return_annotation.as_ref()) {
|
||||
// Intentionally parenthesize any return annotations with trailing comments.
|
||||
// This avoids an instability in cases like:
|
||||
// ```python
|
||||
// def double(
|
||||
// a: int
|
||||
// ) -> (
|
||||
// int # Hello
|
||||
// ):
|
||||
// pass
|
||||
// ```
|
||||
// If we allow this to break, it will be formatted as follows:
|
||||
// ```python
|
||||
// def double(
|
||||
// a: int
|
||||
// ) -> int: # Hello
|
||||
// pass
|
||||
// ```
|
||||
// On subsequent formats, the `# Hello` will be interpreted as a dangling
|
||||
// comment on a function, yielding:
|
||||
// ```python
|
||||
// def double(a: int) -> int: # Hello
|
||||
// pass
|
||||
// ```
|
||||
// Ideally, we'd reach that final formatting in a single pass, but doing so
|
||||
// requires that the parent be aware of how the child is formatted, which
|
||||
// is challenging. As a compromise, we break those expressions to avoid an
|
||||
// instability.
|
||||
write!(
|
||||
f,
|
||||
[return_annotation.format().with_options(Parentheses::Always)]
|
||||
)?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
[maybe_parenthesize_expression(
|
||||
return_annotation,
|
||||
item,
|
||||
if empty_parameters(parameters, f.context().source()) {
|
||||
// If the parameters are empty, add parentheses if the return annotation
|
||||
// breaks at all.
|
||||
Parenthesize::IfBreaksOrIfRequired
|
||||
} else {
|
||||
// Otherwise, use our normal rules for parentheses, which allows us to break
|
||||
// like:
|
||||
// ```python
|
||||
// def f(
|
||||
// x,
|
||||
// ) -> Tuple[
|
||||
// int,
|
||||
// int,
|
||||
// ]:
|
||||
// ...
|
||||
// ```
|
||||
Parenthesize::IfBreaks
|
||||
},
|
||||
)]
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
group(&format_inner).fmt(f)
|
||||
}
|
||||
|
||||
/// Returns `true` if [`Parameters`] is empty (no parameters, no comments, etc.).
|
||||
fn empty_parameters(parameters: &Parameters, source: &str) -> bool {
|
||||
let mut tokenizer = SimpleTokenizer::new(source, parameters.range())
|
||||
|
||||
@@ -248,6 +248,29 @@ f(x=(
|
||||
# comment
|
||||
1
|
||||
))
|
||||
|
||||
args = [2]
|
||||
args2 = [3]
|
||||
kwargs = {"4": 5}
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/6498
|
||||
f(a=1, *args, **kwargs)
|
||||
f(*args, a=1, **kwargs)
|
||||
f(*args, a=1, *args2, **kwargs)
|
||||
f( # a
|
||||
* # b
|
||||
args
|
||||
# c
|
||||
, # d
|
||||
a=1,
|
||||
# e
|
||||
* # f
|
||||
args2
|
||||
# g
|
||||
** # h
|
||||
kwargs,
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
## Output
|
||||
@@ -493,6 +516,27 @@ f(
|
||||
1
|
||||
)
|
||||
)
|
||||
|
||||
args = [2]
|
||||
args2 = [3]
|
||||
kwargs = {"4": 5}
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/6498
|
||||
f(a=1, *args, **kwargs)
|
||||
f(*args, a=1, **kwargs)
|
||||
f(*args, a=1, *args2, **kwargs)
|
||||
f( # a
|
||||
# b
|
||||
*args, # d
|
||||
# c
|
||||
a=1,
|
||||
# e
|
||||
# f
|
||||
*args2
|
||||
# g
|
||||
** # h
|
||||
kwargs,
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -97,6 +97,21 @@ f = "f"[:,]
|
||||
g1 = "g"[(1):(2)]
|
||||
g2 = "g"[(1):(2):(3)]
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/7316
|
||||
section_header_data = byte_array[
|
||||
byte_begin_index
|
||||
+ byte_step_index * event_index : byte_begin_index
|
||||
+ byte_step_index * (event_index + 1)
|
||||
]
|
||||
|
||||
section_header_data2 = byte_array[
|
||||
byte_begin_index
|
||||
+ byte_step_index * event_index : byte_begin_index
|
||||
+ byte_step_index : section_size
|
||||
]
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Output
|
||||
@@ -132,21 +147,25 @@ b1 = "b"[ # a
|
||||
|
||||
# Handle the spacing from the colon correctly with upper leading comments
|
||||
c1 = "c"[
|
||||
1: # e
|
||||
1
|
||||
: # e
|
||||
# f
|
||||
2
|
||||
]
|
||||
c2 = "c"[
|
||||
1: # e
|
||||
1
|
||||
: # e
|
||||
2
|
||||
]
|
||||
c3 = "c"[
|
||||
1:
|
||||
1
|
||||
:
|
||||
# f
|
||||
2
|
||||
]
|
||||
c4 = "c"[
|
||||
1: # f
|
||||
1
|
||||
: # f
|
||||
2
|
||||
]
|
||||
|
||||
@@ -155,7 +174,8 @@ d1 = "d"[ # comment
|
||||
:
|
||||
]
|
||||
d2 = "d"[ # comment
|
||||
1:
|
||||
1
|
||||
:
|
||||
]
|
||||
d3 = "d"[
|
||||
1 # comment
|
||||
@@ -191,6 +211,18 @@ f = "f"[:,]
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/5733
|
||||
g1 = "g"[(1):(2)]
|
||||
g2 = "g"[(1):(2):(3)]
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/7316
|
||||
section_header_data = byte_array[
|
||||
byte_begin_index + byte_step_index * event_index
|
||||
: byte_begin_index + byte_step_index * (event_index + 1)
|
||||
]
|
||||
|
||||
section_header_data2 = byte_array[
|
||||
byte_begin_index + byte_step_index * event_index
|
||||
: byte_begin_index + byte_step_index
|
||||
: section_size
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -175,7 +175,8 @@ raise ( # hey 2
|
||||
|
||||
# some comment
|
||||
raise aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa[
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:bbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
:bbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
]
|
||||
|
||||
raise (
|
||||
|
||||
Reference in New Issue
Block a user