Skip to content
Snippets Groups Projects
Unverified Commit 344ce400 authored by Ian Stapleton Cordasco's avatar Ian Stapleton Cordasco Committed by GitHub
Browse files

Merge pull request #39 from saifelse/decorator-config

Add configurable list of classmethod / staticmethod decorators
parents 5a2c7b0c 08403da8
No related branches found
No related tags found
No related merge requests found
# -*- coding: utf-8 -*-
"""Checker of PEP-8 Naming Conventions."""
import optparse
import re
import sys
from collections import deque
 
from flake8_polyfill import options
try:
import ast
from ast import iter_child_nodes
Loading
Loading
@@ -16,7 +17,6 @@ __version__ = '0.4.1'
LOWERCASE_REGEX = re.compile(r'[_a-z][_a-z0-9]*$')
UPPERCASE_REGEX = re.compile(r'[_A-Z][_A-Z0-9]*$')
MIXEDCASE_REGEX = re.compile(r'_?[A-Z][a-zA-Z0-9]*$')
SPLIT_IGNORED_RE = re.compile(r'[,\s]')
 
 
if sys.version_info[0] < 3:
Loading
Loading
@@ -64,18 +64,24 @@ BaseASTCheck = _ASTCheckMeta('BaseASTCheck', (object,),
{'__doc__': "Base for AST Checks.", 'err': _err})
 
 
def register_opt(parser, *args, **kwargs):
try:
# Flake8 3.x registration
parser.add_option(*args, **kwargs)
except (optparse.OptionError, TypeError):
# Flake8 2.x registration
parse_from_config = kwargs.pop('parse_from_config', False)
kwargs.pop('comma_separated_list', False)
kwargs.pop('normalize_paths', False)
parser.add_option(*args, **kwargs)
if parse_from_config:
parser.config_options.append(args[-1].lstrip('-'))
class _FunctionType(object):
CLASSMETHOD = 'classmethod'
STATICMETHOD = 'staticmethod'
FUNCTION = 'function'
METHOD = 'method'
_default_classmethod_decorators = ['classmethod']
_default_staticmethod_decorators = ['staticmethod']
def _build_decorator_to_type(classmethod_decorators, staticmethod_decorators):
decorator_to_type = {}
for decorator in classmethod_decorators:
decorator_to_type[decorator] = _FunctionType.CLASSMETHOD
for decorator in staticmethod_decorators:
decorator_to_type[decorator] = _FunctionType.STATICMETHOD
return decorator_to_type
 
 
class NamingChecker(object):
Loading
Loading
@@ -83,6 +89,8 @@ class NamingChecker(object):
name = 'naming'
version = __version__
ignore_names = ['setUp', 'tearDown', 'setUpClass', 'tearDownClass']
decorator_to_type = _build_decorator_to_type(
_default_classmethod_decorators, _default_staticmethod_decorators)
 
def __init__(self, tree, filename):
self.visitors = BaseASTCheck._checks
Loading
Loading
@@ -91,21 +99,41 @@ class NamingChecker(object):
 
@classmethod
def add_options(cls, parser):
ignored = ','.join(cls.ignore_names)
register_opt(parser, '--ignore-names',
default=ignored,
action='store',
type='string',
parse_from_config=True,
comma_separated_list=True,
help='List of names the pep8-naming plugin should '
'ignore. (Defaults to %default)')
options.register(parser, '--ignore-names',
default=cls.ignore_names,
action='store',
type='string',
parse_from_config=True,
comma_separated_list=True,
help='List of names the pep8-naming plugin should '
'ignore. (Defaults to %default)')
options.register(parser, '--classmethod-decorators',
default=_default_classmethod_decorators,
action='store',
type='string',
parse_from_config=True,
comma_separated_list=True,
help='List of method decorators pep8-naming plugin '
'should consider classmethods (Defaults to '
'%default)')
options.register(parser, '--staticmethod-decorators',
default=_default_staticmethod_decorators,
action='store',
type='string',
parse_from_config=True,
comma_separated_list=True,
help='List of method decorators pep8-naming plugin '
'should consider staticmethods (Defaults to '
'%default)')
 
