diff --git a/README.md b/README.md index 421d08c815..d13463be07 100644 --- a/README.md +++ b/README.md @@ -1344,7 +1344,7 @@ For more, see [Pylint](https://pypi.org/project/pylint/) on PyPI. | ---- | ---- | ------- | --- | | PLR0133 | constant-comparison | Two constants compared in a comparison, consider replacing `{left_constant} {op} {right_constant}` | | | PLR0206 | property-with-parameters | Cannot have defined parameters for properties | | -| PLR0402 | consider-using-from-import | Use `from {module} import {name}` in lieu of alias | | +| PLR0402 | consider-using-from-import | Use `from {module} import {name}` in lieu of alias | 🛠 | | PLR0913 | too-many-args | Too many arguments to function call ({c_args}/{max_args}) | | | PLR0915 | too-many-statements | Too many statements ({statements}/{max_statements}) | | | PLR1701 | consider-merging-isinstance | Merge these isinstance calls: `isinstance({obj}, ({types}))` | | diff --git a/resources/test/fixtures/pylint/import_aliasing.py b/resources/test/fixtures/pylint/import_aliasing.py index c74d63fe0c..266c5785f7 100644 --- a/resources/test/fixtures/pylint/import_aliasing.py +++ b/resources/test/fixtures/pylint/import_aliasing.py @@ -9,6 +9,7 @@ from collections import OrderedDict as o_dict import os.path as path # [consider-using-from-import] import os.path as p import foo.bar.foobar as foobar # [consider-using-from-import] +import foo.bar.foobar as foobar, sys # [consider-using-from-import] import os import os as OS from sys import version diff --git a/src/checkers/ast.rs b/src/checkers/ast.rs index 71aa6a1bc6..4e9466992f 100644 --- a/src/checkers/ast.rs +++ b/src/checkers/ast.rs @@ -922,7 +922,7 @@ where pylint::rules::useless_import_alias(self, alias); } if self.settings.rules.enabled(&Rule::ConsiderUsingFromImport) { - pylint::rules::use_from_import(self, alias); + pylint::rules::use_from_import(self, stmt, alias, names); } if let Some(asname) = &alias.node.asname { diff --git a/src/rules/pylint/rules/use_from_import.rs b/src/rules/pylint/rules/use_from_import.rs index 2f9cb13392..473c50af82 100644 --- a/src/rules/pylint/rules/use_from_import.rs +++ b/src/rules/pylint/rules/use_from_import.rs @@ -1,27 +1,45 @@ use rustpython_ast::Alias; +use rustpython_parser::ast::Stmt; + +use ruff_macros::derive_message_formats; use crate::ast::types::Range; use crate::checkers::ast::Checker; -use crate::define_violation; +use crate::fix::Fix; use crate::registry::Diagnostic; -use crate::violation::Violation; -use ruff_macros::derive_message_formats; +use crate::violation::{Availability, Violation}; +use crate::{define_violation, AutofixKind}; define_violation!( pub struct ConsiderUsingFromImport { pub module: String, pub name: String, + pub fixable: bool, } ); impl Violation for ConsiderUsingFromImport { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Always)); + #[derive_message_formats] fn message(&self) -> String { - let ConsiderUsingFromImport { module, name } = self; + let ConsiderUsingFromImport { module, name, .. } = self; format!("Use `from {module} import {name}` in lieu of alias") } + + fn autofix_title_formatter(&self) -> Option String> { + let ConsiderUsingFromImport { fixable, .. } = self; + if *fixable { + Some(|ConsiderUsingFromImport { module, name, .. }| { + format!("Replace with `from {module} import {name}`") + }) + } else { + None + } + } } + /// PLR0402 -pub fn use_from_import(checker: &mut Checker, alias: &Alias) { +pub fn use_from_import(checker: &mut Checker, stmt: &Stmt, alias: &Alias, names: &[Alias]) { let Some(asname) = &alias.node.asname else { return; }; @@ -31,11 +49,22 @@ pub fn use_from_import(checker: &mut Checker, alias: &Alias) { if name != asname { return; } - checker.diagnostics.push(Diagnostic::new( + + let fixable = names.len() == 1; + let mut diagnostic = Diagnostic::new( ConsiderUsingFromImport { module: module.to_string(), name: name.to_string(), + fixable, }, Range::from_located(alias), - )); + ); + if fixable && checker.patch(diagnostic.kind.rule()) { + diagnostic.amend(Fix::replacement( + format!("from {module} import {asname}"), + stmt.location, + stmt.end_location.unwrap(), + )); + } + checker.diagnostics.push(diagnostic); } diff --git a/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC0414_import_aliasing.py.snap b/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC0414_import_aliasing.py.snap index b7ac2c628e..949685ed27 100644 --- a/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC0414_import_aliasing.py.snap +++ b/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLC0414_import_aliasing.py.snap @@ -41,109 +41,109 @@ expression: diagnostics - kind: UselessImportAlias: ~ location: - row: 15 + row: 16 column: 14 end_location: - row: 15 + row: 16 column: 24 fix: content: - bar location: - row: 15 + row: 16 column: 14 end_location: - row: 15 + row: 16 column: 24 parent: ~ - kind: UselessImportAlias: ~ location: - row: 18 + row: 19 column: 18 end_location: - row: 18 + row: 19 column: 28 fix: content: - bar location: - row: 18 + row: 19 column: 18 end_location: - row: 18 + row: 19 column: 28 parent: ~ - kind: UselessImportAlias: ~ location: - row: 19 + row: 20 column: 22 end_location: - row: 19 + row: 20 column: 38 fix: content: - foobar location: - row: 19 + row: 20 column: 22 end_location: - row: 19 + row: 20 column: 38 parent: ~ - kind: UselessImportAlias: ~ location: - row: 21 + row: 22 column: 14 end_location: - row: 21 + row: 22 column: 24 fix: content: - foo location: - row: 21 + row: 22 column: 14 end_location: - row: 21 + row: 22 column: 24 parent: ~ - kind: UselessImportAlias: ~ location: - row: 22 + row: 23 column: 26 end_location: - row: 22 + row: 23 column: 38 fix: content: - foo2 location: - row: 22 + row: 23 column: 26 end_location: - row: 22 + row: 23 column: 38 parent: ~ - kind: UselessImportAlias: ~ location: - row: 24 + row: 25 column: 20 end_location: - row: 24 + row: 25 column: 36 fix: content: - foobar location: - row: 24 + row: 25 column: 20 end_location: - row: 24 + row: 25 column: 36 parent: ~ diff --git a/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0402_import_aliasing.py.snap b/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0402_import_aliasing.py.snap index 0741f5b403..56ac8e378e 100644 --- a/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0402_import_aliasing.py.snap +++ b/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLR0402_import_aliasing.py.snap @@ -6,24 +6,55 @@ expression: diagnostics ConsiderUsingFromImport: module: os name: path + fixable: true location: row: 9 column: 7 end_location: row: 9 column: 22 - fix: ~ + fix: + content: + - from os import path + location: + row: 9 + column: 0 + end_location: + row: 9 + column: 22 parent: ~ - kind: ConsiderUsingFromImport: module: foo.bar name: foobar + fixable: true location: row: 11 column: 7 end_location: row: 11 column: 31 + fix: + content: + - from foo.bar import foobar + location: + row: 11 + column: 0 + end_location: + row: 11 + column: 31 + parent: ~ +- kind: + ConsiderUsingFromImport: + module: foo.bar + name: foobar + fixable: false + location: + row: 12 + column: 7 + end_location: + row: 12 + column: 31 fix: ~ parent: ~