Autofixer for ISC001 (#4853)

## Summary

This PR adds autofixer for rule ISC001 in cases where both string
literals are of the same kind and with same quotes (double / single).

Fixes #4829

## Test Plan

I added testcases with different combinations of string literals.
This commit is contained in:
Timofei Kukushkin 2023-06-13 03:28:57 +04:00 committed by GitHub
parent 780336db0a
commit e2130707f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 332 additions and 9 deletions

View file

@ -34,3 +34,19 @@ _ = (
b"abc"
b"def"
)
_ = """a""" """b"""
_ = """a
b""" """c
d"""
_ = f"""a""" f"""b"""
_ = f"a" "b"
_ = """a""" "b"
_ = 'a' "b"
_ = rf"a" rf"b"

View file

@ -3,9 +3,10 @@ use ruff_text_size::TextRange;
use rustpython_parser::lexer::LexResult;
use rustpython_parser::Tok;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::source_code::Locator;
use ruff_python_ast::str::{leading_quote, trailing_quote};
use crate::rules::flake8_implicit_str_concat::settings::Settings;
@ -34,10 +35,16 @@ use crate::rules::flake8_implicit_str_concat::settings::Settings;
pub struct SingleLineImplicitStringConcatenation;
impl Violation for SingleLineImplicitStringConcatenation {
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
format!("Implicitly concatenated string literals on one line")
}
fn autofix_title(&self) -> Option<String> {
Some("Combine string literals".to_string())
}
}
/// ## What it does
@ -106,12 +113,50 @@ pub(crate) fn implicit(
TextRange::new(a_range.start(), b_range.end()),
));
} else {
diagnostics.push(Diagnostic::new(
let mut diagnostic = Diagnostic::new(
SingleLineImplicitStringConcatenation,
TextRange::new(a_range.start(), b_range.end()),
));
}
}
);
if let Some(fix) = concatenate_strings(*a_range, *b_range, locator) {
diagnostic.set_fix(fix);
}
diagnostics.push(diagnostic);
};
};
}
diagnostics
}
fn concatenate_strings(a_range: TextRange, b_range: TextRange, locator: &Locator) -> Option<Fix> {
let a_text = &locator.contents()[a_range];
let b_text = &locator.contents()[b_range];
let a_leading_quote = leading_quote(a_text)?;
let b_leading_quote = leading_quote(b_text)?;
// Require, for now, that the leading quotes are the same.
if a_leading_quote != b_leading_quote {
return None;
}
let a_trailing_quote = trailing_quote(a_text)?;
let b_trailing_quote = trailing_quote(b_text)?;
// Require, for now, that the trailing quotes are the same.
if a_trailing_quote != b_trailing_quote {
return None;
}
let a_body = &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()];
let concatenation = format!("{a_leading_quote}{a_body}{b_body}{a_trailing_quote}");
let range = TextRange::new(a_range.start(), b_range.end());
Some(Fix::automatic(Edit::range_replacement(
concatenation,
range,
)))
}

View file

@ -1,20 +1,151 @@
---
source: crates/ruff/src/rules/flake8_implicit_str_concat/mod.rs
---
ISC.py:1:5: ISC001 Implicitly concatenated string literals on one line
ISC.py:1:5: ISC001 [*] Implicitly concatenated string literals on one line
|
1 | _ = "a" "b" "c"
| ^^^^^^^ ISC001
2 |
3 | _ = "abc" + "def"
|
= help: Combine string literals
ISC.py:1:9: ISC001 Implicitly concatenated string literals on one line
Fix
1 |-_ = "a" "b" "c"
1 |+_ = "ab" "c"
2 2 |
3 3 | _ = "abc" + "def"
4 4 |
ISC.py:1:9: ISC001 [*] Implicitly concatenated string literals on one line
|
1 | _ = "a" "b" "c"
| ^^^^^^^ ISC001
2 |
3 | _ = "abc" + "def"
|
= help: Combine string literals
Fix
1 |-_ = "a" "b" "c"
1 |+_ = "a" "bc"
2 2 |
3 3 | _ = "abc" + "def"
4 4 |
ISC.py:38:5: ISC001 [*] Implicitly concatenated string literals on one line
|
36 | )
37 |
38 | _ = """a""" """b"""
| ^^^^^^^^^^^^^^^ ISC001
39 |
40 | _ = """a
|
= help: Combine string literals
Fix
35 35 | b"def"
36 36 | )
37 37 |
38 |-_ = """a""" """b"""
38 |+_ = """ab"""
39 39 |
40 40 | _ = """a
41 41 | b""" """c
ISC.py:40:5: ISC001 [*] Implicitly concatenated string literals on one line
|
38 | _ = """a""" """b"""
39 |
40 | _ = """a
| _____^
41 | | b""" """c
42 | | d"""
| |____^ ISC001
43 |
44 | _ = f"""a""" f"""b"""
|
= help: Combine string literals
Fix
38 38 | _ = """a""" """b"""
39 39 |
40 40 | _ = """a
41 |-b""" """c
41 |+bc
42 42 | d"""
43 43 |
44 44 | _ = f"""a""" f"""b"""
ISC.py:44:5: ISC001 [*] Implicitly concatenated string literals on one line
|
42 | d"""
43 |
44 | _ = f"""a""" f"""b"""
| ^^^^^^^^^^^^^^^^^ ISC001
45 |
46 | _ = f"a" "b"
|
= help: Combine string literals
Fix
41 41 | b""" """c
42 42 | d"""
43 43 |
44 |-_ = f"""a""" f"""b"""
44 |+_ = f"""ab"""
45 45 |
46 46 | _ = f"a" "b"
47 47 |
ISC.py:46:5: ISC001 Implicitly concatenated string literals on one line
|
44 | _ = f"""a""" f"""b"""
45 |
46 | _ = f"a" "b"
| ^^^^^^^^ ISC001
47 |
48 | _ = """a""" "b"
|
= help: Combine string literals
ISC.py:48:5: ISC001 Implicitly concatenated string literals on one line
|
46 | _ = f"a" "b"
47 |
48 | _ = """a""" "b"
| ^^^^^^^^^^^ ISC001
49 |
50 | _ = 'a' "b"
|
= help: Combine string literals
ISC.py:50:5: ISC001 Implicitly concatenated string literals on one line
|
48 | _ = """a""" "b"
49 |
50 | _ = 'a' "b"
| ^^^^^^^ ISC001
51 |
52 | _ = rf"a" rf"b"
|
= help: Combine string literals
ISC.py:52:5: ISC001 [*] Implicitly concatenated string literals on one line
|
50 | _ = 'a' "b"
51 |
52 | _ = rf"a" rf"b"
| ^^^^^^^^^^^ ISC001
|
= help: Combine string literals
Fix
49 49 |
50 50 | _ = 'a' "b"
51 51 |
52 |-_ = rf"a" rf"b"
52 |+_ = rf"ab"

View file

@ -1,20 +1,151 @@
---
source: crates/ruff/src/rules/flake8_implicit_str_concat/mod.rs
---
ISC.py:1:5: ISC001 Implicitly concatenated string literals on one line
ISC.py:1:5: ISC001 [*] Implicitly concatenated string literals on one line
|
1 | _ = "a" "b" "c"
| ^^^^^^^ ISC001
2 |
3 | _ = "abc" + "def"
|
= help: Combine string literals
ISC.py:1:9: ISC001 Implicitly concatenated string literals on one line
Fix
1 |-_ = "a" "b" "c"
1 |+_ = "ab" "c"
2 2 |
3 3 | _ = "abc" + "def"
4 4 |
ISC.py:1:9: ISC001 [*] Implicitly concatenated string literals on one line
|
1 | _ = "a" "b" "c"
| ^^^^^^^ ISC001
2 |
3 | _ = "abc" + "def"
|
= help: Combine string literals
Fix
1 |-_ = "a" "b" "c"
1 |+_ = "a" "bc"
2 2 |
3 3 | _ = "abc" + "def"
4 4 |
ISC.py:38:5: ISC001 [*] Implicitly concatenated string literals on one line
|
36 | )
37 |
38 | _ = """a""" """b"""
| ^^^^^^^^^^^^^^^ ISC001
39 |
40 | _ = """a
|
= help: Combine string literals
Fix
35 35 | b"def"
36 36 | )
37 37 |
38 |-_ = """a""" """b"""
38 |+_ = """ab"""
39 39 |
40 40 | _ = """a
41 41 | b""" """c
ISC.py:40:5: ISC001 [*] Implicitly concatenated string literals on one line
|
38 | _ = """a""" """b"""
39 |
40 | _ = """a
| _____^
41 | | b""" """c
42 | | d"""
| |____^ ISC001
43 |
44 | _ = f"""a""" f"""b"""
|
= help: Combine string literals
Fix
38 38 | _ = """a""" """b"""
39 39 |
40 40 | _ = """a
41 |-b""" """c
41 |+bc
42 42 | d"""
43 43 |
44 44 | _ = f"""a""" f"""b"""
ISC.py:44:5: ISC001 [*] Implicitly concatenated string literals on one line
|
42 | d"""
43 |
44 | _ = f"""a""" f"""b"""
| ^^^^^^^^^^^^^^^^^ ISC001
45 |
46 | _ = f"a" "b"
|
= help: Combine string literals
Fix
41 41 | b""" """c
42 42 | d"""
43 43 |
44 |-_ = f"""a""" f"""b"""
44 |+_ = f"""ab"""
45 45 |
46 46 | _ = f"a" "b"
47 47 |
ISC.py:46:5: ISC001 Implicitly concatenated string literals on one line
|
44 | _ = f"""a""" f"""b"""
45 |
46 | _ = f"a" "b"
| ^^^^^^^^ ISC001
47 |
48 | _ = """a""" "b"
|
= help: Combine string literals
ISC.py:48:5: ISC001 Implicitly concatenated string literals on one line
|
46 | _ = f"a" "b"
47 |
48 | _ = """a""" "b"
| ^^^^^^^^^^^ ISC001
49 |
50 | _ = 'a' "b"
|
= help: Combine string literals
ISC.py:50:5: ISC001 Implicitly concatenated string literals on one line
|
48 | _ = """a""" "b"
49 |
50 | _ = 'a' "b"
| ^^^^^^^ ISC001
51 |
52 | _ = rf"a" rf"b"
|
= help: Combine string literals
ISC.py:52:5: ISC001 [*] Implicitly concatenated string literals on one line
|
50 | _ = 'a' "b"
51 |
52 | _ = rf"a" rf"b"
| ^^^^^^^^^^^ ISC001
|
= help: Combine string literals
Fix
49 49 |
50 50 | _ = 'a' "b"
51 51 |
52 |-_ = rf"a" rf"b"
52 |+_ = rf"ab"