Skip to content

Commit

Permalink
Write autofixer for ISC001 (astral-sh#4829)
Browse files Browse the repository at this point in the history
  • Loading branch information
tkukushkin committed Jun 4, 2023
1 parent 4667192 commit 09af896
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,13 @@
b"abc"
b"def"
)

_ = """a""" """b"""

_ = f"""a""" f"""b"""

_ = f"a" "b"

_ = """a""" "b"

_ = 'a' "b"
120 changes: 111 additions & 9 deletions crates/ruff/src/rules/flake8_implicit_str_concat/rules/implicit.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use itertools::Itertools;
use ruff_text_size::TextRange;
use rustpython_parser::lexer::LexResult;
use rustpython_parser::Tok;
use rustpython_parser::{lexer::LexResult, StringKind};

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;

Expand Down Expand Up @@ -34,10 +34,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> {
Option::Some("Combine these string literals into one".to_string())
}
}

/// ## What it does
Expand Down Expand Up @@ -99,19 +105,115 @@ pub(crate) fn implicit(
})
.tuple_windows()
{
if matches!(a_tok, Tok::String { .. }) && matches!(b_tok, Tok::String { .. }) {
if let (
Tok::String {
kind: _a_kind,
triple_quoted: _a_triple_quoted,
value: _a_value,
..
},
Tok::String {
kind: _b_kind,
triple_quoted: _b_triple_quoted,
value: _b_value,
..
},
) = (a_tok, b_tok)
{
if locator.contains_line_break(TextRange::new(a_range.end(), b_range.start())) {
diagnostics.push(Diagnostic::new(
MultiLineImplicitStringConcatenation,
TextRange::new(a_range.start(), b_range.end()),
));
} else {
diagnostics.push(Diagnostic::new(
SingleLineImplicitStringConcatenation,
TextRange::new(a_range.start(), b_range.end()),
));
}
}
let range = TextRange::new(a_range.start(), b_range.end());

let mut diagnostic = Diagnostic::new(SingleLineImplicitStringConcatenation, range);

if let Some(fix) = get_fix_for_single_line_implicit_string_concatenation(
&a_tok, *a_range, &b_tok, *b_range, locator,
) {
diagnostic.set_fix(fix);
}

diagnostics.push(diagnostic);
};
};
}
diagnostics
}

fn get_fix_for_single_line_implicit_string_concatenation(
a_tok: &Tok,
a_range: TextRange,
b_tok: &Tok,
b_range: TextRange,
locator: &Locator,
) -> Option<Fix> {
let (
Tok::String {
kind: a_kind,
triple_quoted: a_triple_quoted,
..
},
Tok::String {
kind: b_kind,
triple_quoted: b_triple_quoted,
..
},
) = (a_tok, b_tok) else { return Option::None };

// Fix only strings of the same kind and triple-quotedness
if a_kind != b_kind || a_triple_quoted != b_triple_quoted {
return Option::None;
}

let a_text = &locator.contents()[a_range];
let b_text = &locator.contents()[b_range];

let a_quotes_style = get_quotes_style(a_text);
let b_quotes_style = get_quotes_style(b_text);

// Fix only strings with the same quotes style
if a_quotes_style != b_quotes_style {
return Option::None;
}

let text = skip_string_end(*a_triple_quoted, a_text).to_string()
+ skip_string_start(*b_kind, *b_triple_quoted, b_text);

Option::Some(Fix::automatic(Edit::range_replacement(
text,
TextRange::new(a_range.start(), b_range.end()),
)))
}

fn skip_string_start(kind: StringKind, triple_quoted: bool, text: &str) -> &str {
let quotes_len = match kind {
StringKind::String => 0,
StringKind::Bytes | StringKind::FString | StringKind::Unicode | StringKind::RawString => 1,
StringKind::RawBytes | StringKind::RawFString => 2,
} + (if triple_quoted { 3 } else { 1 });

&text[quotes_len..]
}

fn skip_string_end(triple_quoted: bool, text: &str) -> &str {
let quotes_len = if triple_quoted { 3 } else { 1 };

&text[..text.len() - quotes_len]
}

#[derive(PartialEq)]
enum QuotesStyle {
Double,
Single,
}

fn get_quotes_style(text: &str) -> QuotesStyle {
if text.ends_with('\"') {
QuotesStyle::Double
} else {
QuotesStyle::Single
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,109 @@
---
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 these string literals into one

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 these string literals into one

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
|
38 | )
39 |
40 | _ = """a""" """b"""
| ^^^^^^^^^^^^^^^ ISC001
41 |
42 | _ = f"""a""" f"""b"""
|
= help: Combine these string literals into one

Fix
35 35 | b"def"
36 36 | )
37 37 |
38 |-_ = """a""" """b"""
38 |+_ = """ab"""
39 39 |
40 40 | _ = f"""a""" f"""b"""
41 41 |

ISC.py:40:5: ISC001 [*] Implicitly concatenated string literals on one line
|
40 | _ = """a""" """b"""
41 |
42 | _ = f"""a""" f"""b"""
| ^^^^^^^^^^^^^^^^^ ISC001
43 |
44 | _ = f"a" "b"
|
= help: Combine these string literals into one

Fix
37 37 |
38 38 | _ = """a""" """b"""
39 39 |
40 |-_ = f"""a""" f"""b"""
40 |+_ = f"""ab"""
41 41 |
42 42 | _ = f"a" "b"
43 43 |

ISC.py:42:5: ISC001 Implicitly concatenated string literals on one line
|
42 | _ = f"""a""" f"""b"""
43 |
44 | _ = f"a" "b"
| ^^^^^^^^ ISC001
45 |
46 | _ = """a""" "b"
|
= help: Combine these string literals into one

ISC.py:44:5: ISC001 Implicitly concatenated string literals on one line
|
44 | _ = f"a" "b"
45 |
46 | _ = """a""" "b"
| ^^^^^^^^^^^ ISC001
47 |
48 | _ = 'a' "b"
|
= help: Combine these string literals into one

ISC.py:46:5: ISC001 Implicitly concatenated string literals on one line
|
46 | _ = """a""" "b"
47 |
48 | _ = 'a' "b"
| ^^^^^^^ ISC001
|
= help: Combine these string literals into one


Original file line number Diff line number Diff line change
@@ -1,20 +1,109 @@
---
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 these string literals into one

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 these string literals into one

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
|
38 | )
39 |
40 | _ = """a""" """b"""
| ^^^^^^^^^^^^^^^ ISC001
41 |
42 | _ = f"""a""" f"""b"""
|
= help: Combine these string literals into one

Fix
35 35 | b"def"
36 36 | )
37 37 |
38 |-_ = """a""" """b"""
38 |+_ = """ab"""
39 39 |
40 40 | _ = f"""a""" f"""b"""
41 41 |

ISC.py:40:5: ISC001 [*] Implicitly concatenated string literals on one line
|
40 | _ = """a""" """b"""
41 |
42 | _ = f"""a""" f"""b"""
| ^^^^^^^^^^^^^^^^^ ISC001
43 |
44 | _ = f"a" "b"
|
= help: Combine these string literals into one

Fix
37 37 |
38 38 | _ = """a""" """b"""
39 39 |
40 |-_ = f"""a""" f"""b"""
40 |+_ = f"""ab"""
41 41 |
42 42 | _ = f"a" "b"
43 43 |

ISC.py:42:5: ISC001 Implicitly concatenated string literals on one line
|
42 | _ = f"""a""" f"""b"""
43 |
44 | _ = f"a" "b"
| ^^^^^^^^ ISC001
45 |
46 | _ = """a""" "b"
|
= help: Combine these string literals into one

ISC.py:44:5: ISC001 Implicitly concatenated string literals on one line
|
44 | _ = f"a" "b"
45 |
46 | _ = """a""" "b"
| ^^^^^^^^^^^ ISC001
47 |
48 | _ = 'a' "b"
|
= help: Combine these string literals into one

ISC.py:46:5: ISC001 Implicitly concatenated string literals on one line
|
46 | _ = """a""" "b"
47 |
48 | _ = 'a' "b"
| ^^^^^^^ ISC001
|
= help: Combine these string literals into one


0 comments on commit 09af896

Please sign in to comment.