Compare commits
9 Commits
david/allo
...
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 crate::checkers::ast::deferred::Deferred;
|
||||
use crate::checkers::ast::traits::RegisteredRule;
|
||||
use crate::docstrings::extraction::ExtractionTarget;
|
||||
use crate::docstrings::Docstring;
|
||||
use crate::fs::relativize_path;
|
||||
@@ -56,6 +57,7 @@ use crate::settings::{flags, Settings};
|
||||
use crate::{autofix, docstrings, noqa, warn_user};
|
||||
|
||||
mod deferred;
|
||||
pub(crate) mod traits;
|
||||
|
||||
pub(crate) struct Checker<'a> {
|
||||
// Settings, static metadata, etc.
|
||||
@@ -77,6 +79,16 @@ pub(crate) struct Checker<'a> {
|
||||
deferred: Deferred<'a>,
|
||||
// Check-specific state.
|
||||
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> {
|
||||
@@ -110,10 +122,63 @@ impl<'a> Checker<'a> {
|
||||
diagnostics: Vec::default(),
|
||||
deletions: FxHashSet::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> {
|
||||
/// Return `true` if a patch should be generated under the given autofix
|
||||
/// `Mode`.
|
||||
@@ -2567,12 +2632,26 @@ where
|
||||
}
|
||||
pandas_vet::rules::attr(self, attr, value, expr);
|
||||
}
|
||||
Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
}) => {
|
||||
Expr::Call(call) => {
|
||||
let context = RuleContext {
|
||||
settings: self.settings,
|
||||
locator: self.locator,
|
||||
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(&[
|
||||
// pyflakes
|
||||
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
|
||||
if self
|
||||
.settings
|
||||
@@ -3294,15 +3336,6 @@ where
|
||||
{
|
||||
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 {
|
||||
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_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
|
||||
/// Checks for the use of `locals()` in `render` functions.
|
||||
@@ -43,11 +45,25 @@ impl Violation for DjangoLocalsInRenderFunction {
|
||||
}
|
||||
|
||||
/// 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(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
checker: &RuleContext,
|
||||
ast::ExprCall {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
..
|
||||
}: &ast::ExprCall,
|
||||
) {
|
||||
if !checker
|
||||
.ctx
|
||||
@@ -76,13 +92,13 @@ pub(crate) fn locals_in_render_function(
|
||||
return;
|
||||
};
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
diagnostics.push(Diagnostic::new(
|
||||
DjangoLocalsInRenderFunction,
|
||||
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 {
|
||||
return false
|
||||
};
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
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 locals_in_render_function::{
|
||||
locals_in_render_function, DjangoLocalsInRenderFunction,
|
||||
};
|
||||
pub(crate) use locals_in_render_function::DjangoLocalsInRenderFunction;
|
||||
pub(crate) use model_without_dunder_str::{model_without_dunder_str, DjangoModelWithoutDunderStr};
|
||||
pub(crate) use non_leading_receiver_decorator::{
|
||||
non_leading_receiver_decorator, DjangoNonLeadingReceiverDecorator,
|
||||
|
||||
@@ -4,7 +4,6 @@ use libcst_native::{
|
||||
SmallStatement, Statement, Suite,
|
||||
};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use rustpython_parser::ast::{Expr, Ranged};
|
||||
use rustpython_parser::{lexer, Mode, Tok};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
@@ -51,11 +50,10 @@ pub(crate) fn adjust_indentation(
|
||||
|
||||
/// Generate a fix to remove arguments from a `super` call.
|
||||
pub(crate) fn remove_super_arguments(
|
||||
range: TextRange,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
) -> Option<Edit> {
|
||||
let range = expr.range();
|
||||
let contents = locator.slice(range);
|
||||
|
||||
let mut tree = libcst_native::parse_module(contents, None).ok()?;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use once_cell::sync::Lazy;
|
||||
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_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::checkers::ast::traits::Analyzer;
|
||||
use crate::checkers::ast::RuleContext;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
#[violation]
|
||||
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(|| {
|
||||
FxHashMap::from_iter([
|
||||
("failUnlessEqual", "assertEqual"),
|
||||
@@ -48,11 +59,12 @@ static DEPRECATED_ALIASES: Lazy<FxHashMap<&'static str, &'static str>> = Lazy::n
|
||||
});
|
||||
|
||||
/// UP005
|
||||
pub(crate) fn deprecated_unittest_alias(checker: &mut Checker, expr: &Expr) {
|
||||
let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = expr else {
|
||||
return;
|
||||
};
|
||||
let Some(target) = DEPRECATED_ALIASES.get(attr.as_str()) else {
|
||||
pub(crate) fn deprecated_unittest_alias(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
checker: &RuleContext,
|
||||
ast::ExprCall { func, .. }: &ast::ExprCall,
|
||||
) {
|
||||
let Expr::Attribute(ast::ExprAttribute { value, attr, range, .. }) = func.as_ref() else {
|
||||
return;
|
||||
};
|
||||
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" {
|
||||
return;
|
||||
}
|
||||
let Some(target) = DEPRECATED_ALIASES.get(attr.as_str()) else {
|
||||
return;
|
||||
};
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
DeprecatedUnittestAlias {
|
||||
alias: attr.to_string(),
|
||||
target: (*target).to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
*range,
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||
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 rustc_hash::FxHashMap;
|
||||
use rustpython_format::{
|
||||
FieldName, FieldNamePart, FieldType, FormatPart, FormatString, FromTemplate,
|
||||
};
|
||||
use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
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::{
|
||||
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 f_strings::{f_strings, FString};
|
||||
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::{
|
||||
lru_cache_without_parameters, LRUCacheWithoutParameters,
|
||||
};
|
||||
pub(crate) use native_literals::{native_literals, NativeLiterals};
|
||||
pub(crate) use open_alias::{open_alias, OpenAlias};
|
||||
pub(crate) use os_error_alias::{
|
||||
os_error_alias_call, os_error_alias_handlers, os_error_alias_raise, OSErrorAlias,
|
||||
};
|
||||
pub(crate) use native_literals::NativeLiterals;
|
||||
pub(crate) use open_alias::OpenAlias;
|
||||
pub(crate) use os_error_alias::{os_error_alias_handlers, os_error_alias_raise, OSErrorAlias};
|
||||
pub(crate) use outdated_version_block::{outdated_version_block, OutdatedVersionBlock};
|
||||
pub(crate) use printf_string_formatting::{printf_string_formatting, PrintfStringFormatting};
|
||||
pub(crate) use quoted_annotation::{quoted_annotation, QuotedAnnotation};
|
||||
pub(crate) use redundant_open_modes::{redundant_open_modes, RedundantOpenModes};
|
||||
pub(crate) use replace_stdout_stderr::{replace_stdout_stderr, ReplaceStdoutStderr};
|
||||
pub(crate) use replace_universal_newlines::{replace_universal_newlines, ReplaceUniversalNewlines};
|
||||
pub(crate) use super_call_with_parameters::{super_call_with_parameters, SuperCallWithParameters};
|
||||
pub(crate) use type_of_primitive::{type_of_primitive, TypeOfPrimitive};
|
||||
pub(crate) use redundant_open_modes::RedundantOpenModes;
|
||||
pub(crate) use replace_stdout_stderr::ReplaceStdoutStderr;
|
||||
pub(crate) use replace_universal_newlines::ReplaceUniversalNewlines;
|
||||
pub(crate) use super_call_with_parameters::SuperCallWithParameters;
|
||||
pub(crate) use type_of_primitive::TypeOfPrimitive;
|
||||
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 unnecessary_builtin_import::{unnecessary_builtin_import, UnnecessaryBuiltinImport};
|
||||
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 unpacked_list_comprehension::{
|
||||
unpacked_list_comprehension, UnpackedListComprehension,
|
||||
};
|
||||
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_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_object_inheritance::{useless_object_inheritance, UselessObjectInheritance};
|
||||
pub(crate) use yield_in_for_loop::{yield_in_for_loop, YieldInForLoop};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
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_macros::{derive_message_formats, violation};
|
||||
|
||||
use ruff_python_ast::str::is_implicit_concatenation;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::checkers::ast::traits::Analyzer;
|
||||
use crate::checkers::ast::RuleContext;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
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
|
||||
pub(crate) fn native_literals(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
checker: &RuleContext,
|
||||
ast::ExprCall {
|
||||
func,
|
||||
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 {
|
||||
return;
|
||||
@@ -63,7 +78,7 @@ pub(crate) fn native_literals(
|
||||
LiteralType::Str
|
||||
} else {
|
||||
LiteralType::Bytes
|
||||
}}, expr.range());
|
||||
}}, *range);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let constant = if id == "bytes" {
|
||||
Constant::Bytes(vec![])
|
||||
@@ -74,10 +89,10 @@ pub(crate) fn native_literals(
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||
content,
|
||||
expr.range(),
|
||||
*range,
|
||||
)));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
diagnostics.push(diagnostic);
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -121,15 +136,15 @@ pub(crate) fn native_literals(
|
||||
LiteralType::Bytes
|
||||
},
|
||||
},
|
||||
expr.range(),
|
||||
*range,
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||
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_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::checkers::ast::traits::Analyzer;
|
||||
use crate::checkers::ast::RuleContext;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
#[violation]
|
||||
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
|
||||
pub(crate) fn open_alias(checker: &mut Checker, expr: &Expr, func: &Expr) {
|
||||
if checker
|
||||
pub(crate) fn open_alias(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
context: &RuleContext,
|
||||
ast::ExprCall { func, range, .. }: &ast::ExprCall,
|
||||
) {
|
||||
if context
|
||||
.ctx
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| call_path.as_slice() == ["io", "open"])
|
||||
{
|
||||
let fixable = checker
|
||||
let fixable = context
|
||||
.ctx
|
||||
.find_binding("open")
|
||||
.map_or(true, |binding| binding.kind.is_builtin());
|
||||
let mut diagnostic = Diagnostic::new(OpenAlias, expr.range());
|
||||
if fixable && checker.patch(diagnostic.kind.rule()) {
|
||||
let mut diagnostic = Diagnostic::new(OpenAlias, *range);
|
||||
if fixable && context.patch(diagnostic.kind.rule()) {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||
"open".to_string(),
|
||||
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_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::call_path::compose_call_path;
|
||||
|
||||
use ruff_python_semantic::context::Context;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::checkers::ast::traits::Analyzer;
|
||||
use crate::checkers::ast::{Checker, RuleContext};
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
#[violation]
|
||||
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)] = &[
|
||||
("", "EnvironmentError"),
|
||||
("", "IOError"),
|
||||
@@ -73,6 +83,24 @@ fn atom_diagnostic(checker: &mut Checker, target: &Expr) {
|
||||
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.
|
||||
fn tuple_diagnostic(checker: &mut Checker, target: &Expr, aliases: &[&Expr]) {
|
||||
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
|
||||
pub(crate) fn os_error_alias_call(checker: &mut Checker, func: &Expr) {
|
||||
if is_alias(&checker.ctx, func) {
|
||||
atom_diagnostic(checker, func);
|
||||
pub(crate) fn os_error_alias_call(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
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 ruff_text_size::TextRange;
|
||||
use rustpython_format::cformat::{
|
||||
CConversionFlags, CFormatPart, CFormatPrecision, CFormatQuantity, CFormatString,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
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::source_code::Locator;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checkers::ast::traits::Analyzer;
|
||||
use crate::checkers::ast::RuleContext;
|
||||
use crate::registry::Rule;
|
||||
|
||||
#[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 MODE_KEYWORD_ARGUMENT: &str = "mode";
|
||||
|
||||
@@ -86,24 +97,24 @@ impl OpenMode {
|
||||
}
|
||||
}
|
||||
|
||||
fn match_open(expr: &Expr) -> (Option<&Expr>, Vec<Keyword>) {
|
||||
if let Expr::Call(ast::ExprCall {
|
||||
fn match_open(
|
||||
ast::ExprCall {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
}) = expr
|
||||
{
|
||||
if matches!(func.as_ref(), Expr::Name(ast::ExprName {id, ..}) if id == OPEN_FUNC_NAME) {
|
||||
// Return the "open mode" parameter and keywords.
|
||||
return (args.get(1), keywords.clone());
|
||||
}
|
||||
}: &ast::ExprCall,
|
||||
) -> (Option<&Expr>, Vec<Keyword>) {
|
||||
if matches!(func.as_ref(), Expr::Name(ast::ExprName {id, ..}) if id == OPEN_FUNC_NAME) {
|
||||
// Return the "open mode" parameter and keywords.
|
||||
(args.get(1), keywords.clone())
|
||||
} else {
|
||||
(None, vec![])
|
||||
}
|
||||
(None, vec![])
|
||||
}
|
||||
|
||||
fn create_check(
|
||||
expr: &Expr,
|
||||
expr: &ast::ExprCall,
|
||||
mode_param: &Expr,
|
||||
replacement_value: Option<&str>,
|
||||
locator: &Locator,
|
||||
@@ -130,7 +141,11 @@ fn create_check(
|
||||
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());
|
||||
// Find the last comma before mode_param and create a deletion fix
|
||||
// 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
|
||||
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 !checker.ctx.is_builtin(OPEN_FUNC_NAME) {
|
||||
return;
|
||||
@@ -185,7 +204,7 @@ pub(crate) fn redundant_open_modes(checker: &mut Checker, expr: &Expr) {
|
||||
}) = &keyword.value
|
||||
{
|
||||
if let Ok(mode) = OpenMode::from_str(mode_param_value.as_str()) {
|
||||
checker.diagnostics.push(create_check(
|
||||
diagnostics.push(create_check(
|
||||
expr,
|
||||
&keyword.value,
|
||||
mode.replacement_value(),
|
||||
@@ -202,7 +221,7 @@ pub(crate) fn redundant_open_modes(checker: &mut Checker, expr: &Expr) {
|
||||
}) = &mode_param
|
||||
{
|
||||
if let Ok(mode) = OpenMode::from_str(mode_param_value.as_str()) {
|
||||
checker.diagnostics.push(create_check(
|
||||
diagnostics.push(create_check(
|
||||
expr,
|
||||
mode_param,
|
||||
mode.replacement_value(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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_macros::{derive_message_formats, violation};
|
||||
@@ -7,8 +7,9 @@ use ruff_python_ast::helpers::find_keyword;
|
||||
use ruff_python_ast::source_code::Locator;
|
||||
|
||||
use crate::autofix::actions::remove_argument;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::checkers::ast::traits::Analyzer;
|
||||
use crate::checkers::ast::RuleContext;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
#[violation]
|
||||
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.
|
||||
fn generate_fix(
|
||||
locator: &Locator,
|
||||
@@ -54,11 +65,14 @@ fn generate_fix(
|
||||
|
||||
/// UP022
|
||||
pub(crate) fn replace_stdout_stderr(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
checker: &RuleContext,
|
||||
ast::ExprCall {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
range,
|
||||
}: &ast::ExprCall,
|
||||
) {
|
||||
if checker
|
||||
.ctx
|
||||
@@ -92,12 +106,12 @@ pub(crate) fn replace_stdout_stderr(
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(ReplaceStdoutStderr, expr.range());
|
||||
let mut diagnostic = Diagnostic::new(ReplaceStdoutStderr, *range);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
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 rustpython_parser::ast::{Expr, Keyword, Ranged};
|
||||
use rustpython_parser::ast::{self, Ranged};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::find_keyword;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::checkers::ast::traits::Analyzer;
|
||||
use crate::checkers::ast::RuleContext;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
#[violation]
|
||||
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
|
||||
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
|
||||
.ctx
|
||||
.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"]
|
||||
})
|
||||
{
|
||||
let Some(kwarg) = find_keyword(kwargs, "universal_newlines") else { return; };
|
||||
let range = TextRange::at(kwarg.start(), "universal_newlines".text_len());
|
||||
let Some(keyword) = find_keyword(keywords, "universal_newlines") else { return; };
|
||||
let range = TextRange::at(keyword.start(), "universal_newlines".text_len());
|
||||
let mut diagnostic = Diagnostic::new(ReplaceUniversalNewlines, range);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
#[allow(deprecated)]
|
||||
@@ -41,6 +56,6 @@ pub(crate) fn replace_universal_newlines(checker: &mut Checker, func: &Expr, kwa
|
||||
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_macros::{derive_message_formats, violation};
|
||||
use ruff_python_semantic::scope::ScopeKind;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::checkers::ast::traits::Analyzer;
|
||||
use crate::checkers::ast::RuleContext;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rules::pyupgrade::fixes;
|
||||
|
||||
#[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.
|
||||
fn is_super_call_with_arguments(func: &Expr, args: &[Expr]) -> bool {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = func {
|
||||
@@ -33,10 +44,11 @@ fn is_super_call_with_arguments(func: &Expr, args: &[Expr]) -> bool {
|
||||
|
||||
/// UP008
|
||||
pub(crate) fn super_call_with_parameters(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
checker: &RuleContext,
|
||||
ast::ExprCall {
|
||||
func, args, range, ..
|
||||
}: &ast::ExprCall,
|
||||
) {
|
||||
// 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.)
|
||||
@@ -55,7 +67,7 @@ pub(crate) fn super_call_with_parameters(
|
||||
// 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
|
||||
// argument to the enclosing function.
|
||||
let [first_arg, second_arg] = args else {
|
||||
let [first_arg, second_arg] = args.as_slice() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -97,12 +109,13 @@ pub(crate) fn super_call_with_parameters(
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(SuperCallWithParameters, expr.range());
|
||||
let mut diagnostic = Diagnostic::new(SuperCallWithParameters, *range);
|
||||
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)]
|
||||
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_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::checkers::ast::traits::Analyzer;
|
||||
use crate::checkers::ast::RuleContext;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
|
||||
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
|
||||
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 {
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
let mut diagnostic = Diagnostic::new(TypeOfPrimitive { primitive }, expr.range());
|
||||
let mut diagnostic = Diagnostic::new(TypeOfPrimitive { primitive }, *range);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||
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 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;
|
||||
|
||||
#[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"];
|
||||
|
||||
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
|
||||
/// UTF-8-equivalent encoding.
|
||||
fn match_encoding_arg<'a>(args: &'a [Expr], kwargs: &'a [Keyword]) -> Option<EncodingArg<'a>> {
|
||||
match (args.len(), kwargs.len()) {
|
||||
fn match_encoding_arg<'a>(args: &'a [Expr], keywords: &'a [Keyword]) -> Option<EncodingArg<'a>> {
|
||||
match (args.len(), keywords.len()) {
|
||||
// Ex `"".encode()`
|
||||
(0, 0) => return Some(EncodingArg::Empty),
|
||||
// Ex `"".encode(encoding)`
|
||||
@@ -88,7 +99,7 @@ fn match_encoding_arg<'a>(args: &'a [Expr], kwargs: &'a [Keyword]) -> Option<Enc
|
||||
}
|
||||
// Ex `"".encode(kwarg=kwarg)`
|
||||
(0, 1) => {
|
||||
let kwarg = &kwargs[0];
|
||||
let kwarg = &keywords[0];
|
||||
if kwarg.arg.as_ref().map_or(false, |arg| arg == "encoding") {
|
||||
if is_utf8_encoding_arg(&kwarg.value) {
|
||||
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.
|
||||
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`.
|
||||
let contents = locator.slice(expr.range());
|
||||
let contents = locator.slice(range);
|
||||
let mut replacement = String::with_capacity(contents.len() + 1);
|
||||
let mut prev = expr.start();
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, expr.start()).flatten() {
|
||||
let mut prev = range.start();
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, range.start()).flatten() {
|
||||
match tok {
|
||||
Tok::Dot => break,
|
||||
Tok::String { .. } => {
|
||||
@@ -125,16 +136,19 @@ fn replace_with_bytes_literal(locator: &Locator, expr: &Expr) -> Fix {
|
||||
prev = range.end();
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
Fix::unspecified(Edit::range_replacement(replacement, expr.range()))
|
||||
Fix::unspecified(Edit::range_replacement(replacement, range))
|
||||
}
|
||||
|
||||
/// UP012
|
||||
pub(crate) fn unnecessary_encode_utf8(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
kwargs: &[Keyword],
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
checker: &RuleContext,
|
||||
ast::ExprCall {
|
||||
func,
|
||||
args,
|
||||
range,
|
||||
keywords,
|
||||
}: &ast::ExprCall,
|
||||
) {
|
||||
let Some(variable) = match_encoded_variable(func) else {
|
||||
return;
|
||||
@@ -145,19 +159,19 @@ pub(crate) fn unnecessary_encode_utf8(
|
||||
..
|
||||
}) => {
|
||||
// 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() {
|
||||
// Ex) Convert `"foo".encode()` to `b"foo"`.
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnnecessaryEncodeUTF8 {
|
||||
reason: Reason::BytesLiteral,
|
||||
},
|
||||
expr.range(),
|
||||
*range,
|
||||
);
|
||||
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 {
|
||||
// Ex) Convert `"unicode text©".encode(encoding="utf-8")` to
|
||||
// `"unicode text©".encode()`.
|
||||
@@ -165,7 +179,7 @@ pub(crate) fn unnecessary_encode_utf8(
|
||||
UnnecessaryEncodeUTF8 {
|
||||
reason: Reason::DefaultArgument,
|
||||
},
|
||||
expr.range(),
|
||||
*range,
|
||||
);
|
||||
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
|
||||
#[allow(deprecated)]
|
||||
@@ -175,19 +189,19 @@ pub(crate) fn unnecessary_encode_utf8(
|
||||
func.start(),
|
||||
kwarg.range(),
|
||||
args,
|
||||
kwargs,
|
||||
keywords,
|
||||
false,
|
||||
)
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
diagnostics.push(diagnostic);
|
||||
} else if let EncodingArg::Positional(arg) = encoding_arg {
|
||||
// Ex) Convert `"unicode text©".encode("utf-8")` to `"unicode text©".encode()`.
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnnecessaryEncodeUTF8 {
|
||||
reason: Reason::DefaultArgument,
|
||||
},
|
||||
expr.range(),
|
||||
*range,
|
||||
);
|
||||
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
|
||||
#[allow(deprecated)]
|
||||
@@ -197,18 +211,18 @@ pub(crate) fn unnecessary_encode_utf8(
|
||||
func.start(),
|
||||
arg.range(),
|
||||
args,
|
||||
kwargs,
|
||||
keywords,
|
||||
false,
|
||||
)
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ex) `f"foo{bar}".encode("utf-8")`
|
||||
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 {
|
||||
// Ex) Convert `f"unicode text©".encode(encoding="utf-8")` to
|
||||
// `f"unicode text©".encode()`.
|
||||
@@ -216,7 +230,7 @@ pub(crate) fn unnecessary_encode_utf8(
|
||||
UnnecessaryEncodeUTF8 {
|
||||
reason: Reason::DefaultArgument,
|
||||
},
|
||||
expr.range(),
|
||||
*range,
|
||||
);
|
||||
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
|
||||
#[allow(deprecated)]
|
||||
@@ -226,19 +240,19 @@ pub(crate) fn unnecessary_encode_utf8(
|
||||
func.start(),
|
||||
kwarg.range(),
|
||||
args,
|
||||
kwargs,
|
||||
keywords,
|
||||
false,
|
||||
)
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
diagnostics.push(diagnostic);
|
||||
} else if let EncodingArg::Positional(arg) = encoding_arg {
|
||||
// Ex) Convert `f"unicode text©".encode("utf-8")` to `f"unicode text©".encode()`.
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnnecessaryEncodeUTF8 {
|
||||
reason: Reason::DefaultArgument,
|
||||
},
|
||||
expr.range(),
|
||||
*range,
|
||||
);
|
||||
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
|
||||
#[allow(deprecated)]
|
||||
@@ -248,12 +262,12 @@ pub(crate) fn unnecessary_encode_utf8(
|
||||
func.start(),
|
||||
arg.range(),
|
||||
args,
|
||||
kwargs,
|
||||
keywords,
|
||||
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_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::checkers::ast::traits::Analyzer;
|
||||
use crate::checkers::ast::RuleContext;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
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 {
|
||||
if elts.len() == 1 {
|
||||
elts[0].clone()
|
||||
@@ -65,12 +77,17 @@ fn union(elts: &[Expr]) -> Expr {
|
||||
|
||||
/// UP038
|
||||
pub(crate) fn use_pep604_isinstance(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
checker: &RuleContext,
|
||||
ast::ExprCall {
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
@@ -89,7 +106,7 @@ pub(crate) fn use_pep604_isinstance(
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(NonPEP604Isinstance { kind }, expr.range());
|
||||
let mut diagnostic = Diagnostic::new(NonPEP604Isinstance { kind }, *range);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
#[allow(deprecated)]
|
||||
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
|
||||
@@ -97,7 +114,7 @@ pub(crate) fn use_pep604_isinstance(
|
||||
types.range(),
|
||||
)));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user