diff options
Diffstat (limited to 'checkers/base.py')
-rw-r--r-- | checkers/base.py | 182 |
1 files changed, 101 insertions, 81 deletions
diff --git a/checkers/base.py b/checkers/base.py index 6795457..497aa40 100644 --- a/checkers/base.py +++ b/checkers/base.py @@ -13,7 +13,7 @@ # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """basic checker for Python code""" import sys @@ -52,11 +52,15 @@ NO_REQUIRED_DOC_RGX = re.compile('__.*__') REVERSED_METHODS = (('__getitem__', '__len__'), ('__reversed__', )) +PY33 = sys.version_info >= (3, 3) BAD_FUNCTIONS = ['map', 'filter', 'apply'] if sys.version_info < (3, 0): BAD_FUNCTIONS.append('input') BAD_FUNCTIONS.append('file') +# Name categories that are always consistent with all naming conventions. +EXEMPT_NAME_CATEGORIES = {'exempt', 'ignore'} + del re def in_loop(node): @@ -241,7 +245,8 @@ class BasicErrorChecker(_BasicChecker): 'return-arg-in-generator', 'Used when a "return" statement with an argument is found ' 'outside in a generator function or method (e.g. with some ' - '"yield" statements).'), + '"yield" statements).', + {'maxversion': (3, 3)}), 'E0107': ("Use of the non-existent %s operator", 'nonexistent-operator', "Used when you attempt to use the C-style pre-increment or" @@ -292,11 +297,12 @@ class BasicErrorChecker(_BasicChecker): self.add_message('return-in-init', node=node) elif node.is_generator(): # make sure we don't mix non-None returns and yields - for retnode in returns: - if isinstance(retnode.value, astroid.Const) and \ - retnode.value.value is not None: - self.add_message('return-arg-in-generator', node=node, - line=retnode.fromlineno) + if not PY33: + for retnode in returns: + if isinstance(retnode.value, astroid.Const) and \ + retnode.value.value is not None: + self.add_message('return-arg-in-generator', node=node, + line=retnode.fromlineno) # Check for duplicate names args = set() for name in node.argnames(): @@ -447,6 +453,12 @@ functions, methods 'exec-used', 'Used when you use the "exec" statement (function for Python 3), to discourage its \ usage. That doesn\'t mean you can not use it !'), + 'W0123': ('Use of eval', + 'eval-used', + 'Used when you use the "eval" function, to discourage its ' + 'usage. Consider using `ast.literal_eval` for safely evaluating ' + 'strings containing Python expressions ' + 'from untrusted sources. '), 'W0141': ('Used builtin function %r', 'bad-builtin', 'Used when a black listed builtin function is used (see the ' @@ -677,7 +689,7 @@ functions, methods """just print a warning on exec statements""" self.add_message('exec-used', node=node) - @check_messages('bad-builtin', 'star-args', + @check_messages('bad-builtin', 'star-args', 'eval-used', 'exec-used', 'missing-reversed-argument', 'bad-reversed-sequence') def visit_callfunc(self, node): @@ -694,6 +706,8 @@ functions, methods self.add_message('exec-used', node=node) elif name == 'reversed': self._check_reversed(node) + elif name == 'eval': + self.add_message('eval-used', node=node) if name in self.config.bad_functions: self.add_message('bad-builtin', node=node, args=name) if node.starargs or node.kwargs: @@ -808,79 +822,47 @@ functions, methods # everything else is not a proper sequence for reversed() self.add_message('bad-reversed-sequence', node=node) +_NAME_TYPES = { + 'module': (MOD_NAME_RGX, 'module'), + 'const': (CONST_NAME_RGX, 'constant'), + 'class': (CLASS_NAME_RGX, 'class'), + 'function': (DEFAULT_NAME_RGX, 'function'), + 'method': (DEFAULT_NAME_RGX, 'method'), + 'attr': (DEFAULT_NAME_RGX, 'attribute'), + 'argument': (DEFAULT_NAME_RGX, 'argument'), + 'variable': (DEFAULT_NAME_RGX, 'variable'), + 'class_attribute': (CLASS_ATTRIBUTE_RGX, 'class attribute'), + 'inlinevar': (COMP_VAR_RGX, 'inline iteration'), +} + +def _create_naming_options(): + name_options = [] + for name_type, (rgx, human_readable_name) in _NAME_TYPES.iteritems(): + name_type = name_type.replace('_', '-') + name_options.append(( + '%s-rgx' % (name_type,), + {'default': rgx, 'type': 'regexp', 'metavar': '<regexp>', + 'help': 'Regular expression matching correct %s names' % (human_readable_name,)})) + name_options.append(( + '%s-name-hint' % (name_type,), + {'default': rgx.pattern, 'type': 'string', 'metavar': '<string>', + 'help': 'Naming hint for %s names' % (human_readable_name,)})) + + return tuple(name_options) + class NameChecker(_BasicChecker): msgs = { 'C0102': ('Black listed name "%s"', 'blacklisted-name', 'Used when the name is listed in the black list (unauthorized \ names).'), - 'C0103': ('Invalid %s name "%s"', + 'C0103': ('Invalid %s name "%s"%s', 'invalid-name', 'Used when the name doesn\'t match the regular expression \ associated to its type (constant, variable, class...).'), } - options = (('module-rgx', - {'default' : MOD_NAME_RGX, - 'type' :'regexp', 'metavar' : '<regexp>', - 'help' : 'Regular expression which should only match correct ' - 'module names'} - ), - ('const-rgx', - {'default' : CONST_NAME_RGX, - 'type' :'regexp', 'metavar' : '<regexp>', - 'help' : 'Regular expression which should only match correct ' - 'module level names'} - ), - ('class-rgx', - {'default' : CLASS_NAME_RGX, - 'type' :'regexp', 'metavar' : '<regexp>', - 'help' : 'Regular expression which should only match correct ' - 'class names'} - ), - ('function-rgx', - {'default' : DEFAULT_NAME_RGX, - 'type' :'regexp', 'metavar' : '<regexp>', - 'help' : 'Regular expression which should only match correct ' - 'function names'} - ), - ('method-rgx', - {'default' : DEFAULT_NAME_RGX, - 'type' :'regexp', 'metavar' : '<regexp>', - 'help' : 'Regular expression which should only match correct ' - 'method names'} - ), - ('attr-rgx', - {'default' : DEFAULT_NAME_RGX, - 'type' :'regexp', 'metavar' : '<regexp>', - 'help' : 'Regular expression which should only match correct ' - 'instance attribute names'} - ), - ('argument-rgx', - {'default' : DEFAULT_NAME_RGX, - 'type' :'regexp', 'metavar' : '<regexp>', - 'help' : 'Regular expression which should only match correct ' - 'argument names'}), - ('variable-rgx', - {'default' : DEFAULT_NAME_RGX, - 'type' :'regexp', 'metavar' : '<regexp>', - 'help' : 'Regular expression which should only match correct ' - 'variable names'} - ), - ('class-attribute-rgx', - {'default' : CLASS_ATTRIBUTE_RGX, - 'type' :'regexp', 'metavar' : '<regexp>', - 'help' : 'Regular expression which should only match correct ' - 'attribute names in class bodies'} - ), - ('inlinevar-rgx', - {'default' : COMP_VAR_RGX, - 'type' :'regexp', 'metavar' : '<regexp>', - 'help' : 'Regular expression which should only match correct ' - 'list comprehension / generator expression variable \ - names'} - ), - # XXX use set + options = (# XXX use set ('good-names', {'default' : ('i', 'j', 'k', 'ex', 'Run', '_'), 'type' :'csv', 'metavar' : '<names>', @@ -893,7 +875,24 @@ class NameChecker(_BasicChecker): 'help' : 'Bad variable names which should always be refused, ' 'separated by a comma'} ), - ) + ('name-group', + {'default' : (), + 'type' :'csv', 'metavar' : '<name1:name2>', + 'help' : ('Colon-delimited sets of names that determine each' + ' other\'s naming style when the name regexes' + ' allow several styles.')} + ), + ('include-naming-hint', + {'default': False, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'help': 'Include a hint for the correct naming format with invalid-name'} + ), + ) + _create_naming_options() + + + def __init__(self, linter): + _BasicChecker.__init__(self, linter) + self._name_category = {} + self._name_group = {} def open(self): self.stats = self.linter.add_stats(badname_module=0, @@ -904,6 +903,9 @@ class NameChecker(_BasicChecker): badname_inlinevar=0, badname_argument=0, badname_class_attribute=0) + for group in self.config.name_group: + for name_type in group.split(':'): + self._name_group[name_type] = 'group_%s' % (group,) @check_messages('blacklisted-name', 'invalid-name') def visit_module(self, node): @@ -965,6 +967,14 @@ class NameChecker(_BasicChecker): else: self._recursive_check_names(arg.elts, node) + def _find_name_group(self, node_type): + return self._name_group.get(node_type, node_type) + + def _is_multi_naming_match(self, match): + return (match is not None and + match.lastgroup is not None and + match.lastgroup not in EXEMPT_NAME_CATEGORIES) + def _check_name(self, node_type, name, node): """check for a name using the type's regexp""" if is_inside_except(node): @@ -978,13 +988,21 @@ class NameChecker(_BasicChecker): self.add_message('blacklisted-name', node=node, args=name) return regexp = getattr(self.config, node_type + '_rgx') - if regexp.match(name) is None: - type_label = {'inlinedvar': 'inlined variable', - 'const': 'constant', - 'attr': 'attribute', - 'class_attribute': 'class attribute' - }.get(node_type, node_type) - self.add_message('invalid-name', node=node, args=(type_label, name)) + match = regexp.match(name) + + if self._is_multi_naming_match(match): + name_group = self._find_name_group(node_type) + if name_group not in self._name_category: + self._name_category[name_group] = match.lastgroup + elif self._name_category[name_group] != match.lastgroup: + match = None + + if match is None: + type_label = _NAME_TYPES[node_type][1] + hint = '' + if self.config.include_naming_hint: + hint = ' (hint: %s)' % (getattr(self.config, node_type + '_name_hint')) + self.add_message('invalid-name', node=node, args=(type_label, name, hint)) self.stats['badname_' + node_type] += 1 @@ -1041,15 +1059,17 @@ class DocStringChecker(_BasicChecker): isinstance(ancestor[node.name], astroid.Function): overridden = True break - if not overridden: - self._check_docstring(ftype, node) + self._check_docstring(ftype, node, + report_missing=not overridden) else: self._check_docstring(ftype, node) - def _check_docstring(self, node_type, node): + def _check_docstring(self, node_type, node, report_missing=True): """check the node has a non empty docstring""" docstring = node.doc if docstring is None: + if not report_missing: + return if node.body: lines = node.body[-1].lineno - node.body[0].lineno + 1 else: |