diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_print/T203.py b/crates/ruff_linter/resources/test/fixtures/flake8_print/T203.py index 5d4cd5c390f97..900a0675b7200 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_print/T203.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_print/T203.py @@ -1,9 +1,23 @@ +import sys +import tempfile from pprint import pprint pprint("Hello, world!") # T203 +pprint("Hello, world!", stream=None) # T203 +pprint("Hello, world!", stream=sys.stdout) # T203 +pprint("Hello, world!", stream=sys.stderr) # T203 + +with tempfile.NamedTemporaryFile() as fp: + pprint("Hello, world!", stream=fp) # OK import pprint pprint.pprint("Hello, world!") # T203 +pprint.pprint("Hello, world!", stream=None) # T203 +pprint.pprint("Hello, world!", stream=sys.stdout) # T203 +pprint.pprint("Hello, world!", stream=sys.stderr) # T203 + +with tempfile.NamedTemporaryFile() as fp: + pprint.pprint("Hello, world!", stream=fp) # OK pprint.pformat("Hello, world!") diff --git a/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs b/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs index 7514b0b298d74..870d43405b58d 100644 --- a/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs +++ b/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs @@ -1,5 +1,6 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast as ast; +use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -130,20 +131,19 @@ pub(crate) fn print_call(checker: &Checker, call: &ast::ExprCall) { ["" | "builtins", "print"] => { // If the print call has a `file=` argument (that isn't `None`, `"sys.stdout"`, // or `"sys.stderr"`), don't trigger T201. - if let Some(keyword) = call.arguments.find_keyword("file") { - if !keyword.value.is_none_literal_expr() { - if semantic.resolve_qualified_name(&keyword.value).is_none_or( - |qualified_name| { - !matches!(qualified_name.segments(), ["sys", "stdout" | "stderr"]) - }, - ) { - return; - } - } + if has_non_default_output_target(semantic, call, "file") { + return; } checker.report_diagnostic_if_enabled(Print, call.func.range()) } - ["pprint", "pprint"] => checker.report_diagnostic_if_enabled(PPrint, call.func.range()), + ["pprint", "pprint"] => { + // If the pprint call has a `stream=` argument (that isn't `None`, + // `"sys.stdout"`, or `"sys.stderr"`), don't trigger T203. + if has_non_default_output_target(semantic, call, "stream") { + return; + } + checker.report_diagnostic_if_enabled(PPrint, call.func.range()) + } _ => return, }; @@ -162,3 +162,18 @@ pub(crate) fn print_call(checker: &Checker, call: &ast::ExprCall) { ); } } + +fn has_non_default_output_target( + semantic: &SemanticModel, + call: &ast::ExprCall, + keyword: &str, +) -> bool { + call.arguments.find_keyword(keyword).is_some_and(|keyword| { + !keyword.value.is_none_literal_expr() + && semantic + .resolve_qualified_name(&keyword.value) + .is_none_or(|qualified_name| { + !matches!(qualified_name.segments(), ["sys", "stdout" | "stderr"]) + }) + }) +} diff --git a/crates/ruff_linter/src/rules/flake8_print/snapshots/ruff_linter__rules__flake8_print__tests__T203_T203.py.snap b/crates/ruff_linter/src/rules/flake8_print/snapshots/ruff_linter__rules__flake8_print__tests__T203_T203.py.snap index 0013e8aec6d6c..2fd91f65e4236 100644 --- a/crates/ruff_linter/src/rules/flake8_print/snapshots/ruff_linter__rules__flake8_print__tests__T203_T203.py.snap +++ b/crates/ruff_linter/src/rules/flake8_print/snapshots/ruff_linter__rules__flake8_print__tests__T203_T203.py.snap @@ -1,40 +1,159 @@ --- source: crates/ruff_linter/src/rules/flake8_print/mod.rs +assertion_line: 23 --- T203 [*] `pprint` found - --> T203.py:3:1 + --> T203.py:5:1 | -1 | from pprint import pprint -2 | -3 | pprint("Hello, world!") # T203 - | ^^^^^^ +3 | from pprint import pprint 4 | -5 | import pprint +5 | pprint("Hello, world!") # T203 + | ^^^^^^ +6 | pprint("Hello, world!", stream=None) # T203 +7 | pprint("Hello, world!", stream=sys.stdout) # T203 | help: Remove `pprint` -1 | from pprint import pprint -2 | +2 | import tempfile +3 | from pprint import pprint +4 | - pprint("Hello, world!") # T203 -3 | -4 | import pprint -5 | +5 | pprint("Hello, world!", stream=None) # T203 +6 | pprint("Hello, world!", stream=sys.stdout) # T203 +7 | pprint("Hello, world!", stream=sys.stderr) # T203 +note: This is an unsafe fix and may change runtime behavior + +T203 [*] `pprint` found + --> T203.py:6:1 + | +5 | pprint("Hello, world!") # T203 +6 | pprint("Hello, world!", stream=None) # T203 + | ^^^^^^ +7 | pprint("Hello, world!", stream=sys.stdout) # T203 +8 | pprint("Hello, world!", stream=sys.stderr) # T203 + | +help: Remove `pprint` +3 | from pprint import pprint +4 | +5 | pprint("Hello, world!") # T203 + - pprint("Hello, world!", stream=None) # T203 +6 | pprint("Hello, world!", stream=sys.stdout) # T203 +7 | pprint("Hello, world!", stream=sys.stderr) # T203 +8 | note: This is an unsafe fix and may change runtime behavior T203 [*] `pprint` found --> T203.py:7:1 | -5 | import pprint -6 | -7 | pprint.pprint("Hello, world!") # T203 - | ^^^^^^^^^^^^^ -8 | -9 | pprint.pformat("Hello, world!") +5 | pprint("Hello, world!") # T203 +6 | pprint("Hello, world!", stream=None) # T203 +7 | pprint("Hello, world!", stream=sys.stdout) # T203 + | ^^^^^^ +8 | pprint("Hello, world!", stream=sys.stderr) # T203 | help: Remove `pprint` 4 | -5 | import pprint -6 | - - pprint.pprint("Hello, world!") # T203 -7 | -8 | pprint.pformat("Hello, world!") +5 | pprint("Hello, world!") # T203 +6 | pprint("Hello, world!", stream=None) # T203 + - pprint("Hello, world!", stream=sys.stdout) # T203 +7 | pprint("Hello, world!", stream=sys.stderr) # T203 +8 | +9 | with tempfile.NamedTemporaryFile() as fp: +note: This is an unsafe fix and may change runtime behavior + +T203 [*] `pprint` found + --> T203.py:8:1 + | + 6 | pprint("Hello, world!", stream=None) # T203 + 7 | pprint("Hello, world!", stream=sys.stdout) # T203 + 8 | pprint("Hello, world!", stream=sys.stderr) # T203 + | ^^^^^^ + 9 | +10 | with tempfile.NamedTemporaryFile() as fp: + | +help: Remove `pprint` +5 | pprint("Hello, world!") # T203 +6 | pprint("Hello, world!", stream=None) # T203 +7 | pprint("Hello, world!", stream=sys.stdout) # T203 + - pprint("Hello, world!", stream=sys.stderr) # T203 +8 | +9 | with tempfile.NamedTemporaryFile() as fp: +10 | pprint("Hello, world!", stream=fp) # OK +note: This is an unsafe fix and may change runtime behavior + +T203 [*] `pprint` found + --> T203.py:15:1 + | +13 | import pprint +14 | +15 | pprint.pprint("Hello, world!") # T203 + | ^^^^^^^^^^^^^ +16 | pprint.pprint("Hello, world!", stream=None) # T203 +17 | pprint.pprint("Hello, world!", stream=sys.stdout) # T203 + | +help: Remove `pprint` +12 | +13 | import pprint +14 | + - pprint.pprint("Hello, world!") # T203 +15 | pprint.pprint("Hello, world!", stream=None) # T203 +16 | pprint.pprint("Hello, world!", stream=sys.stdout) # T203 +17 | pprint.pprint("Hello, world!", stream=sys.stderr) # T203 +note: This is an unsafe fix and may change runtime behavior + +T203 [*] `pprint` found + --> T203.py:16:1 + | +15 | pprint.pprint("Hello, world!") # T203 +16 | pprint.pprint("Hello, world!", stream=None) # T203 + | ^^^^^^^^^^^^^ +17 | pprint.pprint("Hello, world!", stream=sys.stdout) # T203 +18 | pprint.pprint("Hello, world!", stream=sys.stderr) # T203 + | +help: Remove `pprint` +13 | import pprint +14 | +15 | pprint.pprint("Hello, world!") # T203 + - pprint.pprint("Hello, world!", stream=None) # T203 +16 | pprint.pprint("Hello, world!", stream=sys.stdout) # T203 +17 | pprint.pprint("Hello, world!", stream=sys.stderr) # T203 +18 | +note: This is an unsafe fix and may change runtime behavior + +T203 [*] `pprint` found + --> T203.py:17:1 + | +15 | pprint.pprint("Hello, world!") # T203 +16 | pprint.pprint("Hello, world!", stream=None) # T203 +17 | pprint.pprint("Hello, world!", stream=sys.stdout) # T203 + | ^^^^^^^^^^^^^ +18 | pprint.pprint("Hello, world!", stream=sys.stderr) # T203 + | +help: Remove `pprint` +14 | +15 | pprint.pprint("Hello, world!") # T203 +16 | pprint.pprint("Hello, world!", stream=None) # T203 + - pprint.pprint("Hello, world!", stream=sys.stdout) # T203 +17 | pprint.pprint("Hello, world!", stream=sys.stderr) # T203 +18 | +19 | with tempfile.NamedTemporaryFile() as fp: +note: This is an unsafe fix and may change runtime behavior + +T203 [*] `pprint` found + --> T203.py:18:1 + | +16 | pprint.pprint("Hello, world!", stream=None) # T203 +17 | pprint.pprint("Hello, world!", stream=sys.stdout) # T203 +18 | pprint.pprint("Hello, world!", stream=sys.stderr) # T203 + | ^^^^^^^^^^^^^ +19 | +20 | with tempfile.NamedTemporaryFile() as fp: + | +help: Remove `pprint` +15 | pprint.pprint("Hello, world!") # T203 +16 | pprint.pprint("Hello, world!", stream=None) # T203 +17 | pprint.pprint("Hello, world!", stream=sys.stdout) # T203 + - pprint.pprint("Hello, world!", stream=sys.stderr) # T203 +18 | +19 | with tempfile.NamedTemporaryFile() as fp: +20 | pprint.pprint("Hello, world!", stream=fp) # OK note: This is an unsafe fix and may change runtime behavior