Try to implement some of the pydocstyle functionality in the normalizer function.

This commit is contained in:
Dave Halter
2017-06-04 18:13:29 +02:00
parent e82976fb37
commit f13643b6cd
4 changed files with 154 additions and 23 deletions

View File

@@ -1,10 +1,34 @@
from contextlib import contextmanager
class Normalizer(object):
def __init__(self, config):
self._config = config
self.issues = []
@contextmanager
def visit_node(self):
yield
def normalize(self, leaf):
return leaf.prefix + leaf.value
class NormalizerConfig(object):
normalizer_class = Normalizer
def create_normalizer(self):
if self.normalizer_class is None:
return None
return self.normalizer_class(self)
@classmethod
def register_rule(cls, rule):
"""
Use it as a class decorator:
>>> normalizer = Normalizer()
>>> normalizer = NormalizerConfig()
>>> @normalizer.register_rule
... class MyRule(Rule):
... error_code = 42
@@ -16,12 +40,6 @@ class Normalizer(object):
rules.append(rule)
return rule
def normalize(self, leaf):
return leaf.prefix + leaf.value
def iter_errors(self, leaf):
return iter([])
class Error(object):
def __init__(self, leaf, code, message):

View File

@@ -1,6 +1,9 @@
from parso.normalizer import Normalizer, Error
from contextlib import contextmanager
from parso.normalizer import Normalizer, Rule, NormalizerConfig
IMPORT_TYPES = ('import_name', 'import_from')
class CompressNormalizer(Normalizer):
"""
Removes comments and whitespace.
@@ -9,16 +12,114 @@ class CompressNormalizer(Normalizer):
return leaf.prefix + leaf.value
class WhitespaceInfo(object):
def __init__(self, leaf):
parts = list(leaf._split_prefix())
'''
' ': 'spaces',
'#': 'comment',
'\\': 'backslash',
'\f': 'formfeed',
'\n': 'newline',
'\r': 'newline',
'\t': 'tabs',
'''
for part in parts:
if part.type:
part
self.newline_count = 2
self.indentation = ' '
self.trailing_whitespace = []
self.comment_whitespace = []
class PEP8Normalizer(Normalizer):
def __init__(self, config):
super(PEP8Normalizer, self).__init__(config)
self.indentation = 0
@contextmanager
def visit_node(self, state, node):
typ = node.type
if typ in 'import_name':
names = node.get_defined_names()
if len(names) > 1:
for name in names[:1]:
self.log_error(401, 'Multiple imports on one line', name)
elif typ == 'lambdef':
if node.parent.type == 'expr_stmt':
self.log_error(731, 'Do not assign a lambda expression, use a def', node)
elif typ == 'try_stmt':
for child in node.children:
# Here we can simply check if it's an except, because otherwise
# it would be an except_clause.
if child == 'except':
self.log_error(722, 'Do not use bare except, specify exception instead', node)
elif typ == 'comparison':
odd = False
for child in node.children:
if odd:
if child not in ('is', '=='):
break
else:
if child.type == 'atom_expr':
break
trailer = child.children[-1]
atom = child.children[-1]
if not (trailer.type == 'trailer' and atom.type == 'name'
and atom.value == 'type'):
break
odd = not odd
else:
self.log_error(721, "Do not compare types, use 'isinstance()", node)
if typ in IMPORT_TYPES:
module = node.parent
if module.type == 'file_input':
index = module.children.index(node)
for child in module.children[:index]:
if child.type not in IMPORT_TYPES:
self.log_error(402, 'Module level import not at top of file', node)
break
if typ == 'suite':
self.indentation += 1
yield
if typ == 'suite':
self.indentation -= 1
def normalize(self, leaf):
typ = leaf.type
if typ == 'name' and leaf.value in ('l', 'O', 'I'):
if leaf.is_definition():
message = "Do not define %s named 'l', 'O', or 'I' one line"
if leaf.parent.type == 'class' and leaf.parent.name == leaf:
self.log_error(742, message % 'classes', leaf)
elif leaf.parent.type == 'function' and leaf.parent.name == leaf:
self.log_error(743, message % 'function', leaf)
else:
self.log_error(741, message % 'variables', leaf)
for part in leaf._split_prefix():
part
return leaf.value
class PEP8NormalizerConfig(NormalizerConfig):
normalizer_class = PEP8Normalizer
"""
Normalizing to PEP8. Not really implemented, yet.
"""
def normalize(self, leaf):
return leaf.value
def iter_errors(self, leaf):
return iter([])
class Rule():
@PEP8NormalizerConfig.register_rule
class FooRule(Rule):
pass
@PEP8NormalizerConfig.register_rule
class BlankLineAtEnd(Rule):
code = 'W391'
message = 'blank line at end of file'
leaf_event = ['endmarker']

View File

@@ -65,7 +65,7 @@ class PythonMixin(object):
Some Python specific utitilies.
"""
__slots__ = ()
default_normalizer = normalizer.PEP8Normalizer()
default_normalizer_config = normalizer.PEP8NormalizerConfig()
def get_definition(self):
if self.type in ('newline', 'endmarker'):

View File

@@ -22,7 +22,7 @@ class NodeOrLeaf(object):
The base class for nodes and leaves.
"""
__slots__ = ()
default_normalizer = None
default_normalizer_config = None
def get_root_node(self):
"""
@@ -154,22 +154,34 @@ class NodeOrLeaf(object):
e.g. a statement.
"""
def normalize(self, normalizer=None):
def _get_normalizer(self, normalizer_config):
if normalizer_config is None:
normalizer_config = self.default_normalizer_config
if normalizer_config is None:
raise ValueError("You need to specify a normalizer, because "
"there's no default normalizer for this tree.")
return normalizer_config.create_normalizer()
def normalize(self, normalizer_config=None):
"""
The returned code will be normalized, e.g. PEP8 for Python.
"""
if normalizer is None:
normalizer = self.default_normalizer
if normalizer is None:
raise ValueError("You need to specify a normalizer, because "
"there's no default normalizer for this tree.")
normalizer = self._get_normalizer(normalizer_config)
return self._normalize(normalizer)
def _normalize(self, normalizer):
try:
children = self.children
except AttributeError:
return normalizer.normalize(self)
else:
return ''.join(child.normalize(normalizer) for child in children)
with normalizer.visit_node(self):
return ''.join(child._normalize(normalizer) for child in children)
def _get_normalize_errors(self, normalizer_config=None):
normalizer = self._get_normalizer(normalizer_config)
self._normalize(normalizer)
return normalizer.issues