@classmethod
def parse_options(cls, options):
cls.ignore_names = options.ignore_names
if not isinstance(cls.ignore_names, list):
cls.ignore_names = SPLIT_IGNORED_RE.split(options.ignore_names)
cls.decorator_to_type = _build_decorator_to_type(
options.classmethod_decorators,
options.staticmethod_decorators)
 
def run(self):
return self.visit_tree(self._node) if self._node else ()
Loading
Loading
@@ -146,26 +174,27 @@ class NamingChecker(object):
isinstance(node.value.func, ast.Name)):
continue
func_name = node.value.func.id
if func_name in ('classmethod', 'staticmethod'):
meth = (len(node.value.args) == 1 and node.value.args[0])
if isinstance(meth, ast.Name):
late_decoration[meth.id] = func_name
if func_name not in self.decorator_to_type:
continue
meth = (len(node.value.args) == 1 and node.value.args[0])
if isinstance(meth, ast.Name):
late_decoration[meth.id] = self.decorator_to_type[func_name]
 
# iterate over all functions and tag them
for node in iter_child_nodes(cls_node):
if not isinstance(node, ast.FunctionDef):
continue
 
node.function_type = 'method'
node.function_type = _FunctionType.METHOD
if node.name in ('__new__', '__init_subclass__'):
node.function_type = 'classmethod'
node.function_type = _FunctionType.CLASSMETHOD
if node.name in late_decoration:
node.function_type = late_decoration[node.name]
elif node.decorator_list:
names = [d.id for d in node.decorator_list
names = [self.decorator_to_type[d.id]
for d in node.decorator_list
if isinstance(d, ast.Name) and
d.id in ('classmethod', 'staticmethod')]
d.id in self.decorator_to_type]
if names:
node.function_type = names[0]
 
Loading
Loading
@@ -209,7 +238,7 @@ class FunctionNameCheck(BaseASTCheck):
N802 = "function name '{name}' should be lowercase xxx"
 
def visit_functiondef(self, node, parents, ignore=None):
function_type = getattr(node, 'function_type', 'function')
function_type = getattr(node, 'function_type', _FunctionType.FUNCTION)
name = node.name
if ignore and name in ignore:
return
Loading
Loading
@@ -253,10 +282,10 @@ class FunctionArgNamesCheck(BaseASTCheck):
return
function_type = getattr(node, 'function_type', 'function')
 
if function_type == 'method':
if function_type == _FunctionType.METHOD:
if arg_names[0] != 'self':
yield self.err(node, 'N805')
elif function_type == 'classmethod':
elif function_type == _FunctionType.CLASSMETHOD:
if arg_names[0] != 'cls':
yield self.err(node, 'N804')
for arg in arg_names:
Loading
Loading
import sys
import optparse
import os
import pep8ext_naming
import re
import sys
import pep8ext_naming
 
PyCF_ONLY_AST = 1024
 
Loading
Loading
@@ -9,6 +12,8 @@ IS_PY3 = sys.version_info[0] == 3
IS_PY3_TEST = re.compile(r"^#\s*python3\s*only")
IS_PY2_TEST = re.compile(r"^#\s*python2\s*only")
 
TESTCASE_RE = re.compile("^#: (?P<code>\w+)(\((?P<options>.+)\))?$")
 
def main():
print('Running pep8-naming tests')
Loading
Loading
@@ -19,10 +24,9 @@ def main():
lines = list(fd)
if not is_test_allowed(lines):
continue
for testcase, codes in load_tests(lines):
for testcase, code, options in load_tests(lines):
test_count += 1
errors += test_file(filename, testcase, codes)
errors += test_file(filename, testcase, code, options)
 
if errors == 0:
print("%s tests run successful" % test_count)
Loading
Loading
@@ -43,38 +47,59 @@ def is_test_allowed(lines):
 
 
def load_tests(lines):
options = None
testcase = []
codes = []
code = None
for line in lines:
if line.startswith("#:"):
line_match = TESTCASE_RE.match(line)
if line_match:
if testcase:
yield testcase, codes
yield testcase, code, options
del testcase[:]
codes = line.split()[1:]
code = line_match.group('code')
if line_match.group('options'):
options = [line_match.group('options')]
else:
options = None
else:
testcase.append(line)
 
if testcase and codes:
yield testcase, codes
if testcase and code:
yield testcase, code, options
 
class OptionsManager(optparse.OptionParser):
"""A Flake8-2.x-compatible OptionsManager."""
def __init__(self, *args, **kwargs):
optparse.OptionParser.__init__(self, *args, **kwargs)
self.config_options = []
 
def test_file(filename, lines, codes):
def parse_options(checker, options):
"""Parse the CLI-style flags from `options` and expose to `checker`"""
options_manager = OptionsManager('flake8')
checker.add_options(options_manager)
processed_options, _ = options_manager.parse_args(options)
checker.parse_options(processed_options)
def test_file(filename, lines, code, options):
tree = compile(''.join(lines), '', 'exec', PyCF_ONLY_AST)
checker = pep8ext_naming.NamingChecker(tree, filename)
parse_options(checker, options)
found_errors = []
for lineno, col_offset, msg, instance in checker.run():
found_errors.append(msg.split()[0])
 
if not found_errors and codes == ['Okay']:
if code is None: # Invalid test case
return 0
errors = 0
for code in codes:
if code not in found_errors:
errors += 1
print("ERROR: %s not in %s" % (code, filename))
return errors
if not found_errors and code == 'Okay': # Expected PASS
return 0
if code in found_errors: # Expected FAIL
return 0
print("ERROR: %s not in %s" % (code, filename))
return 1
 
 
if __name__ == '__main__':
Loading
Loading
Loading
Loading
@@ -43,6 +43,7 @@ setup(
url='https://github.com/flintwork/pep8-naming',
license='Expat license',
py_modules=['pep8ext_naming'],
install_requires=['flake8_polyfill>=1.0.2,<2'],
zip_safe=False,
entry_points={
'flake8.extension': [
Loading
Loading
Loading
Loading
@@ -14,3 +14,8 @@ class Foo(object):
 
def __init_subclass(self, ads):
pass
#: N804(--classmethod-decorators=clazzy,cool)
class NewClassIsRequired(object):
@cool
def test(self, sy):
pass
Loading
Loading
@@ -15,7 +15,72 @@ class Foo(object):
def __prepare__(cls):
pass
 
@staticmethod
def test(so, exciting):
pass
def test1(cls):
pass
test1 = classmethod(test1)
def test2(so, exciting):
pass
test2 = staticmethod(test2)
#: Okay
class Foo(object):
def __init_subclass__(cls):
pass
#: Okay(--classmethod-decorators=clazzy,cool)
class NewClassmethodDecorators(object):
@clazzy
def test1(cls, sy):
pass
@cool
def test2(cls, sy):
pass
def test3(cls, sy):
pass
test3 = clazzy(test3)
def test4(cls, sy):
pass
test4 = cool(test4)
#: N805(--classmethod-decorators=clazzy,cool)
class ButWeLostTheOriginalClassMethodDecorator(object):
@classmethod
def test(cls, sy):
pass
#: N805(--classmethod-decorators=clazzy,cool)
class ButWeLostTheOriginalClassMethodLateDecorator(object):
def test(cls, sy):
pass
test = classmethod(test)
#: Okay(--staticmethod-decorators=ecstatik,stcmthd)
class NewStaticMethodDecorators(object):
@ecstatik
def test1(so, exciting):
pass
@stcmthd
def test2(so, exciting):
pass
def test3(so, exciting):
pass
test3 = ecstatik(test3)
def test4(so, exciting):
pass
test4 = stcmthd(test4)
#: N805(--staticmethod-decorators=exstatik,stcmthd)
class ButWeLostTheOriginalStaticMethodDecorator(object):
@staticmethod
def test(so, exciting):
pass
#: N805(--staticmethod-decorators=exstatik,stcmthd)
class ButWeLostTheOriginalStaticMethodLateDecorator(object):
def test(so, exciting):
pass
test = staticmethod(test)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment