Repository: wemake-services/wemake-python-styleguide

Issue:
Add `wemake-plain` output formatter
It should be the same as `wemake`, but without colors. Why?

When I try to save the output to file, I get this:

```
  106:29   WPS220 Found too deep nesting: 28 > 20
  [34mtry[39;49;00m:
  ^
```

And I want to have this:

```
  106:29   WPS220 Found too deep nesting: 28 > 20
  try:
  ^
```


Discussion:
I can help with this if it is still available!
Thanks a lot, @ydocsgnillats!
To make sure I'm understanding correctly, I need to make an output formatter that is similar to the existing one, but without including different colors/styling? What file is the existing output formatter in? 
> I need to make an output formatter that is similar to the existing one, but without including different colors/styling?

Yes.

> What file is the existing output formatter in?

`formatter.py` 🙂 
Hey @sobolevn, can I take this one?
Sure! Thanks! 
@sobolevn I couldn't reproduce the bug. Am I missing something?

Making a test file `test.py`:

```
# test.py
1 * 0
```

and running:

```
$ flake8 --format wemake test.py > output
```

gives in `output`:

```

test.py

  2:1      WPS428 Found statement that has no effect
  1 * 0
  ^

  2:1      WPS345 Found meaningless number operation
  1 * 0
  ^

  2:5      WPS345 Found meaningless number operation
  1 * 0
      ^

Full list of violations and explanations:
https://wemake-python-stylegui.de/en/0.14.1/pages/usage/violations/
```
Or we can add `NO_COLOR` support https://no-color.org/ 
Which is better in my perspective 
Ok, just to make sure I understand correctly,

### Current behaviour:

- Output **has color codes** if outputted to the terminal
- Output **doesn't have color codes** if piped into another program or a file
- Setting `NO_COLOR` does not change the behaviour

### Desired behaviour

- Presence of color codes depends solely on whether or not `NO_COLOR` environment variable is defined

Am I correct?
> Output has color codes if outputted to the terminal

Correct!

> Output doesn't have color codes if piped into another program or a file

Well, that's complicated. Because it might be very tricky. Different shells, different terminals, different OSes, different programs, different pipe methods, etc. I cannot say that it works. But, I cannot say that it does not work.

> Presence of color codes depends solely on whether or not NO_COLOR environment variable is defined

Yes, this way we would have smooth way to control the output without the need to tweak the formatter manually. 
Hey I'm a beginner and complete newbie. I would like to attempt and solve this issue. Can you please guide me through the process and assign me this issue?
Sure, @0x00zer0day! You need to tweak https://github.com/wemake-services/wemake-python-styleguide/blob/master/wemake_python_styleguide/formatter.py#L52 class. My understanding of this problem has changed.

So:
- If `FORCE_COLOR` env var is set to `0`, do not add colored output
- Otherwise, keep things as-is


> Sure, @0x00zer0day! You need to tweak https://github.com/wemake-services/wemake-python-styleguide/blob/master/wemake_python_styleguide/formatter.py#L52 class. My understanding of this problem has changed.
> 
> So:
> 
> * If `FORCE_COLOR` env var is set to `0`, do not add colored output
> * Otherwise, keep things as-is

Just making sure if I'm on the right track here, I need to check if the system environment variables has the `FORCE_COLOR` flag set to `0` to disable colored output right?
Yes :)
@sobolevn, Sorry for being a complete noob here but does this seem correct?  :

```
class WemakeFormatter(BaseFormatter):  # noqa: WPS214
    """
    We need to format our style :term:`violations <violation>` beatifully.

    The default formatter does not allow us to do that.
    What things do we miss?

    1. Spacing, everything is just mixed up and glued together
    2. Colors and decoration, some information is easier
       to gather just with colors or underlined text
    3. Grouping, we need explicit grouping by filename
    4. Incomplete and non-informative statistics

    """
    if bool(os.environ.get('FORCE_COLOR')) is False:
        _doc_url: ClassVar[str] = DOCS_URL_TEMPLATE.format(pkg_version)

    # API:

    def after_init(self):
        """Called after the original ``init`` is used to set extra fields."""
        self._lexer = PythonLexer()
        self._formatter = TerminalFormatter()
       ...
```

Nope, you should change these three functions: https://github.com/wemake-services/wemake-python-styleguide/blob/d1d6f9541b1da85f3fb547ad761b290456c623e1/wemake_python_styleguide/formatter.py#L203-L238
@sobolevn , So if i understand this correctly, I should perform `os.environ.get('FORCE_COLOR')` check on these three functions right?
Yes :)
@sobolevn, Is this correct? 

```python

# Formatting text:
force_color_check = str(int(bool(environ.get('FORCE_COLOR'))))

def _bold(text: str) -> str:
    r"""
    Returns bold formatted text.

    >>> _bold('Hello!')
    '\x1b[1mHello!\x1b[0m'

    """
#    return '\033[1m{0}\033[0m'.format(text)
    if force_color_check == '0':
        return '\033[1m{0}\033[0m'.format(text)
    else:
        return text


def _underline(text: str) -> str:
    r"""
    Returns underlined formatted text.

    >>> _underline('Hello!')
    '\x1b[4mHello!\x1b[0m'

    """
    if force_color_check == '0':
        return '\033[4m{0}\033[0m'.format(text)
    else:
        return text


def _highlight(source: str, lexer, formatter) -> str:
    """
    Highlights source code. Might fail.

    See also:
        https://github.com/wemake-services/wemake-python-styleguide/issues/794

    """
    if force_color_check == '0':
        try:
            return highlight(source, lexer, formatter)
        except Exception:  # pragma: no cover
            # Might fail on some systems, when colors are set incorrectly,
            # or not available at all. In this case code will be just text.
            return source
    else:
        return source


``` 
```python
_DISABLE_COLORS: Final = environ.get('FORCE_COLOR', '1') == '0'

def _bold(text: str) -> str:
    if _DISABLE_COLORS:
         return text
    return ...
```

