Skip to content

Commit

Permalink
Merge cc0205e into b8b9940
Browse files Browse the repository at this point in the history
  • Loading branch information
skarzi committed Jan 14, 2020
2 parents b8b9940 + cc0205e commit e9c6fcc
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 4 deletions.
29 changes: 29 additions & 0 deletions tests/test_checker/test_invalid_options.py
Expand Up @@ -24,3 +24,32 @@ def test_invalid_options(absolute_path):

assert process.returncode == 1
assert 'ValueError' in stderr


def test_invalid_domain_names_options(absolute_path):
"""End-to-End test to check domain names options validation works."""
process = subprocess.Popen(
[
'flake8',
'--isolated',
'--select',
'WPS',
# values from `allowed-domain-names` cannot intersect with
# `--forbidden-domain-names`
'--allowed-domain-names',
'item,items,handle,visitor',
'--forbidden-domain-names',
'handle,visitor,node',
absolute_path('fixtures', 'noqa.py'),
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
encoding='utf8',
)
_, stderr = process.communicate()

assert process.returncode == 1
assert 'ValueError' in stderr
assert 'handle' in stderr
assert 'visitor' in stderr
28 changes: 28 additions & 0 deletions tests/test_options/test_validate_domain_names_options.py
@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-

import pytest

from wemake_python_styleguide.options.validation import (
validate_domain_names_options,
)


@pytest.mark.parametrize(('allowed_names', 'forbidden_names'), [
(['items'], []),
([], ['items']),
(['item'], ['handle']),
])
def test_passes_when_any_option_not_passed(allowed_names, forbidden_names):
"""Ensures validation passes when any domain option not passed."""
validate_domain_names_options(allowed_names, forbidden_names)


def test_passes_when_names_no_intersect():
"""Ensures validation passes when names no intersect."""
validate_domain_names_options(['node'], ['visitor'])


def test_raises_valueerror_when_names_intersect():
"""Ensures `ValueError` exception is raised when names intersect."""
with pytest.raises(ValueError):
validate_domain_names_options(['visitor', 'handle'], ['visitor'])
Expand Up @@ -25,3 +25,28 @@ def test_naming_correct(
visitor.run()

assert_errors(visitor, [])


@pytest.mark.parametrize('allowed_name', [
'item',
'items',
'handle',
'other_name', # unknown values are ignored silently
])
def test_name_in_allowed_domain_names_option(
assert_errors,
parse_ast_tree,
naming_template,
options,
mode,
allowed_name,
):
"""Ensures that names listed in `allowed-domain-names` are allowed."""
tree = parse_ast_tree(mode(naming_template.format(allowed_name)))

visitor = WrongNameVisitor(
options(allowed_domain_names=[allowed_name]),
tree=tree,
)
visitor.run()
assert_errors(visitor, [])
Expand Up @@ -30,3 +30,29 @@ def test_wrong_variable_name(

assert_errors(visitor, [WrongVariableNameViolation])
assert_error_text(visitor, wrong_name)


@pytest.mark.parametrize('forbidden_name', [
'visitor',
'name_validator',
])
def test_name_in_forbidden_domain_names_option(
assert_errors,
assert_error_text,
parse_ast_tree,
naming_template,
options,
mode,
forbidden_name,
):
"""Ensures that names listed in `forbidden-domain-names` are forbidden."""
tree = parse_ast_tree(mode(naming_template.format(forbidden_name)))

visitor = WrongNameVisitor(
options(forbidden_domain_names=[forbidden_name]),
tree=tree,
)
visitor.run()

assert_errors(visitor, [WrongVariableNameViolation])
assert_error_text(visitor, forbidden_name)
19 changes: 19 additions & 0 deletions wemake_python_styleguide/logic/naming/blacklists.py
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-

from typing import FrozenSet

from wemake_python_styleguide.constants import VARIABLE_NAMES_BLACKLIST
from wemake_python_styleguide.types import ConfigurationOptions


def variable_names_blacklist_from(
options: ConfigurationOptions,
) -> FrozenSet[str]:
"""Creates variable names blacklist from options and constants."""
variable_names_blacklist = {
*VARIABLE_NAMES_BLACKLIST,
*options.forbidden_domain_names,
}
return frozenset(
variable_names_blacklist - set(options.allowed_domain_names),
)
18 changes: 18 additions & 0 deletions wemake_python_styleguide/options/config.py
Expand Up @@ -54,6 +54,10 @@
- ``max-noqa-comments`` - maximum number of `noqa` allowed in a module,
defaults to
:str:`wemake_python_styleguide.options.defaults.MAX_NOQA_COMMENTS`
- ``allowed-domain-names`` - list of allowed domain names, defaults to
:str:`wemake_python_styleguide.options.defaults.ALLOWED_DOMAIN_NAMES`
- ``forbidden-domain-names`` - list of forbidden domain names, defaults to
:str:`wemake_python_styleguide.options.defaults.FORBIDDEN_DOMAIN_NAMES`
.. rubric:: Complexity options
Expand Down Expand Up @@ -216,6 +220,20 @@ class Configuration(object):
type='string',
comma_separated_list=True,
),
_Option(
'--allowed-domain-names',
defaults.ALLOWED_DOMAIN_NAMES,
"Domain names that are removed from variable names' blacklist.",
type='string',
comma_separated_list=True,
),
_Option(
'--forbidden-domain-names',
defaults.FORBIDDEN_DOMAIN_NAMES,
"Domain names that extends variable names' blacklist.",
type='string',
comma_separated_list=True,
),

# Complexity:

Expand Down
6 changes: 6 additions & 0 deletions wemake_python_styleguide/options/defaults.py
Expand Up @@ -111,3 +111,9 @@

#: Maximum number of call chains.
MAX_CALL_LEVEL: Final = 3

#: Domain names that are removed from variable names' blacklist.
ALLOWED_DOMAIN_NAMES: Final = ()

#: Domain names that extends variable names' blacklist.
FORBIDDEN_DOMAIN_NAMES: Final = ()
29 changes: 29 additions & 0 deletions wemake_python_styleguide/options/validation.py
Expand Up @@ -25,6 +25,29 @@ def factory(instance, attribute, field_value):
return factory


def validate_domain_names_options(
allowed_domain_names: List[str],
forbidden_domain_names: List[str],
) -> None:
"""Validator to check that allowed and forbidden names doesn't intersect.
Raises:
ValueError
"""
if not allowed_domain_names or not forbidden_domain_names:
return
intersecting_names = set(allowed_domain_names) & set(forbidden_domain_names)
if intersecting_names:
raise ValueError(
(
'Names passed to `allowed_domain_name` and ' +
'`forbidden_domain_name` cannot intersect. ' +
'Intersecting names: ' +
', '.join(intersecting_names)
),
)


@final
@attr.dataclass(slots=True)
class _ValidatedOptions(object):
Expand All @@ -42,6 +65,8 @@ class _ValidatedOptions(object):
validator=[_min_max(min=1, max=defaults.MAX_NOQA_COMMENTS)],
)
nested_classes_whitelist: List[str]
allowed_domain_names: List[str]
forbidden_domain_names: List[str]

# Complexity:
max_arguments: int = attr.ib(validator=[_min_max(min=1)])
Expand Down Expand Up @@ -71,6 +96,10 @@ class _ValidatedOptions(object):

def validate_options(options: ConfigurationOptions) -> _ValidatedOptions:
"""Validates all options from ``flake8``, uses a subset of them."""
validate_domain_names_options(
options.allowed_domain_names,
options.forbidden_domain_names,
)
fields_to_validate = [
field.name
for field in attr.fields(_ValidatedOptions)
Expand Down
2 changes: 2 additions & 0 deletions wemake_python_styleguide/types.py
Expand Up @@ -103,6 +103,8 @@ class or structure.
max_name_length: int
max_noqa_comments: int
nested_classes_whitelist: List[str]
allowed_domain_names: List[str]
forbidden_domain_names: List[str]

# Complexity:
max_arguments: int
Expand Down
15 changes: 14 additions & 1 deletion wemake_python_styleguide/violations/naming.py
Expand Up @@ -288,7 +288,7 @@ class WrongVariableNameViolation(ASTViolation):
See
:py:data:`~wemake_python_styleguide.constants.VARIABLE_NAMES_BLACKLIST`
for the full list of blacklisted variable names.
for the base list of blacklisted variable names.
Example::
Expand All @@ -298,6 +298,19 @@ class WrongVariableNameViolation(ASTViolation):
# Wrong:
item = None
Configuration:
This rule is configurable with ``--allowed-domain-names``.
Default:
:str:`wemake_python_styleguide.options.defaults.ALLOWED_DOMAIN_NAMES`
And with ``--forbidden-domain-names``.
Default:
:str:`wemake_python_styleguide.options.defaults.FORBIDDEN_DOMAIN_NAMES`
The options listed above are used to create new variable names'
blacklist starting from
:py:data:`~wemake_python_styleguide.constants.VARIABLE_NAMES_BLACKLIST`.
.. versionadded:: 0.1.0
"""
Expand Down
20 changes: 17 additions & 3 deletions wemake_python_styleguide/visitors/ast/naming.py
Expand Up @@ -3,7 +3,16 @@
import ast
import itertools
from collections import Counter
from typing import Callable, Iterable, List, Optional, Tuple, Union, cast
from typing import (
Callable,
FrozenSet,
Iterable,
List,
Optional,
Tuple,
Union,
cast,
)

from typing_extensions import final

Expand All @@ -12,11 +21,11 @@
from wemake_python_styleguide.constants import (
MODULE_METADATA_VARIABLES_BLACKLIST,
SPECIAL_ARGUMENT_NAMES_WHITELIST,
VARIABLE_NAMES_BLACKLIST,
)
from wemake_python_styleguide.logic import functions, nodes
from wemake_python_styleguide.logic.naming import (
access,
blacklists,
builtins,
logical,
name_nodes,
Expand Down Expand Up @@ -45,6 +54,8 @@
class _NameValidator(object):
"""Utility class to separate logic from the naming visitor."""

variable_names_blacklist: FrozenSet[str]

def __init__(
self,
error_callback: Callable[[base.BaseViolation], None],
Expand All @@ -53,6 +64,9 @@ def __init__(
"""Creates new instance of a name validator."""
self._error_callback = error_callback
self._options = options
self._variable_names_blacklist = (
blacklists.variable_names_blacklist_from(options)
)

def check_name(
self,
Expand All @@ -61,7 +75,7 @@ def check_name(
*,
is_first_argument: bool = False,
) -> None:
if logical.is_wrong_name(name, VARIABLE_NAMES_BLACKLIST):
if logical.is_wrong_name(name, self._variable_names_blacklist):
self._error_callback(
naming.WrongVariableNameViolation(node, text=name),
)
Expand Down

0 comments on commit e9c6fcc

Please sign in to comment.