Skip to content

Commit

Permalink
[#271] Restricted variable names with more than one consecutive under…
Browse files Browse the repository at this point in the history
…score (#277)

* restricted variable names with more than one concecutive underscore

* changelog update

* put pyproject.lock back to project

* fixed naming and added missed blank lines

* fixing errors bringed with merge conflict resolving
  • Loading branch information
Roxe322 authored and sobolevn committed Oct 26, 2018
1 parent d3963f7 commit 72958ed
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 4 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ to the project during `#hactoberfest`. List of awesome people:
- Forbid `for` loops with unused `else`
- Forbid variables self reassignment
- Forbid `try` with `finally` without `except`
- Forbit `if` statements with invalid conditionals
- Forbid `if` statements with invalid conditionals
- Forbid opening parenthesis from following keyword without space in between them
- Forbid the use of more than 2 `for` loops within a comprehension
- Forbid variable names with more than one consecutive underscore
- Restrict the maximum number of base classes aka mixins
- Forbid importing protected names

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-

import pytest

from wemake_python_styleguide.violations.naming import (
ConsecutiveUnderscoresInNameViolation,
)
from wemake_python_styleguide.visitors.ast.naming import WrongNameVisitor

variable_test = """
{0} = 'test'
"""

underscore_variable_test1 = """
_{0} = 'test'
"""

underscore_variable_test2 = """
{0}_ = 'test'
"""

for_variable_test = """
for {0} in []:
print()
"""

async_for_variable_test = """
async def container():
async for {0} in []:
print()
"""

with_variable_test = """
with open('test.py') as {0}:
raise ValueError()
"""

async_with_variable_test = """
async def container():
async with open('test.py') as {0}:
raise ValueError()
"""

exception_test = """
try:
1 / 0
except Exception as {0}:
raise
"""


@pytest.mark.parametrize('code', [
variable_test,
for_variable_test,
async_for_variable_test,
with_variable_test,
async_with_variable_test,
exception_test,
])
def test_consecutive_underscores_in_variable_name(
assert_errors,
parse_ast_tree,
code,
default_options,
):
"""Testing that variable can not have private names."""
tree = parse_ast_tree(code.format('some__value'))

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

assert_errors(visitor, [ConsecutiveUnderscoresInNameViolation])
66 changes: 66 additions & 0 deletions wemake_python_styleguide/logics/variables/naming.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,31 @@ def is_too_short_variable_name(
return name != constants.UNUSED_VARIABLE and len(name) < min_length


def is_private_variable(name: Optional[str]) -> bool:
"""
Checks if variable has private name pattern.
>>> is_private_variable(None)
False
>>> is_private_variable('regular')
False
>>> is_private_variable('__private')
True
>>> is_private_variable('_protected')
False
>>> is_private_variable('__magic__')
False
"""
return (
name is not None and name.startswith('__') and not name.endswith('__')
)


def is_variable_name_with_underscored_number(name: str) -> bool:
"""
Checks for variable names with underscored number.
Expand Down Expand Up @@ -135,3 +160,44 @@ def is_variable_name_with_underscored_number(name: str) -> bool:
"""
pattern = constants.UNDERSCORED_NUMBER_PATTERN
return name is not None and pattern.match(name) is not None


def is_variable_name_contains_consecutive_underscores(name: str) -> bool:
"""
Checks if variable contains consecutive underscores in middle of name.
>>> is_variable_name_contains_consecutive_underscores('name')
False
>>> is_variable_name_contains_consecutive_underscores('__magic__')
False
>>> is_variable_name_contains_consecutive_underscores('__private')
False
>>> is_variable_name_contains_consecutive_underscores(None)
False
>>> is_variable_name_contains_consecutive_underscores('name')
False
>>> is_variable_name_contains_consecutive_underscores('some__value')
True
>>> is_variable_name_contains_consecutive_underscores('some_value__')
True
"""
if name is None:
return False

if name.endswith('__') and name.startswith('__'):
return False

if name.startswith('__'):
return False

if '__' in name:
return True

return False
42 changes: 39 additions & 3 deletions wemake_python_styleguide/violations/naming.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
- Variables should use ``snake_case``
- When some variable is unused it should be prefixed with an underscore
- Variables should not contain more than one consecutive underscore
Type aliases
~~~~~~~~~~~~
Expand Down Expand Up @@ -126,8 +127,10 @@
SameAliasImportViolation
UnderScoredNumberNameViolation
UpperCaseAttributeViolation
ConsecutiveUnderscoresInNameViolation
ProtectedModuleViolation
Module names
------------
Expand All @@ -146,7 +149,8 @@
.. autoclass:: PrivateNameViolation
.. autoclass:: SameAliasImportViolation
.. autoclass:: UnderScoredNumberNameViolation
.. autoclass:: UpperCaseAttributeViolation
.. autoclass:: UpperCaseAttributeViolations
.. autoclass:: ConsecutiveUnderscoresInNameViolation
"""

Expand Down Expand Up @@ -547,6 +551,37 @@ class A(object):
code = 115


@final
class ConsecutiveUnderscoresInNameViolation(ASTViolation):
"""
Forbids to use more than one consecutive underscore in variable names.
Reasoning:
This is done to gain extra readability.
This naming rule already exist for module names.
Example::
# Correct:
some_value = 5
__magic__ = 5
# Wrong:
some__value = 5
.. versionadded:: 0.3.0
Note:
Returns Z116 as error code
"""

#: Error message shown to the user.
error_template = 'Found consecutive underscores in a variable "{0}"'

code = 116


@final
class ProtectedModuleViolation(ASTViolation):
"""
Expand All @@ -571,9 +606,10 @@ class ProtectedModuleViolation(ASTViolation):
.. versionadded:: 0.3.0
Note:
Returns Z116 as error code
Returns Z117 as error code
"""

#: Error message shown to the user
error_template = 'Found protected module import "{0}"'
code = 116

code = 117
8 changes: 8 additions & 0 deletions wemake_python_styleguide/visitors/ast/naming.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from wemake_python_styleguide.logics.variables.naming import (
is_too_short_variable_name,
is_upper_case_name,
is_variable_name_contains_consecutive_underscores,
is_variable_name_with_underscored_number,
is_wrong_variable_name,
)
Expand All @@ -23,6 +24,7 @@
WrongModuleMetadataViolation,
)
from wemake_python_styleguide.violations.naming import (
ConsecutiveUnderscoresInNameViolation,
PrivateNameViolation,
TooShortVariableNameViolation,
UnderScoredNumberNameViolation,
Expand Down Expand Up @@ -55,6 +57,7 @@ class WrongNameVisitor(BaseNodeVisitor):
"""Performs checks based on variable names."""

def _check_name(self, node: ast.AST, name: str) -> None:

if is_wrong_variable_name(name, VARIABLE_NAMES_BLACKLIST):
self.add_violation(WrongVariableNameViolation(node, text=name))

Expand All @@ -67,6 +70,10 @@ def _check_name(self, node: ast.AST, name: str) -> None:

if is_variable_name_with_underscored_number(name):
self.add_violation(UnderScoredNumberNameViolation())
if is_variable_name_contains_consecutive_underscores(name):
self.add_violation(
ConsecutiveUnderscoresInNameViolation(node, text=name),
)

def _check_function_signature(self, node: AnyFunctionDef) -> None:
for arg in node.args.args:
Expand Down Expand Up @@ -148,6 +155,7 @@ def visit_variable(self, node: VariableDef) -> None:
"""
variable_name = get_assigned_name(node)

if variable_name is not None:
self._check_name(node, variable_name)
self.generic_visit(node)
Expand Down

0 comments on commit 72958ed

Please sign in to comment.