Skip to content

Commit

Permalink
[flake8-implicit-str-concat] Normalize octals before merging concat…
Browse files Browse the repository at this point in the history
…enated strings in `single-line-implicit-string-concatenation` (`ISC001`) (#13118)

Co-authored-by: Alex Waygood <[email protected]>
  • Loading branch information
dylwil3 and AlexWaygood authored Aug 27, 2024
1 parent eb3dc37 commit 483748c
Show file tree
Hide file tree
Showing 5 changed files with 456 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,15 @@
+ f"second"} d"
_ = f"a {f"first {f"middle"}"
+ f"second"} d"

# See https://github.com/astral-sh/ruff/issues/12936
_ = "\12""0" # fix should be "\0120"
_ = "\\12""0" # fix should be "\\120"
_ = "\\\12""0" # fix should be "\\\0120"
_ = "\12 0""0" # fix should be "\12 00"
_ = r"\12"r"0" # fix should be r"\120"
_ = "\12 and more""0" # fix should be "\12 and more0"
_ = "\8""0" # fix should be "\80"
_ = "\12""8" # fix should be "\128"
_ = "\12""foo" # fix should be "\12foo"
_ = "\12" "" # fix should be "\12"
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::borrow::Cow;

use itertools::Itertools;

use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
Expand Down Expand Up @@ -172,9 +174,16 @@ fn concatenate_strings(a_range: TextRange, b_range: TextRange, locator: &Locator
return None;
}

let a_body = &a_text[a_leading_quote.len()..a_text.len() - a_trailing_quote.len()];
let mut a_body =
Cow::Borrowed(&a_text[a_leading_quote.len()..a_text.len() - a_trailing_quote.len()]);
let b_body = &b_text[b_leading_quote.len()..b_text.len() - b_trailing_quote.len()];

if a_leading_quote.find(['r', 'R']).is_none()
&& matches!(b_body.bytes().next(), Some(b'0'..=b'7'))
{
normalize_ending_octal(&mut a_body);
}

let concatenation = format!("{a_leading_quote}{a_body}{b_body}{a_trailing_quote}");
let range = TextRange::new(a_range.start(), b_range.end());

Expand All @@ -183,3 +192,39 @@ fn concatenate_strings(a_range: TextRange, b_range: TextRange, locator: &Locator
range,
)))
}

/// Pads an octal at the end of the string
/// to three digits, if necessary.
fn normalize_ending_octal(text: &mut Cow<'_, str>) {
// Early return for short strings
if text.len() < 2 {
return;
}

let mut rev_bytes = text.bytes().rev();
if let Some(last_byte @ b'0'..=b'7') = rev_bytes.next() {
// "\y" -> "\00y"
if has_odd_consecutive_backslashes(&mut rev_bytes.clone()) {
let prefix = &text[..text.len() - 2];
*text = Cow::Owned(format!("{prefix}\\00{}", last_byte as char));
}
// "\xy" -> "\0xy"
else if let Some(penultimate_byte @ b'0'..=b'7') = rev_bytes.next() {
if has_odd_consecutive_backslashes(&mut rev_bytes.clone()) {
let prefix = &text[..text.len() - 3];
*text = Cow::Owned(format!(
"{prefix}\\0{}{}",
penultimate_byte as char, last_byte as char
));
}
}
}
}

fn has_odd_consecutive_backslashes(mut itr: impl Iterator<Item = u8>) -> bool {
let mut odd_backslashes = false;
while let Some(b'\\') = itr.next() {
odd_backslashes = !odd_backslashes;
}
odd_backslashes
}
Original file line number Diff line number Diff line change
Expand Up @@ -296,4 +296,202 @@ ISC.py:73:20: ISC001 [*] Implicitly concatenated string literals on one line
75 75 | f"def"} g"
76 76 |

ISC.py:84:5: ISC001 [*] Implicitly concatenated string literals on one line
|
83 | # See https://github.com/astral-sh/ruff/issues/12936
84 | _ = "\12""0" # fix should be "\0120"
| ^^^^^^^^ ISC001
85 | _ = "\\12""0" # fix should be "\\120"
86 | _ = "\\\12""0" # fix should be "\\\0120"
|
= help: Combine string literals

Safe fix
81 81 | + f"second"} d"
82 82 |
83 83 | # See https://github.com/astral-sh/ruff/issues/12936
84 |-_ = "\12""0" # fix should be "\0120"
84 |+_ = "\0120" # fix should be "\0120"
85 85 | _ = "\\12""0" # fix should be "\\120"
86 86 | _ = "\\\12""0" # fix should be "\\\0120"
87 87 | _ = "\12 0""0" # fix should be "\12 00"

