From 90a99ade3eb1fa30d645cd761948a5f758dc7fb3 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 26 Aug 2024 17:09:16 -0500 Subject: [PATCH 1/8] add test fixture --- .../test/fixtures/flake8_implicit_str_concat/ISC.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py b/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py index 5abfd1a2f1e5b..f57032cd272e1 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py @@ -79,3 +79,12 @@ + 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" \ No newline at end of file From 40856ad52e88f7f83458b90ffac881611bc961db Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 26 Aug 2024 17:09:51 -0500 Subject: [PATCH 2/8] update rule --- .../rules/implicit.rs | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs index 7d67980b59a3e..fdac72ba0d513 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs @@ -172,9 +172,14 @@ 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 = + a_text[a_leading_quote.len()..a_text.len() - a_trailing_quote.len()].to_string(); 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() { + a_body = normalize_ending_octal(&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()); @@ -183,3 +188,41 @@ 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: &str) -> String { + // Early return for short strings + if text.len() < 2 { + return text.to_string(); + } + + 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(&rev_bytes) { + let prefix = &text[..text.len() - 2]; + return format!("{prefix}\\00{}", last_byte as char); + } + // "\xy" -> "\0xy" + if let Some(penultimate_byte @ b'0'..=b'7') = rev_bytes.next() { + if has_odd_consecutive_backslashes(&rev_bytes) { + let prefix = &text[..text.len() - 3]; + return format!( + "{prefix}\\0{}{}", + penultimate_byte as char, last_byte as char + ); + } + } + } + text.to_string() +} + +fn has_odd_consecutive_backslashes + Clone>(itr: &I) -> bool { + let mut itrclone = itr.clone(); + let mut odd_backslashes = false; + while let Some(b'\\') = itrclone.next() { + odd_backslashes = !odd_backslashes; + } + odd_backslashes +} From 5b23d3dd17ba52e39fdf81026d799b5aa7694933 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 26 Aug 2024 17:09:59 -0500 Subject: [PATCH 3/8] update snapshots --- ...icit_str_concat__tests__ISC001_ISC.py.snap | 135 ++++++++++++++++++ ...icit_str_concat__tests__ISC003_ISC.py.snap | 4 +- ...oncat__tests__multiline_ISC001_ISC.py.snap | 135 ++++++++++++++++++ 3 files changed, 272 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap index c6a7536f7454c..57867106e07ee 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap @@ -296,4 +296,139 @@ 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" + +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" + | + = 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" + +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 + | + = 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" diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC003_ISC.py.snap b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC003_ISC.py.snap index e168bae22374a..ced4db0533a40 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC003_ISC.py.snap +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC003_ISC.py.snap @@ -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 | - - diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap index c6a7536f7454c..57867106e07ee 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap @@ -296,4 +296,139 @@ 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" + +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" + | + = 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" + +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 + | + = 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" From c8b059b291a870ee8b2e0dfe67e2c11423cd06cd Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Tue, 27 Aug 2024 11:40:03 -0500 Subject: [PATCH 4/8] update test fixture --- .../resources/test/fixtures/flake8_implicit_str_concat/ISC.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py b/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py index f57032cd272e1..4cbe5252afd7a 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py @@ -87,4 +87,6 @@ _ = "\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" \ No newline at end of file +_ = "\8""0" # fix should be "\80" +_ = "\12""8" # fix should be "\128" +_ = "\12""foo" # fix should be "\12foo" \ No newline at end of file From 1e3bdf7fe72bef6a2d3eb8f37f5b4165ccf54ecf Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Tue, 27 Aug 2024 11:46:08 -0500 Subject: [PATCH 5/8] only pad octal when second string starts with octal digit --- .../src/rules/flake8_implicit_str_concat/rules/implicit.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs index fdac72ba0d513..776e7540ebbf5 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs @@ -176,7 +176,9 @@ fn concatenate_strings(a_range: TextRange, b_range: TextRange, locator: &Locator a_text[a_leading_quote.len()..a_text.len() - a_trailing_quote.len()].to_string(); 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() { + if a_leading_quote.find(['r', 'R']).is_none() + && b_body.starts_with(['0', '1', '2', '3', '4', '5', '6', '7']) + { a_body = normalize_ending_octal(&a_body); } From 426ff1751c4365ce60a4260ff20799c9ad5667be Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Tue, 27 Aug 2024 11:47:29 -0500 Subject: [PATCH 6/8] update snapshot --- ...icit_str_concat__tests__ISC001_ISC.py.snap | 42 +++++++++++++++++++ ...oncat__tests__multiline_ISC001_ISC.py.snap | 42 +++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap index 57867106e07ee..20eac52326745 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap @@ -398,6 +398,7 @@ ISC.py:88:5: ISC001 [*] Implicitly concatenated string literals on one line 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 | @@ -406,6 +407,7 @@ ISC.py:89:5: ISC001 [*] Implicitly concatenated string literals on one line 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 @@ -416,6 +418,8 @@ ISC.py:89:5: ISC001 [*] Implicitly concatenated string literals on one line 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 | @@ -423,6 +427,8 @@ ISC.py:90: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" | ^^^^^^^ ISC001 +91 | _ = "\12""8" # fix should be "\128" +92 | _ = "\12""foo" # fix should be "\12foo" | = help: Combine string literals @@ -432,3 +438,39 @@ ISC.py:90:5: ISC001 [*] Implicitly concatenated string literals on one line 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" + +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" + | + = 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" + +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 + | + = 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" diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap index 57867106e07ee..20eac52326745 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap @@ -398,6 +398,7 @@ ISC.py:88:5: ISC001 [*] Implicitly concatenated string literals on one line 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 | @@ -406,6 +407,7 @@ ISC.py:89:5: ISC001 [*] Implicitly concatenated string literals on one line 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 @@ -416,6 +418,8 @@ ISC.py:89:5: ISC001 [*] Implicitly concatenated string literals on one line 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 | @@ -423,6 +427,8 @@ ISC.py:90: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" | ^^^^^^^ ISC001 +91 | _ = "\12""8" # fix should be "\128" +92 | _ = "\12""foo" # fix should be "\12foo" | = help: Combine string literals @@ -432,3 +438,39 @@ ISC.py:90:5: ISC001 [*] Implicitly concatenated string literals on one line 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" + +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" + | + = 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" + +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 + | + = 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" From 30cce39f5ca0d09846eff81b5eb485f5b8bb0ccc Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Tue, 27 Aug 2024 12:04:06 -0500 Subject: [PATCH 7/8] change signature for odd backslash check --- .../rules/flake8_implicit_str_concat/rules/implicit.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs index 776e7540ebbf5..63b87e2770cc3 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs @@ -202,13 +202,13 @@ fn normalize_ending_octal(text: &str) -> String { 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(&rev_bytes) { + if has_odd_consecutive_backslashes(&mut rev_bytes.clone()) { let prefix = &text[..text.len() - 2]; return format!("{prefix}\\00{}", last_byte as char); } // "\xy" -> "\0xy" if let Some(penultimate_byte @ b'0'..=b'7') = rev_bytes.next() { - if has_odd_consecutive_backslashes(&rev_bytes) { + if has_odd_consecutive_backslashes(&mut rev_bytes.clone()) { let prefix = &text[..text.len() - 3]; return format!( "{prefix}\\0{}{}", @@ -220,10 +220,9 @@ fn normalize_ending_octal(text: &str) -> String { text.to_string() } -fn has_odd_consecutive_backslashes + Clone>(itr: &I) -> bool { - let mut itrclone = itr.clone(); +fn has_odd_consecutive_backslashes(mut itr: impl Iterator) -> bool { let mut odd_backslashes = false; - while let Some(b'\\') = itrclone.next() { + while let Some(b'\\') = itr.next() { odd_backslashes = !odd_backslashes; } odd_backslashes From 25805a2992889918b3b5626c5e60139ca756037d Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 27 Aug 2024 18:48:30 +0100 Subject: [PATCH 8/8] Avoid an allocation and add another edge-case test --- .../flake8_implicit_str_concat/ISC.py | 3 ++- .../rules/implicit.rs | 21 ++++++++++--------- ...icit_str_concat__tests__ISC001_ISC.py.snap | 21 +++++++++++++++++++ ...oncat__tests__multiline_ISC001_ISC.py.snap | 21 +++++++++++++++++++ 4 files changed, 55 insertions(+), 11 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py b/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py index 4cbe5252afd7a..68fc1e3d9c63c 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_implicit_str_concat/ISC.py @@ -89,4 +89,5 @@ _ = "\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" \ No newline at end of file +_ = "\12""foo" # fix should be "\12foo" +_ = "\12" "" # fix should be "\12" diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs index 63b87e2770cc3..0011d9d5051c0 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use itertools::Itertools; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; @@ -173,13 +175,13 @@ fn concatenate_strings(a_range: TextRange, b_range: TextRange, locator: &Locator } let mut a_body = - a_text[a_leading_quote.len()..a_text.len() - a_trailing_quote.len()].to_string(); + 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() - && b_body.starts_with(['0', '1', '2', '3', '4', '5', '6', '7']) + && matches!(b_body.bytes().next(), Some(b'0'..=b'7')) { - a_body = normalize_ending_octal(&a_body); + normalize_ending_octal(&mut a_body); } let concatenation = format!("{a_leading_quote}{a_body}{b_body}{a_trailing_quote}"); @@ -193,10 +195,10 @@ fn concatenate_strings(a_range: TextRange, b_range: TextRange, locator: &Locator /// Pads an octal at the end of the string /// to three digits, if necessary. -fn normalize_ending_octal(text: &str) -> String { +fn normalize_ending_octal(text: &mut Cow<'_, str>) { // Early return for short strings if text.len() < 2 { - return text.to_string(); + return; } let mut rev_bytes = text.bytes().rev(); @@ -204,20 +206,19 @@ fn normalize_ending_octal(text: &str) -> String { // "\y" -> "\00y" if has_odd_consecutive_backslashes(&mut rev_bytes.clone()) { let prefix = &text[..text.len() - 2]; - return format!("{prefix}\\00{}", last_byte as char); + *text = Cow::Owned(format!("{prefix}\\00{}", last_byte as char)); } // "\xy" -> "\0xy" - if let Some(penultimate_byte @ b'0'..=b'7') = rev_bytes.next() { + 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]; - return format!( + *text = Cow::Owned(format!( "{prefix}\\0{}{}", penultimate_byte as char, last_byte as char - ); + )); } } } - text.to_string() } fn has_odd_consecutive_backslashes(mut itr: impl Iterator) -> bool { diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap index 20eac52326745..a168e4b85602f 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC.py.snap @@ -440,6 +440,7 @@ ISC.py:90:5: ISC001 [*] Implicitly concatenated string literals on one line 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 | @@ -448,6 +449,7 @@ ISC.py:91:5: ISC001 [*] Implicitly concatenated string literals on one line 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 @@ -458,6 +460,7 @@ ISC.py:91:5: ISC001 [*] Implicitly concatenated string literals on one line 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 | @@ -465,6 +468,7 @@ ISC.py:92:5: ISC001 [*] Implicitly concatenated string literals on one line 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 @@ -474,3 +478,20 @@ ISC.py:92:5: ISC001 [*] Implicitly concatenated string literals on one line 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" diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap index 20eac52326745..a168e4b85602f 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__multiline_ISC001_ISC.py.snap @@ -440,6 +440,7 @@ ISC.py:90:5: ISC001 [*] Implicitly concatenated string literals on one line 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 | @@ -448,6 +449,7 @@ ISC.py:91:5: ISC001 [*] Implicitly concatenated string literals on one line 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 @@ -458,6 +460,7 @@ ISC.py:91:5: ISC001 [*] Implicitly concatenated string literals on one line 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 | @@ -465,6 +468,7 @@ ISC.py:92:5: ISC001 [*] Implicitly concatenated string literals on one line 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 @@ -474,3 +478,20 @@ ISC.py:92:5: ISC001 [*] Implicitly concatenated string literals on one line 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"