Flag deprecated (but renamed) imports in UP035 (#3448)

This commit is contained in:
Charlie Marsh 2023-03-11 01:06:32 -05:00 committed by GitHub
parent 841bcf1cdd
commit 1e081cf9a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 250 additions and 121 deletions

View File

@ -41,7 +41,7 @@ if True:
Good,
)
from typing import Callable, Match, Pattern, List, OrderedDict
from typing import Callable, Match, Pattern, List, OrderedDict, AbstractSet
if True: from collections import (
Mapping, Counter)

View File

@ -1138,8 +1138,8 @@ where
if self.settings.rules.enabled(&Rule::RewriteCElementTree) {
pyupgrade::rules::replace_c_element_tree(self, stmt);
}
if self.settings.rules.enabled(&Rule::ImportReplacements) {
pyupgrade::rules::import_replacements(
if self.settings.rules.enabled(&Rule::DeprecatedImport) {
pyupgrade::rules::deprecated_import(
self,
stmt,
names,

View File

@ -373,7 +373,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pyupgrade, "032") => Rule::FString,
(Pyupgrade, "033") => Rule::FunctoolsCache,
(Pyupgrade, "034") => Rule::ExtraneousParentheses,
(Pyupgrade, "035") => Rule::ImportReplacements,
(Pyupgrade, "035") => Rule::DeprecatedImport,
(Pyupgrade, "036") => Rule::OutdatedVersionBlock,
(Pyupgrade, "037") => Rule::QuotedAnnotation,
(Pyupgrade, "038") => Rule::IsinstanceWithTuple,

View File

@ -339,7 +339,7 @@ ruff_macros::register_rules!(
rules::pyupgrade::rules::FString,
rules::pyupgrade::rules::FunctoolsCache,
rules::pyupgrade::rules::ExtraneousParentheses,
rules::pyupgrade::rules::ImportReplacements,
rules::pyupgrade::rules::DeprecatedImport,
rules::pyupgrade::rules::OutdatedVersionBlock,
rules::pyupgrade::rules::QuotedAnnotation,
rules::pyupgrade::rules::IsinstanceWithTuple,

View File

@ -59,7 +59,7 @@ mod tests {
#[test_case(Rule::FString, Path::new("UP032.py"); "UP032")]
#[test_case(Rule::FunctoolsCache, Path::new("UP033.py"); "UP033")]
#[test_case(Rule::ExtraneousParentheses, Path::new("UP034.py"); "UP034")]
#[test_case(Rule::ImportReplacements, Path::new("UP035.py"); "UP035")]
#[test_case(Rule::DeprecatedImport, Path::new("UP035.py"); "UP035")]
#[test_case(Rule::OutdatedVersionBlock, Path::new("UP036_0.py"); "UP036_0")]
#[test_case(Rule::OutdatedVersionBlock, Path::new("UP036_1.py"); "UP036_1")]
#[test_case(Rule::OutdatedVersionBlock, Path::new("UP036_2.py"); "UP036_2")]

View File

@ -12,28 +12,68 @@ use crate::registry::Rule;
use crate::rules::pyupgrade::fixes;
use crate::settings::types::PythonVersion;
#[violation]
pub struct ImportReplacements {
pub module: String,
pub members: Vec<String>,
pub fixable: bool,
/// An import was moved and renamed as part of a deprecation.
/// For example, `typing.AbstractSet` was moved to `collections.abc.Set`.
#[derive(Debug, PartialEq, Eq)]
struct WithRename {
module: String,
member: String,
target: String,
}
impl Violation for ImportReplacements {
/// A series of imports from the same module were moved to another module,
/// but retain their original names.
#[derive(Debug, PartialEq, Eq)]
struct WithoutRename {
target: String,
members: Vec<String>,
fixable: bool,
}
#[derive(Debug, PartialEq, Eq)]
enum Deprecation {
WithRename(WithRename),
WithoutRename(WithoutRename),
}
#[violation]
pub struct DeprecatedImport {
deprecation: Deprecation,
}
impl Violation for DeprecatedImport {
const AUTOFIX: Option<AutofixKind> = Some(AutofixKind::new(Availability::Sometimes));
#[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}")
match &self.deprecation {
Deprecation::WithoutRename(WithoutRename {
members, target, ..
}) => {
let names = members.iter().map(|name| format!("`{name}`")).join(", ");
format!("Import from `{target}` instead: {names}")
}
Deprecation::WithRename(WithRename {
module,
member,
target,
}) => {
format!("`{module}.{member}` is deprecated, use `{target}` instead")
}
}
}
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
self.fixable
.then_some(|ImportReplacements { module, .. }| format!("Import from `{module}`"))
if let Deprecation::WithoutRename(WithoutRename { fixable, .. }) = self.deprecation {
fixable.then_some(|DeprecatedImport { deprecation }| {
let Deprecation::WithoutRename(WithoutRename { target, .. }) = deprecation else {
unreachable!();
};
format!("Import from `{target}`")
})
} else {
None
}
}
}
@ -47,7 +87,7 @@ const RELEVANT_MODULES: &[&str] = &[
"typing.re",
];
// Members of `collections` that have been moved to `collections.abc`.
// Members of `collections` that were moved to `collections.abc`.
const COLLECTIONS_TO_ABC: &[&str] = &[
"AsyncGenerator",
"AsyncIterable",
@ -76,10 +116,10 @@ const COLLECTIONS_TO_ABC: &[&str] = &[
"ValuesView",
];
// Members of `pipes` that have been moved to `shlex`.
// Members of `pipes` that were moved to `shlex`.
const PIPES_TO_SHLEX: &[&str] = &["quote"];
// Members of `typing_extensions` that have been moved to `typing`.
// Members of `typing_extensions` that were moved to `typing`.
const TYPING_EXTENSIONS_TO_TYPING: &[&str] = &[
"AsyncIterable",
"AsyncIterator",
@ -96,10 +136,10 @@ const TYPING_EXTENSIONS_TO_TYPING: &[&str] = &[
// Python 3.7+
// Members of `mypy_extensions` that have been moved to `typing`.
// Members of `mypy_extensions` that were moved to `typing`.
const MYPY_EXTENSIONS_TO_TYPING_37: &[&str] = &["NoReturn"];
// Members of `typing_extensions` that have been moved to `typing`.
// Members of `typing_extensions` that were moved to `typing`.
const TYPING_EXTENSIONS_TO_TYPING_37: &[&str] = &[
"AsyncContextManager",
"AsyncGenerator",
@ -111,10 +151,10 @@ const TYPING_EXTENSIONS_TO_TYPING_37: &[&str] = &[
// Python 3.8+
// Members of `mypy_extensions` that have been moved to `typing`.
// Members of `mypy_extensions` that were moved to `typing`.
const MYPY_EXTENSIONS_TO_TYPING_38: &[&str] = &["TypedDict"];
// Members of `typing_extensions` that have been moved to `typing`.
// Members of `typing_extensions` that were moved to `typing`.
const TYPING_EXTENSIONS_TO_TYPING_38: &[&str] = &[
"Final",
"Literal",
@ -126,7 +166,7 @@ const TYPING_EXTENSIONS_TO_TYPING_38: &[&str] = &[
// Python 3.9+
// Members of `typing` that have been moved to `collections.abc`.
// Members of `typing` that were moved to `collections.abc`.
const TYPING_TO_COLLECTIONS_ABC_39: &[&str] = &[
"AsyncGenerator",
"AsyncIterable",
@ -153,24 +193,41 @@ const TYPING_TO_COLLECTIONS_ABC_39: &[&str] = &[
"ValuesView",
];
// Members of `typing` that have been moved to `collections`.
// Members of `typing` that were moved to `collections`.
const TYPING_TO_COLLECTIONS_39: &[&str] = &["ChainMap", "Counter", "OrderedDict"];
// Members of `typing` that have been moved to `typing.re`.
// Members of `typing` that were moved to `typing.re`.
const TYPING_TO_RE_39: &[&str] = &["Match", "Pattern"];
// Members of `typing.re` that have been moved to `re`.
// Members of `typing.re` that were moved to `re`.
const TYPING_RE_TO_RE_39: &[&str] = &["Match", "Pattern"];
// Members of `typing_extensions` that have been moved to `typing`.
// Members of `typing_extensions` that were moved to `typing`.
const TYPING_EXTENSIONS_TO_TYPING_39: &[&str] = &["Annotated", "get_type_hints"];
// Members of `typing` that were moved _and_ renamed (and thus cannot be
// automatically fixed).
const TYPING_TO_RENAME_PY39: &[(&str, &str)] = &[
(
"AsyncContextManager",
"contextlib.AbstractAsyncContextManager",
),
("AbstractSet", "collections.abc.Set"),
("Tuple", "tuple"),
("List", "list"),
("FrozenSet", "frozenset"),
("Dict", "dict"),
("Set", "set"),
("Deque", "collections.deque"),
("DefaultDict", "collections.defaultdict"),
];
// Python 3.10+
// Members of `typing` that have been moved to `collections.abc`.
// Members of `typing` that were moved to `collections.abc`.
const TYPING_TO_COLLECTIONS_ABC_310: &[&str] = &["Callable"];
// Members of `typing_extensions` that have been moved to `typing`.
// Members of `typing_extensions` that were moved to `typing`.
const TYPING_EXTENSIONS_TO_TYPING_310: &[&str] = &[
"Concatenate",
"ParamSpecArgs",
@ -184,7 +241,7 @@ const TYPING_EXTENSIONS_TO_TYPING_310: &[&str] = &[
// Python 3.11+
// Members of `typing_extensions` that have been moved to `typing`.
// Members of `typing_extensions` that were moved to `typing`.
const TYPING_EXTENSIONS_TO_TYPING_311: &[&str] = &[
"Any",
"LiteralString",
@ -205,12 +262,6 @@ const TYPING_EXTENSIONS_TO_TYPING_311: &[&str] = &[
"reveal_type",
];
struct Replacement<'a> {
module: &'a str,
members: Vec<&'a AliasData>,
content: Option<String>,
}
struct ImportReplacer<'a> {
stmt: &'a Stmt,
module: &'a str,
@ -239,17 +290,43 @@ impl<'a> ImportReplacer<'a> {
}
}
fn replacements(&self) -> Vec<Replacement> {
let mut replacements = vec![];
/// Return a list of deprecated imports whose members were renamed.
fn with_renames(&self) -> Vec<WithRename> {
let mut operations = vec![];
if self.module == "typing" {
if self.version >= PythonVersion::Py39 {
for member in self.members {
if let Some(target) = TYPING_TO_RENAME_PY39.iter().find_map(|(name, target)| {
if member.name == *name {
Some(*target)
} else {
None
}
}) {
operations.push(WithRename {
module: "typing".to_string(),
member: member.name.to_string(),
target: target.to_string(),
});
}
}
}
}
operations
}
/// Return a list of deprecated imports whose members were moved, but not renamed.
fn without_renames(&self) -> Vec<(WithoutRename, Option<String>)> {
let mut operations = vec![];
match self.module {
"collections" => {
if let Some(replacement) = self.try_replace(COLLECTIONS_TO_ABC, "collections.abc") {
replacements.push(replacement);
if let Some(operation) = self.try_replace(COLLECTIONS_TO_ABC, "collections.abc") {
operations.push(operation);
}
}
"pipes" => {
if let Some(replacement) = self.try_replace(PIPES_TO_SHLEX, "shlex") {
replacements.push(replacement);
if let Some(operation) = self.try_replace(PIPES_TO_SHLEX, "shlex") {
operations.push(operation);
}
}
"typing_extensions" => {
@ -269,9 +346,8 @@ impl<'a> ImportReplacer<'a> {
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);
if let Some(operation) = self.try_replace(&typing_extensions_to_typing, "typing") {
operations.push(operation);
}
}
"mypy_extensions" => {
@ -282,8 +358,8 @@ impl<'a> ImportReplacer<'a> {
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);
if let Some(operation) = self.try_replace(&mypy_extensions_to_typing, "typing") {
operations.push(operation);
}
}
"typing" => {
@ -295,10 +371,10 @@ impl<'a> ImportReplacer<'a> {
if self.version >= PythonVersion::Py310 {
typing_to_collections_abc.extend(TYPING_TO_COLLECTIONS_ABC_310);
}
if let Some(replacement) =
if let Some(operation) =
self.try_replace(&typing_to_collections_abc, "collections.abc")
{
replacements.push(replacement);
operations.push(operation);
}
// `typing` to `collections`
@ -306,8 +382,8 @@ impl<'a> ImportReplacer<'a> {
if self.version >= PythonVersion::Py39 {
typing_to_collections.extend(TYPING_TO_COLLECTIONS_39);
}
if let Some(replacement) = self.try_replace(&typing_to_collections, "collections") {
replacements.push(replacement);
if let Some(operation) = self.try_replace(&typing_to_collections, "collections") {
operations.push(operation);
}
// `typing` to `re`
@ -315,21 +391,29 @@ impl<'a> ImportReplacer<'a> {
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);
if let Some(operation) = self.try_replace(&typing_to_re, "re") {
operations.push(operation);
}
}
"typing.re" if self.version >= PythonVersion::Py39 => {
if let Some(replacement) = self.try_replace(TYPING_RE_TO_RE_39, "re") {
replacements.push(replacement);
if let Some(operation) = self.try_replace(TYPING_RE_TO_RE_39, "re") {
operations.push(operation);
}
}
_ => {}
}
replacements
operations
}
fn try_replace(&'a self, candidates: &[&str], target: &'a str) -> Option<Replacement<'a>> {
fn try_replace(
&'a self,
candidates: &[&str],
target: &'a str,
) -> Option<(WithoutRename, Option<String>)> {
if candidates.is_empty() {
return None;
}
let (matched_names, unmatched_names) = self.partition_imports(candidates);
// If we have no matched names, we don't need to do anything.
@ -339,11 +423,16 @@ impl<'a> ImportReplacer<'a> {
if unmatched_names.is_empty() {
let matched = ImportReplacer::format_import_from(&matched_names, target);
Some(Replacement {
module: target,
members: matched_names,
content: Some(matched),
})
let operation = WithoutRename {
target: target.to_string(),
members: matched_names
.iter()
.map(|name| name.name.to_string())
.collect(),
fixable: true,
};
let fix = Some(matched);
Some((operation, fix))
} else {
let indentation = indentation(self.locator, self.stmt);
@ -351,11 +440,16 @@ impl<'a> ImportReplacer<'a> {
// 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 operation = WithoutRename {
target: target.to_string(),
members: matched_names
.iter()
.map(|name| name.name.to_string())
.collect(),
fixable: false,
};
let fix = None;
return Some((operation, fix));
};
let matched = ImportReplacer::format_import_from(&matched_names, target);
@ -367,15 +461,20 @@ impl<'a> ImportReplacer<'a> {
.collect::<Vec<_>>(),
);
Some(Replacement {
module: target,
members: matched_names,
content: Some(format!(
"{unmatched}{}{}{matched}",
self.stylist.line_ending().as_str(),
indentation,
)),
})
let operation = WithoutRename {
target: target.to_string(),
members: matched_names
.iter()
.map(|name| name.name.to_string())
.collect(),
fixable: true,
};
let fix = Some(format!(
"{unmatched}{}{}{matched}",
self.stylist.line_ending().as_str(),
indentation,
));
Some((operation, fix))
}
}
@ -410,7 +509,7 @@ impl<'a> ImportReplacer<'a> {
}
/// UP035
pub fn import_replacements(
pub fn deprecated_import(
checker: &mut Checker,
stmt: &Stmt,
names: &[Alias],
@ -442,21 +541,15 @@ pub fn import_replacements(
checker.settings.target_version,
);
for replacement in fixer.replacements() {
for (operation, fix) in fixer.without_renames() {
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(),
DeprecatedImport {
deprecation: Deprecation::WithoutRename(operation),
},
Range::from(stmt),
);
if checker.patch(&Rule::ImportReplacements) {
if let Some(content) = replacement.content {
if checker.patch(&Rule::DeprecatedImport) {
if let Some(content) = fix {
diagnostic.amend(Fix::replacement(
content,
stmt.location,
@ -466,4 +559,14 @@ pub fn import_replacements(
}
checker.diagnostics.push(diagnostic);
}
for operation in fixer.with_renames() {
let diagnostic = Diagnostic::new(
DeprecatedImport {
deprecation: Deprecation::WithRename(operation),
},
Range::from(stmt),
);
checker.diagnostics.push(diagnostic);
}
}

View File

@ -5,12 +5,12 @@ pub(crate) use convert_typed_dict_functional_to_class::{
convert_typed_dict_functional_to_class, ConvertTypedDictFunctionalToClass,
};
pub(crate) use datetime_utc_alias::{datetime_utc_alias, DatetimeTimezoneUTC};
pub(crate) use deprecated_import::{deprecated_import, DeprecatedImport};
pub(crate) use deprecated_unittest_alias::{deprecated_unittest_alias, DeprecatedUnittestAlias};
pub(crate) use extraneous_parentheses::{extraneous_parentheses, ExtraneousParentheses};
pub(crate) use f_strings::{f_strings, FString};
pub(crate) use format_literals::{format_literals, FormatLiterals};
pub(crate) use functools_cache::{functools_cache, FunctoolsCache};
pub(crate) use import_replacements::{import_replacements, ImportReplacements};
pub(crate) use lru_cache_without_parameters::{
lru_cache_without_parameters, LRUCacheWithoutParameters,
};
@ -46,12 +46,12 @@ pub(crate) use useless_object_inheritance::{useless_object_inheritance, UselessO
mod convert_named_tuple_functional_to_class;
mod convert_typed_dict_functional_to_class;
mod datetime_utc_alias;
mod deprecated_import;
mod deprecated_unittest_alias;
mod extraneous_parentheses;
mod f_strings;
mod format_literals;
mod functools_cache;
mod import_replacements;
mod lru_cache_without_parameters;
mod native_literals;
mod open_alias;

View File

@ -3,7 +3,7 @@ source: crates/ruff/src/rules/pyupgrade/mod.rs
expression: diagnostics
---
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Mapping`"
suggestion: "Import from `collections.abc`"
fixable: true
@ -23,7 +23,7 @@ expression: diagnostics
column: 31
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Mapping`"
suggestion: "Import from `collections.abc`"
fixable: true
@ -43,7 +43,7 @@ expression: diagnostics
column: 38
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Mapping`, `Sequence`"
suggestion: "Import from `collections.abc`"
fixable: true
@ -63,7 +63,7 @@ expression: diagnostics
column: 41
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Mapping`"
suggestion: "Import from `collections.abc`"
fixable: true
@ -83,7 +83,7 @@ expression: diagnostics
column: 40
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Mapping`"
suggestion: "Import from `collections.abc`"
fixable: true
@ -103,7 +103,7 @@ expression: diagnostics
column: 42
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Mapping`"
suggestion: "Import from `collections.abc`"
fixable: true
@ -123,7 +123,7 @@ expression: diagnostics
column: 33
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Mapping`"
suggestion: "Import from `collections.abc`"
fixable: true
@ -143,7 +143,7 @@ expression: diagnostics
column: 32
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Mapping`, `Sequence`"
suggestion: "Import from `collections.abc`"
fixable: true
@ -163,7 +163,7 @@ expression: diagnostics
column: 50
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Mapping`"
suggestion: "Import from `collections.abc`"
fixable: true
@ -183,7 +183,7 @@ expression: diagnostics
column: 51
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Mapping`"
suggestion: "Import from `collections.abc`"
fixable: true
@ -203,7 +203,7 @@ expression: diagnostics
column: 44
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Mapping`"
suggestion: "Import from `collections.abc`"
fixable: true
@ -223,7 +223,7 @@ expression: diagnostics
column: 44
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Mapping`"
suggestion: "Import from `collections.abc`"
fixable: true
@ -243,7 +243,7 @@ expression: diagnostics
column: 40
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Mapping`"
suggestion: "Import from `collections.abc`"
fixable: true
@ -263,7 +263,7 @@ expression: diagnostics
column: 40
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Mapping`, `Callable`"
suggestion: "Import from `collections.abc`"
fixable: true
@ -283,7 +283,7 @@ expression: diagnostics
column: 5
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Callable`"
suggestion: "Import from `collections.abc`"
fixable: true
@ -292,18 +292,18 @@ expression: diagnostics
column: 0
end_location:
row: 44
column: 62
column: 75
fix:
content: "from typing import Match, Pattern, List, OrderedDict\nfrom collections.abc import Callable"
content: "from typing import Match, Pattern, List, OrderedDict, AbstractSet\nfrom collections.abc import Callable"
location:
row: 44
column: 0
end_location:
row: 44
column: 62
column: 75
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `collections` instead: `OrderedDict`"
suggestion: "Import from `collections`"
fixable: true
@ -312,18 +312,18 @@ expression: diagnostics
column: 0
end_location:
row: 44
column: 62
column: 75
fix:
content: "from typing import Callable, Match, Pattern, List\nfrom collections import OrderedDict"
content: "from typing import Callable, Match, Pattern, List, AbstractSet\nfrom collections import OrderedDict"
location:
row: 44
column: 0
end_location:
row: 44
column: 62
column: 75
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "Import from `re` instead: `Match`, `Pattern`"
suggestion: "Import from `re`"
fixable: true
@ -332,18 +332,44 @@ expression: diagnostics
column: 0
end_location:
row: 44
column: 62
column: 75
fix:
content: "from typing import Callable, List, OrderedDict\nfrom re import Match, Pattern"
content: "from typing import Callable, List, OrderedDict, AbstractSet\nfrom re import Match, Pattern"
location:
row: 44
column: 0
end_location:
row: 44
column: 62
column: 75
parent: ~
- kind:
name: ImportReplacements
name: DeprecatedImport
body: "`typing.List` is deprecated, use `list` instead"
suggestion: ~
fixable: false
location:
row: 44
column: 0
end_location:
row: 44
column: 75
fix: ~
parent: ~
- kind:
name: DeprecatedImport
body: "`typing.AbstractSet` is deprecated, use `collections.abc.Set` instead"
suggestion: ~
fixable: false
location:
row: 44
column: 0
end_location:
row: 44
column: 75
fix: ~
parent: ~
- kind:
name: DeprecatedImport
body: "Import from `collections.abc` instead: `Mapping`"
suggestion: ~
fixable: false