mirror of https://github.com/astral-sh/ruff
Merge b6caf8c92b into b0bc990cbf
This commit is contained in:
commit
23943b5ca2
|
|
@ -29,3 +29,19 @@ nok10 = "".join((foo, '"'))
|
||||||
nok11 = ''.join((foo, "'"))
|
nok11 = ''.join((foo, "'"))
|
||||||
nok12 = ''.join([foo, "'", '"'])
|
nok12 = ''.join([foo, "'", '"'])
|
||||||
nok13 = "".join([foo, "'", '"'])
|
nok13 = "".join([foo, "'", '"'])
|
||||||
|
|
||||||
|
# Regression test for: https://github.com/astral-sh/ruff/issues/21082
|
||||||
|
# Mixing raw and non-raw strings can cause syntax errors or behavior changes
|
||||||
|
nok14 = "".join((r"", '"')) # First is raw, second is not - would break syntax
|
||||||
|
nok15 = "".join((r"", "\\")) # First is raw, second has backslash - would break syntax
|
||||||
|
nok16 = "".join((r"", "\0")) # First is raw, second has null byte - would introduce null bytes
|
||||||
|
nok17 = "".join((r"", "\r")) # First is raw, second has carriage return - would change behavior
|
||||||
|
nok18 = "".join((r"", "\\r")) # First is raw, second has backslash followed by literal r - OK (no special handling needed)
|
||||||
|
|
||||||
|
# Test that all-raw strings still work (should be OK)
|
||||||
|
ok7 = "".join((r"", r"something")) # Both are raw - OK
|
||||||
|
ok8 = "\n".join((r"line1", r'line2')) # Both are raw - OK
|
||||||
|
|
||||||
|
# Test that all-non-raw strings still work (should be OK)
|
||||||
|
ok9 = "".join(("", '"')) # Both are non-raw - OK
|
||||||
|
ok10 = "\n".join(("line1", "line2")) # Both are non-raw - OK
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ use ast::FStringFlags;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||||
use ruff_python_ast::{self as ast, Arguments, Expr, StringFlags, str::Quote};
|
use ruff_python_ast::{
|
||||||
|
self as ast, Arguments, Expr, StringFlags, str::Quote, str_prefix::StringLiteralPrefix,
|
||||||
|
};
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
|
@ -74,14 +76,26 @@ fn build_fstring(joiner: &str, joinees: &[Expr], flags: FStringFlags) -> Option<
|
||||||
// If all elements are string constants, join them into a single string.
|
// If all elements are string constants, join them into a single string.
|
||||||
if joinees.iter().all(Expr::is_string_literal_expr) {
|
if joinees.iter().all(Expr::is_string_literal_expr) {
|
||||||
let mut flags: Option<ast::StringLiteralFlags> = None;
|
let mut flags: Option<ast::StringLiteralFlags> = None;
|
||||||
|
let mut any_raw = false;
|
||||||
|
|
||||||
|
for expr in joinees {
|
||||||
|
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
|
||||||
|
let curr_flags = value.first_literal_flags();
|
||||||
|
let is_raw = curr_flags.prefix().is_raw();
|
||||||
|
|
||||||
|
if flags.is_none() {
|
||||||
|
flags = Some(curr_flags);
|
||||||
|
any_raw = is_raw;
|
||||||
|
} else if is_raw {
|
||||||
|
any_raw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let content = joinees
|
let content = joinees
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|expr| {
|
.filter_map(|expr| {
|
||||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
|
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
|
||||||
if flags.is_none() {
|
|
||||||
// Take the flags from the first Expr
|
|
||||||
flags = Some(value.first_literal_flags());
|
|
||||||
}
|
|
||||||
Some(value.to_str())
|
Some(value.to_str())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -91,6 +105,25 @@ fn build_fstring(joiner: &str, joinees: &[Expr], flags: FStringFlags) -> Option<
|
||||||
|
|
||||||
let mut flags = flags?;
|
let mut flags = flags?;
|
||||||
|
|
||||||
|
// If any input was raw but the content cannot be safely represented as a raw string,
|
||||||
|
// use non-raw representation. This handles cases where raw strings would create invalid
|
||||||
|
// syntax or behavior changes.
|
||||||
|
if any_raw && !content.is_empty() {
|
||||||
|
let needs_non_raw = content.contains(['\r', '\0']) || {
|
||||||
|
// Check if content contains characters that would break raw string syntax
|
||||||
|
let quote_char = flags.quote_str();
|
||||||
|
// A raw string cannot end with a single backslash if it's immediately
|
||||||
|
// followed by the quote delimiter, as that would be invalid syntax.
|
||||||
|
let ends_with_backslash = content.ends_with('\\')
|
||||||
|
&& (content.len() == 1 || content.chars().nth_back(1) != Some('\\'));
|
||||||
|
content.contains(quote_char) || ends_with_backslash
|
||||||
|
};
|
||||||
|
|
||||||
|
if needs_non_raw {
|
||||||
|
flags = flags.with_prefix(StringLiteralPrefix::Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If the result is a raw string and contains a newline, use triple quotes.
|
// If the result is a raw string and contains a newline, use triple quotes.
|
||||||
if flags.prefix().is_raw() && content.contains(['\n', '\r']) {
|
if flags.prefix().is_raw() && content.contains(['\n', '\r']) {
|
||||||
flags = flags.with_triple_quotes(ruff_python_ast::str::TripleQuotes::Yes);
|
flags = flags.with_triple_quotes(ruff_python_ast::str::TripleQuotes::Yes);
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,27 @@ help: Replace with f-string
|
||||||
24 | # Regression test for: https://github.com/astral-sh/ruff/issues/7197
|
24 | # Regression test for: https://github.com/astral-sh/ruff/issues/7197
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
FLY002 [*] Consider `"raw string\n<\"\"\">\n<'''>"` instead of string join
|
||||||
|
--> FLY002.py:21:8
|
||||||
|
|
|
||||||
|
19 | # https://github.com/astral-sh/ruff/issues/19887
|
||||||
|
20 | nok8 = '\n'.join([r'line1','line2'])
|
||||||
|
21 | nok9 = '\n'.join([r"raw string", '<""">', "<'''>"]) # Not OK (both triple-quote delimiters appear; should bail)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
22 |
|
||||||
|
23 | # Regression test for: https://github.com/astral-sh/ruff/issues/7197
|
||||||
|
|
|
||||||
|
help: Replace with `"raw string\n<\"\"\">\n<'''>"`
|
||||||
|
18 | nok7 = "a".join([f"foo{8}", "bar"]) # Not OK (contains an f-string)
|
||||||
|
19 | # https://github.com/astral-sh/ruff/issues/19887
|
||||||
|
20 | nok8 = '\n'.join([r'line1','line2'])
|
||||||
|
- nok9 = '\n'.join([r"raw string", '<""">', "<'''>"]) # Not OK (both triple-quote delimiters appear; should bail)
|
||||||
|
21 + nok9 = "raw string\n<\"\"\">\n<'''>" # Not OK (both triple-quote delimiters appear; should bail)
|
||||||
|
22 |
|
||||||
|
23 | # Regression test for: https://github.com/astral-sh/ruff/issues/7197
|
||||||
|
24 | def create_file_public_url(url, filename):
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
FLY002 [*] Consider `f"{url}{filename}"` instead of string join
|
FLY002 [*] Consider `f"{url}{filename}"` instead of string join
|
||||||
--> FLY002.py:25:11
|
--> FLY002.py:25:11
|
||||||
|
|
|
|
||||||
|
|
@ -205,4 +226,183 @@ help: Replace with `f"{foo}'"`
|
||||||
29 + nok11 = f"{foo}'"
|
29 + nok11 = f"{foo}'"
|
||||||
30 | nok12 = ''.join([foo, "'", '"'])
|
30 | nok12 = ''.join([foo, "'", '"'])
|
||||||
31 | nok13 = "".join([foo, "'", '"'])
|
31 | nok13 = "".join([foo, "'", '"'])
|
||||||
|
32 |
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
FLY002 [*] Consider `'"'` instead of string join
|
||||||
|
--> FLY002.py:35:9
|
||||||
|
|
|
||||||
|
33 | # Regression test for: https://github.com/astral-sh/ruff/issues/21082
|
||||||
|
34 | # Mixing raw and non-raw strings can cause syntax errors or behavior changes
|
||||||
|
35 | nok14 = "".join((r"", '"')) # First is raw, second is not - would break syntax
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^
|
||||||
|
36 | nok15 = "".join((r"", "\\")) # First is raw, second has backslash - would break syntax
|
||||||
|
37 | nok16 = "".join((r"", "\0")) # First is raw, second has null byte - would introduce null bytes
|
||||||
|
|
|
||||||
|
help: Replace with `'"'`
|
||||||
|
32 |
|
||||||
|
33 | # Regression test for: https://github.com/astral-sh/ruff/issues/21082
|
||||||
|
34 | # Mixing raw and non-raw strings can cause syntax errors or behavior changes
|
||||||
|
- nok14 = "".join((r"", '"')) # First is raw, second is not - would break syntax
|
||||||
|
35 + nok14 = '"' # First is raw, second is not - would break syntax
|
||||||
|
36 | nok15 = "".join((r"", "\\")) # First is raw, second has backslash - would break syntax
|
||||||
|
37 | nok16 = "".join((r"", "\0")) # First is raw, second has null byte - would introduce null bytes
|
||||||
|
38 | nok17 = "".join((r"", "\r")) # First is raw, second has carriage return - would change behavior
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
FLY002 [*] Consider `"\\"` instead of string join
|
||||||
|
--> FLY002.py:36:9
|
||||||
|
|
|
||||||
|
34 | # Mixing raw and non-raw strings can cause syntax errors or behavior changes
|
||||||
|
35 | nok14 = "".join((r"", '"')) # First is raw, second is not - would break syntax
|
||||||
|
36 | nok15 = "".join((r"", "\\")) # First is raw, second has backslash - would break syntax
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^
|
||||||
|
37 | nok16 = "".join((r"", "\0")) # First is raw, second has null byte - would introduce null bytes
|
||||||
|
38 | nok17 = "".join((r"", "\r")) # First is raw, second has carriage return - would change behavior
|
||||||
|
|
|
||||||
|
help: Replace with `"\\"`
|
||||||
|
33 | # Regression test for: https://github.com/astral-sh/ruff/issues/21082
|
||||||
|
34 | # Mixing raw and non-raw strings can cause syntax errors or behavior changes
|
||||||
|
35 | nok14 = "".join((r"", '"')) # First is raw, second is not - would break syntax
|
||||||
|
- nok15 = "".join((r"", "\\")) # First is raw, second has backslash - would break syntax
|
||||||
|
36 + nok15 = "\\" # First is raw, second has backslash - would break syntax
|
||||||
|
37 | nok16 = "".join((r"", "\0")) # First is raw, second has null byte - would introduce null bytes
|
||||||
|
38 | nok17 = "".join((r"", "\r")) # First is raw, second has carriage return - would change behavior
|
||||||
|
39 | nok18 = "".join((r"", "\\r")) # First is raw, second has backslash followed by literal r - OK (no special handling needed)
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
FLY002 [*] Consider `"\x00"` instead of string join
|
||||||
|
--> FLY002.py:37:9
|
||||||
|
|
|
||||||
|
35 | nok14 = "".join((r"", '"')) # First is raw, second is not - would break syntax
|
||||||
|
36 | nok15 = "".join((r"", "\\")) # First is raw, second has backslash - would break syntax
|
||||||
|
37 | nok16 = "".join((r"", "\0")) # First is raw, second has null byte - would introduce null bytes
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^
|
||||||
|
38 | nok17 = "".join((r"", "\r")) # First is raw, second has carriage return - would change behavior
|
||||||
|
39 | nok18 = "".join((r"", "\\r")) # First is raw, second has backslash followed by literal r - OK (no special handling needed)
|
||||||
|
|
|
||||||
|
help: Replace with `"\x00"`
|
||||||
|
34 | # Mixing raw and non-raw strings can cause syntax errors or behavior changes
|
||||||
|
35 | nok14 = "".join((r"", '"')) # First is raw, second is not - would break syntax
|
||||||
|
36 | nok15 = "".join((r"", "\\")) # First is raw, second has backslash - would break syntax
|
||||||
|
- nok16 = "".join((r"", "\0")) # First is raw, second has null byte - would introduce null bytes
|
||||||
|
37 + nok16 = "\x00" # First is raw, second has null byte - would introduce null bytes
|
||||||
|
38 | nok17 = "".join((r"", "\r")) # First is raw, second has carriage return - would change behavior
|
||||||
|
39 | nok18 = "".join((r"", "\\r")) # First is raw, second has backslash followed by literal r - OK (no special handling needed)
|
||||||
|
40 |
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
FLY002 [*] Consider `"\r"` instead of string join
|
||||||
|
--> FLY002.py:38:9
|
||||||
|
|
|
||||||
|
36 | nok15 = "".join((r"", "\\")) # First is raw, second has backslash - would break syntax
|
||||||
|
37 | nok16 = "".join((r"", "\0")) # First is raw, second has null byte - would introduce null bytes
|
||||||
|
38 | nok17 = "".join((r"", "\r")) # First is raw, second has carriage return - would change behavior
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^
|
||||||
|
39 | nok18 = "".join((r"", "\\r")) # First is raw, second has backslash followed by literal r - OK (no special handling needed)
|
||||||
|
|
|
||||||
|
help: Replace with `"\r"`
|
||||||
|
35 | nok14 = "".join((r"", '"')) # First is raw, second is not - would break syntax
|
||||||
|
36 | nok15 = "".join((r"", "\\")) # First is raw, second has backslash - would break syntax
|
||||||
|
37 | nok16 = "".join((r"", "\0")) # First is raw, second has null byte - would introduce null bytes
|
||||||
|
- nok17 = "".join((r"", "\r")) # First is raw, second has carriage return - would change behavior
|
||||||
|
38 + nok17 = "\r" # First is raw, second has carriage return - would change behavior
|
||||||
|
39 | nok18 = "".join((r"", "\\r")) # First is raw, second has backslash followed by literal r - OK (no special handling needed)
|
||||||
|
40 |
|
||||||
|
41 | # Test that all-raw strings still work (should be OK)
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
FLY002 [*] Consider `r"\r"` instead of string join
|
||||||
|
--> FLY002.py:39:9
|
||||||
|
|
|
||||||
|
37 | nok16 = "".join((r"", "\0")) # First is raw, second has null byte - would introduce null bytes
|
||||||
|
38 | nok17 = "".join((r"", "\r")) # First is raw, second has carriage return - would change behavior
|
||||||
|
39 | nok18 = "".join((r"", "\\r")) # First is raw, second has backslash followed by literal r - OK (no special handling needed)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
40 |
|
||||||
|
41 | # Test that all-raw strings still work (should be OK)
|
||||||
|
|
|
||||||
|
help: Replace with `r"\r"`
|
||||||
|
36 | nok15 = "".join((r"", "\\")) # First is raw, second has backslash - would break syntax
|
||||||
|
37 | nok16 = "".join((r"", "\0")) # First is raw, second has null byte - would introduce null bytes
|
||||||
|
38 | nok17 = "".join((r"", "\r")) # First is raw, second has carriage return - would change behavior
|
||||||
|
- nok18 = "".join((r"", "\\r")) # First is raw, second has backslash followed by literal r - OK (no special handling needed)
|
||||||
|
39 + nok18 = r"\r" # First is raw, second has backslash followed by literal r - OK (no special handling needed)
|
||||||
|
40 |
|
||||||
|
41 | # Test that all-raw strings still work (should be OK)
|
||||||
|
42 | ok7 = "".join((r"", r"something")) # Both are raw - OK
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
FLY002 [*] Consider `r"something"` instead of string join
|
||||||
|
--> FLY002.py:42:7
|
||||||
|
|
|
||||||
|
41 | # Test that all-raw strings still work (should be OK)
|
||||||
|
42 | ok7 = "".join((r"", r"something")) # Both are raw - OK
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
43 | ok8 = "\n".join((r"line1", r'line2')) # Both are raw - OK
|
||||||
|
|
|
||||||
|
help: Replace with `r"something"`
|
||||||
|
39 | nok18 = "".join((r"", "\\r")) # First is raw, second has backslash followed by literal r - OK (no special handling needed)
|
||||||
|
40 |
|
||||||
|
41 | # Test that all-raw strings still work (should be OK)
|
||||||
|
- ok7 = "".join((r"", r"something")) # Both are raw - OK
|
||||||
|
42 + ok7 = r"something" # Both are raw - OK
|
||||||
|
43 | ok8 = "\n".join((r"line1", r'line2')) # Both are raw - OK
|
||||||
|
44 |
|
||||||
|
45 | # Test that all-non-raw strings still work (should be OK)
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
FLY002 [*] Consider f-string instead of string join
|
||||||
|
--> FLY002.py:43:7
|
||||||
|
|
|
||||||
|
41 | # Test that all-raw strings still work (should be OK)
|
||||||
|
42 | ok7 = "".join((r"", r"something")) # Both are raw - OK
|
||||||
|
43 | ok8 = "\n".join((r"line1", r'line2')) # Both are raw - OK
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
44 |
|
||||||
|
45 | # Test that all-non-raw strings still work (should be OK)
|
||||||
|
|
|
||||||
|
help: Replace with f-string
|
||||||
|
40 |
|
||||||
|
41 | # Test that all-raw strings still work (should be OK)
|
||||||
|
42 | ok7 = "".join((r"", r"something")) # Both are raw - OK
|
||||||
|
- ok8 = "\n".join((r"line1", r'line2')) # Both are raw - OK
|
||||||
|
43 + ok8 = r"""line1
|
||||||
|
44 + line2""" # Both are raw - OK
|
||||||
|
45 |
|
||||||
|
46 | # Test that all-non-raw strings still work (should be OK)
|
||||||
|
47 | ok9 = "".join(("", '"')) # Both are non-raw - OK
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
FLY002 [*] Consider `'"'` instead of string join
|
||||||
|
--> FLY002.py:46:7
|
||||||
|
|
|
||||||
|
45 | # Test that all-non-raw strings still work (should be OK)
|
||||||
|
46 | ok9 = "".join(("", '"')) # Both are non-raw - OK
|
||||||
|
| ^^^^^^^^^^^^^^^^^^
|
||||||
|
47 | ok10 = "\n".join(("line1", "line2")) # Both are non-raw - OK
|
||||||
|
|
|
||||||
|
help: Replace with `'"'`
|
||||||
|
43 | ok8 = "\n".join((r"line1", r'line2')) # Both are raw - OK
|
||||||
|
44 |
|
||||||
|
45 | # Test that all-non-raw strings still work (should be OK)
|
||||||
|
- ok9 = "".join(("", '"')) # Both are non-raw - OK
|
||||||
|
46 + ok9 = '"' # Both are non-raw - OK
|
||||||
|
47 | ok10 = "\n".join(("line1", "line2")) # Both are non-raw - OK
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
FLY002 [*] Consider `"line1\nline2"` instead of string join
|
||||||
|
--> FLY002.py:47:8
|
||||||
|
|
|
||||||
|
45 | # Test that all-non-raw strings still work (should be OK)
|
||||||
|
46 | ok9 = "".join(("", '"')) # Both are non-raw - OK
|
||||||
|
47 | ok10 = "\n".join(("line1", "line2")) # Both are non-raw - OK
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
help: Replace with `"line1\nline2"`
|
||||||
|
44 |
|
||||||
|
45 | # Test that all-non-raw strings still work (should be OK)
|
||||||
|
46 | ok9 = "".join(("", '"')) # Both are non-raw - OK
|
||||||
|
- ok10 = "\n".join(("line1", "line2")) # Both are non-raw - OK
|
||||||
|
47 + ok10 = "line1\nline2" # Both are non-raw - OK
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue