Skip to content

Commit

Permalink
Closes #1159
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn committed Feb 18, 2020
1 parent 4720a66 commit ae664f8
Show file tree
Hide file tree
Showing 30 changed files with 462 additions and 156 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Semantic versioning in our case means:
### Features

- **Breaking**: removes `flake8-print`, now using `WPS421` instead of `T001`
- Removes `cognitive_complexity` dependency, now it is built in into our linter
- Changes how cognitive complexity is calculated
- Adds `python3.8` support
- Removes `radon`, because `cognitive-complexity` is enough
- Removes `flake8-loggin-format` as a direct dependency
Expand All @@ -27,6 +29,9 @@ Semantic versioning in our case means:

- Remove ImplicitTernaryViolation - WPS332 #1099
- Fixes how `i_control_code` behaves with WPS113
- Fixes that cognitive complexity was ignoring
`ast.Continue`, `ast.Break`, and `ast.Raise` statements
- Fixes that cognitive complexity was ignoring `ast.AsyncFor` loops

### Misc

Expand Down
18 changes: 2 additions & 16 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ attrs = "*"
typing_extensions = "^3.6"
astor = "^0.8"
pygments = "^2.4"
cognitive_complexity = "^0.0.4"

flake8-builtins = "^1.4.2"
flake8-commas = "^2.0"
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
'plugins.violations',
'plugins.ast_tree',
'plugins.tokenize_parser',
'plugins.async_sync',
]


Expand Down
31 changes: 31 additions & 0 deletions tests/plugins/async_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-

import pytest


@pytest.fixture()
def async_wrapper():
"""Fixture to convert all regular functions into async ones."""
def factory(template: str) -> str:
return template.replace(
'def ', 'async def ',
).replace(
'with ', 'async with ',
).replace(
'for ', 'async for ',
)
return factory


@pytest.fixture()
def regular_wrapper():
"""Fixture to return regular functions without modifications."""
def factory(template: str) -> str:
return template
return factory


@pytest.fixture(params=['async_wrapper', 'regular_wrapper'])
def mode(request):
"""Fixture that returns either `async` or regular functions."""
return request.getfixturevalue(request.param)
23 changes: 23 additions & 0 deletions tests/test_logic/test_complexity/test_cognitive/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-

"""
Fixtures to make testing cognitive complexity easy.
Adapted from https://github.com/Melevir/cognitive_complexity
"""

import ast
import textwrap

import pytest

from wemake_python_styleguide.logic.complexity import cognitive


@pytest.fixture(scope='session')
def get_code_snippet_compexity():
"""Fixture to parse and count cognitive complexity the easy way."""
def factory(src: str) -> int:
funcdef = ast.parse(textwrap.dedent(src).strip()).body[0]
return cognitive.cognitive_score(funcdef)
return factory
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-

"""
Test to ensure that we count cognitive complexity correctly.
Adapted from https://github.com/Melevir/cognitive_complexity
"""

import pytest

complexity1_1 = """
def f(a, b):
if a: # +1
return 1
"""

complexity1_2 = """
def f(a):
return a * f(a - 1) # +1 for recursion
"""

complexity2_1 = """
def f(a, b):
if a and b and True: # +2
return 1
"""

complexity2_2 = """
def f(a, b):
if (a): # +1
return 1
if b: # +1
return 2
"""

complexity3_1 = """
def f(a, b):
if a and b or True: # +3
return 1
"""

complexity3_2 = """
def f(a, b):
if ( # +1
a and b and # +1
(c or d) # +1
):
return 1
"""

complexity3_3 = """
def f(a, b):
if a: # +1
for i in range(b): # +2
return 1
"""

complexity4_1 = """
def f(a, b):
try:
for foo in bar: # +1
return a
except Exception: # +1
if a < 0: # +2
return a
"""

complexity4_2 = """
def f(a):
def foo(a):
if a: # +2
return 1
bar = lambda a: lambda b: b or 2 # +2
return bar(foo(a))(a)
"""

complexity4_3 = """
def f(a):
if a % 2: # +1
return 'c' if a else 'd' # +2
return 'a' if a else 'b' # +1
"""

complexity6_1 = """
def f(a, b):
if a: # +1
for i in range(b): # +2
if b: # +3
return 1
"""

