Skip to content

Commit

Permalink
implement
Browse files Browse the repository at this point in the history
  • Loading branch information
orsinium committed Jan 14, 2021
0 parents commit 8cb68be
Show file tree
Hide file tree
Showing 13 changed files with 687 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.mypy_cache/
.pytest_cache/
__pycache__/
3 changes: 3 additions & 0 deletions flake8_codes/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from ._cli import entrypoint

entrypoint()
69 changes: 69 additions & 0 deletions flake8_codes/_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import sys
from typing import Iterator, List, NamedTuple, NoReturn, TextIO

from flake8.main.application import Application

from ._codes import extract
from ._plugins import get_installed, Plugin


TEMPLATE = '{c.plugin.name:20} | {c.code:8} | {c.message}'


class Code(NamedTuple):
code: str
message: str
plugin: Plugin


def normalize(name: str) -> str:
return name.replace('-', '_').lower()


def get_codes(lookup_name: str) -> Iterator[Code]:
app = Application()
plugins = sorted(get_installed(app=app), key=lambda p: p.name)
if not plugins:
return

checked = set()
for plugin in plugins:
if plugin.name in checked:
continue
checked.add(plugin.name)

is_prefix = lookup_name.startswith(tuple(plugin.codes))
is_name = normalize(lookup_name) == normalize(plugin.name)
if not is_name and not is_prefix:
continue

try:
codes = extract(plugin.name)
except ImportError:
continue
for code in codes:
if is_prefix and not code.startswith(lookup_name):
continue
yield Code(
code=code,
message=codes[code],
plugin=plugin,
)


def print_codes(lookup_name: str, stream: TextIO) -> int:
count = 0
for code in get_codes(lookup_name):
count += 1
print(TEMPLATE.format(c=code))
return count


def main(argv: List[str], stream: TextIO) -> int:
count = print_codes(argv[0], stream=stream)
return int(count == 0)


def entrypoint() -> NoReturn:
code = main(argv=sys.argv[1:], stream=sys.stdout)
sys.exit(code)
3 changes: 3 additions & 0 deletions flake8_codes/_codes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from ._extract import extract

__all__ = ['extract']
274 changes: 274 additions & 0 deletions flake8_codes/_codes/_adhoc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
# built-in
import re
from importlib import import_module
from pathlib import Path
from typing import Dict

from ._registry import registry


@registry.add
def extract_flake8_commas() -> Dict[str, str]:
from flake8_commas._base import ERRORS

return dict(ERRORS.values())


@registry.add
def extract_flake8_debugger() -> Dict[str, str]:
from flake8_debugger import DEBUGGER_ERROR_CODE
return {DEBUGGER_ERROR_CODE: 'trace found'}


@registry.add
def extract_flake8_mutable() -> Dict[str, str]:
from mutable_defaults import MutableDefaultChecker

return {MutableDefaultChecker._code: MutableDefaultChecker._error_tmpl}


@registry.add
def extract_flake8_fixme() -> Dict[str, str]:
from flake8_fixme import WORD_CODES

return {code: 'fixme found ({})'.format(word) for word, code in WORD_CODES.items()}


@registry.add
def extract_pep8_naming() -> Dict[str, str]:
import pep8ext_naming

codes = dict()
for checker_name in dir(pep8ext_naming):
if not checker_name.endswith('Check'):
continue
checker = getattr(pep8ext_naming, checker_name)
for code, message in checker.__dict__.items():
if code[0] == 'N':
codes[code] = message
return codes


@registry.add
def extract_flake8_pyi() -> Dict[str, str]:
import pyi
codes = dict()
for name, value in vars(pyi).items():
if name.startswith('Y0'):
codes[name] = value
return codes


@registry.add
def extract_flake8_pytest_style() -> Dict[str, str]:
from flake8_pytest_style import errors
codes = dict()
for error in vars(errors).values():
if error is errors.Error:
continue
if not isinstance(error, type):
continue
if not issubclass(error, errors.Error):
continue
codes[error.code] = error.message # type: ignore
return codes


@registry.add
def extract_flake8_annotations_complexity() -> Dict[str, str]:
from flake8_annotations_complexity.checker import AnnotationsComplexityChecker

code, message = AnnotationsComplexityChecker._error_message_template.split(' ', maxsplit=1)
return {code: message}


@registry.add
def extract_flake8_future_import() -> Dict[str, str]:
from flake8_future_import import ALL_FEATURES
codes = dict()
tmpl = 'FI{}'
for feature in ALL_FEATURES:
code = tmpl.format(10 + feature.index)
codes[code] = '__future__ import "{}" missing'.format(feature.name)
code = tmpl.format(50 + feature.index)
codes[code] = '__future__ import "{}" present'.format(feature.name)
codes[tmpl.format(90)] = '__future__ import does not exist'
return codes


@registry.add
def extract_flake8_string_format() -> Dict[str, str]:
from flake8_string_format import StringFormatChecker

return {'P{}'.format(c): m for c, m in StringFormatChecker.ERRORS.items()}


@registry.add
def extract_flake8_bandit() -> Dict[str, str]:
from bandit.core.extension_loader import MANAGER

codes = dict()
for blacklist in MANAGER.blacklist.values():
for check in blacklist:
code = check['id'].replace('B', 'S')
codes[code] = check['message']
for plugin in MANAGER.plugins:
code = plugin.plugin._test_id.replace('B', 'S')
codes[code] = plugin.name.replace('_', ' ')
return codes


