Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,11 @@ def f_typing_cast_excluded_aliased():
raise my_cast(RuntimeError, "This should not trigger EM101")


# Regression test for https://github.com/astral-sh/ruff/issues/24335
# (Do not shadow existing `msg`)
def f():
msg = "."
try:
raise RuntimeError("!")
except RuntimeError:
return msg
19 changes: 19 additions & 0 deletions crates/ruff_linter/src/fix/edits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
use anyhow::{Context, Result};

use ruff_python_ast::AnyNodeRef;
use ruff_python_ast::name::Name;
use ruff_python_ast::token::{self, Tokens, parenthesized_range};
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, ExprList, Parameters, Stmt};
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_semantic::SemanticModel;
use ruff_python_trivia::textwrap::dedent_to;
use ruff_python_trivia::{
PythonWhitespace, SimpleTokenKind, SimpleTokenizer, has_leading_content, is_python_whitespace,
Expand Down Expand Up @@ -397,6 +399,23 @@ pub(crate) fn add_parameter(
}
}

/// Return a fresh binding name derived from `base` that does not shadow an
/// existing non-builtin symbol in the current semantic scope.
pub(crate) fn fresh_binding_name(semantic: &SemanticModel<'_>, base: &str) -> Name {
if semantic.is_available(base) {
return Name::new(base);
}

let mut index = 0;
loop {
let candidate = format!("{base}_{index}");
if semantic.is_available(&candidate) {
return Name::new(candidate);
}
index += 1;
}
}

/// Safely adjust the indentation of the indented block at [`TextRange`].
///
/// The [`TextRange`] is assumed to represent an entire indented block, including the leading
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::whitespace;
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
use ruff_python_codegen::Stylist;
use ruff_python_semantic::SemanticModel;
use ruff_source_file::LineRanges;
use ruff_text_size::Ranged;

use crate::Locator;
use crate::checkers::ast::Checker;
use crate::fix::edits::fresh_binding_name;
use crate::registry::Rule;
use crate::{Edit, Fix, FixAvailability, Violation};

Expand Down Expand Up @@ -211,6 +213,7 @@ pub(crate) fn string_in_exception(checker: &Checker, stmt: &Stmt, exc: &Expr) {
indentation,
checker.stylist(),
checker.locator(),
checker.semantic(),
));
}
}
Expand All @@ -229,6 +232,7 @@ pub(crate) fn string_in_exception(checker: &Checker, stmt: &Stmt, exc: &Expr) {
indentation,
checker.stylist(),
checker.locator(),
checker.semantic(),
));
}
}
Expand All @@ -246,6 +250,7 @@ pub(crate) fn string_in_exception(checker: &Checker, stmt: &Stmt, exc: &Expr) {
indentation,
checker.stylist(),
checker.locator(),
checker.semantic(),
));
}
}
Expand All @@ -266,6 +271,7 @@ pub(crate) fn string_in_exception(checker: &Checker, stmt: &Stmt, exc: &Expr) {
indentation,
checker.stylist(),
checker.locator(),
checker.semantic(),
));
}
}
Expand Down Expand Up @@ -293,19 +299,23 @@ fn generate_fix(
stmt_indentation: &str,
stylist: &Stylist,
locator: &Locator,
semantic: &SemanticModel,
) -> Fix {
let msg_name = fresh_binding_name(semantic, "msg");
Fix::unsafe_edits(
Edit::insertion(
if locator.contains_line_break(exc_arg.range()) {
format!(
"msg = ({line_ending}{stmt_indentation}{indentation}{}{line_ending}{stmt_indentation}){line_ending}{stmt_indentation}",
"{} = ({line_ending}{stmt_indentation}{indentation}{}{line_ending}{stmt_indentation}){line_ending}{stmt_indentation}",
msg_name,
locator.slice(exc_arg.range()),
line_ending = stylist.line_ending().as_str(),
indentation = stylist.indentation().as_str(),
)
} else {
format!(
"msg = {}{}{}",
"{} = {}{}{}",
msg_name,
locator.slice(exc_arg.range()),
stylist.line_ending().as_str(),
stmt_indentation,
Expand All @@ -314,7 +324,7 @@ fn generate_fix(
stmt.start(),
),
[Edit::range_replacement(
String::from("msg"),
msg_name.to_string(),
exc_arg.range(),
)],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ help: Assign to variable; remove string literal
30 | def f_msg_defined():
31 | msg = "hello"
- raise RuntimeError("This is an example exception")
32 + msg = "This is an example exception"
33 + raise RuntimeError(msg)
32 + msg_0 = "This is an example exception"
33 + raise RuntimeError(msg_0)
34 |
35 |
36 | def f_msg_in_nested_scope():
Expand Down Expand Up @@ -111,8 +111,8 @@ help: Assign to variable; remove string literal
44 |
45 | def nested():
- raise RuntimeError("This is an example exception")
46 + msg = "This is an example exception"
47 + raise RuntimeError(msg)
46 + msg_0 = "This is an example exception"
47 + raise RuntimeError(msg_0)
48 |
49 |
50 | def f_fix_indentation_check(foo):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ help: Assign to variable; remove string literal
30 | def f_msg_defined():
31 | msg = "hello"
- raise RuntimeError("This is an example exception")
32 + msg = "This is an example exception"
33 + raise RuntimeError(msg)
32 + msg_0 = "This is an example exception"
33 + raise RuntimeError(msg_0)
34 |
35 |
36 | def f_msg_in_nested_scope():
Expand Down Expand Up @@ -149,8 +149,8 @@ help: Assign to variable; remove string literal
44 |
45 | def nested():
- raise RuntimeError("This is an example exception")
46 + msg = "This is an example exception"
47 + raise RuntimeError(msg)
46 + msg_0 = "This is an example exception"
47 + raise RuntimeError(msg_0)
48 |
49 |
50 | def f_fix_indentation_check(foo):
Expand Down Expand Up @@ -348,3 +348,24 @@ help: Assign to variable; remove `.format()` string
95 |
96 | def raise_typing_cast_exception():
note: This is an unsafe fix and may change runtime behavior

EM101 [*] Exception must not use a string literal, assign to variable first
--> EM.py:118:28
|
116 | msg = "."
117 | try:
118 | raise RuntimeError("!")
| ^^^
119 | except RuntimeError:
120 | return msg
|
help: Assign to variable; remove string literal
115 | def f():
116 | msg = "."
117 | try:
- raise RuntimeError("!")
118 + msg_0 = "!"
119 + raise RuntimeError(msg_0)
120 | except RuntimeError:
121 | return msg
note: This is an unsafe fix and may change runtime behavior
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::fix::edits::fresh_binding_name;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::name::Name;
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::{SemanticModel, analyze::typing::is_mutable_expr};

Expand Down Expand Up @@ -129,20 +129,3 @@ fn generate_dict_comprehension(
};
generator.expr(&dict_comp.into())
}

/// Return a fresh binding name derived from `base` that does not shadow an
/// existing non-builtin symbol in the current semantic scope.
fn fresh_binding_name(semantic: &SemanticModel<'_>, base: &str) -> Name {
if semantic.is_available(base) {
return Name::new(base);
}

let mut index = 0;
loop {
let candidate = format!("{base}_{index}");
if semantic.is_available(&candidate) {
return Name::new(candidate);
}
index += 1;
}
}
Loading