Compare commits

...

2 Commits

Author SHA1 Message Date
konstin
7f97547b5f Add increment/decrement 2024-03-14 16:56:06 +01:00
Hoël Bagard
e944c16c46 [pycodestyle] Do not ignore lines before the first logical line in blank lines rules (#10382)
## Summary

Ignoring all lines until the first logical line does not match the
behavior from pycodestyle. This PR therefore removes the `if
state.is_not_first_logical_line` skipping the line check before the
first logical line, and applies it only to `E302`.

For example, in the snippet below a rule violation should be detected on
the second comment and on the import.

```python
# first comment




# second comment




import foo
```

Fixes #10374

## Test Plan

Add test cases, update the snapshots and verify the ecosystem check output
2024-03-14 14:05:24 +05:30
33 changed files with 23825 additions and 23206 deletions

View File

@@ -0,0 +1,4 @@
"""Test where the error is after the module's docstring."""
def fn():
pass

View File

@@ -0,0 +1,4 @@
"Test where the first line is a comment, " + "and the rule violation follows it."
def fn():
pass

View File

@@ -0,0 +1,5 @@
def fn1():
pass
def fn2():
pass

View File

@@ -0,0 +1,4 @@
print("Test where the first line is a statement, and the rule violation follows it.")
def fn():
pass

View File

@@ -0,0 +1,6 @@
# Test where the first line is a comment, and the rule violation follows it.
def fn():
pass

View File

@@ -0,0 +1,6 @@
"""Test where the error is after the module's docstring."""
def fn():
pass

View File

@@ -0,0 +1,6 @@
"Test where the first line is a comment, " + "and the rule violation follows it."
def fn():
pass

View File

@@ -0,0 +1,6 @@
print("Test where the first line is a statement, and the rule violation follows it.")
def fn():
pass

View File

@@ -171,6 +171,24 @@ mod tests {
Ok(())
}
#[test_case(Rule::BlankLinesTopLevel, Path::new("E302_first_line_docstring.py"))]
#[test_case(Rule::BlankLinesTopLevel, Path::new("E302_first_line_expression.py"))]
#[test_case(Rule::BlankLinesTopLevel, Path::new("E302_first_line_function.py"))]
#[test_case(Rule::BlankLinesTopLevel, Path::new("E302_first_line_statement.py"))]
#[test_case(Rule::TooManyBlankLines, Path::new("E303_first_line_comment.py"))]
#[test_case(Rule::TooManyBlankLines, Path::new("E303_first_line_docstring.py"))]
#[test_case(Rule::TooManyBlankLines, Path::new("E303_first_line_expression.py"))]
#[test_case(Rule::TooManyBlankLines, Path::new("E303_first_line_statement.py"))]
fn blank_lines_first_line(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("pycodestyle").join(path).as_path(),
&settings::LinterSettings::for_rule(rule_code),
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::BlankLineBetweenMethods, Path::new("E30.py"))]
#[test_case(Rule::BlankLinesTopLevel, Path::new("E30.py"))]
#[test_case(Rule::TooManyBlankLines, Path::new("E30.py"))]

View File

@@ -696,9 +696,7 @@ impl<'a> BlankLinesChecker<'a> {
state.class_status.update(&logical_line);
state.fn_status.update(&logical_line);
if state.is_not_first_logical_line {
self.check_line(&logical_line, &state, prev_indent_length, diagnostics);
}
self.check_line(&logical_line, &state, prev_indent_length, diagnostics);
match logical_line.kind {
LogicalLineKind::Class => {
@@ -818,6 +816,8 @@ impl<'a> BlankLinesChecker<'a> {
&& line.kind.is_class_function_or_decorator()
// Blank lines in stub files are used to group definitions. Don't enforce blank lines.
&& !self.source_type.is_stub()
// Do not expect blank lines before the first logical line.
&& state.is_not_first_logical_line
{
// E302
let mut diagnostic = Diagnostic::new(

View File

@@ -0,0 +1,19 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E302_first_line_docstring.py:3:1: E302 [*] Expected 2 blank lines, found 1
|
1 | """Test where the error is after the module's docstring."""
2 |
3 | def fn():
| ^^^ E302
4 | pass
|
= help: Add missing blank line(s)
Safe fix
1 1 | """Test where the error is after the module's docstring."""
2 2 |
3 |+
3 4 | def fn():
4 5 | pass

View File

@@ -0,0 +1,19 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E302_first_line_expression.py:3:1: E302 [*] Expected 2 blank lines, found 1
|
1 | "Test where the first line is a comment, " + "and the rule violation follows it."
2 |
3 | def fn():
| ^^^ E302
4 | pass
|
= help: Add missing blank line(s)
Safe fix
1 1 | "Test where the first line is a comment, " + "and the rule violation follows it."
2 2 |
3 |+
3 4 | def fn():
4 5 | pass

View File

@@ -0,0 +1,20 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E302_first_line_function.py:4:1: E302 [*] Expected 2 blank lines, found 1
|
2 | pass
3 |
4 | def fn2():
| ^^^ E302
5 | pass
|
= help: Add missing blank line(s)
Safe fix
1 1 | def fn1():
2 2 | pass
3 3 |
4 |+
4 5 | def fn2():
5 6 | pass

View File

@@ -0,0 +1,19 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E302_first_line_statement.py:3:1: E302 [*] Expected 2 blank lines, found 1
|
1 | print("Test where the first line is a statement, and the rule violation follows it.")
2 |
3 | def fn():
| ^^^ E302
4 | pass
|
= help: Add missing blank line(s)
Safe fix
1 1 | print("Test where the first line is a statement, and the rule violation follows it.")
2 2 |
3 |+
3 4 | def fn():
4 5 | pass

View File

@@ -0,0 +1,18 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E303_first_line_comment.py:5:1: E303 [*] Too many blank lines (3)
|
5 | def fn():
| ^^^ E303
6 | pass
|
= help: Remove extraneous blank line(s)
Safe fix
1 1 | # Test where the first line is a comment, and the rule violation follows it.
2 2 |
3 3 |
4 |-
5 4 | def fn():
6 5 | pass

View File

@@ -0,0 +1,18 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E303_first_line_docstring.py:5:1: E303 [*] Too many blank lines (3)
|
5 | def fn():
| ^^^ E303
6 | pass
|
= help: Remove extraneous blank line(s)
Safe fix
1 1 | """Test where the error is after the module's docstring."""
2 2 |
3 3 |
4 |-
5 4 | def fn():
6 5 | pass

View File

@@ -0,0 +1,18 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E303_first_line_expression.py:5:1: E303 [*] Too many blank lines (3)
|
5 | def fn():
| ^^^ E303
6 | pass
|
= help: Remove extraneous blank line(s)
Safe fix
1 1 | "Test where the first line is a comment, " + "and the rule violation follows it."
2 2 |
3 3 |
4 |-
5 4 | def fn():
6 5 | pass

View File

@@ -0,0 +1,18 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---
E303_first_line_statement.py:5:1: E303 [*] Too many blank lines (3)
|
5 | def fn():
| ^^^ E303
6 | pass
|
= help: Remove extraneous blank line(s)
Safe fix
1 1 | print("Test where the first line is a statement, and the rule violation follows it.")
2 2 |
3 3 |
4 |-
5 4 | def fn():
6 5 | pass

View File

@@ -141,15 +141,15 @@ impl<'src, 'loc> UselessSuppressionComments<'src, 'loc> {
if comment.kind == SuppressionKind::Off || comment.kind == SuppressionKind::On {
if let Some(
AnyNodeRef::StmtClassDef(StmtClassDef {
name,
decorator_list,
..
})
name,
decorator_list,
..
})
| AnyNodeRef::StmtFunctionDef(StmtFunctionDef {
name,
decorator_list,
..
}),
name,
decorator_list,
..
}),
) = comment.enclosing
{
if comment.line_position.is_own_line() && comment.range.start() < name.start() {
@@ -196,7 +196,7 @@ impl<'src, 'loc> UselessSuppressionComments<'src, 'loc> {
self.captured.sort_by_key(|(t, _)| t.start());
}
fn ignored_comments(&self) -> impl Iterator<Item = (TextRange, IgnoredReason)> + '_ {
fn ignored_comments(&self) -> impl Iterator<Item=(TextRange, IgnoredReason)> + '_ {
self.captured.iter().map(|(r, i)| (*r, *i))
}
}
@@ -276,7 +276,8 @@ const fn is_valid_enclosing_node(node: AnyNodeRef) -> bool {
| AnyNodeRef::StmtIpyEscapeCommand(_)
| AnyNodeRef::ExceptHandlerExceptHandler(_)
| AnyNodeRef::MatchCase(_)
| AnyNodeRef::ElifElseClause(_) => true,
| AnyNodeRef::ElifElseClause(_)
| AnyNodeRef::StmtCrement(_) => true,
AnyNodeRef::ExprBoolOp(_)
| AnyNodeRef::ExprNamed(_)

View File

@@ -1543,6 +1543,9 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> {
ast::Stmt::Pass(_) => Self::Pass,
ast::Stmt::Break(_) => Self::Break,
ast::Stmt::Continue(_) => Self::Continue,
ast::Stmt::Crement(_) => {
todo!()
}
}
}
}

View File

@@ -432,6 +432,7 @@ pub fn any_over_stmt(stmt: &Stmt, func: &dyn Fn(&Expr) -> bool) -> bool {
Stmt::AugAssign(ast::StmtAugAssign { target, value, .. }) => {
any_over_expr(target, func) || any_over_expr(value, func)
}
Stmt::Crement(ast::StmtCrement { target, .. }) => any_over_expr(target, func),
Stmt::AnnAssign(ast::StmtAnnAssign {
target,
annotation,

View File

@@ -36,6 +36,7 @@ pub enum AnyNode {
StmtTypeAlias(ast::StmtTypeAlias),
StmtAssign(ast::StmtAssign),
StmtAugAssign(ast::StmtAugAssign),
StmtCrement(ast::StmtCrement),
StmtAnnAssign(ast::StmtAnnAssign),
StmtFor(ast::StmtFor),
StmtWhile(ast::StmtWhile),
@@ -130,6 +131,7 @@ impl AnyNode {
AnyNode::StmtTypeAlias(node) => Some(Stmt::TypeAlias(node)),
AnyNode::StmtAssign(node) => Some(Stmt::Assign(node)),
AnyNode::StmtAugAssign(node) => Some(Stmt::AugAssign(node)),
AnyNode::StmtCrement(node) => Some(Stmt::Crement(node)),
AnyNode::StmtAnnAssign(node) => Some(Stmt::AnnAssign(node)),
AnyNode::StmtFor(node) => Some(Stmt::For(node)),
AnyNode::StmtWhile(node) => Some(Stmt::While(node)),
@@ -262,6 +264,7 @@ impl AnyNode {
| AnyNode::StmtTypeAlias(_)
| AnyNode::StmtAssign(_)
| AnyNode::StmtAugAssign(_)
| AnyNode::StmtCrement(_)
| AnyNode::StmtAnnAssign(_)
| AnyNode::StmtFor(_)
| AnyNode::StmtWhile(_)
@@ -327,6 +330,7 @@ impl AnyNode {
| AnyNode::StmtTypeAlias(_)
| AnyNode::StmtAssign(_)
| AnyNode::StmtAugAssign(_)
| AnyNode::StmtCrement(_)
| AnyNode::StmtAnnAssign(_)
| AnyNode::StmtFor(_)
| AnyNode::StmtWhile(_)
@@ -432,6 +436,7 @@ impl AnyNode {
| AnyNode::StmtTypeAlias(_)
| AnyNode::StmtAssign(_)
| AnyNode::StmtAugAssign(_)
| AnyNode::StmtCrement(_)
| AnyNode::StmtAnnAssign(_)
| AnyNode::StmtFor(_)
| AnyNode::StmtWhile(_)
@@ -522,6 +527,7 @@ impl AnyNode {
| AnyNode::StmtTypeAlias(_)
| AnyNode::StmtAssign(_)
| AnyNode::StmtAugAssign(_)
| AnyNode::StmtCrement(_)
| AnyNode::StmtAnnAssign(_)
| AnyNode::StmtFor(_)
| AnyNode::StmtWhile(_)
@@ -637,6 +643,7 @@ impl AnyNode {
Self::StmtTypeAlias(node) => AnyNodeRef::StmtTypeAlias(node),
Self::StmtAssign(node) => AnyNodeRef::StmtAssign(node),
Self::StmtAugAssign(node) => AnyNodeRef::StmtAugAssign(node),
Self::StmtCrement(node) => AnyNodeRef::StmtCrement(node),
Self::StmtAnnAssign(node) => AnyNodeRef::StmtAnnAssign(node),
Self::StmtFor(node) => AnyNodeRef::StmtFor(node),
Self::StmtWhile(node) => AnyNodeRef::StmtWhile(node),
@@ -1125,6 +1132,48 @@ impl AstNode for ast::StmtAugAssign {
visitor.visit_expr(value);
}
}
impl AstNode for ast::StmtCrement {
fn cast(kind: AnyNode) -> Option<Self>
where
Self: Sized,
{
if let AnyNode::StmtCrement(node) = kind {
Some(node)
} else {
None
}
}
fn cast_ref(kind: AnyNodeRef) -> Option<&Self> {
if let AnyNodeRef::StmtCrement(node) = kind {
Some(node)
} else {
None
}
}
fn as_any_node_ref(&self) -> AnyNodeRef {
AnyNodeRef::from(self)
}
fn into_any_node(self) -> AnyNode {
AnyNode::from(self)
}
fn visit_preorder<'a, V>(&'a self, visitor: &mut V)
where
V: PreorderVisitor<'a> + ?Sized,
{
let ast::StmtCrement {
target,
op: _,
range: _,
} = self;
visitor.visit_expr(target);
// TODO(konstin): visitor.visit_operator(op);
}
}
impl AstNode for ast::StmtAnnAssign {
fn cast(kind: AnyNode) -> Option<Self>
where
@@ -4538,6 +4587,7 @@ impl From<Stmt> for AnyNode {
Stmt::Break(node) => AnyNode::StmtBreak(node),
Stmt::Continue(node) => AnyNode::StmtContinue(node),
Stmt::IpyEscapeCommand(node) => AnyNode::StmtIpyEscapeCommand(node),
Stmt::Crement(node) => AnyNode::StmtCrement(node),
}
}
}
@@ -4676,6 +4726,12 @@ impl From<ast::StmtAugAssign> for AnyNode {
}
}
impl From<ast::StmtCrement> for AnyNode {
fn from(node: ast::StmtCrement) -> Self {
AnyNode::StmtCrement(node)
}
}
impl From<ast::StmtAnnAssign> for AnyNode {
fn from(node: ast::StmtAnnAssign) -> Self {
AnyNode::StmtAnnAssign(node)
@@ -5251,6 +5307,7 @@ impl Ranged for AnyNode {
AnyNode::StringLiteral(node) => node.range(),
AnyNode::BytesLiteral(node) => node.range(),
AnyNode::ElifElseClause(node) => node.range(),
AnyNode::StmtCrement(node) => node.range(),
}
}
}
@@ -5266,6 +5323,7 @@ pub enum AnyNodeRef<'a> {
StmtTypeAlias(&'a ast::StmtTypeAlias),
StmtAssign(&'a ast::StmtAssign),
StmtAugAssign(&'a ast::StmtAugAssign),
StmtCrement(&'a ast::StmtCrement),
StmtAnnAssign(&'a ast::StmtAnnAssign),
StmtFor(&'a ast::StmtFor),
StmtWhile(&'a ast::StmtWhile),
@@ -5444,6 +5502,7 @@ impl<'a> AnyNodeRef<'a> {
AnyNodeRef::StringLiteral(node) => NonNull::from(*node).cast(),
AnyNodeRef::BytesLiteral(node) => NonNull::from(*node).cast(),
AnyNodeRef::ElifElseClause(node) => NonNull::from(*node).cast(),
AnyNodeRef::StmtCrement(node) => NonNull::from(*node).cast(),
}
}
@@ -5546,6 +5605,7 @@ impl<'a> AnyNodeRef<'a> {
AnyNodeRef::StringLiteral(_) => NodeKind::StringLiteral,
AnyNodeRef::BytesLiteral(_) => NodeKind::BytesLiteral,
AnyNodeRef::ElifElseClause(_) => NodeKind::ElifElseClause,
AnyNodeRef::StmtCrement(_) => NodeKind::StmtCrement,
}
}
@@ -5558,6 +5618,7 @@ impl<'a> AnyNodeRef<'a> {
| AnyNodeRef::StmtTypeAlias(_)
| AnyNodeRef::StmtAssign(_)
| AnyNodeRef::StmtAugAssign(_)
| AnyNodeRef::StmtCrement(_)
| AnyNodeRef::StmtAnnAssign(_)
| AnyNodeRef::StmtFor(_)
| AnyNodeRef::StmtWhile(_)
@@ -5690,6 +5751,7 @@ impl<'a> AnyNodeRef<'a> {
| AnyNodeRef::StmtTypeAlias(_)
| AnyNodeRef::StmtAssign(_)
| AnyNodeRef::StmtAugAssign(_)
| AnyNodeRef::StmtCrement(_)
| AnyNodeRef::StmtAnnAssign(_)
| AnyNodeRef::StmtFor(_)
| AnyNodeRef::StmtWhile(_)
@@ -5754,6 +5816,7 @@ impl<'a> AnyNodeRef<'a> {
| AnyNodeRef::StmtTypeAlias(_)
| AnyNodeRef::StmtAssign(_)
| AnyNodeRef::StmtAugAssign(_)
| AnyNodeRef::StmtCrement(_)
| AnyNodeRef::StmtAnnAssign(_)
| AnyNodeRef::StmtFor(_)
| AnyNodeRef::StmtWhile(_)
@@ -5859,6 +5922,7 @@ impl<'a> AnyNodeRef<'a> {
| AnyNodeRef::StmtTypeAlias(_)
| AnyNodeRef::StmtAssign(_)
| AnyNodeRef::StmtAugAssign(_)
| AnyNodeRef::StmtCrement(_)
| AnyNodeRef::StmtAnnAssign(_)
| AnyNodeRef::StmtFor(_)
| AnyNodeRef::StmtWhile(_)
@@ -5949,6 +6013,7 @@ impl<'a> AnyNodeRef<'a> {
| AnyNodeRef::StmtTypeAlias(_)
| AnyNodeRef::StmtAssign(_)
| AnyNodeRef::StmtAugAssign(_)
| AnyNodeRef::StmtCrement(_)
| AnyNodeRef::StmtAnnAssign(_)
| AnyNodeRef::StmtFor(_)
| AnyNodeRef::StmtWhile(_)
@@ -6058,6 +6123,7 @@ impl<'a> AnyNodeRef<'a> {
AnyNodeRef::StmtTypeAlias(node) => node.visit_preorder(visitor),
AnyNodeRef::StmtAssign(node) => node.visit_preorder(visitor),
AnyNodeRef::StmtAugAssign(node) => node.visit_preorder(visitor),
AnyNodeRef::StmtCrement(node) => node.visit_preorder(visitor),
AnyNodeRef::StmtAnnAssign(node) => node.visit_preorder(visitor),
AnyNodeRef::StmtFor(node) => node.visit_preorder(visitor),
AnyNodeRef::StmtWhile(node) => node.visit_preorder(visitor),
@@ -6372,6 +6438,12 @@ impl<'a> From<&'a ast::StmtAugAssign> for AnyNodeRef<'a> {
}
}
impl<'a> From<&'a ast::StmtCrement> for AnyNodeRef<'a> {
fn from(node: &'a ast::StmtCrement) -> Self {
AnyNodeRef::StmtCrement(node)
}
}
impl<'a> From<&'a ast::StmtAnnAssign> for AnyNodeRef<'a> {
fn from(node: &'a ast::StmtAnnAssign) -> Self {
AnyNodeRef::StmtAnnAssign(node)
@@ -6837,6 +6909,7 @@ impl<'a> From<&'a Stmt> for AnyNodeRef<'a> {
Stmt::Break(node) => AnyNodeRef::StmtBreak(node),
Stmt::Continue(node) => AnyNodeRef::StmtContinue(node),
Stmt::IpyEscapeCommand(node) => AnyNodeRef::StmtIpyEscapeCommand(node),
Stmt::Crement(node) => AnyNodeRef::StmtCrement(node),
}
}
}
@@ -7073,6 +7146,7 @@ impl Ranged for AnyNodeRef<'_> {
AnyNodeRef::FString(node) => node.range(),
AnyNodeRef::StringLiteral(node) => node.range(),
AnyNodeRef::BytesLiteral(node) => node.range(),
AnyNodeRef::StmtCrement(node) => node.range(),
}
}
}
@@ -7173,4 +7247,5 @@ pub enum NodeKind {
FString,
StringLiteral,
BytesLiteral,
StmtCrement,
}

View File

@@ -101,6 +101,8 @@ pub enum Stmt {
// Jupyter notebook specific
#[is(name = "ipy_escape_command_stmt")]
IpyEscapeCommand(StmtIpyEscapeCommand),
#[is(name = "crement_stmt")]
Crement(StmtCrement),
}
/// An AST node used to represent a IPython escape command at the statement level.
@@ -297,6 +299,26 @@ impl From<StmtAugAssign> for Stmt {
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum CrementKind {
Increment,
Decrement,
}
/// `++` or `--`
#[derive(Clone, Debug, PartialEq)]
pub struct StmtCrement {
pub range: TextRange,
pub target: Box<Expr>,
pub op: CrementKind,
}
impl From<StmtCrement> for Stmt {
fn from(payload: StmtCrement) -> Self {
Stmt::Crement(payload)
}
}
/// See also [AnnAssign](https://docs.python.org/3/library/ast.html#ast.AnnAssign)
#[derive(Clone, Debug, PartialEq)]
pub struct StmtAnnAssign {
@@ -3693,6 +3715,11 @@ impl Ranged for crate::nodes::StmtAugAssign {
self.range
}
}
impl Ranged for crate::nodes::StmtCrement {
fn range(&self) -> TextRange {
self.range
}
}
impl Ranged for crate::nodes::StmtAnnAssign {
fn range(&self) -> TextRange {
self.range
@@ -3816,6 +3843,7 @@ impl Ranged for crate::Stmt {
Self::Break(node) => node.range(),
Self::Continue(node) => node.range(),
Stmt::IpyEscapeCommand(node) => node.range(),
Stmt::Crement(node) => node.range(),
}
}
}

View File

@@ -203,6 +203,14 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
visitor.visit_operator(op);
visitor.visit_expr(target);
}
Stmt::Crement(ast::StmtCrement {
target,
op: _,
range: _,
}) => {
// TODO(konstin): visitor.visit_operator(op);
visitor.visit_expr(target);
}
Stmt::AnnAssign(ast::StmtAnnAssign {
target,
annotation,

View File

@@ -209,6 +209,7 @@ where
Stmt::TypeAlias(stmt) => stmt.visit_preorder(visitor),
Stmt::Assign(stmt) => stmt.visit_preorder(visitor),
Stmt::AugAssign(stmt) => stmt.visit_preorder(visitor),
Stmt::Crement(stmt) => stmt.visit_preorder(visitor),
Stmt::AnnAssign(stmt) => stmt.visit_preorder(visitor),
Stmt::For(stmt) => stmt.visit_preorder(visitor),
Stmt::While(stmt) => stmt.visit_preorder(visitor),

View File

@@ -306,6 +306,7 @@ pub fn walk_stmt<V: Transformer + ?Sized>(visitor: &V, stmt: &mut Stmt) {
Stmt::Nonlocal(_) => {}
Stmt::Expr(ast::StmtExpr { value, range: _ }) => visitor.visit_expr(value),
Stmt::Pass(_) | Stmt::Break(_) | Stmt::Continue(_) | Stmt::IpyEscapeCommand(_) => {}
Stmt::Crement(_) => {}
}
}

View File

@@ -4,10 +4,10 @@ use std::ops::Deref;
use ruff_python_ast::str::Quote;
use ruff_python_ast::{
self as ast, Alias, ArgOrKeyword, BoolOp, CmpOp, Comprehension, ConversionFlag, DebugText,
ExceptHandler, Expr, Identifier, MatchCase, Operator, Parameter, Parameters, Pattern,
Singleton, Stmt, Suite, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple,
WithItem,
self as ast, Alias, ArgOrKeyword, BoolOp, CmpOp, Comprehension, ConversionFlag, CrementKind,
DebugText, ExceptHandler, Expr, Identifier, MatchCase, Operator, Parameter, Parameters,
Pattern, Singleton, Stmt, Suite, TypeParam, TypeParamParamSpec, TypeParamTypeVar,
TypeParamTypeVarTuple, WithItem,
};
use ruff_python_ast::{ParameterWithDefault, TypeParams};
use ruff_python_literal::escape::{AsciiEscape, Escape, UnicodeEscape};
@@ -345,6 +345,20 @@ impl<'a> Generator<'a> {
self.unparse_expr(value, precedence::AUG_ASSIGN);
});
}
Stmt::Crement(ast::StmtCrement {
target,
op,
range: _,
}) => {
statement!({
self.unparse_expr(target, precedence::AUG_ASSIGN);
self.p(" ");
self.p(match op {
CrementKind::Increment => "+= 1",
CrementKind::Decrement => "-= 1",
});
});
}
Stmt::AnnAssign(ast::StmtAnnAssign {
target,
annotation,

View File

@@ -706,7 +706,8 @@ impl Format<PyFormatContext<'_>> for FormatEnclosingNode<'_> {
| AnyNodeRef::TypeParamTypeVar(_)
| AnyNodeRef::TypeParamTypeVarTuple(_)
| AnyNodeRef::TypeParamParamSpec(_)
| AnyNodeRef::BytesLiteral(_) => {
| AnyNodeRef::BytesLiteral(_)
| AnyNodeRef::StmtCrement(_) => {
panic!("Range formatting only supports formatting logical lines")
}
}

View File

@@ -45,6 +45,19 @@ impl FormatRule<Stmt, PyFormatContext<'_>> for FormatStmt {
Stmt::Delete(x) => x.format().fmt(f),
Stmt::Assign(x) => x.format().fmt(f),
Stmt::AugAssign(x) => x.format().fmt(f),
Stmt::Crement(x) => {
x.target.format().fmt(f)?;
use ruff_formatter::prelude::*;
match x.op {
ruff_python_ast::CrementKind::Increment => {
ruff_formatter::write!(f, [space(), text("+="), space(), text("1")])?;
}
ruff_python_ast::CrementKind::Decrement => {
ruff_formatter::write!(f, [space(), text("-="), space(), text("1")])?;
}
}
Ok(())
}
Stmt::AnnAssign(x) => x.format().fmt(f),
Stmt::For(x) => x.format().fmt(f),
Stmt::While(x) => x.format().fmt(f),

View File

@@ -1079,7 +1079,9 @@ impl<'source> Lexer<'source> {
}
}
'+' => {
if self.cursor.eat_char('=') {
if self.cursor.eat_char('+') {
Tok::Increment
} else if self.cursor.eat_char('=') {
Tok::PlusEqual
} else {
Tok::Plus
@@ -1166,7 +1168,9 @@ impl<'source> Lexer<'source> {
}
}
'-' => {
if self.cursor.eat_char('=') {
if self.cursor.eat_char('-') {
Tok::Increment
} else if self.cursor.eat_char('=') {
Tok::MinusEqual
} else if self.cursor.eat_char('>') {
Tok::Rarrow

View File

@@ -110,6 +110,16 @@ DelStatement: ast::Stmt = {
};
ExpressionStatement: ast::Stmt = {
<location:@L> <target:TestOrStarExprList> <op:Crement> <end_location:@R> =>? {
invalid::assignment_target(&target.expr)?;
Ok(ast::Stmt::Crement(
ast::StmtCrement {
target: Box::new(set_context(target.into(), ast::ExprContext::Store)),
op,
range: (location..end_location).into()
},
))
},
<location:@L> <expression:TestOrStarExprList> <suffix:AssignSuffix*> <end_location:@R> =>? {
// Just an expression, no assignment:
if suffix.is_empty() {
@@ -204,6 +214,11 @@ AugAssign: ast::Operator = {
"//=" => ast::Operator::FloorDiv,
};
Crement: ast::CrementKind = {
"++" => ast::CrementKind::Increment,
"--" => ast::CrementKind::Decrement,
};
FlowStatement: ast::Stmt = {
<location:@L> "break" <end_location:@R> => {
@@ -2033,6 +2048,8 @@ extern {
">" => token::Tok::Greater,
">=" => token::Tok::GreaterEqual,
"->" => token::Tok::Rarrow,
"++" => token::Tok::Increment,
"--" => token::Tok::Decrement,
"and" => token::Tok::And,
"as" => token::Tok::As,
"assert" => token::Tok::Assert,

File diff suppressed because it is too large Load Diff

View File

@@ -157,6 +157,10 @@ pub enum Tok {
VbarEqual,
/// Token value for caret equal `^=`.
CircumflexEqual,
/// Token value for `++`.
Increment,
/// Token value for `--`.
Decrement,
/// Token value for left shift equal `<<=`.
LeftShiftEqual,
/// Token value for right shift equal `>>=`.
@@ -343,6 +347,8 @@ impl fmt::Display for Tok {
With => f.write_str("'with'"),
Yield => f.write_str("'yield'"),
ColonEqual => f.write_str("':='"),
Increment => f.write_str("++"),
Decrement => f.write_str("--"),
}
}
}
@@ -526,6 +532,8 @@ pub enum TokenKind {
StartModule,
StartInteractive,
StartExpression,
Increment,
Decrement,
}
impl TokenKind {
@@ -620,6 +628,8 @@ impl TokenKind {
| TokenKind::AmperEqual
| TokenKind::VbarEqual
| TokenKind::CircumflexEqual
| TokenKind::Increment
| TokenKind::Decrement
| TokenKind::LeftShiftEqual
| TokenKind::RightShiftEqual
| TokenKind::DoubleStarEqual
@@ -799,6 +809,8 @@ impl TokenKind {
Tok::Yield => TokenKind::Yield,
Tok::StartModule => TokenKind::StartModule,
Tok::StartExpression => TokenKind::StartExpression,
Tok::Increment => TokenKind::Increment,
Tok::Decrement => TokenKind::Decrement,
}
}
}