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

Closes #1327 #1851

Merged
merged 16 commits into from Feb 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/test.yml
@@ -1,6 +1,13 @@
name: test

on: [push, pull_request, workflow_dispatch]
'on':
push:
pull_request:
branches:
# Branches from forks have the form 'user:branch-name'. Reference:
# https://github.community/t/how-to-trigger-an-action-on-push-or-pull-request-but-not-both/16662/9
- '**:**'
workflow_dispatch:

jobs:
build:
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Expand Up @@ -14,6 +14,11 @@ Semantic versioning in our case means:
### Features

- Adds `python3.9` support
- Changes how we treat own/foreign attributes,
since now we only check assigned attribute names for `self`/`cls`/`mcs`,
but not any other ones.
So, now writting `point.x = 1` will not trigger any violations.
Previously, it would raise "too short name".
- Forbids using non-trivial expressions as an argument to `except`
- Forbids using too many variables in a tuple unpacking
- Forbids using `float("NaN")`.
Expand Down Expand Up @@ -42,6 +47,7 @@ Semantic versioning in our case means:
- Forbids using bitwise operation with boolean operation
- Forbids inconsistent structuring of multiline comprehensions
- Forbids to use unpythonic getters and setters such as `get_attribute` or `set_attribute`
- Now `credits`, `license`, and `copyright` builtins are free to shadow

### Bugfixes

Expand All @@ -61,6 +67,7 @@ Semantic versioning in our case means:
- Fixes variable reassignment in class context
- Fixes that `*'abc'` was not counted as pointless star expression
- Fixes that `-some` was counted as overused expression
- Fixes several bugs with attribute names

