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
|
||||
def random_property(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
|
||||
#[violation]
|
||||
pub struct DjangoUnorderedBodyContentInModel {
|
||||
elem_type: ContentType,
|
||||
before: ContentType,
|
||||
element_type: ContentType,
|
||||
prev_element_type: ContentType,
|
||||
}
|
||||
|
||||
impl Violation for DjangoUnorderedBodyContentInModel {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let DjangoUnorderedBodyContentInModel { elem_type, before } = self;
|
||||
format!("Order of model's inner classes, methods, and fields does not follow the Django Style Guide: {elem_type} should come before {before}")
|
||||
let DjangoUnorderedBodyContentInModel {
|
||||
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)]
|
||||
pub(crate) enum ContentType {
|
||||
enum ContentType {
|
||||
FieldDeclaration,
|
||||
ManagerDeclaration,
|
||||
MetaClass,
|
||||
|
|
@ -149,24 +152,38 @@ pub(crate) fn unordered_body_content_in_model(
|
|||
{
|
||||
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() {
|
||||
let Some(current_element_type) = get_element_type(element, checker.semantic()) else {
|
||||
let Some(element_type) = get_element_type(element, checker.semantic()) else {
|
||||
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()
|
||||
.find(|&&element_type| element_type > current_element_type) else {
|
||||
elements_type_found.push(current_element_type);
|
||||
continue;
|
||||
};
|
||||
let diagnostic = Diagnostic::new(
|
||||
DjangoUnorderedBodyContentInModel {
|
||||
elem_type: current_element_type,
|
||||
before: element_type,
|
||||
},
|
||||
element.range(),
|
||||
);
|
||||
checker.diagnostics.push(diagnostic);
|
||||
.find(|&&prev_element_type| prev_element_type > element_type)
|
||||
{
|
||||
let diagnostic = Diagnostic::new(
|
||||
DjangoUnorderedBodyContentInModel {
|
||||
element_type,
|
||||
prev_element_type,
|
||||
},
|
||||
element.range(),
|
||||
);
|
||||
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.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