diff --git a/.gitattributes b/.gitattributes index ebc35c6745458..966aa80ecf018 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12,6 +12,12 @@ crates/ruff_python_parser/resources/invalid/re_lexing/line_continuation_windows_ crates/ruff_python_parser/resources/invalid/re_lex_logical_token_windows_eol.py text eol=crlf crates/ruff_python_parser/resources/invalid/re_lex_logical_token_mac_eol.py text eol=cr +crates/ruff_linter/resources/test/fixtures/ruff/RUF046_CR.py text eol=cr +crates/ruff_linter/resources/test/fixtures/ruff/RUF046_LF.py text eol=lf + +crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_CR.py text eol=cr +crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_LF.py text eol=lf + crates/ruff_python_parser/resources/inline linguist-generated=true ruff.schema.json -diff linguist-generated=true text=auto eol=lf diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/.editorconfig b/crates/ruff_linter/resources/test/fixtures/pyupgrade/.editorconfig new file mode 100644 index 0000000000000..048d4deec14e7 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/.editorconfig @@ -0,0 +1,5 @@ +# These rules test for reformatting with specific line endings. +# Using a formatter fixes that and breaks the tests +[UP018_{CR,LF}.py] +generated_code = true +ij_formatter_enabled = false diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_CR.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_CR.py new file mode 100644 index 0000000000000..7535093fa1487 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_CR.py @@ -0,0 +1 @@ +# Keep parenthesis around preserved CR int(- 1) int(+ 1) diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_LF.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_LF.py new file mode 100644 index 0000000000000..3a2d43335e505 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_LF.py @@ -0,0 +1,7 @@ +# Keep parentheses around preserved \n + +int(- + 1) + +int(+ + 1) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/.editorconfig b/crates/ruff_linter/resources/test/fixtures/ruff/.editorconfig new file mode 100644 index 0000000000000..bf4811bd1a7a2 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/.editorconfig @@ -0,0 +1,5 @@ +# These rules test for reformatting with specific line endings. +# Using a formatter fixes that and breaks the tests +[RUF046_{CR,LF}.py] +generated_code = true +ij_formatter_enabled = false diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF046_CR.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF046_CR.py new file mode 100644 index 0000000000000..9723c347dcf0c --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF046_CR.py @@ -0,0 +1 @@ +int(- 1) # Carriage return as newline \ No newline at end of file diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF046_LF.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF046_LF.py new file mode 100644 index 0000000000000..53a879a56e714 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF046_LF.py @@ -0,0 +1,3 @@ +# \n as newline +int(- + 1) diff --git a/crates/ruff_linter/src/rules/pyupgrade/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/mod.rs index 822e680acfceb..6a7003cabc1ab 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/mod.rs @@ -36,6 +36,8 @@ mod tests { #[test_case(Rule::LRUCacheWithMaxsizeNone, Path::new("UP033_1.py"))] #[test_case(Rule::LRUCacheWithoutParameters, Path::new("UP011.py"))] #[test_case(Rule::NativeLiterals, Path::new("UP018.py"))] + #[test_case(Rule::NativeLiterals, Path::new("UP018_CR.py"))] + #[test_case(Rule::NativeLiterals, Path::new("UP018_LF.py"))] #[test_case(Rule::NonPEP585Annotation, Path::new("UP006_0.py"))] #[test_case(Rule::NonPEP585Annotation, Path::new("UP006_1.py"))] #[test_case(Rule::NonPEP585Annotation, Path::new("UP006_2.py"))] diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs index a1d8bf31deb37..14e31351ca614 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs @@ -4,6 +4,7 @@ use std::str::FromStr; use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::{self as ast, Expr, Int, LiteralExpressionRef, OperatorPrecedence, UnaryOp}; +use ruff_source_file::find_newline; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; @@ -244,6 +245,9 @@ pub(crate) fn native_literals( let arg_code = checker.locator().slice(arg); let content = match (parent_expr, literal_type, has_unary_op) { + // Expressions including newlines must be parenthesised to be valid syntax + (_, _, true) if find_newline(arg_code).is_some() => format!("({arg_code})"), + // Attribute access on an integer requires the integer to be parenthesized to disambiguate from a float // Ex) `(7).denominator` is valid but `7.denominator` is not // Note that floats do not have this problem diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP018_CR.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP018_CR.py.snap new file mode 100644 index 0000000000000..6dae74bfe53b6 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP018_CR.py.snap @@ -0,0 +1,22 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +UP018_CR.py:2:1: UP018 [*] Unnecessary `int` call (rewrite as a literal) + | +1 | # Keep parenthesis around preserved CR int(- 1) int(+ 1) + | ^^^^^^^^^^^ UP018 + | + = help: Replace with integer literal + +ℹ Safe fix +1 1 | # Keep parenthesis around preserved CR 2 |-int(- 2 |+(- 3 3 | 1) 4 4 | int(+ 5 5 | 1) + +UP018_CR.py:4:1: UP018 [*] Unnecessary `int` call (rewrite as a literal) + | +2 | int(- 1) int(+ 1) + | ^^^^^^^^^^^ UP018 + | + = help: Replace with integer literal + +ℹ Safe fix +1 1 | # Keep parenthesis around preserved CR 2 2 | int(- 3 3 | 1) 4 |-int(+ 4 |+(+ 5 5 | 1) diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP018_LF.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP018_LF.py.snap new file mode 100644 index 0000000000000..d3a530d273c71 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP018_LF.py.snap @@ -0,0 +1,41 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +UP018_LF.py:3:1: UP018 [*] Unnecessary `int` call (rewrite as a literal) + | +1 | # Keep parentheses around preserved \n +2 | +3 | / int(- +4 | | 1) + | |______^ UP018 +5 | +6 | int(+ + | + = help: Replace with integer literal + +ℹ Safe fix +1 1 | # Keep parentheses around preserved \n +2 2 | +3 |-int(- + 3 |+(- +4 4 | 1) +5 5 | +6 6 | int(+ + +UP018_LF.py:6:1: UP018 [*] Unnecessary `int` call (rewrite as a literal) + | +4 | 1) +5 | +6 | / int(+ +7 | | 1) + | |______^ UP018 + | + = help: Replace with integer literal + +ℹ Safe fix +3 3 | int(- +4 4 | 1) +5 5 | +6 |-int(+ + 6 |+(+ +7 7 | 1) diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 75c59307957f8..b5006db999719 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -84,6 +84,8 @@ mod tests { #[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.py"))] #[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.pyi"))] #[test_case(Rule::UnnecessaryCastToInt, Path::new("RUF046.py"))] + #[test_case(Rule::UnnecessaryCastToInt, Path::new("RUF046_CR.py"))] + #[test_case(Rule::UnnecessaryCastToInt, Path::new("RUF046_LF.py"))] #[test_case(Rule::NeedlessElse, Path::new("RUF047_if.py"))] #[test_case(Rule::NeedlessElse, Path::new("RUF047_for.py"))] #[test_case(Rule::NeedlessElse, Path::new("RUF047_while.py"))] diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF046_RUF046_CR.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF046_RUF046_CR.py.snap new file mode 100644 index 0000000000000..61b9115357d0a --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF046_RUF046_CR.py.snap @@ -0,0 +1,12 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +--- +RUF046_CR.py:1:1: RUF046 [*] Value being cast to `int` is already an integer + | +1 | int(- 1) # Carriage return as newline + | ^^^^^^^^^^^ RUF046 + | + = help: Remove unnecessary `int` call + +ℹ Safe fix +1 |-int(- 1 |+(- 2 2 | 1) # Carriage return as newline diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF046_RUF046_LF.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF046_RUF046_LF.py.snap new file mode 100644 index 0000000000000..97d44c191d4df --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF046_RUF046_LF.py.snap @@ -0,0 +1,17 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +--- +RUF046_LF.py:2:1: RUF046 [*] Value being cast to `int` is already an integer + | +1 | # \n as newline +2 | / int(- +3 | | 1) + | |______^ RUF046 + | + = help: Remove unnecessary `int` call + +ℹ Safe fix +1 1 | # \n as newline +2 |-int(- + 2 |+(- +3 3 | 1)