From 25849a452fcb4809116d1505e5322b55bd12d6e3 Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Thu, 30 Nov 2023 19:29:40 +0100 Subject: [PATCH 1/7] Add autofix for PLW1514 --- .../pylint/rules/unspecified_encoding.rs | 35 ++++++- ...ests__PLW1514_unspecified_encoding.py.snap | 91 +++++++++++++++++-- 2 files changed, 114 insertions(+), 12 deletions(-) diff --git a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs index 0559919d8e905..a051c6c82094b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs @@ -1,10 +1,12 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; use ruff_python_ast::call_path::{format_call_path, CallPath}; -use ruff_text_size::Ranged; +use ruff_python_ast::imports::{AnyImport, Import}; +use ruff_text_size::{Ranged, TextSize}; use crate::checkers::ast::Checker; +use crate::settings::types::PythonVersion; /// ## What it does /// Checks for uses of `open` and related calls without an explicit `encoding` @@ -35,7 +37,7 @@ pub struct UnspecifiedEncoding { mode: Mode, } -impl Violation for UnspecifiedEncoding { +impl AlwaysFixableViolation for UnspecifiedEncoding { #[derive_message_formats] fn message(&self) -> String { let UnspecifiedEncoding { @@ -52,6 +54,10 @@ impl Violation for UnspecifiedEncoding { } } } + + fn fix_title(&self) -> String { + format!("Add explicit `encoding` argument") + } } /// PLW1514 @@ -70,13 +76,32 @@ pub(crate) fn unspecified_encoding(checker: &mut Checker, call: &ast::ExprCall) return; }; - checker.diagnostics.push(Diagnostic::new( + let mut diagnostic = Diagnostic::new( UnspecifiedEncoding { function_name, mode, }, call.func.range(), - )); + ); + + if checker.settings.target_version >= PythonVersion::Py310 { + diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion( + ", encoding=\"locale\"".to_string(), + call.range().end() - TextSize::from(1), + ))) + } else { + let import_edit = checker.importer().add_import( + &AnyImport::Import(Import::module("locale")), + TextSize::default(), + ); + let call_edit = Edit::insertion( + ", encoding=locale.getpreferredencoding(False)".to_string(), + call.range().end() - TextSize::from(1), + ); + diagnostic.set_fix(Fix::unsafe_edits(import_edit, [call_edit])); + } + + checker.diagnostics.push(diagnostic); } /// Returns `true` if the given expression is a string literal containing a `b` character. diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap index d10f3d3b633fd..2f67f08b691f2 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pylint/mod.rs --- -unspecified_encoding.py:8:1: PLW1514 `open` in text mode without explicit `encoding` argument +unspecified_encoding.py:8:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument | 7 | # Errors. 8 | open("test.txt") @@ -9,8 +9,19 @@ unspecified_encoding.py:8:1: PLW1514 `open` in text mode without explicit `encod 9 | io.TextIOWrapper(io.FileIO("test.txt")) 10 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) | + = help: Add explicit `encoding` argument -unspecified_encoding.py:9:1: PLW1514 `io.TextIOWrapper` without explicit `encoding` argument +ℹ Unsafe fix +5 5 | import codecs +6 6 | +7 7 | # Errors. +8 |-open("test.txt") + 8 |+open("test.txt", encoding="locale") +9 9 | io.TextIOWrapper(io.FileIO("test.txt")) +10 10 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) +11 11 | tempfile.NamedTemporaryFile("w") + +unspecified_encoding.py:9:1: PLW1514 [*] `io.TextIOWrapper` without explicit `encoding` argument | 7 | # Errors. 8 | open("test.txt") @@ -19,8 +30,19 @@ unspecified_encoding.py:9:1: PLW1514 `io.TextIOWrapper` without explicit `encodi 10 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) 11 | tempfile.NamedTemporaryFile("w") | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +6 6 | +7 7 | # Errors. +8 8 | open("test.txt") +9 |-io.TextIOWrapper(io.FileIO("test.txt")) + 9 |+io.TextIOWrapper(io.FileIO("test.txt"), encoding="locale") +10 10 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) +11 11 | tempfile.NamedTemporaryFile("w") +12 12 | tempfile.TemporaryFile("w") -unspecified_encoding.py:10:1: PLW1514 `io.TextIOWrapper` without explicit `encoding` argument +unspecified_encoding.py:10:1: PLW1514 [*] `io.TextIOWrapper` without explicit `encoding` argument | 8 | open("test.txt") 9 | io.TextIOWrapper(io.FileIO("test.txt")) @@ -29,8 +51,19 @@ unspecified_encoding.py:10:1: PLW1514 `io.TextIOWrapper` without explicit `encod 11 | tempfile.NamedTemporaryFile("w") 12 | tempfile.TemporaryFile("w") | + = help: Add explicit `encoding` argument -unspecified_encoding.py:11:1: PLW1514 `tempfile.NamedTemporaryFile` in text mode without explicit `encoding` argument +ℹ Unsafe fix +7 7 | # Errors. +8 8 | open("test.txt") +9 9 | io.TextIOWrapper(io.FileIO("test.txt")) +10 |-hugo.TextIOWrapper(hugo.FileIO("test.txt")) + 10 |+hugo.TextIOWrapper(hugo.FileIO("test.txt"), encoding="locale") +11 11 | tempfile.NamedTemporaryFile("w") +12 12 | tempfile.TemporaryFile("w") +13 13 | codecs.open("test.txt") + +unspecified_encoding.py:11:1: PLW1514 [*] `tempfile.NamedTemporaryFile` in text mode without explicit `encoding` argument | 9 | io.TextIOWrapper(io.FileIO("test.txt")) 10 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) @@ -39,8 +72,19 @@ unspecified_encoding.py:11:1: PLW1514 `tempfile.NamedTemporaryFile` in text mode 12 | tempfile.TemporaryFile("w") 13 | codecs.open("test.txt") | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +8 8 | open("test.txt") +9 9 | io.TextIOWrapper(io.FileIO("test.txt")) +10 10 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) +11 |-tempfile.NamedTemporaryFile("w") + 11 |+tempfile.NamedTemporaryFile("w", encoding="locale") +12 12 | tempfile.TemporaryFile("w") +13 13 | codecs.open("test.txt") +14 14 | tempfile.SpooledTemporaryFile(0, "w") -unspecified_encoding.py:12:1: PLW1514 `tempfile.TemporaryFile` in text mode without explicit `encoding` argument +unspecified_encoding.py:12:1: PLW1514 [*] `tempfile.TemporaryFile` in text mode without explicit `encoding` argument | 10 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) 11 | tempfile.NamedTemporaryFile("w") @@ -49,8 +93,19 @@ unspecified_encoding.py:12:1: PLW1514 `tempfile.TemporaryFile` in text mode with 13 | codecs.open("test.txt") 14 | tempfile.SpooledTemporaryFile(0, "w") | + = help: Add explicit `encoding` argument -unspecified_encoding.py:13:1: PLW1514 `codecs.open` in text mode without explicit `encoding` argument +ℹ Unsafe fix +9 9 | io.TextIOWrapper(io.FileIO("test.txt")) +10 10 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) +11 11 | tempfile.NamedTemporaryFile("w") +12 |-tempfile.TemporaryFile("w") + 12 |+tempfile.TemporaryFile("w", encoding="locale") +13 13 | codecs.open("test.txt") +14 14 | tempfile.SpooledTemporaryFile(0, "w") +15 15 | + +unspecified_encoding.py:13:1: PLW1514 [*] `codecs.open` in text mode without explicit `encoding` argument | 11 | tempfile.NamedTemporaryFile("w") 12 | tempfile.TemporaryFile("w") @@ -58,8 +113,19 @@ unspecified_encoding.py:13:1: PLW1514 `codecs.open` in text mode without explici | ^^^^^^^^^^^ PLW1514 14 | tempfile.SpooledTemporaryFile(0, "w") | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +10 10 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) +11 11 | tempfile.NamedTemporaryFile("w") +12 12 | tempfile.TemporaryFile("w") +13 |-codecs.open("test.txt") + 13 |+codecs.open("test.txt", encoding="locale") +14 14 | tempfile.SpooledTemporaryFile(0, "w") +15 15 | +16 16 | # Non-errors. -unspecified_encoding.py:14:1: PLW1514 `tempfile.SpooledTemporaryFile` in text mode without explicit `encoding` argument +unspecified_encoding.py:14:1: PLW1514 [*] `tempfile.SpooledTemporaryFile` in text mode without explicit `encoding` argument | 12 | tempfile.TemporaryFile("w") 13 | codecs.open("test.txt") @@ -68,5 +134,16 @@ unspecified_encoding.py:14:1: PLW1514 `tempfile.SpooledTemporaryFile` in text mo 15 | 16 | # Non-errors. | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +11 11 | tempfile.NamedTemporaryFile("w") +12 12 | tempfile.TemporaryFile("w") +13 13 | codecs.open("test.txt") +14 |-tempfile.SpooledTemporaryFile(0, "w") + 14 |+tempfile.SpooledTemporaryFile(0, "w", encoding="locale") +15 15 | +16 16 | # Non-errors. +17 17 | open("test.txt", encoding="utf-8") From d4d0b49250b5ab8202feef8b4b3e3122e3fe293b Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Thu, 30 Nov 2023 19:38:26 +0100 Subject: [PATCH 2/7] Add test and fixture for PY39 or lower case --- crates/ruff_linter/src/rules/pylint/mod.rs | 11 ++ ...nspecified_encoding_python39_or_lower.snap | 184 ++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap diff --git a/crates/ruff_linter/src/rules/pylint/mod.rs b/crates/ruff_linter/src/rules/pylint/mod.rs index e2f272619b2ce..37c5323a324f4 100644 --- a/crates/ruff_linter/src/rules/pylint/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/mod.rs @@ -318,4 +318,15 @@ mod tests { assert_messages!(diagnostics); Ok(()) } + + #[test] + fn unspecified_encoding_python39_or_lower() -> Result<()> { + let diagnostics = test_path( + Path::new("pylint/unspecified_encoding.py"), + &LinterSettings::for_rule(Rule::UnspecifiedEncoding) + .with_target_version(PythonVersion::Py39), + )?; + assert_messages!(diagnostics); + Ok(()) + } } diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap new file mode 100644 index 0000000000000..e02d91ec53108 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap @@ -0,0 +1,184 @@ +--- +source: crates/ruff_linter/src/rules/pylint/mod.rs +--- +unspecified_encoding.py:8:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | + 7 | # Errors. + 8 | open("test.txt") + | ^^^^ PLW1514 + 9 | io.TextIOWrapper(io.FileIO("test.txt")) +10 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix + 1 |+import locale +1 2 | import io +2 3 | import sys +3 4 | import tempfile +-------------------------------------------------------------------------------- +5 6 | import codecs +6 7 | +7 8 | # Errors. +8 |-open("test.txt") + 9 |+open("test.txt", encoding=locale.getpreferredencoding(False)) +9 10 | io.TextIOWrapper(io.FileIO("test.txt")) +10 11 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) +11 12 | tempfile.NamedTemporaryFile("w") + +unspecified_encoding.py:9:1: PLW1514 [*] `io.TextIOWrapper` without explicit `encoding` argument + | + 7 | # Errors. + 8 | open("test.txt") + 9 | io.TextIOWrapper(io.FileIO("test.txt")) + | ^^^^^^^^^^^^^^^^ PLW1514 +10 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) +11 | tempfile.NamedTemporaryFile("w") + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix + 1 |+import locale +1 2 | import io +2 3 | import sys +3 4 | import tempfile +-------------------------------------------------------------------------------- +6 7 | +7 8 | # Errors. +8 9 | open("test.txt") +9 |-io.TextIOWrapper(io.FileIO("test.txt")) + 10 |+io.TextIOWrapper(io.FileIO("test.txt"), encoding=locale.getpreferredencoding(False)) +10 11 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) +11 12 | tempfile.NamedTemporaryFile("w") +12 13 | tempfile.TemporaryFile("w") + +unspecified_encoding.py:10:1: PLW1514 [*] `io.TextIOWrapper` without explicit `encoding` argument + | + 8 | open("test.txt") + 9 | io.TextIOWrapper(io.FileIO("test.txt")) +10 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) + | ^^^^^^^^^^^^^^^^^^ PLW1514 +11 | tempfile.NamedTemporaryFile("w") +12 | tempfile.TemporaryFile("w") + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix + 1 |+import locale +1 2 | import io +2 3 | import sys +3 4 | import tempfile +-------------------------------------------------------------------------------- +7 8 | # Errors. +8 9 | open("test.txt") +9 10 | io.TextIOWrapper(io.FileIO("test.txt")) +10 |-hugo.TextIOWrapper(hugo.FileIO("test.txt")) + 11 |+hugo.TextIOWrapper(hugo.FileIO("test.txt"), encoding=locale.getpreferredencoding(False)) +11 12 | tempfile.NamedTemporaryFile("w") +12 13 | tempfile.TemporaryFile("w") +13 14 | codecs.open("test.txt") + +unspecified_encoding.py:11:1: PLW1514 [*] `tempfile.NamedTemporaryFile` in text mode without explicit `encoding` argument + | + 9 | io.TextIOWrapper(io.FileIO("test.txt")) +10 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) +11 | tempfile.NamedTemporaryFile("w") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW1514 +12 | tempfile.TemporaryFile("w") +13 | codecs.open("test.txt") + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix + 1 |+import locale +1 2 | import io +2 3 | import sys +3 4 | import tempfile +-------------------------------------------------------------------------------- +8 9 | open("test.txt") +9 10 | io.TextIOWrapper(io.FileIO("test.txt")) +10 11 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) +11 |-tempfile.NamedTemporaryFile("w") + 12 |+tempfile.NamedTemporaryFile("w", encoding=locale.getpreferredencoding(False)) +12 13 | tempfile.TemporaryFile("w") +13 14 | codecs.open("test.txt") +14 15 | tempfile.SpooledTemporaryFile(0, "w") + +unspecified_encoding.py:12:1: PLW1514 [*] `tempfile.TemporaryFile` in text mode without explicit `encoding` argument + | +10 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) +11 | tempfile.NamedTemporaryFile("w") +12 | tempfile.TemporaryFile("w") + | ^^^^^^^^^^^^^^^^^^^^^^ PLW1514 +13 | codecs.open("test.txt") +14 | tempfile.SpooledTemporaryFile(0, "w") + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix + 1 |+import locale +1 2 | import io +2 3 | import sys +3 4 | import tempfile +-------------------------------------------------------------------------------- +9 10 | io.TextIOWrapper(io.FileIO("test.txt")) +10 11 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) +11 12 | tempfile.NamedTemporaryFile("w") +12 |-tempfile.TemporaryFile("w") + 13 |+tempfile.TemporaryFile("w", encoding=locale.getpreferredencoding(False)) +13 14 | codecs.open("test.txt") +14 15 | tempfile.SpooledTemporaryFile(0, "w") +15 16 | + +unspecified_encoding.py:13:1: PLW1514 [*] `codecs.open` in text mode without explicit `encoding` argument + | +11 | tempfile.NamedTemporaryFile("w") +12 | tempfile.TemporaryFile("w") +13 | codecs.open("test.txt") + | ^^^^^^^^^^^ PLW1514 +14 | tempfile.SpooledTemporaryFile(0, "w") + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix + 1 |+import locale +1 2 | import io +2 3 | import sys +3 4 | import tempfile +-------------------------------------------------------------------------------- +10 11 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) +11 12 | tempfile.NamedTemporaryFile("w") +12 13 | tempfile.TemporaryFile("w") +13 |-codecs.open("test.txt") + 14 |+codecs.open("test.txt", encoding=locale.getpreferredencoding(False)) +14 15 | tempfile.SpooledTemporaryFile(0, "w") +15 16 | +16 17 | # Non-errors. + +unspecified_encoding.py:14:1: PLW1514 [*] `tempfile.SpooledTemporaryFile` in text mode without explicit `encoding` argument + | +12 | tempfile.TemporaryFile("w") +13 | codecs.open("test.txt") +14 | tempfile.SpooledTemporaryFile(0, "w") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW1514 +15 | +16 | # Non-errors. + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix + 1 |+import locale +1 2 | import io +2 3 | import sys +3 4 | import tempfile +-------------------------------------------------------------------------------- +11 12 | tempfile.NamedTemporaryFile("w") +12 13 | tempfile.TemporaryFile("w") +13 14 | codecs.open("test.txt") +14 |-tempfile.SpooledTemporaryFile(0, "w") + 15 |+tempfile.SpooledTemporaryFile(0, "w", encoding=locale.getpreferredencoding(False)) +15 16 | +16 17 | # Non-errors. +17 18 | open("test.txt", encoding="utf-8") + + From fec762cecfe29b191ea61aacf6b0c926cc0e8863 Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Thu, 30 Nov 2023 19:45:25 +0100 Subject: [PATCH 3/7] clippy --- .../ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs index a051c6c82094b..1b6115b5b52ba 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs @@ -88,7 +88,7 @@ pub(crate) fn unspecified_encoding(checker: &mut Checker, call: &ast::ExprCall) diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion( ", encoding=\"locale\"".to_string(), call.range().end() - TextSize::from(1), - ))) + ))); } else { let import_edit = checker.importer().add_import( &AnyImport::Import(Import::module("locale")), From 5e7be984c8eef9441ae64bf0e0d89ce797e3605a Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Fri, 1 Dec 2023 17:58:35 +0100 Subject: [PATCH 4/7] Add generic `add_argument` util and replace fix with call to it --- .../fixtures/pylint/unspecified_encoding.py | 2 + crates/ruff_linter/src/fix/edits.rs | 66 ++++++++++++++++++- .../pylint/rules/unspecified_encoding.rs | 44 +++++++++---- ...ests__PLW1514_unspecified_encoding.py.snap | 16 +++++ ...nspecified_encoding_python39_or_lower.snap | 21 ++++++ 5 files changed, 134 insertions(+), 15 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py b/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py index 216fd83513a9c..c3a35921c024b 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py @@ -42,3 +42,5 @@ def func(*args, **kwargs): tempfile.SpooledTemporaryFile(0, "w", -1, "utf-8") tempfile.SpooledTemporaryFile(0, "wb") tempfile.SpooledTemporaryFile(0, ) + +open("test.txt",) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 76257f7f5967b..a471042948a8a 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -1,10 +1,11 @@ //! Interface for generating fix edits from higher-level actions (e.g., "remove an argument"). use anyhow::{Context, Result}; +use itertools::{Either, Itertools}; use ruff_diagnostics::Edit; -use ruff_python_ast::AnyNodeRef; use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Stmt}; +use ruff_python_ast::{AnyNodeRef, ArgOrKeyword}; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_trivia::{ @@ -138,6 +139,69 @@ pub(crate) fn remove_argument( } } +#[derive(Debug, Copy, Clone)] +pub(crate) enum ArgumentType { + Keyword, +} + +/// Generic function to add arguments or keyword arguments to function +/// calls and class definitions. (For classes `args` should be considered +/// `bases`) +pub(crate) fn add_argument( + argument: &str, + arguments: &Arguments, + arg_type: ArgumentType, + source: &str, +) -> Result { + // Partition into arguments before and after the argument to remove. + let (positional, keyword): (Vec<_>, Vec<_>) = + arguments + .arguments_source_order() + .partition_map(|arg| match arg { + ArgOrKeyword::Arg(_) => Either::Left(arg), + ArgOrKeyword::Keyword(_) => Either::Right(arg), + }); + + if positional.is_empty() && keyword.is_empty() { + // Case 1: no arguments. Add argument to call without comma + // TODO: Check no parentheses case + Ok(Edit::insertion( + argument.to_string(), + arguments.start() + TextSize::from(1), + )) + } else { + match arg_type { + ArgumentType::Keyword => { + // Case 3: Keyword arg passed. Can be added at the end of call + // Look ahead back from last argument for a trailing comma + let Some(last_arg) = arguments.args.last() else { + panic!("No last argument found") + }; + let mut tokenizer = SimpleTokenizer::starts_at(last_arg.range().end(), source); + + // Find the next non-whitespace token. + let next = tokenizer + .find(|token| { + token.kind != SimpleTokenKind::Whitespace + && token.kind != SimpleTokenKind::Newline + }) + .context("Unable to find next token")?; + + match next.kind { + SimpleTokenKind::Comma => Ok(Edit::insertion( + argument.to_string(), + arguments.end() - TextSize::from(1), + )), + _ => Ok(Edit::insertion( + format!(", {argument}"), + arguments.end() - TextSize::from(1), + )), + } + } + } + } +} + /// Determine if a vector contains only one, specific element. fn is_only(vec: &[T], value: &T) -> bool { vec.len() == 1 && vec[0] == *value diff --git a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs index 1b6115b5b52ba..b0e925896c35a 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs @@ -1,4 +1,5 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use anyhow::Result; +use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; use ruff_python_ast::call_path::{format_call_path, CallPath}; @@ -6,6 +7,8 @@ use ruff_python_ast::imports::{AnyImport, Import}; use ruff_text_size::{Ranged, TextSize}; use crate::checkers::ast::Checker; +use crate::fix::edits::add_argument; +use crate::fix::edits::ArgumentType::Keyword; use crate::settings::types::PythonVersion; /// ## What it does @@ -85,25 +88,38 @@ pub(crate) fn unspecified_encoding(checker: &mut Checker, call: &ast::ExprCall) ); if checker.settings.target_version >= PythonVersion::Py310 { - diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion( - ", encoding=\"locale\"".to_string(), - call.range().end() - TextSize::from(1), - ))); + diagnostic.try_set_fix(|| { + add_argument( + "encoding=\"locale\"", + &call.arguments, + Keyword, + checker.locator().contents(), + ) + .map(Fix::unsafe_edit) + }); } else { - let import_edit = checker.importer().add_import( - &AnyImport::Import(Import::module("locale")), - TextSize::default(), - ); - let call_edit = Edit::insertion( - ", encoding=locale.getpreferredencoding(False)".to_string(), - call.range().end() - TextSize::from(1), - ); - diagnostic.set_fix(Fix::unsafe_edits(import_edit, [call_edit])); + diagnostic.try_set_fix(|| generate_fix(checker, call)); } checker.diagnostics.push(diagnostic); } +/// Generate a [`Edit`] for Python39 and older. +fn generate_fix(checker: &Checker, call: &ast::ExprCall) -> Result { + Ok(Fix::unsafe_edits( + checker.importer().add_import( + &AnyImport::Import(Import::module("locale")), + TextSize::default(), + ), + [add_argument( + "encoding=locale.getpreferredencoding(False)", + &call.arguments, + Keyword, + checker.locator().contents(), + )?], + )) +} + /// Returns `true` if the given expression is a string literal containing a `b` character. fn is_binary_mode(expr: &ast::Expr) -> Option { Some( diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap index 2f67f08b691f2..c687d72efada3 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap @@ -146,4 +146,20 @@ unspecified_encoding.py:14:1: PLW1514 [*] `tempfile.SpooledTemporaryFile` in tex 16 16 | # Non-errors. 17 17 | open("test.txt", encoding="utf-8") +unspecified_encoding.py:46:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +44 | tempfile.SpooledTemporaryFile(0, ) +45 | +46 | open("test.txt",) + | ^^^^ PLW1514 + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +43 43 | tempfile.SpooledTemporaryFile(0, "wb") +44 44 | tempfile.SpooledTemporaryFile(0, ) +45 45 | +46 |-open("test.txt",) + 46 |+open("test.txt",encoding="locale") + diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap index e02d91ec53108..75df31df15ea4 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap @@ -181,4 +181,25 @@ unspecified_encoding.py:14:1: PLW1514 [*] `tempfile.SpooledTemporaryFile` in tex 16 17 | # Non-errors. 17 18 | open("test.txt", encoding="utf-8") +unspecified_encoding.py:46:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +44 | tempfile.SpooledTemporaryFile(0, ) +45 | +46 | open("test.txt",) + | ^^^^ PLW1514 + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix + 1 |+import locale +1 2 | import io +2 3 | import sys +3 4 | import tempfile +-------------------------------------------------------------------------------- +43 44 | tempfile.SpooledTemporaryFile(0, "wb") +44 45 | tempfile.SpooledTemporaryFile(0, ) +45 46 | +46 |-open("test.txt",) + 47 |+open("test.txt",encoding=locale.getpreferredencoding(False)) + From afde7114186bd43aad28a83b5d65a973b0b5e374 Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Fri, 1 Dec 2023 18:02:52 +0100 Subject: [PATCH 5/7] Add no arg fixture --- .../fixtures/pylint/unspecified_encoding.py | 1 + ...ests__PLW1514_unspecified_encoding.py.snap | 17 ++++++++++++++ ...nspecified_encoding_python39_or_lower.snap | 22 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py b/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py index c3a35921c024b..e6a9a65269d94 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py @@ -44,3 +44,4 @@ def func(*args, **kwargs): tempfile.SpooledTemporaryFile(0, ) open("test.txt",) +open() diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap index c687d72efada3..20399c78210bd 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap @@ -152,6 +152,7 @@ unspecified_encoding.py:46:1: PLW1514 [*] `open` in text mode without explicit ` 45 | 46 | open("test.txt",) | ^^^^ PLW1514 +47 | open() | = help: Add explicit `encoding` argument @@ -161,5 +162,21 @@ unspecified_encoding.py:46:1: PLW1514 [*] `open` in text mode without explicit ` 45 45 | 46 |-open("test.txt",) 46 |+open("test.txt",encoding="locale") +47 47 | open() + +unspecified_encoding.py:47:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +46 | open("test.txt",) +47 | open() + | ^^^^ PLW1514 + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +44 44 | tempfile.SpooledTemporaryFile(0, ) +45 45 | +46 46 | open("test.txt",) +47 |-open() + 47 |+open(encoding="locale") diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap index 75df31df15ea4..ec231166bfde0 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap @@ -187,6 +187,7 @@ unspecified_encoding.py:46:1: PLW1514 [*] `open` in text mode without explicit ` 45 | 46 | open("test.txt",) | ^^^^ PLW1514 +47 | open() | = help: Add explicit `encoding` argument @@ -201,5 +202,26 @@ unspecified_encoding.py:46:1: PLW1514 [*] `open` in text mode without explicit ` 45 46 | 46 |-open("test.txt",) 47 |+open("test.txt",encoding=locale.getpreferredencoding(False)) +47 48 | open() + +unspecified_encoding.py:47:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +46 | open("test.txt",) +47 | open() + | ^^^^ PLW1514 + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix + 1 |+import locale +1 2 | import io +2 3 | import sys +3 4 | import tempfile +-------------------------------------------------------------------------------- +44 45 | tempfile.SpooledTemporaryFile(0, ) +45 46 | +46 47 | open("test.txt",) +47 |-open() + 48 |+open(encoding=locale.getpreferredencoding(False)) From 70bef5c224966efb6b14d73f3598bcb75a40986e Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 1 Dec 2023 13:00:22 -0500 Subject: [PATCH 6/7] Handle parentheses --- .../fixtures/pylint/unspecified_encoding.py | 16 + crates/ruff_linter/src/fix/edits.rs | 85 ++---- .../pylint/rules/unspecified_encoding.rs | 78 ++--- ...ests__PLW1514_unspecified_encoding.py.snap | 135 ++++++++- ...nspecified_encoding_python39_or_lower.snap | 279 +++++++++++++++--- 5 files changed, 456 insertions(+), 137 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py b/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py index e6a9a65269d94..d89bdf06dcab4 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py @@ -45,3 +45,19 @@ def func(*args, **kwargs): open("test.txt",) open() +open( + "test.txt", # comment +) +open( + "test.txt", + # comment +) +open(("test.txt"),) +open() +open( + ("test.txt"), # comment +) +open( + ("test.txt"), + # comment +) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index a471042948a8a..bfde9f971cf4c 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -1,11 +1,12 @@ //! Interface for generating fix edits from higher-level actions (e.g., "remove an argument"). +use std::ops::Sub; + use anyhow::{Context, Result}; -use itertools::{Either, Itertools}; use ruff_diagnostics::Edit; +use ruff_python_ast::AnyNodeRef; use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Stmt}; -use ruff_python_ast::{AnyNodeRef, ArgOrKeyword}; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_trivia::{ @@ -139,66 +140,30 @@ pub(crate) fn remove_argument( } } -#[derive(Debug, Copy, Clone)] -pub(crate) enum ArgumentType { - Keyword, -} +/// Generic function to add arguments or keyword arguments to function calls. +pub(crate) fn add_argument(argument: &str, arguments: &Arguments, source: &str) -> Edit { + if let Some(last) = arguments.arguments_source_order().last() { + // Case 1: existing arguments, so append after the last argument. + let tokenizer = SimpleTokenizer::new( + source, + TextRange::new(last.end(), arguments.end().sub(TextSize::from(1))), + ); -/// Generic function to add arguments or keyword arguments to function -/// calls and class definitions. (For classes `args` should be considered -/// `bases`) -pub(crate) fn add_argument( - argument: &str, - arguments: &Arguments, - arg_type: ArgumentType, - source: &str, -) -> Result { - // Partition into arguments before and after the argument to remove. - let (positional, keyword): (Vec<_>, Vec<_>) = - arguments - .arguments_source_order() - .partition_map(|arg| match arg { - ArgOrKeyword::Arg(_) => Either::Left(arg), - ArgOrKeyword::Keyword(_) => Either::Right(arg), - }); - - if positional.is_empty() && keyword.is_empty() { - // Case 1: no arguments. Add argument to call without comma - // TODO: Check no parentheses case - Ok(Edit::insertion( - argument.to_string(), - arguments.start() + TextSize::from(1), - )) - } else { - match arg_type { - ArgumentType::Keyword => { - // Case 3: Keyword arg passed. Can be added at the end of call - // Look ahead back from last argument for a trailing comma - let Some(last_arg) = arguments.args.last() else { - panic!("No last argument found") - }; - let mut tokenizer = SimpleTokenizer::starts_at(last_arg.range().end(), source); - - // Find the next non-whitespace token. - let next = tokenizer - .find(|token| { - token.kind != SimpleTokenKind::Whitespace - && token.kind != SimpleTokenKind::Newline - }) - .context("Unable to find next token")?; - - match next.kind { - SimpleTokenKind::Comma => Ok(Edit::insertion( - argument.to_string(), - arguments.end() - TextSize::from(1), - )), - _ => Ok(Edit::insertion( - format!(", {argument}"), - arguments.end() - TextSize::from(1), - )), - } - } + // Skip any parentheses. + if let Some(token) = tokenizer + .skip_while(|token| token.kind.is_trivia()) + .next() + .filter(|token| token.kind == SimpleTokenKind::RParen) + { + // Ex) Insert after `func(x=(1))`. + Edit::insertion(format!(", {argument}"), token.end()) + } else { + // Ex) Insert after `func(x=1)`. + Edit::insertion(format!(", {argument}"), last.end()) } + } else { + // Case 2: no arguments. Add argument, without any trailing comma. + Edit::insertion(argument.to_string(), arguments.start() + TextSize::from(1)) } } diff --git a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs index b0e925896c35a..21f1a12db910c 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs @@ -1,14 +1,15 @@ use anyhow::Result; + use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; use ruff_python_ast::call_path::{format_call_path, CallPath}; -use ruff_python_ast::imports::{AnyImport, Import}; -use ruff_text_size::{Ranged, TextSize}; +use ruff_python_ast::Expr; +use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::add_argument; -use crate::fix::edits::ArgumentType::Keyword; +use crate::importer::ImportRequest; use crate::settings::types::PythonVersion; /// ## What it does @@ -20,7 +21,9 @@ use crate::settings::types::PythonVersion; /// non-portable code, with differing behavior across platforms. /// /// Instead, consider using the `encoding` parameter to enforce a specific -/// encoding. +/// encoding. [PEP 597] recommends using `locale.getpreferredencoding(False)` +/// as the default encoding on versions earlier than Python 3.10, and +/// `encoding="locale"` on Python 3.10 and later. /// /// ## Example /// ```python @@ -34,6 +37,8 @@ use crate::settings::types::PythonVersion; /// /// ## References /// - [Python documentation: `open`](https://docs.python.org/3/library/functions.html#open) +/// +/// [PEP 597]: https://peps.python.org/pep-0597/ #[violation] pub struct UnspecifiedEncoding { function_name: String, @@ -88,40 +93,52 @@ pub(crate) fn unspecified_encoding(checker: &mut Checker, call: &ast::ExprCall) ); if checker.settings.target_version >= PythonVersion::Py310 { - diagnostic.try_set_fix(|| { - add_argument( - "encoding=\"locale\"", - &call.arguments, - Keyword, - checker.locator().contents(), - ) - .map(Fix::unsafe_edit) - }); + diagnostic.set_fix(generate_keyword_fix(checker, call)); } else { - diagnostic.try_set_fix(|| generate_fix(checker, call)); + diagnostic.try_set_fix(|| generate_import_fix(checker, call)); } checker.diagnostics.push(diagnostic); } -/// Generate a [`Edit`] for Python39 and older. -fn generate_fix(checker: &Checker, call: &ast::ExprCall) -> Result { - Ok(Fix::unsafe_edits( - checker.importer().add_import( - &AnyImport::Import(Import::module("locale")), - TextSize::default(), +/// Generate an [`Edit`] for Python 3.10 and later. +fn generate_keyword_fix(checker: &Checker, call: &ast::ExprCall) -> Fix { + Fix::unsafe_edit(add_argument( + &format!( + "encoding={}", + checker + .generator() + .expr(&Expr::StringLiteral(ast::ExprStringLiteral { + value: ast::StringLiteralValue::single(ast::StringLiteral { + value: "locale".to_string(), + unicode: false, + range: TextRange::default(), + }), + range: TextRange::default(), + })) ), - [add_argument( - "encoding=locale.getpreferredencoding(False)", - &call.arguments, - Keyword, - checker.locator().contents(), - )?], + &call.arguments, + checker.locator().contents(), )) } +/// Generate an [`Edit`] for Python 3.9 and earlier. +fn generate_import_fix(checker: &Checker, call: &ast::ExprCall) -> Result { + let (import_edit, binding) = checker.importer().get_or_import_symbol( + &ImportRequest::import("locale", "getpreferredencoding"), + call.start(), + checker.semantic(), + )?; + let argument_edit = add_argument( + &format!("encoding={binding}(False)"), + &call.arguments, + checker.locator().contents(), + ); + Ok(Fix::unsafe_edits(import_edit, [argument_edit])) +} + /// Returns `true` if the given expression is a string literal containing a `b` character. -fn is_binary_mode(expr: &ast::Expr) -> Option { +fn is_binary_mode(expr: &Expr) -> Option { Some( expr.as_string_literal_expr()? .value @@ -133,12 +150,7 @@ fn is_binary_mode(expr: &ast::Expr) -> Option { /// Returns `true` if the given call lacks an explicit `encoding`. fn is_violation(call: &ast::ExprCall, call_path: &CallPath) -> bool { // If we have something like `*args`, which might contain the encoding argument, abort. - if call - .arguments - .args - .iter() - .any(ruff_python_ast::Expr::is_starred_expr) - { + if call.arguments.args.iter().any(Expr::is_starred_expr) { return false; } // If we have something like `**kwargs`, which might contain the encoding argument, abort. diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap index 20399c78210bd..3e32704e769a1 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap @@ -153,6 +153,7 @@ unspecified_encoding.py:46:1: PLW1514 [*] `open` in text mode without explicit ` 46 | open("test.txt",) | ^^^^ PLW1514 47 | open() +48 | open( | = help: Add explicit `encoding` argument @@ -161,14 +162,18 @@ unspecified_encoding.py:46:1: PLW1514 [*] `open` in text mode without explicit ` 44 44 | tempfile.SpooledTemporaryFile(0, ) 45 45 | 46 |-open("test.txt",) - 46 |+open("test.txt",encoding="locale") + 46 |+open("test.txt", encoding="locale",) 47 47 | open() +48 48 | open( +49 49 | "test.txt", # comment unspecified_encoding.py:47:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument | 46 | open("test.txt",) 47 | open() | ^^^^ PLW1514 +48 | open( +49 | "test.txt", # comment | = help: Add explicit `encoding` argument @@ -178,5 +183,133 @@ unspecified_encoding.py:47:1: PLW1514 [*] `open` in text mode without explicit ` 46 46 | open("test.txt",) 47 |-open() 47 |+open(encoding="locale") +48 48 | open( +49 49 | "test.txt", # comment +50 50 | ) + +unspecified_encoding.py:48:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +46 | open("test.txt",) +47 | open() +48 | open( + | ^^^^ PLW1514 +49 | "test.txt", # comment +50 | ) + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +46 46 | open("test.txt",) +47 47 | open() +48 48 | open( +49 |- "test.txt", # comment + 49 |+ "test.txt", encoding="locale", # comment +50 50 | ) +51 51 | open( +52 52 | "test.txt", + +unspecified_encoding.py:51:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +49 | "test.txt", # comment +50 | ) +51 | open( + | ^^^^ PLW1514 +52 | "test.txt", +53 | # comment + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +49 49 | "test.txt", # comment +50 50 | ) +51 51 | open( +52 |- "test.txt", + 52 |+ "test.txt", encoding="locale", +53 53 | # comment +54 54 | ) +55 55 | open(("test.txt"),) + +unspecified_encoding.py:55:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +53 | # comment +54 | ) +55 | open(("test.txt"),) + | ^^^^ PLW1514 +56 | open() +57 | open( + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +52 52 | "test.txt", +53 53 | # comment +54 54 | ) +55 |-open(("test.txt"),) + 55 |+open(("test.txt"), encoding="locale",) +56 56 | open() +57 57 | open( +58 58 | ("test.txt"), # comment + +unspecified_encoding.py:56:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +54 | ) +55 | open(("test.txt"),) +56 | open() + | ^^^^ PLW1514 +57 | open( +58 | ("test.txt"), # comment + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +53 53 | # comment +54 54 | ) +55 55 | open(("test.txt"),) +56 |-open() + 56 |+open(encoding="locale") +57 57 | open( +58 58 | ("test.txt"), # comment +59 59 | ) + +unspecified_encoding.py:57:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +55 | open(("test.txt"),) +56 | open() +57 | open( + | ^^^^ PLW1514 +58 | ("test.txt"), # comment +59 | ) + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +55 55 | open(("test.txt"),) +56 56 | open() +57 57 | open( +58 |- ("test.txt"), # comment + 58 |+ ("test.txt"), encoding="locale", # comment +59 59 | ) +60 60 | open( +61 61 | ("test.txt"), + +unspecified_encoding.py:60:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +58 | ("test.txt"), # comment +59 | ) +60 | open( + | ^^^^ PLW1514 +61 | ("test.txt"), +62 | # comment + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +58 58 | ("test.txt"), # comment +59 59 | ) +60 60 | open( +61 |- ("test.txt"), + 61 |+ ("test.txt"), encoding="locale", +62 62 | # comment +63 63 | ) diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap index ec231166bfde0..d0e5d1c0ebcd1 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap @@ -12,12 +12,10 @@ unspecified_encoding.py:8:1: PLW1514 [*] `open` in text mode without explicit `e = help: Add explicit `encoding` argument ℹ Unsafe fix - 1 |+import locale -1 2 | import io -2 3 | import sys -3 4 | import tempfile --------------------------------------------------------------------------------- -5 6 | import codecs +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale 6 7 | 7 8 | # Errors. 8 |-open("test.txt") @@ -38,11 +36,10 @@ unspecified_encoding.py:9:1: PLW1514 [*] `io.TextIOWrapper` without explicit `en = help: Add explicit `encoding` argument ℹ Unsafe fix - 1 |+import locale -1 2 | import io -2 3 | import sys -3 4 | import tempfile --------------------------------------------------------------------------------- +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale 6 7 | 7 8 | # Errors. 8 9 | open("test.txt") @@ -64,11 +61,11 @@ unspecified_encoding.py:10:1: PLW1514 [*] `io.TextIOWrapper` without explicit `e = help: Add explicit `encoding` argument ℹ Unsafe fix - 1 |+import locale -1 2 | import io -2 3 | import sys -3 4 | import tempfile --------------------------------------------------------------------------------- +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale +6 7 | 7 8 | # Errors. 8 9 | open("test.txt") 9 10 | io.TextIOWrapper(io.FileIO("test.txt")) @@ -90,11 +87,12 @@ unspecified_encoding.py:11:1: PLW1514 [*] `tempfile.NamedTemporaryFile` in text = help: Add explicit `encoding` argument ℹ Unsafe fix - 1 |+import locale -1 2 | import io -2 3 | import sys -3 4 | import tempfile --------------------------------------------------------------------------------- +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale +6 7 | +7 8 | # Errors. 8 9 | open("test.txt") 9 10 | io.TextIOWrapper(io.FileIO("test.txt")) 10 11 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) @@ -116,11 +114,13 @@ unspecified_encoding.py:12:1: PLW1514 [*] `tempfile.TemporaryFile` in text mode = help: Add explicit `encoding` argument ℹ Unsafe fix - 1 |+import locale -1 2 | import io -2 3 | import sys -3 4 | import tempfile --------------------------------------------------------------------------------- +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale +6 7 | +7 8 | # Errors. +8 9 | open("test.txt") 9 10 | io.TextIOWrapper(io.FileIO("test.txt")) 10 11 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) 11 12 | tempfile.NamedTemporaryFile("w") @@ -141,10 +141,13 @@ unspecified_encoding.py:13:1: PLW1514 [*] `codecs.open` in text mode without exp = help: Add explicit `encoding` argument ℹ Unsafe fix - 1 |+import locale -1 2 | import io -2 3 | import sys -3 4 | import tempfile +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale +6 7 | +7 8 | # Errors. +8 9 | open("test.txt") -------------------------------------------------------------------------------- 10 11 | hugo.TextIOWrapper(hugo.FileIO("test.txt")) 11 12 | tempfile.NamedTemporaryFile("w") @@ -167,10 +170,13 @@ unspecified_encoding.py:14:1: PLW1514 [*] `tempfile.SpooledTemporaryFile` in tex = help: Add explicit `encoding` argument ℹ Unsafe fix - 1 |+import locale -1 2 | import io -2 3 | import sys -3 4 | import tempfile +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale +6 7 | +7 8 | # Errors. +8 9 | open("test.txt") -------------------------------------------------------------------------------- 11 12 | tempfile.NamedTemporaryFile("w") 12 13 | tempfile.TemporaryFile("w") @@ -188,40 +194,227 @@ unspecified_encoding.py:46:1: PLW1514 [*] `open` in text mode without explicit ` 46 | open("test.txt",) | ^^^^ PLW1514 47 | open() +48 | open( | = help: Add explicit `encoding` argument ℹ Unsafe fix - 1 |+import locale -1 2 | import io -2 3 | import sys -3 4 | import tempfile +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale +6 7 | +7 8 | # Errors. +8 9 | open("test.txt") -------------------------------------------------------------------------------- 43 44 | tempfile.SpooledTemporaryFile(0, "wb") 44 45 | tempfile.SpooledTemporaryFile(0, ) 45 46 | 46 |-open("test.txt",) - 47 |+open("test.txt",encoding=locale.getpreferredencoding(False)) + 47 |+open("test.txt", encoding=locale.getpreferredencoding(False),) 47 48 | open() +48 49 | open( +49 50 | "test.txt", # comment unspecified_encoding.py:47:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument | 46 | open("test.txt",) 47 | open() | ^^^^ PLW1514 +48 | open( +49 | "test.txt", # comment | = help: Add explicit `encoding` argument ℹ Unsafe fix - 1 |+import locale -1 2 | import io -2 3 | import sys -3 4 | import tempfile +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale +6 7 | +7 8 | # Errors. +8 9 | open("test.txt") -------------------------------------------------------------------------------- 44 45 | tempfile.SpooledTemporaryFile(0, ) 45 46 | 46 47 | open("test.txt",) 47 |-open() 48 |+open(encoding=locale.getpreferredencoding(False)) +48 49 | open( +49 50 | "test.txt", # comment +50 51 | ) + +unspecified_encoding.py:48:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +46 | open("test.txt",) +47 | open() +48 | open( + | ^^^^ PLW1514 +49 | "test.txt", # comment +50 | ) + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale +6 7 | +7 8 | # Errors. +8 9 | open("test.txt") +-------------------------------------------------------------------------------- +46 47 | open("test.txt",) +47 48 | open() +48 49 | open( +49 |- "test.txt", # comment + 50 |+ "test.txt", encoding=locale.getpreferredencoding(False), # comment +50 51 | ) +51 52 | open( +52 53 | "test.txt", + +unspecified_encoding.py:51:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +49 | "test.txt", # comment +50 | ) +51 | open( + | ^^^^ PLW1514 +52 | "test.txt", +53 | # comment + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale +6 7 | +7 8 | # Errors. +8 9 | open("test.txt") +-------------------------------------------------------------------------------- +49 50 | "test.txt", # comment +50 51 | ) +51 52 | open( +52 |- "test.txt", + 53 |+ "test.txt", encoding=locale.getpreferredencoding(False), +53 54 | # comment +54 55 | ) +55 56 | open(("test.txt"),) + +unspecified_encoding.py:55:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +53 | # comment +54 | ) +55 | open(("test.txt"),) + | ^^^^ PLW1514 +56 | open() +57 | open( + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale +6 7 | +7 8 | # Errors. +8 9 | open("test.txt") +-------------------------------------------------------------------------------- +52 53 | "test.txt", +53 54 | # comment +54 55 | ) +55 |-open(("test.txt"),) + 56 |+open(("test.txt"), encoding=locale.getpreferredencoding(False),) +56 57 | open() +57 58 | open( +58 59 | ("test.txt"), # comment + +unspecified_encoding.py:56:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +54 | ) +55 | open(("test.txt"),) +56 | open() + | ^^^^ PLW1514 +57 | open( +58 | ("test.txt"), # comment + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale +6 7 | +7 8 | # Errors. +8 9 | open("test.txt") +-------------------------------------------------------------------------------- +53 54 | # comment +54 55 | ) +55 56 | open(("test.txt"),) +56 |-open() + 57 |+open(encoding=locale.getpreferredencoding(False)) +57 58 | open( +58 59 | ("test.txt"), # comment +59 60 | ) + +unspecified_encoding.py:57:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +55 | open(("test.txt"),) +56 | open() +57 | open( + | ^^^^ PLW1514 +58 | ("test.txt"), # comment +59 | ) + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale +6 7 | +7 8 | # Errors. +8 9 | open("test.txt") +-------------------------------------------------------------------------------- +55 56 | open(("test.txt"),) +56 57 | open() +57 58 | open( +58 |- ("test.txt"), # comment + 59 |+ ("test.txt"), encoding=locale.getpreferredencoding(False), # comment +59 60 | ) +60 61 | open( +61 62 | ("test.txt"), + +unspecified_encoding.py:60:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +58 | ("test.txt"), # comment +59 | ) +60 | open( + | ^^^^ PLW1514 +61 | ("test.txt"), +62 | # comment + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale +6 7 | +7 8 | # Errors. +8 9 | open("test.txt") +-------------------------------------------------------------------------------- +58 59 | ("test.txt"), # comment +59 60 | ) +60 61 | open( +61 |- ("test.txt"), + 62 |+ ("test.txt"), encoding=locale.getpreferredencoding(False), +62 63 | # comment +63 64 | ) From d4c7ccac139c56705818822bc678b8d38d8cc43c Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 1 Dec 2023 13:12:25 -0500 Subject: [PATCH 7/7] Fix multi-parentheses --- .../fixtures/pylint/unspecified_encoding.py | 10 +- crates/ruff_linter/src/fix/edits.rs | 41 +++-- .../pylint/rules/unspecified_encoding.rs | 2 + ...ests__PLW1514_unspecified_encoding.py.snap | 127 ++++++++++------ ...nspecified_encoding_python39_or_lower.snap | 143 ++++++++++++------ 5 files changed, 215 insertions(+), 108 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py b/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py index d89bdf06dcab4..01c12c9fc4c77 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py @@ -53,7 +53,6 @@ def func(*args, **kwargs): # comment ) open(("test.txt"),) -open() open( ("test.txt"), # comment ) @@ -61,3 +60,12 @@ def func(*args, **kwargs): ("test.txt"), # comment ) + +open((("test.txt")),) +open( + (("test.txt")), # comment +) +open( + (("test.txt")), + # comment +) diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index bfde9f971cf4c..89d6db3f39d38 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -1,16 +1,16 @@ //! Interface for generating fix edits from higher-level actions (e.g., "remove an argument"). -use std::ops::Sub; - use anyhow::{Context, Result}; use ruff_diagnostics::Edit; -use ruff_python_ast::AnyNodeRef; +use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Stmt}; +use ruff_python_ast::{AnyNodeRef, ArgOrKeyword}; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_trivia::{ - has_leading_content, is_python_whitespace, PythonWhitespace, SimpleTokenKind, SimpleTokenizer, + has_leading_content, is_python_whitespace, CommentRanges, PythonWhitespace, SimpleTokenKind, + SimpleTokenizer, }; use ruff_source_file::{Locator, NewlineWithTrailingNewline, UniversalNewlines}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; @@ -141,26 +141,25 @@ pub(crate) fn remove_argument( } /// Generic function to add arguments or keyword arguments to function calls. -pub(crate) fn add_argument(argument: &str, arguments: &Arguments, source: &str) -> Edit { +pub(crate) fn add_argument( + argument: &str, + arguments: &Arguments, + comment_ranges: &CommentRanges, + source: &str, +) -> Edit { if let Some(last) = arguments.arguments_source_order().last() { // Case 1: existing arguments, so append after the last argument. - let tokenizer = SimpleTokenizer::new( + let last = parenthesized_range( + match last { + ArgOrKeyword::Arg(arg) => arg.into(), + ArgOrKeyword::Keyword(keyword) => (&keyword.value).into(), + }, + arguments.into(), + comment_ranges, source, - TextRange::new(last.end(), arguments.end().sub(TextSize::from(1))), - ); - - // Skip any parentheses. - if let Some(token) = tokenizer - .skip_while(|token| token.kind.is_trivia()) - .next() - .filter(|token| token.kind == SimpleTokenKind::RParen) - { - // Ex) Insert after `func(x=(1))`. - Edit::insertion(format!(", {argument}"), token.end()) - } else { - // Ex) Insert after `func(x=1)`. - Edit::insertion(format!(", {argument}"), last.end()) - } + ) + .unwrap_or(last.range()); + Edit::insertion(format!(", {argument}"), last.end()) } else { // Case 2: no arguments. Add argument, without any trailing comma. Edit::insertion(argument.to_string(), arguments.start() + TextSize::from(1)) diff --git a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs index 21f1a12db910c..b6728df692415 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs @@ -118,6 +118,7 @@ fn generate_keyword_fix(checker: &Checker, call: &ast::ExprCall) -> Fix { })) ), &call.arguments, + checker.indexer().comment_ranges(), checker.locator().contents(), )) } @@ -132,6 +133,7 @@ fn generate_import_fix(checker: &Checker, call: &ast::ExprCall) -> Result { let argument_edit = add_argument( &format!("encoding={binding}(False)"), &call.arguments, + checker.indexer().comment_ranges(), checker.locator().contents(), ); Ok(Fix::unsafe_edits(import_edit, [argument_edit])) diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap index 3e32704e769a1..9ceaf09daf820 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW1514_unspecified_encoding.py.snap @@ -235,8 +235,8 @@ unspecified_encoding.py:55:1: PLW1514 [*] `open` in text mode without explicit ` 54 | ) 55 | open(("test.txt"),) | ^^^^ PLW1514 -56 | open() -57 | open( +56 | open( +57 | ("test.txt"), # comment | = help: Add explicit `encoding` argument @@ -246,70 +246,111 @@ unspecified_encoding.py:55:1: PLW1514 [*] `open` in text mode without explicit ` 54 54 | ) 55 |-open(("test.txt"),) 55 |+open(("test.txt"), encoding="locale",) -56 56 | open() -57 57 | open( -58 58 | ("test.txt"), # comment +56 56 | open( +57 57 | ("test.txt"), # comment +58 58 | ) unspecified_encoding.py:56:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument | 54 | ) 55 | open(("test.txt"),) -56 | open() +56 | open( | ^^^^ PLW1514 -57 | open( -58 | ("test.txt"), # comment +57 | ("test.txt"), # comment +58 | ) | = help: Add explicit `encoding` argument ℹ Unsafe fix -53 53 | # comment 54 54 | ) 55 55 | open(("test.txt"),) -56 |-open() - 56 |+open(encoding="locale") -57 57 | open( -58 58 | ("test.txt"), # comment -59 59 | ) +56 56 | open( +57 |- ("test.txt"), # comment + 57 |+ ("test.txt"), encoding="locale", # comment +58 58 | ) +59 59 | open( +60 60 | ("test.txt"), -unspecified_encoding.py:57:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument +unspecified_encoding.py:59:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument | -55 | open(("test.txt"),) -56 | open() -57 | open( +57 | ("test.txt"), # comment +58 | ) +59 | open( | ^^^^ PLW1514 -58 | ("test.txt"), # comment -59 | ) +60 | ("test.txt"), +61 | # comment | = help: Add explicit `encoding` argument ℹ Unsafe fix -55 55 | open(("test.txt"),) -56 56 | open() -57 57 | open( -58 |- ("test.txt"), # comment - 58 |+ ("test.txt"), encoding="locale", # comment -59 59 | ) -60 60 | open( -61 61 | ("test.txt"), - -unspecified_encoding.py:60:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument - | -58 | ("test.txt"), # comment -59 | ) -60 | open( +57 57 | ("test.txt"), # comment +58 58 | ) +59 59 | open( +60 |- ("test.txt"), + 60 |+ ("test.txt"), encoding="locale", +61 61 | # comment +62 62 | ) +63 63 | + +unspecified_encoding.py:64:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +62 | ) +63 | +64 | open((("test.txt")),) + | ^^^^ PLW1514 +65 | open( +66 | (("test.txt")), # comment + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +61 61 | # comment +62 62 | ) +63 63 | +64 |-open((("test.txt")),) + 64 |+open((("test.txt")), encoding="locale",) +65 65 | open( +66 66 | (("test.txt")), # comment +67 67 | ) + +unspecified_encoding.py:65:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +64 | open((("test.txt")),) +65 | open( + | ^^^^ PLW1514 +66 | (("test.txt")), # comment +67 | ) + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +63 63 | +64 64 | open((("test.txt")),) +65 65 | open( +66 |- (("test.txt")), # comment + 66 |+ (("test.txt")), encoding="locale", # comment +67 67 | ) +68 68 | open( +69 69 | (("test.txt")), + +unspecified_encoding.py:68:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +66 | (("test.txt")), # comment +67 | ) +68 | open( | ^^^^ PLW1514 -61 | ("test.txt"), -62 | # comment +69 | (("test.txt")), +70 | # comment | = help: Add explicit `encoding` argument ℹ Unsafe fix -58 58 | ("test.txt"), # comment -59 59 | ) -60 60 | open( -61 |- ("test.txt"), - 61 |+ ("test.txt"), encoding="locale", -62 62 | # comment -63 63 | ) +66 66 | (("test.txt")), # comment +67 67 | ) +68 68 | open( +69 |- (("test.txt")), + 69 |+ (("test.txt")), encoding="locale", +70 70 | # comment +71 71 | ) diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap index d0e5d1c0ebcd1..1390eeede0cf3 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__unspecified_encoding_python39_or_lower.snap @@ -308,8 +308,8 @@ unspecified_encoding.py:55:1: PLW1514 [*] `open` in text mode without explicit ` 54 | ) 55 | open(("test.txt"),) | ^^^^ PLW1514 -56 | open() -57 | open( +56 | open( +57 | ("test.txt"), # comment | = help: Add explicit `encoding` argument @@ -327,18 +327,18 @@ unspecified_encoding.py:55:1: PLW1514 [*] `open` in text mode without explicit ` 54 55 | ) 55 |-open(("test.txt"),) 56 |+open(("test.txt"), encoding=locale.getpreferredencoding(False),) -56 57 | open() -57 58 | open( -58 59 | ("test.txt"), # comment +56 57 | open( +57 58 | ("test.txt"), # comment +58 59 | ) unspecified_encoding.py:56:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument | 54 | ) 55 | open(("test.txt"),) -56 | open() +56 | open( | ^^^^ PLW1514 -57 | open( -58 | ("test.txt"), # comment +57 | ("test.txt"), # comment +58 | ) | = help: Add explicit `encoding` argument @@ -351,23 +351,23 @@ unspecified_encoding.py:56:1: PLW1514 [*] `open` in text mode without explicit ` 7 8 | # Errors. 8 9 | open("test.txt") -------------------------------------------------------------------------------- -53 54 | # comment 54 55 | ) 55 56 | open(("test.txt"),) -56 |-open() - 57 |+open(encoding=locale.getpreferredencoding(False)) -57 58 | open( -58 59 | ("test.txt"), # comment -59 60 | ) +56 57 | open( +57 |- ("test.txt"), # comment + 58 |+ ("test.txt"), encoding=locale.getpreferredencoding(False), # comment +58 59 | ) +59 60 | open( +60 61 | ("test.txt"), -unspecified_encoding.py:57:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument +unspecified_encoding.py:59:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument | -55 | open(("test.txt"),) -56 | open() -57 | open( +57 | ("test.txt"), # comment +58 | ) +59 | open( | ^^^^ PLW1514 -58 | ("test.txt"), # comment -59 | ) +60 | ("test.txt"), +61 | # comment | = help: Add explicit `encoding` argument @@ -380,23 +380,80 @@ unspecified_encoding.py:57:1: PLW1514 [*] `open` in text mode without explicit ` 7 8 | # Errors. 8 9 | open("test.txt") -------------------------------------------------------------------------------- -55 56 | open(("test.txt"),) -56 57 | open() -57 58 | open( -58 |- ("test.txt"), # comment - 59 |+ ("test.txt"), encoding=locale.getpreferredencoding(False), # comment -59 60 | ) -60 61 | open( -61 62 | ("test.txt"), - -unspecified_encoding.py:60:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument - | -58 | ("test.txt"), # comment -59 | ) -60 | open( +57 58 | ("test.txt"), # comment +58 59 | ) +59 60 | open( +60 |- ("test.txt"), + 61 |+ ("test.txt"), encoding=locale.getpreferredencoding(False), +61 62 | # comment +62 63 | ) +63 64 | + +unspecified_encoding.py:64:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +62 | ) +63 | +64 | open((("test.txt")),) + | ^^^^ PLW1514 +65 | open( +66 | (("test.txt")), # comment + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale +6 7 | +7 8 | # Errors. +8 9 | open("test.txt") +-------------------------------------------------------------------------------- +61 62 | # comment +62 63 | ) +63 64 | +64 |-open((("test.txt")),) + 65 |+open((("test.txt")), encoding=locale.getpreferredencoding(False),) +65 66 | open( +66 67 | (("test.txt")), # comment +67 68 | ) + +unspecified_encoding.py:65:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +64 | open((("test.txt")),) +65 | open( + | ^^^^ PLW1514 +66 | (("test.txt")), # comment +67 | ) + | + = help: Add explicit `encoding` argument + +ℹ Unsafe fix +3 3 | import tempfile +4 4 | import io as hugo +5 5 | import codecs + 6 |+import locale +6 7 | +7 8 | # Errors. +8 9 | open("test.txt") +-------------------------------------------------------------------------------- +63 64 | +64 65 | open((("test.txt")),) +65 66 | open( +66 |- (("test.txt")), # comment + 67 |+ (("test.txt")), encoding=locale.getpreferredencoding(False), # comment +67 68 | ) +68 69 | open( +69 70 | (("test.txt")), + +unspecified_encoding.py:68:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument + | +66 | (("test.txt")), # comment +67 | ) +68 | open( | ^^^^ PLW1514 -61 | ("test.txt"), -62 | # comment +69 | (("test.txt")), +70 | # comment | = help: Add explicit `encoding` argument @@ -409,12 +466,12 @@ unspecified_encoding.py:60:1: PLW1514 [*] `open` in text mode without explicit ` 7 8 | # Errors. 8 9 | open("test.txt") -------------------------------------------------------------------------------- -58 59 | ("test.txt"), # comment -59 60 | ) -60 61 | open( -61 |- ("test.txt"), - 62 |+ ("test.txt"), encoding=locale.getpreferredencoding(False), -62 63 | # comment -63 64 | ) +66 67 | (("test.txt")), # comment +67 68 | ) +68 69 | open( +69 |- (("test.txt")), + 70 |+ (("test.txt")), encoding=locale.getpreferredencoding(False), +70 71 | # comment +71 72 | )