complexity9_1 = """
def f(a):
for a in range(10): # +1
if a % 2: # +2
continue # +2
if a == 8: # +2
break # +2
"""

complexity10_1 = """
def process_raw_constant(constant, min_word_length):
processed_words = []
raw_camelcase_words = []
for raw_word in re.findall(r'[a-z]+', constant): # +1
word = raw_word.strip()
if ( # +2
len(word) >= min_word_length # +4
and not (word.startswith('-') or word.endswith('-'))
):
if is_camel_case_word(word): # +3
raw_camelcase_words.append(word)
else:
processed_words.append(word.lower())
return processed_words, raw_camelcase_words
"""

complexity14_1 = """
def enhance(tree):
for statement in ast.walk(tree): # +1
if not isinstance(statement, ast.If): # +2
continue # +2
for child in ast.iter_child_nodes(statement): # +2
if isinstance(child, ast.If): # +3
if child in statement.orelse: # +4
setattr(statement, 'wps_if_chained', True)
setattr(child, 'wps_if_chain', statement)
return tree
"""


@pytest.mark.parametrize(('code', 'complexity'), [
(complexity1_1, 1),
(complexity1_2, 1),
(complexity2_1, 2),
(complexity2_2, 2),
(complexity3_1, 3),
(complexity3_2, 3),
(complexity3_3, 3),
(complexity4_1, 4),
(complexity4_2, 4),
(complexity4_3, 4),
(complexity6_1, 6),
(complexity9_1, 9),
(complexity10_1, 10),
(complexity14_1, 14),
])
def test_cognitive_complexity(
get_code_snippet_compexity,
mode,
code,
complexity,
):
"""Ensures that cognitive complexity count is correct."""
assert get_code_snippet_compexity(mode(code)) == complexity
6 changes: 1 addition & 5 deletions tests/test_violations/test_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,5 @@ def test_no_holes(all_violation_codes):
for code in sorted(module_codes.keys()):
if previous_code is not None:
diff = code - previous_code
assertion_name = (
module_codes[code].__qualname__
if module_codes[code] else 'DEPRECATED CODE'
)
assert diff == 1 or diff > 2, assertion_name
assert diff == 1 or diff > 2, module_codes[code].__qualname__
previous_code = code
28 changes: 0 additions & 28 deletions tests/test_visitors/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,31 +59,3 @@ def factory(visitor: BaseVisitor, text: str, multiple: bool = False):
assert reproduction.message() == violation.message()

return factory


@pytest.fixture()
def async_wrapper():
"""Fixture to convert all regular functions into async ones."""
def factory(template: str) -> str:
return template.replace(
'def ', 'async def ',
).replace(
'with ', 'async with ',
).replace(
'for ', 'async for ',
)
return factory


@pytest.fixture()
def regular_wrapper():
"""Fixture to return regular functions without modifications."""
def factory(template: str) -> str:
return template
return factory


@pytest.fixture(params=['async_wrapper', 'regular_wrapper'])
def mode(request):
"""Fixture that returns either `async` or regular functions."""
return request.getfixturevalue(request.param)
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def test_complex_cognitive_module(
[CognitiveModuleComplexityViolation],
ignored_types=(CognitiveComplexityViolation,),
)
assert_error_text(visitor, '19', multiple=True)
assert_error_text(visitor, '22', multiple=True)


@pytest.mark.parametrize('code', [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def test_complex_cognitive_function(
[CognitiveComplexityViolation],
ignored_types=(CognitiveModuleComplexityViolation,),
)
assert_error_text(visitor, '19', multiple=True)
assert_error_text(visitor, '22', multiple=True)


@pytest.mark.parametrize('code', [
Expand Down
12 changes: 12 additions & 0 deletions wemake_python_styleguide/logic/bools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-

import ast


def count_boolops(node: ast.AST) -> int:
"""Counts how many ``BoolOp`` nodes there are in a node."""
return len([
subnode
for subnode in ast.walk(node)
if isinstance(subnode, ast.BoolOp)
])
15 changes: 0 additions & 15 deletions wemake_python_styleguide/logic/complexity.py

This file was deleted.

1 change: 1 addition & 0 deletions wemake_python_styleguide/logic/complexity/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# -*- coding: utf-8 -*-
Loading

0 comments on commit ae664f8

Please sign in to comment.