ISC.py:85:5: ISC001 [*] Implicitly concatenated string literals on one line
|
83 | # See https://github.com/astral-sh/ruff/issues/12936
84 | _ = "\12""0" # fix should be "\0120"
85 | _ = "\\12""0" # fix should be "\\120"
| ^^^^^^^^^ ISC001
86 | _ = "\\\12""0" # fix should be "\\\0120"
87 | _ = "\12 0""0" # fix should be "\12 00"
|
= help: Combine string literals

Safe fix
82 82 |
83 83 | # See https://github.com/astral-sh/ruff/issues/12936
84 84 | _ = "\12""0" # fix should be "\0120"
85 |-_ = "\\12""0" # fix should be "\\120"
85 |+_ = "\\120" # fix should be "\\120"
86 86 | _ = "\\\12""0" # fix should be "\\\0120"
87 87 | _ = "\12 0""0" # fix should be "\12 00"
88 88 | _ = r"\12"r"0" # fix should be r"\120"

ISC.py:86:5: ISC001 [*] Implicitly concatenated string literals on one line
|
84 | _ = "\12""0" # fix should be "\0120"
85 | _ = "\\12""0" # fix should be "\\120"
86 | _ = "\\\12""0" # fix should be "\\\0120"
| ^^^^^^^^^^ ISC001
87 | _ = "\12 0""0" # fix should be "\12 00"
88 | _ = r"\12"r"0" # fix should be r"\120"
|
= help: Combine string literals

Safe fix
83 83 | # See https://github.com/astral-sh/ruff/issues/12936
84 84 | _ = "\12""0" # fix should be "\0120"
85 85 | _ = "\\12""0" # fix should be "\\120"
86 |-_ = "\\\12""0" # fix should be "\\\0120"
86 |+_ = "\\\0120" # fix should be "\\\0120"
87 87 | _ = "\12 0""0" # fix should be "\12 00"
88 88 | _ = r"\12"r"0" # fix should be r"\120"
89 89 | _ = "\12 and more""0" # fix should be "\12 and more0"

ISC.py:87:5: ISC001 [*] Implicitly concatenated string literals on one line
|
85 | _ = "\\12""0" # fix should be "\\120"
86 | _ = "\\\12""0" # fix should be "\\\0120"
87 | _ = "\12 0""0" # fix should be "\12 00"
| ^^^^^^^^^^ ISC001
88 | _ = r"\12"r"0" # fix should be r"\120"
89 | _ = "\12 and more""0" # fix should be "\12 and more0"
|
= help: Combine string literals

Safe fix
84 84 | _ = "\12""0" # fix should be "\0120"
85 85 | _ = "\\12""0" # fix should be "\\120"
86 86 | _ = "\\\12""0" # fix should be "\\\0120"
87 |-_ = "\12 0""0" # fix should be "\12 00"
87 |+_ = "\12 00" # fix should be "\12 00"
88 88 | _ = r"\12"r"0" # fix should be r"\120"
89 89 | _ = "\12 and more""0" # fix should be "\12 and more0"
90 90 | _ = "\8""0" # fix should be "\80"

ISC.py:88:5: ISC001 [*] Implicitly concatenated string literals on one line
|
86 | _ = "\\\12""0" # fix should be "\\\0120"
87 | _ = "\12 0""0" # fix should be "\12 00"
88 | _ = r"\12"r"0" # fix should be r"\120"
| ^^^^^^^^^^ ISC001
89 | _ = "\12 and more""0" # fix should be "\12 and more0"
90 | _ = "\8""0" # fix should be "\80"
|
= help: Combine string literals

Safe fix
85 85 | _ = "\\12""0" # fix should be "\\120"
86 86 | _ = "\\\12""0" # fix should be "\\\0120"
87 87 | _ = "\12 0""0" # fix should be "\12 00"
88 |-_ = r"\12"r"0" # fix should be r"\120"
88 |+_ = r"\120" # fix should be r"\120"
89 89 | _ = "\12 and more""0" # fix should be "\12 and more0"
90 90 | _ = "\8""0" # fix should be "\80"
91 91 | _ = "\12""8" # fix should be "\128"

