mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:16 +00:00
[ruff
] Stabilize: Detect attrs
dataclasses (RUF008
, RUF009
) (#15345)
This commit is contained in:
parent
52aeb8ae11
commit
225dd0a027
8 changed files with 22 additions and 43 deletions
|
@ -25,18 +25,27 @@ mod tests {
|
||||||
use crate::test::{test_path, test_resource_path};
|
use crate::test::{test_path, test_resource_path};
|
||||||
use crate::{assert_messages, settings};
|
use crate::{assert_messages, settings};
|
||||||
|
|
||||||
#[test_case(Rule::AsyncioDanglingTask, Path::new("RUF006.py"))]
|
|
||||||
#[test_case(Rule::CollectionLiteralConcatenation, Path::new("RUF005.py"))]
|
#[test_case(Rule::CollectionLiteralConcatenation, Path::new("RUF005.py"))]
|
||||||
#[test_case(Rule::ExplicitFStringTypeConversion, Path::new("RUF010.py"))]
|
#[test_case(Rule::AsyncioDanglingTask, Path::new("RUF006.py"))]
|
||||||
|
#[test_case(Rule::ZipInsteadOfPairwise, Path::new("RUF007.py"))]
|
||||||
|
#[test_case(Rule::MutableDataclassDefault, Path::new("RUF008.py"))]
|
||||||
|
#[test_case(Rule::MutableDataclassDefault, Path::new("RUF008_attrs.py"))]
|
||||||
#[test_case(Rule::FunctionCallInDataclassDefaultArgument, Path::new("RUF009.py"))]
|
#[test_case(Rule::FunctionCallInDataclassDefaultArgument, Path::new("RUF009.py"))]
|
||||||
|
#[test_case(
|
||||||
|
Rule::FunctionCallInDataclassDefaultArgument,
|
||||||
|
Path::new("RUF009_attrs.py")
|
||||||
|
)]
|
||||||
|
#[test_case(
|
||||||
|
Rule::FunctionCallInDataclassDefaultArgument,
|
||||||
|
Path::new("RUF009_attrs_auto_attribs.py")
|
||||||
|
)]
|
||||||
|
#[test_case(Rule::ExplicitFStringTypeConversion, Path::new("RUF010.py"))]
|
||||||
|
#[test_case(Rule::MutableClassDefault, Path::new("RUF012.py"))]
|
||||||
#[test_case(Rule::ImplicitOptional, Path::new("RUF013_0.py"))]
|
#[test_case(Rule::ImplicitOptional, Path::new("RUF013_0.py"))]
|
||||||
#[test_case(Rule::ImplicitOptional, Path::new("RUF013_1.py"))]
|
#[test_case(Rule::ImplicitOptional, Path::new("RUF013_1.py"))]
|
||||||
#[test_case(Rule::ImplicitOptional, Path::new("RUF013_2.py"))]
|
#[test_case(Rule::ImplicitOptional, Path::new("RUF013_2.py"))]
|
||||||
#[test_case(Rule::ImplicitOptional, Path::new("RUF013_3.py"))]
|
#[test_case(Rule::ImplicitOptional, Path::new("RUF013_3.py"))]
|
||||||
#[test_case(Rule::ImplicitOptional, Path::new("RUF013_4.py"))]
|
#[test_case(Rule::ImplicitOptional, Path::new("RUF013_4.py"))]
|
||||||
#[test_case(Rule::MutableClassDefault, Path::new("RUF012.py"))]
|
|
||||||
#[test_case(Rule::MutableDataclassDefault, Path::new("RUF008.py"))]
|
|
||||||
#[test_case(Rule::ZipInsteadOfPairwise, Path::new("RUF007.py"))]
|
|
||||||
#[test_case(
|
#[test_case(
|
||||||
Rule::UnnecessaryIterableAllocationForFirstElement,
|
Rule::UnnecessaryIterableAllocationForFirstElement,
|
||||||
Path::new("RUF015.py")
|
Path::new("RUF015.py")
|
||||||
|
@ -51,7 +60,6 @@ mod tests {
|
||||||
#[test_case(Rule::UnsortedDunderAll, Path::new("RUF022.py"))]
|
#[test_case(Rule::UnsortedDunderAll, Path::new("RUF022.py"))]
|
||||||
#[test_case(Rule::UnsortedDunderSlots, Path::new("RUF023.py"))]
|
#[test_case(Rule::UnsortedDunderSlots, Path::new("RUF023.py"))]
|
||||||
#[test_case(Rule::MutableFromkeysValue, Path::new("RUF024.py"))]
|
#[test_case(Rule::MutableFromkeysValue, Path::new("RUF024.py"))]
|
||||||
#[test_case(Rule::UnnecessaryEmptyIterableWithinDequeCall, Path::new("RUF037.py"))]
|
|
||||||
#[test_case(Rule::DefaultFactoryKwarg, Path::new("RUF026.py"))]
|
#[test_case(Rule::DefaultFactoryKwarg, Path::new("RUF026.py"))]
|
||||||
#[test_case(Rule::MissingFStringSyntax, Path::new("RUF027_0.py"))]
|
#[test_case(Rule::MissingFStringSyntax, Path::new("RUF027_0.py"))]
|
||||||
#[test_case(Rule::MissingFStringSyntax, Path::new("RUF027_1.py"))]
|
#[test_case(Rule::MissingFStringSyntax, Path::new("RUF027_1.py"))]
|
||||||
|
@ -61,20 +69,21 @@ mod tests {
|
||||||
#[test_case(Rule::AssertWithPrintMessage, Path::new("RUF030.py"))]
|
#[test_case(Rule::AssertWithPrintMessage, Path::new("RUF030.py"))]
|
||||||
#[test_case(Rule::IncorrectlyParenthesizedTupleInSubscript, Path::new("RUF031.py"))]
|
#[test_case(Rule::IncorrectlyParenthesizedTupleInSubscript, Path::new("RUF031.py"))]
|
||||||
#[test_case(Rule::DecimalFromFloatLiteral, Path::new("RUF032.py"))]
|
#[test_case(Rule::DecimalFromFloatLiteral, Path::new("RUF032.py"))]
|
||||||
#[test_case(Rule::UselessIfElse, Path::new("RUF034.py"))]
|
|
||||||
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_0.py"))]
|
|
||||||
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_1.py"))]
|
|
||||||
#[test_case(Rule::PostInitDefault, Path::new("RUF033.py"))]
|
#[test_case(Rule::PostInitDefault, Path::new("RUF033.py"))]
|
||||||
|
#[test_case(Rule::UselessIfElse, Path::new("RUF034.py"))]
|
||||||
#[test_case(Rule::NoneNotAtEndOfUnion, Path::new("RUF036.py"))]
|
#[test_case(Rule::NoneNotAtEndOfUnion, Path::new("RUF036.py"))]
|
||||||
#[test_case(Rule::NoneNotAtEndOfUnion, Path::new("RUF036.pyi"))]
|
#[test_case(Rule::NoneNotAtEndOfUnion, Path::new("RUF036.pyi"))]
|
||||||
|
#[test_case(Rule::UnnecessaryEmptyIterableWithinDequeCall, Path::new("RUF037.py"))]
|
||||||
#[test_case(Rule::RedundantBoolLiteral, Path::new("RUF038.py"))]
|
#[test_case(Rule::RedundantBoolLiteral, Path::new("RUF038.py"))]
|
||||||
#[test_case(Rule::RedundantBoolLiteral, Path::new("RUF038.pyi"))]
|
#[test_case(Rule::RedundantBoolLiteral, Path::new("RUF038.pyi"))]
|
||||||
#[test_case(Rule::InvalidAssertMessageLiteralArgument, Path::new("RUF040.py"))]
|
#[test_case(Rule::InvalidAssertMessageLiteralArgument, Path::new("RUF040.py"))]
|
||||||
#[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.py"))]
|
#[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.py"))]
|
||||||
#[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.pyi"))]
|
#[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.pyi"))]
|
||||||
#[test_case(Rule::IfKeyInDictDel, Path::new("RUF051.py"))]
|
#[test_case(Rule::IfKeyInDictDel, Path::new("RUF051.py"))]
|
||||||
#[test_case(Rule::FalsyDictGetFallback, Path::new("RUF056.py"))]
|
|
||||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"))]
|
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"))]
|
||||||
|
#[test_case(Rule::FalsyDictGetFallback, Path::new("RUF056.py"))]
|
||||||
|
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_0.py"))]
|
||||||
|
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_1.py"))]
|
||||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
|
@ -403,15 +412,6 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case(Rule::UnsafeMarkupUse, Path::new("RUF035.py"))]
|
#[test_case(Rule::UnsafeMarkupUse, Path::new("RUF035.py"))]
|
||||||
#[test_case(
|
|
||||||
Rule::FunctionCallInDataclassDefaultArgument,
|
|
||||||
Path::new("RUF009_attrs.py")
|
|
||||||
)]
|
|
||||||
#[test_case(
|
|
||||||
Rule::FunctionCallInDataclassDefaultArgument,
|
|
||||||
Path::new("RUF009_attrs_auto_attribs.py")
|
|
||||||
)]
|
|
||||||
#[test_case(Rule::MutableDataclassDefault, Path::new("RUF008_attrs.py"))]
|
|
||||||
#[test_case(Rule::MapIntVersionParsing, Path::new("RUF048.py"))]
|
#[test_case(Rule::MapIntVersionParsing, Path::new("RUF048.py"))]
|
||||||
#[test_case(Rule::MapIntVersionParsing, Path::new("RUF048_1.py"))]
|
#[test_case(Rule::MapIntVersionParsing, Path::new("RUF048_1.py"))]
|
||||||
#[test_case(Rule::UnrawRePattern, Path::new("RUF039.py"))]
|
#[test_case(Rule::UnrawRePattern, Path::new("RUF039.py"))]
|
||||||
|
|
|
@ -81,10 +81,6 @@ pub(crate) fn function_call_in_dataclass_default(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if dataclass_kind.is_attrs() && checker.settings.preview.is_disabled() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let attrs_auto_attribs = match dataclass_kind {
|
let attrs_auto_attribs = match dataclass_kind {
|
||||||
DataclassKind::Stdlib => None,
|
DataclassKind::Stdlib => None,
|
||||||
|
|
||||||
|
|
|
@ -101,16 +101,6 @@ pub(super) enum DataclassKind {
|
||||||
Attrs(AttrsAutoAttribs),
|
Attrs(AttrsAutoAttribs),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DataclassKind {
|
|
||||||
pub(super) const fn is_stdlib(self) -> bool {
|
|
||||||
matches!(self, DataclassKind::Stdlib)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) const fn is_attrs(self) -> bool {
|
|
||||||
matches!(self, DataclassKind::Attrs(..))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the kind of dataclass this class definition is (stdlib or `attrs`),
|
/// Return the kind of dataclass this class definition is (stdlib or `attrs`),
|
||||||
/// or `None` if the class is not a dataclass.
|
/// or `None` if the class is not a dataclass.
|
||||||
pub(super) fn dataclass_kind<'a>(
|
pub(super) fn dataclass_kind<'a>(
|
||||||
|
|
|
@ -66,11 +66,8 @@ pub(crate) fn mutable_class_default(checker: &mut Checker, class_def: &ast::Stmt
|
||||||
&& !is_final_annotation(annotation, checker.semantic())
|
&& !is_final_annotation(annotation, checker.semantic())
|
||||||
&& !is_immutable_annotation(annotation, checker.semantic(), &[])
|
&& !is_immutable_annotation(annotation, checker.semantic(), &[])
|
||||||
{
|
{
|
||||||
if let Some((dataclass_kind, _)) = dataclass_kind(class_def, checker.semantic())
|
if dataclass_kind(class_def, checker.semantic()).is_some() {
|
||||||
{
|
continue;
|
||||||
if dataclass_kind.is_stdlib() || checker.settings.preview.is_enabled() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid, e.g., Pydantic and msgspec models, which end up copying defaults on instance creation.
|
// Avoid, e.g., Pydantic and msgspec models, which end up copying defaults on instance creation.
|
||||||
|
|
|
@ -68,14 +68,10 @@ impl Violation for MutableDataclassDefault {
|
||||||
pub(crate) fn mutable_dataclass_default(checker: &mut Checker, class_def: &ast::StmtClassDef) {
|
pub(crate) fn mutable_dataclass_default(checker: &mut Checker, class_def: &ast::StmtClassDef) {
|
||||||
let semantic = checker.semantic();
|
let semantic = checker.semantic();
|
||||||
|
|
||||||
let Some((dataclass_kind, _)) = dataclass_kind(class_def, semantic) else {
|
if dataclass_kind(class_def, semantic).is_none() {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if dataclass_kind.is_attrs() && checker.settings.preview.is_disabled() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for statement in &class_def.body {
|
for statement in &class_def.body {
|
||||||
let Stmt::AnnAssign(ast::StmtAnnAssign {
|
let Stmt::AnnAssign(ast::StmtAnnAssign {
|
||||||
annotation,
|
annotation,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue