From 344eaea4b213cfa43798d5ce4338fb384271664f Mon Sep 17 00:00:00 2001 From: 11happy Date: Sun, 26 Oct 2025 16:10:13 +0530 Subject: [PATCH 01/11] feat: implement supoort for path.open in furb103 Signed-off-by: 11happy --- .../resources/test/fixtures/refurb/FURB103.py | 20 ++++- .../ruff_linter/src/rules/refurb/helpers.rs | 80 ++++++++++++++++++- .../rules/refurb/rules/write_whole_file.rs | 18 ++++- 3 files changed, 114 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py index 35d9600d41..65ece59d6c 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py @@ -152,4 +152,22 @@ import json data = {"price": 100} with open("test.json", "wb") as f: - f.write(json.dumps(data, indent=4).encode("utf-8")) \ No newline at end of file + f.write(json.dumps(data, indent=4).encode("utf-8")) + +with Path("file.txt").open("w") as f: + f.write("test") + +with Path("file.txt").open("wb") as f: + f.write(b"test") + +with Path("file.txt").open(mode="w") as f: + f.write("test") + +with Path("file.txt").open("w", encoding="utf8") as f: + f.write("test") + +with Path("file.txt").open("w", errors="ignore") as f: + f.write("test") + +with Path(foo()).open("w") as f: + f.write("test") diff --git a/crates/ruff_linter/src/rules/refurb/helpers.rs b/crates/ruff_linter/src/rules/refurb/helpers.rs index 0a09d70aba..d3c168ce70 100644 --- a/crates/ruff_linter/src/rules/refurb/helpers.rs +++ b/crates/ruff_linter/src/rules/refurb/helpers.rs @@ -127,6 +127,8 @@ pub(super) struct FileOpen<'a> { pub(super) keywords: Vec<&'a ast::Keyword>, /// We only check `open` operations whose file handles are used exactly once. pub(super) reference: &'a ResolvedReference, + /// `Path().open()` call + pub(super) path_obj: Option<&'a Expr>, } impl FileOpen<'_> { @@ -146,7 +148,10 @@ pub(super) fn find_file_opens<'a>( ) -> Vec> { with.items .iter() - .filter_map(|item| find_file_open(item, with, semantic, read_mode, python_version)) + .filter_map(|item| { + find_file_open(item, with, semantic, read_mode, python_version) + .or_else(|| find_path_open(item, with, semantic, read_mode, python_version)) + }) .collect() } @@ -238,6 +243,79 @@ fn find_file_open<'a>( mode, keywords, reference, + path_obj: None, + }) +} + +fn find_path_open<'a>( + item: &'a ast::WithItem, + with: &'a ast::StmtWith, + semantic: &'a SemanticModel<'a>, + read_mode: bool, + python_version: PythonVersion, +) -> Option> { + let ast::ExprCall { + func, + arguments: ast::Arguments { args, keywords, .. }, + .. + } = item.context_expr.as_call_expr()?; + let attr = func.as_attribute_expr()?; + if attr.attr.as_str() != "open" { + return None; + } + let path_call = attr.value.as_call_expr()?; + let path_func = path_call.func.as_name_expr()?; + if path_func.id.as_str() != "Path" { + return None; + } + let filename = path_call.arguments.args.first()?; + let mode = if args.is_empty() { + OpenMode::ReadText + } else { + match_open_mode(args.first()?)? + }; + let (keywords, kw_mode) = match_open_keywords(keywords, read_mode, python_version)?; + let mode = kw_mode.unwrap_or(mode); + match mode { + OpenMode::ReadText | OpenMode::ReadBytes => { + if !read_mode { + return None; + } + } + OpenMode::WriteText | OpenMode::WriteBytes => { + if read_mode { + return None; + } + } + } + if matches!(mode, OpenMode::ReadBytes | OpenMode::WriteBytes) && !keywords.is_empty() { + return None; + } + let var = item.optional_vars.as_deref()?.as_name_expr()?; + let scope = semantic.current_scope(); + let bindings: Vec<_> = scope.get_all(var.id.as_str()).collect(); + let binding = bindings + .iter() + .map(|id| semantic.binding(*id)) + .find(|binding| binding.range() == var.range())?; + + let references: Vec<&ResolvedReference> = binding + .references + .iter() + .map(|id| semantic.reference(*id)) + .filter(|reference| with.range().contains_range(reference.range())) + .collect(); + + let [reference] = references.as_slice() else { + return None; + }; + Some(FileOpen { + item, + filename, + mode, + keywords, + reference, + path_obj: Some(attr.value.as_ref()), }) } diff --git a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs index f25faa3eb2..d60ce680e6 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs @@ -47,6 +47,7 @@ use crate::{FixAvailability, Violation}; pub(crate) struct WriteWholeFile { filename: SourceCodeSnippet, suggestion: SourceCodeSnippet, + is_path_open: bool, } impl Violation for WriteWholeFile { @@ -56,7 +57,13 @@ impl Violation for WriteWholeFile { fn message(&self) -> String { let filename = self.filename.truncated_display(); let suggestion = self.suggestion.truncated_display(); - format!("`open` and `write` should be replaced by `Path({filename}).{suggestion}`") + if self.is_path_open { + format!( + "`Path.open()` followed by `write()` can be replaced by `Path({filename}).{suggestion}`" + ) + } else { + format!("`open` and `write` should be replaced by `Path({filename}).{suggestion}`") + } } fn fix_title(&self) -> Option { Some(format!( @@ -137,6 +144,7 @@ impl<'a> Visitor<'a> for WriteMatcher<'a, '_> { &self.checker.generator().expr(open.filename), ), suggestion: SourceCodeSnippet::from_str(&suggestion), + is_path_open: open.path_obj.is_some(), }, open.item.range(), ); @@ -217,7 +225,13 @@ fn generate_fix( ) .ok()?; - let replacement = format!("{binding}({filename_code}).{suggestion}"); + let target = if let Some(path_obj) = open.path_obj { + locator.slice(path_obj.range()).to_string() + } else { + format!("{binding}({filename_code})") + }; + + let replacement = format!("{target}.{suggestion}"); let applicability = if checker.comment_ranges().intersects(with_stmt.range()) { Applicability::Unsafe From 7250d1c5024f06a339ec28d5bd23fa5f99eaca49 Mon Sep 17 00:00:00 2001 From: 11happy Date: Sun, 2 Nov 2025 18:26:17 +0530 Subject: [PATCH 02/11] refactor: common implementation and use previously implemented functions with qualified name resolution Signed-off-by: 11happy --- .../resources/test/fixtures/refurb/FURB103.py | 1 + .../flake8_async/rules/blocking_open_call.rs | 2 +- .../src/rules/flake8_async/rules/mod.rs | 2 +- .../ruff_linter/src/rules/refurb/helpers.rs | 173 +++++------ ...es__refurb__tests__FURB103_FURB103.py.snap | 280 +++++------------- ...rb__tests__preview_FURB103_FURB103.py.snap | 238 +++++++++++++++ ...rb__tests__write_whole_file_python_39.snap | 196 ++++-------- temp.txt | 0 8 files changed, 436 insertions(+), 456 deletions(-) create mode 100644 crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap create mode 100644 temp.txt diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py index 65ece59d6c..b84de16bda 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py @@ -1,3 +1,4 @@ +from pathlib import Path def foo(): ... diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs index 0b1427c781..01bea3def4 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs @@ -70,7 +70,7 @@ fn is_open_call(func: &Expr, semantic: &SemanticModel) -> bool { } /// Returns `true` if an expression resolves to a call to `pathlib.Path.open`. -fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool { +pub(crate) fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool { let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else { return false; }; diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs index 3e12ea360c..ecdddaeda6 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs @@ -18,7 +18,7 @@ mod async_zero_sleep; mod blocking_http_call; mod blocking_http_call_httpx; mod blocking_input; -mod blocking_open_call; +pub(crate) mod blocking_open_call; mod blocking_path_methods; mod blocking_process_invocation; mod blocking_sleep; diff --git a/crates/ruff_linter/src/rules/refurb/helpers.rs b/crates/ruff_linter/src/rules/refurb/helpers.rs index d3c168ce70..b851b132d5 100644 --- a/crates/ruff_linter/src/rules/refurb/helpers.rs +++ b/crates/ruff_linter/src/rules/refurb/helpers.rs @@ -1,14 +1,14 @@ use std::borrow::Cow; +use crate::checkers::ast::Checker; +use crate::rules::flake8_async::rules::blocking_open_call::is_open_call_from_pathlib; +use crate::{Applicability, Edit, Fix}; use ruff_python_ast::PythonVersion; use ruff_python_ast::{self as ast, Expr, name::Name, parenthesize::parenthesized_range}; use ruff_python_codegen::Generator; use ruff_python_semantic::{BindingId, ResolvedReference, SemanticModel}; use ruff_text_size::{Ranged, TextRange}; -use crate::checkers::ast::Checker; -use crate::{Applicability, Edit, Fix}; - /// Format a code snippet to call `name.method()`. pub(super) fn generate_method_call(name: Name, method: &str, generator: Generator) -> String { // Construct `name`. @@ -155,6 +155,62 @@ pub(super) fn find_file_opens<'a>( .collect() } +fn resolve_file_open<'a>( + item: &'a ast::WithItem, + with: &'a ast::StmtWith, + semantic: &'a SemanticModel<'a>, + read_mode: bool, + filename: &'a Expr, + mode: OpenMode, + keywords: Vec<&'a ast::Keyword>, + path_obj: Option<&'a Expr>, +) -> Option> { + match mode { + OpenMode::ReadText | OpenMode::ReadBytes => { + if !read_mode { + return None; + } + } + OpenMode::WriteText | OpenMode::WriteBytes => { + if read_mode { + return None; + } + } + } + + if matches!(mode, OpenMode::ReadBytes | OpenMode::WriteBytes) && !keywords.is_empty() { + return None; + } + + let var = item.optional_vars.as_deref()?.as_name_expr()?; + let scope = semantic.current_scope(); + + let binding = scope.get_all(var.id.as_str()).find_map(|id| { + let b = semantic.binding(id); + (b.range() == var.range()).then_some(b) + })?; + + let references: Vec<&ResolvedReference> = binding + .references + .iter() + .map(|id| semantic.reference(*id)) + .filter(|reference| with.range().contains_range(reference.range())) + .collect(); + + let [reference] = references.as_slice() else { + return None; + }; + + Some(FileOpen { + item, + filename, + mode, + keywords, + reference, + path_obj, + }) +} + /// Find `open` operation in the given `with` item. fn find_file_open<'a>( item: &'a ast::WithItem, @@ -170,8 +226,6 @@ fn find_file_open<'a>( .. } = item.context_expr.as_call_expr()?; - let var = item.optional_vars.as_deref()?.as_name_expr()?; - // Ignore calls with `*args` and `**kwargs`. In the exact case of `open(*filename, mode="w")`, // it could be a match; but in all other cases, the call _could_ contain unsupported keyword // arguments, like `buffering`. @@ -192,59 +246,9 @@ fn find_file_open<'a>( let (keywords, kw_mode) = match_open_keywords(keywords, read_mode, python_version)?; let mode = kw_mode.unwrap_or(pos_mode); - - match mode { - OpenMode::ReadText | OpenMode::ReadBytes => { - if !read_mode { - return None; - } - } - OpenMode::WriteText | OpenMode::WriteBytes => { - if read_mode { - return None; - } - } - } - - // Path.read_bytes and Path.write_bytes do not support any kwargs. - if matches!(mode, OpenMode::ReadBytes | OpenMode::WriteBytes) && !keywords.is_empty() { - return None; - } - - // Now we need to find what is this variable bound to... - let scope = semantic.current_scope(); - let bindings: Vec = scope.get_all(var.id.as_str()).collect(); - - let binding = bindings - .iter() - .map(|id| semantic.binding(*id)) - // We might have many bindings with the same name, but we only care - // for the one we are looking at right now. - .find(|binding| binding.range() == var.range())?; - - // Since many references can share the same binding, we can limit our attention span - // exclusively to the body of the current `with` statement. - let references: Vec<&ResolvedReference> = binding - .references - .iter() - .map(|id| semantic.reference(*id)) - .filter(|reference| with.range().contains_range(reference.range())) - .collect(); - - // And even with all these restrictions, if the file handle gets used not exactly once, - // it doesn't fit the bill. - let [reference] = references.as_slice() else { - return None; - }; - - Some(FileOpen { - item, - filename, - mode, - keywords, - reference, - path_obj: None, - }) + resolve_file_open( + item, with, semantic, read_mode, filename, mode, keywords, None, + ) } fn find_path_open<'a>( @@ -259,15 +263,11 @@ fn find_path_open<'a>( arguments: ast::Arguments { args, keywords, .. }, .. } = item.context_expr.as_call_expr()?; + if !is_open_call_from_pathlib(func, semantic) { + return None; + } let attr = func.as_attribute_expr()?; - if attr.attr.as_str() != "open" { - return None; - } let path_call = attr.value.as_call_expr()?; - let path_func = path_call.func.as_name_expr()?; - if path_func.id.as_str() != "Path" { - return None; - } let filename = path_call.arguments.args.first()?; let mode = if args.is_empty() { OpenMode::ReadText @@ -276,47 +276,16 @@ fn find_path_open<'a>( }; let (keywords, kw_mode) = match_open_keywords(keywords, read_mode, python_version)?; let mode = kw_mode.unwrap_or(mode); - match mode { - OpenMode::ReadText | OpenMode::ReadBytes => { - if !read_mode { - return None; - } - } - OpenMode::WriteText | OpenMode::WriteBytes => { - if read_mode { - return None; - } - } - } - if matches!(mode, OpenMode::ReadBytes | OpenMode::WriteBytes) && !keywords.is_empty() { - return None; - } - let var = item.optional_vars.as_deref()?.as_name_expr()?; - let scope = semantic.current_scope(); - let bindings: Vec<_> = scope.get_all(var.id.as_str()).collect(); - let binding = bindings - .iter() - .map(|id| semantic.binding(*id)) - .find(|binding| binding.range() == var.range())?; - - let references: Vec<&ResolvedReference> = binding - .references - .iter() - .map(|id| semantic.reference(*id)) - .filter(|reference| with.range().contains_range(reference.range())) - .collect(); - - let [reference] = references.as_slice() else { - return None; - }; - Some(FileOpen { + resolve_file_open( item, + with, + semantic, + read_mode, filename, mode, keywords, - reference, - path_obj: Some(attr.value.as_ref()), - }) + Some(attr.value.as_ref()), + ) } /// Match positional arguments. Return expression for the file name and open mode. diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap index 8148035435..8bcffc7b3f 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap @@ -1,265 +1,141 @@ --- source: crates/ruff_linter/src/rules/refurb/mod.rs --- -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text("test")` - --> FURB103.py:12:6 +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text("test")` + --> FURB103.py:13:6 | -11 | # FURB103 -12 | with open("file.txt", "w") as f: +12 | # FURB103 +13 | with open("file.txt", "w") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -13 | f.write("test") +14 | f.write("test") | help: Replace with `Path("file.txt").write_text("test")` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -10 | # Errors. -11 | -12 | # FURB103 - - with open("file.txt", "w") as f: - - f.write("test") -13 + pathlib.Path("file.txt").write_text("test") -14 | -15 | # FURB103 -16 | with open("file.txt", "wb") as f: -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)` - --> FURB103.py:16:6 +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)` + --> FURB103.py:17:6 | -15 | # FURB103 -16 | with open("file.txt", "wb") as f: +16 | # FURB103 +17 | with open("file.txt", "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -17 | f.write(foobar) +18 | f.write(foobar) | help: Replace with `Path("file.txt").write_bytes(foobar)` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -14 | f.write("test") -15 | -16 | # FURB103 - - with open("file.txt", "wb") as f: - - f.write(foobar) -17 + pathlib.Path("file.txt").write_bytes(foobar) -18 | -19 | # FURB103 -20 | with open("file.txt", mode="wb") as f: -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")` - --> FURB103.py:20:6 +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")` + --> FURB103.py:21:6 | -19 | # FURB103 -20 | with open("file.txt", mode="wb") as f: +20 | # FURB103 +21 | with open("file.txt", mode="wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -21 | f.write(b"abc") +22 | f.write(b"abc") | help: Replace with `Path("file.txt").write_bytes(b"abc")` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -18 | f.write(foobar) -19 | -20 | # FURB103 - - with open("file.txt", mode="wb") as f: - - f.write(b"abc") -21 + pathlib.Path("file.txt").write_bytes(b"abc") -22 | -23 | # FURB103 -24 | with open("file.txt", "w", encoding="utf8") as f: -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")` - --> FURB103.py:24:6 +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")` + --> FURB103.py:25:6 | -23 | # FURB103 -24 | with open("file.txt", "w", encoding="utf8") as f: +24 | # FURB103 +25 | with open("file.txt", "w", encoding="utf8") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -25 | f.write(foobar) +26 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, encoding="utf8")` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -22 | f.write(b"abc") -23 | -24 | # FURB103 - - with open("file.txt", "w", encoding="utf8") as f: - - f.write(foobar) -25 + pathlib.Path("file.txt").write_text(foobar, encoding="utf8") -26 | -27 | # FURB103 -28 | with open("file.txt", "w", errors="ignore") as f: -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")` - --> FURB103.py:28:6 +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")` + --> FURB103.py:29:6 | -27 | # FURB103 -28 | with open("file.txt", "w", errors="ignore") as f: +28 | # FURB103 +29 | with open("file.txt", "w", errors="ignore") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -29 | f.write(foobar) +30 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, errors="ignore")` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -26 | f.write(foobar) -27 | -28 | # FURB103 - - with open("file.txt", "w", errors="ignore") as f: - - f.write(foobar) -29 + pathlib.Path("file.txt").write_text(foobar, errors="ignore") -30 | -31 | # FURB103 -32 | with open("file.txt", mode="w") as f: -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)` - --> FURB103.py:32:6 +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)` + --> FURB103.py:33:6 | -31 | # FURB103 -32 | with open("file.txt", mode="w") as f: +32 | # FURB103 +33 | with open("file.txt", mode="w") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -33 | f.write(foobar) +34 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar)` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -30 | f.write(foobar) -31 | -32 | # FURB103 - - with open("file.txt", mode="w") as f: - - f.write(foobar) -33 + pathlib.Path("file.txt").write_text(foobar) -34 | -35 | # FURB103 -36 | with open(foo(), "wb") as f: FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())` - --> FURB103.py:36:6 + --> FURB103.py:37:6 | -35 | # FURB103 -36 | with open(foo(), "wb") as f: +36 | # FURB103 +37 | with open(foo(), "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^ -37 | # The body of `with` is non-trivial, but the recommendation holds. -38 | bar("pre") +38 | # The body of `with` is non-trivial, but the recommendation holds. +39 | bar("pre") | help: Replace with `Path(foo()).write_bytes(bar())` FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)` - --> FURB103.py:44:6 + --> FURB103.py:45:6 | -43 | # FURB103 -44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: +44 | # FURB103 +45 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: | ^^^^^^^^^^^^^^^^^^^^^^^ -45 | a.write(x) -46 | b.write(y) +46 | a.write(x) +47 | b.write(y) | help: Replace with `Path("a.txt").write_text(x)` FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)` - --> FURB103.py:44:31 + --> FURB103.py:45:31 | -43 | # FURB103 -44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: +44 | # FURB103 +45 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: | ^^^^^^^^^^^^^^^^^^^^^^^^ -45 | a.write(x) -46 | b.write(y) +46 | a.write(x) +47 | b.write(y) | help: Replace with `Path("b.txt").write_bytes(y)` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(bar(bar(a + x)))` - --> FURB103.py:49:18 + --> FURB103.py:50:18 | -48 | # FURB103 -49 | with foo() as a, open("file.txt", "w") as b, foo() as c: +49 | # FURB103 +50 | with foo() as a, open("file.txt", "w") as b, foo() as c: | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -50 | # We have other things in here, multiple with items, but the user -51 | # writes a single time to file and that bit they can replace. +51 | # We have other things in here, multiple with items, but the user +52 | # writes a single time to file and that bit they can replace. | help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))` -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` - --> FURB103.py:58:6 +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` + --> FURB103.py:59:6 | -57 | # FURB103 -58 | with open("file.txt", "w", newline="\r\n") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -59 | f.write(foobar) - | -help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -56 | -57 | 58 | # FURB103 - - with open("file.txt", "w", newline="\r\n") as f: - - f.write(foobar) -59 + pathlib.Path("file.txt").write_text(foobar, newline="\r\n") -60 | -61 | -62 | import builtins - -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` - --> FURB103.py:66:6 - | -65 | # FURB103 -66 | with builtins.open("file.txt", "w", newline="\r\n") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -67 | f.write(foobar) +59 | with open("file.txt", "w", newline="\r\n") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +60 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` -60 | -61 | -62 | import builtins -63 + import pathlib -64 | -65 | + +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` + --> FURB103.py:67:6 + | 66 | # FURB103 - - with builtins.open("file.txt", "w", newline="\r\n") as f: - - f.write(foobar) -67 + pathlib.Path("file.txt").write_text(foobar, newline="\r\n") -68 | -69 | -70 | from builtins import open as o - -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` - --> FURB103.py:74:6 - | -73 | # FURB103 -74 | with o("file.txt", "w", newline="\r\n") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -75 | f.write(foobar) +67 | with builtins.open("file.txt", "w", newline="\r\n") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +68 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` -68 | -69 | -70 | from builtins import open as o -71 + import pathlib -72 | -73 | -74 | # FURB103 - - with o("file.txt", "w", newline="\r\n") as f: - - f.write(foobar) -75 + pathlib.Path("file.txt").write_text(foobar, newline="\r\n") -76 | -77 | # Non-errors. -78 | -FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....` +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` + --> FURB103.py:75:6 + | +74 | # FURB103 +75 | with o("file.txt", "w", newline="\r\n") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +76 | f.write(foobar) + | +help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` + +FURB103 `open` and `write` should be replaced by `Path("test.json")....` --> FURB103.py:154:6 | 152 | data = {"price": 100} @@ -269,13 +145,3 @@ FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....` 155 | f.write(json.dumps(data, indent=4).encode("utf-8")) | help: Replace with `Path("test.json")....` -148 | -149 | # See: https://github.com/astral-sh/ruff/issues/20785 -150 | import json -151 + import pathlib -152 | -153 | data = {"price": 100} -154 | - - with open("test.json", "wb") as f: - - f.write(json.dumps(data, indent=4).encode("utf-8")) -155 + pathlib.Path("test.json").write_bytes(json.dumps(data, indent=4).encode("utf-8")) diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap new file mode 100644 index 0000000000..8d7da6a783 --- /dev/null +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap @@ -0,0 +1,238 @@ +--- +source: crates/ruff_linter/src/rules/refurb/mod.rs +--- +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text("test")` + --> FURB103.py:13:6 + | +12 | # FURB103 +13 | with open("file.txt", "w") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +14 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test")` +10 | # Errors. +11 | +12 | # FURB103 + - with open("file.txt", "w") as f: + - f.write("test") +13 + Path("file.txt").write_text("test") +14 | +15 | # FURB103 +16 | with open("file.txt", "wb") as f: + +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)` + --> FURB103.py:17:6 + | +16 | # FURB103 +17 | with open("file.txt", "wb") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +18 | f.write(foobar) + | +help: Replace with `Path("file.txt").write_bytes(foobar)` +14 | f.write("test") +15 | +16 | # FURB103 + - with open("file.txt", "wb") as f: + - f.write(foobar) +17 + Path("file.txt").write_bytes(foobar) +18 | +19 | # FURB103 +20 | with open("file.txt", mode="wb") as f: + +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")` + --> FURB103.py:21:6 + | +20 | # FURB103 +21 | with open("file.txt", mode="wb") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +22 | f.write(b"abc") + | +help: Replace with `Path("file.txt").write_bytes(b"abc")` +18 | f.write(foobar) +19 | +20 | # FURB103 + - with open("file.txt", mode="wb") as f: + - f.write(b"abc") +21 + Path("file.txt").write_bytes(b"abc") +22 | +23 | # FURB103 +24 | with open("file.txt", "w", encoding="utf8") as f: + +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")` + --> FURB103.py:25:6 + | +24 | # FURB103 +25 | with open("file.txt", "w", encoding="utf8") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +26 | f.write(foobar) + | +help: Replace with `Path("file.txt").write_text(foobar, encoding="utf8")` +22 | f.write(b"abc") +23 | +24 | # FURB103 + - with open("file.txt", "w", encoding="utf8") as f: + - f.write(foobar) +25 + Path("file.txt").write_text(foobar, encoding="utf8") +26 | +27 | # FURB103 +28 | with open("file.txt", "w", errors="ignore") as f: + +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")` + --> FURB103.py:29:6 + | +28 | # FURB103 +29 | with open("file.txt", "w", errors="ignore") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +30 | f.write(foobar) + | +help: Replace with `Path("file.txt").write_text(foobar, errors="ignore")` +26 | f.write(foobar) +27 | +28 | # FURB103 + - with open("file.txt", "w", errors="ignore") as f: + - f.write(foobar) +29 + Path("file.txt").write_text(foobar, errors="ignore") +30 | +31 | # FURB103 +32 | with open("file.txt", mode="w") as f: + +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)` + --> FURB103.py:33:6 + | +32 | # FURB103 +33 | with open("file.txt", mode="w") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +34 | f.write(foobar) + | +help: Replace with `Path("file.txt").write_text(foobar)` +30 | f.write(foobar) +31 | +32 | # FURB103 + - with open("file.txt", mode="w") as f: + - f.write(foobar) +33 + Path("file.txt").write_text(foobar) +34 | +35 | # FURB103 +36 | with open(foo(), "wb") as f: + +FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())` + --> FURB103.py:37:6 + | +36 | # FURB103 +37 | with open(foo(), "wb") as f: + | ^^^^^^^^^^^^^^^^^^^^^^ +38 | # The body of `with` is non-trivial, but the recommendation holds. +39 | bar("pre") + | +help: Replace with `Path(foo()).write_bytes(bar())` + +FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)` + --> FURB103.py:45:6 + | +44 | # FURB103 +45 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: + | ^^^^^^^^^^^^^^^^^^^^^^^ +46 | a.write(x) +47 | b.write(y) + | +help: Replace with `Path("a.txt").write_text(x)` + +FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)` + --> FURB103.py:45:31 + | +44 | # FURB103 +45 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: + | ^^^^^^^^^^^^^^^^^^^^^^^^ +46 | a.write(x) +47 | b.write(y) + | +help: Replace with `Path("b.txt").write_bytes(y)` + +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(bar(bar(a + x)))` + --> FURB103.py:50:18 + | +49 | # FURB103 +50 | with foo() as a, open("file.txt", "w") as b, foo() as c: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +51 | # We have other things in here, multiple with items, but the user +52 | # writes a single time to file and that bit they can replace. + | +help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))` + +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` + --> FURB103.py:59:6 + | +58 | # FURB103 +59 | with open("file.txt", "w", newline="\r\n") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +60 | f.write(foobar) + | +help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` +56 | +57 | +58 | # FURB103 + - with open("file.txt", "w", newline="\r\n") as f: + - f.write(foobar) +59 + Path("file.txt").write_text(foobar, newline="\r\n") +60 | +61 | +62 | import builtins + +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` + --> FURB103.py:67:6 + | +66 | # FURB103 +67 | with builtins.open("file.txt", "w", newline="\r\n") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +68 | f.write(foobar) + | +help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` +64 | +65 | +66 | # FURB103 + - with builtins.open("file.txt", "w", newline="\r\n") as f: + - f.write(foobar) +67 + Path("file.txt").write_text(foobar, newline="\r\n") +68 | +69 | +70 | from builtins import open as o + +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` + --> FURB103.py:75:6 + | +74 | # FURB103 +75 | with o("file.txt", "w", newline="\r\n") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +76 | f.write(foobar) + | +help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` +72 | +73 | +74 | # FURB103 + - with o("file.txt", "w", newline="\r\n") as f: + - f.write(foobar) +75 + Path("file.txt").write_text(foobar, newline="\r\n") +76 | +77 | # Non-errors. +78 | + +FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....` + --> FURB103.py:154:6 + | +152 | data = {"price": 100} +153 | +154 | with open("test.json", "wb") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +155 | f.write(json.dumps(data, indent=4).encode("utf-8")) + | +help: Replace with `Path("test.json")....` +148 | +149 | # See: https://github.com/astral-sh/ruff/issues/20785 +150 | import json +151 + import pathlib +152 | +153 | data = {"price": 100} +154 | + - with open("test.json", "wb") as f: + - f.write(json.dumps(data, indent=4).encode("utf-8")) +155 + pathlib.Path("test.json").write_bytes(json.dumps(data, indent=4).encode("utf-8")) diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap index 3b68b110d5..4b042a5922 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap @@ -1,195 +1,111 @@ --- source: crates/ruff_linter/src/rules/refurb/mod.rs --- -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text("test")` - --> FURB103.py:12:6 +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text("test")` + --> FURB103.py:13:6 | -11 | # FURB103 -12 | with open("file.txt", "w") as f: +12 | # FURB103 +13 | with open("file.txt", "w") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -13 | f.write("test") +14 | f.write("test") | help: Replace with `Path("file.txt").write_text("test")` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -10 | # Errors. -11 | -12 | # FURB103 - - with open("file.txt", "w") as f: - - f.write("test") -13 + pathlib.Path("file.txt").write_text("test") -14 | -15 | # FURB103 -16 | with open("file.txt", "wb") as f: -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)` - --> FURB103.py:16:6 +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)` + --> FURB103.py:17:6 | -15 | # FURB103 -16 | with open("file.txt", "wb") as f: +16 | # FURB103 +17 | with open("file.txt", "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -17 | f.write(foobar) +18 | f.write(foobar) | help: Replace with `Path("file.txt").write_bytes(foobar)` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -14 | f.write("test") -15 | -16 | # FURB103 - - with open("file.txt", "wb") as f: - - f.write(foobar) -17 + pathlib.Path("file.txt").write_bytes(foobar) -18 | -19 | # FURB103 -20 | with open("file.txt", mode="wb") as f: -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")` - --> FURB103.py:20:6 +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")` + --> FURB103.py:21:6 | -19 | # FURB103 -20 | with open("file.txt", mode="wb") as f: +20 | # FURB103 +21 | with open("file.txt", mode="wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -21 | f.write(b"abc") +22 | f.write(b"abc") | help: Replace with `Path("file.txt").write_bytes(b"abc")` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -18 | f.write(foobar) -19 | -20 | # FURB103 - - with open("file.txt", mode="wb") as f: - - f.write(b"abc") -21 + pathlib.Path("file.txt").write_bytes(b"abc") -22 | -23 | # FURB103 -24 | with open("file.txt", "w", encoding="utf8") as f: -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")` - --> FURB103.py:24:6 +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")` + --> FURB103.py:25:6 | -23 | # FURB103 -24 | with open("file.txt", "w", encoding="utf8") as f: +24 | # FURB103 +25 | with open("file.txt", "w", encoding="utf8") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -25 | f.write(foobar) +26 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, encoding="utf8")` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -22 | f.write(b"abc") -23 | -24 | # FURB103 - - with open("file.txt", "w", encoding="utf8") as f: - - f.write(foobar) -25 + pathlib.Path("file.txt").write_text(foobar, encoding="utf8") -26 | -27 | # FURB103 -28 | with open("file.txt", "w", errors="ignore") as f: -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")` - --> FURB103.py:28:6 +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")` + --> FURB103.py:29:6 | -27 | # FURB103 -28 | with open("file.txt", "w", errors="ignore") as f: +28 | # FURB103 +29 | with open("file.txt", "w", errors="ignore") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -29 | f.write(foobar) +30 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, errors="ignore")` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -26 | f.write(foobar) -27 | -28 | # FURB103 - - with open("file.txt", "w", errors="ignore") as f: - - f.write(foobar) -29 + pathlib.Path("file.txt").write_text(foobar, errors="ignore") -30 | -31 | # FURB103 -32 | with open("file.txt", mode="w") as f: -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)` - --> FURB103.py:32:6 +FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)` + --> FURB103.py:33:6 | -31 | # FURB103 -32 | with open("file.txt", mode="w") as f: +32 | # FURB103 +33 | with open("file.txt", mode="w") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -33 | f.write(foobar) +34 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar)` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -30 | f.write(foobar) -31 | -32 | # FURB103 - - with open("file.txt", mode="w") as f: - - f.write(foobar) -33 + pathlib.Path("file.txt").write_text(foobar) -34 | -35 | # FURB103 -36 | with open(foo(), "wb") as f: FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())` - --> FURB103.py:36:6 + --> FURB103.py:37:6 | -35 | # FURB103 -36 | with open(foo(), "wb") as f: +36 | # FURB103 +37 | with open(foo(), "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^ -37 | # The body of `with` is non-trivial, but the recommendation holds. -38 | bar("pre") +38 | # The body of `with` is non-trivial, but the recommendation holds. +39 | bar("pre") | help: Replace with `Path(foo()).write_bytes(bar())` FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)` - --> FURB103.py:44:6 + --> FURB103.py:45:6 | -43 | # FURB103 -44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: +44 | # FURB103 +45 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: | ^^^^^^^^^^^^^^^^^^^^^^^ -45 | a.write(x) -46 | b.write(y) +46 | a.write(x) +47 | b.write(y) | help: Replace with `Path("a.txt").write_text(x)` FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)` - --> FURB103.py:44:31 + --> FURB103.py:45:31 | -43 | # FURB103 -44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: +44 | # FURB103 +45 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: | ^^^^^^^^^^^^^^^^^^^^^^^^ -45 | a.write(x) -46 | b.write(y) +46 | a.write(x) +47 | b.write(y) | help: Replace with `Path("b.txt").write_bytes(y)` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(bar(bar(a + x)))` - --> FURB103.py:49:18 + --> FURB103.py:50:18 | -48 | # FURB103 -49 | with foo() as a, open("file.txt", "w") as b, foo() as c: +49 | # FURB103 +50 | with foo() as a, open("file.txt", "w") as b, foo() as c: | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -50 | # We have other things in here, multiple with items, but the user -51 | # writes a single time to file and that bit they can replace. +51 | # We have other things in here, multiple with items, but the user +52 | # writes a single time to file and that bit they can replace. | help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))` -FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....` +FURB103 `open` and `write` should be replaced by `Path("test.json")....` --> FURB103.py:154:6 | 152 | data = {"price": 100} @@ -199,13 +115,3 @@ FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....` 155 | f.write(json.dumps(data, indent=4).encode("utf-8")) | help: Replace with `Path("test.json")....` -148 | -149 | # See: https://github.com/astral-sh/ruff/issues/20785 -150 | import json -151 + import pathlib -152 | -153 | data = {"price": 100} -154 | - - with open("test.json", "wb") as f: - - f.write(json.dumps(data, indent=4).encode("utf-8")) -155 + pathlib.Path("test.json").write_bytes(json.dumps(data, indent=4).encode("utf-8")) diff --git a/temp.txt b/temp.txt new file mode 100644 index 0000000000..e69de29bb2 From ce6dd5e93a2958b6a052fb23255ea4a1e21584e9 Mon Sep 17 00:00:00 2001 From: 11happy Date: Sun, 2 Nov 2025 18:42:50 +0530 Subject: [PATCH 03/11] test: fix snapshots Signed-off-by: 11happy --- ...es__refurb__tests__FURB103_FURB103.py.snap | 76 +++++++++- ...rb__tests__preview_FURB103_FURB103.py.snap | 136 ++++++++++++++++-- ...rb__tests__write_whole_file_python_39.snap | 76 +++++++++- 3 files changed, 268 insertions(+), 20 deletions(-) diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap index 8bcffc7b3f..343f0a81fa 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap @@ -136,12 +136,78 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(fo help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` FURB103 `open` and `write` should be replaced by `Path("test.json")....` - --> FURB103.py:154:6 + --> FURB103.py:155:6 | -152 | data = {"price": 100} -153 | -154 | with open("test.json", "wb") as f: +153 | data = {"price": 100} +154 | +155 | with open("test.json", "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -155 | f.write(json.dumps(data, indent=4).encode("utf-8")) +156 | f.write(json.dumps(data, indent=4).encode("utf-8")) | help: Replace with `Path("test.json")....` + +FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` + --> FURB103.py:158:6 + | +156 | f.write(json.dumps(data, indent=4).encode("utf-8")) +157 | +158 | with Path("file.txt").open("w") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +159 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test")` + +FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_bytes(b"test")` + --> FURB103.py:161:6 + | +159 | f.write("test") +160 | +161 | with Path("file.txt").open("wb") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +162 | f.write(b"test") + | +help: Replace with `Path("file.txt").write_bytes(b"test")` + +FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` + --> FURB103.py:164:6 + | +162 | f.write(b"test") +163 | +164 | with Path("file.txt").open(mode="w") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +165 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test")` + +FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", encoding="utf8")` + --> FURB103.py:167:6 + | +165 | f.write("test") +166 | +167 | with Path("file.txt").open("w", encoding="utf8") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +168 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test", encoding="utf8")` + +FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", errors="ignore")` + --> FURB103.py:170:6 + | +168 | f.write("test") +169 | +170 | with Path("file.txt").open("w", errors="ignore") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +171 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test", errors="ignore")` + +FURB103 `Path.open()` followed by `write()` can be replaced by `Path(foo()).write_text("test")` + --> FURB103.py:173:6 + | +171 | f.write("test") +172 | +173 | with Path(foo()).open("w") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +174 | f.write("test") + | +help: Replace with `Path(foo()).write_text("test")` diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap index 8d7da6a783..38636f3465 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap @@ -217,22 +217,138 @@ help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` 78 | FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....` - --> FURB103.py:154:6 + --> FURB103.py:155:6 | -152 | data = {"price": 100} -153 | -154 | with open("test.json", "wb") as f: +153 | data = {"price": 100} +154 | +155 | with open("test.json", "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -155 | f.write(json.dumps(data, indent=4).encode("utf-8")) +156 | f.write(json.dumps(data, indent=4).encode("utf-8")) | help: Replace with `Path("test.json")....` -148 | -149 | # See: https://github.com/astral-sh/ruff/issues/20785 -150 | import json -151 + import pathlib 152 | 153 | data = {"price": 100} 154 | - with open("test.json", "wb") as f: - f.write(json.dumps(data, indent=4).encode("utf-8")) -155 + pathlib.Path("test.json").write_bytes(json.dumps(data, indent=4).encode("utf-8")) +155 + Path("test.json").write_bytes(json.dumps(data, indent=4).encode("utf-8")) +156 | +157 | with Path("file.txt").open("w") as f: +158 | f.write("test") + +FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` + --> FURB103.py:158:6 + | +156 | f.write(json.dumps(data, indent=4).encode("utf-8")) +157 | +158 | with Path("file.txt").open("w") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +159 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test")` +155 | with open("test.json", "wb") as f: +156 | f.write(json.dumps(data, indent=4).encode("utf-8")) +157 | + - with Path("file.txt").open("w") as f: + - f.write("test") +158 + Path("file.txt").write_text("test") +159 | +160 | with Path("file.txt").open("wb") as f: +161 | f.write(b"test") + +FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_bytes(b"test")` + --> FURB103.py:161:6 + | +159 | f.write("test") +160 | +161 | with Path("file.txt").open("wb") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +162 | f.write(b"test") + | +help: Replace with `Path("file.txt").write_bytes(b"test")` +158 | with Path("file.txt").open("w") as f: +159 | f.write("test") +160 | + - with Path("file.txt").open("wb") as f: + - f.write(b"test") +161 + Path("file.txt").write_bytes(b"test") +162 | +163 | with Path("file.txt").open(mode="w") as f: +164 | f.write("test") + +FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` + --> FURB103.py:164:6 + | +162 | f.write(b"test") +163 | +164 | with Path("file.txt").open(mode="w") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +165 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test")` +161 | with Path("file.txt").open("wb") as f: +162 | f.write(b"test") +163 | + - with Path("file.txt").open(mode="w") as f: + - f.write("test") +164 + Path("file.txt").write_text("test") +165 | +166 | with Path("file.txt").open("w", encoding="utf8") as f: +167 | f.write("test") + +FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", encoding="utf8")` + --> FURB103.py:167:6 + | +165 | f.write("test") +166 | +167 | with Path("file.txt").open("w", encoding="utf8") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +168 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test", encoding="utf8")` +164 | with Path("file.txt").open(mode="w") as f: +165 | f.write("test") +166 | + - with Path("file.txt").open("w", encoding="utf8") as f: + - f.write("test") +167 + Path("file.txt").write_text("test", encoding="utf8") +168 | +169 | with Path("file.txt").open("w", errors="ignore") as f: +170 | f.write("test") + +FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", errors="ignore")` + --> FURB103.py:170:6 + | +168 | f.write("test") +169 | +170 | with Path("file.txt").open("w", errors="ignore") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +171 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test", errors="ignore")` +167 | with Path("file.txt").open("w", encoding="utf8") as f: +168 | f.write("test") +169 | + - with Path("file.txt").open("w", errors="ignore") as f: + - f.write("test") +170 + Path("file.txt").write_text("test", errors="ignore") +171 | +172 | with Path(foo()).open("w") as f: +173 | f.write("test") + +FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path(foo()).write_text("test")` + --> FURB103.py:173:6 + | +171 | f.write("test") +172 | +173 | with Path(foo()).open("w") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +174 | f.write("test") + | +help: Replace with `Path(foo()).write_text("test")` +170 | with Path("file.txt").open("w", errors="ignore") as f: +171 | f.write("test") +172 | + - with Path(foo()).open("w") as f: + - f.write("test") +173 + Path(foo()).write_text("test") diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap index 4b042a5922..d83f7330dc 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap @@ -106,12 +106,78 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(ba help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))` FURB103 `open` and `write` should be replaced by `Path("test.json")....` - --> FURB103.py:154:6 + --> FURB103.py:155:6 | -152 | data = {"price": 100} -153 | -154 | with open("test.json", "wb") as f: +153 | data = {"price": 100} +154 | +155 | with open("test.json", "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -155 | f.write(json.dumps(data, indent=4).encode("utf-8")) +156 | f.write(json.dumps(data, indent=4).encode("utf-8")) | help: Replace with `Path("test.json")....` + +FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` + --> FURB103.py:158:6 + | +156 | f.write(json.dumps(data, indent=4).encode("utf-8")) +157 | +158 | with Path("file.txt").open("w") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +159 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test")` + +FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_bytes(b"test")` + --> FURB103.py:161:6 + | +159 | f.write("test") +160 | +161 | with Path("file.txt").open("wb") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +162 | f.write(b"test") + | +help: Replace with `Path("file.txt").write_bytes(b"test")` + +FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` + --> FURB103.py:164:6 + | +162 | f.write(b"test") +163 | +164 | with Path("file.txt").open(mode="w") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +165 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test")` + +FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", encoding="utf8")` + --> FURB103.py:167:6 + | +165 | f.write("test") +166 | +167 | with Path("file.txt").open("w", encoding="utf8") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +168 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test", encoding="utf8")` + +FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", errors="ignore")` + --> FURB103.py:170:6 + | +168 | f.write("test") +169 | +170 | with Path("file.txt").open("w", errors="ignore") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +171 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test", errors="ignore")` + +FURB103 `Path.open()` followed by `write()` can be replaced by `Path(foo()).write_text("test")` + --> FURB103.py:173:6 + | +171 | f.write("test") +172 | +173 | with Path(foo()).open("w") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +174 | f.write("test") + | +help: Replace with `Path(foo()).write_text("test")` From c3a703cbdd9454f015cd69d7c3cce07e96dc154f Mon Sep 17 00:00:00 2001 From: 11happy Date: Thu, 6 Nov 2025 18:04:27 +0530 Subject: [PATCH 04/11] refactor: make filename optional Signed-off-by: 11happy --- .../resources/test/fixtures/refurb/FURB103.py | 17 - .../test/fixtures/refurb/FURB103_EXT.py | 23 ++ .../ruff_linter/src/rules/refurb/helpers.rs | 42 ++- crates/ruff_linter/src/rules/refurb/mod.rs | 1 + .../src/rules/refurb/rules/read_whole_file.rs | 16 +- .../rules/refurb/rules/write_whole_file.rs | 54 ++- ...es__refurb__tests__FURB103_FURB103.py.snap | 188 ++++------- ...refurb__tests__FURB103_FURB103_EXT.py.snap | 78 +++++ ...rb__tests__preview_FURB103_FURB103.py.snap | 307 +++++++----------- ...rb__tests__write_whole_file_python_39.snap | 164 +++------- 10 files changed, 410 insertions(+), 480 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/refurb/FURB103_EXT.py create mode 100644 crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_EXT.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py index b84de16bda..f38e64be9a 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py @@ -1,4 +1,3 @@ -from pathlib import Path def foo(): ... @@ -155,20 +154,4 @@ data = {"price": 100} with open("test.json", "wb") as f: f.write(json.dumps(data, indent=4).encode("utf-8")) -with Path("file.txt").open("w") as f: - f.write("test") -with Path("file.txt").open("wb") as f: - f.write(b"test") - -with Path("file.txt").open(mode="w") as f: - f.write("test") - -with Path("file.txt").open("w", encoding="utf8") as f: - f.write("test") - -with Path("file.txt").open("w", errors="ignore") as f: - f.write("test") - -with Path(foo()).open("w") as f: - f.write("test") diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB103_EXT.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB103_EXT.py new file mode 100644 index 0000000000..f67f793c3e --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB103_EXT.py @@ -0,0 +1,23 @@ +from pathlib import Path + +with Path("file.txt").open("w") as f: + f.write("test") + +with Path("file.txt").open("wb") as f: + f.write(b"test") + +with Path("file.txt").open(mode="w") as f: + f.write("test") + +with Path("file.txt").open("w", encoding="utf8") as f: + f.write("test") + +with Path("file.txt").open("w", errors="ignore") as f: + f.write("test") + +with Path(foo()).open("w") as f: + f.write("test") + +p = Path("file.txt") +with p.open("w") as f: + f.write("test") \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/refurb/helpers.rs b/crates/ruff_linter/src/rules/refurb/helpers.rs index b851b132d5..325b050873 100644 --- a/crates/ruff_linter/src/rules/refurb/helpers.rs +++ b/crates/ruff_linter/src/rules/refurb/helpers.rs @@ -1,13 +1,14 @@ use std::borrow::Cow; +use ruff_python_ast::PythonVersion; +use ruff_python_ast::{self as ast, Expr, name::Name, parenthesize::parenthesized_range}; +use ruff_python_codegen::Generator; +use ruff_python_semantic::{ResolvedReference, SemanticModel}; +use ruff_text_size::{Ranged, TextRange}; + use crate::checkers::ast::Checker; use crate::rules::flake8_async::rules::blocking_open_call::is_open_call_from_pathlib; use crate::{Applicability, Edit, Fix}; -use ruff_python_ast::PythonVersion; -use ruff_python_ast::{self as ast, Expr, name::Name, parenthesize::parenthesized_range}; -use ruff_python_codegen::Generator; -use ruff_python_semantic::{BindingId, ResolvedReference, SemanticModel}; -use ruff_text_size::{Ranged, TextRange}; /// Format a code snippet to call `name.method()`. pub(super) fn generate_method_call(name: Name, method: &str, generator: Generator) -> String { @@ -120,14 +121,14 @@ pub(super) struct FileOpen<'a> { /// With item where the open happens, we use it for the reporting range. pub(super) item: &'a ast::WithItem, /// Filename expression used as the first argument in `open`, we use it in the diagnostic message. - pub(super) filename: &'a Expr, + pub(super) filename: Option<&'a Expr>, /// The file open mode. pub(super) mode: OpenMode, /// The file open keywords. pub(super) keywords: Vec<&'a ast::Keyword>, /// We only check `open` operations whose file handles are used exactly once. pub(super) reference: &'a ResolvedReference, - /// `Path().open()` call + /// Pathlib Path object used to open the file, if any. pub(super) path_obj: Option<&'a Expr>, } @@ -155,12 +156,13 @@ pub(super) fn find_file_opens<'a>( .collect() } +#[expect(clippy::too_many_arguments)] fn resolve_file_open<'a>( item: &'a ast::WithItem, with: &'a ast::StmtWith, semantic: &'a SemanticModel<'a>, read_mode: bool, - filename: &'a Expr, + filename: Option<&'a Expr>, mode: OpenMode, keywords: Vec<&'a ast::Keyword>, path_obj: Option<&'a Expr>, @@ -181,7 +183,6 @@ fn resolve_file_open<'a>( if matches!(mode, OpenMode::ReadBytes | OpenMode::WriteBytes) && !keywords.is_empty() { return None; } - let var = item.optional_vars.as_deref()?.as_name_expr()?; let scope = semantic.current_scope(); @@ -189,7 +190,6 @@ fn resolve_file_open<'a>( let b = semantic.binding(id); (b.range() == var.range()).then_some(b) })?; - let references: Vec<&ResolvedReference> = binding .references .iter() @@ -247,7 +247,14 @@ fn find_file_open<'a>( let mode = kw_mode.unwrap_or(pos_mode); resolve_file_open( - item, with, semantic, read_mode, filename, mode, keywords, None, + item, + with, + semantic, + read_mode, + Some(filename), + mode, + keywords, + None, ) } @@ -263,17 +270,26 @@ fn find_path_open<'a>( arguments: ast::Arguments { args, keywords, .. }, .. } = item.context_expr.as_call_expr()?; + if args.iter().any(Expr::is_starred_expr) + || keywords.iter().any(|keyword| keyword.arg.is_none()) + { + return None; + } if !is_open_call_from_pathlib(func, semantic) { return None; } let attr = func.as_attribute_expr()?; - let path_call = attr.value.as_call_expr()?; - let filename = path_call.arguments.args.first()?; + let filename = if let Expr::Call(path_call) = attr.value.as_ref() { + path_call.arguments.args.first() + } else { + None + }; let mode = if args.is_empty() { OpenMode::ReadText } else { match_open_mode(args.first()?)? }; + let (keywords, kw_mode) = match_open_keywords(keywords, read_mode, python_version)?; let mode = kw_mode.unwrap_or(mode); resolve_file_open( diff --git a/crates/ruff_linter/src/rules/refurb/mod.rs b/crates/ruff_linter/src/rules/refurb/mod.rs index 9187853141..dd43daa7c4 100644 --- a/crates/ruff_linter/src/rules/refurb/mod.rs +++ b/crates/ruff_linter/src/rules/refurb/mod.rs @@ -47,6 +47,7 @@ mod tests { #[test_case(Rule::HashlibDigestHex, Path::new("FURB181.py"))] #[test_case(Rule::ListReverseCopy, Path::new("FURB187.py"))] #[test_case(Rule::WriteWholeFile, Path::new("FURB103.py"))] + #[test_case(Rule::WriteWholeFile, Path::new("FURB103_EXT.py"))] #[test_case(Rule::FStringNumberFormat, Path::new("FURB116.py"))] #[test_case(Rule::SortedMinMax, Path::new("FURB192.py"))] #[test_case(Rule::SliceToRemovePrefixOrSuffix, Path::new("FURB188.py"))] diff --git a/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs b/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs index 2b43af89a8..1c2e39c0d8 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs @@ -114,12 +114,17 @@ impl<'a> Visitor<'a> for ReadMatcher<'a, '_> { .position(|open| open.is_ref(read_from)) { let open = self.candidates.remove(open); + let filename_display = if let Some(filename) = open.filename { + self.checker.generator().expr(filename) + } else if let Some(path_obj) = open.path_obj { + self.checker.locator().slice(path_obj.range()).to_string() + } else { + return; + }; let suggestion = make_suggestion(&open, self.checker.generator()); let mut diagnostic = self.checker.report_diagnostic( ReadWholeFile { - filename: SourceCodeSnippet::from_str( - &self.checker.generator().expr(open.filename), - ), + filename: SourceCodeSnippet::from_str(&filename_display), suggestion: SourceCodeSnippet::from_str(&suggestion), }, open.item.range(), @@ -185,10 +190,9 @@ fn generate_fix( if with_stmt.items.len() != 1 { return None; } - + let filename = open.filename?; let locator = checker.locator(); - - let filename_code = locator.slice(open.filename.range()); + let filename_code = locator.slice(filename.range()); let (import_edit, binding) = checker .importer() diff --git a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs index d60ce680e6..1d1e5dc422 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs @@ -48,6 +48,7 @@ pub(crate) struct WriteWholeFile { filename: SourceCodeSnippet, suggestion: SourceCodeSnippet, is_path_open: bool, + has_filename: bool, } impl Violation for WriteWholeFile { @@ -58,19 +59,35 @@ impl Violation for WriteWholeFile { let filename = self.filename.truncated_display(); let suggestion = self.suggestion.truncated_display(); if self.is_path_open { - format!( - "`Path.open()` followed by `write()` can be replaced by `Path({filename}).{suggestion}`" - ) + if self.has_filename { + format!( + "`Path().open()` followed by `write()` can be replaced by `Path({filename}).{suggestion}`" + ) + } else { + format!( + "`Path.open()` followed by `write()` can be replaced by `{filename}.{suggestion}`" + ) + } } else { format!("`open` and `write` should be replaced by `Path({filename}).{suggestion}`") } } fn fix_title(&self) -> Option { - Some(format!( - "Replace with `Path({}).{}`", - self.filename.truncated_display(), - self.suggestion.truncated_display(), - )) + let formatted = if self.has_filename { + format!( + "Replace with `Path({}).{}`", + self.filename.truncated_display(), + self.suggestion.truncated_display(), + ) + } else { + format!( + "Replace with `{}.{}`", + self.filename.truncated_display(), + self.suggestion.truncated_display(), + ) + }; + + Some(formatted) } } @@ -134,17 +151,27 @@ impl<'a> Visitor<'a> for WriteMatcher<'a, '_> { .position(|open| open.is_ref(write_to)) { let open = self.candidates.remove(open); - + let has_filename: bool; + let filename_display: String; if self.loop_counter == 0 { + if let Some(filename) = open.filename { + filename_display = self.checker.generator().expr(filename); + has_filename = true; + } else if let Some(path_obj) = open.path_obj { + filename_display = + self.checker.locator().slice(path_obj.range()).to_string(); + has_filename = false; + } else { + return; + } let suggestion = make_suggestion(&open, content, self.checker.generator()); let mut diagnostic = self.checker.report_diagnostic( WriteWholeFile { - filename: SourceCodeSnippet::from_str( - &self.checker.generator().expr(open.filename), - ), + filename: SourceCodeSnippet::from_str(&filename_display), suggestion: SourceCodeSnippet::from_str(&suggestion), is_path_open: open.path_obj.is_some(), + has_filename, }, open.item.range(), ); @@ -214,7 +241,8 @@ fn generate_fix( } let locator = checker.locator(); - let filename_code = locator.slice(open.filename.range()); + let filename = open.filename?; + let filename_code = locator.slice(filename.range()); let (import_edit, binding) = checker .importer() diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap index 343f0a81fa..74f3749953 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap @@ -2,212 +2,146 @@ source: crates/ruff_linter/src/rules/refurb/mod.rs --- FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text("test")` - --> FURB103.py:13:6 + --> FURB103.py:12:6 | -12 | # FURB103 -13 | with open("file.txt", "w") as f: +11 | # FURB103 +12 | with open("file.txt", "w") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -14 | f.write("test") +13 | f.write("test") | help: Replace with `Path("file.txt").write_text("test")` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)` - --> FURB103.py:17:6 + --> FURB103.py:16:6 | -16 | # FURB103 -17 | with open("file.txt", "wb") as f: +15 | # FURB103 +16 | with open("file.txt", "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -18 | f.write(foobar) +17 | f.write(foobar) | help: Replace with `Path("file.txt").write_bytes(foobar)` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")` - --> FURB103.py:21:6 + --> FURB103.py:20:6 | -20 | # FURB103 -21 | with open("file.txt", mode="wb") as f: +19 | # FURB103 +20 | with open("file.txt", mode="wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -22 | f.write(b"abc") +21 | f.write(b"abc") | help: Replace with `Path("file.txt").write_bytes(b"abc")` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")` - --> FURB103.py:25:6 + --> FURB103.py:24:6 | -24 | # FURB103 -25 | with open("file.txt", "w", encoding="utf8") as f: +23 | # FURB103 +24 | with open("file.txt", "w", encoding="utf8") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -26 | f.write(foobar) +25 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, encoding="utf8")` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")` - --> FURB103.py:29:6 + --> FURB103.py:28:6 | -28 | # FURB103 -29 | with open("file.txt", "w", errors="ignore") as f: +27 | # FURB103 +28 | with open("file.txt", "w", errors="ignore") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -30 | f.write(foobar) +29 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, errors="ignore")` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)` - --> FURB103.py:33:6 + --> FURB103.py:32:6 | -32 | # FURB103 -33 | with open("file.txt", mode="w") as f: +31 | # FURB103 +32 | with open("file.txt", mode="w") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -34 | f.write(foobar) +33 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar)` FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())` - --> FURB103.py:37:6 + --> FURB103.py:36:6 | -36 | # FURB103 -37 | with open(foo(), "wb") as f: +35 | # FURB103 +36 | with open(foo(), "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^ -38 | # The body of `with` is non-trivial, but the recommendation holds. -39 | bar("pre") +37 | # The body of `with` is non-trivial, but the recommendation holds. +38 | bar("pre") | help: Replace with `Path(foo()).write_bytes(bar())` FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)` - --> FURB103.py:45:6 + --> FURB103.py:44:6 | -44 | # FURB103 -45 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: +43 | # FURB103 +44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: | ^^^^^^^^^^^^^^^^^^^^^^^ -46 | a.write(x) -47 | b.write(y) +45 | a.write(x) +46 | b.write(y) | help: Replace with `Path("a.txt").write_text(x)` FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)` - --> FURB103.py:45:31 + --> FURB103.py:44:31 | -44 | # FURB103 -45 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: +43 | # FURB103 +44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: | ^^^^^^^^^^^^^^^^^^^^^^^^ -46 | a.write(x) -47 | b.write(y) +45 | a.write(x) +46 | b.write(y) | help: Replace with `Path("b.txt").write_bytes(y)` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(bar(bar(a + x)))` - --> FURB103.py:50:18 + --> FURB103.py:49:18 | -49 | # FURB103 -50 | with foo() as a, open("file.txt", "w") as b, foo() as c: +48 | # FURB103 +49 | with foo() as a, open("file.txt", "w") as b, foo() as c: | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -51 | # We have other things in here, multiple with items, but the user -52 | # writes a single time to file and that bit they can replace. +50 | # We have other things in here, multiple with items, but the user +51 | # writes a single time to file and that bit they can replace. | help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` - --> FURB103.py:59:6 + --> FURB103.py:58:6 | -58 | # FURB103 -59 | with open("file.txt", "w", newline="\r\n") as f: +57 | # FURB103 +58 | with open("file.txt", "w", newline="\r\n") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -60 | f.write(foobar) +59 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` - --> FURB103.py:67:6 + --> FURB103.py:66:6 | -66 | # FURB103 -67 | with builtins.open("file.txt", "w", newline="\r\n") as f: +65 | # FURB103 +66 | with builtins.open("file.txt", "w", newline="\r\n") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -68 | f.write(foobar) +67 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` - --> FURB103.py:75:6 + --> FURB103.py:74:6 | -74 | # FURB103 -75 | with o("file.txt", "w", newline="\r\n") as f: +73 | # FURB103 +74 | with o("file.txt", "w", newline="\r\n") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -76 | f.write(foobar) +75 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` FURB103 `open` and `write` should be replaced by `Path("test.json")....` - --> FURB103.py:155:6 + --> FURB103.py:154:6 | -153 | data = {"price": 100} -154 | -155 | with open("test.json", "wb") as f: +152 | data = {"price": 100} +153 | +154 | with open("test.json", "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -156 | f.write(json.dumps(data, indent=4).encode("utf-8")) +155 | f.write(json.dumps(data, indent=4).encode("utf-8")) | help: Replace with `Path("test.json")....` - -FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` - --> FURB103.py:158:6 - | -156 | f.write(json.dumps(data, indent=4).encode("utf-8")) -157 | -158 | with Path("file.txt").open("w") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -159 | f.write("test") - | -help: Replace with `Path("file.txt").write_text("test")` - -FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_bytes(b"test")` - --> FURB103.py:161:6 - | -159 | f.write("test") -160 | -161 | with Path("file.txt").open("wb") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -162 | f.write(b"test") - | -help: Replace with `Path("file.txt").write_bytes(b"test")` - -FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` - --> FURB103.py:164:6 - | -162 | f.write(b"test") -163 | -164 | with Path("file.txt").open(mode="w") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -165 | f.write("test") - | -help: Replace with `Path("file.txt").write_text("test")` - -FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", encoding="utf8")` - --> FURB103.py:167:6 - | -165 | f.write("test") -166 | -167 | with Path("file.txt").open("w", encoding="utf8") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -168 | f.write("test") - | -help: Replace with `Path("file.txt").write_text("test", encoding="utf8")` - -FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", errors="ignore")` - --> FURB103.py:170:6 - | -168 | f.write("test") -169 | -170 | with Path("file.txt").open("w", errors="ignore") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -171 | f.write("test") - | -help: Replace with `Path("file.txt").write_text("test", errors="ignore")` - -FURB103 `Path.open()` followed by `write()` can be replaced by `Path(foo()).write_text("test")` - --> FURB103.py:173:6 - | -171 | f.write("test") -172 | -173 | with Path(foo()).open("w") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -174 | f.write("test") - | -help: Replace with `Path(foo()).write_text("test")` diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_EXT.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_EXT.py.snap new file mode 100644 index 0000000000..53a498e891 --- /dev/null +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_EXT.py.snap @@ -0,0 +1,78 @@ +--- +source: crates/ruff_linter/src/rules/refurb/mod.rs +--- +FURB103 `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` + --> FURB103_EXT.py:3:6 + | +1 | from pathlib import Path +2 | +3 | with Path("file.txt").open("w") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +4 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test")` + +FURB103 `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_bytes(b"test")` + --> FURB103_EXT.py:6:6 + | +4 | f.write("test") +5 | +6 | with Path("file.txt").open("wb") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +7 | f.write(b"test") + | +help: Replace with `Path("file.txt").write_bytes(b"test")` + +FURB103 `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` + --> FURB103_EXT.py:9:6 + | + 7 | f.write(b"test") + 8 | + 9 | with Path("file.txt").open(mode="w") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +10 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test")` + +FURB103 `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", encoding="utf8")` + --> FURB103_EXT.py:12:6 + | +10 | f.write("test") +11 | +12 | with Path("file.txt").open("w", encoding="utf8") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +13 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test", encoding="utf8")` + +FURB103 `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", errors="ignore")` + --> FURB103_EXT.py:15:6 + | +13 | f.write("test") +14 | +15 | with Path("file.txt").open("w", errors="ignore") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +16 | f.write("test") + | +help: Replace with `Path("file.txt").write_text("test", errors="ignore")` + +FURB103 `Path().open()` followed by `write()` can be replaced by `Path(foo()).write_text("test")` + --> FURB103_EXT.py:18:6 + | +16 | f.write("test") +17 | +18 | with Path(foo()).open("w") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +19 | f.write("test") + | +help: Replace with `Path(foo()).write_text("test")` + +FURB103 `Path.open()` followed by `write()` can be replaced by `p.write_text("test")` + --> FURB103_EXT.py:22:6 + | +21 | p = Path("file.txt") +22 | with p.open("w") as f: + | ^^^^^^^^^^^^^^^^ +23 | f.write("test") + | +help: Replace with `p.write_text("test")` diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap index 38636f3465..d6ea939bbb 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap @@ -2,353 +2,282 @@ source: crates/ruff_linter/src/rules/refurb/mod.rs --- FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text("test")` - --> FURB103.py:13:6 + --> FURB103.py:12:6 | -12 | # FURB103 -13 | with open("file.txt", "w") as f: +11 | # FURB103 +12 | with open("file.txt", "w") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -14 | f.write("test") +13 | f.write("test") | help: Replace with `Path("file.txt").write_text("test")` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- 10 | # Errors. 11 | 12 | # FURB103 - with open("file.txt", "w") as f: - f.write("test") -13 + Path("file.txt").write_text("test") +13 + pathlib.Path("file.txt").write_text("test") 14 | 15 | # FURB103 16 | with open("file.txt", "wb") as f: FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)` - --> FURB103.py:17:6 + --> FURB103.py:16:6 | -16 | # FURB103 -17 | with open("file.txt", "wb") as f: +15 | # FURB103 +16 | with open("file.txt", "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -18 | f.write(foobar) +17 | f.write(foobar) | help: Replace with `Path("file.txt").write_bytes(foobar)` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- 14 | f.write("test") 15 | 16 | # FURB103 - with open("file.txt", "wb") as f: - f.write(foobar) -17 + Path("file.txt").write_bytes(foobar) +17 + pathlib.Path("file.txt").write_bytes(foobar) 18 | 19 | # FURB103 20 | with open("file.txt", mode="wb") as f: FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")` - --> FURB103.py:21:6 + --> FURB103.py:20:6 | -20 | # FURB103 -21 | with open("file.txt", mode="wb") as f: +19 | # FURB103 +20 | with open("file.txt", mode="wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -22 | f.write(b"abc") +21 | f.write(b"abc") | help: Replace with `Path("file.txt").write_bytes(b"abc")` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- 18 | f.write(foobar) 19 | 20 | # FURB103 - with open("file.txt", mode="wb") as f: - f.write(b"abc") -21 + Path("file.txt").write_bytes(b"abc") +21 + pathlib.Path("file.txt").write_bytes(b"abc") 22 | 23 | # FURB103 24 | with open("file.txt", "w", encoding="utf8") as f: FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")` - --> FURB103.py:25:6 + --> FURB103.py:24:6 | -24 | # FURB103 -25 | with open("file.txt", "w", encoding="utf8") as f: +23 | # FURB103 +24 | with open("file.txt", "w", encoding="utf8") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -26 | f.write(foobar) +25 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, encoding="utf8")` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- 22 | f.write(b"abc") 23 | 24 | # FURB103 - with open("file.txt", "w", encoding="utf8") as f: - f.write(foobar) -25 + Path("file.txt").write_text(foobar, encoding="utf8") +25 + pathlib.Path("file.txt").write_text(foobar, encoding="utf8") 26 | 27 | # FURB103 28 | with open("file.txt", "w", errors="ignore") as f: FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")` - --> FURB103.py:29:6 + --> FURB103.py:28:6 | -28 | # FURB103 -29 | with open("file.txt", "w", errors="ignore") as f: +27 | # FURB103 +28 | with open("file.txt", "w", errors="ignore") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -30 | f.write(foobar) +29 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, errors="ignore")` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- 26 | f.write(foobar) 27 | 28 | # FURB103 - with open("file.txt", "w", errors="ignore") as f: - f.write(foobar) -29 + Path("file.txt").write_text(foobar, errors="ignore") +29 + pathlib.Path("file.txt").write_text(foobar, errors="ignore") 30 | 31 | # FURB103 32 | with open("file.txt", mode="w") as f: FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)` - --> FURB103.py:33:6 + --> FURB103.py:32:6 | -32 | # FURB103 -33 | with open("file.txt", mode="w") as f: +31 | # FURB103 +32 | with open("file.txt", mode="w") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -34 | f.write(foobar) +33 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar)` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- 30 | f.write(foobar) 31 | 32 | # FURB103 - with open("file.txt", mode="w") as f: - f.write(foobar) -33 + Path("file.txt").write_text(foobar) +33 + pathlib.Path("file.txt").write_text(foobar) 34 | 35 | # FURB103 36 | with open(foo(), "wb") as f: FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())` - --> FURB103.py:37:6 + --> FURB103.py:36:6 | -36 | # FURB103 -37 | with open(foo(), "wb") as f: +35 | # FURB103 +36 | with open(foo(), "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^ -38 | # The body of `with` is non-trivial, but the recommendation holds. -39 | bar("pre") +37 | # The body of `with` is non-trivial, but the recommendation holds. +38 | bar("pre") | help: Replace with `Path(foo()).write_bytes(bar())` FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)` - --> FURB103.py:45:6 + --> FURB103.py:44:6 | -44 | # FURB103 -45 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: +43 | # FURB103 +44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: | ^^^^^^^^^^^^^^^^^^^^^^^ -46 | a.write(x) -47 | b.write(y) +45 | a.write(x) +46 | b.write(y) | help: Replace with `Path("a.txt").write_text(x)` FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)` - --> FURB103.py:45:31 + --> FURB103.py:44:31 | -44 | # FURB103 -45 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: +43 | # FURB103 +44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: | ^^^^^^^^^^^^^^^^^^^^^^^^ -46 | a.write(x) -47 | b.write(y) +45 | a.write(x) +46 | b.write(y) | help: Replace with `Path("b.txt").write_bytes(y)` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(bar(bar(a + x)))` - --> FURB103.py:50:18 + --> FURB103.py:49:18 | -49 | # FURB103 -50 | with foo() as a, open("file.txt", "w") as b, foo() as c: +48 | # FURB103 +49 | with foo() as a, open("file.txt", "w") as b, foo() as c: | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -51 | # We have other things in here, multiple with items, but the user -52 | # writes a single time to file and that bit they can replace. +50 | # We have other things in here, multiple with items, but the user +51 | # writes a single time to file and that bit they can replace. | help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))` FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` - --> FURB103.py:59:6 + --> FURB103.py:58:6 | -58 | # FURB103 -59 | with open("file.txt", "w", newline="\r\n") as f: +57 | # FURB103 +58 | with open("file.txt", "w", newline="\r\n") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -60 | f.write(foobar) +59 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- 56 | 57 | 58 | # FURB103 - with open("file.txt", "w", newline="\r\n") as f: - f.write(foobar) -59 + Path("file.txt").write_text(foobar, newline="\r\n") +59 + pathlib.Path("file.txt").write_text(foobar, newline="\r\n") 60 | 61 | 62 | import builtins FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` - --> FURB103.py:67:6 + --> FURB103.py:66:6 | -66 | # FURB103 -67 | with builtins.open("file.txt", "w", newline="\r\n") as f: +65 | # FURB103 +66 | with builtins.open("file.txt", "w", newline="\r\n") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -68 | f.write(foobar) +67 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` +60 | +61 | +62 | import builtins +63 + import pathlib 64 | 65 | 66 | # FURB103 - with builtins.open("file.txt", "w", newline="\r\n") as f: - f.write(foobar) -67 + Path("file.txt").write_text(foobar, newline="\r\n") +67 + pathlib.Path("file.txt").write_text(foobar, newline="\r\n") 68 | 69 | 70 | from builtins import open as o FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` - --> FURB103.py:75:6 + --> FURB103.py:74:6 | -74 | # FURB103 -75 | with o("file.txt", "w", newline="\r\n") as f: +73 | # FURB103 +74 | with o("file.txt", "w", newline="\r\n") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -76 | f.write(foobar) +75 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` +68 | +69 | +70 | from builtins import open as o +71 + import pathlib 72 | 73 | 74 | # FURB103 - with o("file.txt", "w", newline="\r\n") as f: - f.write(foobar) -75 + Path("file.txt").write_text(foobar, newline="\r\n") +75 + pathlib.Path("file.txt").write_text(foobar, newline="\r\n") 76 | 77 | # Non-errors. 78 | FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....` - --> FURB103.py:155:6 + --> FURB103.py:154:6 | -153 | data = {"price": 100} -154 | -155 | with open("test.json", "wb") as f: +152 | data = {"price": 100} +153 | +154 | with open("test.json", "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -156 | f.write(json.dumps(data, indent=4).encode("utf-8")) +155 | f.write(json.dumps(data, indent=4).encode("utf-8")) | help: Replace with `Path("test.json")....` +148 | +149 | # See: https://github.com/astral-sh/ruff/issues/20785 +150 | import json +151 + import pathlib 152 | 153 | data = {"price": 100} 154 | - with open("test.json", "wb") as f: - f.write(json.dumps(data, indent=4).encode("utf-8")) -155 + Path("test.json").write_bytes(json.dumps(data, indent=4).encode("utf-8")) +155 + pathlib.Path("test.json").write_bytes(json.dumps(data, indent=4).encode("utf-8")) 156 | -157 | with Path("file.txt").open("w") as f: -158 | f.write("test") - -FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` - --> FURB103.py:158:6 - | -156 | f.write(json.dumps(data, indent=4).encode("utf-8")) 157 | -158 | with Path("file.txt").open("w") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -159 | f.write("test") - | -help: Replace with `Path("file.txt").write_text("test")` -155 | with open("test.json", "wb") as f: -156 | f.write(json.dumps(data, indent=4).encode("utf-8")) -157 | - - with Path("file.txt").open("w") as f: - - f.write("test") -158 + Path("file.txt").write_text("test") -159 | -160 | with Path("file.txt").open("wb") as f: -161 | f.write(b"test") - -FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_bytes(b"test")` - --> FURB103.py:161:6 - | -159 | f.write("test") -160 | -161 | with Path("file.txt").open("wb") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -162 | f.write(b"test") - | -help: Replace with `Path("file.txt").write_bytes(b"test")` -158 | with Path("file.txt").open("w") as f: -159 | f.write("test") -160 | - - with Path("file.txt").open("wb") as f: - - f.write(b"test") -161 + Path("file.txt").write_bytes(b"test") -162 | -163 | with Path("file.txt").open(mode="w") as f: -164 | f.write("test") - -FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` - --> FURB103.py:164:6 - | -162 | f.write(b"test") -163 | -164 | with Path("file.txt").open(mode="w") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -165 | f.write("test") - | -help: Replace with `Path("file.txt").write_text("test")` -161 | with Path("file.txt").open("wb") as f: -162 | f.write(b"test") -163 | - - with Path("file.txt").open(mode="w") as f: - - f.write("test") -164 + Path("file.txt").write_text("test") -165 | -166 | with Path("file.txt").open("w", encoding="utf8") as f: -167 | f.write("test") - -FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", encoding="utf8")` - --> FURB103.py:167:6 - | -165 | f.write("test") -166 | -167 | with Path("file.txt").open("w", encoding="utf8") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -168 | f.write("test") - | -help: Replace with `Path("file.txt").write_text("test", encoding="utf8")` -164 | with Path("file.txt").open(mode="w") as f: -165 | f.write("test") -166 | - - with Path("file.txt").open("w", encoding="utf8") as f: - - f.write("test") -167 + Path("file.txt").write_text("test", encoding="utf8") -168 | -169 | with Path("file.txt").open("w", errors="ignore") as f: -170 | f.write("test") - -FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", errors="ignore")` - --> FURB103.py:170:6 - | -168 | f.write("test") -169 | -170 | with Path("file.txt").open("w", errors="ignore") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -171 | f.write("test") - | -help: Replace with `Path("file.txt").write_text("test", errors="ignore")` -167 | with Path("file.txt").open("w", encoding="utf8") as f: -168 | f.write("test") -169 | - - with Path("file.txt").open("w", errors="ignore") as f: - - f.write("test") -170 + Path("file.txt").write_text("test", errors="ignore") -171 | -172 | with Path(foo()).open("w") as f: -173 | f.write("test") - -FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path(foo()).write_text("test")` - --> FURB103.py:173:6 - | -171 | f.write("test") -172 | -173 | with Path(foo()).open("w") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -174 | f.write("test") - | -help: Replace with `Path(foo()).write_text("test")` -170 | with Path("file.txt").open("w", errors="ignore") as f: -171 | f.write("test") -172 | - - with Path(foo()).open("w") as f: - - f.write("test") -173 + Path(foo()).write_text("test") diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap index d83f7330dc..140a274468 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap @@ -2,182 +2,116 @@ source: crates/ruff_linter/src/rules/refurb/mod.rs --- FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text("test")` - --> FURB103.py:13:6 + --> FURB103.py:12:6 | -12 | # FURB103 -13 | with open("file.txt", "w") as f: +11 | # FURB103 +12 | with open("file.txt", "w") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -14 | f.write("test") +13 | f.write("test") | help: Replace with `Path("file.txt").write_text("test")` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)` - --> FURB103.py:17:6 + --> FURB103.py:16:6 | -16 | # FURB103 -17 | with open("file.txt", "wb") as f: +15 | # FURB103 +16 | with open("file.txt", "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -18 | f.write(foobar) +17 | f.write(foobar) | help: Replace with `Path("file.txt").write_bytes(foobar)` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")` - --> FURB103.py:21:6 + --> FURB103.py:20:6 | -20 | # FURB103 -21 | with open("file.txt", mode="wb") as f: +19 | # FURB103 +20 | with open("file.txt", mode="wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -22 | f.write(b"abc") +21 | f.write(b"abc") | help: Replace with `Path("file.txt").write_bytes(b"abc")` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")` - --> FURB103.py:25:6 + --> FURB103.py:24:6 | -24 | # FURB103 -25 | with open("file.txt", "w", encoding="utf8") as f: +23 | # FURB103 +24 | with open("file.txt", "w", encoding="utf8") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -26 | f.write(foobar) +25 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, encoding="utf8")` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")` - --> FURB103.py:29:6 + --> FURB103.py:28:6 | -28 | # FURB103 -29 | with open("file.txt", "w", errors="ignore") as f: +27 | # FURB103 +28 | with open("file.txt", "w", errors="ignore") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -30 | f.write(foobar) +29 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, errors="ignore")` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)` - --> FURB103.py:33:6 + --> FURB103.py:32:6 | -32 | # FURB103 -33 | with open("file.txt", mode="w") as f: +31 | # FURB103 +32 | with open("file.txt", mode="w") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -34 | f.write(foobar) +33 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar)` FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())` - --> FURB103.py:37:6 + --> FURB103.py:36:6 | -36 | # FURB103 -37 | with open(foo(), "wb") as f: +35 | # FURB103 +36 | with open(foo(), "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^ -38 | # The body of `with` is non-trivial, but the recommendation holds. -39 | bar("pre") +37 | # The body of `with` is non-trivial, but the recommendation holds. +38 | bar("pre") | help: Replace with `Path(foo()).write_bytes(bar())` FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)` - --> FURB103.py:45:6 + --> FURB103.py:44:6 | -44 | # FURB103 -45 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: +43 | # FURB103 +44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: | ^^^^^^^^^^^^^^^^^^^^^^^ -46 | a.write(x) -47 | b.write(y) +45 | a.write(x) +46 | b.write(y) | help: Replace with `Path("a.txt").write_text(x)` FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)` - --> FURB103.py:45:31 + --> FURB103.py:44:31 | -44 | # FURB103 -45 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: +43 | # FURB103 +44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: | ^^^^^^^^^^^^^^^^^^^^^^^^ -46 | a.write(x) -47 | b.write(y) +45 | a.write(x) +46 | b.write(y) | help: Replace with `Path("b.txt").write_bytes(y)` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(bar(bar(a + x)))` - --> FURB103.py:50:18 + --> FURB103.py:49:18 | -49 | # FURB103 -50 | with foo() as a, open("file.txt", "w") as b, foo() as c: +48 | # FURB103 +49 | with foo() as a, open("file.txt", "w") as b, foo() as c: | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -51 | # We have other things in here, multiple with items, but the user -52 | # writes a single time to file and that bit they can replace. +50 | # We have other things in here, multiple with items, but the user +51 | # writes a single time to file and that bit they can replace. | help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))` FURB103 `open` and `write` should be replaced by `Path("test.json")....` - --> FURB103.py:155:6 + --> FURB103.py:154:6 | -153 | data = {"price": 100} -154 | -155 | with open("test.json", "wb") as f: +152 | data = {"price": 100} +153 | +154 | with open("test.json", "wb") as f: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -156 | f.write(json.dumps(data, indent=4).encode("utf-8")) +155 | f.write(json.dumps(data, indent=4).encode("utf-8")) | help: Replace with `Path("test.json")....` - -FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` - --> FURB103.py:158:6 - | -156 | f.write(json.dumps(data, indent=4).encode("utf-8")) -157 | -158 | with Path("file.txt").open("w") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -159 | f.write("test") - | -help: Replace with `Path("file.txt").write_text("test")` - -FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_bytes(b"test")` - --> FURB103.py:161:6 - | -159 | f.write("test") -160 | -161 | with Path("file.txt").open("wb") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -162 | f.write(b"test") - | -help: Replace with `Path("file.txt").write_bytes(b"test")` - -FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` - --> FURB103.py:164:6 - | -162 | f.write(b"test") -163 | -164 | with Path("file.txt").open(mode="w") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -165 | f.write("test") - | -help: Replace with `Path("file.txt").write_text("test")` - -FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", encoding="utf8")` - --> FURB103.py:167:6 - | -165 | f.write("test") -166 | -167 | with Path("file.txt").open("w", encoding="utf8") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -168 | f.write("test") - | -help: Replace with `Path("file.txt").write_text("test", encoding="utf8")` - -FURB103 `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", errors="ignore")` - --> FURB103.py:170:6 - | -168 | f.write("test") -169 | -170 | with Path("file.txt").open("w", errors="ignore") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -171 | f.write("test") - | -help: Replace with `Path("file.txt").write_text("test", errors="ignore")` - -FURB103 `Path.open()` followed by `write()` can be replaced by `Path(foo()).write_text("test")` - --> FURB103.py:173:6 - | -171 | f.write("test") -172 | -173 | with Path(foo()).open("w") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -174 | f.write("test") - | -help: Replace with `Path(foo()).write_text("test")` From 971414bfdb1b300a01393806ec52d5536f78f105 Mon Sep 17 00:00:00 2001 From: 11happy Date: Mon, 10 Nov 2025 18:39:33 +0530 Subject: [PATCH 05/11] update snapshot Signed-off-by: 11happy --- ...es__refurb__tests__FURB103_FURB103.py.snap | 156 ++++++++++++++++-- ...refurb__tests__FURB103_FURB103_EXT.py.snap | 65 +++++++- ...rb__tests__write_whole_file_python_39.snap | 110 +++++++++++- 3 files changed, 308 insertions(+), 23 deletions(-) diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap index 74f3749953..d6ea939bbb 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/refurb/mod.rs --- -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text("test")` +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text("test")` --> FURB103.py:12:6 | 11 | # FURB103 @@ -10,8 +10,22 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text("t 13 | f.write("test") | help: Replace with `Path("file.txt").write_text("test")` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- +10 | # Errors. +11 | +12 | # FURB103 + - with open("file.txt", "w") as f: + - f.write("test") +13 + pathlib.Path("file.txt").write_text("test") +14 | +15 | # FURB103 +16 | with open("file.txt", "wb") as f: -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)` +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)` --> FURB103.py:16:6 | 15 | # FURB103 @@ -20,8 +34,22 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(f 17 | f.write(foobar) | help: Replace with `Path("file.txt").write_bytes(foobar)` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- +14 | f.write("test") +15 | +16 | # FURB103 + - with open("file.txt", "wb") as f: + - f.write(foobar) +17 + pathlib.Path("file.txt").write_bytes(foobar) +18 | +19 | # FURB103 +20 | with open("file.txt", mode="wb") as f: -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")` +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")` --> FURB103.py:20:6 | 19 | # FURB103 @@ -30,8 +58,22 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(b 21 | f.write(b"abc") | help: Replace with `Path("file.txt").write_bytes(b"abc")` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- +18 | f.write(foobar) +19 | +20 | # FURB103 + - with open("file.txt", mode="wb") as f: + - f.write(b"abc") +21 + pathlib.Path("file.txt").write_bytes(b"abc") +22 | +23 | # FURB103 +24 | with open("file.txt", "w", encoding="utf8") as f: -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")` +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")` --> FURB103.py:24:6 | 23 | # FURB103 @@ -40,8 +82,22 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(fo 25 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, encoding="utf8")` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- +22 | f.write(b"abc") +23 | +24 | # FURB103 + - with open("file.txt", "w", encoding="utf8") as f: + - f.write(foobar) +25 + pathlib.Path("file.txt").write_text(foobar, encoding="utf8") +26 | +27 | # FURB103 +28 | with open("file.txt", "w", errors="ignore") as f: -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")` +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")` --> FURB103.py:28:6 | 27 | # FURB103 @@ -50,8 +106,22 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(fo 29 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, errors="ignore")` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- +26 | f.write(foobar) +27 | +28 | # FURB103 + - with open("file.txt", "w", errors="ignore") as f: + - f.write(foobar) +29 + pathlib.Path("file.txt").write_text(foobar, errors="ignore") +30 | +31 | # FURB103 +32 | with open("file.txt", mode="w") as f: -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)` +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)` --> FURB103.py:32:6 | 31 | # FURB103 @@ -60,6 +130,20 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(fo 33 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar)` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- +30 | f.write(foobar) +31 | +32 | # FURB103 + - with open("file.txt", mode="w") as f: + - f.write(foobar) +33 + pathlib.Path("file.txt").write_text(foobar) +34 | +35 | # FURB103 +36 | with open(foo(), "wb") as f: FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())` --> FURB103.py:36:6 @@ -105,7 +189,7 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(ba | help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))` -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` --> FURB103.py:58:6 | 57 | # FURB103 @@ -114,8 +198,22 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(fo 59 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- +56 | +57 | +58 | # FURB103 + - with open("file.txt", "w", newline="\r\n") as f: + - f.write(foobar) +59 + pathlib.Path("file.txt").write_text(foobar, newline="\r\n") +60 | +61 | +62 | import builtins -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` --> FURB103.py:66:6 | 65 | # FURB103 @@ -124,8 +222,21 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(fo 67 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` +60 | +61 | +62 | import builtins +63 + import pathlib +64 | +65 | +66 | # FURB103 + - with builtins.open("file.txt", "w", newline="\r\n") as f: + - f.write(foobar) +67 + pathlib.Path("file.txt").write_text(foobar, newline="\r\n") +68 | +69 | +70 | from builtins import open as o -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` --> FURB103.py:74:6 | 73 | # FURB103 @@ -134,8 +245,21 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(fo 75 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` +68 | +69 | +70 | from builtins import open as o +71 + import pathlib +72 | +73 | +74 | # FURB103 + - with o("file.txt", "w", newline="\r\n") as f: + - f.write(foobar) +75 + pathlib.Path("file.txt").write_text(foobar, newline="\r\n") +76 | +77 | # Non-errors. +78 | -FURB103 `open` and `write` should be replaced by `Path("test.json")....` +FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....` --> FURB103.py:154:6 | 152 | data = {"price": 100} @@ -145,3 +269,15 @@ FURB103 `open` and `write` should be replaced by `Path("test.json")....` 155 | f.write(json.dumps(data, indent=4).encode("utf-8")) | help: Replace with `Path("test.json")....` +148 | +149 | # See: https://github.com/astral-sh/ruff/issues/20785 +150 | import json +151 + import pathlib +152 | +153 | data = {"price": 100} +154 | + - with open("test.json", "wb") as f: + - f.write(json.dumps(data, indent=4).encode("utf-8")) +155 + pathlib.Path("test.json").write_bytes(json.dumps(data, indent=4).encode("utf-8")) +156 | +157 | diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_EXT.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_EXT.py.snap index 53a498e891..455a5d80f0 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_EXT.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_EXT.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/refurb/mod.rs --- -FURB103 `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` +FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` --> FURB103_EXT.py:3:6 | 1 | from pathlib import Path @@ -11,8 +11,16 @@ FURB103 `Path().open()` followed by `write()` can be replaced by `Path("file.txt 4 | f.write("test") | help: Replace with `Path("file.txt").write_text("test")` +1 | from pathlib import Path +2 | + - with Path("file.txt").open("w") as f: + - f.write("test") +3 + Path("file.txt").write_text("test") +4 | +5 | with Path("file.txt").open("wb") as f: +6 | f.write(b"test") -FURB103 `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_bytes(b"test")` +FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_bytes(b"test")` --> FURB103_EXT.py:6:6 | 4 | f.write("test") @@ -22,8 +30,17 @@ FURB103 `Path().open()` followed by `write()` can be replaced by `Path("file.txt 7 | f.write(b"test") | help: Replace with `Path("file.txt").write_bytes(b"test")` +3 | with Path("file.txt").open("w") as f: +4 | f.write("test") +5 | + - with Path("file.txt").open("wb") as f: + - f.write(b"test") +6 + Path("file.txt").write_bytes(b"test") +7 | +8 | with Path("file.txt").open(mode="w") as f: +9 | f.write("test") -FURB103 `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` +FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` --> FURB103_EXT.py:9:6 | 7 | f.write(b"test") @@ -33,8 +50,17 @@ FURB103 `Path().open()` followed by `write()` can be replaced by `Path("file.txt 10 | f.write("test") | help: Replace with `Path("file.txt").write_text("test")` +6 | with Path("file.txt").open("wb") as f: +7 | f.write(b"test") +8 | + - with Path("file.txt").open(mode="w") as f: + - f.write("test") +9 + Path("file.txt").write_text("test") +10 | +11 | with Path("file.txt").open("w", encoding="utf8") as f: +12 | f.write("test") -FURB103 `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", encoding="utf8")` +FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", encoding="utf8")` --> FURB103_EXT.py:12:6 | 10 | f.write("test") @@ -44,8 +70,17 @@ FURB103 `Path().open()` followed by `write()` can be replaced by `Path("file.txt 13 | f.write("test") | help: Replace with `Path("file.txt").write_text("test", encoding="utf8")` +9 | with Path("file.txt").open(mode="w") as f: +10 | f.write("test") +11 | + - with Path("file.txt").open("w", encoding="utf8") as f: + - f.write("test") +12 + Path("file.txt").write_text("test", encoding="utf8") +13 | +14 | with Path("file.txt").open("w", errors="ignore") as f: +15 | f.write("test") -FURB103 `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", errors="ignore")` +FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", errors="ignore")` --> FURB103_EXT.py:15:6 | 13 | f.write("test") @@ -55,8 +90,17 @@ FURB103 `Path().open()` followed by `write()` can be replaced by `Path("file.txt 16 | f.write("test") | help: Replace with `Path("file.txt").write_text("test", errors="ignore")` +12 | with Path("file.txt").open("w", encoding="utf8") as f: +13 | f.write("test") +14 | + - with Path("file.txt").open("w", errors="ignore") as f: + - f.write("test") +15 + Path("file.txt").write_text("test", errors="ignore") +16 | +17 | with Path(foo()).open("w") as f: +18 | f.write("test") -FURB103 `Path().open()` followed by `write()` can be replaced by `Path(foo()).write_text("test")` +FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path(foo()).write_text("test")` --> FURB103_EXT.py:18:6 | 16 | f.write("test") @@ -66,6 +110,15 @@ FURB103 `Path().open()` followed by `write()` can be replaced by `Path(foo()).wr 19 | f.write("test") | help: Replace with `Path(foo()).write_text("test")` +15 | with Path("file.txt").open("w", errors="ignore") as f: +16 | f.write("test") +17 | + - with Path(foo()).open("w") as f: + - f.write("test") +18 + Path(foo()).write_text("test") +19 | +20 | p = Path("file.txt") +21 | with p.open("w") as f: FURB103 `Path.open()` followed by `write()` can be replaced by `p.write_text("test")` --> FURB103_EXT.py:22:6 diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap index 140a274468..ee7a295b39 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/refurb/mod.rs --- -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text("test")` +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text("test")` --> FURB103.py:12:6 | 11 | # FURB103 @@ -10,8 +10,22 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text("t 13 | f.write("test") | help: Replace with `Path("file.txt").write_text("test")` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- +10 | # Errors. +11 | +12 | # FURB103 + - with open("file.txt", "w") as f: + - f.write("test") +13 + pathlib.Path("file.txt").write_text("test") +14 | +15 | # FURB103 +16 | with open("file.txt", "wb") as f: -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)` +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)` --> FURB103.py:16:6 | 15 | # FURB103 @@ -20,8 +34,22 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(f 17 | f.write(foobar) | help: Replace with `Path("file.txt").write_bytes(foobar)` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- +14 | f.write("test") +15 | +16 | # FURB103 + - with open("file.txt", "wb") as f: + - f.write(foobar) +17 + pathlib.Path("file.txt").write_bytes(foobar) +18 | +19 | # FURB103 +20 | with open("file.txt", mode="wb") as f: -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")` +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")` --> FURB103.py:20:6 | 19 | # FURB103 @@ -30,8 +58,22 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_bytes(b 21 | f.write(b"abc") | help: Replace with `Path("file.txt").write_bytes(b"abc")` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- +18 | f.write(foobar) +19 | +20 | # FURB103 + - with open("file.txt", mode="wb") as f: + - f.write(b"abc") +21 + pathlib.Path("file.txt").write_bytes(b"abc") +22 | +23 | # FURB103 +24 | with open("file.txt", "w", encoding="utf8") as f: -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")` +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")` --> FURB103.py:24:6 | 23 | # FURB103 @@ -40,8 +82,22 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(fo 25 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, encoding="utf8")` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- +22 | f.write(b"abc") +23 | +24 | # FURB103 + - with open("file.txt", "w", encoding="utf8") as f: + - f.write(foobar) +25 + pathlib.Path("file.txt").write_text(foobar, encoding="utf8") +26 | +27 | # FURB103 +28 | with open("file.txt", "w", errors="ignore") as f: -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")` +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")` --> FURB103.py:28:6 | 27 | # FURB103 @@ -50,8 +106,22 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(fo 29 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar, errors="ignore")` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- +26 | f.write(foobar) +27 | +28 | # FURB103 + - with open("file.txt", "w", errors="ignore") as f: + - f.write(foobar) +29 + pathlib.Path("file.txt").write_text(foobar, errors="ignore") +30 | +31 | # FURB103 +32 | with open("file.txt", mode="w") as f: -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)` +FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)` --> FURB103.py:32:6 | 31 | # FURB103 @@ -60,6 +130,20 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(fo 33 | f.write(foobar) | help: Replace with `Path("file.txt").write_text(foobar)` +1 + import pathlib +2 | def foo(): +3 | ... +4 | +-------------------------------------------------------------------------------- +30 | f.write(foobar) +31 | +32 | # FURB103 + - with open("file.txt", mode="w") as f: + - f.write(foobar) +33 + pathlib.Path("file.txt").write_text(foobar) +34 | +35 | # FURB103 +36 | with open(foo(), "wb") as f: FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())` --> FURB103.py:36:6 @@ -105,7 +189,7 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(ba | help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))` -FURB103 `open` and `write` should be replaced by `Path("test.json")....` +FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....` --> FURB103.py:154:6 | 152 | data = {"price": 100} @@ -115,3 +199,15 @@ FURB103 `open` and `write` should be replaced by `Path("test.json")....` 155 | f.write(json.dumps(data, indent=4).encode("utf-8")) | help: Replace with `Path("test.json")....` +148 | +149 | # See: https://github.com/astral-sh/ruff/issues/20785 +150 | import json +151 + import pathlib +152 | +153 | data = {"price": 100} +154 | + - with open("test.json", "wb") as f: + - f.write(json.dumps(data, indent=4).encode("utf-8")) +155 + pathlib.Path("test.json").write_bytes(json.dumps(data, indent=4).encode("utf-8")) +156 | +157 | From 0e5d291af121dae2e23b3add1b279027b03cbfab Mon Sep 17 00:00:00 2001 From: 11happy Date: Mon, 10 Nov 2025 18:42:49 +0530 Subject: [PATCH 06/11] test:update snapshot Signed-off-by: 11happy --- crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py | 2 -- .../ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap | 2 -- ...inter__rules__refurb__tests__write_whole_file_python_39.snap | 2 -- 3 files changed, 6 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py index f38e64be9a..52c920aa36 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py @@ -153,5 +153,3 @@ data = {"price": 100} with open("test.json", "wb") as f: f.write(json.dumps(data, indent=4).encode("utf-8")) - - diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap index d6ea939bbb..8148035435 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap @@ -279,5 +279,3 @@ help: Replace with `Path("test.json")....` - with open("test.json", "wb") as f: - f.write(json.dumps(data, indent=4).encode("utf-8")) 155 + pathlib.Path("test.json").write_bytes(json.dumps(data, indent=4).encode("utf-8")) -156 | -157 | diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap index ee7a295b39..3b68b110d5 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap @@ -209,5 +209,3 @@ help: Replace with `Path("test.json")....` - with open("test.json", "wb") as f: - f.write(json.dumps(data, indent=4).encode("utf-8")) 155 + pathlib.Path("test.json").write_bytes(json.dumps(data, indent=4).encode("utf-8")) -156 | -157 | From 025b2b4c65afd1b0013cdad3e9e2bee9b6ce7e95 Mon Sep 17 00:00:00 2001 From: 11happy Date: Mon, 10 Nov 2025 18:52:22 +0530 Subject: [PATCH 07/11] test: remove unreference snapshot Signed-off-by: 11happy --- ...rb__tests__preview_FURB103_FURB103.py.snap | 283 ------------------ 1 file changed, 283 deletions(-) delete mode 100644 crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap deleted file mode 100644 index d6ea939bbb..0000000000 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__preview_FURB103_FURB103.py.snap +++ /dev/null @@ -1,283 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/refurb/mod.rs ---- -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text("test")` - --> FURB103.py:12:6 - | -11 | # FURB103 -12 | with open("file.txt", "w") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -13 | f.write("test") - | -help: Replace with `Path("file.txt").write_text("test")` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -10 | # Errors. -11 | -12 | # FURB103 - - with open("file.txt", "w") as f: - - f.write("test") -13 + pathlib.Path("file.txt").write_text("test") -14 | -15 | # FURB103 -16 | with open("file.txt", "wb") as f: - -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)` - --> FURB103.py:16:6 - | -15 | # FURB103 -16 | with open("file.txt", "wb") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -17 | f.write(foobar) - | -help: Replace with `Path("file.txt").write_bytes(foobar)` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -14 | f.write("test") -15 | -16 | # FURB103 - - with open("file.txt", "wb") as f: - - f.write(foobar) -17 + pathlib.Path("file.txt").write_bytes(foobar) -18 | -19 | # FURB103 -20 | with open("file.txt", mode="wb") as f: - -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")` - --> FURB103.py:20:6 - | -19 | # FURB103 -20 | with open("file.txt", mode="wb") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -21 | f.write(b"abc") - | -help: Replace with `Path("file.txt").write_bytes(b"abc")` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -18 | f.write(foobar) -19 | -20 | # FURB103 - - with open("file.txt", mode="wb") as f: - - f.write(b"abc") -21 + pathlib.Path("file.txt").write_bytes(b"abc") -22 | -23 | # FURB103 -24 | with open("file.txt", "w", encoding="utf8") as f: - -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")` - --> FURB103.py:24:6 - | -23 | # FURB103 -24 | with open("file.txt", "w", encoding="utf8") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -25 | f.write(foobar) - | -help: Replace with `Path("file.txt").write_text(foobar, encoding="utf8")` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -22 | f.write(b"abc") -23 | -24 | # FURB103 - - with open("file.txt", "w", encoding="utf8") as f: - - f.write(foobar) -25 + pathlib.Path("file.txt").write_text(foobar, encoding="utf8") -26 | -27 | # FURB103 -28 | with open("file.txt", "w", errors="ignore") as f: - -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")` - --> FURB103.py:28:6 - | -27 | # FURB103 -28 | with open("file.txt", "w", errors="ignore") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -29 | f.write(foobar) - | -help: Replace with `Path("file.txt").write_text(foobar, errors="ignore")` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -26 | f.write(foobar) -27 | -28 | # FURB103 - - with open("file.txt", "w", errors="ignore") as f: - - f.write(foobar) -29 + pathlib.Path("file.txt").write_text(foobar, errors="ignore") -30 | -31 | # FURB103 -32 | with open("file.txt", mode="w") as f: - -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)` - --> FURB103.py:32:6 - | -31 | # FURB103 -32 | with open("file.txt", mode="w") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -33 | f.write(foobar) - | -help: Replace with `Path("file.txt").write_text(foobar)` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -30 | f.write(foobar) -31 | -32 | # FURB103 - - with open("file.txt", mode="w") as f: - - f.write(foobar) -33 + pathlib.Path("file.txt").write_text(foobar) -34 | -35 | # FURB103 -36 | with open(foo(), "wb") as f: - -FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())` - --> FURB103.py:36:6 - | -35 | # FURB103 -36 | with open(foo(), "wb") as f: - | ^^^^^^^^^^^^^^^^^^^^^^ -37 | # The body of `with` is non-trivial, but the recommendation holds. -38 | bar("pre") - | -help: Replace with `Path(foo()).write_bytes(bar())` - -FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)` - --> FURB103.py:44:6 - | -43 | # FURB103 -44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: - | ^^^^^^^^^^^^^^^^^^^^^^^ -45 | a.write(x) -46 | b.write(y) - | -help: Replace with `Path("a.txt").write_text(x)` - -FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)` - --> FURB103.py:44:31 - | -43 | # FURB103 -44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: - | ^^^^^^^^^^^^^^^^^^^^^^^^ -45 | a.write(x) -46 | b.write(y) - | -help: Replace with `Path("b.txt").write_bytes(y)` - -FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(bar(bar(a + x)))` - --> FURB103.py:49:18 - | -48 | # FURB103 -49 | with foo() as a, open("file.txt", "w") as b, foo() as c: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -50 | # We have other things in here, multiple with items, but the user -51 | # writes a single time to file and that bit they can replace. - | -help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))` - -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` - --> FURB103.py:58:6 - | -57 | # FURB103 -58 | with open("file.txt", "w", newline="\r\n") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -59 | f.write(foobar) - | -help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` -1 + import pathlib -2 | def foo(): -3 | ... -4 | --------------------------------------------------------------------------------- -56 | -57 | -58 | # FURB103 - - with open("file.txt", "w", newline="\r\n") as f: - - f.write(foobar) -59 + pathlib.Path("file.txt").write_text(foobar, newline="\r\n") -60 | -61 | -62 | import builtins - -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` - --> FURB103.py:66:6 - | -65 | # FURB103 -66 | with builtins.open("file.txt", "w", newline="\r\n") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -67 | f.write(foobar) - | -help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` -60 | -61 | -62 | import builtins -63 + import pathlib -64 | -65 | -66 | # FURB103 - - with builtins.open("file.txt", "w", newline="\r\n") as f: - - f.write(foobar) -67 + pathlib.Path("file.txt").write_text(foobar, newline="\r\n") -68 | -69 | -70 | from builtins import open as o - -FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` - --> FURB103.py:74:6 - | -73 | # FURB103 -74 | with o("file.txt", "w", newline="\r\n") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -75 | f.write(foobar) - | -help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` -68 | -69 | -70 | from builtins import open as o -71 + import pathlib -72 | -73 | -74 | # FURB103 - - with o("file.txt", "w", newline="\r\n") as f: - - f.write(foobar) -75 + pathlib.Path("file.txt").write_text(foobar, newline="\r\n") -76 | -77 | # Non-errors. -78 | - -FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....` - --> FURB103.py:154:6 - | -152 | data = {"price": 100} -153 | -154 | with open("test.json", "wb") as f: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -155 | f.write(json.dumps(data, indent=4).encode("utf-8")) - | -help: Replace with `Path("test.json")....` -148 | -149 | # See: https://github.com/astral-sh/ruff/issues/20785 -150 | import json -151 + import pathlib -152 | -153 | data = {"price": 100} -154 | - - with open("test.json", "wb") as f: - - f.write(json.dumps(data, indent=4).encode("utf-8")) -155 + pathlib.Path("test.json").write_bytes(json.dumps(data, indent=4).encode("utf-8")) -156 | -157 | From 8138e2c11352bf55d971b744f91956cbc44b34ad Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Mon, 10 Nov 2025 10:20:52 -0500 Subject: [PATCH 08/11] nits --- .../refurb/{FURB103.py => FURB103_0.py} | 2 +- .../refurb/{FURB103_EXT.py => FURB103_1.py} | 0 crates/ruff_linter/src/rules/refurb/mod.rs | 6 ++-- ..._refurb__tests__FURB103_FURB103_0.py.snap} | 28 +++++++++---------- ..._refurb__tests__FURB103_FURB103_1.py.snap} | 14 +++++----- ...rb__tests__write_whole_file_python_39.snap | 22 +++++++-------- temp.txt | 0 7 files changed, 36 insertions(+), 36 deletions(-) rename crates/ruff_linter/resources/test/fixtures/refurb/{FURB103.py => FURB103_0.py} (98%) rename crates/ruff_linter/resources/test/fixtures/refurb/{FURB103_EXT.py => FURB103_1.py} (100%) rename crates/ruff_linter/src/rules/refurb/snapshots/{ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap => ruff_linter__rules__refurb__tests__FURB103_FURB103_0.py.snap} (95%) rename crates/ruff_linter/src/rules/refurb/snapshots/{ruff_linter__rules__refurb__tests__FURB103_FURB103_EXT.py.snap => ruff_linter__rules__refurb__tests__FURB103_FURB103_1.py.snap} (95%) delete mode 100644 temp.txt diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB103_0.py similarity index 98% rename from crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py rename to crates/ruff_linter/resources/test/fixtures/refurb/FURB103_0.py index 52c920aa36..35d9600d41 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB103.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB103_0.py @@ -152,4 +152,4 @@ import json data = {"price": 100} with open("test.json", "wb") as f: - f.write(json.dumps(data, indent=4).encode("utf-8")) + f.write(json.dumps(data, indent=4).encode("utf-8")) \ No newline at end of file diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB103_EXT.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB103_1.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/refurb/FURB103_EXT.py rename to crates/ruff_linter/resources/test/fixtures/refurb/FURB103_1.py diff --git a/crates/ruff_linter/src/rules/refurb/mod.rs b/crates/ruff_linter/src/rules/refurb/mod.rs index dd43daa7c4..2c7f389eae 100644 --- a/crates/ruff_linter/src/rules/refurb/mod.rs +++ b/crates/ruff_linter/src/rules/refurb/mod.rs @@ -46,8 +46,8 @@ mod tests { #[test_case(Rule::MetaClassABCMeta, Path::new("FURB180.py"))] #[test_case(Rule::HashlibDigestHex, Path::new("FURB181.py"))] #[test_case(Rule::ListReverseCopy, Path::new("FURB187.py"))] - #[test_case(Rule::WriteWholeFile, Path::new("FURB103.py"))] - #[test_case(Rule::WriteWholeFile, Path::new("FURB103_EXT.py"))] + #[test_case(Rule::WriteWholeFile, Path::new("FURB103_0.py"))] + #[test_case(Rule::WriteWholeFile, Path::new("FURB103_1.py"))] #[test_case(Rule::FStringNumberFormat, Path::new("FURB116.py"))] #[test_case(Rule::SortedMinMax, Path::new("FURB192.py"))] #[test_case(Rule::SliceToRemovePrefixOrSuffix, Path::new("FURB188.py"))] @@ -66,7 +66,7 @@ mod tests { #[test] fn write_whole_file_python_39() -> Result<()> { let diagnostics = test_path( - Path::new("refurb/FURB103.py"), + Path::new("refurb/FURB103_0.py"), &settings::LinterSettings::for_rule(Rule::WriteWholeFile) .with_target_version(PythonVersion::PY39), )?; diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_0.py.snap similarity index 95% rename from crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap rename to crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_0.py.snap index 8148035435..c4968f2ca1 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_0.py.snap @@ -2,7 +2,7 @@ source: crates/ruff_linter/src/rules/refurb/mod.rs --- FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text("test")` - --> FURB103.py:12:6 + --> FURB103_0.py:12:6 | 11 | # FURB103 12 | with open("file.txt", "w") as f: @@ -26,7 +26,7 @@ help: Replace with `Path("file.txt").write_text("test")` 16 | with open("file.txt", "wb") as f: FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)` - --> FURB103.py:16:6 + --> FURB103_0.py:16:6 | 15 | # FURB103 16 | with open("file.txt", "wb") as f: @@ -50,7 +50,7 @@ help: Replace with `Path("file.txt").write_bytes(foobar)` 20 | with open("file.txt", mode="wb") as f: FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")` - --> FURB103.py:20:6 + --> FURB103_0.py:20:6 | 19 | # FURB103 20 | with open("file.txt", mode="wb") as f: @@ -74,7 +74,7 @@ help: Replace with `Path("file.txt").write_bytes(b"abc")` 24 | with open("file.txt", "w", encoding="utf8") as f: FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")` - --> FURB103.py:24:6 + --> FURB103_0.py:24:6 | 23 | # FURB103 24 | with open("file.txt", "w", encoding="utf8") as f: @@ -98,7 +98,7 @@ help: Replace with `Path("file.txt").write_text(foobar, encoding="utf8")` 28 | with open("file.txt", "w", errors="ignore") as f: FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")` - --> FURB103.py:28:6 + --> FURB103_0.py:28:6 | 27 | # FURB103 28 | with open("file.txt", "w", errors="ignore") as f: @@ -122,7 +122,7 @@ help: Replace with `Path("file.txt").write_text(foobar, errors="ignore")` 32 | with open("file.txt", mode="w") as f: FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)` - --> FURB103.py:32:6 + --> FURB103_0.py:32:6 | 31 | # FURB103 32 | with open("file.txt", mode="w") as f: @@ -146,7 +146,7 @@ help: Replace with `Path("file.txt").write_text(foobar)` 36 | with open(foo(), "wb") as f: FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())` - --> FURB103.py:36:6 + --> FURB103_0.py:36:6 | 35 | # FURB103 36 | with open(foo(), "wb") as f: @@ -157,7 +157,7 @@ FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar()) help: Replace with `Path(foo()).write_bytes(bar())` FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)` - --> FURB103.py:44:6 + --> FURB103_0.py:44:6 | 43 | # FURB103 44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: @@ -168,7 +168,7 @@ FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)` help: Replace with `Path("a.txt").write_text(x)` FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)` - --> FURB103.py:44:31 + --> FURB103_0.py:44:31 | 43 | # FURB103 44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: @@ -179,7 +179,7 @@ FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)` help: Replace with `Path("b.txt").write_bytes(y)` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(bar(bar(a + x)))` - --> FURB103.py:49:18 + --> FURB103_0.py:49:18 | 48 | # FURB103 49 | with foo() as a, open("file.txt", "w") as b, foo() as c: @@ -190,7 +190,7 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(ba help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))` FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` - --> FURB103.py:58:6 + --> FURB103_0.py:58:6 | 57 | # FURB103 58 | with open("file.txt", "w", newline="\r\n") as f: @@ -214,7 +214,7 @@ help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` 62 | import builtins FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` - --> FURB103.py:66:6 + --> FURB103_0.py:66:6 | 65 | # FURB103 66 | with builtins.open("file.txt", "w", newline="\r\n") as f: @@ -237,7 +237,7 @@ help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` 70 | from builtins import open as o FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, newline="\r\n")` - --> FURB103.py:74:6 + --> FURB103_0.py:74:6 | 73 | # FURB103 74 | with o("file.txt", "w", newline="\r\n") as f: @@ -260,7 +260,7 @@ help: Replace with `Path("file.txt").write_text(foobar, newline="\r\n")` 78 | FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....` - --> FURB103.py:154:6 + --> FURB103_0.py:154:6 | 152 | data = {"price": 100} 153 | diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_EXT.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_1.py.snap similarity index 95% rename from crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_EXT.py.snap rename to crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_1.py.snap index 455a5d80f0..b3fbdd54bd 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_EXT.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_1.py.snap @@ -2,7 +2,7 @@ source: crates/ruff_linter/src/rules/refurb/mod.rs --- FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` - --> FURB103_EXT.py:3:6 + --> FURB103_1.py:3:6 | 1 | from pathlib import Path 2 | @@ -21,7 +21,7 @@ help: Replace with `Path("file.txt").write_text("test")` 6 | f.write(b"test") FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_bytes(b"test")` - --> FURB103_EXT.py:6:6 + --> FURB103_1.py:6:6 | 4 | f.write("test") 5 | @@ -41,7 +41,7 @@ help: Replace with `Path("file.txt").write_bytes(b"test")` 9 | f.write("test") FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` - --> FURB103_EXT.py:9:6 + --> FURB103_1.py:9:6 | 7 | f.write(b"test") 8 | @@ -61,7 +61,7 @@ help: Replace with `Path("file.txt").write_text("test")` 12 | f.write("test") FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", encoding="utf8")` - --> FURB103_EXT.py:12:6 + --> FURB103_1.py:12:6 | 10 | f.write("test") 11 | @@ -81,7 +81,7 @@ help: Replace with `Path("file.txt").write_text("test", encoding="utf8")` 15 | f.write("test") FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", errors="ignore")` - --> FURB103_EXT.py:15:6 + --> FURB103_1.py:15:6 | 13 | f.write("test") 14 | @@ -101,7 +101,7 @@ help: Replace with `Path("file.txt").write_text("test", errors="ignore")` 18 | f.write("test") FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path(foo()).write_text("test")` - --> FURB103_EXT.py:18:6 + --> FURB103_1.py:18:6 | 16 | f.write("test") 17 | @@ -121,7 +121,7 @@ help: Replace with `Path(foo()).write_text("test")` 21 | with p.open("w") as f: FURB103 `Path.open()` followed by `write()` can be replaced by `p.write_text("test")` - --> FURB103_EXT.py:22:6 + --> FURB103_1.py:22:6 | 21 | p = Path("file.txt") 22 | with p.open("w") as f: diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap index 3b68b110d5..45fdaca4d7 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__write_whole_file_python_39.snap @@ -2,7 +2,7 @@ source: crates/ruff_linter/src/rules/refurb/mod.rs --- FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text("test")` - --> FURB103.py:12:6 + --> FURB103_0.py:12:6 | 11 | # FURB103 12 | with open("file.txt", "w") as f: @@ -26,7 +26,7 @@ help: Replace with `Path("file.txt").write_text("test")` 16 | with open("file.txt", "wb") as f: FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(foobar)` - --> FURB103.py:16:6 + --> FURB103_0.py:16:6 | 15 | # FURB103 16 | with open("file.txt", "wb") as f: @@ -50,7 +50,7 @@ help: Replace with `Path("file.txt").write_bytes(foobar)` 20 | with open("file.txt", mode="wb") as f: FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_bytes(b"abc")` - --> FURB103.py:20:6 + --> FURB103_0.py:20:6 | 19 | # FURB103 20 | with open("file.txt", mode="wb") as f: @@ -74,7 +74,7 @@ help: Replace with `Path("file.txt").write_bytes(b"abc")` 24 | with open("file.txt", "w", encoding="utf8") as f: FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, encoding="utf8")` - --> FURB103.py:24:6 + --> FURB103_0.py:24:6 | 23 | # FURB103 24 | with open("file.txt", "w", encoding="utf8") as f: @@ -98,7 +98,7 @@ help: Replace with `Path("file.txt").write_text(foobar, encoding="utf8")` 28 | with open("file.txt", "w", errors="ignore") as f: FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar, errors="ignore")` - --> FURB103.py:28:6 + --> FURB103_0.py:28:6 | 27 | # FURB103 28 | with open("file.txt", "w", errors="ignore") as f: @@ -122,7 +122,7 @@ help: Replace with `Path("file.txt").write_text(foobar, errors="ignore")` 32 | with open("file.txt", mode="w") as f: FURB103 [*] `open` and `write` should be replaced by `Path("file.txt").write_text(foobar)` - --> FURB103.py:32:6 + --> FURB103_0.py:32:6 | 31 | # FURB103 32 | with open("file.txt", mode="w") as f: @@ -146,7 +146,7 @@ help: Replace with `Path("file.txt").write_text(foobar)` 36 | with open(foo(), "wb") as f: FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar())` - --> FURB103.py:36:6 + --> FURB103_0.py:36:6 | 35 | # FURB103 36 | with open(foo(), "wb") as f: @@ -157,7 +157,7 @@ FURB103 `open` and `write` should be replaced by `Path(foo()).write_bytes(bar()) help: Replace with `Path(foo()).write_bytes(bar())` FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)` - --> FURB103.py:44:6 + --> FURB103_0.py:44:6 | 43 | # FURB103 44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: @@ -168,7 +168,7 @@ FURB103 `open` and `write` should be replaced by `Path("a.txt").write_text(x)` help: Replace with `Path("a.txt").write_text(x)` FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)` - --> FURB103.py:44:31 + --> FURB103_0.py:44:31 | 43 | # FURB103 44 | with open("a.txt", "w") as a, open("b.txt", "wb") as b: @@ -179,7 +179,7 @@ FURB103 `open` and `write` should be replaced by `Path("b.txt").write_bytes(y)` help: Replace with `Path("b.txt").write_bytes(y)` FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(bar(bar(a + x)))` - --> FURB103.py:49:18 + --> FURB103_0.py:49:18 | 48 | # FURB103 49 | with foo() as a, open("file.txt", "w") as b, foo() as c: @@ -190,7 +190,7 @@ FURB103 `open` and `write` should be replaced by `Path("file.txt").write_text(ba help: Replace with `Path("file.txt").write_text(bar(bar(a + x)))` FURB103 [*] `open` and `write` should be replaced by `Path("test.json")....` - --> FURB103.py:154:6 + --> FURB103_0.py:154:6 | 152 | data = {"price": 100} 153 | diff --git a/temp.txt b/temp.txt deleted file mode 100644 index e69de29bb2..0000000000 From 9b758d8c29322192211a77ba94ec6091e8d9d20d Mon Sep 17 00:00:00 2001 From: 11happy Date: Wed, 12 Nov 2025 11:55:16 +0530 Subject: [PATCH 09/11] strict check for args for length 1 Signed-off-by: 11happy --- .../test/fixtures/refurb/FURB103_1.py | 3 ++ .../ruff_linter/src/rules/refurb/helpers.rs | 6 +++- .../rules/refurb/rules/write_whole_file.rs | 4 +-- ...__refurb__tests__FURB103_FURB103_1.py.snap | 28 ++++++++++++++++++- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB103_1.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB103_1.py index f67f793c3e..f4b9cb1dec 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB103_1.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB103_1.py @@ -20,4 +20,7 @@ with Path(foo()).open("w") as f: p = Path("file.txt") with p.open("w") as f: + f.write("test") + +with Path("foo", "bar", "baz").open("w") as f: f.write("test") \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/refurb/helpers.rs b/crates/ruff_linter/src/rules/refurb/helpers.rs index 325b050873..7d4bc7aaf0 100644 --- a/crates/ruff_linter/src/rules/refurb/helpers.rs +++ b/crates/ruff_linter/src/rules/refurb/helpers.rs @@ -280,7 +280,11 @@ fn find_path_open<'a>( } let attr = func.as_attribute_expr()?; let filename = if let Expr::Call(path_call) = attr.value.as_ref() { - path_call.arguments.args.first() + if path_call.arguments.args.len() == 1 { + path_call.arguments.args.first() + } else { + None + } } else { None }; diff --git a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs index 1d1e5dc422..8e8730bb77 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs @@ -241,8 +241,6 @@ fn generate_fix( } let locator = checker.locator(); - let filename = open.filename?; - let filename_code = locator.slice(filename.range()); let (import_edit, binding) = checker .importer() @@ -256,6 +254,8 @@ fn generate_fix( let target = if let Some(path_obj) = open.path_obj { locator.slice(path_obj.range()).to_string() } else { + let filename = open.filename?; + let filename_code = locator.slice(filename.range()); format!("{binding}({filename_code})") }; diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_1.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_1.py.snap index b3fbdd54bd..4824664dd8 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_1.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_1.py.snap @@ -120,7 +120,7 @@ help: Replace with `Path(foo()).write_text("test")` 20 | p = Path("file.txt") 21 | with p.open("w") as f: -FURB103 `Path.open()` followed by `write()` can be replaced by `p.write_text("test")` +FURB103 [*] `Path.open()` followed by `write()` can be replaced by `p.write_text("test")` --> FURB103_1.py:22:6 | 21 | p = Path("file.txt") @@ -129,3 +129,29 @@ FURB103 `Path.open()` followed by `write()` can be replaced by `p.write_text("te 23 | f.write("test") | help: Replace with `p.write_text("test")` +19 | f.write("test") +20 | +21 | p = Path("file.txt") + - with p.open("w") as f: + - f.write("test") +22 + p.write_text("test") +23 | +24 | with Path("foo", "bar", "baz").open("w") as f: +25 | f.write("test") + +FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("foo", "bar", "baz").write_text("test")` + --> FURB103_1.py:25:6 + | +23 | f.write("test") +24 | +25 | with Path("foo", "bar", "baz").open("w") as f: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +26 | f.write("test") + | +help: Replace with `Path("foo", "bar", "baz").write_text("test")` +22 | with p.open("w") as f: +23 | f.write("test") +24 | + - with Path("foo", "bar", "baz").open("w") as f: + - f.write("test") +25 + Path("foo", "bar", "baz").write_text("test") From fd9e0b17cd5876586d958c0ea5a0cc59a344c96d Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 19 Nov 2025 16:19:39 -0500 Subject: [PATCH 10/11] introduce OpenArgument type --- .../ruff_linter/src/rules/refurb/helpers.rs | 68 +++++++++++++------ .../src/rules/refurb/rules/read_whole_file.rs | 16 ++--- .../rules/refurb/rules/write_whole_file.rs | 51 +++++--------- ...__refurb__tests__FURB103_FURB103_1.py.snap | 12 ++-- 4 files changed, 74 insertions(+), 73 deletions(-) diff --git a/crates/ruff_linter/src/rules/refurb/helpers.rs b/crates/ruff_linter/src/rules/refurb/helpers.rs index 7d4bc7aaf0..b9e9ece8d2 100644 --- a/crates/ruff_linter/src/rules/refurb/helpers.rs +++ b/crates/ruff_linter/src/rules/refurb/helpers.rs @@ -120,16 +120,13 @@ impl OpenMode { pub(super) struct FileOpen<'a> { /// With item where the open happens, we use it for the reporting range. pub(super) item: &'a ast::WithItem, - /// Filename expression used as the first argument in `open`, we use it in the diagnostic message. - pub(super) filename: Option<&'a Expr>, /// The file open mode. pub(super) mode: OpenMode, /// The file open keywords. pub(super) keywords: Vec<&'a ast::Keyword>, /// We only check `open` operations whose file handles are used exactly once. pub(super) reference: &'a ResolvedReference, - /// Pathlib Path object used to open the file, if any. - pub(super) path_obj: Option<&'a Expr>, + pub(super) argument: OpenArgument<'a>, } impl FileOpen<'_> { @@ -140,6 +137,45 @@ impl FileOpen<'_> { } } +#[derive(Debug)] +pub(super) enum OpenArgument<'a> { + /// The filename argument to `open`, e.g. "foo.txt" in: + /// + /// ```py + /// f = open("foo.txt") + /// ``` + Builtin { filename: &'a Expr }, + /// The `Path` receiver of a `pathlib.Path.open` call, e.g. the `p` in the + /// context manager in: + /// + /// ```py + /// p = Path("foo.txt") + /// with p.open() as f: ... + /// ``` + /// + /// or `Path("foo.txt")` in + /// + /// ```py + /// with Path("foo.txt").open() as f: ... + /// ``` + Pathlib { path: &'a Expr }, +} + +impl OpenArgument<'_> { + pub(super) fn display<'src>(&self, source: &'src str) -> &'src str { + &source[self.range()] + } +} + +impl Ranged for OpenArgument<'_> { + fn range(&self) -> TextRange { + match self { + OpenArgument::Builtin { filename } => filename.range(), + OpenArgument::Pathlib { path } => path.range(), + } + } +} + /// Find and return all `open` operations in the given `with` statement. pub(super) fn find_file_opens<'a>( with: &'a ast::StmtWith, @@ -156,16 +192,14 @@ pub(super) fn find_file_opens<'a>( .collect() } -#[expect(clippy::too_many_arguments)] fn resolve_file_open<'a>( item: &'a ast::WithItem, with: &'a ast::StmtWith, semantic: &'a SemanticModel<'a>, read_mode: bool, - filename: Option<&'a Expr>, mode: OpenMode, keywords: Vec<&'a ast::Keyword>, - path_obj: Option<&'a Expr>, + argument: OpenArgument<'a>, ) -> Option> { match mode { OpenMode::ReadText | OpenMode::ReadBytes => { @@ -203,11 +237,10 @@ fn resolve_file_open<'a>( Some(FileOpen { item, - filename, mode, keywords, reference, - path_obj, + argument, }) } @@ -251,10 +284,9 @@ fn find_file_open<'a>( with, semantic, read_mode, - Some(filename), mode, keywords, - None, + OpenArgument::Builtin { filename }, ) } @@ -279,15 +311,6 @@ fn find_path_open<'a>( return None; } let attr = func.as_attribute_expr()?; - let filename = if let Expr::Call(path_call) = attr.value.as_ref() { - if path_call.arguments.args.len() == 1 { - path_call.arguments.args.first() - } else { - None - } - } else { - None - }; let mode = if args.is_empty() { OpenMode::ReadText } else { @@ -301,10 +324,11 @@ fn find_path_open<'a>( with, semantic, read_mode, - filename, mode, keywords, - Some(attr.value.as_ref()), + OpenArgument::Pathlib { + path: attr.value.as_ref(), + }, ) } diff --git a/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs b/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs index 1c2e39c0d8..eb321cc4d0 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs @@ -10,7 +10,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; use crate::importer::ImportRequest; -use crate::rules::refurb::helpers::{FileOpen, find_file_opens}; +use crate::rules::refurb::helpers::{FileOpen, OpenArgument, find_file_opens}; use crate::{FixAvailability, Violation}; /// ## What it does @@ -114,17 +114,11 @@ impl<'a> Visitor<'a> for ReadMatcher<'a, '_> { .position(|open| open.is_ref(read_from)) { let open = self.candidates.remove(open); - let filename_display = if let Some(filename) = open.filename { - self.checker.generator().expr(filename) - } else if let Some(path_obj) = open.path_obj { - self.checker.locator().slice(path_obj.range()).to_string() - } else { - return; - }; + let filename_display = open.argument.display(self.checker.source()); let suggestion = make_suggestion(&open, self.checker.generator()); let mut diagnostic = self.checker.report_diagnostic( ReadWholeFile { - filename: SourceCodeSnippet::from_str(&filename_display), + filename: SourceCodeSnippet::from_str(filename_display), suggestion: SourceCodeSnippet::from_str(&suggestion), }, open.item.range(), @@ -190,7 +184,9 @@ fn generate_fix( if with_stmt.items.len() != 1 { return None; } - let filename = open.filename?; + let OpenArgument::Builtin { filename } = open.argument else { + return None; + }; let locator = checker.locator(); let filename_code = locator.slice(filename.range()); diff --git a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs index cd0e583f0b..9b77f6fc36 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs @@ -9,7 +9,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; use crate::importer::ImportRequest; -use crate::rules::refurb::helpers::{FileOpen, find_file_opens}; +use crate::rules::refurb::helpers::{FileOpen, OpenArgument, find_file_opens}; use crate::{FixAvailability, Locator, Violation}; /// ## What it does @@ -46,7 +46,6 @@ pub(crate) struct WriteWholeFile { filename: SourceCodeSnippet, suggestion: SourceCodeSnippet, is_path_open: bool, - has_filename: bool, } impl Violation for WriteWholeFile { @@ -57,29 +56,23 @@ impl Violation for WriteWholeFile { let filename = self.filename.truncated_display(); let suggestion = self.suggestion.truncated_display(); if self.is_path_open { - if self.has_filename { - format!( - "`Path().open()` followed by `write()` can be replaced by `Path({filename}).{suggestion}`" - ) - } else { - format!( - "`Path.open()` followed by `write()` can be replaced by `{filename}.{suggestion}`" - ) - } + format!( + "`Path.open()` followed by `write()` can be replaced by `{filename}.{suggestion}`" + ) } else { format!("`open` and `write` should be replaced by `Path({filename}).{suggestion}`") } } fn fix_title(&self) -> Option { - let formatted = if self.has_filename { + let formatted = if self.is_path_open { format!( - "Replace with `Path({}).{}`", + "Replace with `{}.{}`", self.filename.truncated_display(), self.suggestion.truncated_display(), ) } else { format!( - "Replace with `{}.{}`", + "Replace with `Path({}).{}`", self.filename.truncated_display(), self.suggestion.truncated_display(), ) @@ -149,27 +142,15 @@ impl<'a> Visitor<'a> for WriteMatcher<'a, '_> { .position(|open| open.is_ref(write_to)) { let open = self.candidates.remove(open); - let has_filename: bool; - let filename_display: String; if self.loop_counter == 0 { - if let Some(filename) = open.filename { - filename_display = self.checker.generator().expr(filename); - has_filename = true; - } else if let Some(path_obj) = open.path_obj { - filename_display = - self.checker.locator().slice(path_obj.range()).to_string(); - has_filename = false; - } else { - return; - } + let filename_display = open.argument.display(self.checker.source()); let suggestion = make_suggestion(&open, content, self.checker.locator()); let mut diagnostic = self.checker.report_diagnostic( WriteWholeFile { - filename: SourceCodeSnippet::from_str(&filename_display), + filename: SourceCodeSnippet::from_str(filename_display), suggestion: SourceCodeSnippet::from_str(&suggestion), - is_path_open: open.path_obj.is_some(), - has_filename, + is_path_open: matches!(open.argument, OpenArgument::Pathlib { .. }), }, open.item.range(), ); @@ -243,12 +224,12 @@ fn generate_fix( ) .ok()?; - let target = if let Some(path_obj) = open.path_obj { - locator.slice(path_obj.range()).to_string() - } else { - let filename = open.filename?; - let filename_code = locator.slice(filename.range()); - format!("{binding}({filename_code})") + let target = match open.argument { + OpenArgument::Builtin { filename } => { + let filename_code = locator.slice(filename.range()); + format!("{binding}({filename_code})") + } + OpenArgument::Pathlib { path } => locator.slice(path.range()).to_string(), }; let replacement = format!("{target}.{suggestion}"); diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_1.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_1.py.snap index 4824664dd8..d30974009c 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_1.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB103_FURB103_1.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/refurb/mod.rs --- -FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` +FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` --> FURB103_1.py:3:6 | 1 | from pathlib import Path @@ -20,7 +20,7 @@ help: Replace with `Path("file.txt").write_text("test")` 5 | with Path("file.txt").open("wb") as f: 6 | f.write(b"test") -FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_bytes(b"test")` +FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_bytes(b"test")` --> FURB103_1.py:6:6 | 4 | f.write("test") @@ -40,7 +40,7 @@ help: Replace with `Path("file.txt").write_bytes(b"test")` 8 | with Path("file.txt").open(mode="w") as f: 9 | f.write("test") -FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` +FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test")` --> FURB103_1.py:9:6 | 7 | f.write(b"test") @@ -60,7 +60,7 @@ help: Replace with `Path("file.txt").write_text("test")` 11 | with Path("file.txt").open("w", encoding="utf8") as f: 12 | f.write("test") -FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", encoding="utf8")` +FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", encoding="utf8")` --> FURB103_1.py:12:6 | 10 | f.write("test") @@ -80,7 +80,7 @@ help: Replace with `Path("file.txt").write_text("test", encoding="utf8")` 14 | with Path("file.txt").open("w", errors="ignore") as f: 15 | f.write("test") -FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", errors="ignore")` +FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path("file.txt").write_text("test", errors="ignore")` --> FURB103_1.py:15:6 | 13 | f.write("test") @@ -100,7 +100,7 @@ help: Replace with `Path("file.txt").write_text("test", errors="ignore")` 17 | with Path(foo()).open("w") as f: 18 | f.write("test") -FURB103 [*] `Path().open()` followed by `write()` can be replaced by `Path(foo()).write_text("test")` +FURB103 [*] `Path.open()` followed by `write()` can be replaced by `Path(foo()).write_text("test")` --> FURB103_1.py:18:6 | 16 | f.write("test") From 279b6cdb8c1abc66a3e3e5231d53a801cc39f85e Mon Sep 17 00:00:00 2001 From: 11happy Date: Wed, 26 Nov 2025 12:38:45 +0000 Subject: [PATCH 11/11] refactor: remove is_path_open Signed-off-by: 11happy --- .../ruff_linter/src/rules/refurb/helpers.rs | 2 +- .../rules/refurb/rules/write_whole_file.rs | 45 +++++++++---------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/crates/ruff_linter/src/rules/refurb/helpers.rs b/crates/ruff_linter/src/rules/refurb/helpers.rs index b9e9ece8d2..406f2aa7c1 100644 --- a/crates/ruff_linter/src/rules/refurb/helpers.rs +++ b/crates/ruff_linter/src/rules/refurb/helpers.rs @@ -137,7 +137,7 @@ impl FileOpen<'_> { } } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub(super) enum OpenArgument<'a> { /// The filename argument to `open`, e.g. "foo.txt" in: /// diff --git a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs index 9b77f6fc36..23a53e1f3a 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/write_whole_file.rs @@ -42,43 +42,40 @@ use crate::{FixAvailability, Locator, Violation}; /// - [Python documentation: `Path.write_text`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.write_text) #[derive(ViolationMetadata)] #[violation_metadata(preview_since = "v0.3.6")] -pub(crate) struct WriteWholeFile { +pub(crate) struct WriteWholeFile<'a> { filename: SourceCodeSnippet, suggestion: SourceCodeSnippet, - is_path_open: bool, + argument: OpenArgument<'a>, } -impl Violation for WriteWholeFile { +impl Violation for WriteWholeFile<'_> { const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; #[derive_message_formats] fn message(&self) -> String { let filename = self.filename.truncated_display(); let suggestion = self.suggestion.truncated_display(); - if self.is_path_open { - format!( - "`Path.open()` followed by `write()` can be replaced by `{filename}.{suggestion}`" - ) - } else { - format!("`open` and `write` should be replaced by `Path({filename}).{suggestion}`") + match self.argument { + OpenArgument::Pathlib { .. } => { + format!( + "`Path.open()` followed by `write()` can be replaced by `{filename}.{suggestion}`" + ) + } + OpenArgument::Builtin { .. } => { + format!("`open` and `write` should be replaced by `Path({filename}).{suggestion}`") + } } } fn fix_title(&self) -> Option { - let formatted = if self.is_path_open { - format!( - "Replace with `{}.{}`", - self.filename.truncated_display(), - self.suggestion.truncated_display(), - ) - } else { - format!( - "Replace with `Path({}).{}`", - self.filename.truncated_display(), - self.suggestion.truncated_display(), - ) - }; + let filename = self.filename.truncated_display(); + let suggestion = self.suggestion.truncated_display(); - Some(formatted) + match self.argument { + OpenArgument::Pathlib { .. } => Some(format!("Replace with `{filename}.{suggestion}`")), + OpenArgument::Builtin { .. } => { + Some(format!("Replace with `Path({filename}).{suggestion}`")) + } + } } } @@ -150,7 +147,7 @@ impl<'a> Visitor<'a> for WriteMatcher<'a, '_> { WriteWholeFile { filename: SourceCodeSnippet::from_str(filename_display), suggestion: SourceCodeSnippet::from_str(&suggestion), - is_path_open: matches!(open.argument, OpenArgument::Pathlib { .. }), + argument: open.argument, }, open.item.range(), );