mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:18 +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::{assert_messages, settings};
|
||||
|
||||
#[test_case(Rule::AsyncioDanglingTask, Path::new("RUF006.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_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_1.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_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(
|
||||
Rule::UnnecessaryIterableAllocationForFirstElement,
|
||||
Path::new("RUF015.py")
|
||||
|
@ -51,7 +60,6 @@ mod tests {
|
|||
#[test_case(Rule::UnsortedDunderAll, Path::new("RUF022.py"))]
|
||||
#[test_case(Rule::UnsortedDunderSlots, Path::new("RUF023.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::MissingFStringSyntax, Path::new("RUF027_0.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::IncorrectlyParenthesizedTupleInSubscript, Path::new("RUF031.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::UselessIfElse, Path::new("RUF034.py"))]
|
||||
#[test_case(Rule::NoneNotAtEndOfUnion, Path::new("RUF036.py"))]
|
||||
#[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.pyi"))]
|
||||
#[test_case(Rule::InvalidAssertMessageLiteralArgument, Path::new("RUF040.py"))]
|
||||
#[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.py"))]
|
||||
#[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.pyi"))]
|
||||
#[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::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<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
@ -403,15 +412,6 @@ mod tests {
|
|||
}
|
||||
|
||||
#[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_1.py"))]
|
||||
#[test_case(Rule::UnrawRePattern, Path::new("RUF039.py"))]
|
||||
|
|
|
@ -81,10 +81,6 @@ pub(crate) fn function_call_in_dataclass_default(
|
|||
return;
|
||||
};
|
||||
|
||||
if dataclass_kind.is_attrs() && checker.settings.preview.is_disabled() {
|
||||
return;
|
||||
}
|
||||
|
||||
let attrs_auto_attribs = match dataclass_kind {
|
||||
DataclassKind::Stdlib => None,
|
||||
|
||||
|
|
|
@ -101,16 +101,6 @@ pub(super) enum DataclassKind {
|
|||
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`),
|
||||
/// or `None` if the class is not a dataclass.
|
||||
pub(super) fn dataclass_kind<'a>(
|
||||
|
|
|
@ -66,12 +66,9 @@ pub(crate) fn mutable_class_default(checker: &mut Checker, class_def: &ast::Stmt
|
|||
&& !is_final_annotation(annotation, checker.semantic())
|
||||
&& !is_immutable_annotation(annotation, checker.semantic(), &[])
|
||||
{
|
||||
if let Some((dataclass_kind, _)) = dataclass_kind(class_def, checker.semantic())
|
||||
{
|
||||
if dataclass_kind.is_stdlib() || checker.settings.preview.is_enabled() {
|
||||
if dataclass_kind(class_def, checker.semantic()).is_some() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid, e.g., Pydantic and msgspec models, which end up copying defaults on instance creation.
|
||||
if has_default_copy_semantics(class_def, checker.semantic()) {
|
||||
|
|
|
@ -68,14 +68,10 @@ impl Violation for MutableDataclassDefault {
|
|||
pub(crate) fn mutable_dataclass_default(checker: &mut Checker, class_def: &ast::StmtClassDef) {
|
||||
let semantic = checker.semantic();
|
||||
|
||||
let Some((dataclass_kind, _)) = dataclass_kind(class_def, semantic) else {
|
||||
if dataclass_kind(class_def, semantic).is_none() {
|
||||
return;
|
||||
};
|
||||
|
||||
if dataclass_kind.is_attrs() && checker.settings.preview.is_disabled() {
|
||||
return;
|
||||
}
|
||||
|
||||
for statement in &class_def.body {
|
||||
let Stmt::AnnAssign(ast::StmtAnnAssign {
|
||||
annotation,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue