From 22534448bae0e1338d98ea0c6bfc4dcae6040bf0 Mon Sep 17 00:00:00 2001 From: njhearp Date: Tue, 24 Jun 2025 22:33:48 -0400 Subject: [PATCH] `EM101` checks for byte strings in exception constructors for preview --- .../flake8_errmsg/EM101_byte_string.py | 6 ++++ crates/ruff_linter/src/preview.rs | 5 +++ .../src/rules/flake8_errmsg/mod.rs | 14 ++++++++ .../rules/string_in_exception.rs | 28 +++++++++++++++ ..._rules__flake8_errmsg__tests__preview.snap | 35 +++++++++++++++++++ 5 files changed, 88 insertions(+) create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_errmsg/EM101_byte_string.py create mode 100644 crates/ruff_linter/src/rules/flake8_errmsg/snapshots/ruff_linter__rules__flake8_errmsg__tests__preview.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_errmsg/EM101_byte_string.py b/crates/ruff_linter/resources/test/fixtures/flake8_errmsg/EM101_byte_string.py new file mode 100644 index 0000000000000..7761036a13c35 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_errmsg/EM101_byte_string.py @@ -0,0 +1,6 @@ +def f_byte(): + raise RuntimeError(b"This is an example exception") + + +def f_byte_empty(): + raise RuntimeError(b"") diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index d33fb7d170fdb..097941f7e0ea0 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -94,3 +94,8 @@ pub(crate) const fn is_ignore_init_files_in_useless_alias_enabled( pub(crate) const fn is_invalid_async_mock_access_check_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() } + +// https://github.com/astral-sh/ruff/pull/18867 +pub(crate) const fn is_raise_exception_byte_string_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} diff --git a/crates/ruff_linter/src/rules/flake8_errmsg/mod.rs b/crates/ruff_linter/src/rules/flake8_errmsg/mod.rs index 1a23754f587b8..ffaba369173e2 100644 --- a/crates/ruff_linter/src/rules/flake8_errmsg/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_errmsg/mod.rs @@ -9,6 +9,7 @@ mod tests { use anyhow::Result; use crate::registry::Rule; + use crate::settings::types::PreviewMode; use crate::test::test_path; use crate::{assert_diagnostics, settings}; @@ -44,4 +45,17 @@ mod tests { assert_diagnostics!("custom", diagnostics); Ok(()) } + + #[test] + fn preview_string_exception() -> Result<()> { + let diagnostics = test_path( + Path::new("flake8_errmsg/EM101_byte_string.py"), + &settings::LinterSettings { + preview: PreviewMode::Enabled, + ..settings::LinterSettings::for_rule(Rule::RawStringInException) + }, + )?; + assert_diagnostics!("preview", diagnostics); + Ok(()) + } } diff --git a/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs b/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs index 649206bee8a41..9f1a2fae3685b 100644 --- a/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs @@ -7,12 +7,16 @@ use ruff_text_size::Ranged; use crate::Locator; use crate::checkers::ast::Checker; +use crate::preview::is_raise_exception_byte_string_enabled; use crate::registry::Rule; use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for the use of string literals in exception constructors. /// +/// In [preview], this rule checks for byte string literals in +/// exception constructors. +/// /// ## Why is this bad? /// Python includes the `raise` in the default traceback (and formatters /// like Rich and IPython do too). @@ -47,6 +51,8 @@ use crate::{Edit, Fix, FixAvailability, Violation}; /// raise RuntimeError(msg) /// RuntimeError: 'Some value' is incorrect /// ``` +/// +/// [preview]: https://docs.astral.sh/ruff/preview/ #[derive(ViolationMetadata)] pub(crate) struct RawStringInException; @@ -203,6 +209,28 @@ pub(crate) fn string_in_exception(checker: &Checker, stmt: &Stmt, exc: &Expr) { } } } + // Check for byte string literals. + Expr::BytesLiteral(ast::ExprBytesLiteral { value: bytes, .. }) => { + if checker.settings().rules.enabled(Rule::RawStringInException) { + if bytes.len() >= checker.settings().flake8_errmsg.max_string_length + && is_raise_exception_byte_string_enabled(checker.settings()) + { + let mut diagnostic = + checker.report_diagnostic(RawStringInException, first.range()); + if let Some(indentation) = + whitespace::indentation(checker.source(), stmt) + { + diagnostic.set_fix(generate_fix( + stmt, + first, + indentation, + checker.stylist(), + checker.locator(), + )); + } + } + } + } // Check for f-strings. Expr::FString(_) => { if checker.is_rule_enabled(Rule::FStringInException) { diff --git a/crates/ruff_linter/src/rules/flake8_errmsg/snapshots/ruff_linter__rules__flake8_errmsg__tests__preview.snap b/crates/ruff_linter/src/rules/flake8_errmsg/snapshots/ruff_linter__rules__flake8_errmsg__tests__preview.snap new file mode 100644 index 0000000000000..f3dbda9bfdbeb --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_errmsg/snapshots/ruff_linter__rules__flake8_errmsg__tests__preview.snap @@ -0,0 +1,35 @@ +--- +source: crates/ruff_linter/src/rules/flake8_errmsg/mod.rs +--- +EM101_byte_string.py:2:24: EM101 [*] Exception must not use a string literal, assign to variable first + | +1 | def f_byte(): +2 | raise RuntimeError(b"This is an example exception") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ EM101 + | + = help: Assign to variable; remove string literal + +ℹ Unsafe fix +1 1 | def f_byte(): +2 |- raise RuntimeError(b"This is an example exception") + 2 |+ msg = b"This is an example exception" + 3 |+ raise RuntimeError(msg) +3 4 | +4 5 | +5 6 | def f_byte_empty(): + +EM101_byte_string.py:6:24: EM101 [*] Exception must not use a string literal, assign to variable first + | +5 | def f_byte_empty(): +6 | raise RuntimeError(b"") + | ^^^ EM101 + | + = help: Assign to variable; remove string literal + +ℹ Unsafe fix +3 3 | +4 4 | +5 5 | def f_byte_empty(): +6 |- raise RuntimeError(b"") + 6 |+ msg = b"" + 7 |+ raise RuntimeError(msg)