Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #1191: Forbid to use unreadable characters #1246

Merged
merged 13 commits into from
Mar 18, 2020
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ Semantic versioning in our case means:
- Adds baseline information for all complexity violation messages: `x > baseline`
- Changes how cognitive complexity is calculated
- Adds support for positional arguments in different checks
- Adds `UnreadableNameViolation` as `WPS124` because there are some
character combination which is not easy to read
- Adds support for `NamedExpr` with in compare type violation

### Bugfixes
Expand Down
6 changes: 6 additions & 0 deletions tests/fixtures/noqa/noqa.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,3 +703,9 @@ def consecutive_yields():
# https://github.com/wemake-services/wemake-python-styleguide/issues/1082
break
my_print(4)


class Mem0Output(object): # noqa: WPS124
# See:
# https://github.com/wemake-services/wemake-python-styleguide/issues/1191
anti_wps124 = 'unreadable class'
1 change: 1 addition & 0 deletions tests/test_checker/test_noqa.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
'WPS121': 1,
'WPS122': 1,
'WPS123': 1,
'WPS124': 1,

'WPS200': 0, # logically unacceptable.
'WPS201': 0, # defined in ignored violations.
Expand Down
16 changes: 8 additions & 8 deletions tests/test_formatter/snapshots/snap_test_formatter_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
snapshots['test_formatter[cli_options2-with_source] formatter_with_source'] = '''
\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m

1:1 WPS111 Found too short name: s
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:
^

Expand Down Expand Up @@ -68,7 +68,7 @@
snapshots['test_formatter[cli_options3-with_source_statistic] formatter_with_source_statistic'] = '''
\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m

1:1 WPS111 Found too short name: s
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:
^