@registry.add
def extract_pylint() -> Dict[str, str]:
import pylint.checkers
try:
from pylint.lint import MSGS
except ImportError:
from pylint.lint.pylinter import MSGS

codes = dict()
for code, (msg, alias, *_) in MSGS.items():
if msg in ('%s', '%s: %s'):
msg = alias.replace('-', ' ')
codes[code] = msg.replace('\n', ' ')

for path in Path(pylint.checkers.__path__[0]).iterdir():
module = import_module('pylint.checkers.' + path.stem)
for class_name in dir(module):
cls = getattr(module, class_name, None)
msgs = getattr(cls, 'msgs', None)
if not msgs:
continue
for code, (msg, alias, *_) in msgs.items():
if msg in ('%s', '%s: %s'):
msg = alias.replace('-', ' ')
codes[code] = msg.replace('\n', ' ')
return codes


@registry.add
def extract_pyflakes() -> Dict[str, str]:
from flake8.plugins.pyflakes import FLAKE8_PYFLAKES_CODES
from pyflakes import messages

codes = dict()
for class_name, code in FLAKE8_PYFLAKES_CODES.items():
codes[code] = getattr(messages, class_name).message
return codes


@registry.add
def extract_flake8_rst_docstrings() -> Dict[str, str]:
from flake8_rst_docstrings import code_mappings_by_level

codes = dict()
for level, codes_mapping in code_mappings_by_level.items():
for message, number in codes_mapping.items():
code = 'RST{}{:02d}'.format(level, number)
codes[code] = message
return codes


@registry.add
def extract_flake8_django() -> Dict[str, str]:
import flake8_django.checkers

codes = dict()
for path in Path(flake8_django.checkers.__path__[0]).iterdir():
module = import_module('flake8_django.checkers.' + path.stem)
for class_name in dir(module):
cls = getattr(module, class_name, None)
if not hasattr(cls, 'code'):
continue
if '0' not in cls.__name__:
continue
codes[cls.__name__] = cls.description
return codes


@registry.add
def extract_flake8_scrapy() -> Dict[str, str]:
from flake8_scrapy import ScrapyStyleIssueFinder

codes = dict()
for finders in ScrapyStyleIssueFinder().finders.values():
for finder in finders:
codes[finder.msg_code] = finder.msg_info
return codes


@registry.add
def extract_flake8_executable() -> Dict[str, str]:
import flake8_executable

path = Path(flake8_executable.__file__)
content = path.read_text()
codes = dict()
for code, msg in re.findall(r"'(EXE00\d)', '(.*)'", content):
codes[code] = msg
return codes


@registry.add
def extract_flake8_strict() -> Dict[str, str]:
from flake8_strict import ErrorCode

codes = dict()
for code, message in ErrorCode._member_map_.items():
codes[code] = message.value
return codes


@registry.add
def extract_flake8_docstrings() -> Dict[str, str]:
from pydocstyle.violations import ErrorRegistry

codes = dict()
for group in ErrorRegistry.groups:
for error in group.errors:
codes[error.code] = error.short_desc
return codes


@registry.add
def extract_dlint() -> Dict[str, str]:
from dlint.linters import ALL

codes = dict()
for linter in ALL:
code, msg = linter._error_tmpl.split(' ', maxsplit=1)
codes[code] = msg
return codes


@registry.add
def extract_flake8_mock() -> Dict[str, str]:
from flake8_mock import MOCK_ERROR_CODE, ERROR_MESSAGE

message = ERROR_MESSAGE.split(' ', maxsplit=1)[1]
return {MOCK_ERROR_CODE: message}


@registry.add
def extract_flake8_pytest() -> Dict[str, str]:
from flake8_pytest import PYTEST_ERROR_CODE, PYTEST_ERROR_MESSAGE

return {PYTEST_ERROR_CODE: PYTEST_ERROR_MESSAGE}


@registry.add
def extract_wemake_python_styleguide() -> Dict[str, str]:
from wemake_python_styleguide import violations

codes = dict()
for path in Path(violations.__path__[0]).iterdir():
module = import_module('wemake_python_styleguide.violations.' + path.stem)
for checker_name in dir(module):
if not checker_name.endswith('Violation'):
continue
checker = getattr(module, checker_name)
if not hasattr(checker, 'code'):
continue
code = 'WPS' + str(checker.code).zfill(3)
codes[code] = checker.error_template
return codes
42 changes: 42 additions & 0 deletions flake8_codes/_codes/_default.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# built-in
import ast
import re
from importlib import import_module
from pathlib import Path
from typing import Dict, List


REX_CODE = re.compile(r'^[A-Z]{1,5}[0-9]{1,5}$')


class CollectStrings(ast.NodeVisitor):
_strings: List[str]

def visit_Str(self, node):
self._strings.append(node.s)


def get_messages(code: str, content: str) -> Dict[str, str]:
root = ast.parse(content)
CollectStrings._strings = []
collector = CollectStrings()
collector.visit(root)

messages = dict()
for message in collector._strings:
message_code, _, message_text = message.partition(' ')
if not message_text:
continue
message_code = message_code.rstrip(':')
if not REX_CODE.match(message_code):
continue
if code and not message_code.startswith(code):
continue
messages[message_code] = message_text
return messages


def extract_default(name: str) -> Dict[str, str]:
module = import_module(name)
content = Path(module.__file__).read_text()
return get_messages(code='', content=content)
Loading

0 comments on commit 8cb68be

Please sign in to comment.