Resolving patch:
diff --git a/wemake_python_styleguide/formatter.py b/wemake_python_styleguide/formatter.py
--- a/wemake_python_styleguide/formatter.py
+++ b/wemake_python_styleguide/formatter.py
@@ -26,7 +26,8 @@
 """
 
 from collections import defaultdict
-from typing import ClassVar, DefaultDict, List
+from os import environ
+from typing import ClassVar, DefaultDict, Final, List
 
 from flake8.formatting.base import BaseFormatter
 from flake8.statistics import Statistics
@@ -34,19 +35,20 @@
 from pygments import highlight
 from pygments.formatters import TerminalFormatter
 from pygments.lexers import PythonLexer
-from typing_extensions import Final
 
 from wemake_python_styleguide.version import pkg_version
 
 #: That url is generated and hosted by Sphinx.
-DOCS_URL_TEMPLATE: Final = (
+_DOCS_URL_TEMPLATE: Final = (
     'https://wemake-python-styleguide.rtfd.io/en/{0}/pages/usage/violations/'
 )
 
 #: This url points to the specific violation page.
-SHORTLINK_TEMPLATE: Final = (
-    'https://pyflak.es/{0}'
-)
+_SHORTLINK_TEMPLATE: Final = 'https://pyflak.es/{0}'
+
+#: Option to disable any code highlight and text output format.
+#: See https://no-color.org
+_NO_COLOR: Final = environ.get('NO_COLOR', '0') == '1'
 
 
 class WemakeFormatter(BaseFormatter):  # noqa: WPS214
@@ -64,7 +66,7 @@ class WemakeFormatter(BaseFormatter):  # noqa: WPS214
 
     """
 
-    _doc_url: ClassVar[str] = DOCS_URL_TEMPLATE.format(pkg_version)
+    _doc_url: ClassVar[str] = _DOCS_URL_TEMPLATE.format(pkg_version)
 
     # API:
 
@@ -160,7 +162,7 @@ def _show_link(self, error: Violation) -> str:
 
         return '  {spacing}-> {link}'.format(
             spacing=' ' * 9,
-            link=SHORTLINK_TEMPLATE.format(error.code),
+            link=_SHORTLINK_TEMPLATE.format(error.code),
         )
 
     def _print_header(self, filename: str) -> None:
@@ -200,36 +202,61 @@ def _should_show_source(self, error: Violation) -> bool:
 
 # Formatting text:
 
-def _bold(text: str) -> str:
+def _bold(text: str, *, no_color: bool = _NO_COLOR) -> str:
     r"""
     Returns bold formatted text.
 
     >>> _bold('Hello!')
     '\x1b[1mHello!\x1b[0m'
 
+    Returns non-formatted text if environment variable ``NO_COLOR=1``.
+
+    >>> _bold('Hello!', no_color=True)
+    'Hello!'
+
     """
+    if no_color:
+        return text
     return '\033[1m{0}\033[0m'.format(text)
 
 
-def _underline(text: str) -> str:
+def _underline(text: str, *, no_color: bool = _NO_COLOR) -> str:
     r"""
     Returns underlined formatted text.
 
     >>> _underline('Hello!')
     '\x1b[4mHello!\x1b[0m'
 
+    Returns non-formatted text if environment variable ``NO_COLOR=1``.
+
+    >>> _underline('Hello!', no_color=True)
+    'Hello!'
+
     """
+    if no_color:
+        return text
     return '\033[4m{0}\033[0m'.format(text)
 
 
-def _highlight(source: str, lexer, formatter) -> str:
+def _highlight(
+    source: str,
+    lexer: PythonLexer,
+    formatter: TerminalFormatter,
+    *,
+    no_color: bool = _NO_COLOR,
+) -> str:
     """
     Highlights source code. Might fail.
 
+    Returns non-formatted text if environment variable ``NO_COLOR=1``.
+
     See also:
         https://github.com/wemake-services/wemake-python-styleguide/issues/794
+        https://no-color.org
 
     """
+    if no_color:
+        return source
     try:
         return highlight(source, lexer, formatter)
     except Exception:  # pragma: no cover


Test patch:
diff --git a/tests/test_formatter/snapshots/snap_test_formatter_output.py b/tests/test_formatter/snapshots/snap_test_formatter_output.py
--- a/tests/test_formatter/snapshots/snap_test_formatter_output.py
+++ b/tests/test_formatter/snapshots/snap_test_formatter_output.py
@@ -7,7 +7,7 @@
 
 snapshots = Snapshot()
 
-snapshots['test_formatter[cli_options0-regular] formatter_regular'] = '''
+snapshots['test_formatter[default_colors-cli_options0-regular] formatter_regular_None'] = '''
 \x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
   1:1      WPS111 Found too short name: s < 2
   1:7      WPS110 Found wrong variable name: handle
@@ -24,7 +24,7 @@
 https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
 '''
 
-snapshots['test_formatter[cli_options1-regular_statistic] formatter_regular_statistic'] = '''
+snapshots['test_formatter[default_colors-cli_options1-regular_statistic] formatter_regular_statistic_None'] = '''
 \x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
   1:1      WPS111 Found too short name: s < 2
   1:7      WPS110 Found wrong variable name: handle
@@ -66,7 +66,7 @@
 https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
 '''
 
-snapshots['test_formatter[cli_options2-with_source] formatter_with_source'] = '''
+snapshots['test_formatter[default_colors-cli_options2-with_source] formatter_with_source_None'] = '''
 \x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
 
   1:1      WPS111 Found too short name: s < 2
@@ -107,7 +107,7 @@
 https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
 '''
 
-snapshots['test_formatter[cli_options3-with_links] formatter_with_links'] = '''
+snapshots['test_formatter[default_colors-cli_options3-with_links] formatter_with_links_None'] = '''
 \x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
   1:1      WPS111 Found too short name: s < 2
            -> https://pyflak.es/WPS111
@@ -132,7 +132,7 @@
 https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
 '''
 
-snapshots['test_formatter[cli_options4-with_source_statistic] formatter_with_source_statistic'] = '''
+snapshots['test_formatter[default_colors-cli_options4-with_source_statistic] formatter_with_source_statistic_None'] = '''
 \x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
 
   1:1      WPS111 Found too short name: s < 2
@@ -198,7 +198,7 @@
 https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
 '''
 
-snapshots['test_formatter[cli_options5-with_source_links] formatter_with_source_links'] = '''
+snapshots['test_formatter[default_colors-cli_options5-with_source_links] formatter_with_source_links_None'] = '''
 \x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
 
   1:1      WPS111 Found too short name: s < 2
@@ -247,7 +247,7 @@
 https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
 '''
 
-snapshots['test_formatter[cli_options6-statistic_with_source] formatter_statistic_with_source'] = '''
+snapshots['test_formatter[default_colors-cli_options6-statistic_with_source] formatter_statistic_with_source_None'] = '''
 \x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
 
   1:1      WPS111 Found too short name: s < 2
@@ -313,29 +313,664 @@
 https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
 '''
 
-snapshots['test_formatter_correct[cli_options0-regular] formatter_correct_regular'] = ''
+snapshots['test_formatter[with_colors-cli_options0-regular] formatter_regular_False'] = '''
+\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
+  1:1      WPS111 Found too short name: s < 2
+  1:7      WPS110 Found wrong variable name: handle
+  2:21     WPS432 Found magic number: 200
+  2:21     WPS303 Found underscored number: 2_00
+
+\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter2.py\x1b[0m\x1b[0m
+  1:1      WPS110 Found wrong variable name: data
+  1:10     WPS110 Found wrong variable name: param
+  2:12     WPS437 Found protected attribute usage: _protected
+  2:31     WPS303 Found underscored number: 10_00
+
+Full list of violations and explanations:
+https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
+'''
+
+snapshots['test_formatter[with_colors-cli_options1-regular_statistic] formatter_regular_statistic_False'] = '''
+\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
+  1:1      WPS111 Found too short name: s < 2
+  1:7      WPS110 Found wrong variable name: handle
+  2:21     WPS432 Found magic number: 200
+  2:21     WPS303 Found underscored number: 2_00
+
+\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter2.py\x1b[0m\x1b[0m
+  1:1      WPS110 Found wrong variable name: data
+  1:10     WPS110 Found wrong variable name: param
+  2:12     WPS437 Found protected attribute usage: _protected
+  2:31     WPS303 Found underscored number: 10_00
+
+\x1b[1mWPS110\x1b[0m: Found wrong variable name: handle
+  1     ./tests/fixtures/formatter/formatter1.py
+  2     ./tests/fixtures/formatter/formatter2.py
+\x1b[4mTotal: 3\x1b[0m
+
+\x1b[1mWPS111\x1b[0m: Found too short name: s < 2
+  1     ./tests/fixtures/formatter/formatter1.py
+\x1b[4mTotal: 1\x1b[0m
+
+\x1b[1mWPS303\x1b[0m: Found underscored number: 2_00
+  1     ./tests/fixtures/formatter/formatter1.py
+  1     ./tests/fixtures/formatter/formatter2.py
+\x1b[4mTotal: 2\x1b[0m
+
+\x1b[1mWPS432\x1b[0m: Found magic number: 200
+  1     ./tests/fixtures/formatter/formatter1.py
+\x1b[4mTotal: 1\x1b[0m
+
+\x1b[1mWPS437\x1b[0m: Found protected attribute usage: _protected
+  1     ./tests/fixtures/formatter/formatter2.py
+\x1b[4mTotal: 1\x1b[0m
+
+
+\x1b[4m\x1b[1mAll errors: 8\x1b[0m\x1b[0m
+
+Full list of violations and explanations:
+https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
+'''
+
+snapshots['test_formatter[with_colors-cli_options2-with_source] formatter_with_source_False'] = '''
+\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
+
+  1:1      WPS111 Found too short name: s < 2
+  \x1b[34mdef\x1b[39;49;00m \x1b[32ms\x1b[39;49;00m(handle: \x1b[36mint\x1b[39;49;00m) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+  ^
+
+  1:7      WPS110 Found wrong variable name: handle
+  \x1b[34mdef\x1b[39;49;00m \x1b[32ms\x1b[39;49;00m(handle: \x1b[36mint\x1b[39;49;00m) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+        ^
+
+  2:21     WPS432 Found magic number: 200
+  \x1b[34mreturn\x1b[39;49;00m handle + \x1b[34m2_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+                  ^
+
+  2:21     WPS303 Found underscored number: 2_00
+  \x1b[34mreturn\x1b[39;49;00m handle + \x1b[34m2_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+                  ^
+
+\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter2.py\x1b[0m\x1b[0m
+
+  1:1      WPS110 Found wrong variable name: data
+  \x1b[34mdef\x1b[39;49;00m \x1b[32mdata\x1b[39;49;00m(param) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+  ^
+
+  1:10     WPS110 Found wrong variable name: param
+  \x1b[34mdef\x1b[39;49;00m \x1b[32mdata\x1b[39;49;00m(param) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+           ^
+
+  2:12     WPS437 Found protected attribute usage: _protected
+  \x1b[34mreturn\x1b[39;49;00m param._protected + \x1b[34m10_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+         ^
+
+  2:31     WPS303 Found underscored number: 10_00
+  \x1b[34mreturn\x1b[39;49;00m param._protected + \x1b[34m10_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+                            ^
+
+Full list of violations and explanations:
+https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
+'''
+
+snapshots['test_formatter[with_colors-cli_options3-with_links] formatter_with_links_False'] = '''
+\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
+  1:1      WPS111 Found too short name: s < 2
+           -> https://pyflak.es/WPS111
+  1:7      WPS110 Found wrong variable name: handle
+           -> https://pyflak.es/WPS110
+  2:21     WPS432 Found magic number: 200
+           -> https://pyflak.es/WPS432
+  2:21     WPS303 Found underscored number: 2_00
+           -> https://pyflak.es/WPS303
+
+\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter2.py\x1b[0m\x1b[0m
+  1:1      WPS110 Found wrong variable name: data
+           -> https://pyflak.es/WPS110
+  1:10     WPS110 Found wrong variable name: param
+           -> https://pyflak.es/WPS110
+  2:12     WPS437 Found protected attribute usage: _protected
+           -> https://pyflak.es/WPS437
+  2:31     WPS303 Found underscored number: 10_00
+           -> https://pyflak.es/WPS303
+
+Full list of violations and explanations:
+https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
+'''
+
+snapshots['test_formatter[with_colors-cli_options4-with_source_statistic] formatter_with_source_statistic_False'] = '''
+\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
+
+  1:1      WPS111 Found too short name: s < 2
+  \x1b[34mdef\x1b[39;49;00m \x1b[32ms\x1b[39;49;00m(handle: \x1b[36mint\x1b[39;49;00m) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+  ^
+
+  1:7      WPS110 Found wrong variable name: handle
+  \x1b[34mdef\x1b[39;49;00m \x1b[32ms\x1b[39;49;00m(handle: \x1b[36mint\x1b[39;49;00m) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+        ^
+
+  2:21     WPS432 Found magic number: 200
+  \x1b[34mreturn\x1b[39;49;00m handle + \x1b[34m2_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+                  ^
+
+  2:21     WPS303 Found underscored number: 2_00
+  \x1b[34mreturn\x1b[39;49;00m handle + \x1b[34m2_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+                  ^
+
+\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter2.py\x1b[0m\x1b[0m
+
+  1:1      WPS110 Found wrong variable name: data
+  \x1b[34mdef\x1b[39;49;00m \x1b[32mdata\x1b[39;49;00m(param) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+  ^
+
+  1:10     WPS110 Found wrong variable name: param
+  \x1b[34mdef\x1b[39;49;00m \x1b[32mdata\x1b[39;49;00m(param) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+           ^
+
+  2:12     WPS437 Found protected attribute usage: _protected
+  \x1b[34mreturn\x1b[39;49;00m param._protected + \x1b[34m10_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+         ^
+
+  2:31     WPS303 Found underscored number: 10_00
+  \x1b[34mreturn\x1b[39;49;00m param._protected + \x1b[34m10_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+                            ^
+
+\x1b[1mWPS110\x1b[0m: Found wrong variable name: handle
+  1     ./tests/fixtures/formatter/formatter1.py
+  2     ./tests/fixtures/formatter/formatter2.py
+\x1b[4mTotal: 3\x1b[0m
+
+\x1b[1mWPS111\x1b[0m: Found too short name: s < 2
+  1     ./tests/fixtures/formatter/formatter1.py
+\x1b[4mTotal: 1\x1b[0m
+
+\x1b[1mWPS303\x1b[0m: Found underscored number: 2_00
+  1     ./tests/fixtures/formatter/formatter1.py
+  1     ./tests/fixtures/formatter/formatter2.py
+\x1b[4mTotal: 2\x1b[0m
+
+\x1b[1mWPS432\x1b[0m: Found magic number: 200
+  1     ./tests/fixtures/formatter/formatter1.py
+\x1b[4mTotal: 1\x1b[0m
+
+\x1b[1mWPS437\x1b[0m: Found protected attribute usage: _protected
+  1     ./tests/fixtures/formatter/formatter2.py
+\x1b[4mTotal: 1\x1b[0m
+
+
+\x1b[4m\x1b[1mAll errors: 8\x1b[0m\x1b[0m
+
+Full list of violations and explanations:
+https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
+'''
+
+snapshots['test_formatter[with_colors-cli_options5-with_source_links] formatter_with_source_links_False'] = '''
+\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
+
+  1:1      WPS111 Found too short name: s < 2
+           -> https://pyflak.es/WPS111
+  \x1b[34mdef\x1b[39;49;00m \x1b[32ms\x1b[39;49;00m(handle: \x1b[36mint\x1b[39;49;00m) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+  ^
+
+  1:7      WPS110 Found wrong variable name: handle
+           -> https://pyflak.es/WPS110
+  \x1b[34mdef\x1b[39;49;00m \x1b[32ms\x1b[39;49;00m(handle: \x1b[36mint\x1b[39;49;00m) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+        ^
+
+  2:21     WPS432 Found magic number: 200
+           -> https://pyflak.es/WPS432
+  \x1b[34mreturn\x1b[39;49;00m handle + \x1b[34m2_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+                  ^
+
+  2:21     WPS303 Found underscored number: 2_00
+           -> https://pyflak.es/WPS303
+  \x1b[34mreturn\x1b[39;49;00m handle + \x1b[34m2_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+                  ^
+
+\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter2.py\x1b[0m\x1b[0m
+
+  1:1      WPS110 Found wrong variable name: data
+           -> https://pyflak.es/WPS110
+  \x1b[34mdef\x1b[39;49;00m \x1b[32mdata\x1b[39;49;00m(param) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+  ^
+
+  1:10     WPS110 Found wrong variable name: param
+           -> https://pyflak.es/WPS110
+  \x1b[34mdef\x1b[39;49;00m \x1b[32mdata\x1b[39;49;00m(param) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+           ^
+
+  2:12     WPS437 Found protected attribute usage: _protected
+           -> https://pyflak.es/WPS437
+  \x1b[34mreturn\x1b[39;49;00m param._protected + \x1b[34m10_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+         ^
+
+  2:31     WPS303 Found underscored number: 10_00
+           -> https://pyflak.es/WPS303
+  \x1b[34mreturn\x1b[39;49;00m param._protected + \x1b[34m10_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+                            ^
+
+Full list of violations and explanations:
+https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
+'''
+
+snapshots['test_formatter[with_colors-cli_options6-statistic_with_source] formatter_statistic_with_source_False'] = '''
+\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
+
+  1:1      WPS111 Found too short name: s < 2
+  \x1b[34mdef\x1b[39;49;00m \x1b[32ms\x1b[39;49;00m(handle: \x1b[36mint\x1b[39;49;00m) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+  ^
+
+  1:7      WPS110 Found wrong variable name: handle
+  \x1b[34mdef\x1b[39;49;00m \x1b[32ms\x1b[39;49;00m(handle: \x1b[36mint\x1b[39;49;00m) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+        ^
+
+  2:21     WPS432 Found magic number: 200
+  \x1b[34mreturn\x1b[39;49;00m handle + \x1b[34m2_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+                  ^
+
+  2:21     WPS303 Found underscored number: 2_00
+  \x1b[34mreturn\x1b[39;49;00m handle + \x1b[34m2_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+                  ^
+
+\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter2.py\x1b[0m\x1b[0m
+
+  1:1      WPS110 Found wrong variable name: data
+  \x1b[34mdef\x1b[39;49;00m \x1b[32mdata\x1b[39;49;00m(param) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+  ^
+
+  1:10     WPS110 Found wrong variable name: param
+  \x1b[34mdef\x1b[39;49;00m \x1b[32mdata\x1b[39;49;00m(param) -> \x1b[36mint\x1b[39;49;00m:\x1b[37m\x1b[39;49;00m
+           ^
+
+  2:12     WPS437 Found protected attribute usage: _protected
+  \x1b[34mreturn\x1b[39;49;00m param._protected + \x1b[34m10_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+         ^
+
+  2:31     WPS303 Found underscored number: 10_00
+  \x1b[34mreturn\x1b[39;49;00m param._protected + \x1b[34m10_00\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+                            ^
+
+\x1b[1mWPS110\x1b[0m: Found wrong variable name: handle
+  1     ./tests/fixtures/formatter/formatter1.py
+  2     ./tests/fixtures/formatter/formatter2.py
+\x1b[4mTotal: 3\x1b[0m
+
+\x1b[1mWPS111\x1b[0m: Found too short name: s < 2
+  1     ./tests/fixtures/formatter/formatter1.py
+\x1b[4mTotal: 1\x1b[0m
+
+\x1b[1mWPS303\x1b[0m: Found underscored number: 2_00
+  1     ./tests/fixtures/formatter/formatter1.py
+  1     ./tests/fixtures/formatter/formatter2.py
+\x1b[4mTotal: 2\x1b[0m
+
+\x1b[1mWPS432\x1b[0m: Found magic number: 200
+  1     ./tests/fixtures/formatter/formatter1.py
+\x1b[4mTotal: 1\x1b[0m
+
+\x1b[1mWPS437\x1b[0m: Found protected attribute usage: _protected
+  1     ./tests/fixtures/formatter/formatter2.py
+\x1b[4mTotal: 1\x1b[0m
+
+
+\x1b[4m\x1b[1mAll errors: 8\x1b[0m\x1b[0m
+
+Full list of violations and explanations:
+https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
+'''
+
+snapshots['test_formatter[without_colors-cli_options0-regular] formatter_regular_True'] = '''
+./tests/fixtures/formatter/formatter1.py
+  1:1      WPS111 Found too short name: s < 2
+  1:7      WPS110 Found wrong variable name: handle
+  2:21     WPS432 Found magic number: 200
+  2:21     WPS303 Found underscored number: 2_00
+
+./tests/fixtures/formatter/formatter2.py
+  1:1      WPS110 Found wrong variable name: data
+  1:10     WPS110 Found wrong variable name: param
+  2:12     WPS437 Found protected attribute usage: _protected
+  2:31     WPS303 Found underscored number: 10_00
+
+Full list of violations and explanations:
+https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
+'''
+
+snapshots['test_formatter[without_colors-cli_options1-regular_statistic] formatter_regular_statistic_True'] = '''
+./tests/fixtures/formatter/formatter1.py
+  1:1      WPS111 Found too short name: s < 2
+  1:7      WPS110 Found wrong variable name: handle
+  2:21     WPS432 Found magic number: 200
+  2:21     WPS303 Found underscored number: 2_00
+
+./tests/fixtures/formatter/formatter2.py
+  1:1      WPS110 Found wrong variable name: data
+  1:10     WPS110 Found wrong variable name: param
+  2:12     WPS437 Found protected attribute usage: _protected
+  2:31     WPS303 Found underscored number: 10_00
+
+WPS110: Found wrong variable name: handle
+  1     ./tests/fixtures/formatter/formatter1.py
+  2     ./tests/fixtures/formatter/formatter2.py
+Total: 3
+
+WPS111: Found too short name: s < 2
+  1     ./tests/fixtures/formatter/formatter1.py
+Total: 1
+
+WPS303: Found underscored number: 2_00
+  1     ./tests/fixtures/formatter/formatter1.py
+  1     ./tests/fixtures/formatter/formatter2.py
+Total: 2
+
+WPS432: Found magic number: 200
+  1     ./tests/fixtures/formatter/formatter1.py
+Total: 1
+
+WPS437: Found protected attribute usage: _protected
+  1     ./tests/fixtures/formatter/formatter2.py
+Total: 1
+
+
+All errors: 8
+
+Full list of violations and explanations:
+https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
+'''
+
+snapshots['test_formatter[without_colors-cli_options2-with_source] formatter_with_source_True'] = '''
+./tests/fixtures/formatter/formatter1.py
+
+  1:1      WPS111 Found too short name: s < 2
+  def s(handle: int) -> int:
+  ^
+
+  1:7      WPS110 Found wrong variable name: handle
+  def s(handle: int) -> int:
+        ^
+
+  2:21     WPS432 Found magic number: 200
+  return handle + 2_00
+                  ^
+
+  2:21     WPS303 Found underscored number: 2_00
+  return handle + 2_00
+                  ^
+
+./tests/fixtures/formatter/formatter2.py
+
+  1:1      WPS110 Found wrong variable name: data
+  def data(param) -> int:
+  ^
+
+  1:10     WPS110 Found wrong variable name: param
+  def data(param) -> int:
+           ^
+
+  2:12     WPS437 Found protected attribute usage: _protected
+  return param._protected + 10_00
+         ^
+
+  2:31     WPS303 Found underscored number: 10_00
+  return param._protected + 10_00
+                            ^
+
+Full list of violations and explanations:
+https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
+'''
+
+snapshots['test_formatter[without_colors-cli_options3-with_links] formatter_with_links_True'] = '''
+./tests/fixtures/formatter/formatter1.py
+  1:1      WPS111 Found too short name: s < 2
+           -> https://pyflak.es/WPS111
+  1:7      WPS110 Found wrong variable name: handle
+           -> https://pyflak.es/WPS110
+  2:21     WPS432 Found magic number: 200
+           -> https://pyflak.es/WPS432
+  2:21     WPS303 Found underscored number: 2_00
+           -> https://pyflak.es/WPS303
+
+./tests/fixtures/formatter/formatter2.py
+  1:1      WPS110 Found wrong variable name: data
+           -> https://pyflak.es/WPS110
+  1:10     WPS110 Found wrong variable name: param
+           -> https://pyflak.es/WPS110
+  2:12     WPS437 Found protected attribute usage: _protected
+           -> https://pyflak.es/WPS437
+  2:31     WPS303 Found underscored number: 10_00
+           -> https://pyflak.es/WPS303
+
+Full list of violations and explanations:
+https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
+'''
+
+snapshots['test_formatter[without_colors-cli_options4-with_source_statistic] formatter_with_source_statistic_True'] = '''
+./tests/fixtures/formatter/formatter1.py
+
+  1:1      WPS111 Found too short name: s < 2
+  def s(handle: int) -> int:
+  ^
+
+  1:7      WPS110 Found wrong variable name: handle
+  def s(handle: int) -> int:
+        ^
+
+  2:21     WPS432 Found magic number: 200
+  return handle + 2_00
+                  ^
+
+  2:21     WPS303 Found underscored number: 2_00
+  return handle + 2_00
+                  ^
+
+./tests/fixtures/formatter/formatter2.py
+
+  1:1      WPS110 Found wrong variable name: data
+  def data(param) -> int:
+  ^
+
+  1:10     WPS110 Found wrong variable name: param
+  def data(param) -> int:
+           ^
+
+  2:12     WPS437 Found protected attribute usage: _protected
+  return param._protected + 10_00
+         ^
+
+  2:31     WPS303 Found underscored number: 10_00
+  return param._protected + 10_00
+                            ^
+
+WPS110: Found wrong variable name: handle
+  1     ./tests/fixtures/formatter/formatter1.py
+  2     ./tests/fixtures/formatter/formatter2.py
+Total: 3
+
+WPS111: Found too short name: s < 2
+  1     ./tests/fixtures/formatter/formatter1.py
+Total: 1
+
+WPS303: Found underscored number: 2_00
+  1     ./tests/fixtures/formatter/formatter1.py
+  1     ./tests/fixtures/formatter/formatter2.py
+Total: 2
+
+WPS432: Found magic number: 200
+  1     ./tests/fixtures/formatter/formatter1.py
+Total: 1
+
+WPS437: Found protected attribute usage: _protected
+  1     ./tests/fixtures/formatter/formatter2.py
+Total: 1
+
+
+All errors: 8
+
+Full list of violations and explanations:
+https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
+'''
+
+snapshots['test_formatter[without_colors-cli_options5-with_source_links] formatter_with_source_links_True'] = '''
+./tests/fixtures/formatter/formatter1.py
+
+  1:1      WPS111 Found too short name: s < 2
+           -> https://pyflak.es/WPS111
+  def s(handle: int) -> int:
+  ^
+
+  1:7      WPS110 Found wrong variable name: handle
+           -> https://pyflak.es/WPS110
+  def s(handle: int) -> int:
+        ^
+
+  2:21     WPS432 Found magic number: 200
+           -> https://pyflak.es/WPS432
+  return handle + 2_00
+                  ^
+
+  2:21     WPS303 Found underscored number: 2_00
+           -> https://pyflak.es/WPS303
+  return handle + 2_00
+                  ^
+
+./tests/fixtures/formatter/formatter2.py
+
+  1:1      WPS110 Found wrong variable name: data
+           -> https://pyflak.es/WPS110
+  def data(param) -> int:
+  ^
+
+  1:10     WPS110 Found wrong variable name: param
+           -> https://pyflak.es/WPS110
+  def data(param) -> int:
+           ^
+
+  2:12     WPS437 Found protected attribute usage: _protected
+           -> https://pyflak.es/WPS437
+  return param._protected + 10_00
+         ^
+
+  2:31     WPS303 Found underscored number: 10_00
+           -> https://pyflak.es/WPS303
+  return param._protected + 10_00
+                            ^
+
+Full list of violations and explanations:
+https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
+'''
+
+snapshots['test_formatter[without_colors-cli_options6-statistic_with_source] formatter_statistic_with_source_True'] = '''
+./tests/fixtures/formatter/formatter1.py
+
+  1:1      WPS111 Found too short name: s < 2
+  def s(handle: int) -> int:
+  ^
+
+  1:7      WPS110 Found wrong variable name: handle
+  def s(handle: int) -> int:
+        ^
+
+  2:21     WPS432 Found magic number: 200
+  return handle + 2_00
+                  ^
+
+  2:21     WPS303 Found underscored number: 2_00
+  return handle + 2_00
+                  ^
+
+./tests/fixtures/formatter/formatter2.py
+
+  1:1      WPS110 Found wrong variable name: data
+  def data(param) -> int:
+  ^
+
+  1:10     WPS110 Found wrong variable name: param
+  def data(param) -> int:
+           ^
+
+  2:12     WPS437 Found protected attribute usage: _protected
+  return param._protected + 10_00
+         ^
+
+  2:31     WPS303 Found underscored number: 10_00
+  return param._protected + 10_00
+                            ^
 
-snapshots['test_formatter_correct[cli_options1-regular_statistic] formatter_correct_regular_statistic'] = '''
+WPS110: Found wrong variable name: handle
+  1     ./tests/fixtures/formatter/formatter1.py
+  2     ./tests/fixtures/formatter/formatter2.py
+Total: 3
+
+WPS111: Found too short name: s < 2
+  1     ./tests/fixtures/formatter/formatter1.py
+Total: 1
+
+WPS303: Found underscored number: 2_00
+  1     ./tests/fixtures/formatter/formatter1.py
+  1     ./tests/fixtures/formatter/formatter2.py
+Total: 2
+
+WPS432: Found magic number: 200
+  1     ./tests/fixtures/formatter/formatter1.py
+Total: 1
+
+WPS437: Found protected attribute usage: _protected
+  1     ./tests/fixtures/formatter/formatter2.py
+Total: 1
+
+
+All errors: 8
+
+Full list of violations and explanations:
+https://wemake-python-styleguide.rtfd.io/en/xx.xx/pages/usage/violations/
+'''
+
+snapshots['test_formatter_correct[with_colors-cli_options0-regular] formatter_correct_regular_False'] = ''
+
+snapshots['test_formatter_correct[with_colors-cli_options1-regular_statistic] formatter_correct_regular_statistic_False'] = '''
 
 \x1b[4m\x1b[1mAll errors: 0\x1b[0m\x1b[0m
 '''
 
-snapshots['test_formatter_correct[cli_options2-with_source] formatter_correct_with_source'] = ''
+snapshots['test_formatter_correct[with_colors-cli_options2-with_source] formatter_correct_with_source_False'] = ''
 
-snapshots['test_formatter_correct[cli_options3-with_links] formatter_correct_with_links'] = ''
+snapshots['test_formatter_correct[with_colors-cli_options3-with_links] formatter_correct_with_links_False'] = ''
 
-snapshots['test_formatter_correct[cli_options4-with_source_statistic] formatter_correct_with_source_statistic'] = '''
+snapshots['test_formatter_correct[with_colors-cli_options4-with_source_statistic] formatter_correct_with_source_statistic_False'] = '''
 
 \x1b[4m\x1b[1mAll errors: 0\x1b[0m\x1b[0m
 '''
 
-snapshots['test_formatter_correct[cli_options5-with_source_links] formatter_correct_with_source_links'] = ''
+snapshots['test_formatter_correct[with_colors-cli_options5-with_source_links] formatter_correct_with_source_links_False'] = ''
 
-snapshots['test_formatter_correct[cli_options6-statistic_with_source] formatter_correct_statistic_with_source'] = '''
+snapshots['test_formatter_correct[with_colors-cli_options6-statistic_with_source] formatter_correct_statistic_with_source_False'] = '''
 
 \x1b[4m\x1b[1mAll errors: 0\x1b[0m\x1b[0m
 '''
 
+snapshots['test_formatter_correct[without_colors-cli_options0-regular] formatter_correct_regular_True'] = ''
+
+snapshots['test_formatter_correct[without_colors-cli_options1-regular_statistic] formatter_correct_regular_statistic_True'] = '''
+
+All errors: 0
+'''
+
+snapshots['test_formatter_correct[without_colors-cli_options2-with_source] formatter_correct_with_source_True'] = ''
+
+snapshots['test_formatter_correct[without_colors-cli_options3-with_links] formatter_correct_with_links_True'] = ''
+
+snapshots['test_formatter_correct[without_colors-cli_options4-with_source_statistic] formatter_correct_with_source_statistic_True'] = '''
+
+All errors: 0
+'''
+
+snapshots['test_formatter_correct[without_colors-cli_options5-with_source_links] formatter_correct_with_source_links_True'] = ''
+
+snapshots['test_formatter_correct[without_colors-cli_options6-statistic_with_source] formatter_correct_statistic_with_source_True'] = '''
+
+All errors: 0
+'''
+
 snapshots['test_ipynb formatter_ipynb'] = '''
 \x1b[4m\x1b[1mtests/fixtures/notebook.ipynb\x1b[0m\x1b[0m
   3:1      DAR101 Missing parameter(s) in Docstring: - good_name
diff --git a/tests/test_formatter/test_formatter_output.py b/tests/test_formatter/test_formatter_output.py
--- a/tests/test_formatter/test_formatter_output.py
+++ b/tests/test_formatter/test_formatter_output.py
@@ -50,7 +50,12 @@ def _safe_output(output: str) -> str:
     (['--show-source', '--show-violation-links'], 'with_source_links'),
     (['--statistic', '--show-source'], 'statistic_with_source'),
 ])
-def test_formatter(snapshot, cli_options, output):
+@pytest.mark.parametrize(
+    'no_color',
+    [True, False, None],
+    ids=['without_colors', 'with_colors', 'default_colors'],
+)
+def test_formatter(snapshot, cli_options, output, no_color):
     """
     End-to-End test to that formatting works well.
 
@@ -59,6 +64,10 @@ def test_formatter(snapshot, cli_options, output):
     """
     filename1 = './tests/fixtures/formatter/formatter1.py'
     filename2 = './tests/fixtures/formatter/formatter2.py'
+    if no_color is None:
+        env = os.environ
+    else:
+        env = {**os.environ, 'NO_COLOR': '1' if no_color else '0'}
 
     process = subprocess.Popen(
         [
@@ -77,12 +86,13 @@ def test_formatter(snapshot, cli_options, output):
         stderr=subprocess.PIPE,
         universal_newlines=True,
         encoding='utf8',
+        env=env,
     )
     stdout, _ = process.communicate()
 
     snapshot.assert_match(
         _safe_output(stdout),
-        'formatter_{0}'.format(output),
+        'formatter_{0}_{1}'.format(output, no_color),
     )
 
 
@@ -95,7 +105,12 @@ def test_formatter(snapshot, cli_options, output):
     (['--show-source', '--show-violation-links'], 'with_source_links'),
     (['--statistic', '--show-source'], 'statistic_with_source'),
 ])
-def test_formatter_correct(snapshot, cli_options, output):
+@pytest.mark.parametrize(
+    'no_color',
+    [True, False],
+    ids=['without_colors', 'with_colors'],
+)
+def test_formatter_correct(snapshot, cli_options, output, no_color):
     """All correct code should not raise any violations and no output."""
     filename = './tests/fixtures/formatter/correct.py'
 
@@ -115,12 +130,14 @@ def test_formatter_correct(snapshot, cli_options, output):
         stderr=subprocess.PIPE,
         universal_newlines=True,
         encoding='utf8',
+        env={**os.environ, 'NO_COLOR': '1' if no_color else '0'},
     )
-    stdout, _ = process.communicate()
+    stdout, stderr = process.communicate()
+    assert process.returncode == 0, (stdout, stderr)
 
     snapshot.assert_match(
         _safe_output(stdout),
-        'formatter_correct_{0}'.format(output),
+        'formatter_correct_{0}_{1}'.format(output, no_color),
     )