mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 04:19:43 +00:00
[flake8-slots
] Add plugin, add SLOT000
, SLOT001
and SLOT002
(#4909)
This commit is contained in:
parent
ee1f094834
commit
2bb32ee943
20 changed files with 441 additions and 11 deletions
23
LICENSE
23
LICENSE
|
@ -354,6 +354,29 @@ are:
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
- flake8-slots, licensed as follows:
|
||||||
|
"""
|
||||||
|
Copyright (c) 2021 Dominic Davis-Foster
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
- flake8-todos, licensed as follows:
|
- flake8-todos, licensed as follows:
|
||||||
"""
|
"""
|
||||||
Copyright (c) 2019 EclecticIQ. All rights reserved.
|
Copyright (c) 2019 EclecticIQ. All rights reserved.
|
||||||
|
|
|
@ -283,6 +283,7 @@ quality tools, including:
|
||||||
- [flake8-return](https://pypi.org/project/flake8-return/)
|
- [flake8-return](https://pypi.org/project/flake8-return/)
|
||||||
- [flake8-self](https://pypi.org/project/flake8-self/)
|
- [flake8-self](https://pypi.org/project/flake8-self/)
|
||||||
- [flake8-simplify](https://pypi.org/project/flake8-simplify/)
|
- [flake8-simplify](https://pypi.org/project/flake8-simplify/)
|
||||||
|
- [flake8-slots](https://pypi.org/project/flake8-slots/)
|
||||||
- [flake8-super](https://pypi.org/project/flake8-super/)
|
- [flake8-super](https://pypi.org/project/flake8-super/)
|
||||||
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
|
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
|
||||||
- [flake8-todos](https://pypi.org/project/flake8-todos/)
|
- [flake8-todos](https://pypi.org/project/flake8-todos/)
|
||||||
|
|
6
crates/ruff/resources/test/fixtures/flake8_slots/SLOT000.py
vendored
Normal file
6
crates/ruff/resources/test/fixtures/flake8_slots/SLOT000.py
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
class Bad(str): # SLOT000
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Good(str): # Ok
|
||||||
|
__slots__ = ["foo"]
|
21
crates/ruff/resources/test/fixtures/flake8_slots/SLOT001.py
vendored
Normal file
21
crates/ruff/resources/test/fixtures/flake8_slots/SLOT001.py
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
class Bad(tuple): # SLOT001
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Good(tuple): # Ok
|
||||||
|
__slots__ = ("foo",)
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
|
||||||
|
class Bad(Tuple): # SLOT001
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Bad(Tuple[str, int, float]): # SLOT001
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Good(Tuple[str, int, float]): # OK
|
||||||
|
__slots__ = ("foo",)
|
14
crates/ruff/resources/test/fixtures/flake8_slots/SLOT002.py
vendored
Normal file
14
crates/ruff/resources/test/fixtures/flake8_slots/SLOT002.py
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
from collections import namedtuple
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
|
||||||
|
class Bad(namedtuple("foo", ["str", "int"])): # SLOT002
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Good(namedtuple("foo", ["str", "int"])): # OK
|
||||||
|
__slots__ = ("foo",)
|
||||||
|
|
||||||
|
|
||||||
|
class Good(NamedTuple): # Ok
|
||||||
|
pass
|
|
@ -48,9 +48,9 @@ use crate::rules::{
|
||||||
flake8_debugger, flake8_django, flake8_errmsg, flake8_future_annotations, flake8_gettext,
|
flake8_debugger, flake8_django, flake8_errmsg, flake8_future_annotations, flake8_gettext,
|
||||||
flake8_implicit_str_concat, flake8_import_conventions, flake8_logging_format, flake8_pie,
|
flake8_implicit_str_concat, flake8_import_conventions, flake8_logging_format, flake8_pie,
|
||||||
flake8_print, flake8_pyi, flake8_pytest_style, flake8_raise, flake8_return, flake8_self,
|
flake8_print, flake8_pyi, flake8_pytest_style, flake8_raise, flake8_return, flake8_self,
|
||||||
flake8_simplify, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments,
|
flake8_simplify, flake8_slots, flake8_tidy_imports, flake8_type_checking,
|
||||||
flake8_use_pathlib, flynt, mccabe, numpy, pandas_vet, pep8_naming, pycodestyle, pydocstyle,
|
flake8_unused_arguments, flake8_use_pathlib, flynt, mccabe, numpy, pandas_vet, pep8_naming,
|
||||||
pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
|
pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
|
||||||
};
|
};
|
||||||
use crate::settings::types::PythonVersion;
|
use crate::settings::types::PythonVersion;
|
||||||
use crate::settings::{flags, Settings};
|
use crate::settings::{flags, Settings};
|
||||||
|
@ -682,14 +682,16 @@ where
|
||||||
pylint::rules::return_in_init(self, stmt);
|
pylint::rules::return_in_init(self, stmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stmt::ClassDef(ast::StmtClassDef {
|
Stmt::ClassDef(
|
||||||
name,
|
class_def @ ast::StmtClassDef {
|
||||||
bases,
|
name,
|
||||||
keywords,
|
bases,
|
||||||
decorator_list,
|
keywords,
|
||||||
body,
|
decorator_list,
|
||||||
range: _,
|
body,
|
||||||
}) => {
|
range: _,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
if self.enabled(Rule::DjangoNullableModelStringField) {
|
if self.enabled(Rule::DjangoNullableModelStringField) {
|
||||||
self.diagnostics
|
self.diagnostics
|
||||||
.extend(flake8_django::rules::nullable_model_string_field(
|
.extend(flake8_django::rules::nullable_model_string_field(
|
||||||
|
@ -818,6 +820,18 @@ where
|
||||||
if self.enabled(Rule::DuplicateBases) {
|
if self.enabled(Rule::DuplicateBases) {
|
||||||
pylint::rules::duplicate_bases(self, name, bases);
|
pylint::rules::duplicate_bases(self, name, bases);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.enabled(Rule::NoSlotsInStrSubclass) {
|
||||||
|
flake8_slots::rules::no_slots_in_str_subclass(self, stmt, class_def);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.enabled(Rule::NoSlotsInTupleSubclass) {
|
||||||
|
flake8_slots::rules::no_slots_in_tuple_subclass(self, stmt, class_def);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.enabled(Rule::NoSlotsInNamedtupleSubclass) {
|
||||||
|
flake8_slots::rules::no_slots_in_namedtuple_subclass(self, stmt, class_def);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Stmt::Import(ast::StmtImport { names, range: _ }) => {
|
Stmt::Import(ast::StmtImport { names, range: _ }) => {
|
||||||
if self.enabled(Rule::MultipleImportsOnOneLine) {
|
if self.enabled(Rule::MultipleImportsOnOneLine) {
|
||||||
|
|
|
@ -782,6 +782,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Flake8Fixme, "003") => (RuleGroup::Unspecified, rules::flake8_fixme::rules::LineContainsXxx),
|
(Flake8Fixme, "003") => (RuleGroup::Unspecified, rules::flake8_fixme::rules::LineContainsXxx),
|
||||||
(Flake8Fixme, "004") => (RuleGroup::Unspecified, rules::flake8_fixme::rules::LineContainsHack),
|
(Flake8Fixme, "004") => (RuleGroup::Unspecified, rules::flake8_fixme::rules::LineContainsHack),
|
||||||
|
|
||||||
|
// flake8-slots
|
||||||
|
(Flake8Slots, "000") => (RuleGroup::Unspecified, rules::flake8_slots::rules::NoSlotsInStrSubclass),
|
||||||
|
(Flake8Slots, "001") => (RuleGroup::Unspecified, rules::flake8_slots::rules::NoSlotsInTupleSubclass),
|
||||||
|
(Flake8Slots, "002") => (RuleGroup::Unspecified, rules::flake8_slots::rules::NoSlotsInNamedtupleSubclass),
|
||||||
|
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,6 +137,9 @@ pub enum Linter {
|
||||||
/// [flake8-self](https://pypi.org/project/flake8-self/)
|
/// [flake8-self](https://pypi.org/project/flake8-self/)
|
||||||
#[prefix = "SLF"]
|
#[prefix = "SLF"]
|
||||||
Flake8Self,
|
Flake8Self,
|
||||||
|
/// [flake8-slots](https://pypi.org/project/flake8-slots/)
|
||||||
|
#[prefix = "SLOT"]
|
||||||
|
Flake8Slots,
|
||||||
/// [flake8-simplify](https://pypi.org/project/flake8-simplify/)
|
/// [flake8-simplify](https://pypi.org/project/flake8-simplify/)
|
||||||
#[prefix = "SIM"]
|
#[prefix = "SIM"]
|
||||||
Flake8Simplify,
|
Flake8Simplify,
|
||||||
|
|
27
crates/ruff/src/rules/flake8_slots/mod.rs
Normal file
27
crates/ruff/src/rules/flake8_slots/mod.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
//! Rules from [flake8-slots](https://pypi.org/project/flake8-slots/).
|
||||||
|
pub(crate) mod rules;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use test_case::test_case;
|
||||||
|
|
||||||
|
use crate::registry::Rule;
|
||||||
|
use crate::test::test_path;
|
||||||
|
use crate::{assert_messages, settings};
|
||||||
|
|
||||||
|
#[test_case(Rule::NoSlotsInStrSubclass, Path::new("SLOT000.py"))]
|
||||||
|
#[test_case(Rule::NoSlotsInTupleSubclass, Path::new("SLOT001.py"))]
|
||||||
|
#[test_case(Rule::NoSlotsInNamedtupleSubclass, Path::new("SLOT002.py"))]
|
||||||
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
|
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||||
|
let diagnostics = test_path(
|
||||||
|
Path::new("flake8_slots").join(path).as_path(),
|
||||||
|
&settings::Settings::for_rule(rule_code),
|
||||||
|
)?;
|
||||||
|
assert_messages!(snapshot, diagnostics);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
27
crates/ruff/src/rules/flake8_slots/rules/helpers.rs
Normal file
27
crates/ruff/src/rules/flake8_slots/rules/helpers.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use rustpython_parser::ast::{self, Expr, Stmt};
|
||||||
|
|
||||||
|
/// Return `true` if the given body contains a `__slots__` assignment.
|
||||||
|
pub(super) fn has_slots(body: &[Stmt]) -> bool {
|
||||||
|
for stmt in body {
|
||||||
|
match stmt {
|
||||||
|
Stmt::Assign(ast::StmtAssign { targets, .. }) => {
|
||||||
|
for target in targets {
|
||||||
|
if let Expr::Name(ast::ExprName { id, .. }) = target {
|
||||||
|
if id.as_str() == "__slots__" {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) => {
|
||||||
|
if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() {
|
||||||
|
if id.as_str() == "__slots__" {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
10
crates/ruff/src/rules/flake8_slots/rules/mod.rs
Normal file
10
crates/ruff/src/rules/flake8_slots/rules/mod.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
pub(crate) use no_slots_in_namedtuple_subclass::{
|
||||||
|
no_slots_in_namedtuple_subclass, NoSlotsInNamedtupleSubclass,
|
||||||
|
};
|
||||||
|
pub(crate) use no_slots_in_str_subclass::{no_slots_in_str_subclass, NoSlotsInStrSubclass};
|
||||||
|
pub(crate) use no_slots_in_tuple_subclass::{no_slots_in_tuple_subclass, NoSlotsInTupleSubclass};
|
||||||
|
|
||||||
|
mod helpers;
|
||||||
|
mod no_slots_in_namedtuple_subclass;
|
||||||
|
mod no_slots_in_str_subclass;
|
||||||
|
mod no_slots_in_tuple_subclass;
|
|
@ -0,0 +1,84 @@
|
||||||
|
use rustpython_parser::ast;
|
||||||
|
use rustpython_parser::ast::{Expr, StmtClassDef};
|
||||||
|
|
||||||
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::helpers::identifier_range;
|
||||||
|
use ruff_python_ast::prelude::Stmt;
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::rules::flake8_slots::rules::helpers::has_slots;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for subclasses of `collections.namedtuple` that lack a `__slots__`
|
||||||
|
/// definition.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// In Python, the `__slots__` attribute allows you to explicitly define the
|
||||||
|
/// attributes (instance variables) that a class can have. By default, Python
|
||||||
|
/// uses a dictionary to store an object's attributes, which incurs some memory
|
||||||
|
/// overhead. However, when `__slots__` is defined, Python uses a more compact
|
||||||
|
/// internal structure to store the object's attributes, resulting in memory
|
||||||
|
/// savings.
|
||||||
|
///
|
||||||
|
/// Subclasses of `namedtuple` inherit all the attributes and methods of the
|
||||||
|
/// built-in `namedtuple` class. Since tuples are typically immutable, they
|
||||||
|
/// don't require additional attributes beyond what the `namedtuple` class
|
||||||
|
/// provides. Defining `__slots__` for subclasses of `namedtuple` prevents the
|
||||||
|
/// creation of a dictionary for each instance, reducing memory consumption.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// from collections import namedtuple
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// class Foo(namedtuple("foo", ["str", "int"])):
|
||||||
|
/// pass
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// from collections import namedtuple
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// class Foo(namedtuple("foo", ["str", "int"])):
|
||||||
|
/// __slots__ = ()
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## References
|
||||||
|
/// - [Python documentation: `__slots__`](https://docs.python.org/3.7/reference/datamodel.html#slots)
|
||||||
|
#[violation]
|
||||||
|
pub struct NoSlotsInNamedtupleSubclass;
|
||||||
|
|
||||||
|
impl Violation for NoSlotsInNamedtupleSubclass {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
format!("Subclasses of `collections.namedtuple()` should define `__slots__`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SLOT002
|
||||||
|
pub(crate) fn no_slots_in_namedtuple_subclass(
|
||||||
|
checker: &mut Checker,
|
||||||
|
stmt: &Stmt,
|
||||||
|
class: &StmtClassDef,
|
||||||
|
) {
|
||||||
|
if class.bases.iter().any(|base| {
|
||||||
|
let Expr::Call(ast::ExprCall { func, .. }) = base else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
checker
|
||||||
|
.semantic_model()
|
||||||
|
.resolve_call_path(func)
|
||||||
|
.map_or(false, |call_path| {
|
||||||
|
matches!(call_path.as_slice(), ["collections", "namedtuple"])
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
if !has_slots(&class.body) {
|
||||||
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
|
NoSlotsInNamedtupleSubclass,
|
||||||
|
identifier_range(stmt, checker.locator),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
use rustpython_parser::ast::{Stmt, StmtClassDef};
|
||||||
|
|
||||||
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::helpers::identifier_range;
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::rules::flake8_slots::rules::helpers::has_slots;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for subclasses of `str` that lack a `__slots__` definition.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// In Python, the `__slots__` attribute allows you to explicitly define the
|
||||||
|
/// attributes (instance variables) that a class can have. By default, Python
|
||||||
|
/// uses a dictionary to store an object's attributes, which incurs some memory
|
||||||
|
/// overhead. However, when `__slots__` is defined, Python uses a more compact
|
||||||
|
/// internal structure to store the object's attributes, resulting in memory
|
||||||
|
/// savings.
|
||||||
|
///
|
||||||
|
/// Subclasses of `str` inherit all the attributes and methods of the built-in
|
||||||
|
/// `str` class. Since strings are typically immutable, they don't require
|
||||||
|
/// additional attributes beyond what the `str` class provides. Defining
|
||||||
|
/// `__slots__` for subclasses of `str` prevents the creation of a dictionary
|
||||||
|
/// for each instance, reducing memory consumption.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// class Foo(str):
|
||||||
|
/// pass
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// class Foo(str):
|
||||||
|
/// __slots__ = ()
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## References
|
||||||
|
/// - [Python documentation: `__slots__`](https://docs.python.org/3.7/reference/datamodel.html#slots)
|
||||||
|
#[violation]
|
||||||
|
pub struct NoSlotsInStrSubclass;
|
||||||
|
|
||||||
|
impl Violation for NoSlotsInStrSubclass {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
format!("Subclasses of `str` should define `__slots__`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SLOT000
|
||||||
|
pub(crate) fn no_slots_in_str_subclass(checker: &mut Checker, stmt: &Stmt, class: &StmtClassDef) {
|
||||||
|
if class.bases.iter().any(|base| {
|
||||||
|
checker
|
||||||
|
.semantic_model()
|
||||||
|
.resolve_call_path(base)
|
||||||
|
.map_or(false, |call_path| {
|
||||||
|
matches!(call_path.as_slice(), ["" | "builtins", "str"])
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
if !has_slots(&class.body) {
|
||||||
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
|
NoSlotsInStrSubclass,
|
||||||
|
identifier_range(stmt, checker.locator),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
use rustpython_parser::ast::{Stmt, StmtClassDef};
|
||||||
|
|
||||||
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::helpers::{identifier_range, map_subscript};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::rules::flake8_slots::rules::helpers::has_slots;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for subclasses of `tuple` that lack a `__slots__` definition.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// In Python, the `__slots__` attribute allows you to explicitly define the
|
||||||
|
/// attributes (instance variables) that a class can have. By default, Python
|
||||||
|
/// uses a dictionary to store an object's attributes, which incurs some memory
|
||||||
|
/// overhead. However, when `__slots__` is defined, Python uses a more compact
|
||||||
|
/// internal structure to store the object's attributes, resulting in memory
|
||||||
|
/// savings.
|
||||||
|
///
|
||||||
|
/// Subclasses of `tuple` inherit all the attributes and methods of the
|
||||||
|
/// built-in `tuple` class. Since tuples are typically immutable, they don't
|
||||||
|
/// require additional attributes beyond what the `tuple` class provides.
|
||||||
|
/// Defining `__slots__` for subclasses of `tuple` prevents the creation of a
|
||||||
|
/// dictionary for each instance, reducing memory consumption.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// class Foo(tuple):
|
||||||
|
/// pass
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// class Foo(tuple):
|
||||||
|
/// __slots__ = ()
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## References
|
||||||
|
/// - [Python documentation: `__slots__`](https://docs.python.org/3.7/reference/datamodel.html#slots)
|
||||||
|
#[violation]
|
||||||
|
pub struct NoSlotsInTupleSubclass;
|
||||||
|
|
||||||
|
impl Violation for NoSlotsInTupleSubclass {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
format!("Subclasses of `tuple` should define `__slots__`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SLOT001
|
||||||
|
pub(crate) fn no_slots_in_tuple_subclass(checker: &mut Checker, stmt: &Stmt, class: &StmtClassDef) {
|
||||||
|
if class.bases.iter().any(|base| {
|
||||||
|
checker
|
||||||
|
.semantic_model()
|
||||||
|
.resolve_call_path(map_subscript(base))
|
||||||
|
.map_or(false, |call_path| {
|
||||||
|
matches!(call_path.as_slice(), ["" | "builtins", "tuple"])
|
||||||
|
|| checker
|
||||||
|
.semantic_model()
|
||||||
|
.match_typing_call_path(&call_path, "Tuple")
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
if !has_slots(&class.body) {
|
||||||
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
|
NoSlotsInTupleSubclass,
|
||||||
|
identifier_range(stmt, checker.locator),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_slots/mod.rs
|
||||||
|
---
|
||||||
|
SLOT000.py:1:7: SLOT000 Subclasses of `str` should define `__slots__`
|
||||||
|
|
|
||||||
|
1 | class Bad(str): # SLOT000
|
||||||
|
| ^^^ SLOT000
|
||||||
|
2 | pass
|
||||||
|
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_slots/mod.rs
|
||||||
|
---
|
||||||
|
SLOT001.py:1:7: SLOT001 Subclasses of `tuple` should define `__slots__`
|
||||||
|
|
|
||||||
|
1 | class Bad(tuple): # SLOT001
|
||||||
|
| ^^^ SLOT001
|
||||||
|
2 | pass
|
||||||
|
|
|
||||||
|
|
||||||
|
SLOT001.py:12:7: SLOT001 Subclasses of `tuple` should define `__slots__`
|
||||||
|
|
|
||||||
|
12 | class Bad(Tuple): # SLOT001
|
||||||
|
| ^^^ SLOT001
|
||||||
|
13 | pass
|
||||||
|
|
|
||||||
|
|
||||||
|
SLOT001.py:16:7: SLOT001 Subclasses of `tuple` should define `__slots__`
|
||||||
|
|
|
||||||
|
16 | class Bad(Tuple[str, int, float]): # SLOT001
|
||||||
|
| ^^^ SLOT001
|
||||||
|
17 | pass
|
||||||
|
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_slots/mod.rs
|
||||||
|
---
|
||||||
|
SLOT002.py:5:7: SLOT002 Subclasses of `collections.namedtuple()` should define `__slots__`
|
||||||
|
|
|
||||||
|
5 | class Bad(namedtuple("foo", ["str", "int"])): # SLOT002
|
||||||
|
| ^^^ SLOT002
|
||||||
|
6 | pass
|
||||||
|
|
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ pub mod flake8_raise;
|
||||||
pub mod flake8_return;
|
pub mod flake8_return;
|
||||||
pub mod flake8_self;
|
pub mod flake8_self;
|
||||||
pub mod flake8_simplify;
|
pub mod flake8_simplify;
|
||||||
|
pub mod flake8_slots;
|
||||||
pub mod flake8_tidy_imports;
|
pub mod flake8_tidy_imports;
|
||||||
pub mod flake8_todos;
|
pub mod flake8_todos;
|
||||||
pub mod flake8_type_checking;
|
pub mod flake8_type_checking;
|
||||||
|
|
|
@ -62,6 +62,7 @@ natively, including:
|
||||||
- [flake8-return](https://pypi.org/project/flake8-return/)
|
- [flake8-return](https://pypi.org/project/flake8-return/)
|
||||||
- [flake8-self](https://pypi.org/project/flake8-self/)
|
- [flake8-self](https://pypi.org/project/flake8-self/)
|
||||||
- [flake8-simplify](https://pypi.org/project/flake8-simplify/)
|
- [flake8-simplify](https://pypi.org/project/flake8-simplify/)
|
||||||
|
- [flake8-slots](https://pypi.org/project/flake8-slots/)
|
||||||
- [flake8-super](https://pypi.org/project/flake8-super/)
|
- [flake8-super](https://pypi.org/project/flake8-super/)
|
||||||
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
|
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
|
||||||
- [flake8-todos](https://pypi.org/project/flake8-todos/)
|
- [flake8-todos](https://pypi.org/project/flake8-todos/)
|
||||||
|
@ -162,6 +163,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||||
- [flake8-return](https://pypi.org/project/flake8-return/)
|
- [flake8-return](https://pypi.org/project/flake8-return/)
|
||||||
- [flake8-self](https://pypi.org/project/flake8-self/)
|
- [flake8-self](https://pypi.org/project/flake8-self/)
|
||||||
- [flake8-simplify](https://pypi.org/project/flake8-simplify/)
|
- [flake8-simplify](https://pypi.org/project/flake8-simplify/)
|
||||||
|
- [flake8-slots](https://pypi.org/project/flake8-slots/)
|
||||||
- [flake8-super](https://pypi.org/project/flake8-super/)
|
- [flake8-super](https://pypi.org/project/flake8-super/)
|
||||||
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
|
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
|
||||||
- [flake8-todos](https://pypi.org/project/flake8-todos/)
|
- [flake8-todos](https://pypi.org/project/flake8-todos/)
|
||||||
|
|
6
ruff.schema.json
generated
6
ruff.schema.json
generated
|
@ -2417,6 +2417,12 @@
|
||||||
"SLF0",
|
"SLF0",
|
||||||
"SLF00",
|
"SLF00",
|
||||||
"SLF001",
|
"SLF001",
|
||||||
|
"SLOT",
|
||||||
|
"SLOT0",
|
||||||
|
"SLOT00",
|
||||||
|
"SLOT000",
|
||||||
|
"SLOT001",
|
||||||
|
"SLOT002",
|
||||||
"T",
|
"T",
|
||||||
"T1",
|
"T1",
|
||||||
"T10",
|
"T10",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue