Repository: wemake-services/wemake-python-styleguide

Issue:
Create configuration
Currently our linter is full of magic constants like: 
- https://github.com/wemake-services/wemake-python-styleguide/blob/master/wemake_python_styleguide/visitors/high_complexity.py#L44
- https://github.com/wemake-services/wemake-python-styleguide/blob/master/wemake_python_styleguide/visitors/high_complexity.py#L58
- and others marked with `TODO: config` in the source code

What we need is:
0. create a file with default configuration values
1. allow users to override these values with `setup.cfg` or any other standard `flake8` tools
2. test that when configuration is applied linter respects it
Adds "no __author__" validator
Implements the rule `no __author__`.

Note that it does not protect against the case
```
if True:
    __author__ = 'John'
```
because this is simply a strange thing to write (and it would complicate the logic for no good reason).

I've created a set `BAD_MODULE_METADATA_VARIABLES`, just in case other variables need to be added (such as `__version__` or `__copyright__`).


Discussion:
How about that all the configs will be placed in `setup.cfg`, and there will be some parser that will put the settings in the specific visitor?
Yes, that's exactly how I see this feature.

@mcproger are you interested in implementing this feature? 
Yes, I will try to implement this. 
Great! 

Please, ask any questions you have.

Things to keep in mind:
- [ ] we will need to test config parsing is correct
- [ ] we will need to test that config values take effect on actual checks
- [ ] we will need to provide some basic docs, like: there are values `a`, `b`, and `c`, they must be placed in `setup.cfg` under `[..]` section
OK, I'll try to present my first PR as soon as possible.

@sobolevn Why are these constants a sum of integers? 

https://github.com/wemake-services/wemake-python-styleguide/blob/master/wemake_python_styleguide/visitors/high_complexity.py#L51

https://github.com/wemake-services/wemake-python-styleguide/blob/master/wemake_python_styleguide/visitors/high_complexity.py#L58
I have no idea, that was other me: the bad one 😆 
offtop: the idea seams familiar https://github.com/wemake-shit 
We were inspired by this https://github.com/sobolevn/python-code-disasters 😆


@sobolevn Well, we've got plans. Pleeease don't take it as an insult. 

The idea is to store the projects we didn't want to write, but absolutely had to.
No worries! We all have these projects. 

`</offtop>`
@vergeev thanks for implementing this! 

I feel a little bit ashamed for such a low quality docs and tasks for this project.
Since it is an internal beta tool. But, I am happy that you are interested in contributing.

I also agree that `__author__` inside any other blocks should be allowed. 
I can see perfectly valid cases for this variable inside `class`es.

Thanks for contributing!
## Pull Request Test Coverage Report for [Build 16](https://coveralls.io/builds/17062730)

