diff --git a/crates/ruff/resources/test/fixtures/flake8_django/DJ012.py b/crates/ruff/resources/test/fixtures/flake8_django/DJ012.py index fc0ffd0cbb..a0f8d9da22 100644 --- a/crates/ruff/resources/test/fixtures/flake8_django/DJ012.py +++ b/crates/ruff/resources/test/fixtures/flake8_django/DJ012.py @@ -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) diff --git a/crates/ruff/src/rules/flake8_django/rules/unordered_body_content_in_model.rs b/crates/ruff/src/rules/flake8_django/rules/unordered_body_content_in_model.rs index 6404a105f6..d3734537a6 100644 --- a/crates/ruff/src/rules/flake8_django/rules/unordered_body_content_in_model.rs +++ b/crates/ruff/src/rules/flake8_django/rules/unordered_body_content_in_model.rs @@ -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); + } } } diff --git a/crates/ruff/src/rules/flake8_django/snapshots/ruff__rules__flake8_django__tests__DJ012_DJ012.py.snap b/crates/ruff/src/rules/flake8_django/snapshots/ruff__rules__flake8_django__tests__DJ012_DJ012.py.snap index 835df4524c..d187a99a41 100644 --- a/crates/ruff/src/rules/flake8_django/snapshots/ruff__rules__flake8_django__tests__DJ012_DJ012.py.snap +++ b/crates/ruff/src/rules/flake8_django/snapshots/ruff__rules__flake8_django__tests__DJ012_DJ012.py.snap @@ -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 + | +