mirror of https://github.com/astral-sh/ruff
[`pyupgrade`] Implement import-replacement rule (`UP035`) (#2049)
This commit is contained in:
parent
69e20c4554
commit
ad8693e3de
|
|
@ -847,6 +847,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/) on PyPI.
|
||||||
| UP032 | f-string | Use f-string instead of `format` call | 🛠 |
|
| UP032 | f-string | Use f-string instead of `format` call | 🛠 |
|
||||||
| UP033 | functools-cache | Use `@functools.cache` instead of `@functools.lru_cache(maxsize=None)` | 🛠 |
|
| UP033 | functools-cache | Use `@functools.cache` instead of `@functools.lru_cache(maxsize=None)` | 🛠 |
|
||||||
| UP034 | extraneous-parentheses | Avoid extraneous parentheses | 🛠 |
|
| UP034 | extraneous-parentheses | Avoid extraneous parentheses | 🛠 |
|
||||||
|
| UP035 | import-replacements | Import from `{module}` instead: {names} | 🛠 |
|
||||||
|
|
||||||
### flake8-2020 (YTT)
|
### flake8-2020 (YTT)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
# UP035
|
||||||
|
from collections import Mapping
|
||||||
|
|
||||||
|
from collections import Mapping as MAP
|
||||||
|
|
||||||
|
from collections import Mapping, Sequence
|
||||||
|
|
||||||
|
from collections import Counter, Mapping
|
||||||
|
|
||||||
|
from collections import (Counter, Mapping)
|
||||||
|
|
||||||
|
from collections import (Counter,
|
||||||
|
Mapping)
|
||||||
|
|
||||||
|
from collections import Counter, \
|
||||||
|
Mapping
|
||||||
|
|
||||||
|
from collections import Counter, Mapping, Sequence
|
||||||
|
|
||||||
|
from collections import Mapping as mapping, Counter
|
||||||
|
|
||||||
|
if True:
|
||||||
|
from collections import Mapping, Counter
|
||||||
|
|
||||||
|
if True:
|
||||||
|
if True:
|
||||||
|
pass
|
||||||
|
from collections import Mapping, Counter
|
||||||
|
|
||||||
|
if True: from collections import Mapping
|
||||||
|
|
||||||
|
import os
|
||||||
|
from collections import Counter, Mapping
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if True:
|
||||||
|
from collections import (
|
||||||
|
Mapping,
|
||||||
|
Callable,
|
||||||
|
Bad,
|
||||||
|
Good,
|
||||||
|
)
|
||||||
|
|
||||||
|
from typing import Callable, Match, Pattern, List
|
||||||
|
|
||||||
|
if True: from collections import (
|
||||||
|
Mapping, Counter)
|
||||||
|
|
||||||
|
# OK
|
||||||
|
from a import b
|
||||||
|
|
@ -1892,6 +1892,7 @@
|
||||||
"UP032",
|
"UP032",
|
||||||
"UP033",
|
"UP033",
|
||||||
"UP034",
|
"UP034",
|
||||||
|
"UP035",
|
||||||
"W",
|
"W",
|
||||||
"W2",
|
"W2",
|
||||||
"W29",
|
"W29",
|
||||||
|
|
|
||||||
|
|
@ -1073,6 +1073,15 @@ where
|
||||||
if self.settings.rules.enabled(&Rule::RewriteCElementTree) {
|
if self.settings.rules.enabled(&Rule::RewriteCElementTree) {
|
||||||
pyupgrade::rules::replace_c_element_tree(self, stmt);
|
pyupgrade::rules::replace_c_element_tree(self, stmt);
|
||||||
}
|
}
|
||||||
|
if self.settings.rules.enabled(&Rule::ImportReplacements) {
|
||||||
|
pyupgrade::rules::import_replacements(
|
||||||
|
self,
|
||||||
|
stmt,
|
||||||
|
names,
|
||||||
|
module.as_ref().map(String::as_str),
|
||||||
|
level.as_ref(),
|
||||||
|
);
|
||||||
|
}
|
||||||
if self.settings.rules.enabled(&Rule::UnnecessaryBuiltinImport) {
|
if self.settings.rules.enabled(&Rule::UnnecessaryBuiltinImport) {
|
||||||
if let Some(module) = module.as_deref() {
|
if let Some(module) = module.as_deref() {
|
||||||
pyupgrade::rules::unnecessary_builtin_import(self, stmt, module, names);
|
pyupgrade::rules::unnecessary_builtin_import(self, stmt, module, names);
|
||||||
|
|
|
||||||
|
|
@ -256,6 +256,7 @@ ruff_macros::define_rule_mapping!(
|
||||||
UP032 => violations::FString,
|
UP032 => violations::FString,
|
||||||
UP033 => violations::FunctoolsCache,
|
UP033 => violations::FunctoolsCache,
|
||||||
UP034 => violations::ExtraneousParentheses,
|
UP034 => violations::ExtraneousParentheses,
|
||||||
|
UP035 => rules::pyupgrade::rules::ImportReplacements,
|
||||||
// pydocstyle
|
// pydocstyle
|
||||||
D100 => violations::PublicModule,
|
D100 => violations::PublicModule,
|
||||||
D101 => violations::PublicClass,
|
D101 => violations::PublicClass,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ use libcst_native::{
|
||||||
Codegen, CodegenState, Expression, ParenthesizableWhitespace, SmallStatement, Statement,
|
Codegen, CodegenState, Expression, ParenthesizableWhitespace, SmallStatement, Statement,
|
||||||
};
|
};
|
||||||
use rustpython_ast::{Expr, Keyword, Location};
|
use rustpython_ast::{Expr, Keyword, Location};
|
||||||
|
use rustpython_parser::lexer;
|
||||||
|
use rustpython_parser::lexer::Tok;
|
||||||
|
|
||||||
use crate::ast::types::Range;
|
use crate::ast::types::Range;
|
||||||
use crate::autofix::helpers::remove_argument;
|
use crate::autofix::helpers::remove_argument;
|
||||||
|
|
@ -58,3 +60,215 @@ pub fn remove_super_arguments(locator: &Locator, stylist: &Stylist, expr: &Expr)
|
||||||
range.end_location,
|
range.end_location,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove any imports matching `members` from an import-from statement.
|
||||||
|
pub fn remove_import_members(contents: &str, members: &[&str]) -> String {
|
||||||
|
let mut names: Vec<Range> = vec![];
|
||||||
|
let mut commas: Vec<Range> = vec![];
|
||||||
|
let mut removal_indices: Vec<usize> = vec![];
|
||||||
|
|
||||||
|
// Find all Tok::Name tokens that are not preceded by Tok::As, and all Tok::Comma tokens.
|
||||||
|
let mut prev_tok = None;
|
||||||
|
for (start, tok, end) in lexer::make_tokenizer(contents)
|
||||||
|
.flatten()
|
||||||
|
.skip_while(|(_, tok, _)| !matches!(tok, Tok::Import))
|
||||||
|
{
|
||||||
|
if let Tok::Name { name } = &tok {
|
||||||
|
if matches!(prev_tok, Some(Tok::As)) {
|
||||||
|
// Adjust the location to take the alias into account.
|
||||||
|
names.last_mut().unwrap().end_location = end;
|
||||||
|
} else {
|
||||||
|
if members.contains(&name.as_str()) {
|
||||||
|
removal_indices.push(names.len());
|
||||||
|
}
|
||||||
|
names.push(Range::new(start, end));
|
||||||
|
}
|
||||||
|
} else if matches!(tok, Tok::Comma) {
|
||||||
|
commas.push(Range::new(start, end));
|
||||||
|
}
|
||||||
|
prev_tok = Some(tok);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconstruct the source code by skipping any names that are in `members`.
|
||||||
|
let locator = Locator::new(contents);
|
||||||
|
let mut output = String::with_capacity(contents.len());
|
||||||
|
let mut last_pos: Location = Location::new(1, 0);
|
||||||
|
let mut is_first = true;
|
||||||
|
for index in 0..names.len() {
|
||||||
|
if !removal_indices.contains(&index) {
|
||||||
|
is_first = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (start_location, end_location) = if is_first {
|
||||||
|
(names[index].location, names[index + 1].location)
|
||||||
|
} else {
|
||||||
|
(commas[index - 1].location, names[index].end_location)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add all contents from `last_pos` to `fix.location`.
|
||||||
|
// It's possible that `last_pos` is after `fix.location`, if we're removing the first _two_
|
||||||
|
// members.
|
||||||
|
if start_location > last_pos {
|
||||||
|
let slice = locator.slice_source_code_range(&Range::new(last_pos, start_location));
|
||||||
|
output.push_str(slice);
|
||||||
|
}
|
||||||
|
|
||||||
|
last_pos = end_location;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the remaining content.
|
||||||
|
let slice = locator.slice_source_code_at(last_pos);
|
||||||
|
output.push_str(slice);
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::rules::pyupgrade::fixes::remove_import_members;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn once() {
|
||||||
|
let source = r#"from foo import bar, baz, bop, qux as q"#;
|
||||||
|
let expected = r#"from foo import bar, baz, qux as q"#;
|
||||||
|
let actual = remove_import_members(source, &["bop"]);
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn twice() {
|
||||||
|
let source = r#"from foo import bar, baz, bop, qux as q"#;
|
||||||
|
let expected = r#"from foo import bar, qux as q"#;
|
||||||
|
let actual = remove_import_members(source, &["baz", "bop"]);
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn aliased() {
|
||||||
|
let source = r#"from foo import bar, baz, bop as boop, qux as q"#;
|
||||||
|
let expected = r#"from foo import bar, baz, qux as q"#;
|
||||||
|
let actual = remove_import_members(source, &["bop"]);
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parenthesized() {
|
||||||
|
let source = r#"from foo import (bar, baz, bop, qux as q)"#;
|
||||||
|
let expected = r#"from foo import (bar, baz, qux as q)"#;
|
||||||
|
let actual = remove_import_members(source, &["bop"]);
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn last_import() {
|
||||||
|
let source = r#"from foo import bar, baz, bop, qux as q"#;
|
||||||
|
let expected = r#"from foo import bar, baz, bop"#;
|
||||||
|
let actual = remove_import_members(source, &["qux"]);
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn first_import() {
|
||||||
|
let source = r#"from foo import bar, baz, bop, qux as q"#;
|
||||||
|
let expected = r#"from foo import baz, bop, qux as q"#;
|
||||||
|
let actual = remove_import_members(source, &["bar"]);
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn first_two_imports() {
|
||||||
|
let source = r#"from foo import bar, baz, bop, qux as q"#;
|
||||||
|
let expected = r#"from foo import bop, qux as q"#;
|
||||||
|
let actual = remove_import_members(source, &["bar", "baz"]);
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn first_two_imports_multiline() {
|
||||||
|
let source = r#"from foo import (
|
||||||
|
bar,
|
||||||
|
baz,
|
||||||
|
bop,
|
||||||
|
qux as q
|
||||||
|
)"#;
|
||||||
|
let expected = r#"from foo import (
|
||||||
|
bop,
|
||||||
|
qux as q
|
||||||
|
)"#;
|
||||||
|
let actual = remove_import_members(source, &["bar", "baz"]);
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiline_once() {
|
||||||
|
let source = r#"from foo import (
|
||||||
|
bar,
|
||||||
|
baz,
|
||||||
|
bop,
|
||||||
|
qux as q,
|
||||||
|
)"#;
|
||||||
|
let expected = r#"from foo import (
|
||||||
|
bar,
|
||||||
|
baz,
|
||||||
|
qux as q,
|
||||||
|
)"#;
|
||||||
|
let actual = remove_import_members(source, &["bop"]);
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiline_twice() {
|
||||||
|
let source = r#"from foo import (
|
||||||
|
bar,
|
||||||
|
baz,
|
||||||
|
bop,
|
||||||
|
qux as q,
|
||||||
|
)"#;
|
||||||
|
let expected = r#"from foo import (
|
||||||
|
bar,
|
||||||
|
qux as q,
|
||||||
|
)"#;
|
||||||
|
let actual = remove_import_members(source, &["baz", "bop"]);
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiline_comment() {
|
||||||
|
let source = r#"from foo import (
|
||||||
|
bar,
|
||||||
|
baz,
|
||||||
|
# This comment should be removed.
|
||||||
|
bop,
|
||||||
|
# This comment should be retained.
|
||||||
|
qux as q,
|
||||||
|
)"#;
|
||||||
|
let expected = r#"from foo import (
|
||||||
|
bar,
|
||||||
|
baz,
|
||||||
|
# This comment should be retained.
|
||||||
|
qux as q,
|
||||||
|
)"#;
|
||||||
|
let actual = remove_import_members(source, &["bop"]);
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_comment_first_import() {
|
||||||
|
let source = r#"from foo import (
|
||||||
|
# This comment should be retained.
|
||||||
|
bar,
|
||||||
|
# This comment should be removed.
|
||||||
|
baz,
|
||||||
|
bop,
|
||||||
|
qux as q,
|
||||||
|
)"#;
|
||||||
|
let expected = r#"from foo import (
|
||||||
|
# This comment should be retained.
|
||||||
|
baz,
|
||||||
|
bop,
|
||||||
|
qux as q,
|
||||||
|
)"#;
|
||||||
|
let actual = remove_import_members(source, &["bar"]);
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ mod tests {
|
||||||
#[test_case(Rule::FString, Path::new("UP032.py"); "UP032")]
|
#[test_case(Rule::FString, Path::new("UP032.py"); "UP032")]
|
||||||
#[test_case(Rule::FunctoolsCache, Path::new("UP033.py"); "UP033")]
|
#[test_case(Rule::FunctoolsCache, Path::new("UP033.py"); "UP033")]
|
||||||
#[test_case(Rule::ExtraneousParentheses, Path::new("UP034.py"); "UP034")]
|
#[test_case(Rule::ExtraneousParentheses, Path::new("UP034.py"); "UP034")]
|
||||||
|
#[test_case(Rule::ImportReplacements, Path::new("UP035.py"); "UP035")]
|
||||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,465 @@
|
||||||
|
use itertools::Itertools;
|
||||||
|
use rustpython_ast::{Alias, AliasData, Stmt};
|
||||||
|
|
||||||
|
use ruff_macros::derive_message_formats;
|
||||||
|
|
||||||
|
use crate::ast::types::Range;
|
||||||
|
use crate::ast::whitespace::indentation;
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::fix::Fix;
|
||||||
|
use crate::registry::{Diagnostic, Rule};
|
||||||
|
use crate::rules::pyupgrade::fixes;
|
||||||
|
use crate::settings::types::PythonVersion;
|
||||||
|
use crate::source_code::{Locator, Stylist};
|
||||||
|
use crate::violation::{Availability, Violation};
|
||||||
|
use crate::{define_violation, AutofixKind};
|
||||||
|
|
||||||
|
define_violation!(
|
||||||
|
pub struct ImportReplacements {
|
||||||
|
pub module: String,
|
||||||
|
pub members: Vec<String>,
|
||||||
|
pub fixable: bool,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
impl Violation for ImportReplacements {
|
||||||
|
const AUTOFIX: Option<AutofixKind> = Some(AutofixKind::new(Availability::Always));
|
||||||
|
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let ImportReplacements {
|
||||||
|
module, members, ..
|
||||||
|
} = self;
|
||||||
|
let names = members.iter().map(|name| format!("`{name}`")).join(", ");
|
||||||
|
format!("Import from `{module}` instead: {names}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||||
|
let ImportReplacements { fixable, .. } = self;
|
||||||
|
if *fixable {
|
||||||
|
Some(|ImportReplacements { module, .. }| format!("Import from `{module}`"))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A list of modules that may involve import rewrites.
|
||||||
|
const RELEVANT_MODULES: &[&str] = &[
|
||||||
|
"collections",
|
||||||
|
"pipes",
|
||||||
|
"mypy_extensions",
|
||||||
|
"typing_extensions",
|
||||||
|
"typing",
|
||||||
|
"typing.re",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Members of `collections` that have been moved to `collections.abc`.
|
||||||
|
const COLLECTIONS_TO_ABC: &[&str] = &[
|
||||||
|
"AsyncGenerator",
|
||||||
|
"AsyncIterable",
|
||||||
|
"AsyncIterator",
|
||||||
|
"Awaitable",
|
||||||
|
"ByteString",
|
||||||
|
"Callable",
|
||||||
|
"Collection",
|
||||||
|
"Container",
|
||||||
|
"Coroutine",
|
||||||
|
"Generator",
|
||||||
|
"Hashable",
|
||||||
|
"ItemsView",
|
||||||
|
"Iterable",
|
||||||
|
"Iterator",
|
||||||
|
"KeysView",
|
||||||
|
"Mapping",
|
||||||
|
"MappingView",
|
||||||
|
"MutableMapping",
|
||||||
|
"MutableSequence",
|
||||||
|
"MutableSet",
|
||||||
|
"Reversible",
|
||||||
|
"Sequence",
|
||||||
|
"Set",
|
||||||
|
"Sized",
|
||||||
|
"ValuesView",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Members of `pipes` that have been moved to `shlex`.
|
||||||
|
const PIPES_TO_SHLEX: &[&str] = &["quote"];
|
||||||
|
|
||||||
|
// Members of `typing_extensions` that have been moved to `typing`.
|
||||||
|
const TYPING_EXTENSIONS_TO_TYPING: &[&str] = &[
|
||||||
|
"AsyncIterable",
|
||||||
|
"AsyncIterator",
|
||||||
|
"Awaitable",
|
||||||
|
"ClassVar",
|
||||||
|
"ContextManager",
|
||||||
|
"Coroutine",
|
||||||
|
"DefaultDict",
|
||||||
|
"NewType",
|
||||||
|
"TYPE_CHECKING",
|
||||||
|
"Text",
|
||||||
|
"Type",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Python 3.7+
|
||||||
|
|
||||||
|
// Members of `mypy_extensions` that have been moved to `typing`.
|
||||||
|
const MYPY_EXTENSIONS_TO_TYPING_37: &[&str] = &["NoReturn"];
|
||||||
|
|
||||||
|
// Members of `typing_extensions` that have been moved to `typing`.
|
||||||
|
const TYPING_EXTENSIONS_TO_TYPING_37: &[&str] = &[
|
||||||
|
"AsyncContextManager",
|
||||||
|
"AsyncGenerator",
|
||||||
|
"ChainMap",
|
||||||
|
"Counter",
|
||||||
|
"Deque",
|
||||||
|
"NoReturn",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Python 3.8+
|
||||||
|
|
||||||
|
// Members of `mypy_extensions` that have been moved to `typing`.
|
||||||
|
const MYPY_EXTENSIONS_TO_TYPING_38: &[&str] = &["TypedDict"];
|
||||||
|
|
||||||
|
// Members of `typing_extensions` that have been moved to `typing`.
|
||||||
|
const TYPING_EXTENSIONS_TO_TYPING_38: &[&str] = &[
|
||||||
|
"Final",
|
||||||
|
"Literal",
|
||||||
|
"OrderedDict",
|
||||||
|
"Protocol",
|
||||||
|
"SupportsIndex",
|
||||||
|
"runtime_checkable",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Python 3.9+
|
||||||
|
|
||||||
|
// Members of `typing` that have been moved to `collections.abc`.
|
||||||
|
const TYPING_TO_COLLECTIONS_ABC_39: &[&str] = &[
|
||||||
|
"AsyncGenerator",
|
||||||
|
"AsyncIterable",
|
||||||
|
"AsyncIterator",
|
||||||
|
"Awaitable",
|
||||||
|
"ByteString",
|
||||||
|
"ChainMap",
|
||||||
|
"Collection",
|
||||||
|
"Container",
|
||||||
|
"Coroutine",
|
||||||
|
"Counter",
|
||||||
|
"Generator",
|
||||||
|
"Hashable",
|
||||||
|
"ItemsView",
|
||||||
|
"Iterable",
|
||||||
|
"Iterator",
|
||||||
|
"KeysView",
|
||||||
|
"Mapping",
|
||||||
|
"MappingView",
|
||||||
|
"MutableMapping",
|
||||||
|
"MutableSequence",
|
||||||
|
"MutableSet",
|
||||||
|
"Reversible",
|
||||||
|
"Sequence",
|
||||||
|
"Sized",
|
||||||
|
"ValuesView",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Members of `typing` that have been moved to `typing.re`.
|
||||||
|
const TYPING_TO_RE_39: &[&str] = &["Match", "Pattern"];
|
||||||
|
|
||||||
|
// Members of `typing.re` that have been moved to `re`.
|
||||||
|
const TYPING_RE_TO_RE_39: &[&str] = &["Match", "Pattern"];
|
||||||
|
|
||||||
|
// Members of `typing_extensions` that have been moved to `typing`.
|
||||||
|
const TYPING_EXTENSIONS_TO_TYPING_39: &[&str] = &["Annotated", "get_type_hints"];
|
||||||
|
|
||||||
|
// Python 3.10+
|
||||||
|
|
||||||
|
// Members of `typing` that have been moved to `collections.abc`.
|
||||||
|
const TYPING_TO_COLLECTIONS_ABC_310: &[&str] = &["Callable"];
|
||||||
|
|
||||||
|
// Members of `typing_extensions` that have been moved to `typing`.
|
||||||
|
const TYPING_EXTENSIONS_TO_TYPING_310: &[&str] = &[
|
||||||
|
"Concatenate",
|
||||||
|
"ParamSpecArgs",
|
||||||
|
"ParamSpecKwargs",
|
||||||
|
"TypeAlias",
|
||||||
|
"TypeGuard",
|
||||||
|
"get_args",
|
||||||
|
"get_origin",
|
||||||
|
"is_typeddict",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Python 3.11+
|
||||||
|
|
||||||
|
// Members of `typing_extensions` that have been moved to `typing`.
|
||||||
|
const TYPING_EXTENSIONS_TO_TYPING_311: &[&str] = &[
|
||||||
|
"Any",
|
||||||
|
"LiteralString",
|
||||||
|
"NamedTuple",
|
||||||
|
"Never",
|
||||||
|
"NotRequired",
|
||||||
|
"Required",
|
||||||
|
"Self",
|
||||||
|
"TypedDict",
|
||||||
|
"Unpack",
|
||||||
|
"assert_never",
|
||||||
|
"assert_type",
|
||||||
|
"clear_overloads",
|
||||||
|
"dataclass_transform",
|
||||||
|
"final",
|
||||||
|
"get_overloads",
|
||||||
|
"overload",
|
||||||
|
"reveal_type",
|
||||||
|
];
|
||||||
|
|
||||||
|
struct Replacement<'a> {
|
||||||
|
module: &'a str,
|
||||||
|
members: Vec<&'a AliasData>,
|
||||||
|
content: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ImportReplacer<'a> {
|
||||||
|
stmt: &'a Stmt,
|
||||||
|
module: &'a str,
|
||||||
|
members: &'a [AliasData],
|
||||||
|
locator: &'a Locator<'a>,
|
||||||
|
stylist: &'a Stylist<'a>,
|
||||||
|
version: PythonVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ImportReplacer<'a> {
|
||||||
|
fn new(
|
||||||
|
stmt: &'a Stmt,
|
||||||
|
module: &'a str,
|
||||||
|
members: &'a [AliasData],
|
||||||
|
locator: &'a Locator<'a>,
|
||||||
|
stylist: &'a Stylist<'a>,
|
||||||
|
version: PythonVersion,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
stmt,
|
||||||
|
module,
|
||||||
|
members,
|
||||||
|
locator,
|
||||||
|
stylist,
|
||||||
|
version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replacements(&self) -> Vec<Replacement> {
|
||||||
|
let mut replacements = vec![];
|
||||||
|
match self.module {
|
||||||
|
"collections" => {
|
||||||
|
if let Some(replacement) = self.try_replace(COLLECTIONS_TO_ABC, "collections.abc") {
|
||||||
|
replacements.push(replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"pipes" => {
|
||||||
|
if let Some(replacement) = self.try_replace(PIPES_TO_SHLEX, "shlex") {
|
||||||
|
replacements.push(replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"typing_extensions" => {
|
||||||
|
let mut typing_extensions_to_typing = TYPING_EXTENSIONS_TO_TYPING.to_vec();
|
||||||
|
if self.version >= PythonVersion::Py37 {
|
||||||
|
typing_extensions_to_typing.extend(TYPING_EXTENSIONS_TO_TYPING_37);
|
||||||
|
}
|
||||||
|
if self.version >= PythonVersion::Py38 {
|
||||||
|
typing_extensions_to_typing.extend(TYPING_EXTENSIONS_TO_TYPING_38);
|
||||||
|
}
|
||||||
|
if self.version >= PythonVersion::Py39 {
|
||||||
|
typing_extensions_to_typing.extend(TYPING_EXTENSIONS_TO_TYPING_39);
|
||||||
|
}
|
||||||
|
if self.version >= PythonVersion::Py310 {
|
||||||
|
typing_extensions_to_typing.extend(TYPING_EXTENSIONS_TO_TYPING_310);
|
||||||
|
}
|
||||||
|
if self.version >= PythonVersion::Py311 {
|
||||||
|
typing_extensions_to_typing.extend(TYPING_EXTENSIONS_TO_TYPING_311);
|
||||||
|
}
|
||||||
|
if let Some(replacement) = self.try_replace(&typing_extensions_to_typing, "typing")
|
||||||
|
{
|
||||||
|
replacements.push(replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"mypy_extensions" => {
|
||||||
|
let mut mypy_extensions_to_typing = vec![];
|
||||||
|
if self.version >= PythonVersion::Py37 {
|
||||||
|
mypy_extensions_to_typing.extend(MYPY_EXTENSIONS_TO_TYPING_37);
|
||||||
|
}
|
||||||
|
if self.version >= PythonVersion::Py38 {
|
||||||
|
mypy_extensions_to_typing.extend(MYPY_EXTENSIONS_TO_TYPING_38);
|
||||||
|
}
|
||||||
|
if let Some(replacement) = self.try_replace(&mypy_extensions_to_typing, "typing") {
|
||||||
|
replacements.push(replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"typing" => {
|
||||||
|
// `typing` to `collections.abc`
|
||||||
|
let mut typing_to_collections_abc = vec![];
|
||||||
|
if self.version >= PythonVersion::Py39 {
|
||||||
|
typing_to_collections_abc.extend(TYPING_TO_COLLECTIONS_ABC_39);
|
||||||
|
}
|
||||||
|
if self.version >= PythonVersion::Py310 {
|
||||||
|
typing_to_collections_abc.extend(TYPING_TO_COLLECTIONS_ABC_310);
|
||||||
|
}
|
||||||
|
if let Some(replacement) =
|
||||||
|
self.try_replace(&typing_to_collections_abc, "collections.abc")
|
||||||
|
{
|
||||||
|
replacements.push(replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// `typing` to `re`
|
||||||
|
let mut typing_to_re = vec![];
|
||||||
|
if self.version >= PythonVersion::Py39 {
|
||||||
|
typing_to_re.extend(TYPING_TO_RE_39);
|
||||||
|
}
|
||||||
|
if let Some(replacement) = self.try_replace(&typing_to_re, "re") {
|
||||||
|
replacements.push(replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"typing.re" if self.version >= PythonVersion::Py39 => {
|
||||||
|
if let Some(replacement) = self.try_replace(TYPING_RE_TO_RE_39, "re") {
|
||||||
|
replacements.push(replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
replacements
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_replace(&'a self, candidates: &[&str], target: &'a str) -> Option<Replacement<'a>> {
|
||||||
|
let (matched_names, unmatched_names) = self.partition_imports(candidates);
|
||||||
|
|
||||||
|
// If we have no matched names, we don't need to do anything.
|
||||||
|
if matched_names.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if unmatched_names.is_empty() {
|
||||||
|
let matched = ImportReplacer::format_import_from(&matched_names, target);
|
||||||
|
Some(Replacement {
|
||||||
|
module: target,
|
||||||
|
members: matched_names,
|
||||||
|
content: Some(matched),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let indentation = indentation(self.locator, self.stmt);
|
||||||
|
|
||||||
|
// If we have matched _and_ unmatched names, but the import is not on its own line, we
|
||||||
|
// can't add a statement after it. For example, if we have `if True: import foo`, we can't
|
||||||
|
// add a statement to the next line.
|
||||||
|
let Some(indentation) = indentation else {
|
||||||
|
return Some(Replacement {
|
||||||
|
module: target,
|
||||||
|
members: matched_names,
|
||||||
|
content: None,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let matched = ImportReplacer::format_import_from(&matched_names, target);
|
||||||
|
let unmatched = fixes::remove_import_members(
|
||||||
|
self.locator
|
||||||
|
.slice_source_code_range(&Range::from_located(self.stmt)),
|
||||||
|
&matched_names
|
||||||
|
.iter()
|
||||||
|
.map(|name| name.name.as_str())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(Replacement {
|
||||||
|
module: target,
|
||||||
|
members: matched_names,
|
||||||
|
content: Some(format!(
|
||||||
|
"{unmatched}{}{}{matched}",
|
||||||
|
self.stylist.line_ending().as_str(),
|
||||||
|
indentation,
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Partitions imports into matched and unmatched names.
|
||||||
|
fn partition_imports(&self, candidates: &[&str]) -> (Vec<&AliasData>, Vec<&AliasData>) {
|
||||||
|
let mut matched_names = vec![];
|
||||||
|
let mut unmatched_names = vec![];
|
||||||
|
for name in self.members {
|
||||||
|
if candidates.contains(&name.name.as_str()) {
|
||||||
|
matched_names.push(name);
|
||||||
|
} else {
|
||||||
|
unmatched_names.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(matched_names, unmatched_names)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a list of names and a module into an `import from`-style import.
|
||||||
|
fn format_import_from(names: &[&AliasData], module: &str) -> String {
|
||||||
|
// Construct the whitespace strings.
|
||||||
|
// Generate the formatted names.
|
||||||
|
let full_names: String = names
|
||||||
|
.iter()
|
||||||
|
.map(|name| match &name.asname {
|
||||||
|
Some(asname) => format!("{} as {asname}", name.name),
|
||||||
|
None => format!("{}", name.name),
|
||||||
|
})
|
||||||
|
.join(", ");
|
||||||
|
format!("from {module} import {full_names}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// UP035
|
||||||
|
pub fn import_replacements(
|
||||||
|
checker: &mut Checker,
|
||||||
|
stmt: &Stmt,
|
||||||
|
names: &[Alias],
|
||||||
|
module: Option<&str>,
|
||||||
|
level: Option<&usize>,
|
||||||
|
) {
|
||||||
|
// Avoid relative and star imports.
|
||||||
|
if level.map_or(false, |level| *level > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if names.first().map_or(false, |name| name.node.name == "*") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(module) = module else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !RELEVANT_MODULES.contains(&module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let members: Vec<AliasData> = names.iter().map(|alias| alias.node.clone()).collect();
|
||||||
|
let fixer = ImportReplacer::new(
|
||||||
|
stmt,
|
||||||
|
module,
|
||||||
|
&members,
|
||||||
|
checker.locator,
|
||||||
|
checker.stylist,
|
||||||
|
checker.settings.target_version,
|
||||||
|
);
|
||||||
|
|
||||||
|
for replacement in fixer.replacements() {
|
||||||
|
let mut diagnostic = Diagnostic::new(
|
||||||
|
ImportReplacements {
|
||||||
|
module: replacement.module.to_string(),
|
||||||
|
members: replacement
|
||||||
|
.members
|
||||||
|
.iter()
|
||||||
|
.map(|name| name.name.to_string())
|
||||||
|
.collect(),
|
||||||
|
fixable: replacement.content.is_some(),
|
||||||
|
},
|
||||||
|
Range::from_located(stmt),
|
||||||
|
);
|
||||||
|
if checker.patch(&Rule::ImportReplacements) {
|
||||||
|
if let Some(content) = replacement.content {
|
||||||
|
diagnostic.amend(Fix::replacement(
|
||||||
|
content,
|
||||||
|
stmt.location,
|
||||||
|
stmt.end_location.unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checker.diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ pub(crate) use extraneous_parentheses::extraneous_parentheses;
|
||||||
pub(crate) use f_strings::f_strings;
|
pub(crate) use f_strings::f_strings;
|
||||||
pub(crate) use format_literals::format_literals;
|
pub(crate) use format_literals::format_literals;
|
||||||
pub(crate) use functools_cache::functools_cache;
|
pub(crate) use functools_cache::functools_cache;
|
||||||
|
pub(crate) use import_replacements::{import_replacements, ImportReplacements};
|
||||||
pub(crate) use lru_cache_without_parameters::lru_cache_without_parameters;
|
pub(crate) use lru_cache_without_parameters::lru_cache_without_parameters;
|
||||||
pub(crate) use native_literals::native_literals;
|
pub(crate) use native_literals::native_literals;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
@ -48,6 +49,7 @@ mod extraneous_parentheses;
|
||||||
mod f_strings;
|
mod f_strings;
|
||||||
mod format_literals;
|
mod format_literals;
|
||||||
mod functools_cache;
|
mod functools_cache;
|
||||||
|
mod import_replacements;
|
||||||
mod lru_cache_without_parameters;
|
mod lru_cache_without_parameters;
|
||||||
mod native_literals;
|
mod native_literals;
|
||||||
mod open_alias;
|
mod open_alias;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,390 @@
|
||||||
|
---
|
||||||
|
source: src/rules/pyupgrade/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Mapping
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 2
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 2
|
||||||
|
column: 31
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- from collections.abc import Mapping
|
||||||
|
location:
|
||||||
|
row: 2
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 2
|
||||||
|
column: 31
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Mapping
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 4
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 4
|
||||||
|
column: 38
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- from collections.abc import Mapping as MAP
|
||||||
|
location:
|
||||||
|
row: 4
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 4
|
||||||
|
column: 38
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Mapping
|
||||||
|
- Sequence
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 6
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 6
|
||||||
|
column: 41
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- "from collections.abc import Mapping, Sequence"
|
||||||
|
location:
|
||||||
|
row: 6
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 6
|
||||||
|
column: 41
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Mapping
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 8
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 8
|
||||||
|
column: 40
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- from collections import Counter
|
||||||
|
- from collections.abc import Mapping
|
||||||
|
location:
|
||||||
|
row: 8
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 8
|
||||||
|
column: 40
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Mapping
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 10
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 10
|
||||||
|
column: 42
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- from collections import (Counter)
|
||||||
|
- from collections.abc import Mapping
|
||||||
|
location:
|
||||||
|
row: 10
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 10
|
||||||
|
column: 42
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Mapping
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 12
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 13
|
||||||
|
column: 33
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- from collections import (Counter)
|
||||||
|
- from collections.abc import Mapping
|
||||||
|
location:
|
||||||
|
row: 12
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 13
|
||||||
|
column: 33
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Mapping
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 15
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 16
|
||||||
|
column: 32
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- from collections import Counter
|
||||||
|
- from collections.abc import Mapping
|
||||||
|
location:
|
||||||
|
row: 15
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 16
|
||||||
|
column: 32
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Mapping
|
||||||
|
- Sequence
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 18
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 18
|
||||||
|
column: 50
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- from collections import Counter
|
||||||
|
- "from collections.abc import Mapping, Sequence"
|
||||||
|
location:
|
||||||
|
row: 18
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 18
|
||||||
|
column: 50
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Mapping
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 20
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 20
|
||||||
|
column: 51
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- from collections import Counter
|
||||||
|
- from collections.abc import Mapping as mapping
|
||||||
|
location:
|
||||||
|
row: 20
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 20
|
||||||
|
column: 51
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Mapping
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 23
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 23
|
||||||
|
column: 44
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- from collections import Counter
|
||||||
|
- " from collections.abc import Mapping"
|
||||||
|
location:
|
||||||
|
row: 23
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 23
|
||||||
|
column: 44
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Mapping
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 28
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 28
|
||||||
|
column: 44
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- from collections import Counter
|
||||||
|
- " from collections.abc import Mapping"
|
||||||
|
location:
|
||||||
|
row: 28
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 28
|
||||||
|
column: 44
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Mapping
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 30
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 30
|
||||||
|
column: 40
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- from collections.abc import Mapping
|
||||||
|
location:
|
||||||
|
row: 30
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 30
|
||||||
|
column: 40
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Mapping
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 33
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 33
|
||||||
|
column: 40
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- from collections import Counter
|
||||||
|
- from collections.abc import Mapping
|
||||||
|
location:
|
||||||
|
row: 33
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 33
|
||||||
|
column: 40
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Mapping
|
||||||
|
- Callable
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 37
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 42
|
||||||
|
column: 5
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- from collections import (
|
||||||
|
- " Bad,"
|
||||||
|
- " Good,"
|
||||||
|
- " )"
|
||||||
|
- " from collections.abc import Mapping, Callable"
|
||||||
|
location:
|
||||||
|
row: 37
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 42
|
||||||
|
column: 5
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Callable
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 44
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 44
|
||||||
|
column: 49
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- "from typing import Match, Pattern, List"
|
||||||
|
- from collections.abc import Callable
|
||||||
|
location:
|
||||||
|
row: 44
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 44
|
||||||
|
column: 49
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: re
|
||||||
|
members:
|
||||||
|
- Match
|
||||||
|
- Pattern
|
||||||
|
fixable: true
|
||||||
|
location:
|
||||||
|
row: 44
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 44
|
||||||
|
column: 49
|
||||||
|
fix:
|
||||||
|
content:
|
||||||
|
- "from typing import Callable, List"
|
||||||
|
- "from re import Match, Pattern"
|
||||||
|
location:
|
||||||
|
row: 44
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 44
|
||||||
|
column: 49
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
ImportReplacements:
|
||||||
|
module: collections.abc
|
||||||
|
members:
|
||||||
|
- Mapping
|
||||||
|
fixable: false
|
||||||
|
location:
|
||||||
|
row: 46
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 47
|
||||||
|
column: 21
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
|
||||||
Loading…
Reference in New Issue