Skip to content

Commit

Permalink
Merge eceba74 into b8b9940
Browse files Browse the repository at this point in the history
  • Loading branch information
skarzi committed Jan 13, 2020
2 parents b8b9940 + eceba74 commit 612b42a
Show file tree
Hide file tree
Showing 11 changed files with 200 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)
15 changes: 15 additions & 0 deletions wemake_python_styleguide/logic/naming/blacklists.py
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-

from typing import Set

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


def variable_names_blacklist_from(options: ConfigurationOptions) -> Set[str]:
"""Creates variable names blacklist from options and constants."""
variable_names_blacklist = {
*VARIABLE_NAMES_BLACKLIST,
*options.forbidden_domain_names,
}
return 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
11 changes: 8 additions & 3 deletions wemake_python_styleguide/visitors/ast/naming.py
Expand Up @@ -3,7 +3,7 @@
import ast
import itertools
from collections import Counter
from typing import Callable, Iterable, List, Optional, Tuple, Union, cast
from typing import Callable, Iterable, List, Optional, Set, Tuple, Union, cast

from typing_extensions import final

Expand All @@ -12,11 +12,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 +45,8 @@
class _NameValidator(object):
"""Utility class to separate logic from the naming visitor."""

variable_names_blacklist: Set[str]

def __init__(
self,
error_callback: Callable[[base.BaseViolation], None],
Expand All @@ -53,6 +55,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 +66,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 612b42a

Please sign in to comment.