Compare commits
9 Commits
pythonplus
...
charlie/di
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9fc63331a | ||
|
|
df3b95a73d | ||
|
|
a4432102f1 | ||
|
|
3295ccfbc4 | ||
|
|
7b315b84e2 | ||
|
|
bb2adb3017 | ||
|
|
5536d2befc | ||
|
|
b4824979b0 | ||
|
|
8a2f58065e |
@@ -35,6 +35,7 @@ use ruff_python_stdlib::builtins::{BUILTINS, MAGIC_GLOBALS};
|
|||||||
use ruff_python_stdlib::path::is_python_stub_file;
|
use ruff_python_stdlib::path::is_python_stub_file;
|
||||||
|
|
||||||
use crate::checkers::ast::deferred::Deferred;
|
use crate::checkers::ast::deferred::Deferred;
|
||||||
|
use crate::checkers::ast::traits::RegisteredRule;
|
||||||
use crate::docstrings::extraction::ExtractionTarget;
|
use crate::docstrings::extraction::ExtractionTarget;
|
||||||
use crate::docstrings::Docstring;
|
use crate::docstrings::Docstring;
|
||||||
use crate::fs::relativize_path;
|
use crate::fs::relativize_path;
|
||||||
@@ -56,6 +57,7 @@ use crate::settings::{flags, Settings};
|
|||||||
use crate::{autofix, docstrings, noqa, warn_user};
|
use crate::{autofix, docstrings, noqa, warn_user};
|
||||||
|
|
||||||
mod deferred;
|
mod deferred;
|
||||||
|
pub(crate) mod traits;
|
||||||
|
|
||||||
pub(crate) struct Checker<'a> {
|
pub(crate) struct Checker<'a> {
|
||||||
// Settings, static metadata, etc.
|
// Settings, static metadata, etc.
|
||||||
@@ -77,6 +79,16 @@ pub(crate) struct Checker<'a> {
|
|||||||
deferred: Deferred<'a>,
|
deferred: Deferred<'a>,
|
||||||
// Check-specific state.
|
// Check-specific state.
|
||||||
pub(crate) flake8_bugbear_seen: Vec<&'a Expr>,
|
pub(crate) flake8_bugbear_seen: Vec<&'a Expr>,
|
||||||
|
// Dispatchers
|
||||||
|
call_rules: Vec<RegisteredRule<ast::ExprCall>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct RuleContext<'a> {
|
||||||
|
pub(crate) settings: &'a Settings,
|
||||||
|
pub(crate) locator: &'a Locator<'a>,
|
||||||
|
pub(crate) stylist: &'a Stylist<'a>,
|
||||||
|
pub(crate) indexer: &'a Indexer,
|
||||||
|
pub(crate) ctx: &'a Context<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Checker<'a> {
|
impl<'a> Checker<'a> {
|
||||||
@@ -110,10 +122,63 @@ impl<'a> Checker<'a> {
|
|||||||
diagnostics: Vec::default(),
|
diagnostics: Vec::default(),
|
||||||
deletions: FxHashSet::default(),
|
deletions: FxHashSet::default(),
|
||||||
flake8_bugbear_seen: Vec::default(),
|
flake8_bugbear_seen: Vec::default(),
|
||||||
|
call_rules: [
|
||||||
|
RegisteredRule::new::<flake8_django::rules::DjangoLocalsInRenderFunction>(),
|
||||||
|
RegisteredRule::new::<pyupgrade::rules::DeprecatedUnittestAlias>(),
|
||||||
|
RegisteredRule::new::<pyupgrade::rules::SuperCallWithParameters>(),
|
||||||
|
RegisteredRule::new::<pyupgrade::rules::UnnecessaryEncodeUTF8>(),
|
||||||
|
RegisteredRule::new::<pyupgrade::rules::RedundantOpenModes>(),
|
||||||
|
RegisteredRule::new::<pyupgrade::rules::NativeLiterals>(),
|
||||||
|
RegisteredRule::new::<pyupgrade::rules::OpenAlias>(),
|
||||||
|
RegisteredRule::new::<pyupgrade::rules::ReplaceUniversalNewlines>(),
|
||||||
|
RegisteredRule::new::<pyupgrade::rules::ReplaceStdoutStderr>(),
|
||||||
|
RegisteredRule::new::<pyupgrade::rules::OSErrorAlias>(),
|
||||||
|
RegisteredRule::new::<pyupgrade::rules::NonPEP604Isinstance>(),
|
||||||
|
RegisteredRule::new::<pyupgrade::rules::TypeOfPrimitive>(),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.filter(|rule| rule.enabled(settings))
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(charlie): Remove these methods from `Checker`, use the immutable `RuleContext` everywhere.
|
||||||
|
impl<'a> RuleContext<'a> {
|
||||||
|
/// Return `true` if a patch should be generated under the given autofix
|
||||||
|
/// `Mode`.
|
||||||
|
pub(crate) fn patch(&self, code: Rule) -> bool {
|
||||||
|
self.settings.rules.should_fix(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a [`Generator`] to generate source code based on the current AST state.
|
||||||
|
pub(crate) fn generator(&self) -> Generator {
|
||||||
|
fn quote_style(context: &Context, locator: &Locator, indexer: &Indexer) -> Option<Quote> {
|
||||||
|
if !context.in_f_string() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the quote character used to start the containing f-string.
|
||||||
|
let expr = context.expr()?;
|
||||||
|
let string_range = indexer.f_string_range(expr.start())?;
|
||||||
|
let trailing_quote = trailing_quote(locator.slice(string_range))?;
|
||||||
|
|
||||||
|
// Invert the quote character, if it's a single quote.
|
||||||
|
match *trailing_quote {
|
||||||
|
"'" => Some(Quote::Double),
|
||||||
|
"\"" => Some(Quote::Single),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Generator::new(
|
||||||
|
self.stylist.indentation(),
|
||||||
|
quote_style(self.ctx, self.locator, self.indexer).unwrap_or(self.stylist.quote()),
|
||||||
|
self.stylist.line_ending(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Checker<'a> {
|
impl<'a> Checker<'a> {
|
||||||
/// Return `true` if a patch should be generated under the given autofix
|
/// Return `true` if a patch should be generated under the given autofix
|
||||||
/// `Mode`.
|
/// `Mode`.
|
||||||
@@ -2567,12 +2632,26 @@ where
|
|||||||
}
|
}
|
||||||
pandas_vet::rules::attr(self, attr, value, expr);
|
pandas_vet::rules::attr(self, attr, value, expr);
|
||||||
}
|
}
|
||||||
Expr::Call(ast::ExprCall {
|
Expr::Call(call) => {
|
||||||
func,
|
let context = RuleContext {
|
||||||
args,
|
settings: self.settings,
|
||||||
keywords,
|
locator: self.locator,
|
||||||
range: _,
|
stylist: self.stylist,
|
||||||
}) => {
|
indexer: self.indexer,
|
||||||
|
ctx: &self.ctx,
|
||||||
|
};
|
||||||
|
for rule in &self.call_rules {
|
||||||
|
rule.run(&mut self.diagnostics, &context, call);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destructure for the rest of the rules, for now.
|
||||||
|
let ast::ExprCall {
|
||||||
|
func,
|
||||||
|
args,
|
||||||
|
keywords,
|
||||||
|
range: _,
|
||||||
|
} = call;
|
||||||
|
|
||||||
if self.settings.rules.any_enabled(&[
|
if self.settings.rules.any_enabled(&[
|
||||||
// pyflakes
|
// pyflakes
|
||||||
Rule::StringDotFormatInvalidFormat,
|
Rule::StringDotFormatInvalidFormat,
|
||||||
@@ -2672,43 +2751,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pyupgrade
|
|
||||||
if self.settings.rules.enabled(Rule::TypeOfPrimitive) {
|
|
||||||
pyupgrade::rules::type_of_primitive(self, expr, func, args);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::DeprecatedUnittestAlias) {
|
|
||||||
pyupgrade::rules::deprecated_unittest_alias(self, func);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::SuperCallWithParameters) {
|
|
||||||
pyupgrade::rules::super_call_with_parameters(self, expr, func, args);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::UnnecessaryEncodeUTF8) {
|
|
||||||
pyupgrade::rules::unnecessary_encode_utf8(self, expr, func, args, keywords);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::RedundantOpenModes) {
|
|
||||||
pyupgrade::rules::redundant_open_modes(self, expr);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::NativeLiterals) {
|
|
||||||
pyupgrade::rules::native_literals(self, expr, func, args, keywords);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::OpenAlias) {
|
|
||||||
pyupgrade::rules::open_alias(self, expr, func);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::ReplaceUniversalNewlines) {
|
|
||||||
pyupgrade::rules::replace_universal_newlines(self, func, keywords);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::ReplaceStdoutStderr) {
|
|
||||||
pyupgrade::rules::replace_stdout_stderr(self, expr, func, args, keywords);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::OSErrorAlias) {
|
|
||||||
pyupgrade::rules::os_error_alias_call(self, func);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::NonPEP604Isinstance)
|
|
||||||
&& self.settings.target_version >= PythonVersion::Py310
|
|
||||||
{
|
|
||||||
pyupgrade::rules::use_pep604_isinstance(self, expr, func, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// flake8-async
|
// flake8-async
|
||||||
if self
|
if self
|
||||||
.settings
|
.settings
|
||||||
@@ -3294,15 +3336,6 @@ where
|
|||||||
{
|
{
|
||||||
pylint::rules::logging_call(self, func, args, keywords);
|
pylint::rules::logging_call(self, func, args, keywords);
|
||||||
}
|
}
|
||||||
|
|
||||||
// flake8-django
|
|
||||||
if self
|
|
||||||
.settings
|
|
||||||
.rules
|
|
||||||
.enabled(Rule::DjangoLocalsInRenderFunction)
|
|
||||||
{
|
|
||||||
flake8_django::rules::locals_in_render_function(self, func, args, keywords);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Expr::Dict(ast::ExprDict {
|
Expr::Dict(ast::ExprDict {
|
||||||
keys,
|
keys,
|
||||||
|
|||||||
42
crates/ruff/src/checkers/ast/traits.rs
Normal file
42
crates/ruff/src/checkers/ast/traits.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use ruff_diagnostics::Diagnostic;
|
||||||
|
|
||||||
|
use crate::checkers::ast::RuleContext;
|
||||||
|
use crate::registry::Rule;
|
||||||
|
use crate::settings::Settings;
|
||||||
|
|
||||||
|
/// Trait for a lint rule that can be run on an AST node of type `T`.
|
||||||
|
pub(crate) trait Analyzer<T>: Sized {
|
||||||
|
/// The [`Rule`] that this analyzer implements.
|
||||||
|
fn rule() -> Rule;
|
||||||
|
|
||||||
|
/// Run the analyzer on the given node.
|
||||||
|
fn run(diagnostics: &mut Vec<Diagnostic>, checker: &RuleContext, node: &T);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal representation of a single [`Rule`] that can be run on an AST node of type `T`.
|
||||||
|
pub(super) struct RegisteredRule<T> {
|
||||||
|
rule: Rule,
|
||||||
|
run: Executor<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> RegisteredRule<T> {
|
||||||
|
pub(super) fn new<R: Analyzer<T> + 'static>() -> Self {
|
||||||
|
Self {
|
||||||
|
rule: R::rule(),
|
||||||
|
run: R::run,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn enabled(&self, settings: &Settings) -> bool {
|
||||||
|
settings.rules.enabled(self.rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn run(&self, diagnostics: &mut Vec<Diagnostic>, context: &RuleContext, node: &T) {
|
||||||
|
(self.run)(diagnostics, context, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executor for an [`Analyzer`] as a generic function pointer.
|
||||||
|
type Executor<T> = fn(diagnostics: &mut Vec<Diagnostic>, checker: &RuleContext, node: &T);
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
use rustpython_parser::ast::{self, Expr, Keyword, Ranged};
|
use rustpython_parser::ast::{self, Expr, Ranged};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::traits::Analyzer;
|
||||||
|
use crate::checkers::ast::RuleContext;
|
||||||
|
use crate::registry::Rule;
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for the use of `locals()` in `render` functions.
|
/// Checks for the use of `locals()` in `render` functions.
|
||||||
@@ -43,11 +45,25 @@ impl Violation for DjangoLocalsInRenderFunction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// DJ003
|
/// DJ003
|
||||||
|
impl Analyzer<ast::ExprCall> for DjangoLocalsInRenderFunction {
|
||||||
|
fn rule() -> Rule {
|
||||||
|
Rule::DjangoLocalsInRenderFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(diagnostics: &mut Vec<Diagnostic>, context: &RuleContext, node: &ast::ExprCall) {
|
||||||
|
locals_in_render_function(diagnostics, context, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn locals_in_render_function(
|
pub(crate) fn locals_in_render_function(
|
||||||
checker: &mut Checker,
|
diagnostics: &mut Vec<Diagnostic>,
|
||||||
func: &Expr,
|
checker: &RuleContext,
|
||||||
args: &[Expr],
|
ast::ExprCall {
|
||||||
keywords: &[Keyword],
|
func,
|
||||||
|
args,
|
||||||
|
keywords,
|
||||||
|
..
|
||||||
|
}: &ast::ExprCall,
|
||||||
) {
|
) {
|
||||||
if !checker
|
if !checker
|
||||||
.ctx
|
.ctx
|
||||||
@@ -76,13 +92,13 @@ pub(crate) fn locals_in_render_function(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
diagnostics.push(Diagnostic::new(
|
||||||
DjangoLocalsInRenderFunction,
|
DjangoLocalsInRenderFunction,
|
||||||
locals.range(),
|
locals.range(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_locals_call(checker: &Checker, expr: &Expr) -> bool {
|
fn is_locals_call(checker: &RuleContext, expr: &Expr) -> bool {
|
||||||
let Expr::Call(ast::ExprCall { func, .. }) = expr else {
|
let Expr::Call(ast::ExprCall { func, .. }) = expr else {
|
||||||
return false
|
return false
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
pub(crate) use all_with_model_form::{all_with_model_form, DjangoAllWithModelForm};
|
pub(crate) use all_with_model_form::{all_with_model_form, DjangoAllWithModelForm};
|
||||||
pub(crate) use exclude_with_model_form::{exclude_with_model_form, DjangoExcludeWithModelForm};
|
pub(crate) use exclude_with_model_form::{exclude_with_model_form, DjangoExcludeWithModelForm};
|
||||||
pub(crate) use locals_in_render_function::{
|
pub(crate) use locals_in_render_function::DjangoLocalsInRenderFunction;
|
||||||
locals_in_render_function, DjangoLocalsInRenderFunction,
|
|
||||||
};
|
|
||||||
pub(crate) use model_without_dunder_str::{model_without_dunder_str, DjangoModelWithoutDunderStr};
|
pub(crate) use model_without_dunder_str::{model_without_dunder_str, DjangoModelWithoutDunderStr};
|
||||||
pub(crate) use non_leading_receiver_decorator::{
|
pub(crate) use non_leading_receiver_decorator::{
|
||||||
non_leading_receiver_decorator, DjangoNonLeadingReceiverDecorator,
|
non_leading_receiver_decorator, DjangoNonLeadingReceiverDecorator,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use libcst_native::{
|
|||||||
SmallStatement, Statement, Suite,
|
SmallStatement, Statement, Suite,
|
||||||
};
|
};
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
use rustpython_parser::ast::{Expr, Ranged};
|
|
||||||
use rustpython_parser::{lexer, Mode, Tok};
|
use rustpython_parser::{lexer, Mode, Tok};
|
||||||
|
|
||||||
use ruff_diagnostics::Edit;
|
use ruff_diagnostics::Edit;
|
||||||
@@ -51,11 +50,10 @@ pub(crate) fn adjust_indentation(
|
|||||||
|
|
||||||
/// Generate a fix to remove arguments from a `super` call.
|
/// Generate a fix to remove arguments from a `super` call.
|
||||||
pub(crate) fn remove_super_arguments(
|
pub(crate) fn remove_super_arguments(
|
||||||
|
range: TextRange,
|
||||||
locator: &Locator,
|
locator: &Locator,
|
||||||
stylist: &Stylist,
|
stylist: &Stylist,
|
||||||
expr: &Expr,
|
|
||||||
) -> Option<Edit> {
|
) -> Option<Edit> {
|
||||||
let range = expr.range();
|
|
||||||
let contents = locator.slice(range);
|
let contents = locator.slice(range);
|
||||||
|
|
||||||
let mut tree = libcst_native::parse_module(contents, None).ok()?;
|
let mut tree = libcst_native::parse_module(contents, None).ok()?;
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use rustpython_parser::ast::{self, Expr, Ranged};
|
use rustpython_parser::ast::{self, Expr};
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::traits::Analyzer;
|
||||||
use crate::registry::AsRule;
|
use crate::checkers::ast::RuleContext;
|
||||||
|
use crate::registry::{AsRule, Rule};
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct DeprecatedUnittestAlias {
|
pub struct DeprecatedUnittestAlias {
|
||||||
@@ -27,6 +28,16 @@ impl AlwaysAutofixableViolation for DeprecatedUnittestAlias {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Analyzer<ast::ExprCall> for DeprecatedUnittestAlias {
|
||||||
|
fn rule() -> Rule {
|
||||||
|
Rule::DeprecatedUnittestAlias
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(diagnostics: &mut Vec<Diagnostic>, checker: &RuleContext, node: &ast::ExprCall) {
|
||||||
|
deprecated_unittest_alias(diagnostics, checker, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static DEPRECATED_ALIASES: Lazy<FxHashMap<&'static str, &'static str>> = Lazy::new(|| {
|
static DEPRECATED_ALIASES: Lazy<FxHashMap<&'static str, &'static str>> = Lazy::new(|| {
|
||||||
FxHashMap::from_iter([
|
FxHashMap::from_iter([
|
||||||
("failUnlessEqual", "assertEqual"),
|
("failUnlessEqual", "assertEqual"),
|
||||||
@@ -48,11 +59,12 @@ static DEPRECATED_ALIASES: Lazy<FxHashMap<&'static str, &'static str>> = Lazy::n
|
|||||||
});
|
});
|
||||||
|
|
||||||
/// UP005
|
/// UP005
|
||||||
pub(crate) fn deprecated_unittest_alias(checker: &mut Checker, expr: &Expr) {
|
pub(crate) fn deprecated_unittest_alias(
|
||||||
let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = expr else {
|
diagnostics: &mut Vec<Diagnostic>,
|
||||||
return;
|
checker: &RuleContext,
|
||||||
};
|
ast::ExprCall { func, .. }: &ast::ExprCall,
|
||||||
let Some(target) = DEPRECATED_ALIASES.get(attr.as_str()) else {
|
) {
|
||||||
|
let Expr::Attribute(ast::ExprAttribute { value, attr, range, .. }) = func.as_ref() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
|
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
|
||||||
@@ -61,19 +73,22 @@ pub(crate) fn deprecated_unittest_alias(checker: &mut Checker, expr: &Expr) {
|
|||||||
if id != "self" {
|
if id != "self" {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let Some(target) = DEPRECATED_ALIASES.get(attr.as_str()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let mut diagnostic = Diagnostic::new(
|
let mut diagnostic = Diagnostic::new(
|
||||||
DeprecatedUnittestAlias {
|
DeprecatedUnittestAlias {
|
||||||
alias: attr.to_string(),
|
alias: attr.to_string(),
|
||||||
target: (*target).to_string(),
|
target: (*target).to_string(),
|
||||||
},
|
},
|
||||||
expr.range(),
|
*range,
|
||||||
);
|
);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||||
format!("self.{target}"),
|
format!("self.{target}"),
|
||||||
expr.range(),
|
*range,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use ruff_text_size::TextRange;
|
use ruff_text_size::TextRange;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use rustpython_format::{
|
use rustpython_format::{
|
||||||
FieldName, FieldNamePart, FieldType, FormatPart, FormatString, FromTemplate,
|
FieldName, FieldNamePart, FieldType, FormatPart, FormatString, FromTemplate,
|
||||||
};
|
};
|
||||||
use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged};
|
use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged};
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ pub(crate) use deprecated_import::{deprecated_import, DeprecatedImport};
|
|||||||
pub(crate) use deprecated_mock_import::{
|
pub(crate) use deprecated_mock_import::{
|
||||||
deprecated_mock_attribute, deprecated_mock_import, DeprecatedMockImport,
|
deprecated_mock_attribute, deprecated_mock_import, DeprecatedMockImport,
|
||||||
};
|
};
|
||||||
pub(crate) use deprecated_unittest_alias::{deprecated_unittest_alias, DeprecatedUnittestAlias};
|
pub(crate) use deprecated_unittest_alias::DeprecatedUnittestAlias;
|
||||||
pub(crate) use extraneous_parentheses::{extraneous_parentheses, ExtraneousParentheses};
|
pub(crate) use extraneous_parentheses::{extraneous_parentheses, ExtraneousParentheses};
|
||||||
pub(crate) use f_strings::{f_strings, FString};
|
pub(crate) use f_strings::{f_strings, FString};
|
||||||
pub(crate) use format_literals::{format_literals, FormatLiterals};
|
pub(crate) use format_literals::{format_literals, FormatLiterals};
|
||||||
@@ -20,31 +20,29 @@ pub(crate) use lru_cache_with_maxsize_none::{
|
|||||||
pub(crate) use lru_cache_without_parameters::{
|
pub(crate) use lru_cache_without_parameters::{
|
||||||
lru_cache_without_parameters, LRUCacheWithoutParameters,
|
lru_cache_without_parameters, LRUCacheWithoutParameters,
|
||||||
};
|
};
|
||||||
pub(crate) use native_literals::{native_literals, NativeLiterals};
|
pub(crate) use native_literals::NativeLiterals;
|
||||||
pub(crate) use open_alias::{open_alias, OpenAlias};
|
pub(crate) use open_alias::OpenAlias;
|
||||||
pub(crate) use os_error_alias::{
|
pub(crate) use os_error_alias::{os_error_alias_handlers, os_error_alias_raise, OSErrorAlias};
|
||||||
os_error_alias_call, os_error_alias_handlers, os_error_alias_raise, OSErrorAlias,
|
|
||||||
};
|
|
||||||
pub(crate) use outdated_version_block::{outdated_version_block, OutdatedVersionBlock};
|
pub(crate) use outdated_version_block::{outdated_version_block, OutdatedVersionBlock};
|
||||||
pub(crate) use printf_string_formatting::{printf_string_formatting, PrintfStringFormatting};
|
pub(crate) use printf_string_formatting::{printf_string_formatting, PrintfStringFormatting};
|
||||||
pub(crate) use quoted_annotation::{quoted_annotation, QuotedAnnotation};
|
pub(crate) use quoted_annotation::{quoted_annotation, QuotedAnnotation};
|
||||||
pub(crate) use redundant_open_modes::{redundant_open_modes, RedundantOpenModes};
|
pub(crate) use redundant_open_modes::RedundantOpenModes;
|
||||||
pub(crate) use replace_stdout_stderr::{replace_stdout_stderr, ReplaceStdoutStderr};
|
pub(crate) use replace_stdout_stderr::ReplaceStdoutStderr;
|
||||||
pub(crate) use replace_universal_newlines::{replace_universal_newlines, ReplaceUniversalNewlines};
|
pub(crate) use replace_universal_newlines::ReplaceUniversalNewlines;
|
||||||
pub(crate) use super_call_with_parameters::{super_call_with_parameters, SuperCallWithParameters};
|
pub(crate) use super_call_with_parameters::SuperCallWithParameters;
|
||||||
pub(crate) use type_of_primitive::{type_of_primitive, TypeOfPrimitive};
|
pub(crate) use type_of_primitive::TypeOfPrimitive;
|
||||||
pub(crate) use typing_text_str_alias::{typing_text_str_alias, TypingTextStrAlias};
|
pub(crate) use typing_text_str_alias::{typing_text_str_alias, TypingTextStrAlias};
|
||||||
pub(crate) use unicode_kind_prefix::{unicode_kind_prefix, UnicodeKindPrefix};
|
pub(crate) use unicode_kind_prefix::{unicode_kind_prefix, UnicodeKindPrefix};
|
||||||
pub(crate) use unnecessary_builtin_import::{unnecessary_builtin_import, UnnecessaryBuiltinImport};
|
pub(crate) use unnecessary_builtin_import::{unnecessary_builtin_import, UnnecessaryBuiltinImport};
|
||||||
pub(crate) use unnecessary_coding_comment::{unnecessary_coding_comment, UTF8EncodingDeclaration};
|
pub(crate) use unnecessary_coding_comment::{unnecessary_coding_comment, UTF8EncodingDeclaration};
|
||||||
pub(crate) use unnecessary_encode_utf8::{unnecessary_encode_utf8, UnnecessaryEncodeUTF8};
|
pub(crate) use unnecessary_encode_utf8::UnnecessaryEncodeUTF8;
|
||||||
pub(crate) use unnecessary_future_import::{unnecessary_future_import, UnnecessaryFutureImport};
|
pub(crate) use unnecessary_future_import::{unnecessary_future_import, UnnecessaryFutureImport};
|
||||||
pub(crate) use unpacked_list_comprehension::{
|
pub(crate) use unpacked_list_comprehension::{
|
||||||
unpacked_list_comprehension, UnpackedListComprehension,
|
unpacked_list_comprehension, UnpackedListComprehension,
|
||||||
};
|
};
|
||||||
pub(crate) use use_pep585_annotation::{use_pep585_annotation, NonPEP585Annotation};
|
pub(crate) use use_pep585_annotation::{use_pep585_annotation, NonPEP585Annotation};
|
||||||
pub(crate) use use_pep604_annotation::{use_pep604_annotation, NonPEP604Annotation};
|
pub(crate) use use_pep604_annotation::{use_pep604_annotation, NonPEP604Annotation};
|
||||||
pub(crate) use use_pep604_isinstance::{use_pep604_isinstance, NonPEP604Isinstance};
|
pub(crate) use use_pep604_isinstance::NonPEP604Isinstance;
|
||||||
pub(crate) use useless_metaclass_type::{useless_metaclass_type, UselessMetaclassType};
|
pub(crate) use useless_metaclass_type::{useless_metaclass_type, UselessMetaclassType};
|
||||||
pub(crate) use useless_object_inheritance::{useless_object_inheritance, UselessObjectInheritance};
|
pub(crate) use useless_object_inheritance::{useless_object_inheritance, UselessObjectInheritance};
|
||||||
pub(crate) use yield_in_for_loop::{yield_in_for_loop, YieldInForLoop};
|
pub(crate) use yield_in_for_loop::{yield_in_for_loop, YieldInForLoop};
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged};
|
use rustpython_parser::ast::{self, Constant, Expr, Ranged};
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
use ruff_python_ast::str::is_implicit_concatenation;
|
use ruff_python_ast::str::is_implicit_concatenation;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::traits::Analyzer;
|
||||||
use crate::registry::AsRule;
|
use crate::checkers::ast::RuleContext;
|
||||||
|
use crate::registry::{AsRule, Rule};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub(crate) enum LiteralType {
|
pub(crate) enum LiteralType {
|
||||||
@@ -43,15 +43,30 @@ impl AlwaysAutofixableViolation for NativeLiterals {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Analyzer<ast::ExprCall> for NativeLiterals {
|
||||||
|
fn rule() -> Rule {
|
||||||
|
Rule::NativeLiterals
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(diagnostics: &mut Vec<Diagnostic>, checker: &RuleContext, node: &ast::ExprCall) {
|
||||||
|
native_literals(diagnostics, checker, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// UP018
|
/// UP018
|
||||||
pub(crate) fn native_literals(
|
pub(crate) fn native_literals(
|
||||||
checker: &mut Checker,
|
diagnostics: &mut Vec<Diagnostic>,
|
||||||
expr: &Expr,
|
checker: &RuleContext,
|
||||||
func: &Expr,
|
ast::ExprCall {
|
||||||
args: &[Expr],
|
func,
|
||||||
keywords: &[Keyword],
|
args,
|
||||||
|
keywords,
|
||||||
|
range,
|
||||||
|
}: &ast::ExprCall,
|
||||||
) {
|
) {
|
||||||
let Expr::Name(ast::ExprName { id, .. }) = func else { return; };
|
let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
if !keywords.is_empty() || args.len() > 1 {
|
if !keywords.is_empty() || args.len() > 1 {
|
||||||
return;
|
return;
|
||||||
@@ -63,7 +78,7 @@ pub(crate) fn native_literals(
|
|||||||
LiteralType::Str
|
LiteralType::Str
|
||||||
} else {
|
} else {
|
||||||
LiteralType::Bytes
|
LiteralType::Bytes
|
||||||
}}, expr.range());
|
}}, *range);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
let constant = if id == "bytes" {
|
let constant = if id == "bytes" {
|
||||||
Constant::Bytes(vec![])
|
Constant::Bytes(vec![])
|
||||||
@@ -74,10 +89,10 @@ pub(crate) fn native_literals(
|
|||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||||
content,
|
content,
|
||||||
expr.range(),
|
*range,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -121,15 +136,15 @@ pub(crate) fn native_literals(
|
|||||||
LiteralType::Bytes
|
LiteralType::Bytes
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expr.range(),
|
*range,
|
||||||
);
|
);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||||
arg_code.to_string(),
|
arg_code.to_string(),
|
||||||
expr.range(),
|
*range,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use rustpython_parser::ast::{Expr, Ranged};
|
use rustpython_parser::ast::{self, Ranged};
|
||||||
|
|
||||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::traits::Analyzer;
|
||||||
use crate::registry::AsRule;
|
use crate::checkers::ast::RuleContext;
|
||||||
|
use crate::registry::{AsRule, Rule};
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct OpenAlias;
|
pub struct OpenAlias;
|
||||||
@@ -22,25 +23,39 @@ impl Violation for OpenAlias {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Analyzer<ast::ExprCall> for OpenAlias {
|
||||||
|
fn rule() -> Rule {
|
||||||
|
Rule::OpenAlias
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(diagnostics: &mut Vec<Diagnostic>, context: &RuleContext, node: &ast::ExprCall) {
|
||||||
|
open_alias(diagnostics, context, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// UP020
|
/// UP020
|
||||||
pub(crate) fn open_alias(checker: &mut Checker, expr: &Expr, func: &Expr) {
|
pub(crate) fn open_alias(
|
||||||
if checker
|
diagnostics: &mut Vec<Diagnostic>,
|
||||||
|
context: &RuleContext,
|
||||||
|
ast::ExprCall { func, range, .. }: &ast::ExprCall,
|
||||||
|
) {
|
||||||
|
if context
|
||||||
.ctx
|
.ctx
|
||||||
.resolve_call_path(func)
|
.resolve_call_path(func)
|
||||||
.map_or(false, |call_path| call_path.as_slice() == ["io", "open"])
|
.map_or(false, |call_path| call_path.as_slice() == ["io", "open"])
|
||||||
{
|
{
|
||||||
let fixable = checker
|
let fixable = context
|
||||||
.ctx
|
.ctx
|
||||||
.find_binding("open")
|
.find_binding("open")
|
||||||
.map_or(true, |binding| binding.kind.is_builtin());
|
.map_or(true, |binding| binding.kind.is_builtin());
|
||||||
let mut diagnostic = Diagnostic::new(OpenAlias, expr.range());
|
let mut diagnostic = Diagnostic::new(OpenAlias, *range);
|
||||||
if fixable && checker.patch(diagnostic.kind.rule()) {
|
if fixable && context.patch(diagnostic.kind.rule()) {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||||
"open".to_string(),
|
"open".to_string(),
|
||||||
func.range(),
|
func.range(),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ use rustpython_parser::ast::{self, Excepthandler, Expr, ExprContext, Ranged};
|
|||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::compose_call_path;
|
use ruff_python_ast::call_path::compose_call_path;
|
||||||
|
|
||||||
use ruff_python_semantic::context::Context;
|
use ruff_python_semantic::context::Context;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::traits::Analyzer;
|
||||||
use crate::registry::AsRule;
|
use crate::checkers::ast::{Checker, RuleContext};
|
||||||
|
use crate::registry::{AsRule, Rule};
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct OSErrorAlias {
|
pub struct OSErrorAlias {
|
||||||
@@ -30,6 +30,16 @@ impl AlwaysAutofixableViolation for OSErrorAlias {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Analyzer<ast::ExprCall> for OSErrorAlias {
|
||||||
|
fn rule() -> Rule {
|
||||||
|
Rule::OSErrorAlias
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(diagnostics: &mut Vec<Diagnostic>, checker: &RuleContext, node: &ast::ExprCall) {
|
||||||
|
os_error_alias_call(diagnostics, checker, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ALIASES: &[(&str, &str)] = &[
|
const ALIASES: &[(&str, &str)] = &[
|
||||||
("", "EnvironmentError"),
|
("", "EnvironmentError"),
|
||||||
("", "IOError"),
|
("", "IOError"),
|
||||||
@@ -73,6 +83,24 @@ fn atom_diagnostic(checker: &mut Checker, target: &Expr) {
|
|||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a [`Diagnostic`] for a single target, like an [`Expr::Name`].
|
||||||
|
fn immutable_atom_diagnostic(checker: &RuleContext, target: &Expr) -> Diagnostic {
|
||||||
|
let mut diagnostic = Diagnostic::new(
|
||||||
|
OSErrorAlias {
|
||||||
|
name: compose_call_path(target),
|
||||||
|
},
|
||||||
|
target.range(),
|
||||||
|
);
|
||||||
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||||
|
"OSError".to_string(),
|
||||||
|
target.range(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
diagnostic
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a [`Diagnostic`] for a tuple of expressions.
|
/// Create a [`Diagnostic`] for a tuple of expressions.
|
||||||
fn tuple_diagnostic(checker: &mut Checker, target: &Expr, aliases: &[&Expr]) {
|
fn tuple_diagnostic(checker: &mut Checker, target: &Expr, aliases: &[&Expr]) {
|
||||||
let mut diagnostic = Diagnostic::new(OSErrorAlias { name: None }, target.range());
|
let mut diagnostic = Diagnostic::new(OSErrorAlias { name: None }, target.range());
|
||||||
@@ -156,9 +184,13 @@ pub(crate) fn os_error_alias_handlers(checker: &mut Checker, handlers: &[Excepth
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// UP024
|
/// UP024
|
||||||
pub(crate) fn os_error_alias_call(checker: &mut Checker, func: &Expr) {
|
pub(crate) fn os_error_alias_call(
|
||||||
if is_alias(&checker.ctx, func) {
|
diagnostics: &mut Vec<Diagnostic>,
|
||||||
atom_diagnostic(checker, func);
|
checker: &RuleContext,
|
||||||
|
ast::ExprCall { func, .. }: &ast::ExprCall,
|
||||||
|
) {
|
||||||
|
if is_alias(checker.ctx, func) {
|
||||||
|
diagnostics.push(immutable_atom_diagnostic(checker, func));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use ruff_text_size::TextRange;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use ruff_text_size::TextRange;
|
||||||
use rustpython_format::cformat::{
|
use rustpython_format::cformat::{
|
||||||
CConversionFlags, CFormatPart, CFormatPrecision, CFormatQuantity, CFormatString,
|
CConversionFlags, CFormatPart, CFormatPrecision, CFormatQuantity, CFormatString,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
use ruff_text_size::TextRange;
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_text_size::TextRange;
|
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ use ruff_macros::{derive_message_formats, violation};
|
|||||||
use ruff_python_ast::helpers::find_keyword;
|
use ruff_python_ast::helpers::find_keyword;
|
||||||
use ruff_python_ast::source_code::Locator;
|
use ruff_python_ast::source_code::Locator;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::traits::Analyzer;
|
||||||
|
use crate::checkers::ast::RuleContext;
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
@@ -41,6 +42,16 @@ impl AlwaysAutofixableViolation for RedundantOpenModes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Analyzer<ast::ExprCall> for RedundantOpenModes {
|
||||||
|
fn rule() -> Rule {
|
||||||
|
Rule::RedundantOpenModes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(diagnostics: &mut Vec<Diagnostic>, checker: &RuleContext, node: &ast::ExprCall) {
|
||||||
|
redundant_open_modes(diagnostics, checker, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const OPEN_FUNC_NAME: &str = "open";
|
const OPEN_FUNC_NAME: &str = "open";
|
||||||
const MODE_KEYWORD_ARGUMENT: &str = "mode";
|
const MODE_KEYWORD_ARGUMENT: &str = "mode";
|
||||||
|
|
||||||
@@ -86,24 +97,24 @@ impl OpenMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_open(expr: &Expr) -> (Option<&Expr>, Vec<Keyword>) {
|
fn match_open(
|
||||||
if let Expr::Call(ast::ExprCall {
|
ast::ExprCall {
|
||||||
func,
|
func,
|
||||||
args,
|
args,
|
||||||
keywords,
|
keywords,
|
||||||
range: _,
|
range: _,
|
||||||
}) = expr
|
}: &ast::ExprCall,
|
||||||
{
|
) -> (Option<&Expr>, Vec<Keyword>) {
|
||||||
if matches!(func.as_ref(), Expr::Name(ast::ExprName {id, ..}) if id == OPEN_FUNC_NAME) {
|
if matches!(func.as_ref(), Expr::Name(ast::ExprName {id, ..}) if id == OPEN_FUNC_NAME) {
|
||||||
// Return the "open mode" parameter and keywords.
|
// Return the "open mode" parameter and keywords.
|
||||||
return (args.get(1), keywords.clone());
|
(args.get(1), keywords.clone())
|
||||||
}
|
} else {
|
||||||
|
(None, vec![])
|
||||||
}
|
}
|
||||||
(None, vec![])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_check(
|
fn create_check(
|
||||||
expr: &Expr,
|
expr: &ast::ExprCall,
|
||||||
mode_param: &Expr,
|
mode_param: &Expr,
|
||||||
replacement_value: Option<&str>,
|
replacement_value: Option<&str>,
|
||||||
locator: &Locator,
|
locator: &Locator,
|
||||||
@@ -130,7 +141,11 @@ fn create_check(
|
|||||||
diagnostic
|
diagnostic
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_remove_param_fix(locator: &Locator, expr: &Expr, mode_param: &Expr) -> Result<Edit> {
|
fn create_remove_param_fix(
|
||||||
|
locator: &Locator,
|
||||||
|
expr: &ast::ExprCall,
|
||||||
|
mode_param: &Expr,
|
||||||
|
) -> Result<Edit> {
|
||||||
let content = locator.slice(expr.range());
|
let content = locator.slice(expr.range());
|
||||||
// Find the last comma before mode_param and create a deletion fix
|
// Find the last comma before mode_param and create a deletion fix
|
||||||
// starting from the comma and ending after mode_param.
|
// starting from the comma and ending after mode_param.
|
||||||
@@ -171,7 +186,11 @@ fn create_remove_param_fix(locator: &Locator, expr: &Expr, mode_param: &Expr) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// UP015
|
/// UP015
|
||||||
pub(crate) fn redundant_open_modes(checker: &mut Checker, expr: &Expr) {
|
pub(crate) fn redundant_open_modes(
|
||||||
|
diagnostics: &mut Vec<Diagnostic>,
|
||||||
|
checker: &RuleContext,
|
||||||
|
expr: &ast::ExprCall,
|
||||||
|
) {
|
||||||
// If `open` has been rebound, skip this check entirely.
|
// If `open` has been rebound, skip this check entirely.
|
||||||
if !checker.ctx.is_builtin(OPEN_FUNC_NAME) {
|
if !checker.ctx.is_builtin(OPEN_FUNC_NAME) {
|
||||||
return;
|
return;
|
||||||
@@ -185,7 +204,7 @@ pub(crate) fn redundant_open_modes(checker: &mut Checker, expr: &Expr) {
|
|||||||
}) = &keyword.value
|
}) = &keyword.value
|
||||||
{
|
{
|
||||||
if let Ok(mode) = OpenMode::from_str(mode_param_value.as_str()) {
|
if let Ok(mode) = OpenMode::from_str(mode_param_value.as_str()) {
|
||||||
checker.diagnostics.push(create_check(
|
diagnostics.push(create_check(
|
||||||
expr,
|
expr,
|
||||||
&keyword.value,
|
&keyword.value,
|
||||||
mode.replacement_value(),
|
mode.replacement_value(),
|
||||||
@@ -202,7 +221,7 @@ pub(crate) fn redundant_open_modes(checker: &mut Checker, expr: &Expr) {
|
|||||||
}) = &mode_param
|
}) = &mode_param
|
||||||
{
|
{
|
||||||
if let Ok(mode) = OpenMode::from_str(mode_param_value.as_str()) {
|
if let Ok(mode) = OpenMode::from_str(mode_param_value.as_str()) {
|
||||||
checker.diagnostics.push(create_check(
|
diagnostics.push(create_check(
|
||||||
expr,
|
expr,
|
||||||
mode_param,
|
mode_param,
|
||||||
mode.replacement_value(),
|
mode.replacement_value(),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rustpython_parser::ast::{Expr, Keyword, Ranged};
|
use rustpython_parser::ast::{self, Expr, Keyword, Ranged};
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
@@ -7,8 +7,9 @@ use ruff_python_ast::helpers::find_keyword;
|
|||||||
use ruff_python_ast::source_code::Locator;
|
use ruff_python_ast::source_code::Locator;
|
||||||
|
|
||||||
use crate::autofix::actions::remove_argument;
|
use crate::autofix::actions::remove_argument;
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::traits::Analyzer;
|
||||||
use crate::registry::AsRule;
|
use crate::checkers::ast::RuleContext;
|
||||||
|
use crate::registry::{AsRule, Rule};
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct ReplaceStdoutStderr;
|
pub struct ReplaceStdoutStderr;
|
||||||
@@ -24,6 +25,16 @@ impl AlwaysAutofixableViolation for ReplaceStdoutStderr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Analyzer<ast::ExprCall> for ReplaceStdoutStderr {
|
||||||
|
fn rule() -> Rule {
|
||||||
|
Rule::ReplaceStdoutStderr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(diagnostics: &mut Vec<Diagnostic>, checker: &RuleContext, node: &ast::ExprCall) {
|
||||||
|
replace_stdout_stderr(diagnostics, checker, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate a [`Edit`] for a `stdout` and `stderr` [`Keyword`] pair.
|
/// Generate a [`Edit`] for a `stdout` and `stderr` [`Keyword`] pair.
|
||||||
fn generate_fix(
|
fn generate_fix(
|
||||||
locator: &Locator,
|
locator: &Locator,
|
||||||
@@ -54,11 +65,14 @@ fn generate_fix(
|
|||||||
|
|
||||||
/// UP022
|
/// UP022
|
||||||
pub(crate) fn replace_stdout_stderr(
|
pub(crate) fn replace_stdout_stderr(
|
||||||
checker: &mut Checker,
|
diagnostics: &mut Vec<Diagnostic>,
|
||||||
expr: &Expr,
|
checker: &RuleContext,
|
||||||
func: &Expr,
|
ast::ExprCall {
|
||||||
args: &[Expr],
|
func,
|
||||||
keywords: &[Keyword],
|
args,
|
||||||
|
keywords,
|
||||||
|
range,
|
||||||
|
}: &ast::ExprCall,
|
||||||
) {
|
) {
|
||||||
if checker
|
if checker
|
||||||
.ctx
|
.ctx
|
||||||
@@ -92,12 +106,12 @@ pub(crate) fn replace_stdout_stderr(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut diagnostic = Diagnostic::new(ReplaceStdoutStderr, expr.range());
|
let mut diagnostic = Diagnostic::new(ReplaceStdoutStderr, *range);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
diagnostic.try_set_fix(|| {
|
diagnostic.try_set_fix(|| {
|
||||||
generate_fix(checker.locator, func, args, keywords, stdout, stderr)
|
generate_fix(checker.locator, func, args, keywords, stdout, stderr)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use ruff_text_size::{TextLen, TextRange};
|
use ruff_text_size::{TextLen, TextRange};
|
||||||
use rustpython_parser::ast::{Expr, Keyword, Ranged};
|
use rustpython_parser::ast::{self, Ranged};
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::helpers::find_keyword;
|
use ruff_python_ast::helpers::find_keyword;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::traits::Analyzer;
|
||||||
use crate::registry::AsRule;
|
use crate::checkers::ast::RuleContext;
|
||||||
|
use crate::registry::{AsRule, Rule};
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct ReplaceUniversalNewlines;
|
pub struct ReplaceUniversalNewlines;
|
||||||
@@ -22,8 +23,22 @@ impl AlwaysAutofixableViolation for ReplaceUniversalNewlines {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Analyzer<ast::ExprCall> for ReplaceUniversalNewlines {
|
||||||
|
fn rule() -> Rule {
|
||||||
|
Rule::ReplaceUniversalNewlines
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(diagnostics: &mut Vec<Diagnostic>, checker: &RuleContext, node: &ast::ExprCall) {
|
||||||
|
replace_universal_newlines(diagnostics, checker, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// UP021
|
/// UP021
|
||||||
pub(crate) fn replace_universal_newlines(checker: &mut Checker, func: &Expr, kwargs: &[Keyword]) {
|
pub(crate) fn replace_universal_newlines(
|
||||||
|
diagnostics: &mut Vec<Diagnostic>,
|
||||||
|
checker: &RuleContext,
|
||||||
|
ast::ExprCall { func, keywords, .. }: &ast::ExprCall,
|
||||||
|
) {
|
||||||
if checker
|
if checker
|
||||||
.ctx
|
.ctx
|
||||||
.resolve_call_path(func)
|
.resolve_call_path(func)
|
||||||
@@ -31,8 +46,8 @@ pub(crate) fn replace_universal_newlines(checker: &mut Checker, func: &Expr, kwa
|
|||||||
call_path.as_slice() == ["subprocess", "run"]
|
call_path.as_slice() == ["subprocess", "run"]
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
let Some(kwarg) = find_keyword(kwargs, "universal_newlines") else { return; };
|
let Some(keyword) = find_keyword(keywords, "universal_newlines") else { return; };
|
||||||
let range = TextRange::at(kwarg.start(), "universal_newlines".text_len());
|
let range = TextRange::at(keyword.start(), "universal_newlines".text_len());
|
||||||
let mut diagnostic = Diagnostic::new(ReplaceUniversalNewlines, range);
|
let mut diagnostic = Diagnostic::new(ReplaceUniversalNewlines, range);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
@@ -41,6 +56,6 @@ pub(crate) fn replace_universal_newlines(checker: &mut Checker, func: &Expr, kwa
|
|||||||
range,
|
range,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use rustpython_parser::ast::{self, Arg, Expr, Ranged, Stmt};
|
use rustpython_parser::ast::{self, Arg, Expr, Stmt};
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_semantic::scope::ScopeKind;
|
use ruff_python_semantic::scope::ScopeKind;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::traits::Analyzer;
|
||||||
use crate::registry::AsRule;
|
use crate::checkers::ast::RuleContext;
|
||||||
|
use crate::registry::{AsRule, Rule};
|
||||||
use crate::rules::pyupgrade::fixes;
|
use crate::rules::pyupgrade::fixes;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
@@ -22,6 +23,16 @@ impl AlwaysAutofixableViolation for SuperCallWithParameters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Analyzer<ast::ExprCall> for SuperCallWithParameters {
|
||||||
|
fn rule() -> Rule {
|
||||||
|
Rule::SuperCallWithParameters
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(diagnostics: &mut Vec<Diagnostic>, checker: &RuleContext, node: &ast::ExprCall) {
|
||||||
|
super_call_with_parameters(diagnostics, checker, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `true` if a call is an argumented `super` invocation.
|
/// Returns `true` if a call is an argumented `super` invocation.
|
||||||
fn is_super_call_with_arguments(func: &Expr, args: &[Expr]) -> bool {
|
fn is_super_call_with_arguments(func: &Expr, args: &[Expr]) -> bool {
|
||||||
if let Expr::Name(ast::ExprName { id, .. }) = func {
|
if let Expr::Name(ast::ExprName { id, .. }) = func {
|
||||||
@@ -33,10 +44,11 @@ fn is_super_call_with_arguments(func: &Expr, args: &[Expr]) -> bool {
|
|||||||
|
|
||||||
/// UP008
|
/// UP008
|
||||||
pub(crate) fn super_call_with_parameters(
|
pub(crate) fn super_call_with_parameters(
|
||||||
checker: &mut Checker,
|
diagnostics: &mut Vec<Diagnostic>,
|
||||||
expr: &Expr,
|
checker: &RuleContext,
|
||||||
func: &Expr,
|
ast::ExprCall {
|
||||||
args: &[Expr],
|
func, args, range, ..
|
||||||
|
}: &ast::ExprCall,
|
||||||
) {
|
) {
|
||||||
// Only bother going through the super check at all if we're in a `super` call.
|
// Only bother going through the super check at all if we're in a `super` call.
|
||||||
// (We check this in `super_args` too, so this is just an optimization.)
|
// (We check this in `super_args` too, so this is just an optimization.)
|
||||||
@@ -55,7 +67,7 @@ pub(crate) fn super_call_with_parameters(
|
|||||||
// For a `super` invocation to be unnecessary, the first argument needs to match
|
// For a `super` invocation to be unnecessary, the first argument needs to match
|
||||||
// the enclosing class, and the second argument needs to match the first
|
// the enclosing class, and the second argument needs to match the first
|
||||||
// argument to the enclosing function.
|
// argument to the enclosing function.
|
||||||
let [first_arg, second_arg] = args else {
|
let [first_arg, second_arg] = args.as_slice() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,12 +109,13 @@ pub(crate) fn super_call_with_parameters(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut diagnostic = Diagnostic::new(SuperCallWithParameters, expr.range());
|
let mut diagnostic = Diagnostic::new(SuperCallWithParameters, *range);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
if let Some(edit) = fixes::remove_super_arguments(checker.locator, checker.stylist, expr) {
|
if let Some(edit) = fixes::remove_super_arguments(*range, checker.locator, checker.stylist)
|
||||||
|
{
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
diagnostic.set_fix(Fix::unspecified(edit));
|
diagnostic.set_fix(Fix::unspecified(edit));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use rustpython_parser::ast::{self, Expr, Ranged};
|
use rustpython_parser::ast::{self, Expr};
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::traits::Analyzer;
|
||||||
use crate::registry::AsRule;
|
use crate::checkers::ast::RuleContext;
|
||||||
|
use crate::registry::{AsRule, Rule};
|
||||||
|
|
||||||
use super::super::types::Primitive;
|
use super::super::types::Primitive;
|
||||||
|
|
||||||
@@ -26,8 +27,24 @@ impl AlwaysAutofixableViolation for TypeOfPrimitive {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Analyzer<ast::ExprCall> for TypeOfPrimitive {
|
||||||
|
fn rule() -> Rule {
|
||||||
|
Rule::TypeOfPrimitive
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(diagnostics: &mut Vec<Diagnostic>, checker: &RuleContext, node: &ast::ExprCall) {
|
||||||
|
type_of_primitive(diagnostics, checker, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// UP003
|
/// UP003
|
||||||
pub(crate) fn type_of_primitive(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
|
pub(crate) fn type_of_primitive(
|
||||||
|
diagnostics: &mut Vec<Diagnostic>,
|
||||||
|
checker: &RuleContext,
|
||||||
|
ast::ExprCall {
|
||||||
|
func, args, range, ..
|
||||||
|
}: &ast::ExprCall,
|
||||||
|
) {
|
||||||
if args.len() != 1 {
|
if args.len() != 1 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -44,13 +61,13 @@ pub(crate) fn type_of_primitive(checker: &mut Checker, expr: &Expr, func: &Expr,
|
|||||||
let Some(primitive) = Primitive::from_constant(value) else {
|
let Some(primitive) = Primitive::from_constant(value) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let mut diagnostic = Diagnostic::new(TypeOfPrimitive { primitive }, expr.range());
|
let mut diagnostic = Diagnostic::new(TypeOfPrimitive { primitive }, *range);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||||
primitive.builtin(),
|
primitive.builtin(),
|
||||||
expr.range(),
|
*range,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ use ruff_macros::{derive_message_formats, violation};
|
|||||||
use ruff_python_ast::source_code::Locator;
|
use ruff_python_ast::source_code::Locator;
|
||||||
|
|
||||||
use crate::autofix::actions::remove_argument;
|
use crate::autofix::actions::remove_argument;
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::traits::Analyzer;
|
||||||
|
use crate::checkers::ast::RuleContext;
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
@@ -35,6 +36,16 @@ impl AlwaysAutofixableViolation for UnnecessaryEncodeUTF8 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Analyzer<ast::ExprCall> for UnnecessaryEncodeUTF8 {
|
||||||
|
fn rule() -> Rule {
|
||||||
|
Rule::UnnecessaryEncodeUTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(diagnostics: &mut Vec<Diagnostic>, checker: &RuleContext, node: &ast::ExprCall) {
|
||||||
|
unnecessary_encode_utf8(diagnostics, checker, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const UTF8_LITERALS: &[&str] = &["utf-8", "utf8", "utf_8", "u8", "utf", "cp65001"];
|
const UTF8_LITERALS: &[&str] = &["utf-8", "utf8", "utf_8", "u8", "utf", "cp65001"];
|
||||||
|
|
||||||
fn match_encoded_variable(func: &Expr) -> Option<&Expr> {
|
fn match_encoded_variable(func: &Expr) -> Option<&Expr> {
|
||||||
@@ -75,8 +86,8 @@ enum EncodingArg<'a> {
|
|||||||
|
|
||||||
/// Return the encoding argument to an `encode` call, if it can be determined to be a
|
/// Return the encoding argument to an `encode` call, if it can be determined to be a
|
||||||
/// UTF-8-equivalent encoding.
|
/// UTF-8-equivalent encoding.
|
||||||
fn match_encoding_arg<'a>(args: &'a [Expr], kwargs: &'a [Keyword]) -> Option<EncodingArg<'a>> {
|
fn match_encoding_arg<'a>(args: &'a [Expr], keywords: &'a [Keyword]) -> Option<EncodingArg<'a>> {
|
||||||
match (args.len(), kwargs.len()) {
|
match (args.len(), keywords.len()) {
|
||||||
// Ex `"".encode()`
|
// Ex `"".encode()`
|
||||||
(0, 0) => return Some(EncodingArg::Empty),
|
(0, 0) => return Some(EncodingArg::Empty),
|
||||||
// Ex `"".encode(encoding)`
|
// Ex `"".encode(encoding)`
|
||||||
@@ -88,7 +99,7 @@ fn match_encoding_arg<'a>(args: &'a [Expr], kwargs: &'a [Keyword]) -> Option<Enc
|
|||||||
}
|
}
|
||||||
// Ex `"".encode(kwarg=kwarg)`
|
// Ex `"".encode(kwarg=kwarg)`
|
||||||
(0, 1) => {
|
(0, 1) => {
|
||||||
let kwarg = &kwargs[0];
|
let kwarg = &keywords[0];
|
||||||
if kwarg.arg.as_ref().map_or(false, |arg| arg == "encoding") {
|
if kwarg.arg.as_ref().map_or(false, |arg| arg == "encoding") {
|
||||||
if is_utf8_encoding_arg(&kwarg.value) {
|
if is_utf8_encoding_arg(&kwarg.value) {
|
||||||
return Some(EncodingArg::Keyword(kwarg));
|
return Some(EncodingArg::Keyword(kwarg));
|
||||||
@@ -102,12 +113,12 @@ fn match_encoding_arg<'a>(args: &'a [Expr], kwargs: &'a [Keyword]) -> Option<Enc
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return a [`Fix`] replacing the call to encode with a byte string.
|
/// Return a [`Fix`] replacing the call to encode with a byte string.
|
||||||
fn replace_with_bytes_literal(locator: &Locator, expr: &Expr) -> Fix {
|
fn replace_with_bytes_literal(locator: &Locator, range: TextRange) -> Fix {
|
||||||
// Build up a replacement string by prefixing all string tokens with `b`.
|
// Build up a replacement string by prefixing all string tokens with `b`.
|
||||||
let contents = locator.slice(expr.range());
|
let contents = locator.slice(range);
|
||||||
let mut replacement = String::with_capacity(contents.len() + 1);
|
let mut replacement = String::with_capacity(contents.len() + 1);
|
||||||
let mut prev = expr.start();
|
let mut prev = range.start();
|
||||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, expr.start()).flatten() {
|
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, range.start()).flatten() {
|
||||||
match tok {
|
match tok {
|
||||||
Tok::Dot => break,
|
Tok::Dot => break,
|
||||||
Tok::String { .. } => {
|
Tok::String { .. } => {
|
||||||
@@ -125,16 +136,19 @@ fn replace_with_bytes_literal(locator: &Locator, expr: &Expr) -> Fix {
|
|||||||
prev = range.end();
|
prev = range.end();
|
||||||
}
|
}
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
Fix::unspecified(Edit::range_replacement(replacement, expr.range()))
|
Fix::unspecified(Edit::range_replacement(replacement, range))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// UP012
|
/// UP012
|
||||||
pub(crate) fn unnecessary_encode_utf8(
|
pub(crate) fn unnecessary_encode_utf8(
|
||||||
checker: &mut Checker,
|
diagnostics: &mut Vec<Diagnostic>,
|
||||||
expr: &Expr,
|
checker: &RuleContext,
|
||||||
func: &Expr,
|
ast::ExprCall {
|
||||||
args: &[Expr],
|
func,
|
||||||
kwargs: &[Keyword],
|
args,
|
||||||
|
range,
|
||||||
|
keywords,
|
||||||
|
}: &ast::ExprCall,
|
||||||
) {
|
) {
|
||||||
let Some(variable) = match_encoded_variable(func) else {
|
let Some(variable) = match_encoded_variable(func) else {
|
||||||
return;
|
return;
|
||||||
@@ -145,19 +159,19 @@ pub(crate) fn unnecessary_encode_utf8(
|
|||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
// Ex) `"str".encode()`, `"str".encode("utf-8")`
|
// Ex) `"str".encode()`, `"str".encode("utf-8")`
|
||||||
if let Some(encoding_arg) = match_encoding_arg(args, kwargs) {
|
if let Some(encoding_arg) = match_encoding_arg(args, keywords) {
|
||||||
if literal.is_ascii() {
|
if literal.is_ascii() {
|
||||||
// Ex) Convert `"foo".encode()` to `b"foo"`.
|
// Ex) Convert `"foo".encode()` to `b"foo"`.
|
||||||
let mut diagnostic = Diagnostic::new(
|
let mut diagnostic = Diagnostic::new(
|
||||||
UnnecessaryEncodeUTF8 {
|
UnnecessaryEncodeUTF8 {
|
||||||
reason: Reason::BytesLiteral,
|
reason: Reason::BytesLiteral,
|
||||||
},
|
},
|
||||||
expr.range(),
|
*range,
|
||||||
);
|
);
|
||||||
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
|
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
|
||||||
diagnostic.set_fix(replace_with_bytes_literal(checker.locator, expr));
|
diagnostic.set_fix(replace_with_bytes_literal(checker.locator, *range));
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
} else if let EncodingArg::Keyword(kwarg) = encoding_arg {
|
} else if let EncodingArg::Keyword(kwarg) = encoding_arg {
|
||||||
// Ex) Convert `"unicode text©".encode(encoding="utf-8")` to
|
// Ex) Convert `"unicode text©".encode(encoding="utf-8")` to
|
||||||
// `"unicode text©".encode()`.
|
// `"unicode text©".encode()`.
|
||||||
@@ -165,7 +179,7 @@ pub(crate) fn unnecessary_encode_utf8(
|
|||||||
UnnecessaryEncodeUTF8 {
|
UnnecessaryEncodeUTF8 {
|
||||||
reason: Reason::DefaultArgument,
|
reason: Reason::DefaultArgument,
|
||||||
},
|
},
|
||||||
expr.range(),
|
*range,
|
||||||
);
|
);
|
||||||
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
|
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
@@ -175,19 +189,19 @@ pub(crate) fn unnecessary_encode_utf8(
|
|||||||
func.start(),
|
func.start(),
|
||||||
kwarg.range(),
|
kwarg.range(),
|
||||||
args,
|
args,
|
||||||
kwargs,
|
keywords,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
} else if let EncodingArg::Positional(arg) = encoding_arg {
|
} else if let EncodingArg::Positional(arg) = encoding_arg {
|
||||||
// Ex) Convert `"unicode text©".encode("utf-8")` to `"unicode text©".encode()`.
|
// Ex) Convert `"unicode text©".encode("utf-8")` to `"unicode text©".encode()`.
|
||||||
let mut diagnostic = Diagnostic::new(
|
let mut diagnostic = Diagnostic::new(
|
||||||
UnnecessaryEncodeUTF8 {
|
UnnecessaryEncodeUTF8 {
|
||||||
reason: Reason::DefaultArgument,
|
reason: Reason::DefaultArgument,
|
||||||
},
|
},
|
||||||
expr.range(),
|
*range,
|
||||||
);
|
);
|
||||||
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
|
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
@@ -197,18 +211,18 @@ pub(crate) fn unnecessary_encode_utf8(
|
|||||||
func.start(),
|
func.start(),
|
||||||
arg.range(),
|
arg.range(),
|
||||||
args,
|
args,
|
||||||
kwargs,
|
keywords,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Ex) `f"foo{bar}".encode("utf-8")`
|
// Ex) `f"foo{bar}".encode("utf-8")`
|
||||||
Expr::JoinedStr(_) => {
|
Expr::JoinedStr(_) => {
|
||||||
if let Some(encoding_arg) = match_encoding_arg(args, kwargs) {
|
if let Some(encoding_arg) = match_encoding_arg(args, keywords) {
|
||||||
if let EncodingArg::Keyword(kwarg) = encoding_arg {
|
if let EncodingArg::Keyword(kwarg) = encoding_arg {
|
||||||
// Ex) Convert `f"unicode text©".encode(encoding="utf-8")` to
|
// Ex) Convert `f"unicode text©".encode(encoding="utf-8")` to
|
||||||
// `f"unicode text©".encode()`.
|
// `f"unicode text©".encode()`.
|
||||||
@@ -216,7 +230,7 @@ pub(crate) fn unnecessary_encode_utf8(
|
|||||||
UnnecessaryEncodeUTF8 {
|
UnnecessaryEncodeUTF8 {
|
||||||
reason: Reason::DefaultArgument,
|
reason: Reason::DefaultArgument,
|
||||||
},
|
},
|
||||||
expr.range(),
|
*range,
|
||||||
);
|
);
|
||||||
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
|
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
@@ -226,19 +240,19 @@ pub(crate) fn unnecessary_encode_utf8(
|
|||||||
func.start(),
|
func.start(),
|
||||||
kwarg.range(),
|
kwarg.range(),
|
||||||
args,
|
args,
|
||||||
kwargs,
|
keywords,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
} else if let EncodingArg::Positional(arg) = encoding_arg {
|
} else if let EncodingArg::Positional(arg) = encoding_arg {
|
||||||
// Ex) Convert `f"unicode text©".encode("utf-8")` to `f"unicode text©".encode()`.
|
// Ex) Convert `f"unicode text©".encode("utf-8")` to `f"unicode text©".encode()`.
|
||||||
let mut diagnostic = Diagnostic::new(
|
let mut diagnostic = Diagnostic::new(
|
||||||
UnnecessaryEncodeUTF8 {
|
UnnecessaryEncodeUTF8 {
|
||||||
reason: Reason::DefaultArgument,
|
reason: Reason::DefaultArgument,
|
||||||
},
|
},
|
||||||
expr.range(),
|
*range,
|
||||||
);
|
);
|
||||||
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
|
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
@@ -248,12 +262,12 @@ pub(crate) fn unnecessary_encode_utf8(
|
|||||||
func.start(),
|
func.start(),
|
||||||
arg.range(),
|
arg.range(),
|
||||||
args,
|
args,
|
||||||
kwargs,
|
keywords,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ use rustpython_parser::ast::{self, Expr, Operator, Ranged};
|
|||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::traits::Analyzer;
|
||||||
use crate::registry::AsRule;
|
use crate::checkers::ast::RuleContext;
|
||||||
|
use crate::registry::{AsRule, Rule};
|
||||||
|
use crate::settings::types::PythonVersion;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub(crate) enum CallKind {
|
pub(crate) enum CallKind {
|
||||||
@@ -50,6 +52,16 @@ impl AlwaysAutofixableViolation for NonPEP604Isinstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Analyzer<ast::ExprCall> for NonPEP604Isinstance {
|
||||||
|
fn rule() -> Rule {
|
||||||
|
Rule::NonPEP604Isinstance
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(diagnostics: &mut Vec<Diagnostic>, checker: &RuleContext, node: &ast::ExprCall) {
|
||||||
|
use_pep604_isinstance(diagnostics, checker, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn union(elts: &[Expr]) -> Expr {
|
fn union(elts: &[Expr]) -> Expr {
|
||||||
if elts.len() == 1 {
|
if elts.len() == 1 {
|
||||||
elts[0].clone()
|
elts[0].clone()
|
||||||
@@ -65,12 +77,17 @@ fn union(elts: &[Expr]) -> Expr {
|
|||||||
|
|
||||||
/// UP038
|
/// UP038
|
||||||
pub(crate) fn use_pep604_isinstance(
|
pub(crate) fn use_pep604_isinstance(
|
||||||
checker: &mut Checker,
|
diagnostics: &mut Vec<Diagnostic>,
|
||||||
expr: &Expr,
|
checker: &RuleContext,
|
||||||
func: &Expr,
|
ast::ExprCall {
|
||||||
args: &[Expr],
|
func, args, range, ..
|
||||||
|
}: &ast::ExprCall,
|
||||||
) {
|
) {
|
||||||
if let Expr::Name(ast::ExprName { id, .. }) = func {
|
if checker.settings.target_version < PythonVersion::Py310 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
|
||||||
let Some(kind) = CallKind::from_name(id) else {
|
let Some(kind) = CallKind::from_name(id) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -89,7 +106,7 @@ pub(crate) fn use_pep604_isinstance(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut diagnostic = Diagnostic::new(NonPEP604Isinstance { kind }, expr.range());
|
let mut diagnostic = Diagnostic::new(NonPEP604Isinstance { kind }, *range);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||||
@@ -97,7 +114,7 @@ pub(crate) fn use_pep604_isinstance(
|
|||||||
types.range(),
|
types.range(),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
checker.diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user