diff options
215 files changed, 2652 insertions, 1266 deletions
@@ -41,4 +41,6 @@ de2dd37ba146f7b37497c7d0794470040de6b66b pylint-debian-version-0.25.0-1 b79adc16cd58a2ed526bec172f4f006e24c9afc5 pylint-debian-version-0.27.0-1 36355f1fe1fda5cbb6f4684935426f7794927c22 pylint-version-0.28.0 3a3b3ec9ab057172d979ac1584aa645aa6aa8e99 pylint-debian-version-0.28.0-1 +e21be0620fa924da83b34ad26654609e79bfeb33 pylint-version-1.0.0 +1a1ac3d203cafea9a15665b1937ba66d9a5ac734 pylint-debian-version-1.0.0-1 a31544127be96da39185ceeef380f3f3cd1c1816 before-astroid diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt new file mode 100644 index 000000000..8e5d5b4e8 --- /dev/null +++ b/CONTRIBUTORS.txt @@ -0,0 +1,33 @@ +Contributors +------------ + +Order doesn't matter (not that much, at least ;) + +* Sylvain Thenault (Logilab): main author / maintainer + +* Torsten Marek (Google): maintainer, main GPyLint developper and (GooglePylint) + upstream integrator + +* Martin Pool (Google): warnings for anomalous backslashes, symbolic names for + messages (like 'unused'), etc + +* Alexandre Fayolle (Logilab): TkInter gui, documentation, debian support + +* Julien Cristau, Emile Anclin (Logilab): python 3 support + +* Sandro Tosi: Debian packaging + +* Claudiu Popa, Mads Kiilerich, Boris Feld: various patches + +* Brian van den Broek: windows installation documentation + +* Amaury Forgeot d'Arc: check names imported from a module exists in the module + +* Benjamin Niemann: allow block level enabling/disabling of messages + +* Nathaniel Manista: suspicious lambda checking + +* Wolfgang Grafen, Axel Muller, Fabio Zadrozny, Pierre Rouleau, + Maarten ter Huurne, Mirko Friedenhagen and all the Logilab's team (among others): + bug reports, feedback, feature requests... Many other people have contributed + by their feedback or even patches, if I've forgotten you, send me a note ! @@ -2,13 +2,112 @@ ChangeLog for Pylint ==================== -- - * bitbucket #6: put back documentation in source distribution - * fix spelling of max-branchs option, now max-branches + * Add check for the use of 'exec' function + + * New --msg-template option to control output, deprecating "msvc" and + "parseable" output formats as well as killing `--include-ids` and `--symbols` + options + + * Do not emit [fixme] for every line if the config value 'notes' + is empty, but [fixme] is enabled. + + * Emit warnings about lines exceeding the column limit when + those lines are inside multiline docstrings. + + * Do not double-check parameter names with the regex for parameters and + inline variables. + + * Added a new warning missing-final-newline (C0304) for files missing + the final newline. + + * Methods that are decorated as properties are now treated as attributes + for the purposes of name checking. + + * Names of derived instance class member are not checked any more. + + * Names in global statements are now checked against the regular + expression for constants. + + * For toplevel name assignment, the class name regex will be used if + pylint can detect that value on the right-hand side is a class + (like collections.namedtuple()). + + * Simplified invalid-name message + + * Added a new warning invalid-encoded-data (W0512) for files that + contain data that cannot be decoded with the specified or + default encoding. + + * New warning bad-open-mode (W1501) for calls to open (or file) that + specify invalid open modes (Original implementation by Sasha Issayev). + + * New warning old-style-class (C1001) for classes that do not have any + base class. + + * Add new name type 'class_attribute' for attributes defined + in class scope. By default, allow both const and variable names. + + * New warning trailing-whitespace (C0303) that warns about + trailing whitespace. + + * Added a new warning unpacking-in-except (W0712) about unpacking + exceptions in handlers, which is unsupported in Python 3. + + * Add a configuration option for missing-docstring to + optionally exempt short functions/methods/classes from + the check. + + * Add the type of the offending node to missing-docstring + and empty-docstring. + + * New utility classes for per-checker unittests in testutils.py + + * Do not warn about redefinitions of variables that match the + dummy regex. + + * Do not treat all variables starting with _ as dummy variables, + only _ itself. + + * Make the line-too-long warning configurable by adding a regex for lines + for with the length limit should not be enforced + + * Do not warn about a long line if a pylint disable + option brings it above the length limit + + * Do not flag names in nested with statements as undefined. + + * Added a new warning 'old-raise-syntax' for the deprecated syntax + raise Exception, args + + * Support for PEP 3102 and new missing-kwoa (E1125) message for missing + mandatory keyword argument (logilab.org's #107788) + + * Fix spelling of max-branchs option, now max-branches * Added a new base class and interface for checkers that work on the tokens rather than the syntax, and only tokenize the input file once. + * Follow astng renaming to astroid + + * bitbucket #37: check for unbalanced unpacking in assignments + + * bitbucket #25: fix incomplete-protocol false positive for read-only + containers like tuple + + * bitbucket #16: fix False positive E1003 on Python 3 for argument-less super() + + * bitbucket #6: put back documentation in source distribution + + * bitbucket #15: epylint shouldn't hang anymore when there is a large + output on pylint'stderr + + * bitbucket #7: fix epylint w/ python3 + + * bitbucket #3: remove string module from the default list of deprecated + modules + + 2013-04-25 -- 0.28.0 * bitbucket #1: fix "dictionary changed size during iteration" crash @@ -1,3 +1,3 @@ python-logilab-common (>= 0.19.0) -python-logilab-astng (>= 0.16.1) +python-astroid python-tk @@ -21,10 +21,10 @@ or read the archives at http://lists.python.org/pipermail/code-quality/ Install ------- -Pylint requires the astng (the later the better) and logilab-common (version >= -0.53) packages. +Pylint requires the astroid (the later the better; formerly known as +logilab-astng) and logilab-common (version >= 0.53) packages. -* https://bitbucket.org/logilab/astng +* https://bitbucket.org/logilab/astroid * http://www.logilab.org/projects/common From the source distribution, extract the tarball and run :: @@ -48,28 +48,3 @@ Pylint is shipped with following additional commands: * symilar: an independent similarities checker * epylint: Emacs and Flymake compatible Pylint * pylint-gui: a graphical interface - -Contributors ------------- - -order doesn't matter... - -* Sylvain Thenault: main author / maintainer -* Alexandre Fayolle: TkInter gui, documentation, debian support -* Emile Anclin: used to maintain, py3k support -* Mads Kiilerich: various patches -* Torsten Marek, various patches -* Boris Feld, various patches -* Brian van den Broek: windows installation documentation -* Amaury Forgeot d'Arc: patch to check names imported from a module - exists in the module -* Benjamin Niemann: patch to allow block level enabling/disabling of messages -* Nathaniel Manista: suspicious lambda checking -* Wolfgang Grafen, Axel Muller, Fabio Zadrozny, Pierre Rouleau, - Maarten ter Huurne, Mirko Friedenhagen (among others): - bug reports, feedback, feature requests... -* Martin Pool (Google): warnings for anomalous backslashes, symbolic names - for messages (like 'unused') -* All the Logilab's team: daily use, bug reports, feature requests -* Other people have contributed by their feedback, if I've forgotten - you, send me a note ! diff --git a/README.Python3 b/README.Python3 index ccc92972e..aee0f2b24 100644 --- a/README.Python3 +++ b/README.Python3 @@ -20,7 +20,7 @@ build step when invoked by the python3 interpreter:: In order to run pylint locally, you have to install the dependencies:: easy_install-3.2 logilab-common - easy_install-3.2 logilab-astng + easy_install-3.2 astroid Debian diff --git a/__pkginfo__.py b/__pkginfo__.py index a018f9343..997b9a59e 100644 --- a/__pkginfo__.py +++ b/__pkginfo__.py @@ -18,10 +18,10 @@ modname = distname = 'pylint' -numversion = (0, 28, 0) +numversion = (1, 0, 0) version = '.'.join([str(num) for num in numversion]) -install_requires = ['logilab-common >= 0.53.0', 'logilab-astng >= 0.24.3'] +install_requires = ['logilab-common >= 0.53.0', 'astroid >= 0.24.3'] license = 'GPL' description = "python code static checker" @@ -66,3 +66,4 @@ scripts = [join('bin', filename) for filename in ('pylint', 'pylint-gui', "symilar", "epylint", "pyreverse")] +include_dirs = ['test'] diff --git a/checkers/__init__.py b/checkers/__init__.py index dd868c655..27dc36459 100644 --- a/checkers/__init__.py +++ b/checkers/__init__.py @@ -38,16 +38,16 @@ messages nor reports. XXX not true, emit a 07 report ! """ +import sys import tokenize import warnings -from os import listdir -from os.path import dirname, join, isdir, splitext +from os.path import dirname -from logilab.astng.utils import ASTWalker -from logilab.common.modutils import load_module_from_file +from astroid.utils import ASTWalker from logilab.common.configuration import OptionsProviderMixIn -from pylint.reporters import diff_string, EmptyReport +from pylint.reporters import diff_string +from pylint.utils import register_plugins def table_lines_from_stats(stats, old_stats, columns): """get values listed in <columns> from <stats> and <old_stats>, @@ -126,8 +126,11 @@ class BaseRawChecker(BaseChecker): "use the ITokenChecker interface.", DeprecationWarning) stream = node.file_stream - stream.seek(0) # XXX may be removed with astng > 0.23 - self.process_tokens(tokenize.generate_tokens(stream.readline)) + stream.seek(0) # XXX may be removed with astroid > 0.23 + if sys.version_info <= (3, 0): + self.process_tokens(tokenize.generate_tokens(stream.readline)) + else: + self.process_tokens(tokenize.tokenize(stream.readline)) def process_tokens(self, tokens): """should be overridden by subclasses""" @@ -136,40 +139,14 @@ class BaseRawChecker(BaseChecker): class BaseTokenChecker(BaseChecker): """Base class for checkers that want to have access to the token stream.""" - + def process_tokens(self, tokens): """Should be overridden by subclasses.""" raise NotImplementedError() -PY_EXTS = ('.py', '.pyc', '.pyo', '.pyw', '.so', '.dll') - def initialize(linter): """initialize linter with checkers in this package """ - package_load(linter, __path__[0]) + register_plugins(linter, __path__[0]) -def package_load(linter, directory): - """load all module and package in the given directory, looking for a - 'register' function in each one, used to register pylint checkers - """ - imported = {} - for filename in listdir(directory): - basename, extension = splitext(filename) - if basename in imported or basename == '__pycache__': - continue - if extension in PY_EXTS and basename != '__init__' or ( - not extension and isdir(join(directory, basename))): - try: - module = load_module_from_file(join(directory, filename)) - except ValueError: - # empty module name (usually emacs auto-save files) - continue - except ImportError, exc: - import sys - print >> sys.stderr, "Problem importing module %s: %s" % (filename, exc) - else: - if hasattr(module, 'register'): - module.register(linter) - imported[basename] = 1 - -__all__ = ('BaseChecker', 'initialize', 'package_load') +__all__ = ('BaseChecker', 'initialize') diff --git a/checkers/base.py b/checkers/base.py index 92fc978e7..de40c4cb9 100644 --- a/checkers/base.py +++ b/checkers/base.py @@ -1,6 +1,7 @@ # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# Copyright (c) 2009-2010 Arista Networks, Inc. # http://www.logilab.fr/ -- mailto:contact@logilab.fr +# Copyright (c) 2009-2010 Arista Networks, Inc. +# # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later @@ -15,18 +16,22 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """basic checker for Python code""" - -from logilab import astng +import sys +import astroid from logilab.common.ureports import Table -from logilab.astng import are_exclusive +from astroid import are_exclusive +import astroid.bases -from pylint.interfaces import IASTNGChecker +from pylint.interfaces import IAstroidChecker +from pylint.utils import EmptyReport from pylint.reporters import diff_string -from pylint.checkers import BaseChecker, EmptyReport +from pylint.checkers import BaseChecker from pylint.checkers.utils import ( check_messages, clobber_in_except, + is_builtin_object, is_inside_except, + overrides_a_method, safe_infer, ) @@ -39,6 +44,7 @@ MOD_NAME_RGX = re.compile('(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$') CONST_NAME_RGX = re.compile('(([A-Z_][A-Z0-9_]*)|(__.*__))$') COMP_VAR_RGX = re.compile('[A-Za-z_][A-Za-z0-9_]*$') DEFAULT_NAME_RGX = re.compile('[a-z_][a-z0-9_]{2,30}$') +CLASS_ATTRIBUTE_RGX = re.compile(r'([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$') # do not require a doc string on system methods NO_REQUIRED_DOC_RGX = re.compile('__.*__') @@ -48,8 +54,8 @@ def in_loop(node): """return True if the node is inside a kind of for loop""" parent = node.parent while parent is not None: - if isinstance(parent, (astng.For, astng.ListComp, astng.SetComp, - astng.DictComp, astng.GenExpr)): + if isinstance(parent, (astroid.For, astroid.ListComp, astroid.SetComp, + astroid.DictComp, astroid.GenExpr)): return True parent = parent.parent return False @@ -68,17 +74,48 @@ def in_nested_list(nested_list, obj): def _loop_exits_early(loop): """Returns true if a loop has a break statement in its body.""" - loop_nodes = (astng.For, astng.While) + loop_nodes = (astroid.For, astroid.While) # Loop over body explicitly to avoid matching break statements # in orelse. for child in loop.body: if isinstance(child, loop_nodes): continue - for _ in child.nodes_of_class(astng.Break, skip_klass=loop_nodes): + for _ in child.nodes_of_class(astroid.Break, skip_klass=loop_nodes): return True return False + +def _determine_function_name_type(node): + """Determine the name type whose regex the a function's name should match. + + :param node: A function node. + :returns: One of ('function', 'method', 'attr') + """ + if not node.is_method(): + return 'function' + if node.decorators: + decorators = node.decorators.nodes + else: + decorators = [] + for decorator in decorators: + # If the function is a property (decorated with @property + # or @abc.abstractproperty), the name type is 'attr'. + if (isinstance(decorator, astroid.Name) or + (isinstance(decorator, astroid.Getattr) and + decorator.attrname == 'abstractproperty')): + infered = safe_infer(decorator) + if (infered and + infered.qname() in ('__builtin__.property', 'abc.abstractproperty')): + return 'attr' + # If the function is decorated using the prop_method.{setter,getter} + # form, treat it like an attribute as well. + elif (isinstance(decorator, astroid.Getattr) and + decorator.attrname in ('setter', 'deleter')): + return 'attr' + return 'method' + + def report_by_type_stats(sect, stats, old_stats): """make a report of @@ -130,13 +167,13 @@ def redefined_by_decorator(node): """ if node.decorators: for decorator in node.decorators.nodes: - if (isinstance(decorator, astng.Getattr) and + if (isinstance(decorator, astroid.Getattr) and getattr(decorator.expr, 'name', None) == node.name): return True return False class _BasicChecker(BaseChecker): - __implements__ = IASTNGChecker + __implements__ = IAstroidChecker name = 'basic' class BasicErrorChecker(_BasicChecker): @@ -187,79 +224,85 @@ class BasicErrorChecker(_BasicChecker): def __init__(self, linter): _BasicChecker.__init__(self, linter) - @check_messages('E0102') + @check_messages('function-redefined') def visit_class(self, node): self._check_redefinition('class', node) - @check_messages('E0100', 'E0101', 'E0102', 'E0106', 'E0108') + @check_messages('init-is-generator', 'return-in-init', + 'function-redefined', 'return-arg-in-generator', + 'duplicate-argument-name') def visit_function(self, node): if not redefined_by_decorator(node): self._check_redefinition(node.is_method() and 'method' or 'function', node) # checks for max returns, branch, return in __init__ - returns = node.nodes_of_class(astng.Return, - skip_klass=(astng.Function, astng.Class)) + returns = node.nodes_of_class(astroid.Return, + skip_klass=(astroid.Function, astroid.Class)) if node.is_method() and node.name == '__init__': if node.is_generator(): - self.add_message('E0100', node=node) + self.add_message('init-is-generator', node=node) else: values = [r.value for r in returns] - if [v for v in values if not (v is None or - (isinstance(v, astng.Const) and v.value is None) - or (isinstance(v, astng.Name) and v.name == 'None'))]: - self.add_message('E0101', node=node) + # Are we returning anything but None from constructors + if [v for v in values if + not (v is None or + (isinstance(v, astroid.Const) and v.value is None) or + (isinstance(v, astroid.Name) and v.name == 'None') + ) ]: + 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, astng.Const) and \ + if isinstance(retnode.value, astroid.Const) and \ retnode.value.value is not None: - self.add_message('E0106', node=node, + self.add_message('return-arg-in-generator', node=node, line=retnode.fromlineno) + # Check for duplicate names args = set() for name in node.argnames(): if name in args: - self.add_message('E0108', node=node, args=(name,)) + self.add_message('duplicate-argument-name', node=node, args=(name,)) else: args.add(name) - @check_messages('E0104') + @check_messages('return-outside-function') def visit_return(self, node): - if not isinstance(node.frame(), astng.Function): - self.add_message('E0104', node=node) + if not isinstance(node.frame(), astroid.Function): + self.add_message('return-outside-function', node=node) - @check_messages('E0105') + @check_messages('yield-outside-function') def visit_yield(self, node): - if not isinstance(node.frame(), (astng.Function, astng.Lambda)): - self.add_message('E0105', node=node) + if not isinstance(node.frame(), (astroid.Function, astroid.Lambda)): + self.add_message('yield-outside-function', node=node) - @check_messages('E0103') + @check_messages('not-in-loop') def visit_continue(self, node): self._check_in_loop(node, 'continue') - @check_messages('E0103') + @check_messages('not-in-loop') def visit_break(self, node): self._check_in_loop(node, 'break') - @check_messages('W0120') + @check_messages('useless-else-on-loop') def visit_for(self, node): self._check_else_on_loop(node) - @check_messages('W0120') + @check_messages('useless-else-on-loop') def visit_while(self, node): self._check_else_on_loop(node) - @check_messages('E0107') + @check_messages('nonexistent-operator') def visit_unaryop(self, node): - """check use of the non-existent ++ adn -- operator operator""" + """check use of the non-existent ++ and -- operator operator""" if ((node.op in '+-') and - isinstance(node.operand, astng.UnaryOp) and + isinstance(node.operand, astroid.UnaryOp) and (node.operand.op == node.op)): - self.add_message('E0107', node=node, args=node.op*2) + self.add_message('nonexistent-operator', node=node, args=node.op*2) def _check_else_on_loop(self, node): """Check that any loop with an else clause has a break statement.""" if node.orelse and not _loop_exits_early(node): - self.add_message('W0120', node=node, + self.add_message('useless-else-on-loop', node=node, # This is not optimal, but the line previous # to the first statement in the else clause # will usually be the one that contains the else:. @@ -269,17 +312,17 @@ class BasicErrorChecker(_BasicChecker): """check that a node is inside a for or while loop""" _node = node.parent while _node: - if isinstance(_node, (astng.For, astng.While)): + if isinstance(_node, (astroid.For, astroid.While)): break _node = _node.parent else: - self.add_message('E0103', node=node, args=node_name) + self.add_message('not-in-loop', node=node, args=node_name) def _check_redefinition(self, redeftype, node): """check for redefinition of a function / method / class name""" defined_self = node.parent.frame()[node.name] if defined_self is not node and not are_exclusive(node, defined_self): - self.add_message('E0102', node=node, + self.add_message('function-redefined', node=node, args=(redeftype, defined_self.fromlineno)) @@ -287,7 +330,6 @@ class BasicErrorChecker(_BasicChecker): class BasicChecker(_BasicChecker): """checks for : * doc strings - * modules / classes / functions / methods / arguments / variables name * number of arguments, local variables, branches, returns and statements in functions, methods * required module attributes @@ -296,7 +338,7 @@ functions, methods * uses of the global statement """ - __implements__ = IASTNGChecker + __implements__ = IAstroidChecker name = 'basic' msgs = { @@ -332,11 +374,10 @@ functions, methods 'duplicate-key', "Used when a dictionary expression binds the same key multiple \ times."), - 'W0122': ('Use of the exec statement', - 'exec-statement', - 'Used when you use the "exec" statement, to discourage its \ + 'W0122': ('Use of exec', + '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 !'), - 'W0141': ('Used builtin function %r', 'bad-builtin', 'Used when a black listed builtin function is used (see the ' @@ -359,11 +400,15 @@ functions, methods 'A call of assert on a tuple will always evaluate to true if ' 'the tuple is not empty, and will always evaluate to false if ' 'it is.'), + 'W0121': ('Use raise ErrorClass(args) instead of raise ErrorClass, args.', + 'old-raise-syntax', + "Used when the alternate raise syntax 'raise foo, bar' is used " + "instead of 'raise foo(bar)'.", + {'maxversion': (3, 0)}), 'C0121': ('Missing required attribute "%s"', # W0103 'missing-module-attribute', 'Used when an attribute required for modules is missing.'), - } options = (('required-attributes', @@ -392,14 +437,14 @@ functions, methods self._tryfinallys = [] self.stats = self.linter.add_stats(module=0, function=0, method=0, class_=0) - + @check_messages('missing-module-attribute') def visit_module(self, node): """check module name, docstring and required arguments """ self.stats['module'] += 1 for attr in self.config.required_attributes: if attr not in node: - self.add_message('C0121', node=node, args=attr) + self.add_message('missing-module-attribute', node=node, args=attr) def visit_class(self, node): """check module name, docstring and redefinition @@ -407,31 +452,32 @@ functions, methods """ self.stats['class'] += 1 - @check_messages('W0104', 'W0105') + @check_messages('pointless-statement', 'pointless-string-statement', + 'expression-not-assigned') def visit_discard(self, node): """check for various kind of statements without effect""" expr = node.value - if isinstance(expr, astng.Const) and isinstance(expr.value, + if isinstance(expr, astroid.Const) and isinstance(expr.value, basestring): # treat string statement in a separated message - self.add_message('W0105', node=node) + self.add_message('pointless-string-statement', node=node) return # ignore if this is : # * a direct function call # * the unique child of a try/except body # * a yield (which are wrapped by a discard node in _ast XXX) # warn W0106 if we have any underlying function call (we can't predict - # side effects), else W0104 - if (isinstance(expr, (astng.Yield, astng.CallFunc)) or - (isinstance(node.parent, astng.TryExcept) and + # side effects), else pointless-statement + if (isinstance(expr, (astroid.Yield, astroid.CallFunc)) or + (isinstance(node.parent, astroid.TryExcept) and node.parent.body == [node])): return - if any(expr.nodes_of_class(astng.CallFunc)): - self.add_message('W0106', node=node, args=expr.as_string()) + if any(expr.nodes_of_class(astroid.CallFunc)): + self.add_message('expression-not-assigned', node=node, args=expr.as_string()) else: - self.add_message('W0104', node=node) + self.add_message('pointless-statement', node=node) - @check_messages('W0108') + @check_messages('unnecessary-lambda') def visit_lambda(self, node): """check whether or not the lambda is suspicious """ @@ -446,11 +492,11 @@ functions, methods # of the lambda. return call = node.body - if not isinstance(call, astng.CallFunc): + if not isinstance(call, astroid.CallFunc): # The body of the lambda must be a function call expression # for the lambda to be unnecessary. return - # XXX are lambda still different with astng >= 0.18 ? + # XXX are lambda still different with astroid >= 0.18 ? # *args and **kwargs need to be treated specially, since they # are structured differently between the lambda and the function # call (in the lambda they appear in the args.args list and are @@ -460,14 +506,14 @@ functions, methods ordinary_args = list(node.args.args) if node.args.kwarg: if (not call.kwargs - or not isinstance(call.kwargs, astng.Name) + or not isinstance(call.kwargs, astroid.Name) or node.args.kwarg != call.kwargs.name): return elif call.kwargs: return if node.args.vararg: if (not call.starargs - or not isinstance(call.starargs, astng.Name) + or not isinstance(call.starargs, astroid.Name) or node.args.vararg != call.starargs.name): return elif call.starargs: @@ -477,12 +523,13 @@ functions, methods if len(ordinary_args) != len(call.args): return for i in xrange(len(ordinary_args)): - if not isinstance(call.args[i], astng.Name): + if not isinstance(call.args[i], astroid.Name): return if node.args.args[i].name != call.args[i].name: return - self.add_message('W0108', line=node.fromlineno, node=node) + self.add_message('unnecessary-lambda', line=node.fromlineno, node=node) + @check_messages('dangerous-default-value') def visit_function(self, node): """check function name, docstring, arguments, redefinition, variable names, max locals @@ -492,19 +539,20 @@ functions, methods for default in node.args.defaults: try: value = default.infer().next() - except astng.InferenceError: + except astroid.InferenceError: continue - if (isinstance(value, astng.Instance) and - value.qname() in ('__builtin__.set', '__builtin__.dict', '__builtin__.list')): + builtins = astroid.bases.BUILTINS + if (isinstance(value, astroid.Instance) and + value.qname() in ['.'.join([builtins, x]) for x in ('set', 'dict', 'list')]): if value is default: msg = default.as_string() - elif type(value) is astng.Instance: + elif type(value) is astroid.Instance: msg = '%s (%s)' % (default.as_string(), value.qname()) else: msg = '%s (%s)' % (default.as_string(), value.as_string()) - self.add_message('W0102', node=node, args=(msg,)) + self.add_message('dangerous-default-value', node=node, args=(msg,)) - @check_messages('W0101', 'W0150') + @check_messages('unreachable', 'lost-exception') def visit_return(self, node): """1 - check is the node has a right sibling (if so, that's some unreachable code) @@ -513,16 +561,16 @@ functions, methods """ self._check_unreachable(node) # Is it inside final body of a try...finally bloc ? - self._check_not_in_finally(node, 'return', (astng.Function,)) + self._check_not_in_finally(node, 'return', (astroid.Function,)) - @check_messages('W0101') + @check_messages('unreachable') def visit_continue(self, node): """check is the node has a right sibling (if so, that's some unreachable code) """ self._check_unreachable(node) - @check_messages('W0101', 'W0150') + @check_messages('unreachable', 'lost-exception') def visit_break(self, node): """1 - check is the node has a right sibling (if so, that's some unreachable code) @@ -532,36 +580,42 @@ functions, methods # 1 - Is it right sibling ? self._check_unreachable(node) # 2 - Is it inside final body of a try...finally bloc ? - self._check_not_in_finally(node, 'break', (astng.For, astng.While,)) + self._check_not_in_finally(node, 'break', (astroid.For, astroid.While,)) - @check_messages('W0101') + @check_messages('unreachable', 'old-raise-syntax') def visit_raise(self, node): - """check is the node has a right sibling (if so, that's some unreachable + """check if the node has a right sibling (if so, that's some unreachable code) """ self._check_unreachable(node) + if sys.version_info >= (3, 0): + return + if node.exc is not None and node.inst is not None and node.tback is None: + self.add_message('old-raise-syntax', node=node) - @check_messages('W0122') + @check_messages('exec-used') def visit_exec(self, node): """just print a warning on exec statements""" - self.add_message('W0122', node=node) + self.add_message('exec-used', node=node) - @check_messages('W0141', 'W0142') + @check_messages('bad-builtin', 'star-args', 'exec-used') def visit_callfunc(self, node): """visit a CallFunc node -> check if this is not a blacklisted builtin call and check for * or ** use """ - if isinstance(node.func, astng.Name): + if isinstance(node.func, astroid.Name): name = node.func.name # ignore the name if it's not a builtin (i.e. not defined in the # locals nor globals scope) if not (name in node.frame() or name in node.root()): + if name == 'exec': + self.add_message('exec-used', node=node) if name in self.config.bad_functions: - self.add_message('W0141', node=node, args=name) + self.add_message('bad-builtin', node=node, args=name) if node.starargs or node.kwargs: scope = node.scope() - if isinstance(scope, astng.Function): + if isinstance(scope, astroid.Function): toprocess = [(n, vn) for (n, vn) in ((node.starargs, scope.args.vararg), (node.kwargs, scope.args.kwarg)) if n] if toprocess: @@ -569,25 +623,25 @@ functions, methods if getattr(cfnode, 'name', None) == fargname: toprocess.remove((cfnode, fargname)) if not toprocess: - return # W0142 can be skipped - self.add_message('W0142', node=node.func) + return # star-args can be skipped + self.add_message('star-args', node=node.func) - @check_messages('W0199') + @check_messages('assert-on-tuple') def visit_assert(self, node): """check the use of an assert statement on a tuple.""" - if node.fail is None and isinstance(node.test, astng.Tuple) and \ + if node.fail is None and isinstance(node.test, astroid.Tuple) and \ len(node.test.elts) == 2: - self.add_message('W0199', node=node) + self.add_message('assert-on-tuple', node=node) - @check_messages('W0109') + @check_messages('duplicate-key') def visit_dict(self, node): """check duplicate key in dictionary""" keys = set() for k, _ in node.items: - if isinstance(k, astng.Const): + if isinstance(k, astroid.Const): key = k.value if key in keys: - self.add_message('W0109', node=node, args=key) + self.add_message('duplicate-key', node=node, args=key) keys.add(key) def visit_tryfinally(self, node): @@ -602,7 +656,7 @@ functions, methods """check unreachable code""" unreach_stmt = node.next_sibling() if unreach_stmt is not None: - self.add_message('W0101', node=unreach_stmt) + self.add_message('unreachable', node=unreach_stmt) def _check_not_in_finally(self, node, node_name, breaker_classes=()): """check that a node is not inside a finally clause of a @@ -617,25 +671,24 @@ functions, methods _node = node while _parent and not isinstance(_parent, breaker_classes): if hasattr(_parent, 'finalbody') and _node in _parent.finalbody: - self.add_message('W0150', node=node, args=node_name) + self.add_message('lost-exception', node=node, args=node_name) return _node = _parent _parent = _node.parent - 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 name "%s" for type %s (should match %s)', + 'C0103': ('Invalid %s name "%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>', @@ -683,6 +736,12 @@ class NameChecker(_BasicChecker): '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>', @@ -712,48 +771,65 @@ class NameChecker(_BasicChecker): badname_const=0, badname_variable=0, badname_inlinevar=0, - badname_argument=0) + badname_argument=0, + badname_class_attribute=0) - @check_messages('C0102', 'C0103') + @check_messages('blacklisted-name', 'invalid-name') def visit_module(self, node): self._check_name('module', node.name.split('.')[-1], node) - @check_messages('C0102', 'C0103') + @check_messages('blacklisted-name', 'invalid-name') def visit_class(self, node): self._check_name('class', node.name, node) for attr, anodes in node.instance_attrs.iteritems(): - self._check_name('attr', attr, anodes[0]) + if not list(node.instance_attr_ancestors(attr)): + self._check_name('attr', attr, anodes[0]) - @check_messages('C0102', 'C0103') + @check_messages('blacklisted-name', 'invalid-name') def visit_function(self, node): - self._check_name(node.is_method() and 'method' or 'function', + # Do not emit any warnings if the method is just an implementation + # of a base class method. + if node.is_method() and overrides_a_method(node.parent.frame(), node.name): + return + self._check_name(_determine_function_name_type(node), node.name, node) - # check arguments name + # Check argument names args = node.args.args if args is not None: self._recursive_check_names(args, node) - @check_messages('C0102', 'C0103') + @check_messages('blacklisted-name', 'invalid-name') + def visit_global(self, node): + for name in node.names: + self._check_name('const', name, node) + + @check_messages('blacklisted-name', 'invalid-name') def visit_assname(self, node): """check module level assigned names""" frame = node.frame() ass_type = node.ass_type() - if isinstance(ass_type, (astng.Comprehension, astng.Comprehension)): + if isinstance(ass_type, astroid.Comprehension): self._check_name('inlinevar', node.name, node) - elif isinstance(frame, astng.Module): - if isinstance(ass_type, astng.Assign) and not in_loop(ass_type): - self._check_name('const', node.name, node) - elif isinstance(ass_type, astng.ExceptHandler): + elif isinstance(frame, astroid.Module): + if isinstance(ass_type, astroid.Assign) and not in_loop(ass_type): + if isinstance(safe_infer(ass_type.value), astroid.Class): + self._check_name('class', node.name, node) + else: + self._check_name('const', node.name, node) + elif isinstance(ass_type, astroid.ExceptHandler): self._check_name('variable', node.name, node) - elif isinstance(frame, astng.Function): + elif isinstance(frame, astroid.Function): # global introduced variable aren't in the function locals - if node.name in frame: + if node.name in frame and node.name not in frame.argnames(): self._check_name('variable', node.name, node) + elif isinstance(frame, astroid.Class): + if not list(frame.local_attr_ancestors(node.name)): + self._check_name('class_attribute', node.name, node) def _recursive_check_names(self, args, node): """check names in a possibly recursive list <arg>""" for arg in args: - if isinstance(arg, astng.AssName): + if isinstance(arg, astroid.AssName): self._check_name('argument', arg.name, node) else: self._recursive_check_names(arg.elts, node) @@ -768,26 +844,27 @@ class NameChecker(_BasicChecker): return if name in self.config.bad_names: self.stats['badname_' + node_type] += 1 - self.add_message('C0102', node=node, args=name) + 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('C0103', node=node, args=(name, type_label, regexp.pattern)) + self.add_message('invalid-name', node=node, args=(type_label, name)) self.stats['badname_' + node_type] += 1 class DocStringChecker(_BasicChecker): msgs = { - 'C0111': ('Missing docstring', # W0131 + 'C0111': ('Missing %s docstring', # W0131 'missing-docstring', 'Used when a module, function, class or method has no docstring.\ Some special methods like __init__ doesn\'t necessary require a \ docstring.'), - 'C0112': ('Empty docstring', # W0132 + 'C0112': ('Empty %s docstring', # W0132 'empty-docstring', 'Used when a module, function, class or method has an empty \ docstring (it would be too easy ;).'), @@ -796,33 +873,41 @@ class DocStringChecker(_BasicChecker): {'default' : NO_REQUIRED_DOC_RGX, 'type' : 'regexp', 'metavar' : '<regexp>', 'help' : 'Regular expression which should only match ' - 'functions or classes name which do not require a ' - 'docstring'} + 'function or class names that do not require a ' + 'docstring.'} + ), + ('docstring-min-length', + {'default' : -1, + 'type' : 'int', 'metavar' : '<int>', + 'help': ('Minimum line length for functions/classes that' + ' require docstrings, shorter ones are exempt.')} ), ) + def open(self): self.stats = self.linter.add_stats(undocumented_module=0, undocumented_function=0, undocumented_method=0, undocumented_class=0) - + @check_messages('missing-docstring', 'empty-docstring') def visit_module(self, node): self._check_docstring('module', node) + @check_messages('missing-docstring', 'empty-docstring') def visit_class(self, node): if self.config.no_docstring_rgx.match(node.name) is None: self._check_docstring('class', node) - + @check_messages('missing-docstring', 'empty-docstring') def visit_function(self, node): if self.config.no_docstring_rgx.match(node.name) is None: ftype = node.is_method() and 'method' or 'function' - if isinstance(node.parent.frame(), astng.Class): + if isinstance(node.parent.frame(), astroid.Class): overridden = False # check if node is from a method overridden by its ancestor for ancestor in node.parent.frame().ancestors(): if node.name in ancestor and \ - isinstance(ancestor[node.name], astng.Function): + isinstance(ancestor[node.name], astroid.Function): overridden = True break if not overridden: @@ -834,24 +919,32 @@ class DocStringChecker(_BasicChecker): """check the node has a non empty docstring""" docstring = node.doc if docstring is None: + if node.body: + lines = node.body[-1].lineno - node.body[0].lineno + 1 + else: + lines = 0 + max_lines = self.config.docstring_min_length + + if node_type != 'module' and max_lines > -1 and lines < max_lines: + return self.stats['undocumented_'+node_type] += 1 - self.add_message('C0111', node=node) + self.add_message('missing-docstring', node=node, args=(node_type,)) elif not docstring.strip(): self.stats['undocumented_'+node_type] += 1 - self.add_message('C0112', node=node) + self.add_message('empty-docstring', node=node, args=(node_type,)) class PassChecker(_BasicChecker): - """check is the pass statement is really necessary""" + """check if the pass statement is really necessary""" msgs = {'W0107': ('Unnecessary pass statement', 'unnecessary-pass', 'Used when a "pass" statement that can be avoided is ' 'encountered.'), } - + @check_messages('unnecessary-pass') def visit_pass(self, node): if len(node.parent.child_sequence(node)) > 1: - self.add_message('W0107', node=node) + self.add_message('unnecessary-pass', node=node) class LambdaForComprehensionChecker(_BasicChecker): @@ -865,23 +958,23 @@ class LambdaForComprehensionChecker(_BasicChecker): 'deprecated-lambda', 'Used when a lambda is the first argument to "map" or ' '"filter". It could be clearer as a list ' - 'comprehension or generator expression.'), + 'comprehension or generator expression.', + {'maxversion': (3, 0)}), } - @check_messages('W0110') + @check_messages('deprecated-lambda') def visit_callfunc(self, node): """visit a CallFunc node, check if map or filter are called with a lambda """ if not node.args: return - if not isinstance(node.args[0], astng.Lambda): + if not isinstance(node.args[0], astroid.Lambda): return infered = safe_infer(node.func) - if (infered - and infered.parent.name == '__builtin__' + if (is_builtin_object(infered) and infered.name in ['map', 'filter']): - self.add_message('W0110', node=node) + self.add_message('deprecated-lambda', node=node) def register(linter): diff --git a/checkers/classes.py b/checkers/classes.py index f2c2913a0..98a2f27df 100644 --- a/checkers/classes.py +++ b/checkers/classes.py @@ -17,10 +17,10 @@ """ from __future__ import generators -from logilab import astng -from logilab.astng import YES, Instance, are_exclusive, AssAttr +import astroid +from astroid import YES, Instance, are_exclusive, AssAttr -from pylint.interfaces import IASTNGChecker +from pylint.interfaces import IAstroidChecker from pylint.checkers import BaseChecker from pylint.checkers.utils import (PYMETHODS, overrides_a_method, check_messages, is_attr_private, is_attr_protected, node_frame_class) @@ -156,7 +156,7 @@ class ClassChecker(BaseChecker): * unreachable code """ - __implements__ = (IASTNGChecker,) + __implements__ = (IAstroidChecker,) # configuration section name name = 'classes' @@ -222,7 +222,7 @@ a metaclass class method.'} if node.type == 'class': try: node.local_attr('__init__') - except astng.NotFoundError: + except astroid.NotFoundError: self.add_message('W0232', args=node, node=node) @check_messages('E0203', 'W0201') @@ -241,7 +241,7 @@ a metaclass class method.'} defining_methods = self.config.defining_attr_methods for attr, nodes in cnode.instance_attrs.iteritems(): nodes = [n for n in nodes if not - isinstance(n.statement(), (astng.Delete, astng.AugAssign))] + isinstance(n.statement(), (astroid.Delete, astroid.AugAssign))] if not nodes: continue # error detected by typechecking attr_defined = False @@ -264,7 +264,7 @@ a metaclass class method.'} # check attribute is defined as a class attribute try: cnode.local_attr(attr) - except astng.NotFoundError: + except astroid.NotFoundError: self.add_message('W0201', args=attr, node=node) def visit_function(self, node): @@ -281,25 +281,25 @@ a metaclass class method.'} return # check signature if the method overloads inherited method for overridden in klass.local_attr_ancestors(node.name): - # get astng for the searched method + # get astroid for the searched method try: meth_node = overridden[node.name] except KeyError: # we have found the method but it's not in the local # dictionary. - # This may happen with astng build from living objects + # This may happen with astroid build from living objects continue - if not isinstance(meth_node, astng.Function): + if not isinstance(meth_node, astroid.Function): continue self._check_signature(node, meth_node, 'overridden') break if node.decorators: for decorator in node.decorators.nodes: - if isinstance(decorator, astng.Getattr) and \ + if isinstance(decorator, astroid.Getattr) and \ decorator.attrname in ('getter', 'setter', 'deleter'): # attribute affectation will call this method, not hiding it return - if isinstance(decorator, astng.Name) and decorator.name == 'property': + if isinstance(decorator, astroid.Name) and decorator.name == 'property': # attribute affectation will either call a setter or raise # an attribute error, anyway not hiding the function return @@ -308,7 +308,7 @@ a metaclass class method.'} overridden = klass.instance_attr(node.name)[0] # XXX args = (overridden.root().name, overridden.fromlineno) self.add_message('E0202', args=args, node=node) - except astng.NotFoundError: + except astroid.NotFoundError: pass def leave_function(self, node): @@ -387,8 +387,8 @@ a metaclass class method.'} return # If the expression begins with a call to super, that's ok. - if isinstance(node.expr, astng.CallFunc) and \ - isinstance(node.expr.func, astng.Name) and \ + if isinstance(node.expr, astroid.CallFunc) and \ + isinstance(node.expr.func, astroid.Name) and \ node.expr.func.name == 'super': return @@ -416,7 +416,7 @@ a metaclass class method.'} node.local_attr(attr) # yes, stop here continue - except astng.NotFoundError: + except astroid.NotFoundError: pass # is it an instance attribute of a parent class ? try: @@ -428,7 +428,7 @@ a metaclass class method.'} # is it an instance attribute ? try: defstmts = node.instance_attr(attr) - except astng.NotFoundError: + except astroid.NotFoundError: pass else: if len(defstmts) == 1: @@ -530,7 +530,7 @@ a metaclass class method.'} e0221_hack = [False] def iface_handler(obj): """filter interface objects, it should be classes""" - if not isinstance(obj, astng.Class): + if not isinstance(obj, astroid.Class): e0221_hack[0] = True self.add_message('E0221', node=node, args=(obj.as_string(),)) @@ -545,10 +545,10 @@ a metaclass class method.'} # don't check method beginning with an underscore, # usually belonging to the interface implementation continue - # get class method astng + # get class method astroid try: method = node_method(node, name) - except astng.NotFoundError: + except astroid.NotFoundError: self.add_message('E0222', args=(name, iface.name), node=node) continue @@ -558,12 +558,12 @@ a metaclass class method.'} # check signature self._check_signature(method, imethod, '%s interface' % iface.name) - except astng.InferenceError: + except astroid.InferenceError: if e0221_hack[0]: return implements = Instance(node).getattr('__implements__')[0] assignment = implements.parent - assert isinstance(assignment, astng.Assign) + assert isinstance(assignment, astroid.Assign) # assignment.expr can be a Name or a Tuple or whatever. # Use as_string() for the message # FIXME: in case of multiple interfaces, find which one could not @@ -580,14 +580,14 @@ a metaclass class method.'} klass_node = node.parent.frame() to_call = _ancestors_to_call(klass_node) not_called_yet = dict(to_call) - for stmt in node.nodes_of_class(astng.CallFunc): + for stmt in node.nodes_of_class(astroid.CallFunc): expr = stmt.func - if not isinstance(expr, astng.Getattr) \ + if not isinstance(expr, astroid.Getattr) \ or expr.attrname != '__init__': continue # skip the test if using super - if isinstance(expr.expr, astng.CallFunc) and \ - isinstance(expr.expr.func, astng.Name) and \ + if isinstance(expr.expr, astroid.CallFunc) and \ + isinstance(expr.expr.func, astroid.Name) and \ expr.expr.func.name == 'super': return try: @@ -599,7 +599,7 @@ a metaclass class method.'} except KeyError: if klass not in to_call: self.add_message('W0233', node=expr, args=klass.name) - except astng.InferenceError: + except astroid.InferenceError: continue for klass, method in not_called_yet.iteritems(): if klass.name == 'object' or method.parent.name == 'object': @@ -611,8 +611,8 @@ a metaclass class method.'} class_type is in 'class', 'interface' """ - if not (isinstance(method1, astng.Function) - and isinstance(refmethod, astng.Function)): + if not (isinstance(method1, astroid.Function) + and isinstance(refmethod, astroid.Function)): self.add_message('F0202', args=(method1, refmethod), node=method1) return # don't care about functions with unknown argument (builtins) @@ -632,7 +632,7 @@ a metaclass class method.'} """Check that attribute lookup name use first attribute variable name (self for method, cls for classmethod and mcs for metaclass). """ - return self._first_attrs and isinstance(node.expr, astng.Name) and \ + return self._first_attrs and isinstance(node.expr, astroid.Name) and \ node.expr.name == self._first_attrs[-1] def _ancestors_to_call(klass_node, method='__init__'): @@ -643,19 +643,19 @@ def _ancestors_to_call(klass_node, method='__init__'): for base_node in klass_node.ancestors(recurs=False): try: to_call[base_node] = base_node.igetattr(method).next() - except astng.InferenceError: + except astroid.InferenceError: continue return to_call def node_method(node, method_name): - """get astng for <method_name> on the given class node, ensuring it + """get astroid for <method_name> on the given class node, ensuring it is a Function node """ for n in node.local_attr(method_name): - if isinstance(n, astng.Function): + if isinstance(n, astroid.Function): return n - raise astng.NotFoundError(method_name) + raise astroid.NotFoundError(method_name) def register(linter): """required method to auto register this checker """ diff --git a/checkers/design_analysis.py b/checkers/design_analysis.py index 2fabfa6e9..f3b58821e 100644 --- a/checkers/design_analysis.py +++ b/checkers/design_analysis.py @@ -15,10 +15,11 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """check for signs of poor design""" -from logilab.astng import Function, If, InferenceError +from astroid import Function, If, InferenceError -from pylint.interfaces import IASTNGChecker +from pylint.interfaces import IAstroidChecker from pylint.checkers import BaseChecker +from pylint.checkers.utils import check_messages import re @@ -28,10 +29,9 @@ IGNORED_ARGUMENT_NAMES = re.compile('_.*') SPECIAL_METHODS = [('Context manager', set(('__enter__', '__exit__',))), ('Container', set(('__len__', - '__getitem__', - '__setitem__', - '__delitem__',))), - ('Callable', set(('__call__',))), + '__getitem__',))), + ('Mutable container', set(('__setitem__', + '__delitem__',))), ] class SpecialMethodChecker(object): @@ -55,8 +55,8 @@ class SpecialMethodChecker(object): required_methods_found = methods_required & self.methods_found if required_methods_found == methods_required: return True - if required_methods_found != set(): - required_methods_missing = methods_required - self.methods_found + if required_methods_found: + required_methods_missing = methods_required - self.methods_found self.on_error((protocol, ', '.join(sorted(required_methods_found)), ', '.join(sorted(required_methods_missing)))) @@ -78,11 +78,11 @@ MSGS = { 'R0901': ('Too many ancestors (%s/%s)', 'too-many-ancestors', 'Used when class has too many parent classes, try to reduce \ - this to get a more simple (and so easier to use) class.'), + this to get a simpler (and so easier to use) class.'), 'R0902': ('Too many instance attributes (%s/%s)', 'too-many-instance-attributes', 'Used when class has too many instance attributes, try to reduce \ - this to get a more simple (and so easier to use) class.'), + this to get a simpler (and so easier to use) class.'), 'R0903': ('Too few public methods (%s/%s)', 'too-few-public-methods', 'Used when class has too few public methods, so be sure it\'s \ @@ -90,7 +90,7 @@ MSGS = { 'R0904': ('Too many public methods (%s/%s)', 'too-many-public-methods', 'Used when class has too many public methods, try to reduce \ - this to get a more simple (and so easier to use) class.'), + this to get a simpler (and so easier to use) class.'), 'R0911': ('Too many return statements (%s/%s)', 'too-many-return-statements', @@ -134,7 +134,7 @@ class MisdesignChecker(BaseChecker): * size, complexity of functions, methods """ - __implements__ = (IASTNGChecker,) + __implements__ = (IAstroidChecker,) # configuration section name name = 'design' @@ -220,6 +220,7 @@ class MisdesignChecker(BaseChecker): self._abstracts = [] self._ifaces = [] + # Check 'R0921', 'R0922', 'R0923' def close(self): """check that abstract/interface classes are used""" for abstract in self._abstracts: @@ -232,6 +233,7 @@ class MisdesignChecker(BaseChecker): if not iface in self._used_ifaces: self.add_message('R0923', node=iface) + @check_messages('R0901', 'R0902', 'R0903', 'R0904', 'R0921', 'R0922', 'R0923') def visit_class(self, node): """check size of inheritance hierarchy and number of instance attributes """ @@ -269,6 +271,7 @@ class MisdesignChecker(BaseChecker): except KeyError: self._used_abstracts[parent] = 1 + @check_messages('R0901', 'R0902', 'R0903', 'R0904', 'R0921', 'R0922', 'R0923') def leave_class(self, node): """check number of public methods""" nb_public_methods = 0 @@ -299,6 +302,7 @@ class MisdesignChecker(BaseChecker): args=(nb_public_methods, self.config.min_public_methods)) + @check_messages('R0911', 'R0912', 'R0913', 'R0914', 'R0915') def visit_function(self, node): """check function name, docstring, arguments, redefinition, variable names, max locals @@ -327,6 +331,7 @@ class MisdesignChecker(BaseChecker): # init statements counter self._stmts = 1 + @check_messages('R0911', 'R0912', 'R0913', 'R0914', 'R0915') def leave_function(self, node): """most of the work is done here on close: checks for max returns, branch, return in __init__ diff --git a/checkers/exceptions.py b/checkers/exceptions.py index db0ebcc1b..eb82d0dff 100644 --- a/checkers/exceptions.py +++ b/checkers/exceptions.py @@ -1,4 +1,4 @@ -# Copyright (c) 2003-2007 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software @@ -18,12 +18,12 @@ import sys from logilab.common.compat import builtins BUILTINS_NAME = builtins.__name__ -from logilab import astng -from logilab.astng import YES, Instance, unpack_infer +import astroid +from astroid import YES, Instance, unpack_infer from pylint.checkers import BaseChecker -from pylint.checkers.utils import is_empty, is_raising -from pylint.interfaces import IASTNGChecker +from pylint.checkers.utils import is_empty, is_raising, check_messages +from pylint.interfaces import IAstroidChecker OVERGENERAL_EXCEPTIONS = ('Exception',) @@ -46,7 +46,7 @@ MSGS = { 'notimplemented-raised', 'Used when NotImplemented is raised instead of \ NotImplementedError'), - + 'W0701': ('Raising a string exception', 'raising-string', 'Used when a string exception is raised.'), @@ -71,6 +71,12 @@ MSGS = { 'Used when the exception to catch is of the form \ "except A or B:". If intending to catch multiple, \ rewrite as "except (A, B):"'), + 'W0712': ('Implicit unpacking of exceptions is not supported in Python 3', + 'unpacking-in-except', + 'Python3 will not allow implicit unpacking of exceptions in except ' + 'clauses. ' + 'See http://www.python.org/dev/peps/pep-3110/', + {'maxversion': (3, 0)}), } @@ -80,12 +86,12 @@ else: EXCEPTIONS_MODULE = "builtins" class ExceptionsChecker(BaseChecker): - """checks for - * excepts without exception filter + """checks for + * excepts without exception filter * type of raise argument : string, Exceptions, other values """ - - __implements__ = IASTNGChecker + + __implements__ = IAstroidChecker name = 'exceptions' msgs = MSGS @@ -99,6 +105,7 @@ class ExceptionsChecker(BaseChecker): ), ) + @check_messages('W0701', 'W0710', 'E0702', 'E0710', 'E0711') def visit_raise(self, node): """visit raise possibly inferring value""" # ignore empty raise @@ -110,7 +117,7 @@ class ExceptionsChecker(BaseChecker): else: try: value = unpack_infer(expr).next() - except astng.InferenceError: + except astroid.InferenceError: return self._check_raise_value(node, value) @@ -118,29 +125,29 @@ class ExceptionsChecker(BaseChecker): """check for bad values, string exception and class inheritance """ value_found = True - if isinstance(expr, astng.Const): + if isinstance(expr, astroid.Const): value = expr.value if isinstance(value, str): self.add_message('W0701', node=node) else: self.add_message('E0702', node=node, args=value.__class__.__name__) - elif (isinstance(expr, astng.Name) and \ + elif (isinstance(expr, astroid.Name) and \ expr.name in ('None', 'True', 'False')) or \ - isinstance(expr, (astng.List, astng.Dict, astng.Tuple, - astng.Module, astng.Function)): + isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, + astroid.Module, astroid.Function)): self.add_message('E0702', node=node, args=expr.name) - elif ( (isinstance(expr, astng.Name) and expr.name == 'NotImplemented') - or (isinstance(expr, astng.CallFunc) and - isinstance(expr.func, astng.Name) and + elif ( (isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') + or (isinstance(expr, astroid.CallFunc) and + isinstance(expr.func, astroid.Name) and expr.func.name == 'NotImplemented') ): self.add_message('E0711', node=node) - elif isinstance(expr, astng.BinOp) and expr.op == '%': + elif isinstance(expr, astroid.BinOp) and expr.op == '%': self.add_message('W0701', node=node) - elif isinstance(expr, (Instance, astng.Class)): + elif isinstance(expr, (Instance, astroid.Class)): if isinstance(expr, Instance): expr = expr._proxied - if (isinstance(expr, astng.Class) and + if (isinstance(expr, astroid.Class) and not inherit_from_std_ex(expr) and expr.root().name != BUILTINS_NAME): if expr.newstyle: @@ -154,6 +161,12 @@ class ExceptionsChecker(BaseChecker): return value_found + @check_messages('W0712') + def visit_excepthandler(self, node): + """Visit an except handler block and check for exception unpacking.""" + if isinstance(node.name, (astroid.Tuple, astroid.List)): + self.add_message('W0712', node=node) + @check_messages('W0702', 'W0703', 'W0704', 'W0711', 'E0701') def visit_tryexcept(self, node): """check for empty except""" exceptions_classes = [] @@ -171,19 +184,19 @@ class ExceptionsChecker(BaseChecker): msg = 'empty except clause should always appear last' self.add_message('E0701', node=node, args=msg) - elif isinstance(handler.type, astng.BoolOp): + elif isinstance(handler.type, astroid.BoolOp): self.add_message('W0711', node=handler, args=handler.type.op) else: try: excs = list(unpack_infer(handler.type)) - except astng.InferenceError: + except astroid.InferenceError: continue for exc in excs: - # XXX skip other non class nodes - if exc is YES or not isinstance(exc, astng.Class): + # XXX skip other non class nodes + if exc is YES or not isinstance(exc, astroid.Class): continue exc_ancestors = [anc for anc in exc.ancestors() - if isinstance(anc, astng.Class)] + if isinstance(anc, astroid.Class)] for previous_exc in exceptions_classes: if previous_exc in exc_ancestors: msg = '%s is an ancestor class of %s' % ( diff --git a/checkers/format.py b/checkers/format.py index ea8cf1756..69ed2e5e1 100644 --- a/checkers/format.py +++ b/checkers/format.py @@ -27,12 +27,12 @@ if not hasattr(tokenize, 'NL'): raise ValueError("tokenize.NL doesn't exist -- tokenize module too old") from logilab.common.textutils import pretty_match -from logilab.astng import nodes +from astroid import nodes -from pylint.interfaces import ITokenChecker, IASTNGChecker +from pylint.interfaces import ITokenChecker, IAstroidChecker from pylint.checkers import BaseTokenChecker from pylint.checkers.utils import check_messages -from pylint.utils import WarningScope +from pylint.utils import WarningScope, OPTION_RGX MSGS = { 'C0301': ('Line too long (%s/%s)', @@ -42,7 +42,13 @@ MSGS = { 'too-many-lines', 'Used when a module has too much lines, reducing its readability.' ), - + 'C0303': ('Trailing whitespace', + 'trailing-whitespace', + 'Used when there is whitespace between the end of a line and the ' + 'newline.'), + 'C0304': ('Final newline missing', + 'missing-final-newline', + 'Used when the last line in a file is missing a newline.'), 'W0311': ('Bad indentation. Found %s %s, expected %s', 'bad-indentation', 'Used when an unexpected number of indentation\'s tabulations or ' @@ -171,7 +177,7 @@ class FormatChecker(BaseTokenChecker): * use of <> instead of != """ - __implements__ = (ITokenChecker, IASTNGChecker) + __implements__ = (ITokenChecker, IAstroidChecker) # configuration section name name = 'format' @@ -182,6 +188,11 @@ class FormatChecker(BaseTokenChecker): options = (('max-line-length', {'default' : 80, 'type' : "int", 'metavar' : '<int>', 'help' : 'Maximum number of characters on a single line.'}), + ('ignore-long-lines', + {'type': 'regexp', 'metavar': '<regexp>', + 'default': r'^\s*(# )?<?https?://\S+>?$', + 'help': ('Regexp for a line that is allowed to be longer than ' + 'the limit.')}), ('max-module-lines', {'default' : 1000, 'type' : 'int', 'metavar' : '<int>', 'help': 'Maximum number of lines in a module'} @@ -221,13 +232,24 @@ class FormatChecker(BaseTokenChecker): previous = None self._lines = {} self._visited_lines = {} + new_line_delay = False for (tok_type, token, start, _, line) in tokens: + if new_line_delay: + new_line_delay = False + self.new_line(tok_type, line, line_num, junk) if start[0] != line_num: if previous is not None and previous[0] == tokenize.OP and previous[1] == ';': self.add_message('W0301', line=previous[2]) previous = None line_num = start[0] - self.new_line(tok_type, line, line_num, junk) + # A tokenizer oddity: if an indented line contains a multi-line + # docstring, the line member of the INDENT token does not contain + # the full line; therefore we delay checking the new line until + # the next token. + if tok_type == tokenize.INDENT: + new_line_delay = True + else: + self.new_line(tok_type, line, line_num, junk) if tok_type not in (indent, dedent, newline) + junk: previous = tok_type, token, start[0] @@ -326,8 +348,22 @@ class FormatChecker(BaseTokenChecker): """check lines have less than a maximum number of characters """ max_chars = self.config.max_line_length - for line in lines.splitlines(): - if len(line) > max_chars: + ignore_long_line = self.config.ignore_long_lines + + for line in lines.splitlines(True): + if not line.endswith('\n'): + self.add_message('C0304', line=i) + else: + stripped_line = line.rstrip() + if line != stripped_line + '\n': + self.add_message('C0303', line=i) + # Don't count excess whitespace in the line length. + line = stripped_line + mobj = OPTION_RGX.search(line) + if mobj and mobj.group(1).split('=', 1)[0].strip() == 'disable': + line = line.split('#')[0].rstrip() + + if len(line) > max_chars and not ignore_long_line.search(line): self.add_message('C0301', line=i, args=(len(line), max_chars)) i += 1 diff --git a/checkers/imports.py b/checkers/imports.py index fe204b1d4..1dd778795 100644 --- a/checkers/imports.py +++ b/checkers/imports.py @@ -1,4 +1,4 @@ -# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -19,11 +19,13 @@ from logilab.common.graph import get_cycles, DotBackend from logilab.common.modutils import is_standard_module from logilab.common.ureports import VerbatimText, Paragraph -from logilab import astng -from logilab.astng import are_exclusive +import astroid +from astroid import are_exclusive -from pylint.interfaces import IASTNGChecker -from pylint.checkers import BaseChecker, EmptyReport +from pylint.interfaces import IAstroidChecker +from pylint.utils import EmptyReport +from pylint.checkers import BaseChecker +from pylint.checkers.utils import check_messages def get_first_import(node, context, name, base, level): @@ -38,11 +40,11 @@ def get_first_import(node, context, name, base, level): continue if first.scope() is node.scope() and first.fromlineno > node.fromlineno: continue - if isinstance(first, astng.Import): + if isinstance(first, astroid.Import): if any(fullname == iname[0] for iname in first.names): found = True break - elif isinstance(first, astng.From): + elif isinstance(first, astroid.From): if level == first.level and any( fullname == '%s.%s' % (first.modname, iname[0]) for iname in first.names): found = True @@ -157,15 +159,14 @@ class ImportsChecker(BaseChecker): * uses of deprecated modules """ - __implements__ = IASTNGChecker + __implements__ = IAstroidChecker name = 'imports' msgs = MSGS priority = -2 options = (('deprecated-modules', - {'default' : ('regsub', 'string', 'TERMIOS', - 'Bastion', 'rexec'), + {'default' : ('regsub', 'TERMIOS', 'Bastion', 'rexec'), 'type' : 'csv', 'metavar' : '<modules>', 'help' : 'Deprecated modules which should not be used, \ @@ -232,7 +233,9 @@ given file (report RP0402 must not be disabled)'} self._check_deprecated_module(node, name) self._check_reimport(node, name) - + # TODO This appears to be the list of all messages of the checker... + # @check_messages('W0410', 'W0401', 'W0403', 'W0402', 'W0404', 'W0406', 'F0401') + @check_messages(*(MSGS.keys())) def visit_from(self, node): """triggered when a from statement is seen""" basename = node.modname @@ -241,7 +244,7 @@ given file (report RP0402 must not be disabled)'} prev = node.previous_sibling() if prev: # consecutive future statements are possible - if not (isinstance(prev, astng.From) + if not (isinstance(prev, astroid.From) and prev.modname == '__future__'): self.add_message('W0410', node=node) return @@ -261,7 +264,7 @@ given file (report RP0402 must not be disabled)'} def get_imported_module(self, modnode, importnode, modname): try: return importnode.do_import_module(modname) - except astng.InferenceError, ex: + except astroid.InferenceError, ex: if str(ex) != modname: args = '%r (%s)' % (modname, ex) else: diff --git a/checkers/logging.py b/checkers/logging.py index 5e36226a0..6986ca4eb 100644 --- a/checkers/logging.py +++ b/checkers/logging.py @@ -14,11 +14,11 @@ """checker for use of Python logging """ -from logilab import astng +import astroid from pylint import checkers from pylint import interfaces from pylint.checkers import utils - +from pylint.checkers.utils import check_messages MSGS = { 'W1201': ('Specify string format arguments as logging function parameters', @@ -57,7 +57,7 @@ CHECKED_CONVENIENCE_FUNCTIONS = set([ class LoggingChecker(checkers.BaseChecker): """Checks use of the logging module.""" - __implements__ = interfaces.IASTNGChecker + __implements__ = interfaces.IAstroidChecker name = 'logging' msgs = MSGS @@ -77,18 +77,19 @@ class LoggingChecker(checkers.BaseChecker): else: self._logging_name = 'logging' + @check_messages(*(MSGS.keys())) def visit_callfunc(self, node): """Checks calls to (simple forms of) logging methods.""" - if (not isinstance(node.func, astng.Getattr) - or not isinstance(node.func.expr, astng.Name)): + if (not isinstance(node.func, astroid.Getattr) + or not isinstance(node.func.expr, astroid.Name)): return try: logger_class = [inferred for inferred in node.func.expr.infer() if ( - isinstance(inferred, astng.Instance) + isinstance(inferred, astroid.Instance) and any(ancestor for ancestor in inferred._proxied.ancestors() if ( ancestor.name == 'Logger' and ancestor.parent.name == 'logging')))] - except astng.exceptions.InferenceError: + except astroid.exceptions.InferenceError: return if (node.func.expr.name != self._logging_name and not logger_class): return @@ -103,9 +104,9 @@ class LoggingChecker(checkers.BaseChecker): # Either no args, star args, or double-star args. Beyond the # scope of this checker. return - if isinstance(node.args[0], astng.BinOp) and node.args[0].op == '%': + if isinstance(node.args[0], astroid.BinOp) and node.args[0].op == '%': self.add_message('W1201', node=node) - elif isinstance(node.args[0], astng.Const): + elif isinstance(node.args[0], astroid.Const): self._check_format_string(node, 0) def _check_log_methods(self, node): @@ -116,9 +117,9 @@ class LoggingChecker(checkers.BaseChecker): # Either a malformed call, star args, or double-star args. Beyond # the scope of this checker. return - if isinstance(node.args[1], astng.BinOp) and node.args[1].op == '%': + if isinstance(node.args[1], astroid.BinOp) and node.args[1].op == '%': self.add_message('W1201', node=node) - elif isinstance(node.args[1], astng.Const): + elif isinstance(node.args[1], astroid.Const): self._check_format_string(node, 1) def _check_format_string(self, node, format_arg): @@ -171,7 +172,7 @@ class LoggingChecker(checkers.BaseChecker): Returns: Number of AST nodes that aren't keywords. """ - return sum(1 for arg in args if not isinstance(arg, astng.Keyword)) + return sum(1 for arg in args if not isinstance(arg, astroid.Keyword)) def register(linter): diff --git a/checkers/misc.py b/checkers/misc.py index 18d7586ad..69959090c 100644 --- a/checkers/misc.py +++ b/checkers/misc.py @@ -27,12 +27,18 @@ MSGS = { 'W0511': ('%s', 'fixme', 'Used when a warning note as FIXME or XXX is detected.'), + 'W0512': ('Cannot decode using encoding "%s", unexpected byte at position %d', + 'invalid-encoded-data', + 'Used when a source line cannot be decoded using the specified ' + 'source file encoding.', + {'maxversion': (3, 0)}), } + class EncodingChecker(BaseChecker): """checks for: * warning notes in the code like FIXME, XXX - * PEP 263: source code with non ascii character but no encoding declaration + * encoding issues. """ __implements__ = IRawChecker @@ -48,30 +54,36 @@ separated by a comma.' }), ) - def __init__(self, linter=None): - BaseChecker.__init__(self, linter) + def _check_note(self, notes, lineno, line): + match = notes.search(line) + if match: + self.add_message('W0511', args=line[match.start():-1], line=lineno) + + def _check_encoding(self, lineno, line, file_encoding): + try: + return unicode(line, file_encoding) + except UnicodeDecodeError, ex: + self.add_message('W0512', line=lineno, + args=(file_encoding, ex.args[2])) - def process_module(self, node): - """inspect the source file to found encoding problem or fixmes like + def process_module(self, module): + """inspect the source file to find encoding problem or fixmes like notes """ - stream = node.file_stream - stream.seek(0) # XXX may be removed with astng > 0.23 - # warning notes in the code - notes = [] - for note in self.config.notes: - notes.append(re.compile(note)) - linenum = 1 - for line in stream.readlines(): - for note in notes: - match = note.search(line) - if match: - self.add_message('W0511', args=line[match.start():-1], - line=linenum) - break - linenum += 1 - - + stream = module.file_stream + stream.seek(0) # XXX may be removed with astroid > 0.23 + if self.config.notes: + notes = re.compile('|'.join(self.config.notes)) + else: + notes = None + if module.file_encoding: + encoding = module.file_encoding + else: + encoding = 'ascii' + for lineno, line in enumerate(stream): + line = self._check_encoding(lineno+1, line, encoding) + if line is not None and notes: + self._check_note(notes, lineno+1, line) def register(linter): """required method to auto register this checker""" diff --git a/checkers/newstyle.py b/checkers/newstyle.py index edadad88d..98321954a 100644 --- a/checkers/newstyle.py +++ b/checkers/newstyle.py @@ -15,10 +15,11 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """check for new / old style related problems """ +import sys -from logilab import astng +import astroid -from pylint.interfaces import IASTNGChecker +from pylint.interfaces import IAstroidChecker from pylint.checkers import BaseChecker from pylint.checkers.utils import check_messages @@ -29,26 +30,34 @@ MSGS = { 'E1002': ('Use of super on an old style class', 'super-on-old-class', 'Used when an old style class uses the super builtin.'), - 'E1003': ('Bad first argument %r given to super class', + 'E1003': ('Bad first argument %r given to super()', 'bad-super-call', 'Used when another argument than the current class is given as \ first argument of the super builtin.'), + 'E1004': ('Missing argument to super()', + 'missing-super-argument', + 'Used when the super builtin didn\'t receive an \ + argument on Python 2'), 'W1001': ('Use of "property" on an old style class', 'property-on-old-class', 'Used when PyLint detect the use of the builtin "property" \ on an old style class while this is relying on new style \ classes features'), + 'C1001': ('Old-style class defined.', + 'old-style-class', + 'Used when a class is defined that does not inherit from another' + 'class and does not inherit explicitly from "object".') } class NewStyleConflictChecker(BaseChecker): """checks for usage of new style capabilities on old style classes and - other new/old styles conflicts problems - * use of property, __slots__, super - * "super" usage + other new/old styles conflicts problems + * use of property, __slots__, super + * "super" usage """ - - __implements__ = (IASTNGChecker,) + + __implements__ = (IAstroidChecker,) # configuration section name name = 'newstyle' @@ -58,53 +67,67 @@ class NewStyleConflictChecker(BaseChecker): # configuration options options = () - @check_messages('E1001') + @check_messages('E1001', 'C1001') def visit_class(self, node): """check __slots__ usage - """ + """ if '__slots__' in node and not node.newstyle: self.add_message('E1001', node=node) + # The node type could be class, exception, metaclass, or + # interface. Presumably, the non-class-type nodes would always + # have an explicit base class anyway. + if not node.bases and node.type == 'class': + self.add_message('C1001', node=node) @check_messages('W1001') def visit_callfunc(self, node): """check property usage""" parent = node.parent.frame() - if (isinstance(parent, astng.Class) and + if (isinstance(parent, astroid.Class) and not parent.newstyle and - isinstance(node.func, astng.Name)): + isinstance(node.func, astroid.Name)): name = node.func.name if name == 'property': self.add_message('W1001', node=node) - @check_messages('E1002', 'E1003') + @check_messages('E1002', 'E1003', 'E1004') def visit_function(self, node): """check use of super""" # ignore actual functions or method within a new style class if not node.is_method(): return klass = node.parent.frame() - for stmt in node.nodes_of_class(astng.CallFunc): + for stmt in node.nodes_of_class(astroid.CallFunc): expr = stmt.func - if not isinstance(expr, astng.Getattr): + if not isinstance(expr, astroid.Getattr): continue call = expr.expr # skip the test if using super - if isinstance(call, astng.CallFunc) and \ - isinstance(call.func, astng.Name) and \ + if isinstance(call, astroid.CallFunc) and \ + isinstance(call.func, astroid.Name) and \ call.func.name == 'super': if not klass.newstyle: # super should not be used on an old style class self.add_message('E1002', node=node) else: # super first arg should be the class + if not call.args and sys.version_info[0] == 3: + # unless Python 3 + continue + try: supcls = (call.args and call.args[0].infer().next() or None) - except astng.InferenceError: + except astroid.InferenceError: continue + + if supcls is None and sys.version_info[0] == 2: + self.add_message('missing-super-argument', node=call) + continue + if klass is not supcls: supcls = getattr(supcls, 'name', supcls) - self.add_message('E1003', node=node, args=supcls) + self.add_message('E1003', node=call, args=(supcls, )) def register(linter): diff --git a/checkers/raw_metrics.py b/checkers/raw_metrics.py index 8728fb6bc..a8e4367ce 100644 --- a/checkers/raw_metrics.py +++ b/checkers/raw_metrics.py @@ -1,3 +1,6 @@ +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later @@ -25,7 +28,8 @@ import tokenize from logilab.common.ureports import Table from pylint.interfaces import ITokenChecker -from pylint.checkers import BaseTokenChecker, EmptyReport +from pylint.utils import EmptyReport +from pylint.checkers import BaseTokenChecker from pylint.reporters import diff_string def report_raw_stats(sect, stats, old_stats): @@ -51,12 +55,12 @@ def report_raw_stats(sect, stats, old_stats): class RawMetricsChecker(BaseTokenChecker): - """does not check anything but gives some raw metrics : - * total number of lines - * total number of code lines - * total number of docstring lines - * total number of comments lines - * total number of empty lines + """does not check anything but gives some raw metrics : + * total number of lines + * total number of code lines + * total number of docstring lines + * total number of comments lines + * total number of empty lines """ __implements__ = (ITokenChecker,) diff --git a/checkers/similar.py b/checkers/similar.py index 6c1b8938f..26b372558 100644 --- a/checkers/similar.py +++ b/checkers/similar.py @@ -1,5 +1,5 @@ # pylint: disable=W0622 -# Copyright (c) 2004-2012 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2004-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -25,7 +25,7 @@ from pylint.interfaces import IRawChecker from pylint.checkers import BaseChecker, table_lines_from_stats -class Similar: +class Similar(object): """finds copy-pasted lines of code in a project""" def __init__(self, min_lines=4, ignore_comments=False, @@ -36,14 +36,21 @@ class Similar: self.ignore_imports = ignore_imports self.linesets = [] - def append_stream(self, streamid, stream): + def append_stream(self, streamid, stream, encoding=None): """append a file to search for similarities""" - stream.seek(0) # XXX may be removed with astng > 0.23 - self.linesets.append(LineSet(streamid, - stream.readlines(), - self.ignore_comments, - self.ignore_docstrings, - self.ignore_imports)) + stream.seek(0) # XXX may be removed with astroid > 0.23 + if encoding is None: + readlines = stream.readlines + else: + readlines = lambda: [line.decode(encoding) for line in stream] + try: + self.linesets.append(LineSet(streamid, + readlines(), + self.ignore_comments, + self.ignore_docstrings, + self.ignore_imports)) + except UnicodeDecodeError: + pass def run(self): """start looking for similarities and display results on stdout""" @@ -80,7 +87,7 @@ class Similar: print "==%s:%s" % (lineset.name, idx) # pylint: disable=W0631 for line in lineset._real_lines[idx:idx+num]: - print " ", line, + print " ", line.rstrip() nb_lignes_dupliquees += num * (len(couples)-1) nb_total_lignes = sum([len(lineset) for lineset in self.linesets]) print "TOTAL lines=%s duplicates=%s percent=%.2f" \ @@ -153,7 +160,8 @@ def stripped_lines(lines, ignore_comments, ignore_docstrings, ignore_imports): strippedlines.append(line) return strippedlines -class LineSet: + +class LineSet(object): """Holds and indexes all the lines of a single source file""" def __init__(self, name, lines, ignore_comments=False, ignore_docstrings=False, ignore_imports=False): @@ -288,7 +296,7 @@ class SimilarChecker(BaseChecker, Similar): stream must implement the readlines method """ - self.append_stream(self.linter.current_name, node.file_stream) + self.append_stream(self.linter.current_name, node.file_stream, node.file_encoding) def close(self): """compute and display similarities on closing (i.e. end of parsing)""" diff --git a/checkers/stdlib.py b/checkers/stdlib.py new file mode 100644 index 000000000..07e1fbe18 --- /dev/null +++ b/checkers/stdlib.py @@ -0,0 +1,69 @@ +# Copyright 2012 Google Inc. +# +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# 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. +"""Checkers for various standard library functions.""" + +import re +import sys + +import astroid + +from pylint.interfaces import IAstroidChecker +from pylint.checkers import BaseChecker, BaseTokenChecker +from pylint.checkers import utils + +_VALID_OPEN_MODE_REGEX = r'^(r?U|[rwa]\+?b?)$' + +if sys.version_info >= (3, 0): + OPEN_MODULE = '_io' +else: + OPEN_MODULE = '__builtin__' + +class OpenModeChecker(BaseChecker): + __implements__ = (IAstroidChecker,) + name = 'open_mode' + + msgs = { + 'W1501': ('"%s" is not a valid mode for open.', + 'bad-open-mode', + 'Python supports: r, w, a modes with b, +, and U options. ' + 'See http://docs.python.org/2/library/functions.html#open'), + } + + @utils.check_messages('W1501') + def visit_callfunc(self, node): + """Visit a CallFunc node.""" + if hasattr(node, 'func'): + infer = utils.safe_infer(node.func) + if infer and infer.root().name == OPEN_MODULE: + if getattr(node.func, 'name', None) in ('open', 'file'): + self._check_open_mode(node) + + def _check_open_mode(self, node): + """Check that the mode argument of an open or file call is valid.""" + try: + mode_arg = utils.get_argument_from_call(node, position=1, keyword='mode') + if mode_arg: + mode_arg = utils.safe_infer(mode_arg) + if (isinstance(mode_arg, astroid.Const) + and not re.match(_VALID_OPEN_MODE_REGEX, mode_arg.value)): + self.add_message('W1501', node=node, args=(mode_arg.value)) + except (utils.NoSuchArgumentError, TypeError): + pass + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(OpenModeChecker(linter)) + diff --git a/checkers/strings.py b/checkers/strings.py index 52ff003b0..42563da77 100644 --- a/checkers/strings.py +++ b/checkers/strings.py @@ -21,11 +21,12 @@ import sys import tokenize -from logilab import astng +import astroid -from pylint.interfaces import ITokenChecker, IASTNGChecker +from pylint.interfaces import ITokenChecker, IAstroidChecker from pylint.checkers import BaseChecker, BaseTokenChecker from pylint.checkers import utils +from pylint.checkers.utils import check_messages _PY3K = sys.version_info >= (3, 0) @@ -72,26 +73,27 @@ MSGS = { specifiers is given too many arguments"), } -OTHER_NODES = (astng.Const, astng.List, astng.Backquote, - astng.Lambda, astng.Function, - astng.ListComp, astng.SetComp, astng.GenExpr) +OTHER_NODES = (astroid.Const, astroid.List, astroid.Backquote, + astroid.Lambda, astroid.Function, + astroid.ListComp, astroid.SetComp, astroid.GenExpr) class StringFormatChecker(BaseChecker): """Checks string formatting operations to ensure that the format string is valid and the arguments match the format string. """ - __implements__ = (IASTNGChecker,) + __implements__ = (IAstroidChecker,) name = 'string' msgs = MSGS + @check_messages(*(MSGS.keys())) def visit_binop(self, node): if node.op != '%': return left = node.left args = node.right - if not (isinstance(left, astng.Const) + if not (isinstance(left, astroid.Const) and isinstance(left.value, basestring)): return format_string = left.value @@ -114,11 +116,11 @@ class StringFormatChecker(BaseChecker): # Check that the RHS of the % operator is a mapping object # that contains precisely the set of keys required by the # format string. - if isinstance(args, astng.Dict): + if isinstance(args, astroid.Dict): keys = set() unknown_keys = False for k, _ in args.items: - if isinstance(k, astng.Const): + if isinstance(k, astroid.Const): key = k.value if isinstance(key, basestring): keys.add(key) @@ -137,7 +139,7 @@ class StringFormatChecker(BaseChecker): for key in keys: if key not in required_keys: self.add_message('W1301', node=node, args=key) - elif isinstance(args, OTHER_NODES + (astng.Tuple,)): + elif isinstance(args, OTHER_NODES + (astroid.Tuple,)): type_name = type(args).__name__ self.add_message('E1303', node=node, args=type_name) # else: @@ -149,9 +151,9 @@ class StringFormatChecker(BaseChecker): # Check that the number of arguments passed to the RHS of # the % operator matches the number required by the format # string. - if isinstance(args, astng.Tuple): + if isinstance(args, astroid.Tuple): num_args = len(args.elts) - elif isinstance(args, OTHER_NODES + (astng.Dict, astng.DictComp)): + elif isinstance(args, OTHER_NODES + (astroid.Dict, astroid.DictComp)): num_args = 1 else: # The RHS of the format specifier is a name or @@ -166,7 +168,7 @@ class StringFormatChecker(BaseChecker): class StringMethodsChecker(BaseChecker): - __implements__ = (IASTNGChecker,) + __implements__ = (IAstroidChecker,) name = 'string' msgs = { 'E1310': ("Suspicious argument in %s.%s call", @@ -175,15 +177,16 @@ class StringMethodsChecker(BaseChecker): " duplicate character, "), } + @check_messages(*(MSGS.keys())) def visit_callfunc(self, node): func = utils.safe_infer(node.func) - if (isinstance(func, astng.BoundMethod) - and isinstance(func.bound, astng.Instance) + if (isinstance(func, astroid.BoundMethod) + and isinstance(func.bound, astroid.Instance) and func.bound.name in ('str', 'unicode', 'bytes') and func.name in ('strip', 'lstrip', 'rstrip') and node.args): arg = utils.safe_infer(node.args[0]) - if not isinstance(arg, astng.Const): + if not isinstance(arg, astroid.Const): return if len(arg.value) != len(set(arg.value)): self.add_message('E1310', node=node, diff --git a/checkers/typecheck.py b/checkers/typecheck.py index 3dcac24a7..69883592b 100644 --- a/checkers/typecheck.py +++ b/checkers/typecheck.py @@ -13,16 +13,16 @@ # 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. -"""try to find more bugs in the code using astng inference capabilities +"""try to find more bugs in the code using astroid inference capabilities """ import re import shlex -from logilab import astng -from logilab.astng import InferenceError, NotFoundError, YES, Instance +import astroid +from astroid import InferenceError, NotFoundError, YES, Instance -from pylint.interfaces import IASTNGChecker +from pylint.interfaces import IAstroidChecker from pylint.checkers import BaseChecker from pylint.checkers.utils import safe_infer, is_super, check_messages @@ -37,7 +37,7 @@ MSGS = { 'E1103': ('%s %r has no %r member (but some types could not be inferred)', 'maybe-no-member', 'Used when a variable is accessed for an unexistent member, but \ - astng was not able to interpret all possible types of this \ + astroid was not able to interpret all possible types of this \ variable.'), 'E1111': ('Assigning to function call which doesn\'t return', 'assignment-from-no-return', @@ -58,7 +58,8 @@ MSGS = { 'E1122': ('Duplicate keyword argument %r in function call', 'duplicate-keyword-arg', 'Used when a function call passes the same keyword argument \ - multiple times.'), + multiple times.', + {'maxversion': (2, 6)}), 'E1123': ('Passing unexpected keyword argument %r in function call', 'unexpected-keyword-arg', 'Used when a function call passes a keyword argument that \ @@ -68,13 +69,18 @@ MSGS = { 'Used when a function call would result in assigning multiple \ values to a function parameter, one value from a positional \ argument and one from a keyword argument.'), + 'E1125': ('Missing mandatory keyword argument %r', + 'missing-kwoa', + 'Used when a function call doesn\'t pass a mandatory \ + keyword-only argument.', + {'minversion': (3, 0)}), } class TypeChecker(BaseChecker): """try to find bugs in the code using type inference """ - __implements__ = (IASTNGChecker,) + __implements__ = (IAstroidChecker,) # configuration section name name = 'typecheck' @@ -120,7 +126,7 @@ accessed. Python regular expressions are accepted.'} self.generated_members.extend(('REQUEST', 'acl_users', 'aq_parent')) def visit_assattr(self, node): - if isinstance(node.ass_type(), astng.AugAssign): + if isinstance(node.ass_type(), astroid.AugAssign): self.visit_getattr(node) def visit_delattr(self, node): @@ -162,7 +168,7 @@ accessed. Python regular expressions are accepted.'} inference_failure = True continue # skip None anyway - if isinstance(owner, astng.Const) and owner.value is None: + if isinstance(owner, astroid.Const) and owner.value is None: continue # XXX "super" / metaclass call if is_super(owner) or getattr(owner, 'type', None) == 'metaclass': @@ -174,14 +180,14 @@ accessed. Python regular expressions are accepted.'} continue try: if not [n for n in owner.getattr(node.attrname) - if not isinstance(n.statement(), astng.AugAssign)]: + if not isinstance(n.statement(), astroid.AugAssign)]: missingattr.add((owner, name)) continue except AttributeError: # XXX method / function continue except NotFoundError: - if isinstance(owner, astng.Function) and owner.decorators: + if isinstance(owner, astroid.Function) and owner.decorators: continue if isinstance(owner, Instance) and owner.has_dynamic_getattr(): continue @@ -212,33 +218,34 @@ accessed. Python regular expressions are accepted.'} args=(owner.display_type(), name, node.attrname)) - + @check_messages('E1111', 'W1111') def visit_assign(self, node): """check that if assigning to a function call, the function is possibly returning something valuable """ - if not isinstance(node.value, astng.CallFunc): + if not isinstance(node.value, astroid.CallFunc): return function_node = safe_infer(node.value.func) # skip class, generator and incomplete function definition - if not (isinstance(function_node, astng.Function) and + if not (isinstance(function_node, astroid.Function) and function_node.root().fully_defined()): return if function_node.is_generator() \ or function_node.is_abstract(pass_is_abstract=False): return - returns = list(function_node.nodes_of_class(astng.Return, - skip_klass=astng.Function)) + returns = list(function_node.nodes_of_class(astroid.Return, + skip_klass=astroid.Function)) if len(returns) == 0: self.add_message('E1111', node=node) else: for rnode in returns: - if not (isinstance(rnode.value, astng.Const) + if not (isinstance(rnode.value, astroid.Const) and rnode.value.value is None): break else: self.add_message('W1111', node=node) + @check_messages(*(MSGS.keys())) def visit_callfunc(self, node): """check that called functions/methods are inferred to callable objects, and that the arguments passed to the function match the parameters in @@ -250,7 +257,7 @@ accessed. Python regular expressions are accepted.'} keyword_args = set() num_positional_args = 0 for arg in node.args: - if isinstance(arg, astng.Keyword): + if isinstance(arg, astroid.Keyword): keyword = arg.arg if keyword in keyword_args: self.add_message('E1122', node=node, args=keyword) @@ -265,18 +272,18 @@ accessed. Python regular expressions are accepted.'} # Note that BoundMethod is a subclass of UnboundMethod (huh?), so must # come first in this 'if..else'. - if isinstance(called, astng.BoundMethod): + if isinstance(called, astroid.BoundMethod): # Bound methods have an extra implicit 'self' argument. num_positional_args += 1 - elif isinstance(called, astng.UnboundMethod): + elif isinstance(called, astroid.UnboundMethod): if called.decorators is not None: for d in called.decorators.nodes: - if isinstance(d, astng.Name) and (d.name == 'classmethod'): + if isinstance(d, astroid.Name) and (d.name == 'classmethod'): # Class methods have an extra implicit 'cls' argument. num_positional_args += 1 break - elif (isinstance(called, astng.Function) or - isinstance(called, astng.Lambda)): + elif (isinstance(called, astroid.Function) or + isinstance(called, astroid.Lambda)): pass else: return @@ -295,15 +302,15 @@ accessed. Python regular expressions are accepted.'} parameters = [] parameter_name_to_index = {} for i, arg in enumerate(called.args.args): - if isinstance(arg, astng.Tuple): + if isinstance(arg, astroid.Tuple): name = None # Don't store any parameter names within the tuple, since those # are not assignable from keyword arguments. else: - if isinstance(arg, astng.Keyword): + if isinstance(arg, astroid.Keyword): name = arg.arg else: - assert isinstance(arg, astng.AssName) + assert isinstance(arg, astroid.AssName) # This occurs with: # def f( (a), (b) ): pass name = arg.name @@ -314,6 +321,15 @@ accessed. Python regular expressions are accepted.'} defval = None parameters.append([(name, defval), False]) + kwparams = {} + for i, arg in enumerate(called.args.kwonlyargs): + if isinstance(arg, astroid.Keyword): + name = arg.arg + else: + assert isinstance(arg, astroid.AssName) + name = arg.name + kwparams[name] = [called.args.kw_defaults[i], False] + # Match the supplied arguments against the function parameters. # 1. Match the positional arguments. @@ -338,6 +354,12 @@ accessed. Python regular expressions are accepted.'} self.add_message('E1124', node=node, args=keyword) else: parameters[i][1] = True + elif keyword in kwparams: + if kwparams[keyword][1]: # XXX is that even possible? + # Duplicate definition of function parameter. + self.add_message('E1124', node=node, args=keyword) + else: + kwparams[keyword][1] = True elif called.args.kwarg is not None: # The keyword argument gets assigned to the **kwargs parameter. pass @@ -382,6 +404,12 @@ accessed. Python regular expressions are accepted.'} display_name = repr(name) self.add_message('E1120', node=node, args=display_name) + for name in kwparams: + defval, assigned = kwparams[name] + if defval is None and not assigned: + self.add_message('E1125', node=node, args=name) + + def register(linter): """required method to auto register this checker """ linter.register_checker(TypeChecker(linter)) diff --git a/checkers/utils.py b/checkers/utils.py index da09e86a1..68b54693d 100644 --- a/checkers/utils.py +++ b/checkers/utils.py @@ -21,19 +21,22 @@ import re import string -from logilab import astng -from logilab.astng import scoped_nodes +import astroid +from astroid import scoped_nodes from logilab.common.compat import builtins BUILTINS_NAME = builtins.__name__ -COMP_NODE_TYPES = astng.ListComp, astng.SetComp, astng.DictComp, astng.GenExpr +COMP_NODE_TYPES = astroid.ListComp, astroid.SetComp, astroid.DictComp, astroid.GenExpr +class NoSuchArgumentError(Exception): + pass + def is_inside_except(node): """Returns true if node is inside the name of an except handler.""" current = node - while current and not isinstance(current.parent, astng.ExceptHandler): + while current and not isinstance(current.parent, astroid.ExceptHandler): current = current.parent return current and current is current.parent.name @@ -41,7 +44,7 @@ def is_inside_except(node): def get_all_elements(node): """Recursively returns all atoms in nested lists and tuples.""" - if isinstance(node, (astng.Tuple, astng.List)): + if isinstance(node, (astroid.Tuple, astroid.List)): for child in node.elts: for e in get_all_elements(child): yield e @@ -56,9 +59,9 @@ def clobber_in_except(node): Returns (True, args for W0623) if assignment clobbers an existing variable, (False, None) otherwise. """ - if isinstance(node, astng.AssAttr): + if isinstance(node, astroid.AssAttr): return (True, (node.attrname, 'object %r' % (node.expr.name,))) - elif isinstance(node, astng.AssName): + elif isinstance(node, astroid.AssName): name = node.name if is_builtin(name): return (True, (name, 'builtins')) @@ -66,7 +69,7 @@ def clobber_in_except(node): scope, stmts = node.lookup(name) if (stmts and not isinstance(stmts[0].ass_type(), - (astng.Assign, astng.AugAssign, astng.ExceptHandler))): + (astroid.Assign, astroid.AugAssign, astroid.ExceptHandler))): return (True, (name, 'outer scope (line %s)' % (stmts[0].fromlineno,))) return (False, None) @@ -79,12 +82,12 @@ def safe_infer(node): try: inferit = node.infer() value = inferit.next() - except astng.InferenceError: + except astroid.InferenceError: return try: inferit.next() return # None if there is ambiguity on the inferred node - except astng.InferenceError: + except astroid.InferenceError: return # there is some kind of ambiguity except StopIteration: return value @@ -100,24 +103,28 @@ def is_super(node): def is_error(node): """return true if the function does nothing but raising an exception""" for child_node in node.get_children(): - if isinstance(child_node, astng.Raise): + if isinstance(child_node, astroid.Raise): return True return False def is_raising(body): """return true if the given statement node raise an exception""" for node in body: - if isinstance(node, astng.Raise): + if isinstance(node, astroid.Raise): return True return False def is_empty(body): """return true if the given node does nothing but 'pass'""" - return len(body) == 1 and isinstance(body[0], astng.Pass) + return len(body) == 1 and isinstance(body[0], astroid.Pass) builtins = builtins.__dict__.copy() SPECIAL_BUILTINS = ('__builtins__',) # '__path__', '__file__') +def is_builtin_object(node): + """Returns True if the given node is an object from the __builtin__ module.""" + return node and node.root().name == '__builtin__' + def is_builtin(name): # was is_native_builtin """return true if <name> could be considered as a builtin defined by python """ @@ -136,20 +143,20 @@ def is_defined_before(var_node): _node = var_node.parent while _node: if isinstance(_node, COMP_NODE_TYPES): - for ass_node in _node.nodes_of_class(astng.AssName): + for ass_node in _node.nodes_of_class(astroid.AssName): if ass_node.name == varname: return True - elif isinstance(_node, astng.For): - for ass_node in _node.target.nodes_of_class(astng.AssName): + elif isinstance(_node, astroid.For): + for ass_node in _node.target.nodes_of_class(astroid.AssName): if ass_node.name == varname: return True - elif isinstance(_node, astng.With): - if _node.vars is None: - # quickfix : case in which 'with' is used without 'as' - return False - if _node.vars.name == varname: - return True - elif isinstance(_node, (astng.Lambda, astng.Function)): + elif isinstance(_node, astroid.With): + for expr, vars in _node.items: + if expr.parent_of(var_node): + break + if vars and vars.name == varname: + return True + elif isinstance(_node, (astroid.Lambda, astroid.Function)): if _node.args.is_argument(varname): return True if getattr(_node, 'name', None) == varname: @@ -161,10 +168,10 @@ def is_defined_before(var_node): _node = stmt.previous_sibling() lineno = stmt.fromlineno while _node and _node.fromlineno == lineno: - for ass_node in _node.nodes_of_class(astng.AssName): + for ass_node in _node.nodes_of_class(astroid.AssName): if ass_node.name == varname: return True - for imp_node in _node.nodes_of_class( (astng.From, astng.Import)): + for imp_node in _node.nodes_of_class( (astroid.From, astroid.Import)): if varname in [name[1] or name[0] for name in imp_node.names]: return True _node = _node.previous_sibling() @@ -175,9 +182,9 @@ def is_func_default(node): value """ parent = node.scope() - if isinstance(parent, astng.Function): + if isinstance(parent, astroid.Function): for default_node in parent.args.defaults: - for default_name_node in default_node.nodes_of_class(astng.Name): + for default_name_node in default_node.nodes_of_class(astroid.Name): if default_name_node is node: return True return False @@ -186,10 +193,10 @@ def is_func_decorator(node): """return true if the name is used in function decorator""" parent = node.parent while parent is not None: - if isinstance(parent, astng.Decorators): + if isinstance(parent, astroid.Decorators): return True if (parent.is_statement or - isinstance(parent, astng.Lambda) or + isinstance(parent, astroid.Lambda) or isinstance(parent, (scoped_nodes.ComprehensionScope, scoped_nodes.ListComp))): break @@ -197,7 +204,7 @@ def is_func_decorator(node): return False def is_ancestor_name(frame, node): - """return True if `frame` is a astng.Class node with `node` in the + """return True if `frame` is a astroid.Class node with `node` in the subtree of its bases attribute """ try: @@ -205,23 +212,23 @@ def is_ancestor_name(frame, node): except AttributeError: return False for base in bases: - if node in base.nodes_of_class(astng.Name): + if node in base.nodes_of_class(astroid.Name): return True return False def assign_parent(node): """return the higher parent which is not an AssName, Tuple or List node """ - while node and isinstance(node, (astng.AssName, - astng.Tuple, - astng.List)): + while node and isinstance(node, (astroid.AssName, + astroid.Tuple, + astroid.List)): node = node.parent return node def overrides_an_abstract_method(class_node, name): """return True if pnode is a parent of node""" for ancestor in class_node.ancestors(): - if name in ancestor and isinstance(ancestor[name], astng.Function) and \ + if name in ancestor and isinstance(ancestor[name], astroid.Function) and \ ancestor[name].is_abstract(pass_is_abstract=False): return True return False @@ -229,7 +236,7 @@ def overrides_an_abstract_method(class_node, name): def overrides_a_method(class_node, name): """return True if <name> is a method overridden from an ancestor""" for ancestor in class_node.ancestors(): - if name in ancestor and isinstance(ancestor[name], astng.Function): + if name in ancestor and isinstance(ancestor[name], astroid.Function): return True return False @@ -352,7 +359,7 @@ def node_frame_class(node): """ klass = node.frame() - while klass is not None and not isinstance(klass, astng.Class): + while klass is not None and not isinstance(klass, astroid.Class): if klass.parent is None: klass = None else: @@ -364,8 +371,8 @@ def is_super_call(expr): """return True if expression node is a function call and if function name is super. Check before that you're in a method. """ - return (isinstance(expr, astng.CallFunc) and - isinstance(expr.func, astng.Name) and + return (isinstance(expr, astroid.CallFunc) and + isinstance(expr.func, astroid.Name) and expr.func.name == 'super') def is_attr_private(attrname): @@ -374,3 +381,28 @@ def is_attr_private(attrname): """ regex = re.compile('^_{2,}.*[^_]+_?$') return regex.match(attrname) + +def get_argument_from_call(callfunc_node, position=None, keyword=None): + """Returns the specified argument from a function call. + + :param callfunc_node: Node representing a function call to check. + :param int position: position of the argument. + :param str keyword: the keyword of the argument. + + :returns: The node representing the argument, None if the argument is not found. + :raises ValueError: if both position and keyword are None. + :raises NoSuchArgumentError: if no argument at the provided position or with + the provided keyword. + """ + if not position and not keyword: + raise ValueError('Must specify at least one of: position or keyword.') + try: + if position and not isinstance(callfunc_node.args[position], astroid.Keyword): + return callfunc_node.args[position] + except IndexError as error: + raise NoSuchArgumentError(error) + if keyword: + for arg in callfunc_node.args: + if isinstance(arg, astroid.Keyword) and arg.arg == keyword: + return arg.value + raise NoSuchArgumentError diff --git a/checkers/variables.py b/checkers/variables.py index ff9a1d954..7e924ef4a 100644 --- a/checkers/variables.py +++ b/checkers/variables.py @@ -19,10 +19,10 @@ import sys from copy import copy -from logilab import astng -from logilab.astng import are_exclusive, builtin_lookup, ASTNGBuildingException +import astroid +from astroid import are_exclusive, builtin_lookup, AstroidBuildingException -from pylint.interfaces import IASTNGChecker +from pylint.interfaces import IAstroidChecker from pylint.checkers import BaseChecker from pylint.checkers.utils import (PYMETHODS, is_ancestor_name, is_builtin, is_defined_before, is_error, is_func_default, is_func_decorator, @@ -32,7 +32,7 @@ from pylint.checkers.utils import (PYMETHODS, is_ancestor_name, is_builtin, def in_for_else_branch(parent, stmt): """Returns True if stmt in inside the else branch for a parent For stmt.""" - return (isinstance(parent, astng.For) and + return (isinstance(parent, astroid.For) and any(else_stmt.parent_of(stmt) for else_stmt in parent.orelse)) def overridden_method(klass, name): @@ -45,9 +45,9 @@ def overridden_method(klass, name): meth_node = parent[name] except KeyError: # We have found an ancestor defining <name> but it's not in the local - # dictionary. This may happen with astng built from living objects. + # dictionary. This may happen with astroid built from living objects. return None - if isinstance(meth_node, astng.Function): + if isinstance(meth_node, astroid.Function): return meth_node return None @@ -118,6 +118,12 @@ MSGS = { 'Used when an loop variable (i.e. defined by a for loop or \ a list comprehension or a generator expression) is used outside \ the loop.'), + + 'W0632': ('Possible unbalanced tuple unpacking: ' + 'left side has %d label(s), right side has %d value(s)', + 'unbalanced-tuple-unpacking', + 'Used when there is an unbalanced tuple unpacking in assignment'), + } class VariablesChecker(BaseChecker): @@ -129,7 +135,7 @@ class VariablesChecker(BaseChecker): * __all__ consistency """ - __implements__ = IASTNGChecker + __implements__ = IAstroidChecker name = 'variables' msgs = MSGS @@ -140,7 +146,7 @@ class VariablesChecker(BaseChecker): 'help' : 'Tells whether we should check for unused import in \ __init__ files.'}), ("dummy-variables-rgx", - {'default': ('_|dummy'), + {'default': ('_$|dummy'), 'type' :'regexp', 'metavar' : '<regexp>', 'help' : 'A regular expression matching the beginning of \ the name of dummy variables (i.e. not used).'}), @@ -166,7 +172,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' # do not print Redefining builtin for additional builtins self.add_message('W0622', args=name, node=stmts[0]) - @check_messages('W0611', 'W0614') + @check_messages('W0611', 'W0614', 'W0622', 'E0603', 'E0604') def leave_module(self, node): """leave module: check globals """ @@ -175,14 +181,14 @@ builtins. Remember that you should avoid to define new builtins when possible.' # attempt to check for __all__ if defined if '__all__' in node.locals: assigned = node.igetattr('__all__').next() - if assigned is not astng.YES: + if assigned is not astroid.YES: for elt in getattr(assigned, 'elts', ()): try: elt_name = elt.infer().next() - except astng.InferenceError: + except astroid.InferenceError: continue - if not isinstance(elt_name, astng.Const) or not isinstance(elt_name.value, basestring): + if not isinstance(elt_name, astroid.Const) or not isinstance(elt_name.value, basestring): self.add_message('E0604', args=elt.as_string(), node=elt) continue elt_name = elt.value @@ -197,9 +203,9 @@ builtins. Remember that you should avoid to define new builtins when possible.' return for name, stmts in not_consumed.iteritems(): stmt = stmts[0] - if isinstance(stmt, astng.Import): + if isinstance(stmt, astroid.Import): self.add_message('W0611', args=name, node=stmt) - elif isinstance(stmt, astng.From) and stmt.modname != '__future__': + elif isinstance(stmt, astroid.From) and stmt.modname != '__future__': if stmt.names[0][0] == '*': self.add_message('W0614', args=name, node=stmt) else: @@ -271,9 +277,11 @@ builtins. Remember that you should avoid to define new builtins when possible.' for name, stmt in node.items(): if is_inside_except(stmt): continue - if name in globs and not isinstance(stmt, astng.Global): + if name in globs and not isinstance(stmt, astroid.Global): line = globs[name][0].fromlineno - self.add_message('W0621', args=(name, line), node=stmt) + dummy_rgx = self.config.dummy_variables_rgx + if not dummy_rgx.match(name): + self.add_message('W0621', args=(name, line), node=stmt) elif is_builtin(name): # do not print Redefining builtin for additional builtins self.add_message('W0622', args=name, node=stmt) @@ -301,7 +309,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' # ignore names imported by the global statement # FIXME: should only ignore them if it's assigned latter stmt = stmts[0] - if isinstance(stmt, astng.Global): + if isinstance(stmt, astroid.Global): continue # care about functions with unknown argument (builtins) if name in argnames: @@ -328,7 +336,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' def visit_global(self, node): """check names imported exists in the global scope""" frame = node.frame() - if isinstance(frame, astng.Module): + if isinstance(frame, astroid.Module): self.add_message('W0604', node=node) return module = frame.root() @@ -336,7 +344,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' for name in node.names: try: assign_nodes = module.getattr(name) - except astng.NotFoundError: + except astroid.NotFoundError: # unassigned global, skip assign_nodes = [] for anode in assign_nodes: @@ -399,10 +407,11 @@ builtins. Remember that you should avoid to define new builtins when possible.' astmts = _astmts if len(astmts) == 1: ass = astmts[0].ass_type() - if isinstance(ass, (astng.For, astng.Comprehension, astng.GenExpr)) \ + if isinstance(ass, (astroid.For, astroid.Comprehension, astroid.GenExpr)) \ and not ass.statement() is node.statement(): self.add_message('W0631', args=name, node=node) + @check_messages('W0623') def visit_excepthandler(self, node): for name in get_all_elements(node.name): clobbering, args = clobber_in_except(name) @@ -410,19 +419,20 @@ builtins. Remember that you should avoid to define new builtins when possible.' self.add_message('W0623', args=args, node=name) def visit_assname(self, node): - if isinstance(node.ass_type(), astng.AugAssign): + if isinstance(node.ass_type(), astroid.AugAssign): self.visit_name(node) def visit_delname(self, node): self.visit_name(node) + @check_messages(*(MSGS.keys())) def visit_name(self, node): """check that a name is defined if the current scope and doesn't redefine a built-in """ stmt = node.statement() if stmt.fromlineno is None: - # name node from a astng built from live code, skip + # name node from a astroid built from live code, skip assert not stmt.root().file.endswith('.py') return name = node.name @@ -481,13 +491,13 @@ builtins. Remember that you should avoid to define new builtins when possible.' and stmt.fromlineno <= defstmt.fromlineno and not is_defined_before(node) and not are_exclusive(stmt, defstmt, ('NameError', 'Exception', 'BaseException'))): - if defstmt is stmt and isinstance(node, (astng.DelName, - astng.AssName)): + if defstmt is stmt and isinstance(node, (astroid.DelName, + astroid.AssName)): self.add_message('E0602', args=name, node=node) elif self._to_consume[-1][-1] != 'lambda': # E0601 may *not* occurs in lambda scope self.add_message('E0601', args=name, node=node) - if not isinstance(node, astng.AssName): # Aug AssName + if not isinstance(node, astroid.AssName): # Aug AssName del to_consume[name] else: del consumed[name] @@ -497,7 +507,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' else: # we have not found the name, if it isn't a builtin, that's an # undefined name ! - if not (name in astng.Module.scope_attrs or is_builtin(name) + if not (name in astroid.Module.scope_attrs or is_builtin(name) or name in self.config.additional_builtins): self.add_message('E0602', args=name, node=node) @@ -508,7 +518,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' parts = name.split('.') try: module = node.infer_name_module(parts[0]).next() - except astng.ResolveError: + except astroid.ResolveError: continue self._check_module_attrs(node, module, parts[1:]) @@ -519,7 +529,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' level = getattr(node, 'level', None) try: module = node.root().import_module(name_parts[0], level=level) - except ASTNGBuildingException: + except AstroidBuildingException: return except Exception, exc: print 'Unhandled exception in VariablesChecker:', exc @@ -532,12 +542,33 @@ builtins. Remember that you should avoid to define new builtins when possible.' continue self._check_module_attrs(node, module, name.split('.')) + @check_messages('unbalanced-tuple-unpacking') + def visit_assign(self, node): + """Check unbalanced tuple unpacking for assignments""" + if not isinstance(node.targets[0], (astroid.Tuple, astroid.List)): + return + try: + infered = node.value.infer().next() + except astroid.InferenceError: + return + if not isinstance(infered, (astroid.Tuple, astroid.List)): + return + targets = node.targets[0].itered() + values = infered.itered() + if any(not isinstance(target_node, astroid.AssName) + for target_node in targets): + return + if len(targets) != len(values): + self.add_message('unbalanced-tuple-unpacking', + node=node, + args=(len(targets), len(values))) + def _check_module_attrs(self, node, module, module_names): """check that module_names (list of string) are accessible through the given module if the latest access name corresponds to a module, return it """ - assert isinstance(module, astng.Module), module + assert isinstance(module, astroid.Module), module while module_names: name = module_names.pop(0) if name == '__dict__': @@ -545,12 +576,12 @@ builtins. Remember that you should avoid to define new builtins when possible.' break try: module = module.getattr(name)[0].infer().next() - if module is astng.YES: + if module is astroid.YES: return None - except astng.NotFoundError: + except astroid.NotFoundError: self.add_message('E0611', args=(name, module.name), node=node) return None - except astng.InferenceError: + except astroid.InferenceError: return None if module_names: # FIXME: other message if name is not the latest part of @@ -559,7 +590,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' self.add_message('E0611', node=node, args=('.'.join(module_names), modname)) return None - if isinstance(module, astng.Module): + if isinstance(module, astroid.Module): return module return None diff --git a/debian.sid/control b/debian.sid/control index 8941f0957..f06253156 100644 --- a/debian.sid/control +++ b/debian.sid/control @@ -12,7 +12,7 @@ Vcs-Browser: http://hg.logilab.org/pylint Package: pylint Architecture: all -Depends: ${python:Depends}, ${misc:Depends}, python-logilab-common (>= 0.53), python-logilab-astng (>= 0.21) +Depends: ${python:Depends}, ${misc:Depends}, python-logilab-common (>= 0.53), python-astroid Recommends: python-tk XB-Python-Version: ${python:Versions} Description: python code static checker and UML diagram generator @@ -37,7 +37,7 @@ Description: python code static checker and UML diagram generator Package: pylint3 Architecture: all -Depends: ${python3:Depends}, ${misc:Depends}, python3-logilab-common (>= 0.53), python3-logilab-astng (>= 0.21) +Depends: ${python3:Depends}, ${misc:Depends}, python3-logilab-common (>= 0.53), python3-astroid Recommends: python3-tk XB-Python-Version: ${python3:Versions} Description: python code static checker and UML diagram generator diff --git a/debian/changelog b/debian/changelog index 34c671e3c..3e46fffa9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +pylint (1.0.0-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Tue, 30 Jul 2013 18:18:06 +0200 + pylint (0.28.0-1) unstable; urgency=low * new upstream release diff --git a/debian/control b/debian/control index 8c4354546..ecc265b1a 100644 --- a/debian/control +++ b/debian/control @@ -2,22 +2,26 @@ Source: pylint Section: python Priority: optional Maintainer: Logilab S.A. <contact@logilab.fr> -Uploaders: Sylvain Thénault <sylvain.thenault@logilab.fr>, Alexandre Fayolle <afayolle@debian.org>, Sandro Tosi <morph@debian.org> -Build-Depends: debhelper (>= 5.0.38), python (>= 2.4) +Uploaders: Sylvain Thénault <sylvain.thenault@logilab.fr>, + Alexandre Fayolle <afayolle@debian.org>, + Sandro Tosi <morph@debian.org> +Build-Depends: debhelper (>= 5.0.38), python (>= 2.5) Build-Depends-Indep: python-support -Standards-Version: 3.8.1 -XS-Python-Version: all +XS-Python-Version: >= 2.5 +Standards-Version: 3.8.2 Homepage: http://www.pylint.org Vcs-Svn: svn://svn.debian.org/svn/python-apps/packages/pylint/trunk/ -Vcs-Browser: http://svn.debian.org/viewsvn/python-apps/packages/pylint/trunk/ +Vcs-Hg: https://bitbucket.org/logilab/pylint +Vcs-Browser: https://bitbucket.org/logilab/pylint/src Package: pylint Architecture: all -Depends: ${python:Depends}, ${misc:Depends}, python-logilab-common (>= 0.53.0), python-logilab-astng (>= 0.24.3) +Depends: ${python:Depends}, + ${misc:Depends}, + python-logilab-common (>= 0.53.0), + python-astroid Suggests: python-tk XB-Python-Version: ${python:Versions} -Conflicts: python2.2-pylint, python2.3-pylint, python2.4-pylint, pylint-common, pylint-test -Replaces: python2.2-pylint, python2.3-pylint, python2.4-pylint, pylint-common, pylint-test Description: python code static checker and UML diagram generator Pylint is a Python source code analyzer which looks for programming errors, helps enforcing a coding standard and sniffs for some code diff --git a/doc/contribute.rst b/doc/contribute.rst index d62a50407..2468933c5 100644 --- a/doc/contribute.rst +++ b/doc/contribute.rst @@ -22,9 +22,9 @@ python code. Note that if you don't find something you have expected in Pylint's issue tracker, it may be because it is an issue with one of its dependencies, namely -astng and common: +astroid and logilab-common: -* https://bitbucket.org/logilab/astng +* https://bitbucket.org/logilab/astroid * http://www.logilab.org/project/logilab-common Mailing lists @@ -52,27 +52,98 @@ Pylint is developped using the mercurial_ distributed version control system. You can clone Pylint and its dependencies from :: hg clone https://bitbucket.org/logilab/pylint - hg clone https://bitbucket.org/logilab/astng + hg clone https://bitbucket.org/logilab/astroid hg clone http://hg.logilab.org/logilab/common .. _mercurial: http://www.selenic.com/mercurial/ -Got a change for Pylint? There a few steps you must take to make sure your -patch gets accepted. +Got a change for Pylint? Below are a few steps you should take to make sure +your patch gets accepted. - Test your code - - Pylint keeps a set of unit tests in the /test directory. To get your - patch accepted you must write (or change) a test input file and message - file in the appropriate input and messages folders. + - Pylint keeps a set of unit tests in the /test directory. The + `test_func.py` module uses external files to have some kind of easy + functionnal testing. To get your patch accepted you must write (or change) + a test input file in the `test/input` directory and message file in the + `test/messages` directory. Then run `python test_func.py` to ensure that + your test is green. - - In the test folder of Pylint run ``./fulltest.sh <python versions>``, make sure - all tests pass before submitting a patch + - You should also run all the tests to ensure that your change isn't + breaking one. -- Add an short entry to the ChangeLog describing the change +- Add a short entry to the ChangeLog describing the change, except for internal + implementation only changes - Write a comprehensive commit message -- Relate your change to an issue in the tracker +- Relate your change to an issue in the tracker if such an issue exists (see + `this page`_ of Bitbucket documentation for more information on this) -- Send a pull request from bitbucket +- Send a pull request from Bitbucket (more on this here_) + +.. _`this page`: https://confluence.atlassian.com/display/BITBUCKET/Resolve+issues+automatically+when+users+push+code +.. _here: https://confluence.atlassian.com/display/BITBUCKET/Work+with+pull+requests + + +Unit test setup +--------------- + +To run the pylint unit tests within your checkout (without having to install +anything), you need to set PYTHONPATH so that pylint, astroid and the +logilab-common are available. Assume you have those packages in ~/src. If +you have a normal clone of logilab-common, it will not be properly +structured to allow import of logilab.common. To remedy this, create the +necessary structure:: + + cd ~/src + mkdir logilab + mv logilab-common logilab/common + touch logilab/__init__.py + +Now, set PYTHONPATH to your src directory:: + + export PYTHONPATH=~/src + +You now have access to the astroid, logilab.common and pylint packages +without installing them. You can run all the unit tests like so:: + + cd ~/src/pylint/test + for f in *.py ; do + echo $f + python -S $f + done + +The -S flag keeps distutils from interfering with sys.path. YMMV. + + +Adding new functionnal tests +---------------------------- + +Pylint comes with an easy way to write functional tests for new checks: + +* put a Python file in the `test/input` directory, whose name starts with + `func_` and should also contains the symbolic name of the tested check + +* add the expected message file in the `test/messages` directory, using the + same name but a `.txt` extension instead of `.py` + +The message file should use the default text output format (without reports) and lines should be +sorted. E.g on Unix system, you may generate it using:: + + pylint -rn input/func_mycheck.py | sort > pylint messages/func_mycheck.txt + +Also, here are a few naming convention which are used: + +* Python files starting with 'func_noerror_' don't have any message file + associated as they are expected to provide no output at all + +* You may provide different input files (and associated output) depending on the + Python interpreter version: + + * tests whose name ends with `_py<xy>.py` are used for Python >= x.y + * tests whose name ends with `_py<_xy>.py` are used for Python < x.y + +* Similarly you may provide different message files for a single input, message + file whose name ends with '_py<xy>.txt' will be used for Python >= x.y, using + the nearest version possible diff --git a/doc/faq.rst b/doc/faq.rst index 15de8c599..ce5102d37 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -64,10 +64,10 @@ Pylint from the repository, simply invoke :: 2.3 What are Pylint's dependencies? ----------------------------------- -Pylint requires the latest `astng`_ and `logilab-common`_ packages. It should be +Pylint requires the latest `astroid`_ and `logilab-common`_ packages. It should be compatible with any Python version greater than 2.5.0. -.. _`astng`: https://bitbucket.org/logilab/astng +.. _`astroid`: https://bitbucket.org/logilab/astroid .. _`logilab-common`: http://www.logilab.org/project/logilab-common diff --git a/doc/installation.rst b/doc/installation.rst index 976b28b53..5992bfc26 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -5,10 +5,10 @@ Installation Dependencies '''''''''''' -Pylint requires the latest `astng`_ and `logilab-common`_ +Pylint requires the latest `astroid`_ and `logilab-common`_ packages. It should be compatible with any Python version >= 2.5. -.. _`astng`: https://bitbucket.org/logilab/astng +.. _`astroid`: https://bitbucket.org/logilab/astroid .. _`logilab-common`: http://www.logilab.org/project/logilab-common diff --git a/doc/output.rst b/doc/output.rst index ffb997737..6617ac443 100644 --- a/doc/output.rst +++ b/doc/output.rst @@ -6,7 +6,40 @@ The default format for the output is raw text. You can change this by passing pylint the ``--output-format=<value>`` option. Possible values are: parseable, colorized, msvs (visual studio) and html. -There are several sections in pylint's output. +Moreover you can customize the exact way information are displayed using the +`--msg-template=<format string>` option. The `format string` uses the +`Python new format syntax`_ and the following fields are available : + +path + relative path to the file +abspath + absolute path to the file +line + line number +column + column number +module + module name +obj + object within the module (if any) +msg + text of the message +symbol + symbolic name of the message +C + one letter indication of the message category +category + fullname of the message category + +For exemple the default format can be obtained with:: + + pylint --msg-template='{sigle}:{line:3d},{column}: {obj}: {msg}' + +and a Visual Studio compatible format can be obtained with:: + + pylint --msg-template='{path}({line}): [{sigle}{obj}] {msg}' + +.. _Python new format syntax: http://docs.python.org/2/library/string.html#formatstrings Source code analysis section '''''''''''''''''''''''''''' diff --git a/epylint.py b/epylint.py index db2ed82f1..fbdfdacd7 100755 --- a/epylint.py +++ b/epylint.py @@ -1,5 +1,5 @@ # -*- coding: utf-8; mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=python:et:sw=4:ts=4:sts=4 -# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -74,9 +74,9 @@ def lint(filename): # Start pylint # Ensure we use the python and pylint associated with the running epylint lintPath = os.path.join(os.path.dirname(__file__), 'lint.py') - cmd = [sys.executable, lintPath, '-f', 'parseable', '-r', 'n', + cmd = [sys.executable, lintPath, '--msg-template', '{path}:{line}: [{symbol}, {obj}] {msg}', '-r', 'n', '--disable=C,R,I', childPath] - process = Popen(cmd, stdout=PIPE, stderr=PIPE, cwd=parentPath) + process = Popen(cmd, stdout=PIPE, cwd=parentPath, universal_newlines=True) # The parseable line format is '%(path)s:%(line)s: [%(sigle)s%(obj)s] %(msg)s' # NOTE: This would be cleaner if we added an Emacs reporter to pylint.reporters.text .. @@ -150,7 +150,7 @@ def py_run(command_options='', return_std=False, stdout=None, stderr=None, else: stderr = sys.stderr # Call pylint in a subprocess - p = Popen(command_line, shell=True, stdout=stdout, stderr=stderr) + p = Popen(command_line, shell=True, stdout=stdout, stderr=stderr, universal_newlines=True) p.wait() # Return standart output and error if return_std: diff --git a/examples/custom.py b/examples/custom.py index 7e4d5e76b..2537bade7 100644 --- a/examples/custom.py +++ b/examples/custom.py @@ -1,14 +1,14 @@ -from logilab import astng +import astroid -from pylint.interfaces import IASTNGChecker +from pylint.interfaces import IAstroidChecker from pylint.checkers import BaseChecker -class MyASTNGChecker(BaseChecker): +class MyAstroidChecker(BaseChecker): """add member attributes defined using my own "properties" function to the class locals dictionary """ - __implements__ = IASTNGChecker + __implements__ = IAstroidChecker name = 'custom' msgs = {} @@ -19,9 +19,9 @@ class MyASTNGChecker(BaseChecker): def visit_callfunc(self, node): """called when a CallFunc node is encountered. - See logilab.astng for the description of available nodes.""" - if not (isinstance(node.func, astng.Getattr) - and isinstance(node.func.expr, astng.Name) + See astroid for the description of available nodes.""" + if not (isinstance(node.func, astroid.Getattr) + and isinstance(node.func.expr, astroid.Name) and node.func.expr.name == 'properties' and node.func.attrname == 'create'): return @@ -32,5 +32,5 @@ class MyASTNGChecker(BaseChecker): def register(linter): """required method to auto register this checker""" - linter.register_checker(MyASTNGChecker(linter)) + linter.register_checker(MyAstroidChecker(linter)) diff --git a/examples/pylintrc b/examples/pylintrc index 5284fab53..d560f9e60 100644 --- a/examples/pylintrc +++ b/examples/pylintrc @@ -86,6 +86,8 @@ max-module-lines=1000 # tab). indent-string=' ' +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )?<?https?://\S+>?$ [BASIC] @@ -1,4 +1,4 @@ -# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -23,7 +23,7 @@ from threading import Thread from Tkinter import (Tk, Frame, Listbox, Entry, Label, Button, Scrollbar, Checkbutton, Radiobutton, IntVar, StringVar) from Tkinter import (TOP, LEFT, RIGHT, BOTTOM, END, X, Y, BOTH, SUNKEN, W, - HORIZONTAL, DISABLED, NORMAL, W, E) + HORIZONTAL, DISABLED, NORMAL, W) from tkFileDialog import askopenfilename, askdirectory import pylint.lint @@ -86,7 +86,7 @@ class BasicStream(object): """finalize what the contents of the dict should look like before output""" for item in self.outdict: numEmpty = self.outdict[item].count('') - for i in xrange(numEmpty): + for _ in xrange(numEmpty): self.outdict[item].remove('') if self.outdict[item]: self.outdict[item].pop(0) diff --git a/interfaces.py b/interfaces.py index a24e36f3c..e0754ce05 100644 --- a/interfaces.py +++ b/interfaces.py @@ -10,13 +10,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. -""" Copyright (c) 2002-2003 LOGILAB S.A. (Paris, FRANCE). - http://www.logilab.fr/ -- mailto:contact@logilab.fr - -Interfaces for PyLint objects -""" - -__revision__ = "$Id: interfaces.py,v 1.9 2004-04-24 12:14:53 syt Exp $" +"""Interfaces for PyLint objects""" from logilab.common.interface import Interface @@ -32,21 +26,15 @@ class IChecker(Interface): def close(self): """called after visiting project (i.e set of modules)""" -## def open_module(self): -## """called before visiting a module""" - -## def close_module(self): -## """called after visiting a module""" - class IRawChecker(IChecker): """interface for checker which need to parse the raw file """ - def process_module(self, astng): + def process_module(self, astroid): """ process a module - the module's content is accessible via astng.file_stream + the module's content is accessible via astroid.file_stream """ @@ -54,40 +42,17 @@ class ITokenChecker(IChecker): """Interface for checkers that need access to the token list.""" def process_tokens(self, tokens): """Process a module. - + tokens is a list of all source code tokens in the file. """ -class IASTNGChecker(IChecker): +class IAstroidChecker(IChecker): """ interface for checker which prefers receive events according to statement type """ -class ILinter(Interface): - """interface for the linter class - - the linter class will generate events to its registered checkers. - Each checker may interact with the linter instance using this API - """ - - def register_checker(self, checker): - """register a new checker class - - checker is a class implementing IrawChecker or / and IASTNGChecker - """ - - def add_message(self, msg_id, line=None, node=None, args=None): - """add the message corresponding to the given id. - - If provided, msg is expanded using args - - astng checkers should provide the node argument, - raw checkers should provide the line argument. - """ - - class IReporter(Interface): """ reporter collect messages and display results encapsulated in a layout """ @@ -104,4 +69,4 @@ class IReporter(Interface): """ -__all__ = ('IRawChecker', 'ILinter', 'IReporter') +__all__ = ('IRawChecker', 'IAstroidChecker', 'ITokenChecker', 'IReporter') @@ -31,7 +31,6 @@ from pylint.checkers import utils import sys import os -import re import tokenize from warnings import warn @@ -43,30 +42,24 @@ from logilab.common.textutils import splitstrip from logilab.common.ureports import Table, Text, Section from logilab.common.__pkginfo__ import version as common_version -from logilab.astng import MANAGER, nodes, ASTNGBuildingException -from logilab.astng.__pkginfo__ import version as astng_version - -from pylint.utils import (PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn, - ReportsHandlerMixIn, MSG_TYPES, expand_modules, - WarningScope, tokenize_module) -from pylint.interfaces import ILinter, IRawChecker, ITokenChecker, IASTNGChecker -from pylint.checkers import (BaseTokenChecker, EmptyReport, - table_lines_from_stats) -from pylint.reporters.text import (TextReporter, ParseableTextReporter, - VSTextReporter, ColorizedTextReporter) -from pylint.reporters.html import HTMLReporter +from astroid import MANAGER, nodes, AstroidBuildingException +from astroid.__pkginfo__ import version as astroid_version + +from pylint.utils import ( + MSG_TYPES, OPTION_RGX, + PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn, ReportsHandlerMixIn, + EmptyReport, WarningScope, + expand_modules, tokenize_module) +from pylint.interfaces import IRawChecker, ITokenChecker, IAstroidChecker +from pylint.checkers import (BaseTokenChecker, + table_lines_from_stats, + initialize as checkers_initialize) +from pylint.reporters import initialize as reporters_initialize from pylint import config from pylint.__pkginfo__ import version -OPTION_RGX = re.compile(r'\s*#.*\bpylint:(.*)') -REPORTER_OPT_MAP = {'text': TextReporter, - 'parseable': ParseableTextReporter, - 'msvs': VSTextReporter, - 'colorized': ColorizedTextReporter, - 'html': HTMLReporter,} - def _get_python_path(filepath): dirname = os.path.dirname(os.path.realpath( @@ -88,8 +81,8 @@ MSGS = { 'Used when an error occurred preventing the analysis of a \ module (unable to find it for instance).'), 'F0002': ('%s: %s', - 'astng-error', - 'Used when an unexpected error occurred while building the ASTNG \ + 'astroid-error', + 'Used when an unexpected error occurred while building the Astroid \ representation. This is usually accompanied by a traceback. \ Please report such errors !'), 'F0003': ('ignored builtin module %s', @@ -102,8 +95,8 @@ MSGS = { inferred.'), 'F0010': ('error while code parsing: %s', 'parse-error', - 'Used when an exception occured while building the ASTNG \ - representation which could be handled by astng.'), + 'Used when an exception occured while building the Astroid \ + representation which could be handled by astroid.'), 'I0001': ('Unable to run raw checkers on built-in module %s', 'raw-checker-failed', @@ -161,17 +154,17 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, """lint Python modules using external checkers. This is the main checker controlling the other ones and the reports - generation. It is itself both a raw checker and an astng checker in order + generation. It is itself both a raw checker and an astroid checker in order to: * handle message activation / deactivation at the module level * handle some basic but necessary stats'data (number of classes, methods...) IDE plugins developpers: you may have to call - `logilab.astng.builder.MANAGER.astng_cache.clear()` accross run if you want + `astroid.builder.MANAGER.astroid_cache.clear()` accross run if you want to ensure the latest code version is actually checked. """ - __implements__ = (ILinter, ITokenChecker) + __implements__ = (ITokenChecker,) name = 'master' priority = 0 @@ -206,18 +199,6 @@ python modules names) to load, usually to register additional checkers.'}), 'can also give a reporter class, eg mypackage.mymodule.' 'MyReporterClass.'}), - ('include-ids', - {'type' : 'yn', 'metavar' : '<y_or_n>', 'default' : 0, - 'short': 'i', - 'group': 'Reports', - 'help' : 'Include message\'s id in output'}), - - ('symbols', - {'type' : 'yn', 'metavar' : '<y_or_n>', 'default' : 0, - 'short': 's', - 'group': 'Reports', - 'help' : 'Include symbolic ids of messages in output'}), - ('files-output', {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', 'group': 'Reports', 'level': 1, @@ -274,6 +255,16 @@ This is used by the global evaluation report (RP0004).'}), 'If you want to run only the classes checker, but have no ' 'Warning level messages displayed, use' '"--disable=all --enable=classes --disable=W"'}), + + ('msg-template', + {'type' : 'string', 'metavar': '<template>', + #'short': 't', + 'group': 'Reports', + 'help' : ('Template used to display messages. ' + 'This is a python new-style format string ' + 'used to format the massage information. ' + 'See doc for all details') + }), # msg-template ) option_groups = ( @@ -281,12 +272,13 @@ This is used by the global evaluation report (RP0004).'}), ('Reports', 'Options related to output formating and reporting'), ) - def __init__(self, options=(), reporter=None, option_groups=(), - pylintrc=None): + def __init__(self, options=(), reporter=None, option_groups=(), pylintrc=None): # some stuff has to be done before ancestors initialization... # - # checkers / reporter / astng manager + # checkers / reporter / astroid manager self.reporter = None + self._reporter_name = None + self._reporters = {} self._checkers = {} self._ignore_file = False # visit variables @@ -303,8 +295,8 @@ This is used by the global evaluation report (RP0004).'}), 'disable': self.disable} self._bw_options_methods = {'disable-msg': self.disable, 'enable-msg': self.enable} - full_version = '%%prog %s, \nastng %s, common %s\nPython %s' % ( - version, astng_version, common_version, sys.version) + full_version = '%%prog %s, \nastroid %s, common %s\nPython %s' % ( + version, astroid_version, common_version, sys.version) OptionsManagerMixIn.__init__(self, usage=__doc__, version=full_version, config_file=pylintrc or config.PYLINTRC) @@ -324,11 +316,16 @@ This is used by the global evaluation report (RP0004).'}), self.register_checker(self) self._dynamic_plugins = set() self.load_provider_defaults() - self.set_reporter(reporter or TextReporter(sys.stdout)) + if reporter: + self.set_reporter(reporter) def load_default_plugins(self): - from pylint import checkers - checkers.initialize(self) + checkers_initialize(self) + reporters_initialize(self) + # Make sure to load the default reporter, because + # the option has been set before the plugins had been loaded. + if not self.reporter: + self._load_reporter() def prepare_import_path(self, args): """Prepare sys.path for running the linter checks.""" @@ -352,6 +349,17 @@ This is used by the global evaluation report (RP0004).'}), module = load_module_from_name(modname) module.register(self) + def _load_reporter(self): + name = self._reporter_name.lower() + if name in self._reporters: + self.set_reporter(self._reporters[name]()) + else: + qname = self._reporter_name + module = load_module_from_name(get_module_part(qname)) + class_name = qname.split('.')[-1] + reporter_class = getattr(module, class_name) + self.set_reporter(reporter_class()) + def set_reporter(self, reporter): """set the reporter used to display messages and reports""" self.reporter = reporter @@ -376,26 +384,26 @@ This is used by the global evaluation report (RP0004).'}), else : meth(value) elif optname == 'output-format': - if value.lower() in REPORTER_OPT_MAP: - self.set_reporter(REPORTER_OPT_MAP[value.lower()]()) - else: - module = load_module_from_name(get_module_part(value)) - class_name = value.split('.')[-1] - reporter_class = getattr(module, class_name) - self.set_reporter(reporter_class()) - + self._reporter_name = value + # If the reporters are already available, load + # the reporter class. + if self._reporters: + self._load_reporter() try: BaseTokenChecker.set_option(self, optname, value, action, optdict) except UnsupportedAction: print >> sys.stderr, 'option %s can\'t be read from config file' % \ optname + def register_reporter(self, reporter_class): + self._reporters[reporter_class.name] = reporter_class + # checkers manipulation methods ############################################ def register_checker(self, checker): """register a new checker - checker is an object implementing IRawChecker or / and IASTNGChecker + checker is an object implementing IRawChecker or / and IAstroidChecker """ assert checker.priority <= 0, 'checker priority can\'t be >= 0' self._checkers.setdefault(checker.name, []).append(checker) @@ -559,8 +567,6 @@ This is used by the global evaluation report (RP0004).'}), """main checking entry: check a list of files or modules from their name. """ - self.reporter.include_ids = self.config.include_ids - self.reporter.symbols = self.config.symbols if not isinstance(files_or_modules, (list, tuple)): files_or_modules = (files_or_modules,) walker = PyLintASTWalker(self) @@ -571,7 +577,7 @@ This is used by the global evaluation report (RP0004).'}), # notify global begin for checker in checkers: checker.open() - if implements(checker, IASTNGChecker): + if implements(checker, IAstroidChecker): walker.add_checker(checker) # build ast and check modules or packages for descr in self.expand_files(files_or_modules): @@ -581,16 +587,16 @@ This is used by the global evaluation report (RP0004).'}), self.reporter.set_output(open(reportfile, 'w')) self.set_current_module(modname, filepath) # get the module representation - astng = self.get_astng(filepath, modname) - if astng is None: + astroid = self.get_astroid(filepath, modname) + if astroid is None: continue self.base_name = descr['basename'] self.base_file = descr['basepath'] self._ignore_file = False # fix the current file (if the source file was not available or # if it's actually a c extension) - self.current_file = astng.file - self.check_astng_module(astng, walker, rawcheckers, tokencheckers) + self.current_file = astroid.file + self.check_astroid_module(astroid, walker, rawcheckers, tokencheckers) self._add_suppression_messages() # notify global end self.set_current_module('') @@ -633,28 +639,32 @@ This is used by the global evaluation report (RP0004).'}), self._raw_module_msgs_state = {} self._ignored_msgs = {} - def get_astng(self, filepath, modname): - """return a astng representation for a module""" + def get_astroid(self, filepath, modname): + """return a astroid representation for a module""" try: - return MANAGER.astng_from_file(filepath, modname, source=True) + return MANAGER.ast_from_file(filepath, modname, source=True) except SyntaxError, ex: self.add_message('E0001', line=ex.lineno, args=ex.msg) - except ASTNGBuildingException, ex: + except AstroidBuildingException, ex: self.add_message('F0010', args=ex) except Exception, ex: import traceback traceback.print_exc() self.add_message('F0002', args=(ex.__class__, ex)) - def check_astng_module(self, astng, walker, rawcheckers, tokencheckers): - """check a module from its astng representation, real work""" + def check_astroid_module(self, astroid, walker, rawcheckers, tokencheckers): + """check a module from its astroid representation, real work""" # call raw checkers if possible - tokens = tokenize_module(astng) + try: + tokens = tokenize_module(astroid) + except tokenize.TokenError, ex: + self.add_message('E0001', line=ex.args[1][0], args=ex.args[0]) + return - if not astng.pure_python: - self.add_message('I0001', args=astng.name) + if not astroid.pure_python: + self.add_message('I0001', args=astroid.name) else: - #assert astng.file.endswith('.py') + #assert astroid.file.endswith('.py') # invoke ITokenChecker interface on self to fetch module/block # level options self.process_tokens(tokens) @@ -666,16 +676,16 @@ This is used by the global evaluation report (RP0004).'}), orig_state = self._module_msgs_state.copy() self._module_msgs_state = {} self._suppression_mapping = {} - self.collect_block_lines(astng, orig_state) + self.collect_block_lines(astroid, orig_state) for checker in rawcheckers: - checker.process_module(astng) + checker.process_module(astroid) for checker in tokencheckers: checker.process_tokens(tokens) - # generate events to astng checkers - walker.walk(astng) + # generate events to astroid checkers + walker.walk(astroid) return True - # IASTNGChecker interface ################################################# + # IAstroidChecker interface ################################################# def open(self): """initialize counters""" @@ -925,8 +935,7 @@ are done by default'''}), 'default': False, 'hide': True, 'help' : 'Profiled execution.'}), - ), option_groups=self.option_groups, - reporter=reporter, pylintrc=self._rcfile) + ), option_groups=self.option_groups, pylintrc=self._rcfile) # register standard checkers linter.load_default_plugins() # load command line plugins diff --git a/pyreverse/diadefslib.py b/pyreverse/diadefslib.py index 68ca68ca4..da21e1f15 100644 --- a/pyreverse/diadefslib.py +++ b/pyreverse/diadefslib.py @@ -1,4 +1,4 @@ -# Copyright (c) 2000-2010 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2000-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -17,17 +17,19 @@ """ from logilab.common.compat import builtins -BUILTINS_NAME = builtins.__name__ -from logilab import astng -from logilab.astng.utils import LocalsVisitor + +import astroid +from astroid.utils import LocalsVisitor from pylint.pyreverse.diagrams import PackageDiagram, ClassDiagram +BUILTINS_NAME = builtins.__name__ + # diagram generators ########################################################## -class DiaDefGenerator: - """handle diagram generation options - """ +class DiaDefGenerator(object): + """handle diagram generation options""" + def __init__(self, linker, handler): """common Diagram Handler initialization""" self.config = handler.config @@ -100,9 +102,9 @@ class DiaDefGenerator: for ass_nodes in klass_node.instance_attrs_type.values() + \ klass_node.locals_type.values(): for ass_node in ass_nodes: - if isinstance(ass_node, astng.Instance): + if isinstance(ass_node, astroid.Instance): ass_node = ass_node._proxied - if not (isinstance(ass_node, astng.Class) + if not (isinstance(ass_node, astroid.Class) and self.show_node(ass_node)): continue yield ass_node @@ -132,7 +134,7 @@ class DefaultDiadefGenerator(LocalsVisitor, DiaDefGenerator): LocalsVisitor.__init__(self) def visit_project(self, node): - """visit an astng.Project node + """visit an astroid.Project node create a diagram definition for packages """ @@ -144,7 +146,7 @@ class DefaultDiadefGenerator(LocalsVisitor, DiaDefGenerator): self.classdiagram = ClassDiagram('classes %s' % node.name, mode) def leave_project(self, node): - """leave the astng.Project node + """leave the astroid.Project node return the generated diagram definition """ @@ -153,7 +155,7 @@ class DefaultDiadefGenerator(LocalsVisitor, DiaDefGenerator): return self.classdiagram, def visit_module(self, node): - """visit an astng.Module node + """visit an astroid.Module node add this class to the package diagram definition """ @@ -162,7 +164,7 @@ class DefaultDiadefGenerator(LocalsVisitor, DiaDefGenerator): self.pkgdiagram.add_object(node.name, node) def visit_class(self, node): - """visit an astng.Class node + """visit an astroid.Class node add this class to the class diagram definition """ @@ -170,7 +172,7 @@ class DefaultDiadefGenerator(LocalsVisitor, DiaDefGenerator): self.extract_classes(node, anc_level, ass_level) def visit_from(self, node): - """visit astng.From and catch modules for package diagram + """visit astroid.From and catch modules for package diagram """ if self.pkgdiagram: self.pkgdiagram.add_from_depend(node, node.modname) @@ -204,7 +206,7 @@ class ClassDiadefGenerator(DiaDefGenerator): # diagram handler ############################################################# -class DiadefsHandler: +class DiadefsHandler(object): """handle diagram definitions : get it from user (i.e. xml files) or generate them @@ -215,8 +217,8 @@ class DiadefsHandler: def get_diadefs(self, project, linker): """get the diagrams configuration data - :param linker: astng.inspector.Linker(IdGeneratorMixIn, LocalsVisitor) - :param project: astng.manager.Project + :param linker: astroid.inspector.Linker(IdGeneratorMixIn, LocalsVisitor) + :param project: astroid.manager.Project """ # read and interpret diagram definitions (Diadefs) diff --git a/pyreverse/diagrams.py b/pyreverse/diagrams.py index b411bd04c..7cb5930e8 100644 --- a/pyreverse/diagrams.py +++ b/pyreverse/diagrams.py @@ -16,7 +16,7 @@ """diagram objects """ -from logilab import astng +import astroid from pylint.pyreverse.utils import is_interface, FilterMixIn def set_counter(value): @@ -42,7 +42,7 @@ class Relationship(Figure): class DiagramEntity(Figure): - """a diagram object, i.e. a label associated to an astng node + """a diagram object, i.e. a label associated to an astroid node """ def __init__(self, title='No name', node=None): Figure.__init__(self) @@ -93,7 +93,7 @@ class ClassDiagram(Figure, FilterMixIn): def get_methods(self, node): """return visible methods""" return [m for m in node.values() - if isinstance(m, astng.Function) and self.show_attr(m.name)] + if isinstance(m, astroid.Function) and self.show_attr(m.name)] def add_object(self, title, node): """create a diagram object @@ -107,9 +107,9 @@ class ClassDiagram(Figure, FilterMixIn): """return class names if needed in diagram""" names = [] for ass_node in nodes: - if isinstance(ass_node, astng.Instance): + if isinstance(ass_node, astroid.Instance): ass_node = ass_node._proxied - if isinstance(ass_node, astng.Class) \ + if isinstance(ass_node, astroid.Class) \ and hasattr(ass_node, "name") and not self.has_node(ass_node): if ass_node.name not in names: ass_name = ass_node.name @@ -133,7 +133,7 @@ class ClassDiagram(Figure, FilterMixIn): def classes(self): """return all class nodes in the diagram""" - return [o for o in self.objects if isinstance(o.node, astng.Class)] + return [o for o in self.objects if isinstance(o.node, astroid.Class)] def classe(self, name): """return a class by its name, raise KeyError if not found @@ -173,9 +173,9 @@ class ClassDiagram(Figure, FilterMixIn): for name, values in node.instance_attrs_type.items() + \ node.locals_type.items(): for value in values: - if value is astng.YES: + if value is astroid.YES: continue - if isinstance( value, astng.Instance): + if isinstance( value, astroid.Instance): value = value._proxied try: ass_obj = self.object_from_node(value) @@ -191,7 +191,7 @@ class PackageDiagram(ClassDiagram): def modules(self): """return all module nodes in the diagram""" - return [o for o in self.objects if isinstance(o.node, astng.Module)] + return [o for o in self.objects if isinstance(o.node, astroid.Module)] def module(self, name): """return a module by its name, raise KeyError if not found diff --git a/pyreverse/main.py b/pyreverse/main.py index 4e4458c46..cdd6607dc 100644 --- a/pyreverse/main.py +++ b/pyreverse/main.py @@ -21,8 +21,8 @@ import sys, os from logilab.common.configuration import ConfigurationMixIn -from logilab.astng.manager import ASTNGManager -from logilab.astng.inspector import Linker +from astroid.manager import AstroidManager +from astroid.inspector import Linker from pylint.pyreverse.diadefslib import DiadefsHandler from pylint.pyreverse import writer @@ -92,7 +92,7 @@ class Run(ConfigurationMixIn): def __init__(self, args): ConfigurationMixIn.__init__(self, usage=__doc__) insert_default_options() - self.manager = ASTNGManager() + self.manager = AstroidManager() self.register_options_provider(self.manager) args = self.load_command_line_configuration() sys.exit(self.run(args)) diff --git a/pyreverse/utils.py b/pyreverse/utils.py index ea8b67ccd..976f70459 100644 --- a/pyreverse/utils.py +++ b/pyreverse/utils.py @@ -1,4 +1,4 @@ -# Copyright (c) 2002-2010 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2002-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -50,7 +50,7 @@ def insert_default_options(): -# astng utilities ########################################################### +# astroid utilities ########################################################### SPECIAL = re.compile('^__[A-Za-z0-9]+[A-Za-z0-9_]*__$') PRIVATE = re.compile('^__[_A-Za-z0-9]*[A-Za-z0-9]+_?$') @@ -109,7 +109,7 @@ MODES = { VIS_MOD = {'special': _SPECIAL, 'protected': _PROTECTED, \ 'private': _PRIVATE, 'public': 0 } -class FilterMixIn: +class FilterMixIn(object): """filter nodes according to a mode and nodes' visibility """ def __init__(self, mode): diff --git a/pyreverse/writer.py b/pyreverse/writer.py index 6dbfc2658..dd0085025 100644 --- a/pyreverse/writer.py +++ b/pyreverse/writer.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2008-2010 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2008-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -14,16 +14,14 @@ # 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. -""" -Utilities for creating VCG and Dot diagrams. -""" +"""Utilities for creating VCG and Dot diagrams""" from logilab.common.vcgutils import VCGPrinter from logilab.common.graph import DotBackend from pylint.pyreverse.utils import is_exception -class DiagramWriter: +class DiagramWriter(object): """base class for writing project diagrams """ def __init__(self, config, styles): @@ -93,8 +91,8 @@ class DotWriter(DiagramWriter): """ def __init__(self, config): - styles = [dict(arrowtail='none', arrowhead="open"), - dict(arrowtail = "none", arrowhead='empty'), + styles = [dict(arrowtail='none', arrowhead="open"), + dict(arrowtail = "none", arrowhead='empty'), dict(arrowtail="node", arrowhead='empty', style='dashed'), dict(fontcolor='green', arrowtail='none', arrowhead='diamond', style='solid') ] @@ -113,14 +111,14 @@ class DotWriter(DiagramWriter): def get_values(self, obj): """get label and shape for classes. - + The label contains all attributes and methods """ label = obj.title if obj.shape == 'interface': - label = "«interface»\\n%s" % label + label = u"«interface»\\n%s" % label if not self.config.only_classnames: - label = "%s|%s\l|" % (label, r"\l".join(obj.attrs) ) + label = r"%s|%s\l|" % (label, r"\l".join(obj.attrs) ) for func in obj.methods: label = r'%s%s()\l' % (label, func.name) label = '{%s}' % label @@ -139,7 +137,7 @@ class VCGWriter(DiagramWriter): def __init__(self, config): styles = [dict(arrowstyle='solid', backarrowstyle='none', backarrowsize=0), - dict(arrowstyle='solid', backarrowstyle='none', + dict(arrowstyle='solid', backarrowstyle='none', backarrowsize=10), dict(arrowstyle='solid', backarrowstyle='none', linestyle='dotted', backarrowsize=10), @@ -163,7 +161,7 @@ class VCGWriter(DiagramWriter): def get_values(self, obj): """get label and shape for classes. - + The label contains all attributes and methods """ if is_exception(obj.node): diff --git a/reporters/__init__.py b/reporters/__init__.py index fa09c1ad7..53064c736 100644 --- a/reporters/__init__.py +++ b/reporters/__init__.py @@ -1,4 +1,3 @@ -# Copyright (c) 2003-2010 Sylvain Thenault (thenault@gmail.com). # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software @@ -14,7 +13,13 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """utilities methods and classes for reporters""" -import sys, locale +import sys +import locale +import os + +from pylint.utils import MSG_TYPES + +from pylint import utils CMPS = ['=', '-', '+'] @@ -32,10 +37,28 @@ def diff_string(old, new): return diff_str -class EmptyReport(Exception): - """raised when a report is empty and so should not be displayed""" +class Message(object): + """This class represent a message to be issued by the reporters""" + + def __init__(self, reporter, msg_id, location, msg): + self.msg_id = msg_id + self.abspath, self.module, self.obj, self.line, self.column = location + self.path = self.abspath.replace(reporter.path_strip_prefix, '') + self.msg = msg + self.C = msg_id[0] + self.category = MSG_TYPES[msg_id[0]] + self.symbol = reporter.linter.check_message_id(msg_id).symbol -class BaseReporter: + def format(self, template): + """Format the message according to the given template. + + The template format is the one of the format method : + cf. http://docs.python.org/2/library/string.html#formatstrings + """ + return template.format(**(self.__dict__)) + + +class BaseReporter(object): """base class for reporters symbols: show short symbolic names for messages. @@ -45,28 +68,20 @@ class BaseReporter: def __init__(self, output=None): self.linter = None - self.include_ids = None - self.symbols = None + # self.include_ids = None # Deprecated + # self.symbols = None # Deprecated self.section = 0 self.out = None self.out_encoding = None + self.encode = None self.set_output(output) + # Build the path prefix to strip to get relative paths + self.path_strip_prefix = os.getcwd() + os.sep - def make_sigle(self, msg_id): - """generate a short prefix for a message. - - The sigle can include the id, the symbol, or both, or it can just be - the message class. - """ - if self.include_ids: - sigle = msg_id - else: - sigle = msg_id[0] - if self.symbols: - symbol = self.linter.check_message_id(msg_id).symbol - if symbol: - sigle += '(%s)' % symbol - return sigle + def add_message(self, msg_id, location, msg): + """Client API to send a message""" + # Shall we store the message objects somewhere, do some validity checking ? + raise NotImplementedError def set_output(self, output=None): """set output stream""" @@ -95,7 +110,7 @@ class BaseReporter: def display_results(self, layout): """display results encapsulated in the layout tree""" self.section = 0 - if self.include_ids and hasattr(layout, 'report_id'): + if hasattr(layout, 'report_id'): layout.children[0].children[0].data += ' (%s)' % layout.report_id self._display(layout) @@ -114,3 +129,6 @@ class BaseReporter: pass +def initialize(linter): + """initialize linter with reporters in this package """ + utils.register_plugins(linter, __path__[0]) diff --git a/reporters/guireporter.py b/reporters/guireporter.py index 83cc049ce..9f3ae79aa 100644 --- a/reporters/guireporter.py +++ b/reporters/guireporter.py @@ -22,8 +22,8 @@ class GUIReporter(BaseReporter): def add_message(self, msg_id, location, msg): """manage message of different type and in the context of path""" filename, module, obj, line, col_offset = location - sigle = self.make_sigle(msg_id) - full_msg = [sigle, msg_id, filename, module, obj, str(line), msg] + msg = Message(self, msg_id, location, msg) + full_msg = [msg.C, msg_id, filename, module, obj, str(line), msg] self.msgs += [[sigle, module, obj, str(line)]] self.gui.msg_queue.put(full_msg) diff --git a/reporters/html.py b/reporters/html.py index cac08b28e..a51e0e7bb 100644 --- a/reporters/html.py +++ b/reporters/html.py @@ -1,5 +1,4 @@ -# Copyright (c) 2003-2006 Sylvain Thenault (thenault@gmail.com). -# Copyright (c) 2003-2011 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later @@ -20,13 +19,14 @@ from cgi import escape from logilab.common.ureports import HTMLWriter, Section, Table from pylint.interfaces import IReporter -from pylint.reporters import BaseReporter +from pylint.reporters import BaseReporter, Message class HTMLReporter(BaseReporter): """report messages and layouts in HTML""" __implements__ = IReporter + name = 'html' extension = 'html' def __init__(self, output=sys.stdout): @@ -35,9 +35,9 @@ class HTMLReporter(BaseReporter): def add_message(self, msg_id, location, msg): """manage message of different type and in the context of path""" - module, obj, line, col_offset = location[1:] - sigle = self.make_sigle(msg_id) - self.msgs += [sigle, module, obj, str(line), str(col_offset), escape(msg)] + msg = Message(self, msg_id, location, msg) + self.msgs += (msg.category, msg.module, msg.obj, + str(msg.line), str(msg.column), escape(msg.msg)) def set_output(self, output=None): """set output stream @@ -64,3 +64,7 @@ class HTMLReporter(BaseReporter): self.msgs = [] HTMLWriter().format(layout, self.out) + +def register(linter): + """Register the reporter classes with the linter.""" + linter.register_reporter(HTMLReporter) diff --git a/reporters/text.py b/reporters/text.py index d5b4a9bf1..555efc805 100644 --- a/reporters/text.py +++ b/reporters/text.py @@ -14,48 +14,50 @@ """Plain text reporters: :text: the default one grouping messages by module -:parseable: - standard parseable output with full module path on each message (for - editor integration) :colorized: an ANSI colorized text reporter - """ -import os +import warnings from logilab.common.ureports import TextWriter from logilab.common.textutils import colorize_ansi from pylint.interfaces import IReporter -from pylint.reporters import BaseReporter +from pylint.reporters import BaseReporter, Message TITLE_UNDERLINES = ['', '=', '-', '.'] class TextReporter(BaseReporter): - """reports messages and layouts in plain text - """ + """reports messages and layouts in plain text""" __implements__ = IReporter + name = 'text' extension = 'txt' + line_format = '{C}:{line:3d},{column:2d}: {msg} ({symbol})' def __init__(self, output=None): BaseReporter.__init__(self, output) self._modules = {} + self._template = None + + def on_set_current_module(self, module, filepath): + self._template = unicode(self.linter.config.msg_template or self.line_format) + + def write_message(self, msg): + """Convenience method to write a formated message with class default template""" + self.writeln(msg.format(self._template)) def add_message(self, msg_id, location, msg): """manage message of different type and in the context of path""" - module, obj, line, col_offset = location[1:] - if module not in self._modules: - if module: - self.writeln('************* Module %s' % module) - self._modules[module] = 1 + m = Message(self, msg_id, location, msg) + if m.module not in self._modules: + if m.module: + self.writeln('************* Module %s' % m.module) + self._modules[m.module] = 1 else: - self.writeln('************* %s' % module) - if obj: - obj = ':%s' % obj - sigle = self.make_sigle(msg_id) - self.writeln('%s:%3s,%s%s: %s' % (sigle, line, col_offset, obj, msg)) + self.writeln('************* ') + self.write_message(m) def _display(self, layout): """launch layouts display""" @@ -69,33 +71,25 @@ class ParseableTextReporter(TextReporter): <filename>:<linenum>:<msg> """ - line_format = '%(path)s:%(line)s: [%(sigle)s%(obj)s] %(msg)s' + name = 'parseable' + line_format = '{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}' - def __init__(self, output=None, relative=True): + def __init__(self, output=None): + warnings.warn('%s output format is deprecated. This is equivalent to --msg-template=%s' + % (self.name, self.line_format)) TextReporter.__init__(self, output) - if relative: - self._prefix = os.getcwd() + os.sep - else: - self._prefix = '' - - def add_message(self, msg_id, location, msg): - """manage message of different type and in the context of path""" - path, _, obj, line, _ = location - if obj: - obj = ', %s' % obj - sigle = self.make_sigle(msg_id) - if self._prefix: - path = path.replace(self._prefix, '') - self.writeln(self.line_format % locals()) class VSTextReporter(ParseableTextReporter): """Visual studio text reporter""" - line_format = '%(path)s(%(line)s): [%(sigle)s%(obj)s] %(msg)s' + name = 'msvs' + line_format = '{path}({line}): [{msg_id}({symbol}){obj}] {msg}' + class ColorizedTextReporter(TextReporter): """Simple TextReporter that colorizes text output""" + name = 'colorized' COLOR_MAPPING = { "I" : ("green", None), 'C' : (None, "bold"), @@ -111,7 +105,6 @@ class ColorizedTextReporter(TextReporter): self.color_mapping = color_mapping or \ dict(ColorizedTextReporter.COLOR_MAPPING) - def _get_decoration(self, msg_id): """Returns the tuple color, style associated with msg_id as defined in self.color_mapping @@ -125,21 +118,26 @@ class ColorizedTextReporter(TextReporter): """manage message of different types, and colorize output using ansi escape codes """ - module, obj, line, _ = location[1:] - if module not in self._modules: + msg = Message(self, msg_id, location, msg) + if msg.module not in self._modules: color, style = self._get_decoration('S') - if module: - modsep = colorize_ansi('************* Module %s' % module, + if msg.module: + modsep = colorize_ansi('************* Module %s' % msg.module, color, style) else: - modsep = colorize_ansi('************* %s' % module, + modsep = colorize_ansi('************* %s' % msg.module, color, style) self.writeln(modsep) - self._modules[module] = 1 - if obj: - obj = ':%s' % obj - sigle = self.make_sigle(msg_id) - color, style = self._get_decoration(sigle) - msg = colorize_ansi(msg, color, style) - sigle = colorize_ansi(sigle, color, style) - self.writeln('%s:%3s%s: %s' % (sigle, line, obj, msg)) + self._modules[msg.module] = 1 + color, style = self._get_decoration(msg.C) + for attr in ('msg', 'symbol', 'category', 'C'): + setattr(msg, attr, colorize_ansi(getattr(msg, attr), color, style)) + self.write_message(msg) + + +def register(linter): + """Register the reporter classes with the linter.""" + linter.register_reporter(TextReporter) + linter.register_reporter(ParseableTextReporter) + linter.register_reporter(VSTextReporter) + linter.register_reporter(ColorizedTextReporter) @@ -41,19 +41,6 @@ except ImportError: try: # python3 from distutils.command.build_py import build_py_2to3 as build_py - def run(self): - self.updated_files = [] - # Base class code - if self.py_modules: - self.build_modules() - if self.packages: - self.build_packages() - self.build_package_data() - # 2to3 - self.run_2to3(self.updated_files) - # Remaining base class code - self.byte_compile(self.get_outputs(include_bytecode=0)) - build_py.run = run except ImportError: # python2.x from distutils.command.build_py import build_py @@ -114,7 +101,7 @@ except ImportError: ''' class MyInstallLib(install_lib.install_lib): - """extend install_lib command to handle package __init__.py and + """extend install_lib command to handle package __init__.py and include_dirs variable if necessary """ def run(self): @@ -136,8 +123,19 @@ class MyInstallLib(install_lib.install_lib): base = modname for directory in include_dirs: dest = join(self.install_dir, base, directory) + if sys.version_info >= (3, 0): + exclude = set(('func_unknown_encoding.py', + 'func_invalid_encoded_data.py')) + else: + exclude = set() shutil.rmtree(dest, ignore_errors=True) - shutil.copytree(directory, dest) + shutil.copytree(directory, dest, ignore=lambda dir, names: list(set(names) & exclude)) + + if sys.version_info >= (3, 0): + # process manually python file in include_dirs (test data) + from subprocess import call + print('running 2to3 on', dest) + call(['2to3', '-wn', dest]) def install(**kwargs): diff --git a/test/input/func_attrs_definition_order.py b/test/input/func_attrs_definition_order.py index 2f12a79e2..3c65d736e 100644 --- a/test/input/func_attrs_definition_order.py +++ b/test/input/func_attrs_definition_order.py @@ -3,7 +3,7 @@ __revision__ = '$I$' -class Aaaa: +class Aaaa(object): """class with attributes defined in wrong order""" def __init__(self): var1 = self._var2 @@ -13,7 +13,7 @@ class Aaaa: class Bbbb(object): """hop""" __revision__ = __revision__ # no problemo marge - + def __getattr__(self, attr): # pylint: disable=W0201 try: @@ -22,7 +22,7 @@ class Bbbb(object): self.__repo = attr return attr - + def catchme(self, attr): """no AttributeError catched""" # pylint: disable=W0201 diff --git a/test/input/func_bad_open_mode.py b/test/input/func_bad_open_mode.py new file mode 100644 index 000000000..fefa3996a --- /dev/null +++ b/test/input/func_bad_open_mode.py @@ -0,0 +1,15 @@ +"""Warnings for using open() with an invalid mode string.""" + +__revision__ = 0 + +open('foo.bar', 'w', 2) +open('foo.bar', 'rw') +open(name='foo.bar', buffering=10, mode='rw') +open(mode='rw', name='foo.bar') +open('foo.bar', 'U+') +open('foo.bar', 'rb+') +open('foo.bar', 'Uw') +open('foo.bar', 2) +open('foo.bar', buffering=2) +WRITE_MODE = 'w' +open('foo.bar', 'U' + WRITE_MODE + 'z') diff --git a/test/input/func_block_disable_msg.py b/test/input/func_block_disable_msg.py index 1a43ede62..cce04a454 100644 --- a/test/input/func_block_disable_msg.py +++ b/test/input/func_block_disable_msg.py @@ -4,14 +4,14 @@ __revision__ = None class Foo(object): """block-disable test""" - + def __init__(self): pass def meth1(self, arg): """this issues a message""" print self - + def meth2(self, arg): """and this one not""" # pylint: disable=W0613 @@ -23,14 +23,14 @@ class Foo(object): # no error print self.bla # pylint: disable=E1101 # error - print self.blop + print self.blop def meth4(self): """test re-enabling""" # pylint: disable=E1101 # no error print self.bla - print self.blop + print self.blop # pylint: enable=E1101 # error print self.blip @@ -84,7 +84,7 @@ class Foo(object): # pylint: disable=E1101 # no error print self.bla - print self.blop + print self.blop def meth9(self): """test re-enabling right after a block with whitespace""" @@ -114,7 +114,7 @@ class ClassLevelMessage(object): """shouldn't display to much attributes/not enough methods messages """ # pylint: disable=R0902,R0903 - + def __init__(self): self.attr1 = 1 self.attr2 = 1 diff --git a/test/input/func_class_access_protected_members.py b/test/input/func_class_access_protected_members.py index 302085ff2..123c5efba 100644 --- a/test/input/func_class_access_protected_members.py +++ b/test/input/func_class_access_protected_members.py @@ -3,7 +3,7 @@ __revision__ = '' -class MyClass: +class MyClass(object): _cls_protected = 5 def __init__(self, other): diff --git a/test/input/func_class_members.py b/test/input/func_class_members.py index c1a70347c..ad147e500 100644 --- a/test/input/func_class_members.py +++ b/test/input/func_class_members.py @@ -3,9 +3,9 @@ __revision__ = '' -class MyClass: +class MyClass(object): """class docstring""" - + def __init__(self): """init""" self.correct = 1 @@ -18,10 +18,10 @@ class MyClass: self.nonexistent1.truc() self.nonexistent2[1] = 'hehe' -class XYZMixin: - """access to undefined members should be ignored in mixin classes by +class XYZMixin(object): + """access to undefined members should be ignored in mixin classes by default - """ + """ def __init__(self): print self.nonexistent diff --git a/test/input/func_defining-attr-methods_order.py b/test/input/func_defining-attr-methods_order.py index 017cb1eca..d64217dff 100644 --- a/test/input/func_defining-attr-methods_order.py +++ b/test/input/func_defining-attr-methods_order.py @@ -1,13 +1,13 @@ # pylint: disable=C0103 ''' Test that y is defined properly, z is not. - Default defining methods are __init__, + Default defining methods are __init__, __new__, and setUp. Order of methods should not matter. ''' __revision__ = '' -class A: +class A(object): ''' class A ''' def __init__(self): diff --git a/test/input/func_deprecated_lambda.py b/test/input/func_deprecated_lambda_py_30.py index 973bb320f..973bb320f 100644 --- a/test/input/func_deprecated_lambda.py +++ b/test/input/func_deprecated_lambda_py_30.py diff --git a/test/input/func_docstring.py b/test/input/func_docstring.py index 01cd9e7b0..c7930f297 100644 --- a/test/input/func_docstring.py +++ b/test/input/func_docstring.py @@ -14,7 +14,7 @@ def function3(value): """docstring""" print value -class AAAA: +class AAAA(object): # missing docstring ## class BBBB: diff --git a/test/input/func_e0101.py b/test/input/func_e0101.py index 0bc86e3e1..2bf5a286e 100644 --- a/test/input/func_e0101.py +++ b/test/input/func_e0101.py @@ -4,31 +4,31 @@ __revision__ = 'yo' -class MyClass: +class MyClass(object): """dummy class""" def __init__(self): return 1 -class MyClass2: +class MyClass2(object): """dummy class""" def __init__(self): return -class MyClass3: +class MyClass3(object): """dummy class""" def __init__(self): return None -class MyClass4: +class MyClass4(object): """dummy class""" def __init__(self): yield None -class MyClass5: +class MyClass5(object): """dummy class""" def __init__(self): diff --git a/test/input/func_e0203.py b/test/input/func_e0203.py index d51de0de3..d95a8dd39 100644 --- a/test/input/func_e0203.py +++ b/test/input/func_e0203.py @@ -4,7 +4,7 @@ __revision__ = 0 -class Abcd: +class Abcd(object): """dummy class""" def __init__(self): pass diff --git a/test/input/func_e0204.py b/test/input/func_e0204.py index ecbc51c39..26cbd5227 100644 --- a/test/input/func_e0204.py +++ b/test/input/func_e0204.py @@ -4,7 +4,7 @@ __revision__ = 0 -class Abcd: +class Abcd(object): """dummy class""" def __init__(truc): diff --git a/test/input/func_e0205.py b/test/input/func_e0205.py index 00e1c074f..34208f96b 100644 --- a/test/input/func_e0205.py +++ b/test/input/func_e0205.py @@ -4,7 +4,7 @@ __revision__ = '' -class Abcd: +class Abcd(object): """dummy""" def __init__(self): self.abcd = 1 diff --git a/test/input/func_e0206.py b/test/input/func_e0206.py index 9ec01cdba..a9f579082 100644 --- a/test/input/func_e0206.py +++ b/test/input/func_e0206.py @@ -3,14 +3,14 @@ __revision__ = None -class Abcd: +class Abcd(object): """dummy""" __implements__ = __revision__ - + def __init__(self): - self.attr = None + self.attr = None -class Cdef: +class Cdef(object): """dummy""" __implements__ = (__revision__, Abcd) diff --git a/test/input/func_excess_escapes.py b/test/input/func_excess_escapes.py index e3483d63c..178ace8e3 100644 --- a/test/input/func_excess_escapes.py +++ b/test/input/func_excess_escapes.py @@ -28,20 +28,3 @@ ESCAPE_UNICODE = "\\\\n" You shouldn't have ambiguous text like: C:\Program Files\alpha """ - -# Would be valid in Unicode, but probably not what you want otherwise -BAD_UNICODE = '\u0042' -BAD_LONG_UNICODE = '\U00000042' -BAD_NAMED_UNICODE = '\N{GREEK SMALL LETTER ALPHA}' - -GOOD_UNICODE = u'\u0042' -GOOD_LONG_UNICODE = u'\U00000042' -GOOD_NAMED_UNICODE = u'\N{GREEK SMALL LETTER ALPHA}' - - -# Valid raw strings -RAW_BACKSLASHES = r'raw' -RAW_UNICODE = ur"\u0062\n" - -# In a comment you can have whatever you want: \ \\ \n \m -# even things that look like bad strings: "C:\Program Files" diff --git a/test/input/func_exec_used_py30.py b/test/input/func_exec_used_py30.py new file mode 100644 index 000000000..dbcc0249d --- /dev/null +++ b/test/input/func_exec_used_py30.py @@ -0,0 +1,13 @@ +"""test global statement""" + +__revision__ = 0 + +exec('a = __revision__') +exec('a = 1', globals={}) + +exec('a = 1', globals=globals()) + +def func(): + """exec in local scope""" + exec('b = 1') + diff --git a/test/input/func_f0001.py b/test/input/func_f0001.py index af6de2409..b0e559a10 100644 --- a/test/input/func_f0001.py +++ b/test/input/func_f0001.py @@ -1,4 +1,4 @@ -"""test astng error +"""test astroid error """ import whatever __revision__ = None diff --git a/test/input/func_first_arg.py b/test/input/func_first_arg.py index d4c7a8abb..efff51da9 100644 --- a/test/input/func_first_arg.py +++ b/test/input/func_first_arg.py @@ -5,7 +5,7 @@ __revision__ = 0 -class Obj: +class Obj(object): # C0202, classmethod def __new__(something): pass diff --git a/test/input/func_fixme.py b/test/input/func_fixme.py index 0cfe2bd83..b6371f1ea 100644 --- a/test/input/func_fixme.py +++ b/test/input/func_fixme.py @@ -6,4 +6,4 @@ __revision__ = '' def function(): '''XXX:bop''' - + diff --git a/test/input/func_format.py b/test/input/func_format.py index 638d9420f..a4eed23cc 100644 --- a/test/input/func_format.py +++ b/test/input/func_format.py @@ -49,7 +49,7 @@ boo = 2 # allclose(x,y) uses |x-y|<ATOL+RTOL*|y| def other(funky): """yo, test formatted result with indentation""" funky= funky+2 - + html = """<option value="=">ist genau gleich</option> yo+=4 """ diff --git a/test/input/func_globals.py b/test/input/func_globals.py index a52eeea4a..cf5812879 100644 --- a/test/input/func_globals.py +++ b/test/input/func_globals.py @@ -36,5 +36,5 @@ other() def define_constant(): """ok but somevar is not defined at the module scope""" - global somevar - somevar = 2 + global SOMEVAR + SOMEVAR = 2 diff --git a/test/input/func_indent.py b/test/input/func_indent.py index 44eb42997..18159e90f 100644 --- a/test/input/func_indent.py +++ b/test/input/func_indent.py @@ -16,7 +16,7 @@ def tataa(kdict): """blank line unindented""" for key in ['1', '2', '3']: key = key.lower() - + if kdict.has_key(key): del kdict[key] diff --git a/test/input/func_init_vars.py b/test/input/func_init_vars.py index 9d8950526..db9447552 100644 --- a/test/input/func_init_vars.py +++ b/test/input/func_init_vars.py @@ -4,8 +4,8 @@ __revision__ = '' -class MyClass: - """Inherits from nothing +class MyClass(object): + """Inherits from nothing """ def __init__(self): @@ -25,7 +25,7 @@ class MySubClass(MyClass): """Inherits from MyClass """ class_attr = 1 - + def __init__(self): MyClass.__init__(self) self.var2 = 2 @@ -39,7 +39,7 @@ class MySubClass(MyClass): self.var[1] = 'one' self.var2 += 1 print self.class_attr - + if __name__ == '__main__': OBJ = MyClass() OBJ.met() diff --git a/test/input/func_interfaces.py b/test/input/func_interfaces.py index 64230612a..9756183c0 100644 --- a/test/input/func_interfaces.py +++ b/test/input/func_interfaces.py @@ -2,18 +2,18 @@ """docstring""" __revision__ = '' -class Interface: +class Interface(object): """base class for interfaces""" class IMachin(Interface): """docstring""" def truc(self): """docstring""" - + def troc(self, argument): """docstring""" -class Correct1: +class Correct1(object): """docstring""" __implements__ = IMachin @@ -23,12 +23,12 @@ class Correct1: def truc(self): """docstring""" pass - + def troc(self, argument): """docstring""" pass - -class Correct2: + +class Correct2(object): """docstring""" __implements__ = (IMachin,) @@ -38,12 +38,12 @@ class Correct2: def truc(self): """docstring""" pass - + def troc(self, argument): """docstring""" print argument -class MissingMethod: +class MissingMethod(object): """docstring""" __implements__ = IMachin, @@ -53,45 +53,45 @@ class MissingMethod: def troc(self, argument): """docstring""" print argument - + def other(self): """docstring""" - -class BadArgument: + +class BadArgument(object): """docstring""" __implements__ = (IMachin,) def __init__(self): pass - + def truc(self): """docstring""" pass - + def troc(self): """docstring""" pass - -class InterfaceCantBeFound: + +class InterfaceCantBeFound(object): """docstring""" __implements__ = undefined def __init__(self): """only to make pylint happier""" - + def please(self): """public method 1/2""" def besilent(self): """public method 2/2""" -class InterfaceCanNowBeFound: +class InterfaceCanNowBeFound(object): """docstring""" __implements__ = BadArgument.__implements__ + Correct2.__implements__ def __init__(self): """only to make pylint happier""" - + def please(self): """public method 1/2""" @@ -99,7 +99,7 @@ class InterfaceCanNowBeFound: """public method 2/2""" -class EmptyImplements: +class EmptyImplements(object): """no pb""" __implements__ = () def __init__(self): diff --git a/test/input/func_invalid_encoded_data.py b/test/input/func_invalid_encoded_data.py new file mode 100644 index 000000000..a465e3cb7 --- /dev/null +++ b/test/input/func_invalid_encoded_data.py @@ -0,0 +1,6 @@ +# coding: utf-8 +"""Test data file with encoding errors.""" + +__revision__ = 0 + +STR = 'СуÑеÑтвительное' diff --git a/test/input/func_kwoa_py30.py b/test/input/func_kwoa_py30.py new file mode 100644 index 000000000..4be5302b3 --- /dev/null +++ b/test/input/func_kwoa_py30.py @@ -0,0 +1,12 @@ +# pylint: disable=C0121, C0102 +'''A little testscript for PEP 3102 and pylint''' +def function(*, foo): + '''A function for testing''' + print(foo) + +function(foo=1) + +foo = 1 +function(foo) + +function(1) diff --git a/test/input/func_method_could_be_function.py b/test/input/func_method_could_be_function.py index 1a3cc40a4..47b0fc50b 100644 --- a/test/input/func_method_could_be_function.py +++ b/test/input/func_method_could_be_function.py @@ -8,7 +8,7 @@ class Toto(object): def __init__(self): self.aaa = 2 - + def regular_method(self): """this method is a real method since it access to self""" self.function_method() @@ -18,9 +18,9 @@ class Toto(object): print 'hello' -class Base: +class Base(object): """an abstract class""" - + def __init__(self): self.aaa = 2 @@ -31,19 +31,19 @@ class Base: class Sub(Base): """a concret class""" - + def check(self, arg): """a concret method, could not be a function since it need polymorphism benefits """ return arg == 0 -class Super: +class Super(object): """same as before without abstract""" - x = 1 + attr = 1 def method(self): """regular""" - print self.x + print self.attr class Sub1(Super): """override method with need for self""" diff --git a/test/input/func_method_missing_self.py b/test/input/func_method_missing_self.py index e553c418f..955fafc25 100644 --- a/test/input/func_method_missing_self.py +++ b/test/input/func_method_missing_self.py @@ -4,7 +4,7 @@ __revision__ = '' -class MyClass: +class MyClass(object): """SimpleClass """ diff --git a/test/input/func_method_without_self_but_self_assignment.py b/test/input/func_method_without_self_but_self_assignment.py index 38e591427..ef1bb60e4 100644 --- a/test/input/func_method_without_self_but_self_assignment.py +++ b/test/input/func_method_without_self_but_self_assignment.py @@ -4,9 +4,9 @@ __revision__ = 1 -class Example: +class Example(object): """bla""" - + def __init__(self): pass diff --git a/test/input/func_missing_super_argument_py20.py b/test/input/func_missing_super_argument_py20.py new file mode 100644 index 000000000..ec20857ca --- /dev/null +++ b/test/input/func_missing_super_argument_py20.py @@ -0,0 +1,9 @@ +"""Check missing super argument for Python 2""" + +__revision__ = 0 + +class MyClass(object): + """ New style class """ + def __init__(self): + super().__init__() +
\ No newline at end of file diff --git a/test/input/func_name_checking.py b/test/input/func_name_checking.py new file mode 100644 index 000000000..ce7f4398e --- /dev/null +++ b/test/input/func_name_checking.py @@ -0,0 +1,135 @@ +# pylint: disable=R0903,R0201,R0921,W0603 +"""Test for the invalid-name (C0103) warning.""" + +__revision__ = 1 + +import collections + +def Run(): + """method without any good name""" + class B(object): + """nested class should not be tested has a variable""" + def __init__(self): + pass + bBb = 1 + return A, bBb, B + +def run(): + """anothrer method without only good name""" + class Aaa(object): + """nested class should not be tested has a variable""" + def __init__(self): + pass + bbb = 1 + return Aaa(bbb) + +A = None + +def HOHOHOHO(): + """yo""" + HIHIHI = 1 + print HIHIHI + +class xyz(object): + """yo""" + + zz = 'Bad Class Attribute' + + def __init__(self): + pass + + def Youplapoum(self): + """bad method name""" + + +class Derived(xyz): + """Derived class.""" + zz = 'Not a bad class attribute' + + +def no_nested_args(arg1, arg21, arg22): + """a function which had nested arguments but no more""" + print arg1, arg21, arg22 + + +GOOD_CONST_NAME = '' +benpasceluila = 0 + +class Correct(object): + """yo""" + def __init__(self): + self.cava = 12 + self._Ca_va_Pas = None + + def BadMethodName(self): + """Ignored.""" + +V = [WHAT_Ever_inListComp for WHAT_Ever_inListComp in GOOD_CONST_NAME] + +def class_builder(): + """Function returning a class object.""" + + class EmbeddedClass(object): + """Useless class.""" + + return EmbeddedClass + +BAD_NAME_FOR_CLASS = collections.namedtuple('Named', ['tuple']) +NEXT_BAD_NAME_FOR_CLASS = class_builder() + +GoodName = collections.namedtuple('Named', ['tuple']) +ToplevelClass = class_builder() + +AlsoCorrect = Correct +NOT_CORRECT = Correct + + +def test_globals(): + """Names in global statements are also checked.""" + global NOT_CORRECT + global AlsoCorrect + NOT_CORRECT = 1 + AlsoCorrect = 2 + + +class DerivedFromCorrect(Correct): + """A derived class with an invalid inherited members. + + Derived attributes and methods with invalid names do not trigger warnings. + """ + + def __init__(self): + super(DerivedFromCorrect, self).__init__() + self._Ca_va_Pas = None + + def BadMethodName(self): + """Ignored.""" + +import abc + +class FooClass(object): + """A test case for property names. + + Since by default, the regex for attributes is the same as the one + for method names, we check the warning messages to contain the + string 'attribute'. + """ + @property + def PROPERTY_NAME(self): + """Ignored.""" + pass + + @abc.abstractproperty + def ABSTRACT_PROPERTY_NAME(self): + """Ignored.""" + pass + + @PROPERTY_NAME.setter + def PROPERTY_NAME_SETTER(self): + """Ignored.""" + pass + + +def func_bad_argname(NOT_GOOD): + """Function with a badly named argument.""" + return NOT_GOOD diff --git a/test/input/func_newstyle___slots__.py b/test/input/func_newstyle___slots__.py index 820c8ecba..f01c40eda 100644 --- a/test/input/func_newstyle___slots__.py +++ b/test/input/func_newstyle___slots__.py @@ -6,7 +6,7 @@ __revision__ = 1 class OkOk(object): """correct usage""" __slots__ = ('a', 'b') - + class HaNonNonNon: """bad usage""" __slots__ = ('a', 'b') diff --git a/test/input/func_newstyle_exceptions.py b/test/input/func_newstyle_exceptions.py index a9a2fab71..ee6e60e21 100644 --- a/test/input/func_newstyle_exceptions.py +++ b/test/input/func_newstyle_exceptions.py @@ -5,10 +5,10 @@ __revision__ = 1 class OkException(Exception): """bien bien bien""" - + class BofException: """mouais""" - + class NewException(object): """non si py < 2.5 !""" diff --git a/test/input/func_newstyle_super.py b/test/input/func_newstyle_super.py index c526cbe0c..fdba69cdf 100644 --- a/test/input/func_newstyle_super.py +++ b/test/input/func_newstyle_super.py @@ -19,4 +19,13 @@ class NewAaaa(object): def __init__(self): super(object, self).__init__() - + +class Py3kAaaa(NewAaaa): + """new style""" + def __init__(self): + super().__init__() + +class Py3kWrongSuper(Py3kAaaa): + """new style""" + def __init__(self): + super(NewAaaa, self).__init__() diff --git a/test/input/func_no_dummy_redefined.py b/test/input/func_no_dummy_redefined.py new file mode 100644 index 000000000..8af89d868 --- /dev/null +++ b/test/input/func_no_dummy_redefined.py @@ -0,0 +1,14 @@ +"""Make sure warnings about redefinitions do not trigger for dummy variables.""" +__revision__ = 0 + + +_, INTERESTING = 'a=b'.split('=') + +value = 10 + + +def clobbering(): + """Clobbers a dummy name from the outer scope.""" + value = 9 + for _ in range(7): + print value diff --git a/test/input/func_no_final_new_line.py b/test/input/func_no_final_new_line.py new file mode 100644 index 000000000..a9c728da0 --- /dev/null +++ b/test/input/func_no_final_new_line.py @@ -0,0 +1,2 @@ +'''hop''' +__revision__ = 0
\ No newline at end of file diff --git a/test/input/func_noerror___init___return_from_inner_function.py b/test/input/func_noerror___init___return_from_inner_function.py index 10b6c00ee..397b0fcc1 100644 --- a/test/input/func_noerror___init___return_from_inner_function.py +++ b/test/input/func_noerror___init___return_from_inner_function.py @@ -3,11 +3,11 @@ __revision__ = 1 -class Aaa: +class Aaa(object): """docstring""" def __init__(self): def inner_function(arg): """inner docstring""" return arg + 4 self.func = inner_function - + diff --git a/test/input/func_noerror_access_attr_before_def_false_positive.py b/test/input/func_noerror_access_attr_before_def_false_positive.py index 5f37f824c..3bf966b7d 100644 --- a/test/input/func_noerror_access_attr_before_def_false_positive.py +++ b/test/input/func_noerror_access_attr_before_def_false_positive.py @@ -13,8 +13,8 @@ import telnetlib class SeeTelnet(telnetlib.Telnet): """ Extension of telnetlib. - """ - + """ + def __init__(self, host=None, port=0): """ Constructor. @@ -41,7 +41,7 @@ class SeeTelnet(telnetlib.Telnet): class Base(object): """bla bla""" dougloup_papa = None - + def __init__(self): self._var = False @@ -56,7 +56,7 @@ class Derived(Base): else: print "False" self._var = True - + # E0203 - Access to member 'dougloup_papa' before its definition if self.dougloup_papa: print 'dougloup !' @@ -65,13 +65,13 @@ class Derived(Base): if self.dougloup_moi: print 'dougloup !' self.dougloup_moi = True - + class QoSALConnection(object): """blabla""" - + _the_instance = None - + def __new__(cls): if cls._the_instance is None: cls._the_instance = object.__new__(cls) @@ -80,7 +80,7 @@ class QoSALConnection(object): def __init__(self): pass -class DefinedOutsideInit: +class DefinedOutsideInit(object): """use_attr is seen as the method defining attr because its in first position """ diff --git a/test/input/func_noerror_base_init_vars.py b/test/input/func_noerror_base_init_vars.py index 24ad879dd..0870cef12 100644 --- a/test/input/func_noerror_base_init_vars.py +++ b/test/input/func_noerror_base_init_vars.py @@ -3,10 +3,10 @@ """ __revision__ = '' -class BaseClass: +class BaseClass(object): """A simple base class """ - + def __init__(self): self.base_var = {} diff --git a/test/input/func_noerror_classes_meth_could_be_a_function.py b/test/input/func_noerror_classes_meth_could_be_a_function.py index c1b160fa0..bdf0da434 100644 --- a/test/input/func_noerror_classes_meth_could_be_a_function.py +++ b/test/input/func_noerror_classes_meth_could_be_a_function.py @@ -7,20 +7,20 @@ like factory method pattern """ __revision__ = 1 -class XAsub: +class XAsub(object): pass class XBsub(XAsub): pass class XCsub(XAsub): pass -class Aimpl: +class Aimpl(object): # disable "method could be a function" on classes which are not overriding # the factory method because in that case the usage of polymorphism is not # detected # pylint: disable=R0201 - def makex(self): - return XAsub() + def makex(self): + return XAsub() class Bimpl(Aimpl): diff --git a/test/input/func_noerror_classes_protected_member_access.py b/test/input/func_noerror_classes_protected_member_access.py index d7667b8be..bef1bff18 100644 --- a/test/input/func_noerror_classes_protected_member_access.py +++ b/test/input/func_noerror_classes_protected_member_access.py @@ -3,13 +3,13 @@ """ __revision__ = 1 -class A3123: +class A3123(object): """oypuee""" _protected = 1 def __init__(self): pass - + def cmeth(cls, val): """set protected member""" cls._protected = +val @@ -21,4 +21,4 @@ class A3123: A3123._protected += val smeth = staticmethod(smeth) - + diff --git a/test/input/func_noerror_decorator_scope.py b/test/input/func_noerror_decorator_scope.py index 2f35d97ee..95e7a47c6 100644 --- a/test/input/func_noerror_decorator_scope.py +++ b/test/input/func_noerror_decorator_scope.py @@ -2,13 +2,13 @@ """Test that decorators sees the class namespace - just like function default values does but function body doesn't. -https://www.logilab.net/elo/ticket/3711 - bug finding decorator arguments +https://www.logilab.net/elo/ticket/3711 - bug finding decorator arguments https://www.logilab.net/elo/ticket/5626 - name resolution bug inside classes """ __revision__ = 0 - -class Test: + +class Test(object): """test class""" ident = lambda x: x diff --git a/test/input/func_noerror_defined_and_used_on_same_line.py b/test/input/func_noerror_defined_and_used_on_same_line.py index a7bbf9e2e..71e67b9da 100644 --- a/test/input/func_noerror_defined_and_used_on_same_line.py +++ b/test/input/func_noerror_defined_and_used_on_same_line.py @@ -28,4 +28,3 @@ FUNC3 = lambda (a, b) : a != b # test http://www.logilab.org/ticket/6954: with open('f') as f: print(f.read()) - diff --git a/test/input/func_noerror_defined_and_used_on_same_line_py27.py b/test/input/func_noerror_defined_and_used_on_same_line_py27.py new file mode 100644 index 000000000..5a7572205 --- /dev/null +++ b/test/input/func_noerror_defined_and_used_on_same_line_py27.py @@ -0,0 +1,5 @@ +#pylint: disable=C0111,C0321 +"""pylint complains about 'index' being used before definition""" + +with open('f') as f, open(f.read()) as g: + print g.read() diff --git a/test/input/func_noerror_e1101_but_getattr.py b/test/input/func_noerror_e1101_but_getattr.py index 143ddc06e..03b8fb9b4 100644 --- a/test/input/func_noerror_e1101_but_getattr.py +++ b/test/input/func_noerror_e1101_but_getattr.py @@ -2,9 +2,9 @@ __revision__ = None -class MyString: +class MyString(object): """proxied string""" - + def __init__(self, string): self.string = string diff --git a/test/input/func_noerror_external_classmethod_crash.py b/test/input/func_noerror_external_classmethod_crash.py index 7bd579200..318f01c3e 100644 --- a/test/input/func_noerror_external_classmethod_crash.py +++ b/test/input/func_noerror_external_classmethod_crash.py @@ -5,7 +5,7 @@ signature overriding def fetch_config(mainattr=None): """return a class method""" - + def fetch_order(cls, attr, var): """a class method""" if attr == mainattr: @@ -14,7 +14,7 @@ def fetch_config(mainattr=None): fetch_order = classmethod(fetch_order) return fetch_order -class Aaa: +class Aaa(object): """hop""" fetch_order = fetch_config('A') diff --git a/test/input/func_noerror_factory_method.py b/test/input/func_noerror_factory_method.py index bd2447993..80ad43367 100644 --- a/test/input/func_noerror_factory_method.py +++ b/test/input/func_noerror_factory_method.py @@ -1,17 +1,17 @@ # pylint: disable=R0903 -"""use new astng context sensitive inference""" +"""use new astroid context sensitive inference""" __revision__ = 1 class Super(object): """super class""" def __init__(self): self.bla = None - + def instance(cls): """factory method""" return cls() instance = classmethod(instance) - + class Sub(Super): """dub class""" def method(self): diff --git a/test/input/func_noerror_genexp_in_class_scope.py b/test/input/func_noerror_genexp_in_class_scope.py index 508f540fa..5631026ab 100644 --- a/test/input/func_noerror_genexp_in_class_scope.py +++ b/test/input/func_noerror_genexp_in_class_scope.py @@ -3,7 +3,7 @@ __revision__ = '' -class MyClass: +class MyClass(object): """ds""" var1 = [] var2 = list(value*2 for value in var1) diff --git a/test/input/func_noerror_indirect_interface.py b/test/input/func_noerror_indirect_interface.py index 2b240f970..a0f245a3b 100644 --- a/test/input/func_noerror_indirect_interface.py +++ b/test/input/func_noerror_indirect_interface.py @@ -10,7 +10,7 @@ class ConcreteToto(AbstractToto): """abstract to implements an interface requiring machin to be defined""" def __init__(self): self.duh = 2 - + def machin(self): """for ifacd""" return self.helper()*2 diff --git a/test/input/func_noerror_nested_classes.py b/test/input/func_noerror_nested_classes.py index 838e66c3b..0996c66c6 100644 --- a/test/input/func_noerror_nested_classes.py +++ b/test/input/func_noerror_nested_classes.py @@ -3,12 +3,12 @@ __revision__ = 1 -class Temelekefe: +class Temelekefe(object): """gloubliboulga""" - + def __init__(self): """nested class with function raise error""" - class Toto: + class Toto(object): """toto nested class""" def __init__(self): self.attr = 2 diff --git a/test/input/func_noerror_no_warning_docstring.py b/test/input/func_noerror_no_warning_docstring.py index 97983149b..1af00c2a3 100644 --- a/test/input/func_noerror_no_warning_docstring.py +++ b/test/input/func_noerror_no_warning_docstring.py @@ -2,37 +2,37 @@ __revision__ = 1 -class AAAA: +class AAAA(object): ''' class AAAA ''' - + def __init__(self): pass - + def method1(self): ''' method 1 ''' print self - + def method2(self): ''' method 2 ''' print self class BBBB(AAAA): ''' class BBBB ''' - + def __init__(self): AAAA.__init__(self) - + # should ignore docstring calling from class AAAA def method1(self): AAAA.method1(self) class CCCC(BBBB): ''' class CCCC ''' - + def __init__(self): BBBB.__init__(self) - - # should ignore docstring since CCCC is inherited from BBBB which is + + # should ignore docstring since CCCC is inherited from BBBB which is # inherited from AAAA containing method2 if __revision__: def method2(self): diff --git a/test/input/func_noerror_nonregr.py b/test/input/func_noerror_nonregr.py index 35598e0b9..992f9f39d 100644 --- a/test/input/func_noerror_nonregr.py +++ b/test/input/func_noerror_nonregr.py @@ -5,7 +5,7 @@ __revision__ = 1 def function1(cbarg = lambda: None): """ - File "/usr/lib/python2.4/site-packages/logilab/astng/scoped_nodes.py", line + File "/usr/lib/python2.4/site-packages/logilab/astroid/scoped_nodes.py", line 391, in mularg_class # this method doesn't exist anymore i = self.args.args.index(argname) ValueError: list.index(x): x not in list diff --git a/test/input/func_noerror_object_as_class_attribute.py b/test/input/func_noerror_object_as_class_attribute.py index 39eae0e4f..c69b2b966 100644 --- a/test/input/func_noerror_object_as_class_attribute.py +++ b/test/input/func_noerror_object_as_class_attribute.py @@ -5,7 +5,7 @@ * pylint will therefore check that baseclasses' init() are called - If this class defines an 'object' attribute, then pylint - will use this new definition when trying to retrieve + will use this new definition when trying to retrieve object.__init__() """ diff --git a/test/input/func_noerror_overloaded_operator.py b/test/input/func_noerror_overloaded_operator.py index 3edd12212..a0136731d 100644 --- a/test/input/func_noerror_overloaded_operator.py +++ b/test/input/func_noerror_overloaded_operator.py @@ -2,7 +2,7 @@ """#3291""" __revision__ = 1 -class Myarray: +class Myarray(object): def __init__(self, array): self.array = array diff --git a/test/input/func_noerror_socket_member.py b/test/input/func_noerror_socket_member.py index 8c79ff579..ffa3e9c41 100644 --- a/test/input/func_noerror_socket_member.py +++ b/test/input/func_noerror_socket_member.py @@ -7,7 +7,7 @@ Version used: - Pylint 0.10.0 - Logilab common 0.15.0 - - Logilab astng 0.15.1 + - Logilab astroid 0.15.1 False E1101 positive, line 23: diff --git a/test/input/func_noerror_static_method.py b/test/input/func_noerror_static_method.py index 618496109..d109d2eaa 100644 --- a/test/input/func_noerror_static_method.py +++ b/test/input/func_noerror_static_method.py @@ -3,7 +3,7 @@ __revision__ = '' -class MyClass: +class MyClass(object): """doc """ def __init__(self): @@ -26,4 +26,4 @@ if __name__ == '__main__': MyClass.static_met("var1","var2") MyClass.class_met("var1") - + diff --git a/test/input/func_noerror_staticmethod_as_decorator_py24.py b/test/input/func_noerror_staticmethod_as_decorator_py24.py index efad8c943..d6718798f 100644 --- a/test/input/func_noerror_staticmethod_as_decorator_py24.py +++ b/test/input/func_noerror_staticmethod_as_decorator_py24.py @@ -11,7 +11,7 @@ class StaticMethod1(object): @staticmethod def do_work(): "Working..." - + @staticmethod def do_work_with_arg(job): "Working on something" @@ -26,7 +26,7 @@ class ClassMethod2(object): @classmethod def do_work(cls): "Working..." - + @classmethod def do_work_with_arg(cls, job): "Working on something" diff --git a/test/input/func_r0901.py b/test/input/func_r0901.py index 79e98d18f..ac69fb634 100644 --- a/test/input/func_r0901.py +++ b/test/input/func_r0901.py @@ -2,26 +2,26 @@ """test max parents""" __revision__ = None -class Aaaa: +class Aaaa(object): """yo""" -class Bbbb: +class Bbbb(object): """yo""" -class Cccc: +class Cccc(object): """yo""" -class Dddd: +class Dddd(object): """yo""" -class Eeee: +class Eeee(object): """yo""" -class Ffff: +class Ffff(object): """yo""" -class Gggg: +class Gggg(object): """yo""" -class Hhhh: +class Hhhh(object): """yo""" class Iiii(Aaaa, Bbbb, Cccc, Dddd, Eeee, Ffff, Gggg, Hhhh): """yo""" - + class Jjjj(Iiii): """yo""" - + diff --git a/test/input/func_r0902.py b/test/input/func_r0902.py index db39cfa38..d0a74832a 100644 --- a/test/input/func_r0902.py +++ b/test/input/func_r0902.py @@ -2,7 +2,7 @@ """test max instance attributes""" __revision__ = None -class Aaaa: +class Aaaa(object): """yo""" def __init__(self): self.aaaa = 1 diff --git a/test/input/func_r0903.py b/test/input/func_r0903.py index 4c119299c..ccbad5819 100644 --- a/test/input/func_r0903.py +++ b/test/input/func_r0903.py @@ -1,7 +1,7 @@ """test min methods""" __revision__ = None -class Aaaa: +class Aaaa(object): """yo""" def __init__(self): pass diff --git a/test/input/func_r0904.py b/test/input/func_r0904.py index a4c694f8b..28bda5cae 100644 --- a/test/input/func_r0904.py +++ b/test/input/func_r0904.py @@ -1,11 +1,11 @@ # pylint: disable=R0201 """test max methods""" __revision__ = None -class Aaaa: +class Aaaa(object): """yo""" def __init__(self): pass - + def meth1(self): """hehehe""" diff --git a/test/input/func_r0921.py b/test/input/func_r0921.py index b9f2de2ba..1baf0a8ef 100644 --- a/test/input/func_r0921.py +++ b/test/input/func_r0921.py @@ -1,15 +1,15 @@ """test max methods""" __revision__ = None -class Aaaa: +class Aaaa(object): """yo""" def __init__(self): pass - + def meth1(self): """hehehe""" raise NotImplementedError - + def meth2(self): """hehehe""" return 'Yo', self diff --git a/test/input/func_r0922.py b/test/input/func_r0922.py index da7dfcf22..d4abafeb9 100644 --- a/test/input/func_r0922.py +++ b/test/input/func_r0922.py @@ -1,15 +1,15 @@ """test max methods""" __revision__ = None -class Aaaa: +class Aaaa(object): """yo""" def __init__(self): pass - + def meth1(self): """hehehe""" raise NotImplementedError - + def meth2(self): """hehehe""" return 'Yo', self diff --git a/test/input/func_r0923.py b/test/input/func_r0923.py index 8dacf1e44..514f042d8 100644 --- a/test/input/func_r0923.py +++ b/test/input/func_r0923.py @@ -5,28 +5,28 @@ from logilab.common.interface import Interface class IAaaa(Interface): """yo""" - + def meth1(self): """hehehe""" - + class IBbbb(Interface): """yo""" - + def meth1(self): """hehehe""" -class Concret: +class Concret(object): """implements IBbbb""" __implements__ = IBbbb def __init__(self): pass - + def meth1(self): """hehehe""" return "et hop", self - + def meth2(self): """hehehe""" return "et hop", self - + diff --git a/test/input/func_raw_escapes.py b/test/input/func_raw_escapes.py new file mode 100644 index 000000000..b08b6f142 --- /dev/null +++ b/test/input/func_raw_escapes.py @@ -0,0 +1,19 @@ +# pylint:disable=W0105, W0511, C0121 +"""Test for backslash escapes in byte vs unicode strings""" + +# Would be valid in Unicode, but probably not what you want otherwise +BAD_UNICODE = b'\u0042' +BAD_LONG_UNICODE = b'\U00000042' +BAD_NAMED_UNICODE = b'\N{GREEK SMALL LETTER ALPHA}' + +GOOD_UNICODE = u'\u0042' +GOOD_LONG_UNICODE = u'\U00000042' +GOOD_NAMED_UNICODE = u'\N{GREEK SMALL LETTER ALPHA}' + + +# Valid raw strings +RAW_BACKSLASHES = r'raw' +RAW_UNICODE = ur"\u0062\n" + +# In a comment you can have whatever you want: \ \\ \n \m +# even things that look like bad strings: "C:\Program Files" diff --git a/test/input/func_scope_regrtest.py b/test/input/func_scope_regrtest.py index 367a9085b..576339751 100644 --- a/test/input/func_scope_regrtest.py +++ b/test/input/func_scope_regrtest.py @@ -5,11 +5,11 @@ __revision__ = None class Well(object): """well""" - class Data: + class Data(object): """base hidden class""" class Sub(Data): """whaou, is Data found???""" - yo = Data() + attr = Data() def func(self): """check Sub is not defined here""" return Sub(), self diff --git a/test/input/func_special_methods.py b/test/input/func_special_methods.py index 699efd144..1b30e269f 100644 --- a/test/input/func_special_methods.py +++ b/test/input/func_special_methods.py @@ -1,7 +1,7 @@ #pylint: disable=C0111 __revision__ = None -class ContextManager: +class ContextManager(object): def __enter__(self): pass def __exit__(self, *args): @@ -9,13 +9,13 @@ class ContextManager: def __init__(self): pass -class BadContextManager: +class BadContextManager(object): def __enter__(self): pass def __init__(self): pass -class Container: +class Container(object): def __init__(self): pass def __len__(self): @@ -29,7 +29,7 @@ class Container: def __iter__(self): pass -class BadContainer: +class BadROContainer(object): def __init__(self): pass def __len__(self): @@ -39,9 +39,17 @@ class BadContainer: def __iter__(self): pass -class Callable: - def __call__(self): - pass + +class BadContainer(object): def __init__(self): pass + def __len__(self): + return 0 + def __getitem__(self, key, value): + pass + def __setitem__(self, key, value): + pass +class MyTuple(tuple): + def __getitem__(self, index): + return super(MyTuple, self).__getitem__(index) diff --git a/test/input/func_tokenize_error.py b/test/input/func_tokenize_error.py new file mode 100644 index 000000000..1b52cb934 --- /dev/null +++ b/test/input/func_tokenize_error.py @@ -0,0 +1,6 @@ +"""A module that is accepted by Python but rejected by tokenize. + +The problem is the trailing line continuation at the end of the line, +which produces a TokenError.""" + +""\ diff --git a/test/input/func_too_many_locals_arguments.py b/test/input/func_too_many_locals_arguments.py index 95ef771fc..f63a5eeb7 100644 --- a/test/input/func_too_many_locals_arguments.py +++ b/test/input/func_too_many_locals_arguments.py @@ -47,4 +47,6 @@ def ignored_arguments_function(arga, argu, argi, arg8 = arg7 * 8 arg9 = arg8 * 9 arg9 += arg0 + if _args: + arg9 += sum(_args) return arg9 diff --git a/test/input/func_toolonglines.py b/test/input/func_toolonglines.py index 76a401874..d02e4bbab 100644 --- a/test/input/func_toolonglines.py +++ b/test/input/func_toolonglines.py @@ -2,3 +2,26 @@ """ that one is too long tooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo""" __revision__ = '' + +# The next line is exactly 80 characters long. +A = '--------------------------------------------------------------------------' + +# The next line is longer than 80 characters, because the file is encoded +# in ASCII. +THIS_IS_A_VERY_LONG_VARIABLE_NAME = 'СущеÑтвительное ЧаÑтица' # With warnings. + +# Do not trigger the line-too-long warning if the only token that makes the +# line longer than 80 characters is a trailing pylint disable. +var = 'This line has a disable pragma and whitespace trailing beyond 80 chars. ' # pylint:disable=invalid-name + +badname = 'This line is already longer than 80 characters even without the pragma.' # pylint:disable=invalid-name + +# http://example.com/this/is/a/very/long/url?but=splitting&urls=is&a=pain&so=they&can=be&long + + +def function(): + """This is a docstring. + + That contains a very, very long line that exceeds the 80 character limit by a good margin. + """ + pass diff --git a/test/input/func_trailing_whitespace.py b/test/input/func_trailing_whitespace.py new file mode 100644 index 000000000..54f6ae481 --- /dev/null +++ b/test/input/func_trailing_whitespace.py @@ -0,0 +1,6 @@ +"""Regression test for trailing-whitespace (C0303).""" + +__revision__ = 0 + +print 'some trailing whitespace' +print 'trailing whitespace does not count towards the line length limit' diff --git a/test/input/func_typecheck_callfunc_assigment.py b/test/input/func_typecheck_callfunc_assigment.py index ee7bbeeb4..a909f17fd 100644 --- a/test/input/func_typecheck_callfunc_assigment.py +++ b/test/input/func_typecheck_callfunc_assigment.py @@ -45,11 +45,11 @@ A = generator() class Abstract(object): """bla bla""" - + def abstract_method(self): """use to return something in concrete implementation""" raise NotImplementedError - + def use_abstract(self): """should not issue E1111""" var = self.abstract_method() diff --git a/test/input/func_typecheck_getattr.py b/test/input/func_typecheck_getattr.py index 90b7c075d..b120ca493 100644 --- a/test/input/func_typecheck_getattr.py +++ b/test/input/func_typecheck_getattr.py @@ -3,7 +3,7 @@ __revision__ = None -class Provider: +class Provider(object): """provide some attributes and method""" cattr = 4 def __init__(self): @@ -14,11 +14,11 @@ class Provider: def hophop(self): """hop method""" print 'hop hop hop', self - + class Client: """use provider class""" - + def __init__(self): self._prov = Provider() self._prov_attr = Provider.cattr @@ -28,7 +28,7 @@ class Client: def set_set_later(self, value): """set set_later attribute (introduce an inference ambiguity)""" self.set_later = value - + def use_method(self): """use provider's method""" self._prov.hophop() @@ -66,4 +66,4 @@ print property.__init__ print Client().set_later.lower() # should detect mixing new style / old style classes -Client.__bases__ += (object,) +Client.__bases__ += (object,) diff --git a/test/input/func_typecheck_non_callable_call.py b/test/input/func_typecheck_non_callable_call.py index 73c5db2da..8d8a6c291 100644 --- a/test/input/func_typecheck_non_callable_call.py +++ b/test/input/func_typecheck_non_callable_call.py @@ -22,7 +22,7 @@ class MetaCorrect(object): """callable object""" def __call__(self): return self - + INSTANCE = Correct() CALLABLE_INSTANCE = MetaCorrect() CORRECT = CALLABLE_INSTANCE() diff --git a/test/input/func_unbalanced_tuple_unpacking.py b/test/input/func_unbalanced_tuple_unpacking.py new file mode 100644 index 000000000..1c251f009 --- /dev/null +++ b/test/input/func_unbalanced_tuple_unpacking.py @@ -0,0 +1,40 @@ +"""Check possible unbalanced tuple unpacking """ + +__revision__ = 0 + +def do_stuff(): + """This is not right.""" + first, second = 1, 2, 3 + return first + second + +def do_stuff1(): + """This is not right.""" + first, second = [1, 2, 3] + return first + second + +def do_stuff2(): + """This is not right.""" + (first, second) = 1, 2, 3 + return first + second + +def do_stuff3(): + """This is not right.""" + first, second = range(100) + return first + second + +def do_stuff4(): + """ This is right """ + first, second = 1, 2 + return first + second + +def do_stuff5(): + """ This is also right """ + first, second = (1, 2) + return first + second + +def do_stuff6(): + """ This is right """ + (first, second) = (1, 2) + return first + second + + diff --git a/test/input/func_undefined_var.py b/test/input/func_undefined_var.py index 27a9ed616..407f3f627 100644 --- a/test/input/func_undefined_var.py +++ b/test/input/func_undefined_var.py @@ -15,7 +15,7 @@ def in_method(var): assert var DEFINED = {DEFINED:__revision__} -DEFINED[__revision__] = OTHER = 'move this is astng test' +DEFINED[__revision__] = OTHER = 'move this is astroid test' OTHER += '$' @@ -28,7 +28,7 @@ def bad_default(var, default=unknown2): del vardel # Warning for Attribute access to undefinde attributes ? -#class Attrs(object): +#class Attrs(object): #"""dummy class for wrong attribute access""" #AOU = Attrs() #AOU.number *= 1.3 diff --git a/test/input/func_unpack_exception_py27.py b/test/input/func_unpack_exception_py_30.py index 763b0cfbc..cf2e23787 100644 --- a/test/input/func_unpack_exception_py27.py +++ b/test/input/func_unpack_exception_py_30.py @@ -10,4 +10,4 @@ def new_style(): print errno, message except IOError as (new_style, tuple): # W0623 twice print new_style, tuple - + diff --git a/test/input/func_unreachable.py b/test/input/func_unreachable.py index 1cabce718..c5f7eec20 100644 --- a/test/input/func_unreachable.py +++ b/test/input/func_unreachable.py @@ -19,4 +19,4 @@ def func3(): print i continue print 'unreachable' - + diff --git a/test/input/func_unused_overridden_argument.py b/test/input/func_unused_overridden_argument.py index d0794ae7d..b587a1232 100644 --- a/test/input/func_unused_overridden_argument.py +++ b/test/input/func_unused_overridden_argument.py @@ -18,7 +18,7 @@ class Sub(Base): def inherited(self, aaa, aab, aac): "overridden method, though don't use every argument" return aaa - + def newmethod(self, aax, aay): "another method, warning for aay desired" print self, aax @@ -29,4 +29,4 @@ class Sub2(Base): def inherited(self, aaa, aab, aac): "overridden method, use every argument" return aaa + aab + aac - + diff --git a/test/input/func_utf8_lines.py b/test/input/func_utf8_lines.py new file mode 100644 index 000000000..ee29db723 --- /dev/null +++ b/test/input/func_utf8_lines.py @@ -0,0 +1,8 @@ +# coding: utf-8 +"""Test data file files with non-ASCII content.""" + +__revision__ = 1 + +THIS_IS_A_LONG_VARIABLE_NAME = 'СущеÑтвительное ЧаÑтица' # but the line is okay + +THIS_IS_A_VERY_LONG_VARIABLE_NAME = 'СущеÑтвительное ЧаÑтица' # and the line is not okay diff --git a/test/input/func_w0102.py b/test/input/func_w0102.py index f415fe30a..8a8ae4dff 100644 --- a/test/input/func_w0102.py +++ b/test/input/func_w0102.py @@ -2,37 +2,37 @@ """docstring""" __revision__ = '' -class AAAA: +class AAAA(object): """docstring""" def __init__(self): pass def method1(self): """docstring""" - + def method2(self): """docstring""" - + def method2(self): """docstring""" -class AAAA: +class AAAA(object): """docstring""" def __init__(self): pass def yeah(self): """hehehe""" def yoo(self): - """yoo""" + """yoo""" def func1(): """docstring""" - + def func2(): """docstring""" - + def func2(): """docstring""" - __revision__ = 1 - return __revision__ + __revision__ = 1 + return __revision__ if __revision__: def exclusive_func(): diff --git a/test/input/func_w0103.py b/test/input/func_w0103.py index 9417cb55c..f8216d9e7 100644 --- a/test/input/func_w0103.py +++ b/test/input/func_w0103.py @@ -1,4 +1,4 @@ -"""test max branch +"""test max branch """ __revision__ = '' diff --git a/test/input/func_w0104.py b/test/input/func_w0104.py index bddf00123..d601682b8 100644 --- a/test/input/func_w0104.py +++ b/test/input/func_w0104.py @@ -1,4 +1,4 @@ -"""test max branch +"""test max branch """ __revision__ = '' @@ -9,4 +9,4 @@ def stupid_function(arg1, arg2, arg3, arg4, arg5): print arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 loc1, loc2, loc3, loc4, loc5, loc6, loc7 = arg1, arg2, arg3, arg4, arg5, \ arg6, arg7 - print loc1, loc2, loc3, loc4, loc5, loc6, loc7 + print loc1, loc2, loc3, loc4, loc5, loc6, loc7 diff --git a/test/input/func_w0105.py b/test/input/func_w0105.py index 430cfd4f3..c951e9da5 100644 --- a/test/input/func_w0105.py +++ b/test/input/func_w0105.py @@ -1,4 +1,4 @@ -"""test max branch +"""test max branch """ __revision__ = '' @@ -30,33 +30,33 @@ def stupid_function(arg): print 100 arg = 0 for val in range(arg): - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val - print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val diff --git a/test/input/func_w0112.py b/test/input/func_w0112.py index 5cd01ae34..cdf2bd0d1 100644 --- a/test/input/func_w0112.py +++ b/test/input/func_w0112.py @@ -1,4 +1,4 @@ -"""test max branch +"""test max branch """ __revision__ = '' diff --git a/test/input/func_w0133.py b/test/input/func_w0133.py deleted file mode 100644 index de642a855..000000000 --- a/test/input/func_w0133.py +++ /dev/null @@ -1,54 +0,0 @@ -# pylint: disable=R0903,R0201 -"""test Invalid name""" - -__revision__ = 1 - -def Run(): - """method without any good name""" - class B: - """nested class should not be tested has a variable""" - def __init__(self): - pass - bBb = 1 - return A, bBb - -def run(): - """anothrer method without only good name""" - class Aaa: - """nested class should not be tested has a variable""" - def __init__(self): - pass - bbb = 1 - return Aaa(bbb) - -A = None - -def HOHOHOHO(): - """yo""" - HIHIHI = 1 - print HIHIHI - -class xyz: - """yo""" - def __init__(self): - pass - - def Youplapoum(self): - """bad method name""" - - -def no_nested_args(arg1, arg21, arg22): - """a function which had nested arguments but no more""" - print arg1, arg21, arg22 - - -GOOD_CONST_NAME = '' -benpasceluila = 0 - -class Correct: - """yo""" - def __init__(self): - self.cava = 12 - self._Ca_va_Pas = None - -V = [WHAT_Ever_inListComp for WHAT_Ever_inListComp in GOOD_CONST_NAME] diff --git a/test/input/func_w0202.py b/test/input/func_w0202.py index 4d36d40da..00b5b0fbb 100644 --- a/test/input/func_w0202.py +++ b/test/input/func_w0202.py @@ -2,9 +2,9 @@ __revision__ = None -class Abcd: +class Abcd(object): """dummy""" - + def method1(self): """hehe""" method1 = staticmethod(method1) diff --git a/test/input/func_w0205.py b/test/input/func_w0205.py index d2e3b29cb..6e7bf43d8 100644 --- a/test/input/func_w0205.py +++ b/test/input/func_w0205.py @@ -2,10 +2,10 @@ __revision__ = 0 -class Abcd: +class Abcd(object): '''dummy''' def __init__(self): - self.aarg = False + self.aarg = False def abcd(self, aaa=1, bbbb=None): """hehehe""" print self, aaa, bbbb diff --git a/test/input/func_w0223.py b/test/input/func_w0223.py index 9980bd30d..1563b41e9 100644 --- a/test/input/func_w0223.py +++ b/test/input/func_w0223.py @@ -4,7 +4,7 @@ __revision__ = '$Id: func_w0223.py,v 1.2 2004-09-29 08:35:13 syt Exp $' -class Abstract: +class Abstract(object): """abstract class """ def aaaa(self): diff --git a/test/input/func_w0231.py b/test/input/func_w0231.py index 74b56ccbc..7c4cc025d 100644 --- a/test/input/func_w0231.py +++ b/test/input/func_w0231.py @@ -15,14 +15,14 @@ class BBBB: def __init__(self): print 'init', self - + class CCCC: """ancestor 3""" class ZZZZ(AAAA, BBBB, CCCC): """derived class""" - + def __init__(self): AAAA.__init__(self) @@ -31,7 +31,7 @@ class NewStyleA(object): def __init__(self): super(NewStyleA, self).__init__() print 'init', self - + class NewStyleB(NewStyleA): """derived new style class""" def __init__(self): diff --git a/test/input/func_w0233.py b/test/input/func_w0233.py index 0d90edeba..857743aad 100644 --- a/test/input/func_w0233.py +++ b/test/input/func_w0233.py @@ -4,14 +4,14 @@ __revision__ = '$Id: func_w0233.py,v 1.2 2004-09-29 08:35:13 syt Exp $' -class AAAA: +class AAAA(object): """ancestor 1""" def __init__(self): print 'init', self BBBBMixin.__init__(self) -class BBBBMixin: +class BBBBMixin(object): """ancestor 2""" def __init__(self): diff --git a/test/input/func_w0331_py_30.py b/test/input/func_w0331_py_30.py index 7e3452335..6ee600695 100644 --- a/test/input/func_w0331_py_30.py +++ b/test/input/func_w0331_py_30.py @@ -4,4 +4,4 @@ __revision__ = 1 if __revision__ <> 2: - __revision__ = 2 + __revision__ = 2 diff --git a/test/input/func_w0332_py_30.py b/test/input/func_w0332_py_30.py index ff078efc5..d6fed47f4 100644 --- a/test/input/func_w0332_py_30.py +++ b/test/input/func_w0332_py_30.py @@ -1,4 +1,4 @@ -"""check use of l as long int marker +"""check use of l as long int marker """ __revision__ = 1l diff --git a/test/input/func_w0401.py b/test/input/func_w0401.py index 6b7a64b64..160b555f8 100644 --- a/test/input/func_w0401.py +++ b/test/input/func_w0401.py @@ -2,7 +2,6 @@ """ __revision__ = 0 - from input import w0401_cycle if __revision__: diff --git a/test/input/func_w0403.py b/test/input/func_w0403.py index 72fb79502..89d362d03 100644 --- a/test/input/func_w0403.py +++ b/test/input/func_w0403.py @@ -5,7 +5,7 @@ __revision__ = 0 if __revision__: - import Bastion + import Bastion print Bastion # false positive (#10061) import stringfile diff --git a/test/input/func_w0405.py b/test/input/func_w0405.py index 745c615cb..8b96cd9b1 100644 --- a/test/input/func_w0405.py +++ b/test/input/func_w0405.py @@ -22,7 +22,7 @@ def func(yooo): ass.remove(yooo) import re re.compile('.*') - + if 1: import sys print sys.modules diff --git a/test/input/func_w0611.py b/test/input/func_w0611.py index 2c3796699..6bef753e5 100644 --- a/test/input/func_w0611.py +++ b/test/input/func_w0611.py @@ -4,7 +4,7 @@ __revision__ = 1 import os import sys -class NonRegr: +class NonRegr(object): """???""" def __init__(self): print 'initialized' @@ -16,7 +16,7 @@ class NonRegr: def dummy(self, truc): """yo""" return self, truc - + def blop(self): """yo""" print self, 'blip' diff --git a/test/input/func_w0613.py b/test/input/func_w0613.py index 5b8e6eee6..4d51d1029 100644 --- a/test/input/func_w0613.py +++ b/test/input/func_w0613.py @@ -8,7 +8,7 @@ def function(arg=1): """ignore arg""" -class AAAA: +class AAAA(object): """dummy class""" def method(self, arg): @@ -34,7 +34,7 @@ class AAAA: # pylint: disable = W0201 rset.get_entity = inner -class BBBB: +class BBBB(object): """dummy class""" def __init__(self, arg): diff --git a/test/input/func_w0622.py b/test/input/func_w0622.py index 3d0933b42..576984171 100644 --- a/test/input/func_w0622.py +++ b/test/input/func_w0622.py @@ -1,5 +1,5 @@ # pylint: disable=C0103 -"""test built-in redefinition +"""test built-in redefinition """ __revision__ = 0 diff --git a/test/input/func_w0623_py30.py b/test/input/func_w0623_py30.py new file mode 100644 index 000000000..163256b13 --- /dev/null +++ b/test/input/func_w0623_py30.py @@ -0,0 +1,16 @@ +"""Test for W0623, overwriting names in exception handlers.""" + +__revision__ = '' + +class MyError(Exception): + """Special exception class.""" + pass + + +def some_function(): + """A function.""" + + try: + {}["a"] + except KeyError as some_function: # W0623 + pass diff --git a/test/input/func_w0623.py b/test/input/func_w0623_py_30.py index 7c489cc60..7c489cc60 100644 --- a/test/input/func_w0623.py +++ b/test/input/func_w0623_py_30.py diff --git a/test/input/func_w0704.py b/test/input/func_w0704.py index e8c643284..524df17ea 100644 --- a/test/input/func_w0704.py +++ b/test/input/func_w0704.py @@ -13,4 +13,4 @@ try: except TypeError: pass else: - __revision__ = None + __revision__ = None diff --git a/test/input/func_w0705.py b/test/input/func_w0705.py index 898e2b8f0..0a77ac217 100644 --- a/test/input/func_w0705.py +++ b/test/input/func_w0705.py @@ -1,4 +1,4 @@ -"""test misordered except +"""test misordered except """ __revision__ = 1 @@ -36,7 +36,7 @@ try: except TypeError: __revision__ = None except: - __revision__ = None + __revision__ = None try: __revision__ += 1 diff --git a/test/input/w0401_cycle.py b/test/input/w0401_cycle.py index 4c03cae0c..bf731e0ce 100644 --- a/test/input/w0401_cycle.py +++ b/test/input/w0401_cycle.py @@ -6,4 +6,4 @@ __revision__ = 0 import input.func_w0401 if __revision__: - print input + print input diff --git a/test/messages/func___name___access.txt b/test/messages/func___name___access.txt index 51aeeaa3d..4351425fb 100644 --- a/test/messages/func___name___access.txt +++ b/test/messages/func___name___access.txt @@ -1,3 +1,4 @@ +C: 8:Aaaa: Old-style class defined. E: 11:Aaaa.__init__: Instance of 'Aaaa' has no '__name__' member E: 21:NewClass.__init__: Instance of 'NewClass' has no '__name__' member diff --git a/test/messages/func_bad_open_mode.txt b/test/messages/func_bad_open_mode.txt new file mode 100644 index 000000000..d785d174a --- /dev/null +++ b/test/messages/func_bad_open_mode.txt @@ -0,0 +1,7 @@ +W: 6: "rw" is not a valid mode for open. +W: 7: "rw" is not a valid mode for open. +W: 8: "rw" is not a valid mode for open. +W: 9: "U+" is not a valid mode for open. +W: 10: "rb+" is not a valid mode for open. +W: 11: "Uw" is not a valid mode for open. +W: 15: "Uwz" is not a valid mode for open. diff --git a/test/messages/func_bad_str_strip_call_py30.txt b/test/messages/func_bad_str_strip_call_py30.txt new file mode 100644 index 000000000..8ea0372b5 --- /dev/null +++ b/test/messages/func_bad_str_strip_call_py30.txt @@ -0,0 +1,3 @@ +E: 7: Suspicious argument in str.strip call +E: 8: Suspicious argument in str.lstrip call +E: 9: Suspicious argument in bytes.rstrip call diff --git a/test/messages/func_dangerous_default_py30.txt b/test/messages/func_dangerous_default_py30.txt new file mode 100644 index 000000000..b0681497a --- /dev/null +++ b/test/messages/func_dangerous_default_py30.txt @@ -0,0 +1,6 @@ +W: 7:function1: Dangerous default value [] as argument +W: 11:function2: Dangerous default value HEHE ({}) as argument +W: 19:function4: Dangerous default value set() (builtins.set) as argument +W: 29:function6: Dangerous default value GLOBAL_SET (builtins.set) as argument +W: 33:function7: Dangerous default value dict() (builtins.dict) as argument +W: 37:function8: Dangerous default value list() (builtins.list) as argument diff --git a/test/messages/func_deprecated_lambda.txt b/test/messages/func_deprecated_lambda_py_30.txt index e7ecd5bda..e7ecd5bda 100644 --- a/test/messages/func_deprecated_lambda.txt +++ b/test/messages/func_deprecated_lambda_py_30.txt diff --git a/test/messages/func_disable_linebased_py30.txt b/test/messages/func_disable_linebased_py30.txt new file mode 100644 index 000000000..7054dfc56 --- /dev/null +++ b/test/messages/func_disable_linebased_py30.txt @@ -0,0 +1,2 @@ +C: 1: Line too long (127/80) +C: 14: Line too long (114/80) diff --git a/test/messages/func_docstring.txt b/test/messages/func_docstring.txt index 716de4c1d..2b0b1919c 100644 --- a/test/messages/func_docstring.txt +++ b/test/messages/func_docstring.txt @@ -1,4 +1,4 @@ -C: 1: Missing docstring -C: 5:function1: Missing docstring -C: 17:AAAA: Missing docstring -C: 33:AAAA.method1: Missing docstring +C: 1: Missing module docstring +C: 5:function1: Missing function docstring +C: 17:AAAA: Missing class docstring +C: 33:AAAA.method1: Missing method docstring diff --git a/test/messages/func_empty_module.txt b/test/messages/func_empty_module.txt index 4f0d347b2..bbc5a0d8b 100644 --- a/test/messages/func_empty_module.txt +++ b/test/messages/func_empty_module.txt @@ -1,2 +1,2 @@ -C: 1: Missing docstring +C: 1: Missing module docstring C: 1: Missing required attribute "__revision__" diff --git a/test/messages/func_excess_escapes.txt b/test/messages/func_excess_escapes.txt index 173b108de..2f07722cf 100644 --- a/test/messages/func_excess_escapes.txt +++ b/test/messages/func_excess_escapes.txt @@ -7,6 +7,3 @@ W: 15: Anomalous backslash in string: '\o'. String constant might be missing an W: 17: Anomalous backslash in string: '\8'. String constant might be missing an r prefix. W: 17: Anomalous backslash in string: '\9'. String constant might be missing an r prefix. W: 27: Anomalous backslash in string: '\P'. String constant might be missing an r prefix. -W: 33: Anomalous Unicode escape in byte string: '\u'. String constant might be missing an r or u prefix. -W: 34: Anomalous Unicode escape in byte string: '\U'. String constant might be missing an r or u prefix. -W: 35: Anomalous Unicode escape in byte string: '\N'. String constant might be missing an r or u prefix. diff --git a/test/messages/func_exec_used_py30.txt b/test/messages/func_exec_used_py30.txt new file mode 100644 index 000000000..362da6842 --- /dev/null +++ b/test/messages/func_exec_used_py30.txt @@ -0,0 +1,4 @@ +W: 5: Use of exec
+W: 6: Use of exec
+W: 8: Use of exec
+W: 12:func: Use of exec
diff --git a/test/messages/func_globals.txt b/test/messages/func_globals.txt index 46ea4b19f..d1bb53df0 100644 --- a/test/messages/func_globals.txt +++ b/test/messages/func_globals.txt @@ -3,4 +3,4 @@ E: 32:other: Undefined variable 'HOP' W: 23:fix_contant: Using the global statement W: 26: Using the global statement at the module level W: 31:other: Using global for 'HOP' but no assignment is done -W: 39:define_constant: Global variable 'somevar' undefined at the module level +W: 39:define_constant: Global variable 'SOMEVAR' undefined at the module level diff --git a/test/messages/func_i0011.txt b/test/messages/func_i0011.txt index 4b5ef231f..87c1f1f8a 100644 --- a/test/messages/func_i0011.txt +++ b/test/messages/func_i0011.txt @@ -1,2 +1,2 @@ I: 1: Locally disabling W0404 -I: 1: Useless suppression of W0404 +I: 1: Useless suppression of 'reimported' diff --git a/test/messages/func_i0020.txt b/test/messages/func_i0020.txt index 5a9ef9737..e25be4ec4 100644 --- a/test/messages/func_i0020.txt +++ b/test/messages/func_i0020.txt @@ -1,2 +1,2 @@ I: 7: Locally disabling W0612 -I: 8: Suppressed W0612 (from line 7) +I: 8: Suppressed 'unused-variable' (from line 7) diff --git a/test/messages/func_import_syntax_error.txt b/test/messages/func_import_syntax_error.txt index 46827a210..8e2a4949e 100644 --- a/test/messages/func_import_syntax_error.txt +++ b/test/messages/func_import_syntax_error.txt @@ -1,4 +1,4 @@ -C: 1: Missing docstring +C: 1: Missing module docstring C: 1: Missing required attribute "__revision__" F: 1: Unable to import 'syntax_error' (expected an indented block (<string>, line 2)) W: 1: Unused import syntax_error diff --git a/test/messages/func_import_syntax_error_py30.txt b/test/messages/func_import_syntax_error_py30.txt new file mode 100644 index 000000000..3c5a95a39 --- /dev/null +++ b/test/messages/func_import_syntax_error_py30.txt @@ -0,0 +1,4 @@ +C: 1: Missing module docstring +C: 1: Missing required attribute "__revision__" +E: 1: No name 'syntax_error' in module 'input' +W: 1: Unused import syntax_error diff --git a/test/messages/func_invalid_encoded_data.txt b/test/messages/func_invalid_encoded_data.txt new file mode 100644 index 000000000..4a5c17e25 --- /dev/null +++ b/test/messages/func_invalid_encoded_data.txt @@ -0,0 +1 @@ +W: 6: Cannot decode using encoding "utf-8", unexpected byte at position 11 diff --git a/test/messages/func_kwoa_py30.txt b/test/messages/func_kwoa_py30.txt new file mode 100644 index 000000000..5ccdf00e5 --- /dev/null +++ b/test/messages/func_kwoa_py30.txt @@ -0,0 +1,5 @@ +E: 10: Missing mandatory keyword argument 'foo' +E: 10: Too many positional arguments for function call +E: 12: Missing mandatory keyword argument 'foo' +E: 12: Too many positional arguments for function call +W: 3:function: Redefining name 'foo' from outer scope (line 9) diff --git a/test/messages/func_missing_super_argument_py20.txt b/test/messages/func_missing_super_argument_py20.txt new file mode 100644 index 000000000..5c97f0d8f --- /dev/null +++ b/test/messages/func_missing_super_argument_py20.txt @@ -0,0 +1,2 @@ +E: 8:MyClass.__init__: Missing argument to super() +R: 5:MyClass: Too few public methods (0/2) diff --git a/test/messages/func_name_checking.txt b/test/messages/func_name_checking.txt new file mode 100644 index 000000000..e8d0a73f3 --- /dev/null +++ b/test/messages/func_name_checking.txt @@ -0,0 +1,18 @@ +C: 10:Run.B: Invalid class name "B" +C: 14:Run: Invalid variable name "bBb" +C: 28:HOHOHOHO: Invalid function name "HOHOHOHO" +C: 30:HOHOHOHO: Invalid variable name "HIHIHI" +C: 33:xyz: Invalid class name "xyz" +C: 36:xyz: Invalid class attribute name "zz" +C: 41:xyz.Youplapoum: Invalid method name "Youplapoum" +C: 56: Invalid constant name "benpasceluila" +C: 62:Correct.__init__: Invalid attribute name "_Ca_va_Pas" +C: 64:Correct.BadMethodName: Invalid method name "BadMethodName" +C: 77: Invalid class name "BAD_NAME_FOR_CLASS" +C: 78: Invalid class name "NEXT_BAD_NAME_FOR_CLASS" +C: 84: Invalid class name "NOT_CORRECT" +C: 90:test_globals: Invalid constant name "AlsoCorrect" +C:118:FooClass.PROPERTY_NAME: Invalid attribute name "PROPERTY_NAME" +C:123:FooClass.ABSTRACT_PROPERTY_NAME: Invalid attribute name "ABSTRACT_PROPERTY_NAME" +C:128:FooClass.PROPERTY_NAME_SETTER: Invalid attribute name "PROPERTY_NAME_SETTER" +C:133:func_bad_argname: Invalid argument name "NOT_GOOD" diff --git a/test/messages/func_newstyle___slots__.txt b/test/messages/func_newstyle___slots__.txt index 87b495873..cb69e14c2 100644 --- a/test/messages/func_newstyle___slots__.txt +++ b/test/messages/func_newstyle___slots__.txt @@ -1 +1,2 @@ +C: 10:HaNonNonNon: Old-style class defined. E: 10:HaNonNonNon: Use of __slots__ on an old style class diff --git a/test/messages/func_newstyle_exceptions.txt b/test/messages/func_newstyle_exceptions.txt index 72166ba55..305326c75 100644 --- a/test/messages/func_newstyle_exceptions.txt +++ b/test/messages/func_newstyle_exceptions.txt @@ -3,3 +3,5 @@ E: 33:fonctionNew2: Raising a new style class which doesn't inherit from BaseExc E: 37:fonctionNotImplemented: NotImplemented raised - should raise NotImplementedError W: 21:fonctionBof: Exception doesn't inherit from standard "Exception" class W: 29:fonctionBof2: Exception doesn't inherit from standard "Exception" class +W: 29:fonctionBof2: Use raise ErrorClass(args) instead of raise ErrorClass, args. +W: 37:fonctionNotImplemented: Use raise ErrorClass(args) instead of raise ErrorClass, args. diff --git a/test/messages/func_newstyle_exceptions_py30.txt b/test/messages/func_newstyle_exceptions_py30.txt new file mode 100644 index 000000000..72166ba55 --- /dev/null +++ b/test/messages/func_newstyle_exceptions_py30.txt @@ -0,0 +1,5 @@ +E: 25:fonctionNew: Raising a new style class which doesn't inherit from BaseException +E: 33:fonctionNew2: Raising a new style class which doesn't inherit from BaseException +E: 37:fonctionNotImplemented: NotImplemented raised - should raise NotImplementedError +W: 21:fonctionBof: Exception doesn't inherit from standard "Exception" class +W: 29:fonctionBof2: Exception doesn't inherit from standard "Exception" class diff --git a/test/messages/func_newstyle_property.txt b/test/messages/func_newstyle_property.txt index f0ef26145..ba6018f7c 100644 --- a/test/messages/func_newstyle_property.txt +++ b/test/messages/func_newstyle_property.txt @@ -1 +1,2 @@ +C: 14:HaNonNonNon: Old-style class defined. W: 16:HaNonNonNon: Use of "property" on an old style class diff --git a/test/messages/func_newstyle_super.txt b/test/messages/func_newstyle_super.txt index e31a9176d..a0192d7c1 100644 --- a/test/messages/func_newstyle_super.txt +++ b/test/messages/func_newstyle_super.txt @@ -1,4 +1,6 @@ +C: 5:Aaaa: Old-style class defined. E: 7:Aaaa.hop: Use of super on an old style class E: 11:Aaaa.__init__: Use of super on an old style class -E: 20:NewAaaa.__init__: Bad first argument 'object' given to super class - +E: 21:NewAaaa.__init__: Bad first argument 'object' given to super() +E: 26:Py3kAaaa.__init__: Missing argument to super() +E: 31:Py3kWrongSuper.__init__: Bad first argument 'NewAaaa' given to super() diff --git a/test/messages/func_newstyle_super_py30.txt b/test/messages/func_newstyle_super_py30.txt new file mode 100644 index 000000000..4ed306cca --- /dev/null +++ b/test/messages/func_newstyle_super_py30.txt @@ -0,0 +1,5 @@ +C: 5:Aaaa: Old-style class defined. +E: 7:Aaaa.hop: Use of super on an old style class +E: 11:Aaaa.__init__: Use of super on an old style class +E: 21:NewAaaa.__init__: Bad first argument 'object' given to super() +E: 31:Py3kWrongSuper.__init__: Bad first argument 'NewAaaa' given to super() diff --git a/test/messages/func_no_dummy_redefined.txt b/test/messages/func_no_dummy_redefined.txt new file mode 100644 index 000000000..9ef7891eb --- /dev/null +++ b/test/messages/func_no_dummy_redefined.txt @@ -0,0 +1,2 @@ +C: 7: Invalid constant name "value" +W: 12:clobbering: Redefining name 'value' from outer scope (line 7) diff --git a/test/messages/func_no_final_new_line.txt b/test/messages/func_no_final_new_line.txt new file mode 100644 index 000000000..5040aa710 --- /dev/null +++ b/test/messages/func_no_final_new_line.txt @@ -0,0 +1 @@ +C: 2: Final newline missing diff --git a/test/messages/func_r0901.txt b/test/messages/func_r0901.txt index d20460e22..1f4007f75 100644 --- a/test/messages/func_r0901.txt +++ b/test/messages/func_r0901.txt @@ -1,2 +1,2 @@ -R: 22:Iiii: Too many ancestors (8/7) -R: 25:Jjjj: Too many ancestors (9/7) +R: 22:Iiii: Too many ancestors (9/7) +R: 25:Jjjj: Too many ancestors (10/7) diff --git a/test/messages/func_raw_escapes.txt b/test/messages/func_raw_escapes.txt new file mode 100644 index 000000000..991fba7dd --- /dev/null +++ b/test/messages/func_raw_escapes.txt @@ -0,0 +1,3 @@ +W: 5: Anomalous Unicode escape in byte string: '\u'. String constant might be missing an r or u prefix. +W: 6: Anomalous Unicode escape in byte string: '\U'. String constant might be missing an r or u prefix. +W: 7: Anomalous Unicode escape in byte string: '\N'. String constant might be missing an r or u prefix. diff --git a/test/messages/func_special_methods.txt b/test/messages/func_special_methods.txt index 3add213b7..f7e88ae92 100644 --- a/test/messages/func_special_methods.txt +++ b/test/messages/func_special_methods.txt @@ -1,5 +1,6 @@ R: 12:BadContextManager: Badly implemented Context manager, implements __enter__ but not __exit__ R: 12:BadContextManager: Too few public methods (0/2) -R: 32:BadContainer: Badly implemented Container, implements __len__, __setitem__ but not __delitem__, __getitem__ -R: 32:BadContainer: Too few public methods (0/2) - +R: 32:BadROContainer: Badly implemented Container, implements __len__ but not __getitem__ +R: 32:BadROContainer: Badly implemented Mutable container, implements __setitem__ but not __delitem__ +R: 32:BadROContainer: Too few public methods (0/2) +R: 43:BadContainer: Badly implemented Mutable container, implements __setitem__ but not __delitem__ diff --git a/test/messages/func_tokenize_error.txt b/test/messages/func_tokenize_error.txt new file mode 100644 index 000000000..a6c5cda44 --- /dev/null +++ b/test/messages/func_tokenize_error.txt @@ -0,0 +1 @@ +E: 7: EOF in multi-line statement diff --git a/test/messages/func_toolonglines.txt b/test/messages/func_toolonglines.txt index 29b16c695..a9840230c 100644 --- a/test/messages/func_toolonglines.txt +++ b/test/messages/func_toolonglines.txt @@ -1,2 +1,6 @@ C: 1: Line too long (90/80) C: 2: Line too long (94/80) +C: 11: Line too long (101/80) +C: 17: Line too long (83/80) +C: 25: Line too long (94/80) +W: 11: Cannot decode using encoding "ascii", unexpected byte at position 37 diff --git a/test/messages/func_trailing_whitespace.txt b/test/messages/func_trailing_whitespace.txt new file mode 100644 index 000000000..51e4d52c7 --- /dev/null +++ b/test/messages/func_trailing_whitespace.txt @@ -0,0 +1,2 @@ +C: 5: Trailing whitespace +C: 6: Trailing whitespace diff --git a/test/messages/func_typecheck_getattr.txt b/test/messages/func_typecheck_getattr.txt index b6bf150f6..bd9122805 100644 --- a/test/messages/func_typecheck_getattr.txt +++ b/test/messages/func_typecheck_getattr.txt @@ -1,3 +1,4 @@ +C: 19:Client: Old-style class defined. E: 25:Client.__init__: Class 'Provider' has no 'cattribute' member E: 35:Client.use_method: Instance of 'Provider' has no 'hophophop' member E: 40:Client.use_attr: Instance of 'Provider' has no 'attribute' member diff --git a/test/messages/func_unbalanced_tuple_unpacking.txt b/test/messages/func_unbalanced_tuple_unpacking.txt new file mode 100644 index 000000000..3074a145b --- /dev/null +++ b/test/messages/func_unbalanced_tuple_unpacking.txt @@ -0,0 +1,3 @@ +W: 7:do_stuff: Possible unbalanced tuple unpacking: left side has 2 label(s), right side has 3 value(s) +W: 12:do_stuff1: Possible unbalanced tuple unpacking: left side has 2 label(s), right side has 3 value(s) +W: 17:do_stuff2: Possible unbalanced tuple unpacking: left side has 2 label(s), right side has 3 value(s) diff --git a/test/messages/func_unpack_exception_py27.txt b/test/messages/func_unpack_exception_py_30.txt index a09a265d3..aaff92fe8 100644 --- a/test/messages/func_unpack_exception_py27.txt +++ b/test/messages/func_unpack_exception_py_30.txt @@ -1,3 +1,5 @@ +W: 9:new_style: Implicit unpacking of exceptions is not supported in Python 3 +W: 11:new_style: Implicit unpacking of exceptions is not supported in Python 3 W: 11:new_style: Redefining name 'new_style' from outer scope (line 5) in exception handler W: 11:new_style: Redefining name 'tuple' from builtins in exception handler diff --git a/test/messages/func_utf8_lines.txt b/test/messages/func_utf8_lines.txt new file mode 100644 index 000000000..97067818a --- /dev/null +++ b/test/messages/func_utf8_lines.txt @@ -0,0 +1 @@ +C: 8: Line too long (89/80) diff --git a/test/messages/func_w0109.txt b/test/messages/func_w0109.txt index 4685d382c..bcb97aa10 100644 --- a/test/messages/func_w0109.txt +++ b/test/messages/func_w0109.txt @@ -1 +1 @@ -C: 6:function: Empty docstring +C: 6:function: Empty function docstring diff --git a/test/messages/func_w0110.txt b/test/messages/func_w0110.txt index d8d5ef892..b61400ecc 100644 --- a/test/messages/func_w0110.txt +++ b/test/messages/func_w0110.txt @@ -1 +1 @@ -C: 8:a: Invalid name "a" for type function (should match [a-z_][a-z0-9_]{2,30}$) +C: 8:a: Invalid function name "a" diff --git a/test/messages/func_w0122_py_30.txt b/test/messages/func_w0122_py_30.txt index 1522cac30..d833076d5 100644 --- a/test/messages/func_w0122_py_30.txt +++ b/test/messages/func_w0122_py_30.txt @@ -1,5 +1,5 @@ -W: 5: Use of the exec statement -W: 6: Use of the exec statement -W: 8: Use of the exec statement -W: 12:func: Use of the exec statement +W: 5: Use of exec +W: 6: Use of exec +W: 8: Use of exec +W: 12:func: Use of exec diff --git a/test/messages/func_w0133.txt b/test/messages/func_w0133.txt deleted file mode 100644 index 48c768d49..000000000 --- a/test/messages/func_w0133.txt +++ /dev/null @@ -1,9 +0,0 @@ -C: 8:Run.B: Invalid name "B" for type class (should match [A-Z_][a-zA-Z0-9]+$) -C: 12:Run: Invalid name "bBb" for type variable (should match [a-z_][a-z0-9_]{2,30}$) -C: 26:HOHOHOHO: Invalid name "HOHOHOHO" for type function (should match [a-z_][a-z0-9_]{2,30}$) -C: 28:HOHOHOHO: Invalid name "HIHIHI" for type variable (should match [a-z_][a-z0-9_]{2,30}$) -C: 31:xyz: Invalid name "xyz" for type class (should match [A-Z_][a-zA-Z0-9]+$) -C: 36:xyz.Youplapoum: Invalid name "Youplapoum" for type method (should match [a-z_][a-z0-9_]{2,30}$) -C: 46: Invalid name "benpasceluila" for type constant (should match (([A-Z_][A-Z0-9_]*)|(__.*__))$) -C: 52:Correct.__init__: Invalid name "_Ca_va_Pas" for type attribute (should match [a-z_][a-z0-9_]{2,30}$) -W: 8:Run.B: Unused variable 'B' diff --git a/test/messages/func_w0231.txt b/test/messages/func_w0231.txt index 44e4f11a2..8cbf0b734 100644 --- a/test/messages/func_w0231.txt +++ b/test/messages/func_w0231.txt @@ -1,3 +1,6 @@ +C: 7:AAAA: Old-style class defined. +C: 13:BBBB: Old-style class defined. +C: 19:CCCC: Old-style class defined. W: 19:CCCC: Class has no __init__ method W: 26:ZZZZ.__init__: __init__ method from base class 'BBBB' is not called W: 59:AssignedInit.__init__: __init__ method from base class 'NewStyleC' is not called diff --git a/test/messages/func_w0623.txt b/test/messages/func_w0623.txt index 7e2cb1f99..b764def81 100644 --- a/test/messages/func_w0623.txt +++ b/test/messages/func_w0623.txt @@ -1,6 +1,6 @@ -C: 28:some_function: Invalid name "FOO" for type variable (should match [a-z_][a-z0-9_]{2,30}$) -C: 41: Invalid name "exc3" for type constant (should match (([A-Z_][A-Z0-9_]*)|(__.*__))$) -C: 55: Invalid name "OOPS" for type variable (should match [a-z_][a-z0-9_]{2,30}$) +C: 28:some_function: Invalid variable name "FOO" +C: 41: Invalid constant name "exc3" +C: 55: Invalid variable name "OOPS" W: 18:some_function: Redefining name 'RuntimeError' from object 'exceptions' in exception handler W: 20:some_function: Redefining name 'OSError' from builtins in exception handler W: 20:some_function: Unused variable 'OSError' diff --git a/test/messages/func_w0623_py30.txt b/test/messages/func_w0623_py30.txt new file mode 100644 index 000000000..adcd2088e --- /dev/null +++ b/test/messages/func_w0623_py30.txt @@ -0,0 +1,3 @@ +W: 15:some_function: Except doesn't do anything +W: 15:some_function: Redefining name 'some_function' from outer scope (line 10) in exception handler +W: 15:some_function: Unused variable 'some_function' diff --git a/test/messages/func_w0623_py_30.txt b/test/messages/func_w0623_py_30.txt new file mode 100644 index 000000000..b764def81 --- /dev/null +++ b/test/messages/func_w0623_py_30.txt @@ -0,0 +1,11 @@ +C: 28:some_function: Invalid variable name "FOO" +C: 41: Invalid constant name "exc3" +C: 55: Invalid variable name "OOPS" +W: 18:some_function: Redefining name 'RuntimeError' from object 'exceptions' in exception handler +W: 20:some_function: Redefining name 'OSError' from builtins in exception handler +W: 20:some_function: Unused variable 'OSError' +W: 22:some_function: Redefining name 'MyError' from outer scope (line 7) in exception handler +W: 22:some_function: Unused variable 'MyError' +W: 45: Redefining name 'RuntimeError' from object 'exceptions' in exception handler +W: 47: Redefining name 'OSError' from builtins in exception handler +W: 49: Redefining name 'MyOtherError' from outer scope (line 36) in exception handler diff --git a/test/messages/func_w0701_py_30.txt b/test/messages/func_w0701_py_30.txt index 677443bec..d643517c0 100644 --- a/test/messages/func_w0701_py_30.txt +++ b/test/messages/func_w0701_py_30.txt @@ -1,3 +1,3 @@ W: 8:function1: Raising a string exception W: 12:function2: Raising a string exception - +W: 12:function2: Use raise ErrorClass(args) instead of raise ErrorClass, args. diff --git a/test/regrtest_data/classdoc_usage.py b/test/regrtest_data/classdoc_usage.py index ae8b9fe3f..7c30f7e95 100644 --- a/test/regrtest_data/classdoc_usage.py +++ b/test/regrtest_data/classdoc_usage.py @@ -2,10 +2,10 @@ __revision__ = None -class SomeClass: +class SomeClass(object): """cds""" doc = __doc__ - + def __init__(self): """only to make pylint happier""" diff --git a/test/regrtest_data/decimal_inference.py b/test/regrtest_data/decimal_inference.py index 1b97f60f0..b47bc1ca8 100644 --- a/test/regrtest_data/decimal_inference.py +++ b/test/regrtest_data/decimal_inference.py @@ -1,7 +1,7 @@ """hum E1011 on .prec member is justifiable since Context instance are built using setattr/locals :( -2007/02/17 update: .prec attribute is now detected by astng :o) +2007/02/17 update: .prec attribute is now detected by astroid :o) """ import decimal diff --git a/test/regrtest_data/import_package_subpackage_module.py b/test/regrtest_data/import_package_subpackage_module.py index a1d47476c..937c8c335 100644 --- a/test/regrtest_data/import_package_subpackage_module.py +++ b/test/regrtest_data/import_package_subpackage_module.py @@ -1,6 +1,6 @@ # pylint: disable=I0011,C0301,W0611 """I found some of my scripts trigger off an AttributeError in pylint -0.8.1 (with common 0.12.0 and astng 0.13.1). +0.8.1 (with common 0.12.0 and astroid 0.13.1). Traceback (most recent call last): File "/usr/bin/pylint", line 4, in ? @@ -10,18 +10,18 @@ Traceback (most recent call last): File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 412, in check self.check_file(filepath, modname, checkers) File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 426, in check_file - astng = self._check_file(filepath, modname, checkers) + astroid = self._check_file(filepath, modname, checkers) File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 450, in _check_file - self.check_astng_module(astng, checkers) - File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 494, in check_astng_module - self.astng_events(astng, [checker for checker in checkers - File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astng_events - self.astng_events(child, checkers, _reversed_checkers) - File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astng_events - self.astng_events(child, checkers, _reversed_checkers) - File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 508, in astng_events - checker.visit(astng) - File "/usr/lib/python2.4/site-packages/logilab/astng/utils.py", line 84, in visit + self.check_astroid_module(astroid, checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 494, in check_astroid_module + self.astroid_events(astroid, [checker for checker in checkers + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astroid_events + self.astroid_events(child, checkers, _reversed_checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astroid_events + self.astroid_events(child, checkers, _reversed_checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 508, in astroid_events + checker.visit(astroid) + File "/usr/lib/python2.4/site-packages/logilab/astroid/utils.py", line 84, in visit method(node) File "/usr/lib/python2.4/site-packages/pylint/checkers/variables.py", line 295, in visit_import self._check_module_attrs(node, module, name_parts[1:]) diff --git a/test/regrtest_data/module_global.py b/test/regrtest_data/module_global.py index 074b9cf5c..107d65289 100644 --- a/test/regrtest_data/module_global.py +++ b/test/regrtest_data/module_global.py @@ -3,5 +3,5 @@ """ __revision__ = 1 -global bar -bar.foo +global GLOBAL_VAR +GLOBAL_VAR.foo diff --git a/test/regrtest_data/pygtk_enum_crash.py b/test/regrtest_data/pygtk_enum_crash.py index 8ac9e570a..471afe313 100644 --- a/test/regrtest_data/pygtk_enum_crash.py +++ b/test/regrtest_data/pygtk_enum_crash.py @@ -4,8 +4,8 @@ import gtk def print_some_constant(arg=gtk.BUTTONS_OK): - """crash because gtk.BUTTONS_OK, a gtk enum type, is returned by astng as a - constant + """crash because gtk.BUTTONS_OK, a gtk enum type, is returned by + astroid as a constant """ print arg diff --git a/test/smoketest.py b/test/smoketest.py index 30d5712cf..25f30fd95 100644 --- a/test/smoketest.py +++ b/test/smoketest.py @@ -55,29 +55,19 @@ class RunTC(TestCase): @tag('smoke') def test1(self): """make pylint checking itself""" - self._runtest(['--include-ids=y', 'pylint.lint'], reporter=TextReporter(StringIO())) + self._runtest(['pylint.lint'], reporter=TextReporter(StringIO())) @tag('smoke') def test2(self): """make pylint checking itself""" - self._runtest(['pylint.lint'], reporter=ParseableTextReporter(StringIO())) - - @tag('smoke') - def test3(self): - """make pylint checking itself""" self._runtest(['pylint.lint'], reporter=HTMLReporter(StringIO())) @tag('smoke') - def test4(self): + def test3(self): """make pylint checking itself""" self._runtest(['pylint.lint'], reporter=ColorizedTextReporter(StringIO())) @tag('smoke') - def test5(self): - """make pylint checking itself""" - self._runtest(['pylint.lint'], reporter=VSTextReporter(StringIO())) - - @tag('smoke') def test_no_ext_file(self): self._runtest([join(HERE, 'input', 'noext')], code=0) diff --git a/test/test_base.py b/test/test_base.py new file mode 100644 index 000000000..9bd3aa504 --- /dev/null +++ b/test/test_base.py @@ -0,0 +1,135 @@ +"""Unittest for the base checker.""" + +import re + +from astroid import test_utils +from pylint.checkers import base +from pylint.testutils import CheckerTestCase, Message + + +class DocstringTest(CheckerTestCase): + CHECKER_CLASS = base.DocStringChecker + + def testMissingDocstringModule(self): + module = test_utils.build_module("") + with self.assertAddsMessages(Message('missing-docstring', node=module, args=('module',))): + self.checker.visit_module(module) + + def testEmptyDocstringModule(self): + module = test_utils.build_module("''''''") + with self.assertAddsMessages(Message('empty-docstring', node=module, args=('module',))): + self.checker.visit_module(module) + + def testEmptyDocstringFunction(self): + func = test_utils.extract_node(""" + def func(tion): + pass""") + with self.assertAddsMessages(Message('missing-docstring', node=func, args=('function',))): + self.checker.visit_function(func) + + def testShortFunctionNoDocstring(self): + self.checker.config.docstring_min_length = 2 + func = test_utils.extract_node(""" + def func(tion): + pass""") + with self.assertNoMessages(): + self.checker.visit_function(func) + + def testFunctionNoDocstringByName(self): + self.checker.config.docstring_min_length = 2 + func = test_utils.extract_node(""" + def __fun__(tion): + pass""") + with self.assertNoMessages(): + self.checker.visit_function(func) + + def testClassNoDocstring(self): + klass = test_utils.extract_node(""" + class Klass(object): + pass""") + with self.assertAddsMessages(Message('missing-docstring', node=klass, args=('class',))): + self.checker.visit_class(klass) + + +class NameCheckerTest(CheckerTestCase): + CHECKER_CLASS = base.NameChecker + CONFIG = { + 'bad_names': set(), + } + + def testPropertyNames(self): + # If a method is annotated with @property, it's name should + # match the attr regex. Since by default the attribute regex is the same + # as the method regex, we override it here. + self.checker.config.attr_rgx = re.compile('[A-Z]+') + methods = test_utils.extract_node(""" + import abc + + class FooClass(object): + @property + def FOO(self): #@ + pass + + @property + def bar(self): #@ + pass + + @abc.abstractproperty + def BAZ(self): #@ + pass + """) + with self.assertNoMessages(): + self.checker.visit_function(methods[0]) + self.checker.visit_function(methods[2]) + with self.assertAddsMessages(Message('invalid-name', node=methods[1], + args=('attribute', 'bar'))): + self.checker.visit_function(methods[1]) + + def testPropertySetters(self): + self.checker.config.attr_rgx = re.compile('[A-Z]+') + method = test_utils.extract_node(""" + class FooClass(object): + @property + def foo(self): pass + + @foo.setter + def FOOSETTER(self): #@ + pass + """) + with self.assertNoMessages(): + self.checker.visit_function(method) + + def testModuleLevelNames(self): + assign = test_utils.extract_node(""" + import collections + Class = collections.namedtuple("a", ("b", "c")) #@ + """) + with self.assertNoMessages(): + self.checker.visit_assname(assign.targets[0]) + + assign = test_utils.extract_node(""" + class ClassA(object): + pass + ClassB = ClassA + """) + with self.assertNoMessages(): + self.checker.visit_assname(assign.targets[0]) + + module = test_utils.build_module(""" + def A(): + return 1, 2, 3 + CONSTA, CONSTB, CONSTC = A() + CONSTD = A()""") + with self.assertNoMessages(): + self.checker.visit_assname(module.body[1].targets[0].elts[0]) + self.checker.visit_assname(module.body[2].targets[0]) + + assign = test_utils.extract_node(""" + CONST = "12 34 ".rstrip().split()""") + with self.assertNoMessages(): + self.checker.visit_assname(assign.targets[0]) + + +if __name__ == '__main__': + from logilab.common.testlib import unittest_main + unittest_main() diff --git a/test/test_func.py b/test/test_func.py index c71ee380b..56bb82c7e 100644 --- a/test/test_func.py +++ b/test/test_func.py @@ -27,7 +27,6 @@ from logilab.common import testlib from pylint.testutils import (make_tests, LintTestUsingModule, LintTestUsingFile, cb_test_gen, linter, test_reporter) -PY26 = sys.version_info >= (2, 6) PY3K = sys.version_info >= (3, 0) # Configure paths @@ -36,9 +35,11 @@ MSG_DIR = join(dirname(abspath(__file__)), 'messages') # Classes +quote = "'" if sys.version_info >= (3, 3) else '' + class LintTestNonExistentModuleTC(LintTestUsingModule): module = 'nonexistent' - _get_expected = lambda self: 'F: 1: No module named nonexistent\n' + _get_expected = lambda self: 'F: 1: No module named %snonexistent%s\n' % (quote, quote) tags = testlib.Tags(('generated','pylint_input_%s' % module)) class LintTestNonExistentFileTC(LintTestUsingFile): @@ -61,17 +62,13 @@ class TestTests(testlib.TestCase): continue todo.sort() if PY3K: - rest = ['E1122', 'I0001', - # deprecated exec statement removed from py3k : - 'W0122', + rest = ['I0001', # XXX : no use case for now : 'W0402', # deprecated module 'W0403', # implicit relative import 'W0410', # __future__ import not first statement ] self.assertEqual(todo, rest) - elif PY26: - self.assertEqual(todo, ['E1122', 'I0001']) else: self.assertEqual(todo, ['I0001']) @@ -126,6 +123,9 @@ def suite(): return testlib.TestSuite([unittest.makeSuite(test, suiteClass=testlib.TestSuite) for test in gen_tests(FILTER_RGX)]) +del LintTestUsingModule +del LintTestUsingFile + if __name__=='__main__': if '-m' in sys.argv: MODULES_ONLY = True diff --git a/test/test_misc.py b/test/test_misc.py new file mode 100644 index 000000000..68f3e4a3f --- /dev/null +++ b/test/test_misc.py @@ -0,0 +1,61 @@ +# Copyright 2013 Google Inc. All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# 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. +""" +Tests for the misc checker. +""" + +import tempfile + +from logilab.common.testlib import unittest_main +from astroid import test_utils +from pylint.checkers import misc +from pylint.testutils import CheckerTestCase, Message + + +class FixmeTest(CheckerTestCase): + CHECKER_CLASS = misc.EncodingChecker + + def create_file_backed_module(self, code): + tmp = tempfile.NamedTemporaryFile() + tmp.write(code) + tmp.flush() + module = test_utils.build_module(code) + module.file = tmp.name + # Just make sure to keep a reference to the file + # so it isn't deleted. + module._tmpfile = tmp + return module + + def test_fixme(self): + module = self.create_file_backed_module( + """a = 1 + # FIXME + """) + with self.assertAddsMessages( + Message(msg_id='W0511', line=2, args=u'FIXME')): + self.checker.process_module(module) + + def test_emtpy_fixme_regex(self): + self.checker.config.notes = [] + module = self.create_file_backed_module( + """a = 1 + # fixme + """) + with self.assertNoMessages(): + self.checker.process_module(module) + + +if __name__ == '__main__': + unittest_main() diff --git a/test/test_regr.py b/test/test_regr.py index c124858ac..93aef0685 100644 --- a/test/test_regr.py +++ b/test/test_regr.py @@ -163,7 +163,7 @@ class NonRegrTC(TestCase): def test_absolute_import(self): linter.check(join(REGR_DATA, 'absimp', 'string.py')) got = linter.reporter.finalize().strip() - self.assertEqual(got, "W: 6: Uses of a deprecated module 'string'") + self.assertEqual(got, "") if __name__ == '__main__': unittest_main() diff --git a/test/test_utils.py b/test/test_utils.py new file mode 100644 index 000000000..fc35c3e29 --- /dev/null +++ b/test/test_utils.py @@ -0,0 +1,63 @@ +# Copyright 2013 Google Inc. +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# 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. +from logilab.common.testlib import TestCase + +from astroid import test_utils +from pylint import utils +from pylint.checkers.utils import check_messages + + +class PyLintASTWalkerTest(TestCase): + class MockLinter(object): + def __init__(self, msgs): + self._msgs = msgs + + def is_message_enabled(self, msgid): + return self._msgs.get(msgid, True) + + class Checker(object): + def __init__(self): + self.called = set() + + @check_messages('first-message') + def visit_module(self, module): + self.called.add('module') + + @check_messages('second-message') + def visit_callfunc(self, module): + raise NotImplementedError + + @check_messages('second-message', 'third-message') + def visit_assname(self, module): + self.called.add('assname') + + @check_messages('second-message') + def leave_assname(self, module): + raise NotImplementedError + + def testCheckMessages(self): + linter = self.MockLinter({'first-message': True, + 'second-message': False, + 'third-message': True}) + walker = utils.PyLintASTWalker(linter) + checker = self.Checker() + walker.add_checker(checker) + walker.walk(test_utils.build_module("x = func()")) + self.assertEqual(set(['module', 'assname']), checker.called) + + +if __name__ == '__main__': + from logilab.common.testlib import unittest_main + unittest_main() + diff --git a/test/unittest_checkers_utils.py b/test/unittest_checkers_utils.py index 61c1bc211..683f6b986 100644 --- a/test/unittest_checkers_utils.py +++ b/test/unittest_checkers_utils.py @@ -21,6 +21,8 @@ __revision__ = '$Id: unittest_checkers_utils.py,v 1.6 2005-11-02 09:22:07 syt Ex import unittest import sys +from astroid import test_utils + from pylint.checkers import utils try: __builtins__.mybuiltin = 2 @@ -44,6 +46,27 @@ class UtilsTC(unittest.TestCase): self.assertEqual(utils.is_builtin('whatever'), False) self.assertEqual(utils.is_builtin('mybuiltin'), False) + def testGetArgumentFromCall(self): + node = test_utils.extract_node('foo(bar=3)') + self.assertIsNotNone(utils.get_argument_from_call(node, keyword='bar')) + with self.assertRaises(utils.NoSuchArgumentError): + node = test_utils.extract_node('foo(3)') + utils.get_argument_from_call(node, keyword='bar') + with self.assertRaises(utils.NoSuchArgumentError): + node = test_utils.extract_node('foo(one=a, two=b, three=c)') + utils.get_argument_from_call(node, position=1) + node = test_utils.extract_node('foo(a, b, c)') + self.assertIsNotNone(utils.get_argument_from_call(node, position=1)) + node = test_utils.extract_node('foo(a, not_this_one=1, this_one=2)') + arg = utils.get_argument_from_call(node, position=1, keyword='this_one') + self.assertEqual(2, arg.value) + node = test_utils.extract_node('foo(a)') + with self.assertRaises(utils.NoSuchArgumentError): + utils.get_argument_from_call(node, position=1) + with self.assertRaises(ValueError): + utils.get_argument_from_call(node, None, None) + + if __name__ == '__main__': unittest.main() diff --git a/test/unittest_lint.py b/test/unittest_lint.py index 94c0b0343..a65eb01fd 100644 --- a/test/unittest_lint.py +++ b/test/unittest_lint.py @@ -1,4 +1,4 @@ -# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later @@ -28,7 +28,8 @@ from pylint.lint import PyLinter, Run, UnknownMessage, preprocess_options, \ ArgumentPreprocessingError from pylint.utils import sort_msgs, PyLintASTWalker, MSG_STATE_SCOPE_CONFIG, \ MSG_STATE_SCOPE_MODULE, tokenize_module - +from pylint.testutils import TestReporter +from pylint.reporters import text from pylint import checkers class SortMessagesTC(TestCase): @@ -42,12 +43,6 @@ class SortMessagesTC(TestCase): 'I0001', 'I0201', 'F0002', 'F0203',]) -try: - optimized = True - raise AssertionError -except AssertionError: - optimized = False - class GetNoteMessageTC(TestCase): def test(self): msg = None @@ -55,8 +50,6 @@ class GetNoteMessageTC(TestCase): note_msg = config.get_note_message(note) self.assertNotEqual(msg, note_msg) msg = note_msg - if optimized: - self.assertRaises(AssertionError, config.get_note_message, 11) HERE = abspath(dirname(__file__)) @@ -88,6 +81,7 @@ class PyLinterTC(TestCase): self.linter.config.persistent = 0 # register checkers checkers.initialize(self.linter) + self.linter.set_reporter(TestReporter()) def test_message_help(self): msg = self.linter.get_message_help('F0001', checkerref=True) @@ -153,12 +147,12 @@ class PyLinterTC(TestCase): linter.open() filepath = join(INPUTDIR, 'func_block_disable_msg.py') linter.set_current_module('func_block_disable_msg') - astng = linter.get_astng(filepath, 'func_block_disable_msg') - linter.process_tokens(tokenize_module(astng)) + astroid = linter.get_astroid(filepath, 'func_block_disable_msg') + linter.process_tokens(tokenize_module(astroid)) orig_state = linter._module_msgs_state.copy() linter._module_msgs_state = {} linter._suppression_mapping = {} - linter.collect_block_lines(astng, orig_state) + linter.collect_block_lines(astroid, orig_state) # global (module level) self.assertTrue(linter.is_message_enabled('W0613')) self.assertTrue(linter.is_message_enabled('E1101')) @@ -244,9 +238,10 @@ class PyLinterTC(TestCase): finally: sys.stdout = sys.__stdout__ # cursory examination of the output: we're mostly testing it completes - self.assertTrue(':C0112 (empty-docstring): *Empty docstring*' in output) + self.assertTrue(':C0112 (empty-docstring): *Empty %s docstring*' in output) def test_lint_ext_module_with_file_output(self): + self.linter.set_reporter(text.TextReporter()) if sys.version_info < (3, 0): strio = 'StringIO' else: @@ -272,6 +267,7 @@ class PyLinterTC(TestCase): self.assertEqual(self.linter.report_is_enabled('RP0001'), True) def test_report_output_format_aliased(self): + text.register(self.linter) self.linter.set_option('output-format', 'text') self.assertEqual(self.linter.reporter.__class__.__name__, 'TextReporter') @@ -314,10 +310,10 @@ class PyLinterTC(TestCase): linter = self.linter self.linter.error_mode() checkers = self.linter.prepare_checkers() - checker_names = tuple(c.name for c in checkers) - should_not = ('design', 'format', 'imports', 'metrics', - 'miscellaneous', 'similarities') - self.assertFalse(any(name in checker_names for name in should_not)) + checker_names = set(c.name for c in checkers) + should_not = set(('design', 'format', 'imports', 'metrics', + 'miscellaneous', 'similarities')) + self.assertSetEqual(set(), should_not & checker_names) def test_disable_similar(self): self.linter.set_option('disable', 'RP0801') @@ -333,6 +329,16 @@ class PyLinterTC(TestCase): 'imports'): # as a Fatal message that should be ignored self.assertFalse(cname in checker_names, cname) + def test_addmessage(self): + self.linter.set_reporter(TestReporter()) + self.linter.open() + self.linter.set_current_module('0123') + self.linter.add_message('C0301', line=1, args=(1, 2)) + self.linter.add_message('line-too-long', line=2, args=(3, 4)) + self.assertEqual( + ['C: 1: Line too long (1/2)', 'C: 2: Line too long (3/4)'], + self.linter.reporter.messages) + class ConfigTC(TestCase): diff --git a/test/unittest_pyreverse_diadefs.py b/test/unittest_pyreverse_diadefs.py index 21ded4758..ebcf371e1 100644 --- a/test/unittest_pyreverse_diadefs.py +++ b/test/unittest_pyreverse_diadefs.py @@ -20,25 +20,25 @@ unittest for the extensions.diadefslib modules import unittest import sys -from logilab import astng -from logilab.astng import MANAGER -from logilab.astng.inspector import Linker +import astroid +from astroid import MANAGER +from astroid.inspector import Linker from pylint.pyreverse.diadefslib import * from utils import Config -def astng_wrapper(func, modname): +def astroid_wrapper(func, modname): return func(modname) -PROJECT = MANAGER.project_from_files(['data'], astng_wrapper) +PROJECT = MANAGER.project_from_files(['data'], astroid_wrapper) CONFIG = Config() HANDLER = DiadefsHandler(CONFIG) def _process_classes(classes): """extract class names of a list""" - return sorted([(isinstance(c.node, astng.Class), c.title) for c in classes]) + return sorted([(isinstance(c.node, astroid.Class), c.title) for c in classes]) def _process_relations(relations): """extract relation indices from a relation list""" @@ -95,7 +95,7 @@ class DefaultDiadefGeneratorTC(unittest.TestCase): self.assertEqual(keys, ['package', 'class']) pd = dd[0] self.assertEqual(pd.title, 'packages No Name') - modules = sorted([(isinstance(m.node, astng.Module), m.title) + modules = sorted([(isinstance(m.node, astroid.Module), m.title) for m in pd.objects]) self.assertEqual(modules, [(True, 'data'), (True, 'data.clientmodule_test'), @@ -125,7 +125,7 @@ class DefaultDiadefGeneratorTC(unittest.TestCase): different classes possibly in different modules""" # XXX should be catching pyreverse environnement problem but doesn't # pyreverse doesn't extracts the relations but this test ok - project = MANAGER.project_from_files(['data'], astng_wrapper) + project = MANAGER.project_from_files(['data'], astroid_wrapper) handler = DiadefsHandler( Config() ) diadefs = handler.get_diadefs(project, Linker(project, tag=True) ) cd = diadefs[1] @@ -133,7 +133,7 @@ class DefaultDiadefGeneratorTC(unittest.TestCase): self.assertEqual(relations, self._should_rels) def test_known_values2(self): - project = MANAGER.project_from_files(['data.clientmodule_test'], astng_wrapper) + project = MANAGER.project_from_files(['data.clientmodule_test'], astroid_wrapper) dd = DefaultDiadefGenerator(Linker(project), HANDLER).visit(project) self.assertEqual(len(dd), 1) keys = [d.TYPE for d in dd] diff --git a/test/unittest_pyreverse_writer.py b/test/unittest_pyreverse_writer.py index 3ccc231f6..a4e283e91 100644 --- a/test/unittest_pyreverse_writer.py +++ b/test/unittest_pyreverse_writer.py @@ -18,7 +18,7 @@ unittest for visitors.diadefs and extensions.diadefslib modules """ from os.path import abspath, dirname, join -from logilab.astng.inspector import Linker +from astroid.inspector import Linker from logilab.common.testlib import TestCase, unittest_main from pylint.pyreverse.diadefslib import DefaultDiadefGenerator, DiadefsHandler diff --git a/test/unittest_reporting.py b/test/unittest_reporting.py new file mode 100644 index 000000000..d33867009 --- /dev/null +++ b/test/unittest_reporting.py @@ -0,0 +1,66 @@ +# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# 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. + +import sys +import os +import tempfile +from shutil import rmtree +from os import getcwd, chdir +from os.path import join, basename, dirname, isdir, abspath +from cStringIO import StringIO + +from logilab.common.testlib import TestCase, unittest_main, create_files +from logilab.common.compat import reload + +from pylint import config +from pylint.lint import PyLinter, Run, UnknownMessage, preprocess_options, \ + ArgumentPreprocessingError +from pylint.utils import sort_msgs, PyLintASTWalker, MSG_STATE_SCOPE_CONFIG, \ + MSG_STATE_SCOPE_MODULE, tokenize_module +from pylint.testutils import TestReporter +from pylint import checkers +from pylint.reporters.text import TextReporter + +HERE = abspath(dirname(__file__)) +INPUTDIR = join(HERE, 'input') + +class PyLinterTC(TestCase): + + def setUp(self): + self.linter = PyLinter(reporter=TextReporter()) + self.linter.disable('I') + self.linter.config.persistent = 0 + # register checkers + checkers.initialize(self.linter) + os.environ.pop('PYLINTRC', None) + + def test_template_option(self): + # self.linter.set_reporter(TextReporter()) + expected = ( '************* Module 0123\n' + 'C0301:001\n' + 'C0301:002\n' + ) + + output = StringIO() + self.linter.reporter.set_output(output) + self.linter.set_option('msg-template', '{msg_id}:{line:03d}') + self.linter.open() + self.linter.set_current_module('0123') + self.linter.add_message('C0301', line=1, args=(1, 2)) + self.linter.add_message('line-too-long', line=2, args=(3, 4)) + self.assertMultiLineEqual(output.getvalue(), expected) + + +if __name__ == '__main__': + unittest_main() diff --git a/test/utils.py b/test/utils.py index 3cf169961..34e1d4449 100644 --- a/test/utils.py +++ b/test/utils.py @@ -4,17 +4,19 @@ import os import sys from os.path import join, dirname, abspath +import codecs from logilab.common.testlib import TestCase -from logilab.astng import MANAGER +from astroid import MANAGER -def _astng_wrapper(func, modname): +def _astroid_wrapper(func, modname): return func(modname) def _sorted_file(path): - lines = [line.strip() for line in open(path).readlines() + # we don't care about the actual encoding, but python3 forces us to pick one + lines = [line.strip() for line in codecs.open(path, encoding='latin1').readlines() if (line.find('squeleton generated by ') == -1 and not line.startswith('__revision__ = "$Id:'))] lines = [line for line in lines if line] @@ -22,12 +24,12 @@ def _sorted_file(path): return '\n'.join(lines) def get_project(module, name=None): - """return a astng project representation + """return a astroid project representation """ manager = MANAGER # flush cache manager._modules_by_name = {} - return manager.project_from_files([module], _astng_wrapper, + return manager.project_from_files([module], _astroid_wrapper, project_name=name) DEFAULTS = {'all_ancestors': None, 'show_associated': None, diff --git a/testutils.py b/testutils.py index 94d88d109..e939e5131 100644 --- a/testutils.py +++ b/testutils.py @@ -15,6 +15,8 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """functional/non regression tests for pylint""" +import collections +import contextlib import sys import re @@ -45,11 +47,11 @@ def get_tests_info(input_dir, msg_dir, prefix, suffix): We use following conventions for input files and messages: for different inputs: - don't test for python < x.y -> input = <name>_pyxy.py - don't test for python >= x.y -> input = <name>_py_xy.py + test for python >= x.y -> input = <name>_pyxy.py + test for python < x.y -> input = <name>_py_xy.py for one input and different messages: - message for python <= x.y -> message = <name>_pyxy.txt - higher versions -> message with highest num + message for python >= x.y -> message = <name>_pyxy.txt + lower versions -> message with highest num """ result = [] for fname in glob(join(input_dir, prefix + '*' + suffix)): @@ -110,8 +112,73 @@ class TestReporter(BaseReporter): def display_results(self, layout): """ignore layouts""" -# Init +class Message(collections.namedtuple('Message', + ['msg_id', 'line', 'node', 'args'])): + def __new__(cls, msg_id, line=None, node=None, args=None): + return tuple.__new__(cls, (msg_id, line, node, args)) + + +class UnittestLinter(object): + """A fake linter class to capture checker messages.""" + + def __init__(self): + self._messages = [] + self.stats = {} + + def release_messages(self): + try: + return self._messages + finally: + self._messages = [] + + def add_message(self, msg_id, line=None, node=None, args=None): + self._messages.append(Message(msg_id, line, node, args)) + + def is_message_enabled(self, *unused_args): + return True + + def add_stats(self, **kwargs): + for name, value in kwargs.iteritems(): + self.stats[name] = value + + +class CheckerTestCase(testlib.TestCase): + """A base testcase class for unittesting individual checker classes.""" + CHECKER_CLASS = None + CONFIG = {} + + def setUp(self): + self.linter = UnittestLinter() + self.checker = self.CHECKER_CLASS(self.linter) + for key, value in self.CONFIG.iteritems(): + setattr(self.checker.config, key, value) + self.checker.open() + self.checker.stats = self.linter.stats + + @contextlib.contextmanager + def assertNoMessages(self): + """Assert that no messages are added by the given method.""" + with self.assertAddsMessages(): + yield + + @contextlib.contextmanager + def assertAddsMessages(self, *messages): + """Assert that exactly the given method adds the given messages. + + The list of messages must exactly match *all* the messages added by the + method. Additionally, we check to see whether the args in each message can + actually be substituted into the message string. + """ + yield + got = self.linter.release_messages() + msg = ('Expected messages did not match actual.\n' + 'Expected:\n%s\nGot:\n%s' % ('\n'.join(repr(m) for m in messages), + '\n'.join(repr(m) for m in got))) + self.assertEqual(got, list(messages), msg) + + +# Init test_reporter = TestReporter() linter = PyLinter() linter.set_reporter(test_reporter) @@ -17,27 +17,32 @@ main pylint class """ +import re import sys import tokenize from warnings import warn +import os from os.path import dirname, basename, splitext, exists, isdir, join, normpath from logilab.common.interface import implements from logilab.common.modutils import modpath_from_file, get_module_files, \ - file_from_modpath + file_from_modpath, load_module_from_file from logilab.common.textutils import normalize_text from logilab.common.configuration import rest_format_section from logilab.common.ureports import Section -from logilab.astng import nodes, Module +from astroid import nodes, Module -from pylint.checkers import EmptyReport from pylint.interfaces import IRawChecker, ITokenChecker class UnknownMessage(Exception): """raised when a unregistered message id is encountered""" +class EmptyReport(Exception): + """raised when a report is empty and so should not be displayed""" + + MSG_TYPES = { 'I' : 'info', @@ -62,6 +67,7 @@ _MSG_ORDER = 'EWRCIF' MSG_STATE_SCOPE_CONFIG = 0 MSG_STATE_SCOPE_MODULE = 1 +OPTION_RGX = re.compile(r'\s*#.*\bpylint:(.*)') # The line/node distinction does not apply to fatal errors and reports. _SCOPE_EXEMPT = 'FR' @@ -108,15 +114,16 @@ def category_id(id): def tokenize_module(module): stream = module.file_stream stream.seek(0) - if sys.version_info < (3, 0) and module.file_encoding is not None: - readline = lambda: stream.readline().decode(module.file_encoding, + readline = stream.readline + if sys.version_info < (3, 0): + if module.file_encoding is not None: + readline = lambda: stream.readline().decode(module.file_encoding, 'replace') - else: - readline = stream.readline - return list(tokenize.generate_tokens(readline)) + return list(tokenize.generate_tokens(readline)) + return list(tokenize.tokenize(readline)) - -class Message(object): + +class MessageDefinition(object): def __init__(self, checker, msgid, msg, descr, symbol, scope): assert len(msgid) == 5, 'Invalid message id %s' % msgid assert msgid[0] in MSG_TYPES, \ @@ -167,8 +174,19 @@ class MessagesHandlerMixIn(object): (msg, msgsymbol, msgdescr) = msg_tuple[:3] assert msgsymbol not in self._messages_by_symbol, \ 'Message symbol %r is already defined' % msgsymbol - if len(msg_tuple) > 3 and 'scope' in msg_tuple[3]: - scope = msg_tuple[3]['scope'] + if len(msg_tuple) > 3: + if 'scope' in msg_tuple[3]: + scope = msg_tuple[3]['scope'] + if 'minversion' in msg_tuple[3]: + minversion = msg_tuple[3]['minversion'] + if minversion > sys.version_info: + self._msgs_state[msgid] = False + continue + if 'maxversion' in msg_tuple[3]: + maxversion = msg_tuple[3]['maxversion'] + if maxversion <= sys.version_info: + self._msgs_state[msgid] = False + continue else: # messages should have a symbol, but for backward compatibility # they may not. @@ -182,7 +200,7 @@ class MessagesHandlerMixIn(object): assert chkid is None or chkid == msgid[1:3], \ 'Inconsistent checker part in message id %r' % msgid chkid = msgid[1:3] - msg = Message(checker, msgid, msg, msgdescr, msgsymbol, scope) + msg = MessageDefinition(checker, msgid, msg, msgdescr, msgsymbol, scope) self._messages[msgid] = msg self._messages_by_symbol[msgsymbol] = msg self._msgs_by_category.setdefault(msgid[0], []).append(msgid) @@ -222,7 +240,8 @@ class MessagesHandlerMixIn(object): if msgid.lower() in self._checkers: for checker in self._checkers[msgid.lower()]: for _msgid in checker.msgs: - self.disable(_msgid, scope, line) + if _msgid in self._messages: + self.disable(_msgid, scope, line) return # msgid is report id? if msgid.lower().startswith('rp'): @@ -258,8 +277,8 @@ class MessagesHandlerMixIn(object): # msgid is a checker name? if msgid.lower() in self._checkers: for checker in self._checkers[msgid.lower()]: - for msgid in checker.msgs: - self.enable(msgid, scope, line) + for msgid_ in checker.msgs: + self.enable(msgid_, scope, line) return # msgid is report id? if msgid.lower().startswith('rp'): @@ -300,11 +319,7 @@ class MessagesHandlerMixIn(object): Can be just the message ID or the ID and the symbol. """ - if self.config.symbols: - symbol = self.check_message_id(msgid).symbol - if symbol: - msgid += '(%s)' % symbol - return msgid + return repr(self.check_message_id(msgid).symbol) def get_message_state_scope(self, msgid, line=None): """Returns the scope at which a message was enabled/disabled.""" @@ -343,15 +358,17 @@ class MessagesHandlerMixIn(object): except KeyError: pass - def add_message(self, msgid, line=None, node=None, args=None): - """add the message corresponding to the given id. + def add_message(self, msg_descr, line=None, node=None, args=None): + """Adds a message given by ID or name. - If provided, msg is expanded using args + If provided, the message string is expanded using args - astng checkers should provide the node argument, raw checkers should - provide the line argument. + AST checkers should must the node argument (but may optionally + provide line if the line number is different), raw and token checkers + must provide the line argument. """ - msg_info = self._messages[msgid] + msg_info = self.check_message_id(msg_descr) + msgid = msg_info.msgid # Fatal messages and reports are special, the node/scope distinction # does not apply to them. if msgid[0] not in _SCOPE_EXEMPT: @@ -465,16 +482,15 @@ class MessagesHandlerMixIn(object): def list_messages(self): """output full messages list documentation in ReST format""" msgids = [] - for checker in self.get_checkers(): - for msgid in checker.msgs.iterkeys(): - msgids.append(msgid) + for msgid in self._messages: + msgids.append(msgid) msgids.sort() for msgid in msgids: print self.get_message_help(msgid, False) print -class ReportsHandlerMixIn: +class ReportsHandlerMixIn(object): """a mix-in class containing all the reports and stats manipulation related methods for the main lint class """ @@ -591,6 +607,15 @@ class PyLintASTWalker(object): self.leave_events = {} self.linter = linter + def _is_method_enabled(self, method): + if not hasattr(method, 'checks_msgs'): + return True + + for msg_desc in method.checks_msgs: + if self.linter.is_message_enabled(msg_desc): + return True + return False + def add_checker(self, checker): """walk to the checker's dir and collect visit and leave methods""" # XXX : should be possible to merge needed_checkers and add_checker @@ -598,7 +623,6 @@ class PyLintASTWalker(object): lcids = set() visits = self.visit_events leaves = self.leave_events - msgs = self.linter._msgs_state for member in dir(checker): cid = member[6:] if cid == 'default': @@ -606,19 +630,15 @@ class PyLintASTWalker(object): if member.startswith('visit_'): v_meth = getattr(checker, member) # don't use visit_methods with no activated message: - if hasattr(v_meth, 'checks_msgs'): - if not any(msgs.get(m, True) for m in v_meth.checks_msgs): - continue - visits.setdefault(cid, []).append(v_meth) - vcids.add(cid) + if self._is_method_enabled(v_meth): + visits.setdefault(cid, []).append(v_meth) + vcids.add(cid) elif member.startswith('leave_'): l_meth = getattr(checker, member) # don't use leave_methods with no activated message: - if hasattr(l_meth, 'checks_msgs'): - if not any(msgs.get(m, True) for m in l_meth.checks_msgs): - continue - leaves.setdefault(cid, []).append(l_meth) - lcids.add(cid) + if self._is_method_enabled(l_meth): + leaves.setdefault(cid, []).append(l_meth) + lcids.add(cid) visit_default = getattr(checker, 'visit_default', None) if visit_default: for cls in nodes.ALL_NODE_CLASSES: @@ -627,18 +647,45 @@ class PyLintASTWalker(object): visits.setdefault(cid, []).append(visit_default) # for now we have no "leave_default" method in Pylint - def walk(self, astng): - """call visit events of astng checkers for the given node, recurse on + def walk(self, astroid): + """call visit events of astroid checkers for the given node, recurse on its children, then leave events. """ - cid = astng.__class__.__name__.lower() - if astng.is_statement: + cid = astroid.__class__.__name__.lower() + if astroid.is_statement: self.nbstatements += 1 # generate events for this node on each checker for cb in self.visit_events.get(cid, ()): - cb(astng) + cb(astroid) # recurse on children - for child in astng.get_children(): + for child in astroid.get_children(): self.walk(child) for cb in self.leave_events.get(cid, ()): - cb(astng) + cb(astroid) + + +PY_EXTS = ('.py', '.pyc', '.pyo', '.pyw', '.so', '.dll') + +def register_plugins(linter, directory): + """load all module and package in the given directory, looking for a + 'register' function in each one, used to register pylint checkers + """ + imported = {} + for filename in os.listdir(directory): + base, extension = splitext(filename) + if base in imported or base == '__pycache__': + continue + if extension in PY_EXTS and base != '__init__' or ( + not extension and isdir(join(directory, base))): + try: + module = load_module_from_file(join(directory, filename)) + except ValueError: + # empty module name (usually emacs auto-save files) + continue + except ImportError, exc: + print >> sys.stderr, "Problem importing module %s: %s" % (filename, exc) + else: + if hasattr(module, 'register'): + module.register(linter) + imported[base] = 1 + |