mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:35:58 +00:00
## Summary Addresses #11974 to add a `RUF` rule to replace `print` expressions in `assert` statements with the inner message. An autofix is available, but is considered unsafe as it changes behaviour of the execution, notably: - removal of the printout in `stdout`, and - `AssertionError` instance containing a different message. While the detection of the condition is a straightforward matter, deciding how to resolve the print arguments into a string literal can be a relatively subjective matter. The implementation of this PR chooses to be as tolerant as possible, and will attempt to reformat any number of `print` arguments containing single or concatenated strings or variables into either a string literal, or a f-string if any variables or placeholders are detected. ## Test Plan `cargo test`. ## Examples For ease of discussion, this is the diff for the tests: ```diff # Standard Case # Expects: # - single StringLiteral -assert True, print("This print is not intentional.") +assert True, "This print is not intentional." # Concatenated string literals # Expects: # - single StringLiteral -assert True, print("This print" " is not intentional.") +assert True, "This print is not intentional." # Positional arguments, string literals # Expects: # - single StringLiteral concatenated with " " -assert True, print("This print", "is not intentional") +assert True, "This print is not intentional" # Concatenated string literals combined with Positional arguments # Expects: # - single stringliteral concatenated with " " only between `print` and `is` -assert True, print("This " "print", "is not intentional.") +assert True, "This print is not intentional." # Positional arguments, string literals with a variable # Expects: # - single FString concatenated with " " -assert True, print("This", print.__name__, "is not intentional.") +assert True, f"This {print.__name__} is not intentional." # Mixed brackets string literals # Expects: # - single StringLiteral concatenated with " " -assert True, print("This print", 'is not intentional', """and should be removed""") +assert True, "This print is not intentional and should be removed" # Mixed brackets with other brackets inside # Expects: # - single StringLiteral concatenated with " " and escaped brackets -assert True, print("This print", 'is not "intentional"', """and "should" be 'removed'""") +assert True, "This print is not \"intentional\" and \"should\" be 'removed'" # Positional arguments, string literals with a separator # Expects: # - single StringLiteral concatenated with "|" -assert True, print("This print", "is not intentional", sep="|") +assert True, "This print|is not intentional" # Positional arguments, string literals with None as separator # Expects: # - single StringLiteral concatenated with " " -assert True, print("This print", "is not intentional", sep=None) +assert True, "This print is not intentional" # Positional arguments, string literals with variable as separator, needs f-string # Expects: # - single FString concatenated with "{U00A0}" -assert True, print("This print", "is not intentional", sep=U00A0) +assert True, f"This print{U00A0}is not intentional" # Unnecessary f-string # Expects: # - single StringLiteral -assert True, print(f"This f-string is just a literal.") +assert True, "This f-string is just a literal." # Positional arguments, string literals and f-strings # Expects: # - single FString concatenated with " " -assert True, print("This print", f"is not {'intentional':s}") +assert True, f"This print is not {'intentional':s}" # Positional arguments, string literals and f-strings with a separator # Expects: # - single FString concatenated with "|" -assert True, print("This print", f"is not {'intentional':s}", sep="|") +assert True, f"This print|is not {'intentional':s}" # A single f-string # Expects: # - single FString -assert True, print(f"This print is not {'intentional':s}") +assert True, f"This print is not {'intentional':s}" # A single f-string with a redundant separator # Expects: # - single FString -assert True, print(f"This print is not {'intentional':s}", sep="|") +assert True, f"This print is not {'intentional':s}" # Complex f-string with variable as separator # Expects: # - single FString concatenated with "{U00A0}", all placeholders preserved condition = "True is True" maintainer = "John Doe" -assert True, print("Unreachable due to", condition, f", ask {maintainer} for advice", sep=U00A0) +assert True, f"Unreachable due to{U00A0}{condition}{U00A0}, ask {maintainer} for advice" # Empty print # Expects: # - `msg` entirely removed from assertion -assert True, print() +assert True # Empty print with separator # Expects: # - `msg` entirely removed from assertion -assert True, print(sep=" ") +assert True # Custom print function that actually returns a string # Expects: @@ -100,4 +100,4 @@ # Use of `builtins.print` # Expects: # - single StringLiteral -assert True, builtins.print("This print should be removed.") +assert True, "This print should be removed." ``` ## Known Issues The current implementation resolves all arguments and separators of the `print` expression into a single string, be it `StringLiteralValue::single` or a `FStringValue::single`. This: - potentially joins together strings well beyond the ideal character limit for each line, and - does not preserve multi-line strings in their original format, in favour of a single line `"...\n...\n..."` format. These are purely formatting issues only occurring in unusual scenarios. Additionally, the autofix will tolerate `print` calls that were previously invalid: ```python assert True, print("this", "should not be allowed", sep=42) ``` This will be transformed into ```python assert True, f"this{42}should not be allowed" ``` which some could argue is an alteration of behaviour. --------- Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
parent
0c8b5eb17a
commit
c3f61a012e
8 changed files with 810 additions and 5 deletions
108
crates/ruff_linter/resources/test/fixtures/ruff/RUF030.py
vendored
Normal file
108
crates/ruff_linter/resources/test/fixtures/ruff/RUF030.py
vendored
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
U00A0 = "\u00a0"
|
||||||
|
|
||||||
|
# Standard Case
|
||||||
|
# Expects:
|
||||||
|
# - single StringLiteral
|
||||||
|
assert True, print("This print is not intentional.")
|
||||||
|
|
||||||
|
# Concatenated string literals
|
||||||
|
# Expects:
|
||||||
|
# - single StringLiteral
|
||||||
|
assert True, print("This print" " is not intentional.")
|
||||||
|
|
||||||
|
# Positional arguments, string literals
|
||||||
|
# Expects:
|
||||||
|
# - single StringLiteral concatenated with " "
|
||||||
|
assert True, print("This print", "is not intentional")
|
||||||
|
|
||||||
|
# Concatenated string literals combined with Positional arguments
|
||||||
|
# Expects:
|
||||||
|
# - single stringliteral concatenated with " " only between `print` and `is`
|
||||||
|
assert True, print("This " "print", "is not intentional.")
|
||||||
|
|
||||||
|
# Positional arguments, string literals with a variable
|
||||||
|
# Expects:
|
||||||
|
# - single FString concatenated with " "
|
||||||
|
assert True, print("This", print.__name__, "is not intentional.")
|
||||||
|
|
||||||
|
# Mixed brackets string literals
|
||||||
|
# Expects:
|
||||||
|
# - single StringLiteral concatenated with " "
|
||||||
|
assert True, print("This print", 'is not intentional', """and should be removed""")
|
||||||
|
|
||||||
|
# Mixed brackets with other brackets inside
|
||||||
|
# Expects:
|
||||||
|
# - single StringLiteral concatenated with " " and escaped brackets
|
||||||
|
assert True, print("This print", 'is not "intentional"', """and "should" be 'removed'""")
|
||||||
|
|
||||||
|
# Positional arguments, string literals with a separator
|
||||||
|
# Expects:
|
||||||
|
# - single StringLiteral concatenated with "|"
|
||||||
|
assert True, print("This print", "is not intentional", sep="|")
|
||||||
|
|
||||||
|
# Positional arguments, string literals with None as separator
|
||||||
|
# Expects:
|
||||||
|
# - single StringLiteral concatenated with " "
|
||||||
|
assert True, print("This print", "is not intentional", sep=None)
|
||||||
|
|
||||||
|
# Positional arguments, string literals with variable as separator, needs f-string
|
||||||
|
# Expects:
|
||||||
|
# - single FString concatenated with "{U00A0}"
|
||||||
|
assert True, print("This print", "is not intentional", sep=U00A0)
|
||||||
|
|
||||||
|
# Unnecessary f-string
|
||||||
|
# Expects:
|
||||||
|
# - single StringLiteral
|
||||||
|
assert True, print(f"This f-string is just a literal.")
|
||||||
|
|
||||||
|
# Positional arguments, string literals and f-strings
|
||||||
|
# Expects:
|
||||||
|
# - single FString concatenated with " "
|
||||||
|
assert True, print("This print", f"is not {'intentional':s}")
|
||||||
|
|
||||||
|
# Positional arguments, string literals and f-strings with a separator
|
||||||
|
# Expects:
|
||||||
|
# - single FString concatenated with "|"
|
||||||
|
assert True, print("This print", f"is not {'intentional':s}", sep="|")
|
||||||
|
|
||||||
|
# A single f-string
|
||||||
|
# Expects:
|
||||||
|
# - single FString
|
||||||
|
assert True, print(f"This print is not {'intentional':s}")
|
||||||
|
|
||||||
|
# A single f-string with a redundant separator
|
||||||
|
# Expects:
|
||||||
|
# - single FString
|
||||||
|
assert True, print(f"This print is not {'intentional':s}", sep="|")
|
||||||
|
|
||||||
|
# Complex f-string with variable as separator
|
||||||
|
# Expects:
|
||||||
|
# - single FString concatenated with "{U00A0}", all placeholders preserved
|
||||||
|
condition = "True is True"
|
||||||
|
maintainer = "John Doe"
|
||||||
|
assert True, print("Unreachable due to", condition, f", ask {maintainer} for advice", sep=U00A0)
|
||||||
|
|
||||||
|
# Empty print
|
||||||
|
# Expects:
|
||||||
|
# - `msg` entirely removed from assertion
|
||||||
|
assert True, print()
|
||||||
|
|
||||||
|
# Empty print with separator
|
||||||
|
# Expects:
|
||||||
|
# - `msg` entirely removed from assertion
|
||||||
|
assert True, print(sep=" ")
|
||||||
|
|
||||||
|
# Custom print function that actually returns a string
|
||||||
|
# Expects:
|
||||||
|
# - no violation as the function is not a built-in print
|
||||||
|
def print(s: str):
|
||||||
|
return "This is my assertion error message: " + s
|
||||||
|
|
||||||
|
assert True, print("this print shall not be removed.")
|
||||||
|
|
||||||
|
import builtins
|
||||||
|
|
||||||
|
# Use of `builtins.print`
|
||||||
|
# Expects:
|
||||||
|
# - single StringLiteral
|
||||||
|
assert True, builtins.print("This print should be removed.")
|
|
@ -1232,11 +1232,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stmt::Assert(ast::StmtAssert {
|
Stmt::Assert(
|
||||||
test,
|
assert_stmt @ ast::StmtAssert {
|
||||||
msg,
|
test,
|
||||||
range: _,
|
msg,
|
||||||
}) => {
|
range: _,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
if !checker.semantic.in_type_checking_block() {
|
if !checker.semantic.in_type_checking_block() {
|
||||||
if checker.enabled(Rule::Assert) {
|
if checker.enabled(Rule::Assert) {
|
||||||
checker
|
checker
|
||||||
|
@ -1267,6 +1269,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::InvalidMockAccess) {
|
if checker.enabled(Rule::InvalidMockAccess) {
|
||||||
pygrep_hooks::rules::non_existent_mock_method(checker, test);
|
pygrep_hooks::rules::non_existent_mock_method(checker, test);
|
||||||
}
|
}
|
||||||
|
if checker.enabled(Rule::AssertWithPrintMessage) {
|
||||||
|
ruff::rules::assert_with_print_message(checker, assert_stmt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Stmt::With(with_stmt @ ast::StmtWith { items, body, .. }) => {
|
Stmt::With(with_stmt @ ast::StmtWith { items, body, .. }) => {
|
||||||
if checker.enabled(Rule::TooManyNestedBlocks) {
|
if checker.enabled(Rule::TooManyNestedBlocks) {
|
||||||
|
|
|
@ -977,6 +977,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Ruff, "027") => (RuleGroup::Preview, rules::ruff::rules::MissingFStringSyntax),
|
(Ruff, "027") => (RuleGroup::Preview, rules::ruff::rules::MissingFStringSyntax),
|
||||||
(Ruff, "028") => (RuleGroup::Preview, rules::ruff::rules::InvalidFormatterSuppressionComment),
|
(Ruff, "028") => (RuleGroup::Preview, rules::ruff::rules::InvalidFormatterSuppressionComment),
|
||||||
(Ruff, "029") => (RuleGroup::Preview, rules::ruff::rules::UnusedAsync),
|
(Ruff, "029") => (RuleGroup::Preview, rules::ruff::rules::UnusedAsync),
|
||||||
|
(Ruff, "030") => (RuleGroup::Preview, rules::ruff::rules::AssertWithPrintMessage),
|
||||||
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
||||||
(Ruff, "101") => (RuleGroup::Preview, rules::ruff::rules::RedirectedNOQA),
|
(Ruff, "101") => (RuleGroup::Preview, rules::ruff::rules::RedirectedNOQA),
|
||||||
(Ruff, "200") => (RuleGroup::Stable, rules::ruff::rules::InvalidPyprojectToml),
|
(Ruff, "200") => (RuleGroup::Stable, rules::ruff::rules::InvalidPyprojectToml),
|
||||||
|
|
|
@ -54,6 +54,7 @@ mod tests {
|
||||||
#[test_case(Rule::MissingFStringSyntax, Path::new("RUF027_2.py"))]
|
#[test_case(Rule::MissingFStringSyntax, Path::new("RUF027_2.py"))]
|
||||||
#[test_case(Rule::InvalidFormatterSuppressionComment, Path::new("RUF028.py"))]
|
#[test_case(Rule::InvalidFormatterSuppressionComment, Path::new("RUF028.py"))]
|
||||||
#[test_case(Rule::UnusedAsync, Path::new("RUF029.py"))]
|
#[test_case(Rule::UnusedAsync, Path::new("RUF029.py"))]
|
||||||
|
#[test_case(Rule::AssertWithPrintMessage, Path::new("RUF030.py"))]
|
||||||
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101.py"))]
|
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101.py"))]
|
||||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||||
|
|
|
@ -0,0 +1,290 @@
|
||||||
|
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||||
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
|
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for uses of `assert expression, print(message)`.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// The return value of the second expression is used as the contents of the
|
||||||
|
/// `AssertionError` raised by the `assert` statement. Using a `print` expression
|
||||||
|
/// in this context will output the message to `stdout`, before raising an
|
||||||
|
/// empty `AssertionError(None)`.
|
||||||
|
///
|
||||||
|
/// Instead, remove the `print` and pass the message directly as the second
|
||||||
|
/// expression, allowing `stderr` to capture the message in a well-formatted context.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// assert False, print("This is a message")
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// assert False, "This is a message"
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Fix safety
|
||||||
|
/// This rule's fix is marked as unsafe, as changing the second expression
|
||||||
|
/// will result in a different `AssertionError` message being raised, as well as
|
||||||
|
/// a change in `stdout` output.
|
||||||
|
///
|
||||||
|
/// ## References
|
||||||
|
/// - [Python documentation: `assert`](https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement)
|
||||||
|
#[violation]
|
||||||
|
pub struct AssertWithPrintMessage;
|
||||||
|
|
||||||
|
impl AlwaysFixableViolation for AssertWithPrintMessage {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
format!("`print()` expression in `assert` statement is likely unintentional")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fix_title(&self) -> String {
|
||||||
|
"Remove `print`".to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// RUF030
|
||||||
|
///
|
||||||
|
/// Checks if the `msg` argument to an `assert` statement is a `print` call, and if so,
|
||||||
|
/// replace the message with the arguments to the `print` call.
|
||||||
|
pub(crate) fn assert_with_print_message(checker: &mut Checker, stmt: &ast::StmtAssert) {
|
||||||
|
if let Some(Expr::Call(call)) = stmt.msg.as_deref() {
|
||||||
|
// We have to check that the print call is a call to the built-in `print` function
|
||||||
|
let semantic = checker.semantic();
|
||||||
|
|
||||||
|
if semantic.match_builtin_expr(&call.func, "print") {
|
||||||
|
// This is the confirmed rule condition
|
||||||
|
let mut diagnostic = Diagnostic::new(AssertWithPrintMessage, call.range());
|
||||||
|
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||||
|
checker.generator().stmt(&Stmt::Assert(ast::StmtAssert {
|
||||||
|
test: stmt.test.clone(),
|
||||||
|
msg: print_arguments::to_expr(&call.arguments).map(Box::new),
|
||||||
|
range: TextRange::default(),
|
||||||
|
})),
|
||||||
|
// We have to replace the entire statement,
|
||||||
|
// as the `print` could be empty and thus `call.range()`
|
||||||
|
// will cease to exist.
|
||||||
|
stmt.range(),
|
||||||
|
)));
|
||||||
|
checker.diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the arguments from a `print` call and converts them to some kind of string
|
||||||
|
/// expression.
|
||||||
|
///
|
||||||
|
/// Three cases are handled:
|
||||||
|
/// - if there are no arguments, return `None` so that `diagnostic` can remove `msg` from `assert`;
|
||||||
|
/// - if all of `print` arguments including `sep` are string literals, return a `Expr::StringLiteral`;
|
||||||
|
/// - otherwise, return a `Expr::FString`.
|
||||||
|
mod print_arguments {
|
||||||
|
use itertools::Itertools;
|
||||||
|
use ruff_python_ast::{
|
||||||
|
Arguments, ConversionFlag, Expr, ExprFString, ExprStringLiteral, FString, FStringElement,
|
||||||
|
FStringElements, FStringExpressionElement, FStringFlags, FStringLiteralElement,
|
||||||
|
FStringValue, StringLiteral, StringLiteralFlags, StringLiteralValue,
|
||||||
|
};
|
||||||
|
use ruff_text_size::TextRange;
|
||||||
|
|
||||||
|
/// Converts an expression to a list of `FStringElement`s.
|
||||||
|
///
|
||||||
|
/// Three cases are handled:
|
||||||
|
/// - if the expression is a string literal, each part of the string will be converted to a
|
||||||
|
/// `FStringLiteralElement`.
|
||||||
|
/// - if the expression is an f-string, the elements will be returned as-is.
|
||||||
|
/// - otherwise, the expression will be wrapped in a `FStringExpressionElement`.
|
||||||
|
fn expr_to_fstring_elements(expr: &Expr) -> Vec<FStringElement> {
|
||||||
|
match expr {
|
||||||
|
// If the expression is a string literal, convert each part to a `FStringLiteralElement`.
|
||||||
|
Expr::StringLiteral(string) => string
|
||||||
|
.value
|
||||||
|
.iter()
|
||||||
|
.map(|part| {
|
||||||
|
FStringElement::Literal(FStringLiteralElement {
|
||||||
|
value: part.value.clone(),
|
||||||
|
range: TextRange::default(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
|
||||||
|
// If the expression is an f-string, return the elements.
|
||||||
|
Expr::FString(fstring) => fstring.value.elements().cloned().collect(),
|
||||||
|
|
||||||
|
// Otherwise, return the expression as a single `FStringExpressionElement` wrapping
|
||||||
|
// the expression.
|
||||||
|
expr => vec![FStringElement::Expression(FStringExpressionElement {
|
||||||
|
expression: Box::new(expr.clone()),
|
||||||
|
debug_text: None,
|
||||||
|
conversion: ConversionFlag::None,
|
||||||
|
format_spec: None,
|
||||||
|
range: TextRange::default(),
|
||||||
|
})],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a list of `FStringElement`s to a list of `StringLiteral`s.
|
||||||
|
///
|
||||||
|
/// If any of the elements are not string literals, `None` is returned.
|
||||||
|
///
|
||||||
|
/// This is useful (in combination with [`expr_to_fstring_elements`]) for
|
||||||
|
/// checking if the `sep` and `args` arguments to `print` are all string
|
||||||
|
/// literals.
|
||||||
|
fn fstring_elements_to_string_literals<'a>(
|
||||||
|
mut elements: impl ExactSizeIterator<Item = &'a FStringElement>,
|
||||||
|
) -> Option<Vec<StringLiteral>> {
|
||||||
|
elements.try_fold(Vec::with_capacity(elements.len()), |mut acc, element| {
|
||||||
|
if let FStringElement::Literal(literal) = element {
|
||||||
|
acc.push(StringLiteral {
|
||||||
|
value: literal.value.clone(),
|
||||||
|
flags: StringLiteralFlags::default(),
|
||||||
|
range: TextRange::default(),
|
||||||
|
});
|
||||||
|
Some(acc)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the `sep` and `args` arguments to a [`Expr::StringLiteral`].
|
||||||
|
///
|
||||||
|
/// This function will return [`None`] if any of the arguments are not string literals,
|
||||||
|
/// or if there are no arguments at all.
|
||||||
|
fn args_to_string_literal_expr<'a>(
|
||||||
|
args: impl ExactSizeIterator<Item = &'a Vec<FStringElement>>,
|
||||||
|
sep: impl ExactSizeIterator<Item = &'a FStringElement>,
|
||||||
|
) -> Option<Expr> {
|
||||||
|
// If there are no arguments, short-circuit and return `None`
|
||||||
|
if args.len() == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to convert the `sep` and `args` arguments to string literals.
|
||||||
|
// We need to maintain `args` as a Vec of Vecs, as the first Vec represents
|
||||||
|
// the arguments to the `print` call, and the inner Vecs represent the elements
|
||||||
|
// of a concatenated string literal. (e.g. "text", "text" "text") The `sep` will
|
||||||
|
// be inserted only between the outer Vecs.
|
||||||
|
let (Some(sep), Some(args)) = (
|
||||||
|
fstring_elements_to_string_literals(sep),
|
||||||
|
args.map(|arg| fstring_elements_to_string_literals(arg.iter()))
|
||||||
|
.collect::<Option<Vec<_>>>(),
|
||||||
|
) else {
|
||||||
|
// If any of the arguments are not string literals, return None
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Put the `sep` into a single Rust `String`
|
||||||
|
let sep_string = sep
|
||||||
|
.into_iter()
|
||||||
|
.map(|string_literal| string_literal.value)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
// Join the `args` with the `sep`
|
||||||
|
let combined_string = args
|
||||||
|
.into_iter()
|
||||||
|
.map(|string_literals| {
|
||||||
|
string_literals
|
||||||
|
.into_iter()
|
||||||
|
.map(|string_literal| string_literal.value)
|
||||||
|
.join("")
|
||||||
|
})
|
||||||
|
.join(&sep_string);
|
||||||
|
|
||||||
|
Some(Expr::StringLiteral(ExprStringLiteral {
|
||||||
|
range: TextRange::default(),
|
||||||
|
value: StringLiteralValue::single(StringLiteral {
|
||||||
|
value: combined_string.into(),
|
||||||
|
flags: StringLiteralFlags::default(),
|
||||||
|
range: TextRange::default(),
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the `sep` and `args` arguments to a [`Expr::FString`].
|
||||||
|
///
|
||||||
|
/// This function will only return [`None`] if there are no arguments at all.
|
||||||
|
///
|
||||||
|
/// ## Note
|
||||||
|
/// This function will always return an f-string, even if all arguments are string literals.
|
||||||
|
/// This can produce unnecessary f-strings.
|
||||||
|
///
|
||||||
|
/// Also note that the iterator arguments of this function are consumed,
|
||||||
|
/// as opposed to the references taken by [`args_to_string_literal_expr`].
|
||||||
|
fn args_to_fstring_expr(
|
||||||
|
mut args: impl ExactSizeIterator<Item = Vec<FStringElement>>,
|
||||||
|
sep: impl ExactSizeIterator<Item = FStringElement>,
|
||||||
|
) -> Option<Expr> {
|
||||||
|
// If there are no arguments, short-circuit and return `None`
|
||||||
|
let first_arg = args.next()?;
|
||||||
|
let sep = sep.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let fstring_elements = args.fold(first_arg, |mut elements, arg| {
|
||||||
|
elements.extend(sep.clone());
|
||||||
|
elements.extend(arg);
|
||||||
|
elements
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(Expr::FString(ExprFString {
|
||||||
|
value: FStringValue::single(FString {
|
||||||
|
elements: FStringElements::from(fstring_elements),
|
||||||
|
flags: FStringFlags::default(),
|
||||||
|
range: TextRange::default(),
|
||||||
|
}),
|
||||||
|
range: TextRange::default(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to convert the `print` arguments to a suitable string expression.
|
||||||
|
///
|
||||||
|
/// If the `sep` argument is provided, it will be used as the separator between
|
||||||
|
/// arguments. Otherwise, a space will be used.
|
||||||
|
///
|
||||||
|
/// `end` and `file` keyword arguments are ignored, as they don't affect the
|
||||||
|
/// output of the `print` statement.
|
||||||
|
///
|
||||||
|
/// ## Returns
|
||||||
|
///
|
||||||
|
/// - [`Some`]<[`Expr::StringLiteral`]> if all arguments including `sep` are string literals.
|
||||||
|
/// - [`Some`]<[`Expr::FString`]> if any of the arguments are not string literals.
|
||||||
|
/// - [`None`] if the `print` contains no positional arguments at all.
|
||||||
|
pub(super) fn to_expr(arguments: &Arguments) -> Option<Expr> {
|
||||||
|
// Convert the `sep` argument into `FStringElement`s
|
||||||
|
let sep = arguments
|
||||||
|
.find_keyword("sep")
|
||||||
|
.and_then(
|
||||||
|
// If the `sep` argument is `None`, treat this as default behavior.
|
||||||
|
|keyword| {
|
||||||
|
if let Expr::NoneLiteral(_) = keyword.value {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(&keyword.value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map(expr_to_fstring_elements)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
vec![FStringElement::Literal(FStringLiteralElement {
|
||||||
|
range: TextRange::default(),
|
||||||
|
value: " ".into(),
|
||||||
|
})]
|
||||||
|
});
|
||||||
|
|
||||||
|
let args = arguments
|
||||||
|
.args
|
||||||
|
.iter()
|
||||||
|
.map(expr_to_fstring_elements)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Attempt to convert the `sep` and `args` arguments to a string literal,
|
||||||
|
// falling back to an f-string if the arguments are not all string literals.
|
||||||
|
args_to_string_literal_expr(args.iter(), sep.iter())
|
||||||
|
.or_else(|| args_to_fstring_expr(args.into_iter(), sep.into_iter()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
pub(crate) use ambiguous_unicode_character::*;
|
pub(crate) use ambiguous_unicode_character::*;
|
||||||
|
pub(crate) use assert_with_print_message::*;
|
||||||
pub(crate) use assignment_in_assert::*;
|
pub(crate) use assignment_in_assert::*;
|
||||||
pub(crate) use asyncio_dangling_task::*;
|
pub(crate) use asyncio_dangling_task::*;
|
||||||
pub(crate) use collection_literal_concatenation::*;
|
pub(crate) use collection_literal_concatenation::*;
|
||||||
|
@ -30,6 +31,7 @@ pub(crate) use unused_async::*;
|
||||||
pub(crate) use unused_noqa::*;
|
pub(crate) use unused_noqa::*;
|
||||||
|
|
||||||
mod ambiguous_unicode_character;
|
mod ambiguous_unicode_character;
|
||||||
|
mod assert_with_print_message;
|
||||||
mod assignment_in_assert;
|
mod assignment_in_assert;
|
||||||
mod asyncio_dangling_task;
|
mod asyncio_dangling_task;
|
||||||
mod collection_literal_concatenation;
|
mod collection_literal_concatenation;
|
||||||
|
|
|
@ -0,0 +1,396 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||||
|
---
|
||||||
|
RUF030.py:6:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
4 | # Expects:
|
||||||
|
5 | # - single StringLiteral
|
||||||
|
6 | assert True, print("This print is not intentional.")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
7 |
|
||||||
|
8 | # Concatenated string literals
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
3 3 | # Standard Case
|
||||||
|
4 4 | # Expects:
|
||||||
|
5 5 | # - single StringLiteral
|
||||||
|
6 |-assert True, print("This print is not intentional.")
|
||||||
|
6 |+assert True, "This print is not intentional."
|
||||||
|
7 7 |
|
||||||
|
8 8 | # Concatenated string literals
|
||||||
|
9 9 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:11:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
9 | # Expects:
|
||||||
|
10 | # - single StringLiteral
|
||||||
|
11 | assert True, print("This print" " is not intentional.")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
12 |
|
||||||
|
13 | # Positional arguments, string literals
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
8 8 | # Concatenated string literals
|
||||||
|
9 9 | # Expects:
|
||||||
|
10 10 | # - single StringLiteral
|
||||||
|
11 |-assert True, print("This print" " is not intentional.")
|
||||||
|
11 |+assert True, "This print is not intentional."
|
||||||
|
12 12 |
|
||||||
|
13 13 | # Positional arguments, string literals
|
||||||
|
14 14 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:16:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
14 | # Expects:
|
||||||
|
15 | # - single StringLiteral concatenated with " "
|
||||||
|
16 | assert True, print("This print", "is not intentional")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
17 |
|
||||||
|
18 | # Concatenated string literals combined with Positional arguments
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
13 13 | # Positional arguments, string literals
|
||||||
|
14 14 | # Expects:
|
||||||
|
15 15 | # - single StringLiteral concatenated with " "
|
||||||
|
16 |-assert True, print("This print", "is not intentional")
|
||||||
|
16 |+assert True, "This print is not intentional"
|
||||||
|
17 17 |
|
||||||
|
18 18 | # Concatenated string literals combined with Positional arguments
|
||||||
|
19 19 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:21:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
19 | # Expects:
|
||||||
|
20 | # - single stringliteral concatenated with " " only between `print` and `is`
|
||||||
|
21 | assert True, print("This " "print", "is not intentional.")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
22 |
|
||||||
|
23 | # Positional arguments, string literals with a variable
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
18 18 | # Concatenated string literals combined with Positional arguments
|
||||||
|
19 19 | # Expects:
|
||||||
|
20 20 | # - single stringliteral concatenated with " " only between `print` and `is`
|
||||||
|
21 |-assert True, print("This " "print", "is not intentional.")
|
||||||
|
21 |+assert True, "This print is not intentional."
|
||||||
|
22 22 |
|
||||||
|
23 23 | # Positional arguments, string literals with a variable
|
||||||
|
24 24 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:26:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
24 | # Expects:
|
||||||
|
25 | # - single FString concatenated with " "
|
||||||
|
26 | assert True, print("This", print.__name__, "is not intentional.")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
27 |
|
||||||
|
28 | # Mixed brackets string literals
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
23 23 | # Positional arguments, string literals with a variable
|
||||||
|
24 24 | # Expects:
|
||||||
|
25 25 | # - single FString concatenated with " "
|
||||||
|
26 |-assert True, print("This", print.__name__, "is not intentional.")
|
||||||
|
26 |+assert True, f"This {print.__name__} is not intentional."
|
||||||
|
27 27 |
|
||||||
|
28 28 | # Mixed brackets string literals
|
||||||
|
29 29 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:31:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
29 | # Expects:
|
||||||
|
30 | # - single StringLiteral concatenated with " "
|
||||||
|
31 | assert True, print("This print", 'is not intentional', """and should be removed""")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
32 |
|
||||||
|
33 | # Mixed brackets with other brackets inside
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
28 28 | # Mixed brackets string literals
|
||||||
|
29 29 | # Expects:
|
||||||
|
30 30 | # - single StringLiteral concatenated with " "
|
||||||
|
31 |-assert True, print("This print", 'is not intentional', """and should be removed""")
|
||||||
|
31 |+assert True, "This print is not intentional and should be removed"
|
||||||
|
32 32 |
|
||||||
|
33 33 | # Mixed brackets with other brackets inside
|
||||||
|
34 34 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:36:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
34 | # Expects:
|
||||||
|
35 | # - single StringLiteral concatenated with " " and escaped brackets
|
||||||
|
36 | assert True, print("This print", 'is not "intentional"', """and "should" be 'removed'""")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
37 |
|
||||||
|
38 | # Positional arguments, string literals with a separator
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
33 33 | # Mixed brackets with other brackets inside
|
||||||
|
34 34 | # Expects:
|
||||||
|
35 35 | # - single StringLiteral concatenated with " " and escaped brackets
|
||||||
|
36 |-assert True, print("This print", 'is not "intentional"', """and "should" be 'removed'""")
|
||||||
|
36 |+assert True, "This print is not \"intentional\" and \"should\" be 'removed'"
|
||||||
|
37 37 |
|
||||||
|
38 38 | # Positional arguments, string literals with a separator
|
||||||
|
39 39 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:41:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
39 | # Expects:
|
||||||
|
40 | # - single StringLiteral concatenated with "|"
|
||||||
|
41 | assert True, print("This print", "is not intentional", sep="|")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
42 |
|
||||||
|
43 | # Positional arguments, string literals with None as separator
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
38 38 | # Positional arguments, string literals with a separator
|
||||||
|
39 39 | # Expects:
|
||||||
|
40 40 | # - single StringLiteral concatenated with "|"
|
||||||
|
41 |-assert True, print("This print", "is not intentional", sep="|")
|
||||||
|
41 |+assert True, "This print|is not intentional"
|
||||||
|
42 42 |
|
||||||
|
43 43 | # Positional arguments, string literals with None as separator
|
||||||
|
44 44 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:46:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
44 | # Expects:
|
||||||
|
45 | # - single StringLiteral concatenated with " "
|
||||||
|
46 | assert True, print("This print", "is not intentional", sep=None)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
47 |
|
||||||
|
48 | # Positional arguments, string literals with variable as separator, needs f-string
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
43 43 | # Positional arguments, string literals with None as separator
|
||||||
|
44 44 | # Expects:
|
||||||
|
45 45 | # - single StringLiteral concatenated with " "
|
||||||
|
46 |-assert True, print("This print", "is not intentional", sep=None)
|
||||||
|
46 |+assert True, "This print is not intentional"
|
||||||
|
47 47 |
|
||||||
|
48 48 | # Positional arguments, string literals with variable as separator, needs f-string
|
||||||
|
49 49 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:51:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
49 | # Expects:
|
||||||
|
50 | # - single FString concatenated with "{U00A0}"
|
||||||
|
51 | assert True, print("This print", "is not intentional", sep=U00A0)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
52 |
|
||||||
|
53 | # Unnecessary f-string
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
48 48 | # Positional arguments, string literals with variable as separator, needs f-string
|
||||||
|
49 49 | # Expects:
|
||||||
|
50 50 | # - single FString concatenated with "{U00A0}"
|
||||||
|
51 |-assert True, print("This print", "is not intentional", sep=U00A0)
|
||||||
|
51 |+assert True, f"This print{U00A0}is not intentional"
|
||||||
|
52 52 |
|
||||||
|
53 53 | # Unnecessary f-string
|
||||||
|
54 54 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:56:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
54 | # Expects:
|
||||||
|
55 | # - single StringLiteral
|
||||||
|
56 | assert True, print(f"This f-string is just a literal.")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
57 |
|
||||||
|
58 | # Positional arguments, string literals and f-strings
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
53 53 | # Unnecessary f-string
|
||||||
|
54 54 | # Expects:
|
||||||
|
55 55 | # - single StringLiteral
|
||||||
|
56 |-assert True, print(f"This f-string is just a literal.")
|
||||||
|
56 |+assert True, "This f-string is just a literal."
|
||||||
|
57 57 |
|
||||||
|
58 58 | # Positional arguments, string literals and f-strings
|
||||||
|
59 59 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:61:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
59 | # Expects:
|
||||||
|
60 | # - single FString concatenated with " "
|
||||||
|
61 | assert True, print("This print", f"is not {'intentional':s}")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
62 |
|
||||||
|
63 | # Positional arguments, string literals and f-strings with a separator
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
58 58 | # Positional arguments, string literals and f-strings
|
||||||
|
59 59 | # Expects:
|
||||||
|
60 60 | # - single FString concatenated with " "
|
||||||
|
61 |-assert True, print("This print", f"is not {'intentional':s}")
|
||||||
|
61 |+assert True, f"This print is not {'intentional':s}"
|
||||||
|
62 62 |
|
||||||
|
63 63 | # Positional arguments, string literals and f-strings with a separator
|
||||||
|
64 64 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:66:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
64 | # Expects:
|
||||||
|
65 | # - single FString concatenated with "|"
|
||||||
|
66 | assert True, print("This print", f"is not {'intentional':s}", sep="|")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
67 |
|
||||||
|
68 | # A single f-string
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
63 63 | # Positional arguments, string literals and f-strings with a separator
|
||||||
|
64 64 | # Expects:
|
||||||
|
65 65 | # - single FString concatenated with "|"
|
||||||
|
66 |-assert True, print("This print", f"is not {'intentional':s}", sep="|")
|
||||||
|
66 |+assert True, f"This print|is not {'intentional':s}"
|
||||||
|
67 67 |
|
||||||
|
68 68 | # A single f-string
|
||||||
|
69 69 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:71:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
69 | # Expects:
|
||||||
|
70 | # - single FString
|
||||||
|
71 | assert True, print(f"This print is not {'intentional':s}")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
72 |
|
||||||
|
73 | # A single f-string with a redundant separator
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
68 68 | # A single f-string
|
||||||
|
69 69 | # Expects:
|
||||||
|
70 70 | # - single FString
|
||||||
|
71 |-assert True, print(f"This print is not {'intentional':s}")
|
||||||
|
71 |+assert True, f"This print is not {'intentional':s}"
|
||||||
|
72 72 |
|
||||||
|
73 73 | # A single f-string with a redundant separator
|
||||||
|
74 74 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:76:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
74 | # Expects:
|
||||||
|
75 | # - single FString
|
||||||
|
76 | assert True, print(f"This print is not {'intentional':s}", sep="|")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
77 |
|
||||||
|
78 | # Complex f-string with variable as separator
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
73 73 | # A single f-string with a redundant separator
|
||||||
|
74 74 | # Expects:
|
||||||
|
75 75 | # - single FString
|
||||||
|
76 |-assert True, print(f"This print is not {'intentional':s}", sep="|")
|
||||||
|
76 |+assert True, f"This print is not {'intentional':s}"
|
||||||
|
77 77 |
|
||||||
|
78 78 | # Complex f-string with variable as separator
|
||||||
|
79 79 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:83:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
81 | condition = "True is True"
|
||||||
|
82 | maintainer = "John Doe"
|
||||||
|
83 | assert True, print("Unreachable due to", condition, f", ask {maintainer} for advice", sep=U00A0)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
84 |
|
||||||
|
85 | # Empty print
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
80 80 | # - single FString concatenated with "{U00A0}", all placeholders preserved
|
||||||
|
81 81 | condition = "True is True"
|
||||||
|
82 82 | maintainer = "John Doe"
|
||||||
|
83 |-assert True, print("Unreachable due to", condition, f", ask {maintainer} for advice", sep=U00A0)
|
||||||
|
83 |+assert True, f"Unreachable due to{U00A0}{condition}{U00A0}, ask {maintainer} for advice"
|
||||||
|
84 84 |
|
||||||
|
85 85 | # Empty print
|
||||||
|
86 86 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:88:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
86 | # Expects:
|
||||||
|
87 | # - `msg` entirely removed from assertion
|
||||||
|
88 | assert True, print()
|
||||||
|
| ^^^^^^^ RUF030
|
||||||
|
89 |
|
||||||
|
90 | # Empty print with separator
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
85 85 | # Empty print
|
||||||
|
86 86 | # Expects:
|
||||||
|
87 87 | # - `msg` entirely removed from assertion
|
||||||
|
88 |-assert True, print()
|
||||||
|
88 |+assert True
|
||||||
|
89 89 |
|
||||||
|
90 90 | # Empty print with separator
|
||||||
|
91 91 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:93:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
91 | # Expects:
|
||||||
|
92 | # - `msg` entirely removed from assertion
|
||||||
|
93 | assert True, print(sep=" ")
|
||||||
|
| ^^^^^^^^^^^^^^ RUF030
|
||||||
|
94 |
|
||||||
|
95 | # Custom print function that actually returns a string
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
90 90 | # Empty print with separator
|
||||||
|
91 91 | # Expects:
|
||||||
|
92 92 | # - `msg` entirely removed from assertion
|
||||||
|
93 |-assert True, print(sep=" ")
|
||||||
|
93 |+assert True
|
||||||
|
94 94 |
|
||||||
|
95 95 | # Custom print function that actually returns a string
|
||||||
|
96 96 | # Expects:
|
||||||
|
|
||||||
|
RUF030.py:108:14: RUF030 [*] `print()` expression in `assert` statement is likely unintentional
|
||||||
|
|
|
||||||
|
106 | # Expects:
|
||||||
|
107 | # - single StringLiteral
|
||||||
|
108 | assert True, builtins.print("This print should be removed.")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF030
|
||||||
|
|
|
||||||
|
= help: Remove `print`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
105 105 | # Use of `builtins.print`
|
||||||
|
106 106 | # Expects:
|
||||||
|
107 107 | # - single StringLiteral
|
||||||
|
108 |-assert True, builtins.print("This print should be removed.")
|
||||||
|
108 |+assert True, "This print should be removed."
|
2
ruff.schema.json
generated
2
ruff.schema.json
generated
|
@ -3641,6 +3641,8 @@
|
||||||
"RUF027",
|
"RUF027",
|
||||||
"RUF028",
|
"RUF028",
|
||||||
"RUF029",
|
"RUF029",
|
||||||
|
"RUF03",
|
||||||
|
"RUF030",
|
||||||
"RUF1",
|
"RUF1",
|
||||||
"RUF10",
|
"RUF10",
|
||||||
"RUF100",
|
"RUF100",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue