mirror of https://github.com/astral-sh/ruff
[`flynt`] Use triple quotes for joined raw strings with newlines (`FLY002`) (#20197)
<!-- Thank you for contributing to Ruff/ty! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? (Please prefix with `[ty]` for ty pull requests.) - Does this pull request include references to any relevant issues? --> ## Summary <!-- What's the purpose of the change? What does it do, and why? --> Fixes #19887 - flynt(FLY002): When joining only string constants, upgrade raw single-quoted strings to raw triple-quoted if the resulting content contains a newline. - Choose a safe triple-quote delimiter by switching to the opposite quote style if the preferred triple appears inside the content. - Update FLY002 snapshot to include the `\n'.join([r'line1','line2'])` case. ## Test Plan I've added one test case to FLY002.py. <!-- How was it tested? --> --------- Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
This commit is contained in:
parent
0b60584b7e
commit
b4b5d67a4a
|
|
@ -16,7 +16,9 @@ nok4 = "a".join([a, a, *a]) # Not OK (not a static length)
|
||||||
nok5 = "a".join([choice("flarp")]) # Not OK (not a simple call)
|
nok5 = "a".join([choice("flarp")]) # Not OK (not a simple call)
|
||||||
nok6 = "a".join(x for x in "feefoofum") # Not OK (generator)
|
nok6 = "a".join(x for x in "feefoofum") # Not OK (generator)
|
||||||
nok7 = "a".join([f"foo{8}", "bar"]) # Not OK (contains an f-string)
|
nok7 = "a".join([f"foo{8}", "bar"]) # Not OK (contains an f-string)
|
||||||
|
# https://github.com/astral-sh/ruff/issues/19887
|
||||||
|
nok8 = '\n'.join([r'line1','line2'])
|
||||||
|
nok9 = '\n'.join([r"raw string", '<""">', "<'''>"]) # Not OK (both triple-quote delimiters appear; should bail)
|
||||||
|
|
||||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7197
|
# Regression test for: https://github.com/astral-sh/ruff/issues/7197
|
||||||
def create_file_public_url(url, filename):
|
def create_file_public_url(url, filename):
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ 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};
|
use ruff_python_ast::{self as ast, Arguments, Expr, StringFlags};
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
|
@ -72,24 +72,42 @@ fn is_static_length(elts: &[Expr]) -> bool {
|
||||||
fn build_fstring(joiner: &str, joinees: &[Expr], flags: FStringFlags) -> Option<Expr> {
|
fn build_fstring(joiner: &str, joinees: &[Expr], flags: FStringFlags) -> Option<Expr> {
|
||||||
// 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 = None;
|
let mut flags: Option<ast::StringLiteralFlags> = None;
|
||||||
let node = ast::StringLiteral {
|
let content = joinees
|
||||||
value: 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() {
|
||||||
if flags.is_none() {
|
// Take the flags from the first Expr
|
||||||
// take the flags from the first Expr
|
flags = Some(value.first_literal_flags());
|
||||||
flags = Some(value.first_literal_flags());
|
|
||||||
}
|
|
||||||
Some(value.to_str())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
})
|
Some(value.to_str())
|
||||||
.join(joiner)
|
} else {
|
||||||
.into_boxed_str(),
|
None
|
||||||
flags: flags?,
|
}
|
||||||
|
})
|
||||||
|
.join(joiner);
|
||||||
|
|
||||||
|
let mut flags = flags?;
|
||||||
|
|
||||||
|
// If the result is a raw string and contains a newline, use triple quotes.
|
||||||
|
if flags.prefix().is_raw() && content.contains(['\n', '\r']) {
|
||||||
|
flags = flags.with_triple_quotes(ruff_python_ast::str::TripleQuotes::Yes);
|
||||||
|
|
||||||
|
// Prefer a delimiter that doesn't occur in the content; if both occur, bail.
|
||||||
|
if content.contains(flags.quote_str()) {
|
||||||
|
flags = flags.with_quote_style(flags.quote_style().opposite());
|
||||||
|
if content.contains(flags.quote_str()) {
|
||||||
|
// Both "'''" and "\"\"\"" are present in content; avoid emitting
|
||||||
|
// an invalid raw triple-quoted literal (or escaping). Bail on the fix.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let node = ast::StringLiteral {
|
||||||
|
value: content.into_boxed_str(),
|
||||||
|
flags,
|
||||||
range: TextRange::default(),
|
range: TextRange::default(),
|
||||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -125,18 +125,39 @@ help: Replace with `f"{secrets.token_urlsafe()}a{secrets.token_hex()}"`
|
||||||
13 | nok2 = a.join(["1", "2", "3"]) # Not OK (not a static joiner)
|
13 | nok2 = a.join(["1", "2", "3"]) # Not OK (not a static joiner)
|
||||||
note: This is an unsafe fix and may change runtime behavior
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
FLY002 [*] Consider `f"{url}{filename}"` instead of string join
|
FLY002 [*] Consider f-string instead of string join
|
||||||
--> FLY002.py:23:11
|
--> FLY002.py:20:8
|
||||||
|
|
|
|
||||||
21 | # Regression test for: https://github.com/astral-sh/ruff/issues/7197
|
18 | nok7 = "a".join([f"foo{8}", "bar"]) # Not OK (contains an f-string)
|
||||||
22 | def create_file_public_url(url, filename):
|
19 | # https://github.com/astral-sh/ruff/issues/19887
|
||||||
23 | return''.join([url, filename])
|
20 | nok8 = '\n'.join([r'line1','line2'])
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
21 | nok9 = '\n'.join([r"raw string", '<""">', "<'''>"]) # Not OK (both triple-quote delimiters appear; should bail)
|
||||||
|
|
|
||||||
|
help: Replace with f-string
|
||||||
|
17 | nok6 = "a".join(x for x in "feefoofum") # Not OK (generator)
|
||||||
|
18 | nok7 = "a".join([f"foo{8}", "bar"]) # Not OK (contains an f-string)
|
||||||
|
19 | # https://github.com/astral-sh/ruff/issues/19887
|
||||||
|
- nok8 = '\n'.join([r'line1','line2'])
|
||||||
|
20 + nok8 = r'''line1
|
||||||
|
21 + line2'''
|
||||||
|
22 | nok9 = '\n'.join([r"raw string", '<""">', "<'''>"]) # Not OK (both triple-quote delimiters appear; should bail)
|
||||||
|
23 |
|
||||||
|
24 | # Regression test for: https://github.com/astral-sh/ruff/issues/7197
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
FLY002 [*] Consider `f"{url}{filename}"` instead of string join
|
||||||
|
--> FLY002.py:25:11
|
||||||
|
|
|
||||||
|
23 | # Regression test for: https://github.com/astral-sh/ruff/issues/7197
|
||||||
|
24 | def create_file_public_url(url, filename):
|
||||||
|
25 | return''.join([url, filename])
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
help: Replace with `f"{url}{filename}"`
|
help: Replace with `f"{url}{filename}"`
|
||||||
20 |
|
22 |
|
||||||
21 | # Regression test for: https://github.com/astral-sh/ruff/issues/7197
|
23 | # Regression test for: https://github.com/astral-sh/ruff/issues/7197
|
||||||
22 | def create_file_public_url(url, filename):
|
24 | def create_file_public_url(url, filename):
|
||||||
- return''.join([url, filename])
|
- return''.join([url, filename])
|
||||||
23 + return f"{url}{filename}"
|
25 + return f"{url}{filename}"
|
||||||
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