Expand Down Expand Up @@ -107,7 +107,7 @@
2 ./tests/fixtures/formatter/formatter2.py
\x1b[4mTotal: 3\x1b[0m

\x1b[1mWPS111\x1b[0m: Found too short name: s
\x1b[1mWPS111\x1b[0m: Found too short name: s < 2
1 ./tests/fixtures/formatter/formatter1.py
\x1b[4mTotal: 1\x1b[0m

Expand All @@ -134,7 +134,7 @@
snapshots['test_formatter[cli_options4-statistic_with_source] formatter_statistic_with_source'] = '''
\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m

1:1 WPS111 Found too short name: s
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:
^

Expand Down Expand Up @@ -173,7 +173,7 @@
2 ./tests/fixtures/formatter/formatter2.py
\x1b[4mTotal: 3\x1b[0m

\x1b[1mWPS111\x1b[0m: Found too short name: s
\x1b[1mWPS111\x1b[0m: Found too short name: s < 2
1 ./tests/fixtures/formatter/formatter1.py
\x1b[4mTotal: 1\x1b[0m

Expand All @@ -199,7 +199,7 @@

snapshots['test_formatter[cli_options0-regular] formatter_regular'] = '''
\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
1:1 WPS111 Found too short name: s
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
Expand All @@ -216,7 +216,7 @@

snapshots['test_formatter[cli_options1-regular_statistic] formatter_regular_statistic'] = '''
\x1b[4m\x1b[1m./tests/fixtures/formatter/formatter1.py\x1b[0m\x1b[0m
1:1 WPS111 Found too short name: s
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
Expand All @@ -232,7 +232,7 @@
2 ./tests/fixtures/formatter/formatter2.py
\x1b[4mTotal: 3\x1b[0m

\x1b[1mWPS111\x1b[0m: Found too short name: s
\x1b[1mWPS111\x1b[0m: Found too short name: s < 2
1 ./tests/fixtures/formatter/formatter1.py
\x1b[4mTotal: 1\x1b[0m

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def test_useful_len_call(

@pytest.mark.parametrize('code', [
wrong_len_call1,
wrong_len_call2,
])
def test_useless_len_call(
assert_errors,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def test_long_variable_name(
visitor.run()

assert_errors(visitor, [TooLongNameViolation])
assert_error_text(visitor, long_name)
assert_error_text(visitor, long_name, default_options.max_name_length)


def test_long_variable_name_config(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import pytest

from wemake_python_styleguide import constants
from wemake_python_styleguide.logic.naming.alphabet import (
get_unreadable_characters,
)
from wemake_python_styleguide.violations.naming import UnreadableNameViolation
from wemake_python_styleguide.visitors.ast.naming import WrongNameVisitor

class_template = """
class {0}(object):
def __init__(self):
...
"""


@pytest.mark.parametrize('code', [
class_template,
])
@pytest.mark.parametrize('expression', [
'My1Item',
'Element0Operation',
'O0S',
'S0O',
])
def test_unreadable_name(
assert_errors,
assert_error_text,
parse_ast_tree,
code,
expression,
default_options,
mode,
):
"""Ensures that unreadable names are not allowed."""
tree = parse_ast_tree(mode(code.format(expression, expression)))

visitor = WrongNameVisitor(default_options, tree=tree)
visitor.run()

unreadable = get_unreadable_characters(
expression, constants.UNREADABLE_CHARACTER_COMBINATIONS,
)

assert_errors(visitor, [UnreadableNameViolation])
assert_error_text(visitor, unreadable)
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_short_variable_name(
visitor.run()

assert_errors(visitor, [TooShortNameViolation])
assert_error_text(visitor, short_name)
assert_error_text(visitor, short_name, default_options.min_name_length)


@pytest.mark.parametrize('short_name', [
Expand Down Expand Up @@ -70,4 +70,4 @@ def test_naming_length_settings(
visitor.run()

assert_errors(visitor, [TooShortNameViolation])
assert_error_text(visitor, short_name)
assert_error_text(visitor, short_name, option_values.min_name_length)
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ def test_length_option(assert_errors, assert_error_text, options):
visitor.run()

assert_errors(visitor, [TooShortNameViolation])
assert_error_text(visitor, filename.replace('.py', ''))
assert_error_text(
visitor, filename.replace('.py', ''), option_values.min_name_length,
)


@pytest.mark.parametrize('filename', [
Expand All @@ -73,4 +75,6 @@ def test_max_length_option(assert_errors, assert_error_text, options):
visitor.run()

assert_errors(visitor, [TooLongNameViolation])
assert_error_text(visitor, filename.replace('.py', ''))
assert_error_text(
visitor, filename.replace('.py', ''), option_values.max_name_length,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import pytest

from wemake_python_styleguide.violations.naming import (
UnderscoredNumberNameViolation,
UnreadableNameViolation,
WrongModuleNamePatternViolation,
)
from wemake_python_styleguide.visitors.filenames.module import (
WrongModuleNameVisitor,
)


@pytest.mark.parametrize('filename', [
'the1long',
])
def test_unreadable_filename(assert_errors, filename, default_options):
"""Testing that unreadable characters combinations do not allowed."""
visitor = WrongModuleNameVisitor(default_options, filename=filename)
visitor.run()

assert_errors(visitor, [UnreadableNameViolation])


@pytest.mark.parametrize('filename', [
'ordinary',
'first_module',
sobolevn marked this conversation as resolved.
Show resolved Hide resolved
])
def test_readable_filename(assert_errors, filename, default_options):
"""Testing that ordinary naming works well."""
visitor = WrongModuleNameVisitor(default_options, filename=filename)
visitor.run()

assert_errors(visitor, [])


@pytest.mark.parametrize('filename', [
'TestO_0',
])
def test_corner_case(assert_errors, filename, default_options):
"""Testing corner case related to underscore name patterns."""
visitor = WrongModuleNameVisitor(default_options, filename=filename)
visitor.run()

assert_errors(visitor, [
WrongModuleNamePatternViolation,
UnderscoredNumberNameViolation,
])
18 changes: 17 additions & 1 deletion wemake_python_styleguide/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@
'baz',
))

#: List of characters sequences that are hard to read.
UNREADABLE_CHARACTER_COMBINATIONS: Final = frozenset((
'1l',
'1I',
'0O',
'O0',
# Not included: 'lI', 'l1', 'Il'
# Because these names are quite common in real words.
))

#: List of special names that are used only as first argument in methods.
SPECIAL_ARGUMENT_NAMES_WHITELIST: Final = frozenset((
'self',
Expand Down Expand Up @@ -366,11 +376,17 @@
))

#: List of functions in which arguments must be tuples.
TUPLE_ARGUMENTS_METHODS = frozenset((
TUPLE_ARGUMENTS_METHODS: Final = frozenset((
'frozenset',
))


# Internal variables
# ==================

# Please, do not touch values beyond this line!
# ---------------------------------------------

# They are not publicly documented since they are not used by the end user.
# But, we still need them to be defined here.

Expand Down