Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 227 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/ruff/RUF071.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
"""Tests for RUF071 (incorrect-decorator-order)."""

from abc import abstractmethod, abstractproperty, abstractclassmethod, abstractstaticmethod
from contextlib import contextmanager, asynccontextmanager
from functools import cache, cached_property, lru_cache, wraps
import abc
import functools

# ===== Errors =====

# --- Core: abstractmethod above descriptors ---

class AbstractAboveProperty:
@abstractmethod # RUF071
@property
def foo(self): ...

class AbstractAboveClassmethod:
@abstractmethod # RUF071
@classmethod
def foo(cls): ...

class AbstractAboveStaticmethod:
@abstractmethod # RUF071
@staticmethod
def foo(): ...

# --- Core: contextmanager above descriptors ---

class ContextManagerAboveStaticmethod:
@contextmanager # RUF071
@staticmethod
def foo(): ...

class ContextManagerAboveClassmethod:
@contextmanager # RUF071
@classmethod
def foo(cls): ...

class AsyncContextManagerAboveStaticmethod:
@asynccontextmanager # RUF071
@staticmethod
def foo(): ...

class AsyncContextManagerAboveClassmethod:
@asynccontextmanager # RUF071
@classmethod
def foo(cls): ...

# --- Functools: caching decorators above descriptors ---

class CacheAboveProperty:
@cache # RUF071
@property
def foo(self): ...

class LruCacheAboveProperty:
@lru_cache # RUF071
@property
def foo(self): ...

class CacheAboveClassmethod:
@cache # RUF071
@classmethod
def foo(cls): ...

class LruCacheAboveClassmethod:
@lru_cache # RUF071
@classmethod
def foo(cls): ...

class CacheAboveCachedProperty:
@cache # RUF071
@cached_property
def foo(self): ...

class LruCacheAboveCachedProperty:
@lru_cache # RUF071
@cached_property
def foo(self): ...

class ClassmethodAboveCachedProperty:
@classmethod # RUF071
@cached_property
def foo(cls): ...

# --- Qualified imports ---

class QualifiedAbstractAboveProperty:
@abc.abstractmethod # RUF071
@property
def foo(self): ...

class QualifiedLruCacheAboveProperty:
@functools.lru_cache # RUF071
@property
def foo(self): ...

# --- Deprecated abc forms ---

class DeprecatedAbstractPropertyBelowAbstractmethod:
@abstractmethod # RUF071
@abstractproperty
def foo(self): ...

class DeprecatedAbstractClassmethodBelowAbstractmethod:
@abstractmethod # RUF071
@abstractclassmethod
def foo(cls): ...

class DeprecatedAbstractStaticmethodBelowAbstractmethod:
@abstractmethod # RUF071
@abstractstaticmethod
def foo(): ...

# --- Decorator call form ---

class LruCacheCallAboveProperty:
@lru_cache() # RUF071
@property
def foo(self): ...

class LruCacheCallWithArgsAboveProperty:
@lru_cache(maxsize=128) # RUF071
@property
def foo(self): ...

# --- Multiple violations on same function (adjacent pairs only) ---

class MultipleViolations:
@abstractmethod # RUF071
@property
@contextmanager # RUF071
@classmethod
def foo(cls): ...

# ===== No errors =====

# --- Correct orderings ---

class CorrectPropertyAboveAbstractmethod:
@property
@abstractmethod
def foo(self): ...

class CorrectClassmethodAboveAbstractmethod:
@classmethod
@abstractmethod
def foo(cls): ...

class CorrectStaticmethodAboveAbstractmethod:
@staticmethod
@abstractmethod
def foo(): ...

class CorrectStaticmethodAboveContextmanager:
@staticmethod
@contextmanager
def foo(): ...

class CorrectClassmethodAboveContextmanager:
@classmethod
@contextmanager
def foo(cls): ...

class CorrectPropertyAboveCache:
@property
@cache
def foo(self): ...

class CorrectPropertyAboveLruCache:
@property
@lru_cache
def foo(self): ...

class CorrectAbstractmethodAboveCachedProperty:
@abstractmethod
@cached_property
def foo(self): ...

class CachedPropertyAboveAbstractmethod:
@cached_property
@abstractmethod
def foo(self): ...

# --- Non-adjacent known-bad pair (only adjacent pairs are checked) ---

class InterleavedDecorator:
@abstractmethod
@some_other_decorator
@property
def foo(self): ...

# --- Single decorators ---

class SingleAbstractmethod:
@abstractmethod
def foo(self): ...

class SingleProperty:
@property
def foo(self): ...

# --- Unrelated decorators only ---

class UnrelatedDecorators:
@some_decorator
@another_decorator
def foo(self): ...

# --- Same decorator twice ---

class DuplicateDecorator:
@abstractmethod
@abstractmethod
def foo(self): ...

# --- functools.wraps is not checked ---

class WrapsWithAnything:
@wraps(some_func)
@property
def foo(self): ...

@abstractmethod
@wraps(some_func)
def bar(self): ...
3 changes: 3 additions & 0 deletions crates/ruff_linter/src/checkers/ast/analyze/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.is_rule_enabled(Rule::DjangoNonLeadingReceiverDecorator) {
flake8_django::rules::non_leading_receiver_decorator(checker, decorator_list);
}
if checker.is_rule_enabled(Rule::IncorrectDecoratorOrder) {
ruff::rules::incorrect_decorator_order(checker, decorator_list);
}
if checker.is_rule_enabled(Rule::FastApiRedundantResponseModel) {
fastapi::rules::fastapi_redundant_response_model(checker, function_def);
}
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "068") => rules::ruff::rules::DuplicateEntryInDunderAll,
(Ruff, "069") => rules::ruff::rules::FloatEqualityComparison,
(Ruff, "070") => rules::ruff::rules::UnnecessaryAssignBeforeYield,
(Ruff, "071") => rules::ruff::rules::IncorrectDecoratorOrder,

(Ruff, "100") => rules::ruff::rules::UnusedNOQA,
(Ruff, "101") => rules::ruff::rules::RedirectedNOQA,
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_linter/src/registry/rule_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use ruff_macros::CacheKey;

use crate::registry::Rule;

const RULESET_SIZE: usize = 15;
const RULESET_SIZE: usize = 16;

/// A set of [`Rule`]s.
///
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/rules/ruff/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,7 @@ mod tests {
#[test_case(Rule::ImplicitClassVarInDataclass, Path::new("RUF045.py"))]
#[test_case(Rule::FloatEqualityComparison, Path::new("RUF069.py"))]
#[test_case(Rule::UnnecessaryAssignBeforeYield, Path::new("RUF070.py"))]
#[test_case(Rule::IncorrectDecoratorOrder, Path::new("RUF071.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",
Expand Down
Loading
Loading