Repository: wemake-services/wemake-python-styleguide

Issue:
Add `__init_subclass__` to the order of methods in WPS338
# Rule request

<!-- Thanks for coming up with a proposal! -->

## Thesis

`__init_subclass__` ([PEP 487](https://peps.python.org/pep-0487/)) should be added to `WPS338` order. The resulting list would look like this:
- `__init_subclass__`
- `__new__`
- `__init__`
- `__call__`
- `__await__`
- public and magic methods
- protected methods
- private methods (we discourage using them)

## Reasoning
In current order I see that construction and initialisation methods are given priority and ordered from instance construction to instance initialisation.

Chronologically, before all that come class construction and its initialisation. First happens in meta class, second can also be done in `__init_subclass__`. Therefore it naturally should come before class `__new__` and `__init__` methods.


## Second thoughts
It might be worth considering doing the same with `__class_getitem__`, since it mostly used for generics construction. But since it might not be even that interesting from user perspective, I'm hesitant at proposing it.

It also should be possible to ignore specific methods by placing #noqa WPS338 at the line of their definition. But that means that reporting will also be done per method instead of current per class reporting, which can be both good (you can see what method is causing the error) and bad (there may be too many errors per class)

<!--
Why do you think this is a good idea?

Please, include some strong points and considerations.
-->

<!--

  Love wemake-python-styleguide? ❤️
  Do you want your features to be implemented faster?

  Please consider supporting our collective:
👉  https://opencollective.com/wemake-python-styleguide/donate

-->


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services) Report
> Merging [#2411](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services) (3786a48) into [master](https://codecov.io/gh/wemake-services/wemake-python-styleguide/commit/3daaea47fdc3bca4d8ea64e0c35761b7325c7479?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services) (3daaea4) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@            Coverage Diff            @@
##            master     #2411   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          120       120           
  Lines         6408      6382   -26     
  Branches      1445      1438    -7     
=========================================
- Hits          6408      6382   -26     
```


| [Impacted Files](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services) | Coverage Δ | |
|---|---|---|
| [wemake\_python\_styleguide/violations/consistency.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL3Zpb2xhdGlvbnMvY29uc2lzdGVuY3kucHk=) | `100.00% <ø> (ø)` | |
| [wemake\_python\_styleguide/visitors/ast/classes.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL3Zpc2l0b3JzL2FzdC9jbGFzc2VzLnB5) | `100.00% <ø> (ø)` | |
| [wemake\_python\_styleguide/logic/walk.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2xvZ2ljL3dhbGsucHk=) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/compat/nodes.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2NvbXBhdC9ub2Rlcy5weQ==) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/compat/routing.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2NvbXBhdC9yb3V0aW5nLnB5) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/logic/tree/ifs.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2xvZ2ljL3RyZWUvaWZzLnB5) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/logic/safe\_eval.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2xvZ2ljL3NhZmVfZXZhbC5weQ==) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/compat/packaging.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2NvbXBhdC9wYWNrYWdpbmcucHk=) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/logic/tree/calls.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2xvZ2ljL3RyZWUvY2FsbHMucHk=) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/logic/scopes/defs.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2xvZ2ljL3Njb3Blcy9kZWZzLnB5) | `100.00% <0.00%> (ø)` | |
| ... and [5 more](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services). Last update [3daaea4...3786a48](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/2411?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0cd64aea..1c438e33 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,10 @@ Semantic versioning in our case means:
   But, in the future we might change the configuration names/logic,
   change the client facing API, change code conventions significantly, etc.
 
+## 0.17.1
+
+### Features
+- Adds `__init_subclass__` in the beginning of accepted methods order as per WPS338
 
 ## 0.16.1
 
diff --git a/wemake_python_styleguide/violations/consistency.py b/wemake_python_styleguide/violations/consistency.py
index 594bdd67..78020975 100644
--- a/wemake_python_styleguide/violations/consistency.py
+++ b/wemake_python_styleguide/violations/consistency.py
@@ -1564,6 +1564,7 @@ class WrongMethodOrderViolation(ASTViolation):
 
     We follow the same ordering:
 
+    - ``__init_subclass__``
     - ``__new__``
     - ``__init__``
     - ``__call__``
diff --git a/wemake_python_styleguide/visitors/ast/classes.py b/wemake_python_styleguide/visitors/ast/classes.py
index 1a08021b..4ff53cab 100644
--- a/wemake_python_styleguide/visitors/ast/classes.py
+++ b/wemake_python_styleguide/visitors/ast/classes.py
@@ -395,7 +395,8 @@ class ClassMethodOrderVisitor(base.BaseNodeVisitor):
 
     def _ideal_order(self, first: str) -> int:
         base_methods_order = {
-            '__new__': 6,  # highest priority
+            '__init_subclass__': 7,  # highest priority
+            '__new__': 6,
             '__init__': 5,
             '__call__': 4,
             '__await__': 3,


Test patch:
diff --git a/tests/test_visitors/test_ast/test_classes/test_method_order.py b/tests/test_visitors/test_ast/test_classes/test_method_order.py
index d2303ad9..7969a2bb 100644
--- a/tests/test_visitors/test_ast/test_classes/test_method_order.py
+++ b/tests/test_visitors/test_ast/test_classes/test_method_order.py
@@ -9,6 +9,9 @@ from wemake_python_styleguide.visitors.ast.classes import (
 
 correct_method_order = """
 class Test(object):
+    def __init_subclass__(cls):
+        ...
+
     def __new__(self):
         ...
 
@@ -82,21 +85,25 @@ def test_correct_method_order(
 
 
 @pytest.mark.parametrize(('first', 'second'), [
+    ('__new__', '__init_subclass__'),
     ('__init__', '__new__'),
     ('__call__', '__init__'),
     ('__call__', '__new__'),
     ('__await__', '__call__'),
 
+    ('public', '__init_subclass__'),
     ('public', '__new__'),
     ('public', '__init__'),
     ('public', '__call__'),
     ('public', '__await__'),
 
+    ('__magic__', '__init_subclass__'),
     ('__magic__', '__new__'),
     ('__magic__', '__init__'),
     ('__magic__', '__call__'),
     ('__magic__', '__await__'),
 
+    ('_protected', '__init_subclass__'),
     ('_protected', '__new__'),
     ('_protected', '__init__'),
     ('_protected', '__call__'),
@@ -104,6 +111,7 @@ def test_correct_method_order(
     ('_protected', 'public'),
     ('_protected', '__magic__'),
 
+    ('__private', '__init_subclass__'),
     ('__private', '__new__'),
     ('__private', '__init__'),
     ('__private', '__call__'),