Skip to content

Commit

Permalink
Adds type: comment validation
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn committed Sep 23, 2018
1 parent 3897fed commit daaf848
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 8 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ We used to have incremental versioning before `0.1.0`.
- Adds `--i-control-code` option to ignore `InitModuleHasLogicViolation`
- Adds check for underscored numbers
- Forbids `u''` strings
- Adds `flake8`, `noqa`, and `type` comments check
- Adds `noqa` and `type` comments checks

### Misc

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
'x = 10_00 # noqa: Z002, Z114',
'wallet = 10_00 # noqa: Z002',
'x = 1000 # noqa: Z002',
'x = 1000 # noqa: Z002 ',
'print(12 + 3) # regular comment',
'print(12 + 3) #',
'print(12 + 3)',
'',
])
Expand All @@ -35,6 +37,7 @@ def test_correct_comments(

@pytest.mark.parametrize('code', [
'x = 10_00 # noqa',
'x = 10_00 # noqa ',
'x = 10_00 #noqa',
'x = 10_00#noqa',
'wallet = 10_00 # noqa: some comments',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-

import pytest

from wemake_python_styleguide.visitors.tokenize.wrong_comments import (
WrongCommentVisitor,
WrongMagicCommentViolation,
)


@pytest.mark.parametrize('code', [
'1 + "12" # type: ignore',
'1 + "12" # type:ignore',
'total = 1000 # type is not clear',
'print(12 + 3) # regular comment',
'print(12 + 3) #',
'print(12 + 3)',
'',
])
def test_correct_comments(
parse_tokens,
assert_errors,
default_options,
code,
):
"""Ensures that correct comments do not raise a warning."""
file_tokens = parse_tokens(code)

visitor = WrongCommentVisitor(default_options, file_tokens=file_tokens)
visitor.run()

assert_errors(visitor, [])


@pytest.mark.parametrize('code', [
'total = 1000 # type: int',
'total = 1000 # type:int',
'total = 1000 # type: int ',
'total = 1000#type:int',
'numbs = [1, 2, 3] # type: missing',
'numbs = [1, 2, 3] # type: List[int]',
'numbs = [1, 2, 3] # type: List["int"]',
"numbs = [1, 2, 3] # type: List['int']",
'field = SomeField() # type: drf.Field',
'# type: fixme',
])
def test_incorrect_noqa_comment(
parse_tokens,
assert_errors,
default_options,
code,
):
"""Ensures that incorrect `type` comments raise a warning."""
file_tokens = parse_tokens(code)

visitor = WrongCommentVisitor(default_options, file_tokens=file_tokens)
visitor.run()

assert_errors(visitor, [WrongMagicCommentViolation])
3 changes: 2 additions & 1 deletion wemake_python_styleguide/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@

#: List of nested classes' names we allow to use.
NESTED_CLASSES_WHITELIST = frozenset((
'Meta',
'Meta', # django forms, models, drf, etc
'Params', # factoryboy specific
))

#: List of nested functions' names we allow to use.
Expand Down
5 changes: 1 addition & 4 deletions wemake_python_styleguide/errors/tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,11 @@ class WrongMagicCommentViolation(SimpleStyleViolation):
We do not allow to use:
1. ``# noqa`` comment without specified errors
2. ``# flake8: noqa`` comment to disable the whole file
3. ``type: some_type`` comments to specify a type for ``typed_ast``
2. ``type: some_type`` comments to specify a type for ``typed_ast``
Reasoning:
We cover several different use-cases in a single rule.
``# noqa`` comment is restricted because it can hide other errors.
``# flake8: noqa`` is restricted because it is hard to tell
what files are ignored with this comment.
``type: int`` comment is restricted because
we can already use type annotations instead.
Expand Down
21 changes: 19 additions & 2 deletions wemake_python_styleguide/visitors/tokenize/wrong_comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,40 @@
from wemake_python_styleguide.visitors.base import BaseTokenVisitor

NOQA_CHECK: Pattern = re.compile(r'^noqa:?($|[A-Z\d\,\s]+)')
TYPE_CHECK: Pattern = re.compile(r'^type:\s?([\w\d\[\]\'\"\.]+)$')


class WrongCommentVisitor(BaseTokenVisitor):
"""Checks comment tokens."""

def _get_comment_text(self, token: tokenize.TokenInfo) -> str:
return token.string[1:].strip()

def _check_noqa(self, token: tokenize.TokenInfo) -> None:
comment_text = token.string[1:].strip()
comment_text = self._get_comment_text(token)
match: Match = NOQA_CHECK.match(comment_text)
if not match:
return

excludes = match.groups()[0].strip()
if not excludes:
# We can not pass the actual line here,
# since it will be ignored due to `noqa` comment:
# since it will be ignored due to `# noqa` comment:
self.add_error(WrongMagicCommentViolation(text=comment_text))

def _check_typed_ast(self, token: tokenize.TokenInfo) -> None:
comment_text = self._get_comment_text(token)
match: Match = TYPE_CHECK.match(comment_text)
if not match:
return

declared_type = match.groups()[0].strip()
if declared_type != 'ignore':
self.add_error(
WrongMagicCommentViolation(token, text=comment_text),
)

def visit_comment(self, token: tokenize.TokenInfo) -> None:
"""Performs comment checks."""
self._check_noqa(token)
self._check_typed_ast(token)

0 comments on commit daaf848

Please sign in to comment.