mirror of https://github.com/astral-sh/ruff
[`flake8-django`] Skip duplicate violations in `DJ012` (#5469)
## Summary
This PR reduces the noise from `DJ012` by emitting a single violation
when you have multiple consecutive violations of the same "type".
For example, given:
```py
class MultipleConsecutiveFields(models.Model):
"""Model that contains multiple out-of-order field definitions in a row."""
class Meta:
verbose_name = "test"
first_name = models.CharField(max_length=32)
last_name = models.CharField(max_length=32)
```
It's convenient to only error on `first_name`, and not `last_name`,
since we're really flagging that the _section_ is out-of-order.
Closes #5465.
This commit is contained in:
parent
d0b2fffb87
commit
6cc04d64e4
|
|
@ -111,3 +111,19 @@ class PerfectlyFine(models.Model):
|
||||||
@property
|
@property
|
||||||
def random_property(self):
|
def random_property(self):
|
||||||
return "%s" % self
|
return "%s" % self
|
||||||
|
|
||||||
|
|
||||||
|
class MultipleConsecutiveFields(models.Model):
|
||||||
|
"""Model that contains multiple out-of-order field definitions in a row."""
|
||||||
|
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "test"
|
||||||
|
|
||||||
|
first_name = models.CharField(max_length=32)
|
||||||
|
last_name = models.CharField(max_length=32)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
middle_name = models.CharField(max_length=32)
|
||||||
|
|
|
||||||
|
|
@ -63,20 +63,23 @@ use super::helpers;
|
||||||
/// [Django Style Guide]: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#model-style
|
/// [Django Style Guide]: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#model-style
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct DjangoUnorderedBodyContentInModel {
|
pub struct DjangoUnorderedBodyContentInModel {
|
||||||
elem_type: ContentType,
|
element_type: ContentType,
|
||||||
before: ContentType,
|
prev_element_type: ContentType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Violation for DjangoUnorderedBodyContentInModel {
|
impl Violation for DjangoUnorderedBodyContentInModel {
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
let DjangoUnorderedBodyContentInModel { elem_type, before } = self;
|
let DjangoUnorderedBodyContentInModel {
|
||||||
format!("Order of model's inner classes, methods, and fields does not follow the Django Style Guide: {elem_type} should come before {before}")
|
element_type,
|
||||||
|
prev_element_type,
|
||||||
|
} = self;
|
||||||
|
format!("Order of model's inner classes, methods, and fields does not follow the Django Style Guide: {element_type} should come before {prev_element_type}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
|
||||||
pub(crate) enum ContentType {
|
enum ContentType {
|
||||||
FieldDeclaration,
|
FieldDeclaration,
|
||||||
ManagerDeclaration,
|
ManagerDeclaration,
|
||||||
MetaClass,
|
MetaClass,
|
||||||
|
|
@ -149,24 +152,38 @@ pub(crate) fn unordered_body_content_in_model(
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut elements_type_found = Vec::new();
|
|
||||||
|
// Track all the element types we've seen so far.
|
||||||
|
let mut element_types = Vec::new();
|
||||||
|
let mut prev_element_type = None;
|
||||||
for element in body.iter() {
|
for element in body.iter() {
|
||||||
let Some(current_element_type) = get_element_type(element, checker.semantic()) else {
|
let Some(element_type) = get_element_type(element, checker.semantic()) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(&element_type) = elements_type_found
|
|
||||||
|
// Skip consecutive elements of the same type. It's less noisy to only report
|
||||||
|
// violations at type boundaries (e.g., avoid raising a violation for _every_
|
||||||
|
// field declaration that's out of order).
|
||||||
|
if prev_element_type == Some(element_type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev_element_type = Some(element_type);
|
||||||
|
|
||||||
|
if let Some(&prev_element_type) = element_types
|
||||||
.iter()
|
.iter()
|
||||||
.find(|&&element_type| element_type > current_element_type) else {
|
.find(|&&prev_element_type| prev_element_type > element_type)
|
||||||
elements_type_found.push(current_element_type);
|
{
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let diagnostic = Diagnostic::new(
|
let diagnostic = Diagnostic::new(
|
||||||
DjangoUnorderedBodyContentInModel {
|
DjangoUnorderedBodyContentInModel {
|
||||||
elem_type: current_element_type,
|
element_type,
|
||||||
before: element_type,
|
prev_element_type,
|
||||||
},
|
},
|
||||||
element.range(),
|
element.range(),
|
||||||
);
|
);
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
|
} else {
|
||||||
|
element_types.push(element_type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,4 +37,21 @@ DJ012.py:69:5: DJ012 Order of model's inner classes, methods, and fields does no
|
||||||
| |____________^ DJ012
|
| |____________^ DJ012
|
||||||
|
|
|
|
||||||
|
|
||||||
|
DJ012.py:123:5: DJ012 Order of model's inner classes, methods, and fields does not follow the Django Style Guide: field declaration should come before `Meta` class
|
||||||
|
|
|
||||||
|
121 | verbose_name = "test"
|
||||||
|
122 |
|
||||||
|
123 | first_name = models.CharField(max_length=32)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DJ012
|
||||||
|
124 | last_name = models.CharField(max_length=32)
|
||||||
|
|
|
||||||
|
|
||||||
|
DJ012.py:129:5: DJ012 Order of model's inner classes, methods, and fields does not follow the Django Style Guide: field declaration should come before `Meta` class
|
||||||
|
|
|
||||||
|
127 | pass
|
||||||
|
128 |
|
||||||
|
129 | middle_name = models.CharField(max_length=32)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DJ012
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue