mirror of https://github.com/astral-sh/ruff
Merge 95efd8c4dc into b0bc990cbf
This commit is contained in:
commit
45af9a6acb
|
|
@ -84,3 +84,9 @@ def in_type_def():
|
||||||
# https://github.com/astral-sh/ruff/issues/18860
|
# https://github.com/astral-sh/ruff/issues/18860
|
||||||
def fuzz_bug():
|
def fuzz_bug():
|
||||||
c('{\t"i}')
|
c('{\t"i}')
|
||||||
|
|
||||||
|
# Test case for backslash handling in f-string interpolations
|
||||||
|
# Should not trigger RUF027 for Python < 3.12 due to backslashes in interpolations
|
||||||
|
def backslash_test():
|
||||||
|
x = "test"
|
||||||
|
print("Hello {'\\n'}{x}") # Should not trigger RUF027 for Python < 3.12
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,21 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_fstring_syntax_backslash_py311() -> Result<()> {
|
||||||
|
let diagnostics = test_path(
|
||||||
|
Path::new("ruff/RUF027_0.py"),
|
||||||
|
&LinterSettings {
|
||||||
|
unresolved_target_version: PythonVersion::PY311.into(),
|
||||||
|
..LinterSettings::for_rule(Rule::MissingFStringSyntax)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
// With Python 3.11, backslashes in interpolations should NOT trigger RUF027
|
||||||
|
// (only the backslash_test function should be skipped)
|
||||||
|
assert_diagnostics!(diagnostics);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn prefer_parentheses_getitem_tuple() -> Result<()> {
|
fn prefer_parentheses_getitem_tuple() -> Result<()> {
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use memchr::memchr2_iter;
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast::{self as ast, PythonVersion};
|
||||||
use ruff_python_literal::format::FormatSpec;
|
use ruff_python_literal::format::FormatSpec;
|
||||||
use ruff_python_parser::parse_expression;
|
use ruff_python_parser::parse_expression;
|
||||||
use ruff_python_semantic::analyze::logging::is_logger_candidate;
|
use ruff_python_semantic::analyze::logging::is_logger_candidate;
|
||||||
|
|
@ -116,7 +116,12 @@ pub(crate) fn missing_fstring_syntax(checker: &Checker, literal: &ast::StringLit
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_be_fstring(literal, checker.locator(), semantic) {
|
if should_be_fstring(
|
||||||
|
literal,
|
||||||
|
checker.locator(),
|
||||||
|
semantic,
|
||||||
|
checker.target_version(),
|
||||||
|
) {
|
||||||
checker
|
checker
|
||||||
.report_diagnostic(MissingFStringSyntax, literal.range())
|
.report_diagnostic(MissingFStringSyntax, literal.range())
|
||||||
.set_fix(fix_fstring_syntax(literal.range()));
|
.set_fix(fix_fstring_syntax(literal.range()));
|
||||||
|
|
@ -180,6 +185,7 @@ fn should_be_fstring(
|
||||||
literal: &ast::StringLiteral,
|
literal: &ast::StringLiteral,
|
||||||
locator: &Locator,
|
locator: &Locator,
|
||||||
semantic: &SemanticModel,
|
semantic: &SemanticModel,
|
||||||
|
target_version: PythonVersion,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if !has_brackets(&literal.value) {
|
if !has_brackets(&literal.value) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -216,6 +222,13 @@ fn should_be_fstring(
|
||||||
for f_string in value.f_strings() {
|
for f_string in value.f_strings() {
|
||||||
let mut has_name = false;
|
let mut has_name = false;
|
||||||
for element in f_string.elements.interpolations() {
|
for element in f_string.elements.interpolations() {
|
||||||
|
// Check if the interpolation expression contains backslashes
|
||||||
|
// F-strings with backslashes in interpolations are only valid in Python 3.12+
|
||||||
|
let interpolation_text = &fstring_expr[element.range()];
|
||||||
|
if interpolation_text.contains('\\') && target_version < PythonVersion::PY312 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if let ast::Expr::Name(ast::ExprName { id, .. }) = element.expression.as_ref() {
|
if let ast::Expr::Name(ast::ExprName { id, .. }) = element.expression.as_ref() {
|
||||||
if arg_names.contains(id) {
|
if arg_names.contains(id) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -320,3 +320,19 @@ help: Add `f` prefix
|
||||||
76 | # fstrings are never correct as type definitions
|
76 | # fstrings are never correct as type definitions
|
||||||
77 | # so we should always skip those
|
77 | # so we should always skip those
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:92:11
|
||||||
|
|
|
||||||
|
90 | def backslash_test():
|
||||||
|
91 | x = "test"
|
||||||
|
92 | print("Hello {'\\n'}{x}") # Should not trigger RUF027 for Python < 3.12
|
||||||
|
| ^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
89 | # Should not trigger RUF027 for Python < 3.12 due to backslashes in interpolations
|
||||||
|
90 | def backslash_test():
|
||||||
|
91 | x = "test"
|
||||||
|
- print("Hello {'\\n'}{x}") # Should not trigger RUF027 for Python < 3.12
|
||||||
|
92 + print(f"Hello {'\\n'}{x}") # Should not trigger RUF027 for Python < 3.12
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,300 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||||
|
---
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:5:7
|
||||||
|
|
|
||||||
|
3 | "always ignore this: {val}"
|
||||||
|
4 |
|
||||||
|
5 | print("but don't ignore this: {val}") # RUF027
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
2 |
|
||||||
|
3 | "always ignore this: {val}"
|
||||||
|
4 |
|
||||||
|
- print("but don't ignore this: {val}") # RUF027
|
||||||
|
5 + print(f"but don't ignore this: {val}") # RUF027
|
||||||
|
6 |
|
||||||
|
7 |
|
||||||
|
8 | def simple_cases():
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:10:9
|
||||||
|
|
|
||||||
|
8 | def simple_cases():
|
||||||
|
9 | a = 4
|
||||||
|
10 | b = "{a}" # RUF027
|
||||||
|
| ^^^^^
|
||||||
|
11 | c = "{a} {b} f'{val}' " # RUF027
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
7 |
|
||||||
|
8 | def simple_cases():
|
||||||
|
9 | a = 4
|
||||||
|
- b = "{a}" # RUF027
|
||||||
|
10 + b = f"{a}" # RUF027
|
||||||
|
11 | c = "{a} {b} f'{val}' " # RUF027
|
||||||
|
12 |
|
||||||
|
13 |
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:11:9
|
||||||
|
|
|
||||||
|
9 | a = 4
|
||||||
|
10 | b = "{a}" # RUF027
|
||||||
|
11 | c = "{a} {b} f'{val}' " # RUF027
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
8 | def simple_cases():
|
||||||
|
9 | a = 4
|
||||||
|
10 | b = "{a}" # RUF027
|
||||||
|
- c = "{a} {b} f'{val}' " # RUF027
|
||||||
|
11 + c = f"{a} {b} f'{val}' " # RUF027
|
||||||
|
12 |
|
||||||
|
13 |
|
||||||
|
14 | def escaped_string():
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:21:9
|
||||||
|
|
|
||||||
|
19 | def raw_string():
|
||||||
|
20 | a = 4
|
||||||
|
21 | b = r"raw string with formatting: {a}" # RUF027
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
22 | c = r"raw string with \backslashes\ and \"escaped quotes\": {a}" # RUF027
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
18 |
|
||||||
|
19 | def raw_string():
|
||||||
|
20 | a = 4
|
||||||
|
- b = r"raw string with formatting: {a}" # RUF027
|
||||||
|
21 + b = fr"raw string with formatting: {a}" # RUF027
|
||||||
|
22 | c = r"raw string with \backslashes\ and \"escaped quotes\": {a}" # RUF027
|
||||||
|
23 |
|
||||||
|
24 |
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:22:9
|
||||||
|
|
|
||||||
|
20 | a = 4
|
||||||
|
21 | b = r"raw string with formatting: {a}" # RUF027
|
||||||
|
22 | c = r"raw string with \backslashes\ and \"escaped quotes\": {a}" # RUF027
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
19 | def raw_string():
|
||||||
|
20 | a = 4
|
||||||
|
21 | b = r"raw string with formatting: {a}" # RUF027
|
||||||
|
- c = r"raw string with \backslashes\ and \"escaped quotes\": {a}" # RUF027
|
||||||
|
22 + c = fr"raw string with \backslashes\ and \"escaped quotes\": {a}" # RUF027
|
||||||
|
23 |
|
||||||
|
24 |
|
||||||
|
25 | def print_name(name: str):
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:27:11
|
||||||
|
|
|
||||||
|
25 | def print_name(name: str):
|
||||||
|
26 | a = 4
|
||||||
|
27 | print("Hello, {name}!") # RUF027
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
28 | print("The test value we're using today is {a}") # RUF027
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
24 |
|
||||||
|
25 | def print_name(name: str):
|
||||||
|
26 | a = 4
|
||||||
|
- print("Hello, {name}!") # RUF027
|
||||||
|
27 + print(f"Hello, {name}!") # RUF027
|
||||||
|
28 | print("The test value we're using today is {a}") # RUF027
|
||||||
|
29 |
|
||||||
|
30 |
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:28:11
|
||||||
|
|
|
||||||
|
26 | a = 4
|
||||||
|
27 | print("Hello, {name}!") # RUF027
|
||||||
|
28 | print("The test value we're using today is {a}") # RUF027
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
25 | def print_name(name: str):
|
||||||
|
26 | a = 4
|
||||||
|
27 | print("Hello, {name}!") # RUF027
|
||||||
|
- print("The test value we're using today is {a}") # RUF027
|
||||||
|
28 + print(f"The test value we're using today is {a}") # RUF027
|
||||||
|
29 |
|
||||||
|
30 |
|
||||||
|
31 | def nested_funcs():
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:33:33
|
||||||
|
|
|
||||||
|
31 | def nested_funcs():
|
||||||
|
32 | a = 4
|
||||||
|
33 | print(do_nothing(do_nothing("{a}"))) # RUF027
|
||||||
|
| ^^^^^
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
30 |
|
||||||
|
31 | def nested_funcs():
|
||||||
|
32 | a = 4
|
||||||
|
- print(do_nothing(do_nothing("{a}"))) # RUF027
|
||||||
|
33 + print(do_nothing(do_nothing(f"{a}"))) # RUF027
|
||||||
|
34 |
|
||||||
|
35 |
|
||||||
|
36 | def tripled_quoted():
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:39:19
|
||||||
|
|
|
||||||
|
37 | a = 4
|
||||||
|
38 | c = a
|
||||||
|
39 | single_line = """ {a} """ # RUF027
|
||||||
|
| ^^^^^^^^^^^
|
||||||
|
40 | # RUF027
|
||||||
|
41 | multi_line = a = """b { # comment
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
36 | def tripled_quoted():
|
||||||
|
37 | a = 4
|
||||||
|
38 | c = a
|
||||||
|
- single_line = """ {a} """ # RUF027
|
||||||
|
39 + single_line = f""" {a} """ # RUF027
|
||||||
|
40 | # RUF027
|
||||||
|
41 | multi_line = a = """b { # comment
|
||||||
|
42 | c} d
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:41:22
|
||||||
|
|
|
||||||
|
39 | single_line = """ {a} """ # RUF027
|
||||||
|
40 | # RUF027
|
||||||
|
41 | multi_line = a = """b { # comment
|
||||||
|
| ______________________^
|
||||||
|
42 | | c} d
|
||||||
|
43 | | """
|
||||||
|
| |_______^
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
38 | c = a
|
||||||
|
39 | single_line = """ {a} """ # RUF027
|
||||||
|
40 | # RUF027
|
||||||
|
- multi_line = a = """b { # comment
|
||||||
|
41 + multi_line = a = f"""b { # comment
|
||||||
|
42 | c} d
|
||||||
|
43 | """
|
||||||
|
44 |
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:56:9
|
||||||
|
|
|
||||||
|
54 | def implicit_concat():
|
||||||
|
55 | a = 4
|
||||||
|
56 | b = "{a}" "+" "{b}" r" \\ " # RUF027 for the first part only
|
||||||
|
| ^^^^^
|
||||||
|
57 | print(f"{a}" "{a}" f"{b}") # RUF027
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
53 |
|
||||||
|
54 | def implicit_concat():
|
||||||
|
55 | a = 4
|
||||||
|
- b = "{a}" "+" "{b}" r" \\ " # RUF027 for the first part only
|
||||||
|
56 + b = f"{a}" "+" "{b}" r" \\ " # RUF027 for the first part only
|
||||||
|
57 | print(f"{a}" "{a}" f"{b}") # RUF027
|
||||||
|
58 |
|
||||||
|
59 |
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:57:18
|
||||||
|
|
|
||||||
|
55 | a = 4
|
||||||
|
56 | b = "{a}" "+" "{b}" r" \\ " # RUF027 for the first part only
|
||||||
|
57 | print(f"{a}" "{a}" f"{b}") # RUF027
|
||||||
|
| ^^^^^
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
54 | def implicit_concat():
|
||||||
|
55 | a = 4
|
||||||
|
56 | b = "{a}" "+" "{b}" r" \\ " # RUF027 for the first part only
|
||||||
|
- print(f"{a}" "{a}" f"{b}") # RUF027
|
||||||
|
57 + print(f"{a}" f"{a}" f"{b}") # RUF027
|
||||||
|
58 |
|
||||||
|
59 |
|
||||||
|
60 | def escaped_chars():
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:62:9
|
||||||
|
|
|
||||||
|
60 | def escaped_chars():
|
||||||
|
61 | a = 4
|
||||||
|
62 | b = "\"not escaped:\" '{a}' \"escaped:\": '{{c}}'" # RUF027
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
59 |
|
||||||
|
60 | def escaped_chars():
|
||||||
|
61 | a = 4
|
||||||
|
- b = "\"not escaped:\" '{a}' \"escaped:\": '{{c}}'" # RUF027
|
||||||
|
62 + b = f"\"not escaped:\" '{a}' \"escaped:\": '{{c}}'" # RUF027
|
||||||
|
63 |
|
||||||
|
64 |
|
||||||
|
65 | def method_calls():
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:70:18
|
||||||
|
|
|
||||||
|
68 | first = "Wendy"
|
||||||
|
69 | last = "Appleseed"
|
||||||
|
70 | value.method("{first} {last}") # RUF027
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
71 |
|
||||||
|
72 | def format_specifiers():
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
67 | value.method = print_name
|
||||||
|
68 | first = "Wendy"
|
||||||
|
69 | last = "Appleseed"
|
||||||
|
- value.method("{first} {last}") # RUF027
|
||||||
|
70 + value.method(f"{first} {last}") # RUF027
|
||||||
|
71 |
|
||||||
|
72 | def format_specifiers():
|
||||||
|
73 | a = 4
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
RUF027 [*] Possible f-string without an `f` prefix
|
||||||
|
--> RUF027_0.py:74:9
|
||||||
|
|
|
||||||
|
72 | def format_specifiers():
|
||||||
|
73 | a = 4
|
||||||
|
74 | b = "{a:b} {a:^5}"
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
75 |
|
||||||
|
76 | # fstrings are never correct as type definitions
|
||||||
|
|
|
||||||
|
help: Add `f` prefix
|
||||||
|
71 |
|
||||||
|
72 | def format_specifiers():
|
||||||
|
73 | a = 4
|
||||||
|
- b = "{a:b} {a:^5}"
|
||||||
|
74 + b = f"{a:b} {a:^5}"
|
||||||
|
75 |
|
||||||
|
76 | # fstrings are never correct as type definitions
|
||||||
|
77 | # so we should always skip those
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
Loading…
Reference in New Issue