* **14** of **14**  **(100.0%)** changed or added relevant lines in **3** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**0.02%**) to **99.405%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/17062730/badge)](https://coveralls.io/builds/17062730) |
| :-- | --: |
| Change from base [Build 12](https://coveralls.io/builds/16286210): |  0.02% |
| Covered Lines: | 334 |
| Relevant Lines: | 336 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Resolving patch:
diff --git a/wemake_python_styleguide/logic/naming/access.py b/wemake_python_styleguide/logic/naming/access.py
--- a/wemake_python_styleguide/logic/naming/access.py
+++ b/wemake_python_styleguide/logic/naming/access.py
@@ -3,7 +3,7 @@
 from typing_extensions import Final
 
 #: Used as a special name patterns for unused variables, like `_` and `__`.
-_UNUSED_VARIABLE_REGEX: Final = re.compile(r'^_+$')
+_UNUSED_VARIABLE_REGEX: Final = re.compile('^_+$')
 
 
 def is_unused(name: str) -> bool:
diff --git a/wemake_python_styleguide/violations/consistency.py b/wemake_python_styleguide/violations/consistency.py
--- a/wemake_python_styleguide/violations/consistency.py
+++ b/wemake_python_styleguide/violations/consistency.py
@@ -83,6 +83,7 @@
    LineCompriseCarriageReturnViolation
    FloatZeroViolation
    UnpackingIterableToListViolation
+   RawStringNotNeededViolation
 
 Consistency checks
 ------------------
@@ -147,6 +148,7 @@
 .. autoclass:: LineCompriseCarriageReturnViolation
 .. autoclass:: FloatZeroViolation
 .. autoclass:: UnpackingIterableToListViolation
+.. autoclass:: RawStringNotNeededViolation
 
 """
 
@@ -2228,3 +2230,30 @@ class UnpackingIterableToListViolation(ASTViolation):
 
     error_template = 'Found an iterable unpacking to list'
     code = 359
+
+
+@final
+class RawStringNotNeededViolation(TokenizeViolation):
+    r"""
+    Forbid the use of raw strings when there is no backslash in the string.
+
+    Reasoning:
+        Raw string are only needed when dealing with ``\`` in the string.
+
+    Solution:
+        Do not prefix the string with ``r``. Use a normal string instead.
+
+    Example::
+
+        # Correct:
+        r'This is a correct use \n'
+
+        # Wrong:
+        r'This string should not be prefixed with r.'
+
+    .. versionadded:: 0.15.0
+
+    """
+
+    error_template = 'Found an unnecessary use of a raw string: {0}'
+    code = 360
diff --git a/wemake_python_styleguide/visitors/tokenize/primitives.py b/wemake_python_styleguide/visitors/tokenize/primitives.py
--- a/wemake_python_styleguide/visitors/tokenize/primitives.py
+++ b/wemake_python_styleguide/visitors/tokenize/primitives.py
@@ -175,12 +175,14 @@ def visit_string(self, token: tokenize.TokenInfo) -> None:
             WrongMultilineStringViolation
             ImplicitRawStringViolation
             WrongUnicodeEscapeViolation
+            RawStringNotNeededViolation
 
         """
         self._check_correct_multiline(token)
         self._check_string_modifiers(token)
         self._check_implicit_raw_string(token)
         self._check_wrong_unicode_escape(token)
+        self._check_unnecessary_raw_string(token)
 
     def _check_correct_multiline(self, token: tokenize.TokenInfo) -> None:
         _, string_def = split_prefixes(token.string)
@@ -241,6 +243,14 @@ def _check_wrong_unicode_escape(self, token: tokenize.TokenInfo) -> None:
             # character can never be the start of a new backslash escape.
             index += 2
 
+    def _check_unnecessary_raw_string(self, token: tokenize.TokenInfo) -> None:
+        modifiers, string_def = split_prefixes(token.string)
+
+        if 'r' in modifiers.lower() and '\\' not in string_def:
+            self.add_violation(
+                consistency.RawStringNotNeededViolation(token, text=string_def),
+            )
+
 
 @final
 class WrongStringConcatenationVisitor(BaseTokenVisitor):


Test patch:
diff --git a/tests/fixtures/noqa/noqa.py b/tests/fixtures/noqa/noqa.py
--- a/tests/fixtures/noqa/noqa.py
+++ b/tests/fixtures/noqa/noqa.py
@@ -356,7 +356,7 @@ def function(  # noqa: WPS320
                }  # noqa: WPS318
 
 
-string_modifier = R'(s)'  # noqa: WPS321
+string_modifier = R'(\n)'  # noqa: WPS321
 multiline_string = """abc"""  # noqa: WPS322
 modulo_formatting = 'some %s'  # noqa: WPS323
 
@@ -737,3 +737,5 @@ def raise_bad_exception():
 def infinite_loop():
     while True:  # noqa: WPS457
         my_print('forever')
+
+unnecessary_raw_string = r'no backslashes.' # noqa: WPS360
diff --git a/tests/test_checker/test_noqa.py b/tests/test_checker/test_noqa.py
--- a/tests/test_checker/test_noqa.py
+++ b/tests/test_checker/test_noqa.py
@@ -168,6 +168,7 @@
     'WPS357': 0,  # logically unacceptable.
     'WPS358': 1,
     'WPS359': 1,
+    'WPS360': 1,
 
     'WPS400': 0,  # defined in ignored violations.
     'WPS401': 0,  # logically unacceptable.
diff --git a/tests/test_visitors/test_ast/test_complexity/test_overuses/test_overused_string.py b/tests/test_visitors/test_ast/test_complexity/test_overuses/test_overused_string.py
--- a/tests/test_visitors/test_ast/test_complexity/test_overuses/test_overused_string.py
+++ b/tests/test_visitors/test_ast/test_complexity/test_overuses/test_overused_string.py
@@ -199,7 +199,7 @@ def test_string_type_annotations(
 @pytest.mark.parametrize('string_value', [
     r'"\t"',
     r'"\n"',
-    r'""',
+    '""',
 ])
 @pytest.mark.parametrize('prefix', [
     'b',
diff --git a/tests/test_visitors/test_tokenize/test_primitives/test_string_tokens/test_string_modifier.py b/tests/test_visitors/test_tokenize/test_primitives/test_string_tokens/test_string_modifier.py
--- a/tests/test_visitors/test_tokenize/test_primitives/test_string_tokens/test_string_modifier.py
+++ b/tests/test_visitors/test_tokenize/test_primitives/test_string_tokens/test_string_modifier.py
@@ -1,6 +1,7 @@
 import pytest
 
 from wemake_python_styleguide.violations.consistency import (
+    RawStringNotNeededViolation,
     UppercaseStringModifierViolation,
 )
 from wemake_python_styleguide.visitors.tokenize.primitives import (
@@ -10,10 +11,10 @@
 
 @pytest.mark.parametrize('modifier', [
     'r',
-    'b',
     'rb',
-    'f',
     'fr',
+    'b',
+    'f',
     '',  # special case, no modifier is used
 ])
 @pytest.mark.parametrize('primitive', [
@@ -38,7 +39,11 @@ def test_correct_prefix(
     visitor = WrongStringTokenVisitor(default_options, file_tokens=file_tokens)
     visitor.run()
 
-    assert_errors(visitor, [])
+    assert_errors(
+        visitor,
+        [],
+        ignored_types=(RawStringNotNeededViolation,),
+    )
 
 
 @pytest.mark.parametrize('modifier', [
@@ -69,8 +74,12 @@ def test_uppercase_prefix(
     visitor = WrongStringTokenVisitor(default_options, file_tokens=file_tokens)
     visitor.run()
 
-    assert_errors(visitor, [UppercaseStringModifierViolation])
-    assert_error_text(visitor, modifier)
+    assert_errors(
+        visitor,
+        [UppercaseStringModifierViolation],
+        ignored_types=(RawStringNotNeededViolation,),
+    )
+    assert_error_text(visitor, modifier, multiple=True)
 
 
 @pytest.mark.parametrize('modifier', [
@@ -104,4 +113,5 @@ def test_uppercase_prefix_multiple(
     assert_errors(visitor, [
         UppercaseStringModifierViolation,
         UppercaseStringModifierViolation,
+        RawStringNotNeededViolation,
     ])
diff --git a/tests/test_visitors/test_tokenize/test_primitives/test_string_tokens/test_string_multiline.py b/tests/test_visitors/test_tokenize/test_primitives/test_string_tokens/test_string_multiline.py
--- a/tests/test_visitors/test_tokenize/test_primitives/test_string_tokens/test_string_multiline.py
+++ b/tests/test_visitors/test_tokenize/test_primitives/test_string_tokens/test_string_multiline.py
@@ -1,6 +1,7 @@
 import pytest
 
 from wemake_python_styleguide.violations.consistency import (
+    RawStringNotNeededViolation,
     WrongMultilineStringViolation,
 )
 from wemake_python_styleguide.visitors.tokenize.primitives import (
@@ -74,7 +75,11 @@ def test_incorrect_multiline_strings(
     visitor = WrongStringTokenVisitor(default_options, file_tokens=file_tokens)
     visitor.run()
 
-    assert_errors(visitor, [WrongMultilineStringViolation])
+    assert_errors(
+        visitor,
+        [WrongMultilineStringViolation],
+        ignored_types=(RawStringNotNeededViolation,),
+    )
 
 
 @pytest.mark.parametrize('primitive', [
diff --git a/tests/test_visitors/test_tokenize/test_primitives/test_string_tokens/test_unicode_prefix.py b/tests/test_visitors/test_tokenize/test_primitives/test_string_tokens/test_unicode_prefix.py
--- a/tests/test_visitors/test_tokenize/test_primitives/test_string_tokens/test_unicode_prefix.py
+++ b/tests/test_visitors/test_tokenize/test_primitives/test_string_tokens/test_unicode_prefix.py
@@ -36,7 +36,7 @@ def test_unicode_prefix(
 
 @pytest.mark.parametrize('primitive', [
     '"name"',
-    'r"text"',
+    'r"text with escape carac \n"',
     "b'unicode'",
     '"u"',
     '"12"',
diff --git a/tests/test_visitors/test_tokenize/test_primitives/test_string_tokens/test_unnecessary_raw_strings.py b/tests/test_visitors/test_tokenize/test_primitives/test_string_tokens/test_unnecessary_raw_strings.py
new file mode 100644
--- /dev/null
+++ b/tests/test_visitors/test_tokenize/test_primitives/test_string_tokens/test_unnecessary_raw_strings.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from wemake_python_styleguide.violations.consistency import (
+    RawStringNotNeededViolation,
+)
+from wemake_python_styleguide.visitors.tokenize.primitives import (
+    WrongStringTokenVisitor,
+)
+
+
+@pytest.mark.parametrize('raw_strings', [
+    r"r'some text\\'",
+    r"r'some text\''",
+    r"r'some text\"'",
+    r'r"some text\'"',
+    r"r'some text\t'",
+    r"r'some text\a'",
+    r"r'some text\n'",
+    r"r'some text\u041b'",
+    r"r'some text\043'",
+    r"r'some text\x23'",
+])
+def test_necessary_raw_string(
+    parse_tokens,
+    assert_errors,
+    default_options,
+    raw_strings,
+):
+    """Ensures that correct usage of raw string works."""
+    file_tokens = parse_tokens(raw_strings)
+
+    visitor = WrongStringTokenVisitor(default_options, file_tokens=file_tokens)
+    visitor.run()
+
+    assert_errors(visitor, [])
+
+
+@pytest.mark.parametrize('raw_strings', [
+    "r'No escaped character'",
+    'r"Here neither"',
+    "r'''Not here as well'''",
+    'r"""Not here as well"""',
+])
+def test_unnecessary_raw_string(
+    parse_tokens,
+    assert_errors,
+    default_options,
+    raw_strings,
+):
+    """Ensures that usage of raw string is forbidden if no backslash."""
+    file_tokens = parse_tokens(raw_strings)
+
+    visitor = WrongStringTokenVisitor(default_options, file_tokens=file_tokens)
+    visitor.run()
+
+    assert_errors(visitor, [RawStringNotNeededViolation])