ISC.py:89:5: ISC001 [*] Implicitly concatenated string literals on one line
|
87 | _ = "\12 0""0" # fix should be "\12 00"
88 | _ = r"\12"r"0" # fix should be r"\120"
89 | _ = "\12 and more""0" # fix should be "\12 and more0"
| ^^^^^^^^^^^^^^^^^ ISC001
90 | _ = "\8""0" # fix should be "\80"
91 | _ = "\12""8" # fix should be "\128"
|
= help: Combine string literals

Safe fix
86 86 | _ = "\\\12""0" # fix should be "\\\0120"
87 87 | _ = "\12 0""0" # fix should be "\12 00"
88 88 | _ = r"\12"r"0" # fix should be r"\120"
89 |-_ = "\12 and more""0" # fix should be "\12 and more0"
89 |+_ = "\12 and more0" # fix should be "\12 and more0"
90 90 | _ = "\8""0" # fix should be "\80"
91 91 | _ = "\12""8" # fix should be "\128"
92 92 | _ = "\12""foo" # fix should be "\12foo"

ISC.py:90:5: ISC001 [*] Implicitly concatenated string literals on one line
|
88 | _ = r"\12"r"0" # fix should be r"\120"
89 | _ = "\12 and more""0" # fix should be "\12 and more0"
90 | _ = "\8""0" # fix should be "\80"
| ^^^^^^^ ISC001
91 | _ = "\12""8" # fix should be "\128"
92 | _ = "\12""foo" # fix should be "\12foo"
|
= help: Combine string literals

Safe fix
87 87 | _ = "\12 0""0" # fix should be "\12 00"
88 88 | _ = r"\12"r"0" # fix should be r"\120"
89 89 | _ = "\12 and more""0" # fix should be "\12 and more0"
90 |-_ = "\8""0" # fix should be "\80"
90 |+_ = "\80" # fix should be "\80"
91 91 | _ = "\12""8" # fix should be "\128"
92 92 | _ = "\12""foo" # fix should be "\12foo"
93 93 | _ = "\12" "" # fix should be "\12"

ISC.py:91:5: ISC001 [*] Implicitly concatenated string literals on one line
|
89 | _ = "\12 and more""0" # fix should be "\12 and more0"
90 | _ = "\8""0" # fix should be "\80"
91 | _ = "\12""8" # fix should be "\128"
| ^^^^^^^^ ISC001
92 | _ = "\12""foo" # fix should be "\12foo"
93 | _ = "\12" "" # fix should be "\12"
|
= help: Combine string literals

Safe fix
88 88 | _ = r"\12"r"0" # fix should be r"\120"
89 89 | _ = "\12 and more""0" # fix should be "\12 and more0"
90 90 | _ = "\8""0" # fix should be "\80"
91 |-_ = "\12""8" # fix should be "\128"
91 |+_ = "\128" # fix should be "\128"
92 92 | _ = "\12""foo" # fix should be "\12foo"
93 93 | _ = "\12" "" # fix should be "\12"

ISC.py:92:5: ISC001 [*] Implicitly concatenated string literals on one line
|
90 | _ = "\8""0" # fix should be "\80"
91 | _ = "\12""8" # fix should be "\128"
92 | _ = "\12""foo" # fix should be "\12foo"
| ^^^^^^^^^^ ISC001
93 | _ = "\12" "" # fix should be "\12"
|
= help: Combine string literals

Safe fix
89 89 | _ = "\12 and more""0" # fix should be "\12 and more0"
90 90 | _ = "\8""0" # fix should be "\80"
91 91 | _ = "\12""8" # fix should be "\128"
92 |-_ = "\12""foo" # fix should be "\12foo"
92 |+_ = "\12foo" # fix should be "\12foo"
93 93 | _ = "\12" "" # fix should be "\12"

ISC.py:93:5: ISC001 [*] Implicitly concatenated string literals on one line
|
91 | _ = "\12""8" # fix should be "\128"
92 | _ = "\12""foo" # fix should be "\12foo"
93 | _ = "\12" "" # fix should be "\12"
| ^^^^^^^^ ISC001
|
= help: Combine string literals

Safe fix
90 90 | _ = "\8""0" # fix should be "\80"
91 91 | _ = "\12""8" # fix should be "\128"
92 92 | _ = "\12""foo" # fix should be "\12foo"
93 |-_ = "\12" "" # fix should be "\12"
93 |+_ = "\12" # fix should be "\12"
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ ISC.py:80:10: ISC003 Explicitly concatenated string should be implicitly concate
| __________^
81 | | + f"second"} d"
| |_______________^ ISC003
82 |
83 | # See https://github.com/astral-sh/ruff/issues/12936
|


Loading

0 comments on commit 483748c

Please sign in to comment.