### Misc

Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Expand Up @@ -57,6 +57,7 @@ per-file-ignores =
# Allows mypy type hinting, `Ellipsis`` usage, multiple methods:
wemake_python_styleguide/types.py: D102, WPS214, WPS220, WPS428
# There are multiple fixtures, `assert`s, and subprocesses in tests:
tests/test_visitors/test_ast/test_naming/conftest.py: WPS202
tests/*.py: S101, S105, S404, S603, S607, WPS211, WPS226, WPS323
# Docs can have the configuration they need:
docs/conf.py: WPS407
Expand Down
8 changes: 1 addition & 7 deletions tests/plugins/ast_tree.py
@@ -1,5 +1,4 @@
import ast
import sys
from textwrap import dedent

import pytest
Expand Down Expand Up @@ -39,9 +38,4 @@ def _compile_code(code_to_parse: str) -> None:
that are validated after the ``ast`` is processed:
like double arguments or ``break`` outside of loops.
"""
try:
compile(code_to_parse, '<filename>', 'exec') # noqa: WPS421
except SyntaxError:
if sys.version_info[:3] == (3, 9, 0):
pytest.skip('Python 3.9.0 has strange syntax errors')
raise
compile(code_to_parse, '<filename>', 'exec') # noqa: WPS421
30 changes: 24 additions & 6 deletions tests/test_visitors/conftest.py
@@ -1,22 +1,30 @@
from typing import Optional, Sequence
from typing import Optional, Sequence, Tuple, Type, Union

import pytest

from wemake_python_styleguide.violations.base import (
ASTViolation,
BaseViolation,
TokenizeViolation,
)
from wemake_python_styleguide.visitors.base import BaseVisitor

_IgnoredTypes = Union[
Type[BaseViolation],
Tuple[BaseViolation, ...],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably it should be:

Tuple[Type[BaseViolation], ...],

None,
]


@pytest.fixture(scope='session')
def assert_errors():
"""Helper function to assert visitor violations."""
def factory(
visitor: BaseVisitor,
errors: Sequence[str],
ignored_types=None,
):
*,
ignored_types: _IgnoredTypes = None,
) -> None:
if ignored_types:
real_errors = [
error
Expand Down Expand Up @@ -46,11 +54,21 @@ def factory(
baseline: Optional[int] = None,
*,
multiple: bool = False,
):
ignored_types: _IgnoredTypes = None,
) -> None:
if ignored_types:
real_errors = [
error
for error in visitor.violations
if not isinstance(error, ignored_types)
]
else:
real_errors = visitor.violations

if not multiple:
assert len(visitor.violations) == 1
assert len(real_errors) == 1

violation = visitor.violations[0]
violation = real_errors[0]
error_format = ': {0}'

assert error_format in violation.error_template
Expand Down
Expand Up @@ -57,9 +57,11 @@ def test_alphabet_as_fstring_violation(
visitor = WrongStringVisitor(default_options, tree=tree)
visitor.run()

assert_errors(visitor, [StringConstantRedefinedViolation], (
FormattedStringViolation,
))
assert_errors(
visitor,
[StringConstantRedefinedViolation],
ignored_types=FormattedStringViolation,
)


@pytest.mark.parametrize('code', [
Expand Down
Expand Up @@ -96,9 +96,11 @@ def test_modulo_formatting(
visitor = WrongStringVisitor(default_options, tree=tree)
visitor.run()

assert_errors(visitor, [ModuloStringFormatViolation], (
FormattedStringViolation,
))
assert_errors(
visitor,
[ModuloStringFormatViolation],
ignored_types=FormattedStringViolation,
)


@pytest.mark.parametrize('code', [
Expand Down Expand Up @@ -144,10 +146,14 @@ def test_regular_modulo_string(
visitor = WrongStringVisitor(default_options, tree=tree)
visitor.run()

assert_errors(visitor, [], (
FormattedStringViolation,
TooComplexFormattedStringViolation,
))
assert_errors(
visitor,
[],
ignored_types=(
FormattedStringViolation,
TooComplexFormattedStringViolation,
),
)


@pytest.mark.parametrize('code', [
Expand All @@ -174,7 +180,7 @@ def test_functions_modulo_string(
visitor = WrongStringVisitor(default_options, tree=tree)
visitor.run()

assert_errors(visitor, [], (FormattedStringViolation,))
assert_errors(visitor, [], ignored_types=FormattedStringViolation)


@pytest.mark.parametrize('code', [
Expand Down
Expand Up @@ -122,4 +122,4 @@ def test_correct_constant_compare(
visitor = WrongConstantCompareVisitor(default_options, tree=tree)
visitor.run()

assert_errors(visitor, [], (WrongIsCompareViolation,))
assert_errors(visitor, [], ignored_types=WrongIsCompareViolation)
Expand Up @@ -57,9 +57,11 @@ def test_wrong_constant_is(
visitor = WrongConstantCompareVisitor(default_options, tree=tree)
visitor.run()

assert_errors(visitor, [WrongIsCompareViolation], (
FalsyConstantCompareViolation,
))
assert_errors(
visitor,
[WrongIsCompareViolation],
ignored_types=FalsyConstantCompareViolation,
)


@pytest.mark.parametrize('comparators', [
Expand Down
8 changes: 6 additions & 2 deletions tests/test_visitors/test_ast/test_compares/test_literal.py
Expand Up @@ -116,7 +116,7 @@ def test_literal_special2(
assert_errors(
visitor,
[ConstantCompareViolation],
ReversedComplexCompareViolation,
ignored_types=ReversedComplexCompareViolation,
)


Expand Down Expand Up @@ -145,4 +145,8 @@ def test_literal_special_without_errors(
visitor = CompareSanityVisitor(default_options, tree=tree)
visitor.run()

assert_errors(visitor, [], ReversedComplexCompareViolation)
assert_errors(
visitor,
[],
ignored_types=ReversedComplexCompareViolation,
)
Expand Up @@ -396,7 +396,7 @@ def test_wrong_return_in_else_or_finally(
assert_errors(
visitor,
[TryExceptMultipleReturnPathViolation],
(UselessExceptCaseViolation),
ignored_types=UselessExceptCaseViolation,
)


Expand Down Expand Up @@ -438,7 +438,7 @@ def test_correct_return_path_in_try_except(
visitor = WrongTryExceptVisitor(default_options, tree=tree)
visitor.run()

assert_errors(visitor, [], (UselessExceptCaseViolation))
assert_errors(visitor, [], ignored_types=UselessExceptCaseViolation)


@pytest.mark.parametrize('statements', [
Expand Down
60 changes: 56 additions & 4 deletions tests/test_visitors/test_ast/test_naming/conftest.py
Expand Up @@ -14,7 +14,7 @@

# Class names:

class_name = 'class {0}: ...'
class_name = 'class {0}(SomeParent): ...'


# Function names:
Expand Down Expand Up @@ -56,7 +56,7 @@ def validate(self, **{0}): ...
"""

function_posonly_argument = """
def test({0}, /): ...
def test(first, {0}, /): ...
"""

function_kwonly_argument = """
Expand All @@ -75,13 +75,18 @@ def test(self, *, {0}=True): ...
lambda_argument = 'lambda {0}: ...'
lambda_posonly_argument = 'lambda {0}, /: ...'

# Class attributes:
# Own attributes:

static_attribute = """
class Test:
{0} = None
"""

static_multiple_attributes = """
class Test:
{0}, other = (1, 2)
"""

static_typed_attribute = """
class Test:
{0}: int = None
Expand All @@ -104,6 +109,10 @@ def __init__(self):
self.{0}: int = 123
"""

# Foreign attributes:

foreign_attribute = 'other.{0} = 1'

# Variables:

variable_def = '{0} = 1'
Expand Down Expand Up @@ -180,12 +189,14 @@ def container():
method_kwonly_argument,
lambda_argument,

# Class attributes:
# Attributes:
static_attribute,
static_multiple_attributes,
static_typed_attribute,
static_typed_annotation,
instance_attribute,
instance_typed_attribute,
foreign_attribute,

# Variables:
variable_def,
Expand All @@ -207,6 +218,23 @@ def container():
assignment_expression,
}

_ATTRIBUTES = frozenset((
method_name,

static_attribute,
static_multiple_attributes,
static_typed_attribute,
static_typed_annotation,
instance_attribute,
instance_typed_attribute,

foreign_attribute,
))

_FOREIGN_NAMING_PATTERNS = frozenset((
foreign_attribute,
))

_FORBIDDEN_UNUSED_TUPLE = frozenset((
unpacking_variables,
variable_def,
Expand Down Expand Up @@ -249,6 +277,30 @@ def naming_template(request):
return request.param


@pytest.fixture(params=_ATTRIBUTES)
def attribute_template(request):
"""Parametrized fixture that contains patterns for attributes."""
return request.param


@pytest.fixture(params=_ALL_FIXTURES - _ATTRIBUTES)
def non_attribute_template(request):
"""Fixture that contains all naming templates except attributes."""
return request.param


@pytest.fixture(params=_FOREIGN_NAMING_PATTERNS)
def foreign_naming_template(request):
"""Fixture that contains all foreign name templates."""
return request.param


@pytest.fixture(params=_ALL_FIXTURES - _FOREIGN_NAMING_PATTERNS)
def own_naming_template(request):
"""Fixture that contains all own name templates."""
return request.param


@pytest.fixture(params=_FORBIDDEN_UNUSED_TUPLE)
def forbidden_tuple_unused_template(request):
"""Returns template that can be used to define wrong unused tuples."""
Expand Down
Expand Up @@ -3,7 +3,9 @@
from wemake_python_styleguide.violations.naming import (
UpperCaseAttributeViolation,
)
from wemake_python_styleguide.visitors.ast.naming import WrongNameVisitor
from wemake_python_styleguide.visitors.ast.naming.validation import (
WrongNameVisitor,
)

static_attribute = """
class Test(object):
Expand Down
@@ -1,7 +1,9 @@
import pytest

from wemake_python_styleguide.constants import SPECIAL_ARGUMENT_NAMES_WHITELIST
from wemake_python_styleguide.visitors.ast.naming import WrongNameVisitor
from wemake_python_styleguide.visitors.ast.naming.validation import (
WrongNameVisitor,
)

lambda_first_argument = 'lambda {0}: ...'
function_first_argument = 'def function({0}): ...'
Expand Down
Expand Up @@ -3,7 +3,7 @@
from wemake_python_styleguide.violations.best_practices import (
WrongModuleMetadataViolation,
)
from wemake_python_styleguide.visitors.ast.naming import (
from wemake_python_styleguide.visitors.ast.naming.variables import (
MODULE_METADATA_VARIABLES_BLACKLIST,
WrongModuleMetadataVisitor,
)
Expand Down