diff options
| author | Sylvain Th?nault <thenault@gmail.com> | 2013-12-20 17:26:41 +0100 |
|---|---|---|
| committer | Sylvain Th?nault <thenault@gmail.com> | 2013-12-20 17:26:41 +0100 |
| commit | 1722f16ed58528768370ace7991ddaa70f3cbe3f (patch) | |
| tree | 7354c64adbbddf4448fe4f057f743362f3e957ff | |
| parent | e7fc0a23d8a963daf4b367e4e8fd9f6ec7087a35 (diff) | |
| parent | f24227aae012740653e8708379b7e9c22f938400 (diff) | |
| download | pylint-1722f16ed58528768370ace7991ddaa70f3cbe3f.tar.gz | |
Merged in jmcgeheeiv/pylintrc_search_doc (pull request #65)
Correct documentation for the manner in which Pylint finds its pylintrc configuration file
67 files changed, 1099 insertions, 832 deletions
@@ -8,3 +8,4 @@ ^doc/_build ^dist/ ^pylint.egg-info/ +.tox @@ -2,9 +2,29 @@ ChangeLog for Pylint ==================== -- + + * Add new check for use of deprecated pragma directives "pylint:disable-msg" + or "pylint:enable-msg" (I0022, deprecated-pragma) which was previously + emmited as a regular warn() + + * Avoid false used-before-assignment for except handler defined + identifier used on the same line (#111) + + * Combine 'no-space-after-operator', 'no-space-after-comma' and + 'no-space-before-operator' into a new warning 'bad-whitespace' + + * Add a new warning 'superfluous-parens' for unnecessary + parentheses after certain keywords. + + * Fix a potential crash in the redefine-in-handler warning + if the redefined name is a nested getattr node. + + * Add a new option for the multi-statement warning to + allow single-line if statements. + * Add 'bad-context-manager' error, checking that '__exit__' special method accepts the right number of arguments. - + * Run pylint as a python module 'python -m pylint' (anatoly techtonik) * Check for non-exception classes inside an except clause @@ -13,7 +33,7 @@ ChangeLog for Pylint have basic input validation (bitbucket #53 and #54), patches provided by felipeochoa and Brian Lane - * Added a new warning, 'non-iterator-returned', for non-iterators + * Added a new warning, 'non-iterator-returned', for non-iterators returned by '__iter__' * Add new warning for unpacking non-sequences in assignments diff --git a/MANIFEST.in b/MANIFEST.in index 32ec38e..37c6e67 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,6 +7,6 @@ include examples/*.py examples/pylintrc examples/pylintrc_camelcase include elisp/startup elisp/*.el include man/*.1 recursive-include doc *.rst *.jpeg Makefile *.html *.py -recursive-include test *.py *.txt *.txt2 *.dot *.sh -include test/input/similar* noext +recursive-include test *.py *.txt *.dot *.sh +include test/input/similar* include test/input/noext diff --git a/__pkginfo__.py b/__pkginfo__.py index 997b9a5..614828e 100644 --- a/__pkginfo__.py +++ b/__pkginfo__.py @@ -30,18 +30,18 @@ mailinglist = "mailto://python-projects@lists.logilab.org" author = 'Logilab' author_email = 'python-projects@lists.logilab.org' -classifiers = ['Development Status :: 4 - Beta', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU General Public License (GPL)', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 3', - 'Topic :: Software Development :: Debuggers', - 'Topic :: Software Development :: Quality Assurance', - 'Topic :: Software Development :: Testing', - ] +classifiers = ['Development Status :: 4 - Beta', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU General Public License (GPL)', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', + 'Topic :: Software Development :: Debuggers', + 'Topic :: Software Development :: Quality Assurance', + 'Topic :: Software Development :: Testing', + ] long_desc = """\ diff --git a/checkers/__init__.py b/checkers/__init__.py index 27dc364..1d0aa42 100644 --- a/checkers/__init__.py +++ b/checkers/__init__.py @@ -99,10 +99,6 @@ class BaseChecker(OptionsProviderMixIn, ASTWalker): """add a message of a given type""" self.linter.add_message(msg_id, line, node, args) - def package_dir(self): - """return the base directory for the analysed package""" - return dirname(self.linter.base_file) - # dummy methods implementing the IChecker interface def open(self): diff --git a/checkers/base.py b/checkers/base.py index de40c4c..61ec528 100644 --- a/checkers/base.py +++ b/checkers/base.py @@ -84,7 +84,10 @@ def _loop_exits_early(loop): return True return False - +if sys.version_info < (3, 0): + PROPERTY_CLASSES = set(('__builtin__.property', 'abc.abstractproperty')) +else: + PROPERTY_CLASSES = set(('builtins.property', 'abc.abstractproperty')) def _determine_function_name_type(node): """Determine the name type whose regex the a function's name should match. @@ -105,8 +108,7 @@ def _determine_function_name_type(node): (isinstance(decorator, astroid.Getattr) and decorator.attrname == 'abstractproperty')): infered = safe_infer(decorator) - if (infered and - infered.qname() in ('__builtin__.property', 'abc.abstractproperty')): + if infered and infered.qname() in PROPERTY_CLASSES: return 'attr' # If the function is decorated using the prop_method.{setter,getter} # form, treat it like an attribute as well. diff --git a/checkers/format.py b/checkers/format.py index 1cf0edc..45a7477 100644 --- a/checkers/format.py +++ b/checkers/format.py @@ -21,19 +21,43 @@ http://www.python.org/doc/essays/styleguide.html Some parts of the process_token method is based from The Tab Nanny std module. """ -import re, sys +import keyword +import sys import tokenize + if not hasattr(tokenize, 'NL'): raise ValueError("tokenize.NL doesn't exist -- tokenize module too old") -from logilab.common.textutils import pretty_match from astroid import nodes -from pylint.interfaces import ITokenChecker, IAstroidChecker +from pylint.interfaces import ITokenChecker, IAstroidChecker, IRawChecker from pylint.checkers import BaseTokenChecker from pylint.checkers.utils import check_messages from pylint.utils import WarningScope, OPTION_RGX +_KEYWORD_TOKENS = ['assert', 'del', 'elif', 'except', 'for', 'if', 'in', 'not', + 'raise', 'return', 'while', 'yield'] +if sys.version_info < (3, 0): + _KEYWORD_TOKENS.append('print') + +_SPACED_OPERATORS = ['==', '<', '>', '!=', '<>', '<=', '>=', + '+=', '-=', '*=', '**=', '/=', '//=', '&=', '|=', '^=', + '%=', '>>=', '<<='] +_OPENING_BRACKETS = ['(', '[', '{'] +_CLOSING_BRACKETS = [')', ']', '}'] + +_EOL = frozenset([tokenize.NEWLINE, tokenize.NL, tokenize.COMMENT]) + +# Whitespace checking policy constants +_MUST = 0 +_MUST_NOT = 1 +_IGNORE = 2 + +# Whitespace checking config constants +_DICT_SEPARATOR = 'dict-separator' +_TRAILING_COMMA = 'trailing-comma' +_NO_SPACE_CHECK_CHOICES = [_TRAILING_COMMA, _DICT_SEPARATOR] + MSGS = { 'C0301': ('Line too long (%s/%s)', 'line-too-long', @@ -64,22 +88,20 @@ MSGS = { 'multiple-statements', 'Used when more than on statement are found on the same line.', {'scope': WarningScope.NODE}), - 'C0322': ('Operator not preceded by a space\n%s', - 'no-space-before-operator', - 'Used when one of the following operator (!= | <= | == | >= | < ' - '| > | = | \\+= | -= | \\*= | /= | %) is not preceded by a space.', - {'scope': WarningScope.NODE}), - 'C0323': ('Operator not followed by a space\n%s', - 'no-space-after-operator', - 'Used when one of the following operator (!= | <= | == | >= | < ' - '| > | = | \\+= | -= | \\*= | /= | %) is not followed by a space.', - {'scope': WarningScope.NODE}), - 'C0324': ('Comma not followed by a space\n%s', - 'no-space-after-comma', - 'Used when a comma (",") is not followed by a space.', - {'scope': WarningScope.NODE}), + 'C0325' : ('Unnecessary parens after %r keyword', + 'superfluous-parens', + 'Used when a single item in parentheses follows an if, for, or ' + 'other keyword.'), + 'C0326': ('%s space %s %s %s\n%s', + 'bad-whitespace', + ('Used when a wrong number of spaces is used around an operator, ' + 'bracket or block opener.'), + {'old_names': [('C0323', 'no-space-after-operator'), + ('C0324', 'no-space-after-comma'), + ('C0322', 'no-space-before-operator')]}) } + if sys.version_info < (3, 0): MSGS.update({ @@ -99,74 +121,21 @@ if sys.version_info < (3, 0): {'scope': WarningScope.NODE}), }) -# simple quoted string rgx -SQSTRING_RGX = r'"([^"\\]|\\.)*?"' -# simple apostrophed rgx -SASTRING_RGX = r"'([^'\\]|\\.)*?'" -# triple quoted string rgx -TQSTRING_RGX = r'"""([^"]|("(?!"")))*?(""")' -# triple apostrophe'd string rgx -TASTRING_RGX = r"'''([^']|('(?!'')))*?(''')" - -# finally, the string regular expression -STRING_RGX = re.compile('(%s)|(%s)|(%s)|(%s)' % (TQSTRING_RGX, TASTRING_RGX, - SQSTRING_RGX, SASTRING_RGX), - re.MULTILINE|re.DOTALL) - -COMMENT_RGX = re.compile("#.*$", re.M) - -OPERATORS = r'!=|<=|==|>=|<|>|=|\+=|-=|\*=|/=|%' -OP_RGX_MATCH_1 = r'[^(]*(?<!\s|\^|<|>|=|\+|-|\*|/|!|%%|&|\|)(%s).*' % OPERATORS -OP_RGX_SEARCH_1 = r'(?<!\s|\^|<|>|=|\+|-|\*|/|!|%%|&|\|)(%s)' % OPERATORS +def _underline_token(token): + length = token[3][1] - token[2][1] + offset = token[2][1] + return token[4] + (' ' * offset) + ('^' * length) -OP_RGX_MATCH_2 = r'[^(]*(%s)(?!\s|=|>|<).*' % OPERATORS -OP_RGX_SEARCH_2 = r'(%s)(?!\s|=|>)' % OPERATORS -BAD_CONSTRUCT_RGXS = ( - - (re.compile(OP_RGX_MATCH_1, re.M), - re.compile(OP_RGX_SEARCH_1, re.M), - 'C0322'), - - (re.compile(OP_RGX_MATCH_2, re.M), - re.compile(OP_RGX_SEARCH_2, re.M), - 'C0323'), - - (re.compile(r'.*,[^(\s|\]|}|\))].*', re.M), - re.compile(r',[^\s)]', re.M), - 'C0324'), - ) - - -def get_string_coords(line): - """return a list of string positions (tuple (start, end)) in the line - """ - result = [] - for match in re.finditer(STRING_RGX, line): - result.append( (match.start(), match.end()) ) - return result - -def in_coords(match, string_coords): - """return true if the match is in the string coord""" - mstart = match.start() - for start, end in string_coords: - if mstart >= start and mstart < end: - return True - return False - -def check_line(line): - """check a line for a bad construction - if it founds one, return a message describing the problem - else return None - """ - cleanstr = COMMENT_RGX.sub('', STRING_RGX.sub('', line)) - for rgx_match, rgx_search, msg_id in BAD_CONSTRUCT_RGXS: - if rgx_match.match(cleanstr): - string_positions = get_string_coords(line) - for match in re.finditer(rgx_search, line): - if not in_coords(match, string_positions): - return msg_id, pretty_match(match, line.rstrip()) +def _column_distance(token1, token2): + if token1 == token2: + return 0 + if token2[3] < token1[3]: + token1, token2 = token2, token1 + if token1[3][0] != token2[2][0]: + return None + return token2[2][1] - token1[3][1] class FormatChecker(BaseTokenChecker): @@ -177,7 +146,7 @@ class FormatChecker(BaseTokenChecker): * use of <> instead of != """ - __implements__ = (ITokenChecker, IAstroidChecker) + __implements__ = (ITokenChecker, IAstroidChecker, IRawChecker) # configuration section name name = 'format' @@ -193,6 +162,16 @@ class FormatChecker(BaseTokenChecker): 'default': r'^\s*(# )?<?https?://\S+>?$', 'help': ('Regexp for a line that is allowed to be longer than ' 'the limit.')}), + ('single-line-if-stmt', + {'default': False, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'help' : ('Allow the body of an if to be on the same ' + 'line as the test if there is no else.')}), + ('no-space-check', + {'default': ','.join(_NO_SPACE_CHECK_CHOICES), + 'type': 'multiple_choice', + 'choices': _NO_SPACE_CHECK_CHOICES, + 'help': ('List of optional constructs for which whitespace ' + 'checking is disabled')}), ('max-module-lines', {'default' : 1000, 'type' : 'int', 'metavar' : '<int>', 'help': 'Maximum number of lines in a module'} @@ -213,6 +192,225 @@ class FormatChecker(BaseTokenChecker): self._lines[line_num] = line.split('\n')[0] self.check_lines(line, line_num) + def process_module(self, module): + self._keywords_with_parens = set() + for node in module.body: + if (isinstance(node, nodes.From) and node.modname == '__future__' + and any(name == 'print_function' for name, _ in node.names)): + self._keywords_with_parens.add('print') + + def _check_keyword_parentheses(self, tokens, start): + """Check that there are not unnecessary parens after a keyword. + + Parens are unnecessary if there is exactly one balanced outer pair on a + line, and it is followed by a colon, and contains no commas (i.e. is not a + tuple). + + Args: + tokens: list of Tokens; the entire list of Tokens. + start: int; the position of the keyword in the token list. + """ + # If the next token is not a paren, we're fine. + if tokens[start+1][1] != '(': + return + + found_comma = False + found_and_or = False + depth = 0 + keyword_token = tokens[start][1] + line_num = tokens[start][2][0] + + for i in xrange(start, len(tokens) - 1): + token = tokens[i] + + # If we hit a newline, then assume any parens were for continuation. + if token[0] == tokenize.NL: + return + + if token[1] == '(': + depth += 1 + elif token[1] == ')': + depth -= 1 + if not depth: + # ')' can't happen after if (foo), since it would be a syntax error. + if (tokens[i+1][1] in (':', ')', ']', '}', 'in') or + tokens[i+1][0] in (tokenize.NEWLINE, tokenize.ENDMARKER, + tokenize.COMMENT)): + # The empty tuple () is always accepted. + if i == start + 2: + return + if keyword_token == 'not': + if not found_and_or: + self.add_message('C0325', line=line_num, + args=keyword_token) + elif keyword_token in ('return', 'yield'): + self.add_message('C0325', line=line_num, + args=keyword_token) + elif keyword_token not in self._keywords_with_parens: + if not (tokens[i+1][1] == 'in' and found_and_or): + self.add_message('C0325', line=line_num, + args=keyword_token) + return + elif depth == 1: + # This is a tuple, which is always acceptable. + if token[1] == ',': + return + # 'and' and 'or' are the only boolean operators with lower precedence + # than 'not', so parens are only required when they are found. + elif token[1] in ('and', 'or'): + found_and_or = True + # A yield inside an expression must always be in parentheses, + # quit early without error. + elif token[1] == 'yield': + return + # A generator expression always has a 'for' token in it, and + # the 'for' token is only legal inside parens when it is in a + # generator expression. The parens are necessary here, so bail + # without an error. + elif token[1] == 'for': + return + + def _opening_bracket(self, tokens, i): + self._bracket_stack.append(tokens[i][1]) + # Special case: ignore slices + if tokens[i][1] == '[' and tokens[i+1][1] == ':': + return + + if (i > 0 and (tokens[i-1][0] == tokenize.NAME and + not (keyword.iskeyword(tokens[i-1][1])) + or tokens[i-1][1] in _CLOSING_BRACKETS)): + self._check_space(tokens, i, (_MUST_NOT, _MUST_NOT)) + else: + self._check_space(tokens, i, (_IGNORE, _MUST_NOT)) + + def _closing_bracket(self, tokens, i): + self._bracket_stack.pop() + # Special case: ignore slices + if tokens[i-1][1] == ':' and tokens[i][1] == ']': + return + policy_before = _MUST_NOT + if tokens[i][1] in _CLOSING_BRACKETS and tokens[i-1][1] == ',': + if _TRAILING_COMMA in self.config.no_space_check: + policy_before = _IGNORE + + self._check_space(tokens, i, (policy_before, _IGNORE)) + + def _check_equals_spacing(self, tokens, i): + """Check the spacing of a single equals sign.""" + if self._inside_brackets('(') or self._inside_brackets('lambda'): + self._check_space(tokens, i, (_MUST_NOT, _MUST_NOT)) + else: + self._check_space(tokens, i, (_MUST, _MUST)) + + def _open_lambda(self, unused_tokens, unused_i): + self._bracket_stack.append('lambda') + + def _handle_colon(self, tokens, i): + # Special case: ignore slices + if self._inside_brackets('['): + return + if (self._inside_brackets('{') and + _DICT_SEPARATOR in self.config.no_space_check): + policy = (_IGNORE, _IGNORE) + else: + policy = (_MUST_NOT, _MUST) + self._check_space(tokens, i, policy) + + if self._inside_brackets('lambda'): + self._bracket_stack.pop() + + def _handle_comma(self, tokens, i): + # Only require a following whitespace if this is + # not a hanging comma before a closing bracket. + if tokens[i+1][1] in _CLOSING_BRACKETS: + self._check_space(tokens, i, (_MUST_NOT, _IGNORE)) + else: + self._check_space(tokens, i, (_MUST_NOT, _MUST)) + + def _check_surrounded_by_space(self, tokens, i): + """Check that a binary operator is surrounded by exactly one space.""" + self._check_space(tokens, i, (_MUST, _MUST)) + + def _check_space(self, tokens, i, policies): + def _policy_string(policy): + if policy == _MUST: + return 'Exactly one', 'required' + else: + return 'No', 'allowed' + + def _name_construct(token): + if tokens[i][1] == ',': + return 'comma' + elif tokens[i][1] == ':': + return ':' + elif tokens[i][1] in '()[]{}': + return 'bracket' + elif tokens[i][1] in ('<', '>', '<=', '>=', '!='): + return 'comparison' + else: + if self._inside_brackets('('): + return 'keyword argument assignment' + else: + return 'assignment' + + good_space = [True, True] + pairs = [(tokens[i-1], tokens[i]), (tokens[i], tokens[i+1])] + + for other_idx, (policy, token_pair) in enumerate(zip(policies, pairs)): + current_idx = 1 - other_idx + if token_pair[other_idx][0] in _EOL or policy == _IGNORE: + continue + + distance = _column_distance(*token_pair) + if distance is None: + continue + good_space[other_idx] = ( + (policy == _MUST and distance == 1) or + (policy == _MUST_NOT and distance == 0)) + + warnings = [] + if not any(good_space) and policies[0] == policies[1]: + warnings.append((policies[0], 'around')) + else: + for ok, policy, position in zip(good_space, policies, ('before', 'after')): + if not ok: + warnings.append((policy, position)) + for policy, position in warnings: + construct = _name_construct(tokens[i]) + count, state = _policy_string(policy) + self.add_message('C0326', line=tokens[i][2][0], + args=(count, state, position, construct, + _underline_token(tokens[i]))) + + def _inside_brackets(self, left): + return self._bracket_stack[-1] == left + + def _prepare_token_dispatcher(self): + raw = [ + (_KEYWORD_TOKENS, + self._check_keyword_parentheses), + + (_OPENING_BRACKETS, self._opening_bracket), + + (_CLOSING_BRACKETS, self._closing_bracket), + + (['='], self._check_equals_spacing), + + (_SPACED_OPERATORS, self._check_surrounded_by_space), + + ([','], self._handle_comma), + + ([':'], self._handle_colon), + + (['lambda'], self._open_lambda), + ] + + dispatch = {} + for tokens, handler in raw: + for token in tokens: + dispatch[token] = handler + return dispatch + def process_tokens(self, tokens): """process tokens and search for : @@ -222,6 +420,7 @@ class FormatChecker(BaseTokenChecker): _ optionally bad construct (if given, bad_construct must be a compiled regular expression). """ + self._bracket_stack = [None] indent = tokenize.INDENT dedent = tokenize.DEDENT newline = tokenize.NEWLINE @@ -233,7 +432,8 @@ class FormatChecker(BaseTokenChecker): self._lines = {} self._visited_lines = {} new_line_delay = False - for (tok_type, token, start, _, line) in tokens: + token_handlers = self._prepare_token_dispatcher() + for idx, (tok_type, token, start, _, line) in enumerate(tokens): if new_line_delay: new_line_delay = False self.new_line(tok_type, line, line_num, junk) @@ -292,6 +492,13 @@ class FormatChecker(BaseTokenChecker): check_equal = 0 self.check_indent_level(line, indents[-1], line_num) + try: + handler = token_handlers[token] + except KeyError: + pass + else: + handler(tokens, idx) + line_num -= 1 # to be ok with "wc -l" if line_num > self.config.max_module_lines: self.add_message('C0302', args=line_num, line=1) @@ -307,16 +514,19 @@ class FormatChecker(BaseTokenChecker): if prev_sibl is not None: prev_line = prev_sibl.fromlineno else: - prev_line = node.parent.statement().fromlineno + # The line on which a finally: occurs in a try/finally + # is not directly represented in the AST. We infer it + # by taking the last line of the body and adding 1, which + # should be the line of finally: + if (isinstance(node.parent, nodes.TryFinally) + and node in node.parent.finalbody): + prev_line = node.parent.body[0].tolineno + 1 + else: + prev_line = node.parent.statement().fromlineno line = node.fromlineno assert line, node if prev_line == line and self._visited_lines.get(line) != 2: - # py2.5 try: except: finally: - if not (isinstance(node, nodes.TryExcept) - and isinstance(node.parent, nodes.TryFinally) - and node.fromlineno == node.parent.fromlineno): - self.add_message('C0321', node=node) - self._visited_lines[line] = 2 + self._check_multi_statement_line(node, line) return if line in self._visited_lines: return @@ -332,13 +542,23 @@ class FormatChecker(BaseTokenChecker): lines.append(self._lines[line].rstrip()) except KeyError: lines.append('') - try: - msg_def = check_line('\n'.join(lines)) - if msg_def: - self.add_message(msg_def[0], node=node, args=msg_def[1]) - except KeyError: - # FIXME: internal error ! - pass + + def _check_multi_statement_line(self, node, line): + """Check for lines containing multiple statements.""" + # Do not warn about multiple nested context managers + # in with statements. + if isinstance(node, nodes.With): + return + # For try... except... finally..., the two nodes + # appear to be on the same line due to how the AST is built. + if (isinstance(node, nodes.TryExcept) and + isinstance(node.parent, nodes.TryFinally)): + return + if (isinstance(node.parent, nodes.If) and not node.parent.orelse + and self.config.single_line_if_stmt): + return + self.add_message('C0321', node=node) + self._visited_lines[line] = 2 @check_messages('W0333') def visit_backquote(self, node): diff --git a/checkers/imports.py b/checkers/imports.py index 1dd7787..7068dcb 100644 --- a/checkers/imports.py +++ b/checkers/imports.py @@ -301,11 +301,10 @@ given file (report RP0402 must not be disabled)'} importedmodname, set()) if not context_name in importedmodnames: importedmodnames.add(context_name) - if is_standard_module(importedmodname, (self.package_dir(),)): - # update import graph - mgraph = self.import_graph.setdefault(context_name, set()) - if not importedmodname in mgraph: - mgraph.add(importedmodname) + # update import graph + mgraph = self.import_graph.setdefault(context_name, set()) + if not importedmodname in mgraph: + mgraph.add(importedmodname) def _check_deprecated_module(self, node, mod_path): """check if the module is deprecated""" diff --git a/checkers/newstyle.py b/checkers/newstyle.py index 9832195..ff9bbc2 100644 --- a/checkers/newstyle.py +++ b/checkers/newstyle.py @@ -1,4 +1,4 @@ -# Copyright (c) 2005-2006 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2005-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 @@ -37,7 +37,8 @@ MSGS = { 'E1004': ('Missing argument to super()', 'missing-super-argument', 'Used when the super builtin didn\'t receive an \ - argument on Python 2'), + argument on Python 2', + {'maxversion': (3, 0)}), 'W1001': ('Use of "property" on an old style class', 'property-on-old-class', 'Used when PyLint detect the use of the builtin "property" \ diff --git a/checkers/utils.py b/checkers/utils.py index 5e028f2..72a9733 100644 --- a/checkers/utils.py +++ b/checkers/utils.py @@ -60,7 +60,7 @@ def clobber_in_except(node): (False, None) otherwise. """ if isinstance(node, astroid.AssAttr): - return (True, (node.attrname, 'object %r' % (node.expr.name,))) + return (True, (node.attrname, 'object %r' % (node.expr.as_string(),))) elif isinstance(node, astroid.AssName): name = node.name if is_builtin(name): @@ -162,6 +162,11 @@ def is_defined_before(var_node): if getattr(_node, 'name', None) == varname: return True break + elif isinstance(_node, astroid.ExceptHandler): + if isinstance(_node.name, astroid.AssName): + ass_node=_node.name + if ass_node.name == varname: + return True _node = _node.parent # possibly multiple statements on the same line using semi colon separator stmt = var_node.statement() diff --git a/checkers/variables.py b/checkers/variables.py index 0d35884..90b7fe7 100644 --- a/checkers/variables.py +++ b/checkers/variables.py @@ -51,6 +51,20 @@ def overridden_method(klass, name): return meth_node return None +def _get_unpacking_extra_info(node, infered): + """return extra information to add to the message for unpacking-non-sequence + and unbalanced-tuple-unpacking errors + """ + more = '' + infered_module = infered.root().name + if node.root().name == infered_module: + if node.lineno == infered.lineno: + more = ' %s' % infered.as_string() + elif infered.lineno: + more = ' defined at line %s' % infered.lineno + elif infered.lineno: + more = ' defined at line %s of %s' % (infered.lineno, infered_module) + return more MSGS = { 'E0601': ('Using variable %r before assignment', @@ -120,13 +134,12 @@ MSGS = { the loop.'), 'W0632': ('Possible unbalanced tuple unpacking with ' - 'sequence at line %s: ' + 'sequence%s: ' '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'), - 'W0633': ('Attempting to unpack a non-sequence with ' - 'non-sequence at line %s', + 'W0633': ('Attempting to unpack a non-sequence%s', 'unpacking-non-sequence', 'Used when something which is not ' 'a sequence is used in an unpack assignment'), @@ -556,7 +569,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' """ if not isinstance(node.targets[0], (astroid.Tuple, astroid.List)): return - + targets = node.targets[0].itered() if any(not isinstance(target_node, astroid.AssName) for target_node in targets): @@ -572,41 +585,30 @@ builtins. Remember that you should avoid to define new builtins when possible.' """ Check for unbalanced tuple unpacking and unpacking non sequences. """ - if isinstance(infered, (astroid.Tuple, astroid.List)): + if infered is astroid.YES: + return + if isinstance(infered, (astroid.Tuple, astroid.List)): + # attempt to check unpacking is properly balanced values = infered.itered() if len(targets) != len(values): - if node.root().name == infered.root().name: - location = infered.lineno or 'unknown' - else: - location = '%s (%s)' % (infered.lineno or 'unknown', - infered.root().name) - - self.add_message('unbalanced-tuple-unpacking', - node=node, - args=(location, - len(targets), + self.add_message('unbalanced-tuple-unpacking', node=node, + args=(_get_unpacking_extra_info(node, infered), + len(targets), len(values))) - else: - if infered is astroid.YES: - return - + # attempt to check unpacking may be possible (ie RHS is iterable) + elif isinstance(infered, astroid.Instance): for meth in ('__iter__', '__getitem__'): try: infered.getattr(meth) + break except astroid.NotFoundError: continue - else: - break - else: - if node.root().name == infered.root().name: - location = infered.lineno or 'unknown' - else: - location = '%s (%s)' % (infered.lineno or 'unknown', - infered.root().name) - - self.add_message('unpacking-non-sequence', - node=node, - args=(location, )) + else: + self.add_message('unpacking-non-sequence', node=node, + args=(_get_unpacking_extra_info(node, infered),)) + else: + self.add_message('unpacking-non-sequence', node=node, + args=(_get_unpacking_extra_info(node, infered),)) def _check_module_attrs(self, node, module, module_names): @@ -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 @@ -16,6 +16,7 @@ * pylintrc * pylint.d (PYLINTHOME) """ +from __future__ import with_statement import pickle import os @@ -34,12 +35,6 @@ elif USER_HOME == '~': else: PYLINT_HOME = join(USER_HOME, '.pylint.d') -if not exists(PYLINT_HOME): - try: - os.mkdir(PYLINT_HOME) - except OSError: - print >> sys.stderr, 'Unable to create directory %s' % PYLINT_HOME - def get_pdata_path(base_name, recurs): """return the path of the file which should contain old search data for the given base_name with the given options values @@ -55,7 +50,8 @@ def load_results(base): """ data_file = get_pdata_path(base, 1) try: - return pickle.load(open(data_file)) + with open(data_file) as stream: + return pickle.load(stream) except: return {} @@ -66,9 +62,15 @@ else: def save_results(results, base): """pickle results""" + if not exists(PYLINT_HOME): + try: + os.mkdir(PYLINT_HOME) + except OSError: + print >> sys.stderr, 'Unable to create directory %s' % PYLINT_HOME data_file = get_pdata_path(base, 1) try: - pickle.dump(results, open(data_file, _PICK_MOD)) + with open(data_file, _PICK_MOD) as stream: + pickle.dump(results, stream) except (IOError, OSError), ex: print >> sys.stderr, 'Unable to create file %s: %s' % (data_file, ex) diff --git a/debian.intrepid/rules b/debian.intrepid/rules deleted file mode 100755 index 81b90c3..0000000 --- a/debian.intrepid/rules +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/make -f -# Sample debian/rules that uses debhelper. -# GNU copyright 1997 to 1999 by Joey Hess. -# -# adapted by Logilab for automatic generation by debianize -# (part of the devtools project, http://www.logilab.org/projects/devtools) -# -# Copyright (c) 2003-2009 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr - -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - -build: build-stamp -build-stamp: - dh_testdir - NO_SETUPTOOLS=1 python setup.py -q build - touch build-stamp - -clean: - dh_testdir - dh_testroot - - NO_SETUPTOOLS=1 python setup.py clean - - find . -name "*.pyc" -delete - - rm -rf build - - dh_clean build-stamp - -install: build - dh_testdir - dh_testroot - dh_clean -k - dh_installdirs - - NO_SETUPTOOLS=1 python setup.py -q install --no-compile \ - --root=$(CURDIR)/debian/pylint \ - - - rm -rf debian/pylint/usr/lib/python*/*-packages/pylint/test - - # fixes shebangs - for exec in pylint pylint-gui symilar ; do \ - if head -1 debian/pylint/usr/bin/$$exec | grep "^#! */usr/bin" | grep "python" >/dev/null ; then \ - sed -i "s@^#! */usr/bin/env \+python\$$@#!/usr/bin/python@" debian/pylint/usr/bin/$$exec; \ - fi ; \ - chmod a+x debian/pylint/usr/bin/$$exec; \ - done - - install -m 644 elisp/pylint.el debian/pylint/usr/share/emacs/site-lisp/pylint/ - - # install tests - #(cd test && find . -type f -not \( -path '*/CVS/*' -or -name '*.pyc' \) -exec install -D --mode=644 {} ../debian/pylint/usr/share/doc/pylint/test/{} \;) - -# Build architecture-independent files here. -binary-indep: build install - dh_testdir - dh_testroot - dh_install -i - dh_pysupport -i - dh_installchangelogs -i ChangeLog - dh_installexamples -i - dh_installdocs -i - dh_installman -i - dh_installemacsen - dh_link -i - dh_compress -i -X.py -X.ini -X.xml -Xtest - dh_fixperms -i - dh_installdeb -i - dh_gencontrol -i - dh_md5sums -i - dh_builddeb -i - -binary-arch: - -binary: binary-indep -.PHONY: build clean binary binary-indep binary-arch diff --git a/debian.jaunty/rules b/debian.jaunty/rules deleted file mode 100755 index 81b90c3..0000000 --- a/debian.jaunty/rules +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/make -f -# Sample debian/rules that uses debhelper. -# GNU copyright 1997 to 1999 by Joey Hess. -# -# adapted by Logilab for automatic generation by debianize -# (part of the devtools project, http://www.logilab.org/projects/devtools) -# -# Copyright (c) 2003-2009 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr - -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - -build: build-stamp -build-stamp: - dh_testdir - NO_SETUPTOOLS=1 python setup.py -q build - touch build-stamp - -clean: - dh_testdir - dh_testroot - - NO_SETUPTOOLS=1 python setup.py clean - - find . -name "*.pyc" -delete - - rm -rf build - - dh_clean build-stamp - -install: build - dh_testdir - dh_testroot - dh_clean -k - dh_installdirs - - NO_SETUPTOOLS=1 python setup.py -q install --no-compile \ - --root=$(CURDIR)/debian/pylint \ - - - rm -rf debian/pylint/usr/lib/python*/*-packages/pylint/test - - # fixes shebangs - for exec in pylint pylint-gui symilar ; do \ - if head -1 debian/pylint/usr/bin/$$exec | grep "^#! */usr/bin" | grep "python" >/dev/null ; then \ - sed -i "s@^#! */usr/bin/env \+python\$$@#!/usr/bin/python@" debian/pylint/usr/bin/$$exec; \ - fi ; \ - chmod a+x debian/pylint/usr/bin/$$exec; \ - done - - install -m 644 elisp/pylint.el debian/pylint/usr/share/emacs/site-lisp/pylint/ - - # install tests - #(cd test && find . -type f -not \( -path '*/CVS/*' -or -name '*.pyc' \) -exec install -D --mode=644 {} ../debian/pylint/usr/share/doc/pylint/test/{} \;) - -# Build architecture-independent files here. -binary-indep: build install - dh_testdir - dh_testroot - dh_install -i - dh_pysupport -i - dh_installchangelogs -i ChangeLog - dh_installexamples -i - dh_installdocs -i - dh_installman -i - dh_installemacsen - dh_link -i - dh_compress -i -X.py -X.ini -X.xml -Xtest - dh_fixperms -i - dh_installdeb -i - dh_gencontrol -i - dh_md5sums -i - dh_builddeb -i - -binary-arch: - -binary: binary-indep -.PHONY: build clean binary binary-indep binary-arch diff --git a/debian.lenny/rules b/debian.lenny/rules deleted file mode 100755 index 81b90c3..0000000 --- a/debian.lenny/rules +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/make -f -# Sample debian/rules that uses debhelper. -# GNU copyright 1997 to 1999 by Joey Hess. -# -# adapted by Logilab for automatic generation by debianize -# (part of the devtools project, http://www.logilab.org/projects/devtools) -# -# Copyright (c) 2003-2009 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr - -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - -build: build-stamp -build-stamp: - dh_testdir - NO_SETUPTOOLS=1 python setup.py -q build - touch build-stamp - -clean: - dh_testdir - dh_testroot - - NO_SETUPTOOLS=1 python setup.py clean - - find . -name "*.pyc" -delete - - rm -rf build - - dh_clean build-stamp - -install: build - dh_testdir - dh_testroot - dh_clean -k - dh_installdirs - - NO_SETUPTOOLS=1 python setup.py -q install --no-compile \ - --root=$(CURDIR)/debian/pylint \ - - - rm -rf debian/pylint/usr/lib/python*/*-packages/pylint/test - - # fixes shebangs - for exec in pylint pylint-gui symilar ; do \ - if head -1 debian/pylint/usr/bin/$$exec | grep "^#! */usr/bin" | grep "python" >/dev/null ; then \ - sed -i "s@^#! */usr/bin/env \+python\$$@#!/usr/bin/python@" debian/pylint/usr/bin/$$exec; \ - fi ; \ - chmod a+x debian/pylint/usr/bin/$$exec; \ - done - - install -m 644 elisp/pylint.el debian/pylint/usr/share/emacs/site-lisp/pylint/ - - # install tests - #(cd test && find . -type f -not \( -path '*/CVS/*' -or -name '*.pyc' \) -exec install -D --mode=644 {} ../debian/pylint/usr/share/doc/pylint/test/{} \;) - -# Build architecture-independent files here. -binary-indep: build install - dh_testdir - dh_testroot - dh_install -i - dh_pysupport -i - dh_installchangelogs -i ChangeLog - dh_installexamples -i - dh_installdocs -i - dh_installman -i - dh_installemacsen - dh_link -i - dh_compress -i -X.py -X.ini -X.xml -Xtest - dh_fixperms -i - dh_installdeb -i - dh_gencontrol -i - dh_md5sums -i - dh_builddeb -i - -binary-arch: - -binary: binary-indep -.PHONY: build clean binary binary-indep binary-arch diff --git a/doc/logo.png b/doc/logo.png Binary files differnew file mode 100644 index 0000000..15c1c6c --- /dev/null +++ b/doc/logo.png diff --git a/doc/run.rst b/doc/run.rst index e69fee7..1fb95cd 100644 --- a/doc/run.rst +++ b/doc/run.rst @@ -125,5 +125,3 @@ Other useful global options include: --list-msgs Generate pylint's messages. --full-documentation Generate pylint's full documentation, in reST format. - - @@ -70,14 +70,14 @@ class BasicStream(object): if text.strip(): self.nextTitle = text.strip() - if text.startswith('\n'): + if text.startswith(os.linesep): self.contents.append('') if self.currout: self.outdict[self.currout].append('') - self.contents[-1] += text.strip('\n') + self.contents[-1] += text.strip(os.linesep) if self.currout: - self.outdict[self.currout][-1] += text.strip('\n') - if text.endswith('\n') and text.strip(): + self.outdict[self.currout][-1] += text.strip(os.linesep) + if text.endswith(os.linesep) and text.strip(): self.contents.append('') if self.currout: self.outdict[self.currout].append('') @@ -227,7 +227,7 @@ class LintGui(object): #buttons Button(top_frame, text='Open', command=self.file_open).pack(side=LEFT) - Button(top_frame, text='Open Package', + Button(top_frame, text='Open Package', command=(lambda : self.file_open(package=True))).pack(side=LEFT) self.btnRun = Button(top_frame, text='Run', command=self.run_lint) @@ -134,7 +134,11 @@ MSGS = { 'useless-suppression', 'Reported when a message is explicitly disabled for a line or ' 'a block of code, but never triggered.'), - + 'I0022': ('Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg"', + 'deprecated-pragma', + 'You should preferably use "pylint:disable" or "pylint:enable" ' + 'instead of the deprecated suppression pragma style ' + '"pylint:disable-msg" or "pylint:enable-msg"'), 'E0001': ('%s', 'syntax-error', @@ -470,9 +474,8 @@ This is used by the global evaluation report (RP0004).'}), meth = self._options_methods[opt] except KeyError: meth = self._bw_options_methods[opt] - warn('%s is deprecated, replace it with %s (%s, line %s)' % ( - opt, opt.split('-')[0], self.current_file, line), - DeprecationWarning) + # found a "(dis|en)able-msg" pragma deprecated suppresssion + self.add_message('deprecated-pragma', line=start[0]) for msgid in splitstrip(value): try: if (opt, msgid) == ('disable', 'all'): @@ -518,7 +521,7 @@ This is used by the global evaluation report (RP0004).'}), if first <= lineno <= last: # Set state for all lines for this block, if the # warning is applied to nodes. - if self._messages[msgid].scope == WarningScope.NODE: + if self.check_message_id(msgid).scope == WarningScope.NODE: if lineno > firstchildlineno: state = True first_, last_ = node.block_range(lineno) @@ -563,6 +566,22 @@ This is used by the global evaluation report (RP0004).'}), checker.active_msgs = messages return neededcheckers + def should_analyze_file(self, modname, path): + """Returns whether or not a module should be checked. + + This implementation returns True for all inputs, indicating that all + files should be linted. + + Subclasses may override this method to indicate that modules satisfying + certain conditions should not be linted. + + :param str modname: The name of the module to be checked. + :param str path: The full path to the source code of the module. + :returns: True if the module should be checked. + :rtype: bool + """ + return True + def check(self, files_or_modules): """main checking entry: check a list of files or modules from their name. @@ -582,12 +601,14 @@ This is used by the global evaluation report (RP0004).'}), # build ast and check modules or packages for descr in self.expand_files(files_or_modules): modname, filepath = descr['name'], descr['path'] + if not self.should_analyze_file(modname, filepath): + continue if self.config.files_output: reportfile = 'pylint_%s.%s' % (modname, self.reporter.extension) self.reporter.set_output(open(reportfile, 'w')) self.set_current_module(modname, filepath) # get the module representation - astroid = self.get_astroid(filepath, modname) + astroid = self.get_ast(filepath, modname) if astroid is None: continue self.base_name = descr['basename'] @@ -639,8 +660,8 @@ This is used by the global evaluation report (RP0004).'}), self._raw_module_msgs_state = {} self._ignored_msgs = {} - def get_astroid(self, filepath, modname): - """return a astroid representation for a module""" + def get_ast(self, filepath, modname): + """return a ast(roid) representation for a module""" try: return MANAGER.ast_from_file(filepath, modname, source=True) except SyntaxError, ex: @@ -717,6 +738,8 @@ This is used by the global evaluation report (RP0004).'}), # save results if persistent run if self.config.persistent: config.save_results(self.stats, self.base_name) + else: + self.reporter.on_close(self.stats, {}) # specific reports ######################################################## diff --git a/man/pyreverse.1 b/man/pyreverse.1 index d37f83d..a68ceb5 100644 --- a/man/pyreverse.1 +++ b/man/pyreverse.1 @@ -10,7 +10,7 @@ them. .SH DESCRIPTION .B pyreverse -is a python source analyzer. It parses a python packages and produces UML +is a python source analyzer. It parses python packages and produces UML diagrams in different output formats. (dot, all formats available for dot, and vcg). With different options, you can have fine tuning on what and how modules, @@ -45,10 +45,10 @@ modes using '+' like 'SPECIAL+OTHER'. Correct modes are : - 'PUB_ONLY' : filter all non public attributes (default) - 'ALL' : no filter - 'SPECIAL' : filter Python special functions except constructor - - 'OTHER' : filter protected and private attributes [currentt: PUB_ONLY] + - 'OTHER' : filter protected and private attributes [current: PUB_ONLY] .IP "-d<file>, --diadefs=<file>" -create diagram according to the diagrams definitions in <file> +create diagram according to the diagram definitions in <file> .IP "-c <class>, --class=<class>" create a class diagram with all classes related to <class> [current: none] the class must be in the file <modules>. By default, this will include all @@ -69,8 +69,8 @@ show recursively all associated off all associated classes [current: none] .IP "-b, --builtin" include builtin objects in representation of classes [current: False] .IP "-m [yn], --module-names=[yn]" -include module name in representation of classes. This will include full -module path in the class name. [current: none] +include module name in representation of classes. This will include the +full module path in the class name. [current: none] .IP "-k, --only-classnames" don't show attributes and methods in the class boxes; @@ -83,15 +83,15 @@ are all formats that dot can produce and [default: dot] .SH EXAMPLES - Here are some examples for command line options : +Here are some examples for command line options: .IP "pyreverse <project> -a1 -s1 -m" \-a1 \-s1 will include one level of ancestor and associated classes in the diagram of the <project> modules, while \-m will show the full module -path of each class. You can use the same way the +path of each class. You can use the .B -a, -s, -A, -S -options. +options in the same way. Note that on class diagrams (using .B -c ) \-a and \-s will rather reduce than enlarge your diagram. diff --git a/pyreverse/diagrams.py b/pyreverse/diagrams.py index 7cb5930..288ce0f 100644 --- a/pyreverse/diagrams.py +++ b/pyreverse/diagrams.py @@ -1,4 +1,4 @@ -# 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 @@ -19,16 +19,8 @@ import astroid from pylint.pyreverse.utils import is_interface, FilterMixIn -def set_counter(value): - """Figure counter (re)set""" - Figure._UID_COUNT = value - -class Figure: +class Figure(object): """base class for counter handling""" - _UID_COUNT = 0 - def __init__(self): - Figure._UID_COUNT += 1 - self.fig_id = Figure._UID_COUNT class Relationship(Figure): """a relation ship from an object in the diagram to another @@ -62,6 +54,11 @@ class ClassDiagram(Figure, FilterMixIn): self._nodes = {} self.depends = [] + def get_relationships(self, role): + # sorted to get predictable (hence testable) results + return sorted(self.relationships.get(role, ()), + key=lambda x: (x.from_object.fig_id, x.to_object.fig_id)) + def add_relationship(self, from_object, to_object, relation_type, name=None): """create a relation ship diff --git a/pyreverse/writer.py b/pyreverse/writer.py index dd00850..446d6d6 100644 --- a/pyreverse/writer.py +++ b/pyreverse/writer.py @@ -44,28 +44,31 @@ class DiagramWriter(object): def write_packages(self, diagram): """write a package diagram""" - for obj in diagram.modules(): - label = self.get_title(obj) - self.printer.emit_node(obj.fig_id, label=label, shape='box') + # sorted to get predictable (hence testable) results + for i, obj in enumerate(sorted(diagram.modules(), key=lambda x: x.title)): + self.printer.emit_node(i, label=self.get_title(obj), shape='box') + obj.fig_id = i # package dependencies - for rel in diagram.relationships.get('depends', ()): + for rel in diagram.get_relationships('depends'): self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, **self.pkg_edges) def write_classes(self, diagram): """write a class diagram""" - for obj in diagram.objects: - self.printer.emit_node(obj.fig_id, **self.get_values(obj) ) + # sorted to get predictable (hence testable) results + for i, obj in enumerate(sorted(diagram.objects, key=lambda x: x.title)): + self.printer.emit_node(i, **self.get_values(obj) ) + obj.fig_id = i # inheritance links - for rel in diagram.relationships.get('specialization', ()): + for rel in diagram.get_relationships('specialization'): self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, **self.inh_edges) # implementation links - for rel in diagram.relationships.get('implements', ()): + for rel in diagram.get_relationships('implements'): self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, **self.imp_edges) # generate associations - for rel in diagram.relationships.get('association', ()): + for rel in diagram.get_relationships('association'): self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, label=rel.name, **self.ass_edges) @@ -114,7 +117,7 @@ class DotWriter(DiagramWriter): The label contains all attributes and methods """ - label = obj.title + label = obj.title if obj.shape == 'interface': label = u"«interface»\\n%s" % label if not self.config.only_classnames: diff --git a/test/data/classes_No_Name.dot b/test/data/classes_No_Name.dot index 975ecf6..e75553e 100644 --- a/test/data/classes_No_Name.dot +++ b/test/data/classes_No_Name.dot @@ -1,12 +1,12 @@ digraph "classes_No_Name" { charset="utf-8" rankdir=BT -"4" [shape="record", label="{Ancestor|attr : str\lcls_member\l|set_value()\lget_value()\l}"]; -"5" [shape="record", label="{Specialization|relation\ltop : str\lTYPE : str\l|}"]; -"8" [shape="record", label="{«interface»\nInterface|\l|get_value()\lset_value()\l}"]; -"9" [shape="record", label="{DoNothing|\l|}"]; -"5" -> "4" [arrowtail="none", arrowhead="empty"]; -"4" -> "8" [arrowtail="node", style="dashed", arrowhead="empty"]; -"9" -> "4" [arrowhead="diamond", style="solid", arrowtail="none", fontcolor="green", label="cls_member"]; -"9" -> "5" [arrowhead="diamond", style="solid", arrowtail="none", fontcolor="green", label="relation"]; +"0" [label="{Ancestor|attr : str\lcls_member\l|set_value()\lget_value()\l}", shape="record"]; +"1" [label="{DoNothing|\l|}", shape="record"]; +"2" [label="{«interface»\nInterface|\l|get_value()\lset_value()\l}", shape="record"]; +"3" [label="{Specialization|relation\ltop : str\lTYPE : str\l|}", shape="record"]; +"3" -> "0" [arrowhead="empty", arrowtail="none"]; +"0" -> "2" [arrowhead="empty", arrowtail="node", style="dashed"]; +"1" -> "0" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"]; +"1" -> "3" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"]; } diff --git a/test/data/packages_No_Name.dot b/test/data/packages_No_Name.dot index 19416a4..1ceeb72 100644 --- a/test/data/packages_No_Name.dot +++ b/test/data/packages_No_Name.dot @@ -1,8 +1,8 @@ digraph "packages_No_Name" { charset="utf-8" rankdir=BT -"3" [shape="box", label="data.clientmodule_test"]; -"6" [shape="box", label="data"]; -"7" [shape="box", label="data.suppliermodule_test"]; -"3" -> "7" [arrowtail="none", arrowhead="open"]; +"0" [label="data", shape="box"]; +"1" [label="data.clientmodule_test", shape="box"]; +"2" [label="data.suppliermodule_test", shape="box"]; +"1" -> "2" [arrowhead="open", arrowtail="none"]; } diff --git a/test/input/func_assert_2uple.py b/test/input/func_assert_2uple.py index a6746ce..de93d3b 100644 --- a/test/input/func_assert_2uple.py +++ b/test/input/func_assert_2uple.py @@ -8,4 +8,4 @@ assert (1 == 1, ), "no error" assert (1 == 1, ) assert (1 == 1, 2 == 2, 3 == 5), "no error" assert () -assert (True,'error msg') #this should generate a warning +assert (True, 'error msg') #this should generate a warning diff --git a/test/input/func_block_disable_msg.py b/test/input/func_block_disable_msg.py index cce04a4..2551823 100644 --- a/test/input/func_block_disable_msg.py +++ b/test/input/func_block_disable_msg.py @@ -91,7 +91,7 @@ class Foo(object): eris = 5 if eris: - print ("In block") + print "In block" # pylint: disable=E1101 # no error diff --git a/test/input/func_dangerous_default.py b/test/input/func_dangerous_default.py index a68aaec..3e07415 100644 --- a/test/input/func_dangerous_default.py +++ b/test/input/func_dangerous_default.py @@ -4,11 +4,11 @@ __revision__ = '' HEHE = {} -def function1(value = []): +def function1(value=[]): """docstring""" print value -def function2(value = HEHE): +def function2(value=HEHE): """docstring""" print value @@ -16,24 +16,24 @@ def function3(value): """docstring""" print value -def function4(value = set()): +def function4(value=set()): """set is mutable and dangerous.""" print value -def function5(value = frozenset()): +def function5(value=frozenset()): """frozenset is immutable and safe.""" print value GLOBAL_SET = set() -def function6(value = GLOBAL_SET): +def function6(value=GLOBAL_SET): """set is mutable and dangerous.""" print value -def function7(value = dict()): +def function7(value=dict()): """dict is mutable and dangerous.""" print value -def function8(value = list()): +def function8(value=list()): """list is mutable and dangerous.""" print value diff --git a/test/input/func_format.py b/test/input/func_format.py index a4eed23..828a180 100644 --- a/test/input/func_format.py +++ b/test/input/func_format.py @@ -85,3 +85,24 @@ titreprojet = '<tr><td colspan="10">\ <img src="images/drapeau_vert.png" alt="Drapeau vert" />\ <strong>%s</strong></td></tr>' % aaaa +with open('a') as a, open('b') as b: + pass + +with open('a') as a, open('b') as b: pass # multiple-statements + +# Well-formatted try-except-finally block. +try: + pass +except IOError, e: + print e +finally: + pass + +try: + pass +except IOError, e: + print e +finally: pass # multiple-statements + +# This is not allowed by the default configuration. +if True: print False diff --git a/test/input/func_i0022.py b/test/input/func_i0022.py new file mode 100644 index 0000000..f5b308f --- /dev/null +++ b/test/input/func_i0022.py @@ -0,0 +1,22 @@ +"""Deprecated suppression style.""" + +__revision__ = None + +a = 1 # pylint: disable=invalid-name +b = 1 # pylint: disable-msg=invalid-name + +# pylint: disable=invalid-name +c = 1 +# pylint: enable=invalid-name + +# pylint: disable-msg=invalid-name +d = 1 +# pylint: enable-msg=invalid-name + +# pylint: disable-msg=C0103 +e = 1 +# pylint: enable-msg=C0103 + +# pylint: disable=C0103 +f = 1 +# pylint: enable=C0103 diff --git a/test/input/func_missing_super_argument_py20.py b/test/input/func_missing_super_argument_py_30.py index ec20857..ec20857 100644 --- a/test/input/func_missing_super_argument_py20.py +++ b/test/input/func_missing_super_argument_py_30.py diff --git a/test/input/func_namedtuple.py b/test/input/func_namedtuple.py new file mode 100644 index 0000000..8cfd048 --- /dev/null +++ b/test/input/func_namedtuple.py @@ -0,0 +1,10 @@ +"""Test namedtuple attributes. + +Regression test for: +https://bitbucket.org/logilab/pylint/issue/93/pylint-crashes-on-namedtuple-attribute +""" +__revision__ = None + +from collections import namedtuple +Thing = namedtuple('Thing', ()) +print Thing.x diff --git a/test/input/func_noerror_classes_meth_signature.py b/test/input/func_noerror_classes_meth_signature.py index 8f120cd..2aa5b18 100644 --- a/test/input/func_noerror_classes_meth_signature.py +++ b/test/input/func_noerror_classes_meth_signature.py @@ -34,5 +34,5 @@ class Sub(Super): def ___private3(self, arg): pass - def method(self, param = 'abc'): + def method(self, param='abc'): pass 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 71e67b9..2ed0b1f 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 @@ -16,15 +16,15 @@ def func(xxx): return xxx def func2(xxx): return xxx + func2(1) -import sys; print sys.exc_info( ) +import sys; print sys.exc_info() for i in range(10): print i j = 4; LAMB = lambda x: x+j -FUNC4 = lambda a, b : a != b -FUNC3 = lambda (a, b) : a != b +FUNC4 = lambda a, b: a != b +FUNC3 = lambda (a, b): a != b # test http://www.logilab.org/ticket/6954: -with open('f') as f: print(f.read()) +with open('f') as f: print f.read() diff --git a/test/input/func_noerror_inner_classes.py b/test/input/func_noerror_inner_classes.py index 02391a2..84fb43d 100644 --- a/test/input/func_noerror_inner_classes.py +++ b/test/input/func_noerror_inner_classes.py @@ -6,7 +6,7 @@ __revision__ = "alpha" class Aaa(object): """docstring""" def __init__(self): - self.__setattr__('a','b') + self.__setattr__('a', 'b') def one_public(self): diff --git a/test/input/func_noerror_new_style_class_py_30.py b/test/input/func_noerror_new_style_class_py_30.py index d08ab46..87bfb9e 100644 --- a/test/input/func_noerror_new_style_class_py_30.py +++ b/test/input/func_noerror_new_style_class_py_30.py @@ -35,7 +35,7 @@ self.mode) self.was_modified = True # - def close(self) : + def close(self): """Close the file.""" if self.verbose: diff --git a/test/input/func_noerror_nonregr.py b/test/input/func_noerror_nonregr.py index 992f9f3..c4c8c38 100644 --- a/test/input/func_noerror_nonregr.py +++ b/test/input/func_noerror_nonregr.py @@ -3,7 +3,7 @@ __revision__ = 1 -def function1(cbarg = lambda: None): +def function1(cbarg=lambda: None): """ File "/usr/lib/python2.4/site-packages/logilab/astroid/scoped_nodes.py", line 391, in mularg_class # this method doesn't exist anymore diff --git a/test/input/func_noerror_static_method.py b/test/input/func_noerror_static_method.py index d109d2e..ea5f7d8 100644 --- a/test/input/func_noerror_static_method.py +++ b/test/input/func_noerror_static_method.py @@ -23,7 +23,5 @@ class MyClass(object): class_met = classmethod(class_met) if __name__ == '__main__': - MyClass.static_met("var1","var2") + MyClass.static_met("var1", "var2") MyClass.class_met("var1") - - diff --git a/test/input/func_noerror_used_before_assignment.py b/test/input/func_noerror_used_before_assignment.py new file mode 100644 index 0000000..6ba0b88 --- /dev/null +++ b/test/input/func_noerror_used_before_assignment.py @@ -0,0 +1,5 @@ +# pylint: disable = line-too-long, multiple-statements, missing-module-attribute +"""https://bitbucket.org/logilab/pylint/issue/111/false-positive-used-before-assignment-with""" + +try: raise IOError(1, "a") +except IOError, err: print err diff --git a/test/input/func_set_literal_as_default_py27.py b/test/input/func_set_literal_as_default_py27.py index 04fb9b5..5c7200e 100644 --- a/test/input/func_set_literal_as_default_py27.py +++ b/test/input/func_set_literal_as_default_py27.py @@ -2,7 +2,6 @@ __revision__ = '' -def function1(value = {1}): +def function1(value={1}): """set is mutable and dangerous.""" print value - diff --git a/test/input/func_superfluous_parens.py b/test/input/func_superfluous_parens.py new file mode 100644 index 0000000..112ab2b --- /dev/null +++ b/test/input/func_superfluous_parens.py @@ -0,0 +1,19 @@ +"""Test the superfluous-parens warning.""" + +__revision__ = 1 + +if (3 == 5): + pass +if not (3 == 5): + pass +if not (3 or 5): + pass +for (x) in (1, 2, 3): + print x +if (1) in (1, 2, 3): + pass +if (1, 2) in (1, 2, 3): + pass +DICT = {'a': 1, 'b': 2} +del(DICT['b']) +del DICT['a'] diff --git a/test/input/func_unpacking_non_sequence.py b/test/input/func_unpacking_non_sequence.py index 973c40d..cd35328 100644 --- a/test/input/func_unpacking_non_sequence.py +++ b/test/input/func_unpacking_non_sequence.py @@ -41,7 +41,7 @@ a, b = [1, 2] a, b = (1, 2) a, b = set([1, 2]) a, b = {1: 2, 2: 3} -a, b = "xy" +a, b = "xy" a, b = Seq() a, b = Iter() a, b = (number for number in range(2)) diff --git a/test/input/func_use_for_or_listcomp_var.py b/test/input/func_use_for_or_listcomp_var.py index a8918df..3de5a72 100644 --- a/test/input/func_use_for_or_listcomp_var.py +++ b/test/input/func_use_for_or_listcomp_var.py @@ -24,4 +24,4 @@ for line in __revision__: for x in []: pass for x in range(3): - print (lambda : x)() # OK + print (lambda: x)() # OK diff --git a/test/input/func_w0108.py b/test/input/func_w0108.py index 0da9a96..0d4cc62 100644 --- a/test/input/func_w0108.py +++ b/test/input/func_w0108.py @@ -21,7 +21,7 @@ _ = lambda x, y, z, *args, **kwargs: _ANYARGS(x, y, z, *args, **kwargs) # Lambdas that are *not* unnecessary and should *not* trigger warnings. _ = lambda x: x _ = lambda x: x() -_ = lambda x = 4: hash(x) +_ = lambda x=4: hash(x) _ = lambda x, y: range(y, x) _ = lambda x: range(5, x) _ = lambda x, y: range(x, 5) diff --git a/test/input/func_w0623_py_30.py b/test/input/func_w0623_py_30.py index 7c489cc..9bccbc6 100644 --- a/test/input/func_w0623_py_30.py +++ b/test/input/func_w0623_py_30.py @@ -44,6 +44,8 @@ try: pass except KeyError, exceptions.RuntimeError: # W0623 pass +except KeyError, exceptions.RuntimeError.args: # W0623 + pass except KeyError, OSError: # W0623 pass except KeyError, MyOtherError: # W0623 @@ -63,4 +65,3 @@ except IOError, exc5: # this is fine print exc5 except MyOtherError, exc5: # this is fine print exc5 - diff --git a/test/input/func_w0711.py b/test/input/func_w0711.py index 9cc791e..97c3697 100644 --- a/test/input/func_w0711.py +++ b/test/input/func_w0711.py @@ -9,7 +9,7 @@ except Exception or StandardError: print "caught1" except Exception and StandardError: print "caught2" -except (Exception or StandardError): +except Exception or StandardError: print "caught3" except (Exception or StandardError), exc: print "caught4" diff --git a/test/input/syntax_error.py b/test/input/syntax_error.py index 505f8dc..6c1e7a7 100644 --- a/test/input/syntax_error.py +++ b/test/input/syntax_error.py @@ -1,2 +1,2 @@ if False: -print 'hop' +print('hop') diff --git a/test/messages/func_bad_context_manager.txt b/test/messages/func_bad_context_manager.txt index 1634546..1b5d5d5 100644 --- a/test/messages/func_bad_context_manager.txt +++ b/test/messages/func_bad_context_manager.txt @@ -1,3 +1,3 @@ -E: 40:FirstBadContextManager.__exit__: __exit__ must accept 3 arguments: type, value, traceback
-E: 49:SecondBadContextManager.__exit__: __exit__ must accept 3 arguments: type, value, traceback
-E: 58:ThirdBadContextManager.__exit__: __exit__ must accept 3 arguments: type, value, traceback
+E: 40:FirstBadContextManager.__exit__: __exit__ must accept 3 arguments: type, value, traceback +E: 49:SecondBadContextManager.__exit__: __exit__ must accept 3 arguments: type, value, traceback +E: 58:ThirdBadContextManager.__exit__: __exit__ must accept 3 arguments: type, value, traceback diff --git a/test/messages/func_format.txt b/test/messages/func_format.txt index 27b8107..ce5df81 100644 --- a/test/messages/func_format.txt +++ b/test/messages/func_format.txt @@ -1,31 +1,46 @@ -C: 6: Operator not preceded by a space +C: 6: Exactly one space required before assignment notpreceded= 1 ^ -C: 7: Operator not followed by a space +C: 7: Exactly one space required after assignment notfollowed =1 ^ -C: 8: Operator not followed by a space +C: 8: Exactly one space required after comparison notfollowed <=1 ^^ -C: 19: Comma not followed by a space +C: 19: Exactly one space required after comma aaaa,bbbb = 1,2 - ^^ + ^ +C: 19: Exactly one space required after comma +aaaa,bbbb = 1,2 + ^ C: 24: More than one statement on a single line -C: 26: Comma not followed by a space +C: 26: Exactly one space required after comma + aaaa,bbbb = 1,2 + ^ +C: 26: Exactly one space required after comma aaaa,bbbb = 1,2 - ^^ -C: 27: Comma not followed by a space + ^ +C: 27: Exactly one space required after comma + aaaa,bbbb = bbbb,aaaa + ^ +C: 27: Exactly one space required after comma aaaa,bbbb = bbbb,aaaa - ^^ -C: 29: Comma not followed by a space + ^ +C: 29: Exactly one space required after comma bbbb = (1,2,3) - ^^ -C: 51:other: Operator not preceded by a space + ^ +C: 29: Exactly one space required after comma +bbbb = (1,2,3) + ^ +C: 51: Exactly one space required before assignment funky= funky+2 ^ -C: 71:_gc_debug: Operator not preceded by a space +C: 71: Exactly one space required before assignment ocount[obj.__class__]+= 1 ^^ -C: 73:_gc_debug: Operator not preceded by a space +C: 73: Exactly one space required around assignment ocount[obj.__class__]=1 ^ +C: 91: More than one statement on a single line +C:105: More than one statement on a single line +C:108: More than one statement on a single line diff --git a/test/messages/func_i0022.txt b/test/messages/func_i0022.txt new file mode 100644 index 0000000..477335c --- /dev/null +++ b/test/messages/func_i0022.txt @@ -0,0 +1,13 @@ +I: 5: Locally disabling C0103 +I: 5: Suppressed 'invalid-name' (from line 5) +I: 6: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg" +I: 6: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg" +I: 6: Suppressed 'invalid-name' (from line 6) +I: 9: Suppressed 'invalid-name' (from line 8) +I: 12: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg" +I: 13: Suppressed 'invalid-name' (from line 12) +I: 14: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg" +I: 16: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg" +I: 17: Suppressed 'invalid-name' (from line 16) +I: 18: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg" +I: 21: Suppressed 'invalid-name' (from line 20) diff --git a/test/messages/func_missing_super_argument_py20.txt b/test/messages/func_missing_super_argument_py_30.txt index 5c97f0d..5c97f0d 100644 --- a/test/messages/func_missing_super_argument_py20.txt +++ b/test/messages/func_missing_super_argument_py_30.txt diff --git a/test/messages/func_namedtuple.txt b/test/messages/func_namedtuple.txt new file mode 100644 index 0000000..d1ef2b1 --- /dev/null +++ b/test/messages/func_namedtuple.txt @@ -0,0 +1 @@ +E: 10: Class 'Thing' has no 'x' member diff --git a/test/messages/func_superfluous_parens.txt b/test/messages/func_superfluous_parens.txt new file mode 100644 index 0000000..77ff186 --- /dev/null +++ b/test/messages/func_superfluous_parens.txt @@ -0,0 +1,5 @@ +C: 5: Unnecessary parens after 'if' keyword +C: 7: Unnecessary parens after 'not' keyword +C: 11: Unnecessary parens after 'for' keyword +C: 13: Unnecessary parens after 'if' keyword +C: 18: Unnecessary parens after 'del' keyword diff --git a/test/messages/func_toolonglines_py30.txt b/test/messages/func_toolonglines_py30.txt new file mode 100644 index 0000000..7678258 --- /dev/null +++ b/test/messages/func_toolonglines_py30.txt @@ -0,0 +1,4 @@ +C: 1: Line too long (90/80) +C: 2: Line too long (94/80) +C: 17: Line too long (83/80) +C: 25: Line too long (94/80) diff --git a/test/messages/func_unbalanced_tuple_unpacking.txt b/test/messages/func_unbalanced_tuple_unpacking.txt index 91880e7..c9311cc 100644 --- a/test/messages/func_unbalanced_tuple_unpacking.txt +++ b/test/messages/func_unbalanced_tuple_unpacking.txt @@ -1,5 +1,6 @@ -W: 9:do_stuff: Possible unbalanced tuple unpacking with sequence at line 9: left side has 2 label(s), right side has 3 value(s) -W: 14:do_stuff1: Possible unbalanced tuple unpacking with sequence at line 14: left side has 2 label(s), right side has 3 value(s) -W: 19:do_stuff2: Possible unbalanced tuple unpacking with sequence at line 19: left side has 2 label(s), right side has 3 value(s) -W: 50:do_stuff7: Possible unbalanced tuple unpacking with sequence at line 46: left side has 2 label(s), right side has 3 value(s) -W: 69:do_stuff9: Possible unbalanced tuple unpacking with sequence at line 7 (input.unpacking): left side has 2 label(s), right side has 3 value(s)
\ No newline at end of file +W: 9:do_stuff: Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s) +W: 14:do_stuff1: Possible unbalanced tuple unpacking with sequence [1, 2, 3]: left side has 2 label(s), right side has 3 value(s) +W: 19:do_stuff2: Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s) +W: 50:do_stuff7: Possible unbalanced tuple unpacking with sequence defined at line 46: left side has 2 label(s), right side has 3 value(s) +W: 69:do_stuff9: Possible unbalanced tuple unpacking with sequence defined at line 7 of input.unpacking: left side has 2 label(s), right side has 3 value(s) + diff --git a/test/messages/func_unpacking_non_sequence.txt b/test/messages/func_unpacking_non_sequence.txt index 5023de3..55a7745 100644 --- a/test/messages/func_unpacking_non_sequence.txt +++ b/test/messages/func_unpacking_non_sequence.txt @@ -1,7 +1,8 @@ -W: 61: Attempting to unpack a non-sequence with non-sequence at line 52
-W: 63: Attempting to unpack a non-sequence with non-sequence at line 63
-W: 64: Attempting to unpack a non-sequence with non-sequence at line 64
-W: 65: Attempting to unpack a non-sequence with non-sequence at line 9 (input.unpacking)
-W: 66: Attempting to unpack a non-sequence with non-sequence at line 11 (input.unpacking)
-W: 67: Attempting to unpack a non-sequence with non-sequence at line 58
-W: 68: Attempting to unpack a non-sequence with non-sequence at line unknown (sys)
\ No newline at end of file +W: 61: Attempting to unpack a non-sequence defined at line 52 +W: 62: Attempting to unpack a non-sequence +W: 63: Attempting to unpack a non-sequence None +W: 64: Attempting to unpack a non-sequence 1 +W: 65: Attempting to unpack a non-sequence defined at line 9 of input.unpacking +W: 66: Attempting to unpack a non-sequence defined at line 11 of input.unpacking +W: 67: Attempting to unpack a non-sequence defined at line 58 +W: 68: Attempting to unpack a non-sequence diff --git a/test/messages/func_w0623_py_30.txt b/test/messages/func_w0623_py_30.txt index b764def..a2923f1 100644 --- a/test/messages/func_w0623_py_30.txt +++ b/test/messages/func_w0623_py_30.txt @@ -1,11 +1,12 @@ C: 28:some_function: Invalid variable name "FOO" C: 41: Invalid constant name "exc3" -C: 55: Invalid variable name "OOPS" +C: 57: 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 +W: 47: Redefining name 'args' from object 'exceptions.RuntimeError' in exception handler +W: 49: Redefining name 'OSError' from builtins in exception handler +W: 51: Redefining name 'MyOtherError' from outer scope (line 36) in exception handler diff --git a/test/test_format.py b/test/test_format.py index f14b476..b682671 100644 --- a/test/test_format.py +++ b/test/test_format.py @@ -19,150 +19,239 @@ Check format checker helper functions import sys import re from os import linesep +import tokenize +import StringIO from logilab.common.testlib import TestCase, unittest_main +from astroid import test_utils from pylint.checkers.format import * -from pylint.testutils import TestReporter - -REPORTER = TestReporter() - -class StringRgxTest(TestCase): - """test the STRING_RGX regular expression""" - - def test_known_values_1(self): - self.assertEqual(STRING_RGX.sub('', '"yo"'), '') - - def test_known_values_2(self): - self.assertEqual(STRING_RGX.sub('', "'yo'"), '') - - def test_known_values_tq_1(self): - self.assertEqual(STRING_RGX.sub('', '"""yo"""'), '') - - def test_known_values_tq_2(self): - self.assertEqual(STRING_RGX.sub('', '"""yo\n"""'), '') - - def test_known_values_ta_1(self): - self.assertEqual(STRING_RGX.sub('', "'''yo'''"), '') - - def test_known_values_ta_2(self): - self.assertEqual(STRING_RGX.sub('', "'''yo\n'''"), '') - - def test_known_values_5(self): - self.assertEqual(STRING_RGX.sub('', r'"yo\"yo"'), '') - - def test_known_values_6(self): - self.assertEqual(STRING_RGX.sub('', r"'yo\'yo'"), '') - - def test_known_values_7(self): - self.assertEqual(STRING_RGX.sub('', '"yo"upi"yo"upi'), 'upiupi') - - def test_known_values_8(self): - self.assertEqual(STRING_RGX.sub('', r"'yo\'yo\''"), '') - - def test_known_values_9(self): - self.assertEqual(STRING_RGX.sub('', r'"yoyo\""'), '') - - def test_known_values_10(self): - self.assertEqual(STRING_RGX.sub('', 'self.filterFunc = eval(\'lambda %s: %s\'%(\',\'.join(variables),formula),{},{})'), - 'self.filterFunc = eval(%(.join(variables),formula),{},{})') - - def test_known_values_11(self): - self.assertEqual(STRING_RGX.sub('', 'cond_list[index] = OLD_PROG.sub(r\'getattr(__old__,"\1")\',cond)'), - 'cond_list[index] = OLD_PROG.sub(r,cond)') - - - def test_no_crash(self): - crash_str = """wizardBmp = ('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\n', (115, 260), None)""" - re.sub(SQSTRING_RGX, '', crash_str) - re.sub(TQSTRING_RGX, '', crash_str) - re.sub(SASTRING_RGX, '', crash_str) - re.sub(TASTRING_RGX, '', crash_str) - self.skipTest('XXX: explain the test, remove it or assert something') - -if linesep != '\n': - LINE_RGX = re.compile(linesep) - def ulines(strings): - return strings[0], LINE_RGX.sub('\n', strings[1]) -else: - def ulines(strings): - return strings - -class ChecklineFunctionTest(TestCase): - """test the check_line method""" - - def test_known_values_opspace_1(self): - self.assertEqual(ulines(check_line('a=1')), ('C0322', 'a=1\n ^')) - - def test_known_values_opspace_2(self): - self.assertEqual(ulines(check_line('a= 1')), ('C0322', 'a= 1\n ^') ) - - def test_known_values_opspace_3(self): - self.assertEqual(ulines(check_line('a =1')), ('C0323', 'a =1\n ^')) - - def test_known_values_opspace_4(self): - self.assertEqual(check_line('f(a=1)'), None) - - def test_known_values_opspace_4(self): - self.assertEqual(check_line('f(a=1)'), None) - - def test_known_values_colonnl_2(self): - self.assertEqual(check_line('a[:1]'), None) - - def test_known_values_colonnl_3(self): - self.assertEqual(check_line('a[1:]'), None) - - def test_known_values_colonnl_4(self): - self.assertEqual(check_line('a[1:2]'), None) - - def test_known_values_colonnl_5(self): - self.assertEqual(check_line('def intersection(list1, list2):'), None) - - def test_known_values_colonnl_6(self): - self.assertEqual(check_line('def intersection(list1, list2):\n'), None) - - def test_known_values_colonnl_7(self): - self.assertEqual(check_line('if file[:pfx_len] == path:\n'), None) - - def test_known_values_colonnl_9(self): - self.assertEqual(check_line('if file[:pfx_len[1]] == path:\n'), None) - - def test_known_values_colonnl_10(self): - self.assertEqual(check_line('if file[pfx_len[1]] == path:\n'), None) - - def test_known_values_commaspace_1(self): - self.assertEqual(ulines(check_line('a, b = 1,2')), - ('C0324', 'a, b = 1,2\n ^^')) - - def test_known_values_commaspace_2(self): - self.assertEqual(check_line('should_not_warn = [1, 2, 3,]\n'), - None) - - def test_known_values_commaspace_3(self): - self.assertEqual(check_line('should_not_warn = {1:2, 3:4,}\n'), - None) - - def test_known_values_commaspace_4(self): - self.assertEqual(check_line('should_not_warn = (1, 2, 3,)\n'), - None) - - def test_known_values_instring_1(self): - self.assertEqual(check_line('f("a=1")'), None) - - def test_known_values_instring_2(self): - self.assertEqual(ulines(check_line('print >>1, ("a:1")')), - ('C0323', 'print >>1, ("a:1")\n ^')) - - def test_known_values_all_1(self): - self.assertEqual(ulines(check_line("self.filterFunc = eval('lambda %s: %s'%(','.join(variables),formula),{},{})")), - ('C0324', "self.filterFunc = eval('lambda %s: %s'%(','.join(variables),formula),{},{})\n ^^")) - - def test_known_values_tqstring(self): - self.assertEqual(check_line('print """<a="=")\n"""'), None) +from pylint.testutils import CheckerTestCase, Message + + +def tokenize_str(code): + return list(tokenize.generate_tokens(StringIO.StringIO(code).readline)) + + +class MultiStatementLineTest(CheckerTestCase): + CHECKER_CLASS = FormatChecker + + def testSingleLineIfStmts(self): + stmt = test_utils.extract_node(""" + if True: pass #@ + """) + with self.assertAddsMessages(Message('C0321', node=stmt.body[0])): + self.checker.process_tokens([]) + self.checker.visit_default(stmt.body[0]) + self.checker.config.single_line_if_stmt = True + with self.assertNoMessages(): + self.checker.process_tokens([]) + self.checker.visit_default(stmt.body[0]) + stmt = test_utils.extract_node(""" + if True: pass #@ + else: + pass + """) + with self.assertAddsMessages(Message('C0321', node=stmt.body[0])): + self.checker.process_tokens([]) + self.checker.visit_default(stmt.body[0]) + + def testTryExceptFinallyNoMultipleStatement(self): + tree = test_utils.extract_node(""" + try: #@ + pass + except: + pass + finally: + pass""") + with self.assertNoMessages(): + self.checker.process_tokens([]) + self.checker.visit_default(tree.body[0]) + + + +class SuperfluousParenthesesTest(CheckerTestCase): + CHECKER_CLASS = FormatChecker + + def testCheckKeywordParensHandlesValidCases(self): + self.checker._keywords_with_parens = set() + cases = [ + 'if foo:', + 'if foo():', + 'if (x and y) or z:', + 'assert foo()', + 'assert ()', + 'if (1, 2) in (3, 4):', + 'if (a or b) in c:', + 'return (x for x in x)', + 'if (x for x in x):', + 'for x in (x for x in x):', + 'not (foo or bar)', + 'not (foo or bar) and baz', + ] + with self.assertNoMessages(): + for code in cases: + self.checker._check_keyword_parentheses(tokenize_str(code), 0) + + def testCheckKeywordParensHandlesUnnecessaryParens(self): + self.checker._keywords_with_parens = set() + cases = [ + (Message('C0325', line=1, args='if'), + 'if (foo):', 0), + (Message('C0325', line=1, args='if'), + 'if ((foo, bar)):', 0), + (Message('C0325', line=1, args='if'), + 'if (foo(bar)):', 0), + (Message('C0325', line=1, args='return'), + 'return ((x for x in x))', 0), + (Message('C0325', line=1, args='not'), + 'not (foo)', 0), + (Message('C0325', line=1, args='not'), + 'if not (foo):', 1), + (Message('C0325', line=1, args='if'), + 'if (not (foo)):', 0), + (Message('C0325', line=1, args='not'), + 'if (not (foo)):', 2), + ] + for msg, code, offset in cases: + with self.assertAddsMessages(msg): + self.checker._check_keyword_parentheses(tokenize_str(code), offset) + + def testFuturePrintStatementWithoutParensWarning(self): + code = """from __future__ import print_function +print('Hello world!') +""" + tree = test_utils.build_module(code) + with self.assertNoMessages(): + self.checker.process_module(tree) + self.checker.process_tokens(tokenize_str(code)) + + +class CheckSpaceTest(CheckerTestCase): + CHECKER_CLASS = FormatChecker + + def testParenthesesGood(self): + good_cases = [ + '(a)\n', + '(a * (b + c))\n', + '( #\na)\n', + ] + with self.assertNoMessages(): + for code in good_cases: + self.checker.process_tokens(tokenize_str(code)) + + def testParenthesesBad(self): + with self.assertAddsMessages( + Message('C0326', line=1, + args=('No', 'allowed', 'after', 'bracket', '( a)\n^'))): + self.checker.process_tokens(tokenize_str('( a)\n')) + + with self.assertAddsMessages( + Message('C0326', line=1, + args=('No', 'allowed', 'before', 'bracket', '(a )\n ^'))): + self.checker.process_tokens(tokenize_str('(a )\n')) + + with self.assertAddsMessages( + Message('C0326', line=1, + args=('No', 'allowed', 'before', 'bracket', 'foo (a)\n ^'))): + self.checker.process_tokens(tokenize_str('foo (a)\n')) + + with self.assertAddsMessages( + Message('C0326', line=1, + args=('No', 'allowed', 'before', 'bracket', '{1: 2} [1]\n ^'))): + self.checker.process_tokens(tokenize_str('{1: 2} [1]\n')) + + def testTrailingCommaGood(self): + with self.assertNoMessages(): + self.checker.process_tokens(tokenize_str('(a, )\n')) + self.checker.process_tokens(tokenize_str('(a,)\n')) + + self.checker.config.no_space_check = [] + with self.assertNoMessages(): + self.checker.process_tokens(tokenize_str('(a,)\n')) + + def testTrailingCommaBad(self): + self.checker.config.no_space_check = [] + with self.assertAddsMessages( + Message('C0326', line=1, + args=('No', 'allowed', 'before', 'bracket', '(a, )\n ^'))): + self.checker.process_tokens(tokenize_str('(a, )\n')) + + def testComma(self): + with self.assertAddsMessages( + Message('C0326', line=1, + args=('No', 'allowed', 'before', 'comma', '(a , b)\n ^'))): + self.checker.process_tokens(tokenize_str('(a , b)\n')) + + def testSpacesAllowedInsideSlices(self): + good_cases = [ + '[a:b]\n', + '[a : b]\n', + '[a : ]\n', + '[:a]\n', + '[:]\n', + '[::]\n', + ] + with self.assertNoMessages(): + for code in good_cases: + self.checker.process_tokens(tokenize_str(code)) + + def testKeywordSpacingGood(self): + with self.assertNoMessages(): + self.checker.process_tokens(tokenize_str('foo(foo=bar)\n')) + self.checker.process_tokens(tokenize_str('lambda x=1: x\n')) + + def testKeywordSpacingBad(self): + with self.assertAddsMessages( + Message('C0326', line=1, + args=('No', 'allowed', 'before', 'keyword argument assignment', + '(foo =bar)\n ^'))): + self.checker.process_tokens(tokenize_str('(foo =bar)\n')) + + with self.assertAddsMessages( + Message('C0326', line=1, + args=('No', 'allowed', 'after', 'keyword argument assignment', + '(foo= bar)\n ^'))): + self.checker.process_tokens(tokenize_str('(foo= bar)\n')) + + with self.assertAddsMessages( + Message('C0326', line=1, + args=('No', 'allowed', 'around', 'keyword argument assignment', + '(foo = bar)\n ^'))): + self.checker.process_tokens(tokenize_str('(foo = bar)\n')) + + def testOperatorSpacingGood(self): + good_cases = [ + 'a = b\n' + 'a < b\n' + 'a\n< b\n', + ] + with self.assertNoMessages(): + for code in good_cases: + self.checker.process_tokens(tokenize_str(code)) + + def testOperatorSpacingBad(self): + with self.assertAddsMessages( + Message('C0326', line=1, + args=('Exactly one', 'required', 'before', 'comparison', 'a< b\n ^'))): + self.checker.process_tokens(tokenize_str('a< b\n')) + + with self.assertAddsMessages( + Message('C0326', line=1, + args=('Exactly one', 'required', 'after', 'comparison', 'a <b\n ^'))): + self.checker.process_tokens(tokenize_str('a <b\n')) + + with self.assertAddsMessages( + Message('C0326', line=1, + args=('Exactly one', 'required', 'around', 'comparison', 'a<b\n ^'))): + self.checker.process_tokens(tokenize_str('a<b\n')) + + with self.assertAddsMessages( + Message('C0326', line=1, + args=('Exactly one', 'required', 'around', 'comparison', 'a< b\n ^'))): + self.checker.process_tokens(tokenize_str('a< b\n')) - def test_known_values_tastring(self): - self.assertEqual(check_line("print '''<a='=')\n'''"), None) if __name__ == '__main__': unittest_main() diff --git a/test/test_func.py b/test/test_func.py index 56bb82c..e8c746d 100644 --- a/test/test_func.py +++ b/test/test_func.py @@ -42,19 +42,12 @@ class LintTestNonExistentModuleTC(LintTestUsingModule): _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): - module = join(INPUT_DIR, 'nonexistent.py') - _get_expected = lambda self: 'F: 1: No module named %s\n' % self.module[len(getcwd())+1 :] - tags = testlib.Tags(('generated', 'pylint_input_%s' % module)) - def test_functionality(self): - self._test([self.module]) - class TestTests(testlib.TestCase): """check that all testable messages have been checked""" @testlib.tag('coverage') def test_exhaustivity(self): # skip fatal messages - todo = [msgid for msgid in linter._messages if msgid[0] != 'F'] + todo = [msg.msgid for msg in linter.messages if msg.msgid[0] != 'F'] for msgid in test_reporter.message_ids: try: todo.remove(msgid) @@ -89,7 +82,7 @@ def cb_file(*args): return base_cb_file(*args) callbacks = [cb_test_gen(LintTestUsingModule), - cb_file] + cb_file] # Gen tests @@ -103,8 +96,6 @@ def gen_tests(filter_rgx): if is_to_run('nonexistent'): tests.append(LintTestNonExistentModuleTC) - if not MODULES_ONLY: - tests.append(LintTestNonExistentFileTC) tests.append(LintBuiltinModuleTest) diff --git a/test/test_import_graph.py b/test/test_import_graph.py index f9aba99..a1dfdc8 100644 --- a/test/test_import_graph.py +++ b/test/test_import_graph.py @@ -51,9 +51,9 @@ class ImportCheckerTC(TestCase): l.global_set_option('ignore', ('func_unknown_encoding.py',)) try: l.check('input') - self.assert_(exists('import.dot')) - self.assert_(exists('ext_import.dot')) - self.assert_(exists('int_import.dot')) + self.assertTrue(exists('import.dot')) + self.assertTrue(exists('ext_import.dot')) + self.assertTrue(exists('int_import.dot')) finally: for fname in ('import.dot', 'ext_import.dot', 'int_import.dot'): try: diff --git a/test/test_misc.py b/test/test_misc.py index d2c03c0..a2cba9b 100644 --- a/test/test_misc.py +++ b/test/test_misc.py @@ -15,7 +15,7 @@ """ Tests for the misc checker. """ - +import sys import tempfile import os import contextlib @@ -31,7 +31,11 @@ def create_file_backed_module(code): # because on Windows the file must be closed before writing to it, # see http://bugs.python.org/issue14243 fd, tmp = tempfile.mkstemp() - os.write(fd, code) + if sys.version_info >= (3, 0): + # erff + os.write(fd, bytes(code, 'ascii')) + else: + os.write(fd, code) try: module = test_utils.build_module(code) @@ -53,7 +57,7 @@ class FixmeTest(CheckerTestCase): Message(msg_id='W0511', line=2, args=u'FIXME')): self.checker.process_module(module) - def test_emtpy_fixme_regex(self): + def test_empty_fixme_regex(self): self.checker.config.notes = [] with create_file_backed_module( """a = 1 diff --git a/test/unittest_lint.py b/test/unittest_lint.py index 7612ef4..1102982 100644 --- a/test/unittest_lint.py +++ b/test/unittest_lint.py @@ -86,12 +86,12 @@ class PyLinterTC(TestCase): def test_message_help(self): msg = self.linter.check_message_id('F0001') self.assertMultiLineEqual( - ''':F0001 (fatal): + ''':fatal (F0001): Used when an error occurred preventing the analysis of a module (unable to find it for instance). This message belongs to the master checker.''', msg.format_help(checkerref=True)) self.assertMultiLineEqual( - ''':F0001 (fatal): + ''':fatal (F0001): Used when an error occurred preventing the analysis of a module (unable to find it for instance).''', msg.format_help(checkerref=False)) @@ -101,13 +101,13 @@ class PyLinterTC(TestCase): msg = build_message_def(self.linter._checkers['typecheck'][0], 'E1122', checkers.typecheck.MSGS['E1122']) self.assertMultiLineEqual( - ''':E1122 (duplicate-keyword-arg): *Duplicate keyword argument %r in function call* + ''':duplicate-keyword-arg (E1122): *Duplicate keyword argument %r in function call* Used when a function call passes the same keyword argument multiple times. This message belongs to the typecheck checker. It can't be emitted when using Python >= 2.6.''', msg.format_help(checkerref=True)) self.assertMultiLineEqual( - ''':E1122 (duplicate-keyword-arg): *Duplicate keyword argument %r in function call* + ''':duplicate-keyword-arg (E1122): *Duplicate keyword argument %r in function call* Used when a function call passes the same keyword argument multiple times. This message can't be emitted when using Python >= 2.6.''', msg.format_help(checkerref=False)) @@ -170,7 +170,7 @@ class PyLinterTC(TestCase): linter.open() filepath = join(INPUTDIR, 'func_block_disable_msg.py') linter.set_current_module('func_block_disable_msg') - astroid = linter.get_astroid(filepath, 'func_block_disable_msg') + astroid = linter.get_ast(filepath, 'func_block_disable_msg') linter.process_tokens(tokenize_module(astroid)) orig_state = linter._module_msgs_state.copy() linter._module_msgs_state = {} @@ -214,7 +214,7 @@ class PyLinterTC(TestCase): self.assertEqual(17, linter._suppression_mapping['W0613', 18]) self.assertEqual(30, linter._suppression_mapping['E1101', 33]) - self.assert_(('E1101', 46) not in linter._suppression_mapping) + self.assertTrue(('E1101', 46) not in linter._suppression_mapping) self.assertEqual(1, linter._suppression_mapping['C0302', 18]) self.assertEqual(1, linter._suppression_mapping['C0302', 50]) # This is tricky. While the disable in line 106 is disabling @@ -261,7 +261,7 @@ 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 %s docstring*' in output) + self.assertIn(':empty-docstring (C0112): *Empty %s docstring*', output) def test_lint_ext_module_with_file_output(self): self.linter.set_reporter(text.TextReporter()) @@ -282,6 +282,13 @@ class PyLinterTC(TestCase): except: pass + def test_lint_should_analyze_file(self): + self.linter.set_reporter(text.TextReporter()) + self.linter.config.files_output = True + self.linter.should_analyze_file = lambda *args: False + self.linter.check('os') + self.assertFalse(os.path.exists('pylint_os.txt')) + def test_enable_report(self): self.assertEqual(self.linter.report_is_enabled('RP0001'), True) self.linter.disable('RP0001') @@ -362,6 +369,23 @@ class PyLinterTC(TestCase): ['C: 1: Line too long (1/2)', 'C: 2: Line too long (3/4)'], self.linter.reporter.messages) + def test_add_renamed_message(self): + self.linter.add_renamed_message('C9999', 'old-bad-name', 'invalid-name') + self.assertEqual('invalid-name', + self.linter.check_message_id('C9999').symbol) + self.assertEqual('invalid-name', + self.linter.check_message_id('old-bad-name').symbol) + + def test_renamed_message_register(self): + class Checker(object): + msgs = {'W1234': ('message', 'msg-symbol', 'msg-description', + {'old_names': [('W0001', 'old-symbol')]})} + self.linter.register_messages(Checker()) + self.assertEqual('msg-symbol', + self.linter.check_message_id('W0001').symbol) + self.assertEqual('msg-symbol', + self.linter.check_message_id('old-symbol').symbol) + class ConfigTC(TestCase): diff --git a/test/unittest_pyreverse_diadefs.py b/test/unittest_pyreverse_diadefs.py index ebcf371..a42d73a 100644 --- a/test/unittest_pyreverse_diadefs.py +++ b/test/unittest_pyreverse_diadefs.py @@ -1,4 +1,4 @@ -# Copyright (c) 2000-2004 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 @@ -26,15 +26,10 @@ from astroid.inspector import Linker from pylint.pyreverse.diadefslib import * -from utils import Config +from unittest_pyreverse_writer import Config, get_project -def astroid_wrapper(func, modname): - return func(modname) - -PROJECT = MANAGER.project_from_files(['data'], astroid_wrapper) - -CONFIG = Config() -HANDLER = DiadefsHandler(CONFIG) +PROJECT = get_project('data') +HANDLER = DiadefsHandler(Config()) def _process_classes(classes): """extract class names of a list""" @@ -43,7 +38,7 @@ def _process_classes(classes): def _process_relations(relations): """extract relation indices from a relation list""" result = [] - for rel_type, rels in relations.iteritems(): + for rel_type, rels in relations.iteritems(): for rel in rels: result.append( (rel_type, rel.from_object.title, rel.to_object.title) ) @@ -54,7 +49,7 @@ def _process_relations(relations): class DiaDefGeneratorTC(unittest.TestCase): def test_option_values(self): """test for ancestor, associated and module options""" - handler = DiadefsHandler( Config()) + handler = DiadefsHandler(Config()) df_h = DiaDefGenerator(Linker(PROJECT), handler) cl_config = Config() cl_config.classes = ['Specialization'] @@ -125,15 +120,15 @@ 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'], astroid_wrapper) - handler = DiadefsHandler( Config() ) + project = get_project('data') + handler = DiadefsHandler(Config()) diadefs = handler.get_diadefs(project, Linker(project, tag=True) ) cd = diadefs[1] relations = _process_relations(cd.relationships) self.assertEqual(relations, self._should_rels) def test_known_values2(self): - project = MANAGER.project_from_files(['data.clientmodule_test'], astroid_wrapper) + project = get_project('data.clientmodule_test') dd = DefaultDiadefGenerator(Linker(project), HANDLER).visit(project) self.assertEqual(len(dd), 1) keys = [d.TYPE for d in dd] @@ -158,7 +153,7 @@ class ClassDiadefGeneratorTC(unittest.TestCase): (True, special), (True, 'data.suppliermodule_test.DoNothing'), ]) - + def test_known_values2(self): HANDLER.config.module_names = False cd = ClassDiadefGenerator(Linker(PROJECT), HANDLER).class_diagram(PROJECT, 'data.clientmodule_test.Specialization') diff --git a/test/unittest_pyreverse_writer.py b/test/unittest_pyreverse_writer.py index a4e283e..b850679 100644 --- a/test/unittest_pyreverse_writer.py +++ b/test/unittest_pyreverse_writer.py @@ -1,4 +1,4 @@ -# Copyright (c) 2000-2008 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,37 +17,96 @@ unittest for visitors.diadefs and extensions.diadefslib modules """ -from os.path import abspath, dirname, join -from astroid.inspector import Linker + +import os +import sys +import codecs +from os.path import join, dirname, abspath +from difflib import unified_diff + from logilab.common.testlib import TestCase, unittest_main +from astroid import MANAGER +from astroid.inspector import Linker + from pylint.pyreverse.diadefslib import DefaultDiadefGenerator, DiadefsHandler -from pylint.pyreverse.diagrams import set_counter from pylint.pyreverse.writer import DotWriter - from pylint.pyreverse.utils import get_visibility -from utils import FileTC, build_file_case, get_project, Config -project = get_project(join(dirname(abspath(__file__)), 'data')) -linker = Linker(project) -set_counter(0) -config = Config() +_DEFAULTS = { + 'all_ancestors': None, 'show_associated': None, + 'module_names': None, + 'output_format': 'dot', 'diadefs_file': None, 'quiet': 0, + 'show_ancestors': None, 'classes': (), 'all_associated': None, + 'mode': 'PUB_ONLY', 'show_builtin': False, 'only_classnames': False + } + +class Config(object): + """config object for tests""" + def __init__(self): + for attr, value in _DEFAULTS.items(): + setattr(self, attr, value) + + +def _file_lines(path): + # 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:'))] + return [line for line in lines if line] + +def get_project(module, name=None): + """return a astroid project representation""" + # flush cache + MANAGER._modules_by_name = {} + def _astroid_wrapper(func, modname): + return func(modname) + return MANAGER.project_from_files([module], _astroid_wrapper, + project_name=name) + +CONFIG = Config() + +class DotWriterTC(TestCase): + + @classmethod + def setUpClass(cls): + project = get_project(cls.datadir) + linker = Linker(project) + handler = DiadefsHandler(CONFIG) + dd = DefaultDiadefGenerator(linker, handler).visit(project) + for diagram in dd: + diagram.extract_relationships() + writer = DotWriter(CONFIG) + writer.write(dd) -handler = DiadefsHandler(config) -dd = DefaultDiadefGenerator(linker, handler).visit(project) -for diagram in dd: - diagram.extract_relationships() + @classmethod + def tearDownClass(cls): + for fname in ('packages_No_Name.dot', 'classes_No_Name.dot',): + try: + os.remove(fname) + except: + continue -class DotWriterTC(FileTC): + def _test_same_file(self, generated_file): + expected_file = self.datapath(generated_file) + generated = _file_lines(generated_file) + expected = _file_lines(expected_file) + generated = '\n'.join(generated) + expected = '\n'.join(expected) + files = "\n *** expected : %s, generated : %s \n" % ( + expected_file, generated_file) + self.assertEqual(expected, generated, '%s%s' % ( + files, '\n'.join(line for line in unified_diff( + expected.splitlines(), generated.splitlines() ))) ) + os.remove(generated_file) + + def test_package_diagram(self): + self._test_same_file('packages_No_Name.dot') + + def test_class_diagram(self): + self._test_same_file('classes_No_Name.dot') - generated_files = ('packages_No_Name.dot', 'classes_No_Name.dot',) - def setUp(self): - FileTC.setUp(self) - writer = DotWriter(config) - writer.write(dd) - -build_file_case(DotWriterTC) class GetVisibilityTC(TestCase): @@ -60,16 +119,17 @@ class GetVisibilityTC(TestCase): for name in ["__g_", "____dsf", "__23_9"]: got = get_visibility(name) self.assertEqual(got, 'private', - 'got %s instead of private for value %s' % (got, name)) + 'got %s instead of private for value %s' % (got, name)) def test_public(self): self.assertEqual(get_visibility('simple'), 'public') def test_protected(self): - for name in ["_","__", "___", "____", "_____", "___e__", "_nextsimple", "_filter_it_"]: + for name in ["_","__", "___", "____", "_____", "___e__", + "_nextsimple", "_filter_it_"]: got = get_visibility(name) self.assertEqual(got, 'protected', - 'got %s instead of protected for value %s' % (got, name)) + 'got %s instead of protected for value %s' % (got, name)) if __name__ == '__main__': diff --git a/test/utils.py b/test/utils.py deleted file mode 100644 index 34e1d44..0000000 --- a/test/utils.py +++ /dev/null @@ -1,81 +0,0 @@ -"""some pylint test utilities -""" - -import os -import sys -from os.path import join, dirname, abspath -import codecs - -from logilab.common.testlib import TestCase -from astroid import MANAGER - - -def _astroid_wrapper(func, modname): - return func(modname) - - -def _sorted_file(path): - # 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] - lines.sort() - return '\n'.join(lines) - -def get_project(module, name=None): - """return a astroid project representation - """ - manager = MANAGER - # flush cache - manager._modules_by_name = {} - return manager.project_from_files([module], _astroid_wrapper, - project_name=name) - -DEFAULTS = {'all_ancestors': None, 'show_associated': None, - 'module_names': None, - 'output_format': 'dot', 'diadefs_file': None, 'quiet': 0, - 'show_ancestors': None, 'classes': (), 'all_associated': None, - 'mode': 'PUB_ONLY', 'show_builtin': False, 'only_classnames': False} - -class Config(object): - """config object for tests""" - def __init__(self): - for attr, value in DEFAULTS.items(): - setattr(self, attr, value) - -class FileTC(TestCase): - """base test case for testing file output""" - - generated_files = () - - def setUp(self): - self.expected_files = [join(dirname(abspath(__file__)), 'data', file) - for file in self.generated_files] - - def tearDown(self): - for fname in self.generated_files: - try: - os.remove(fname) - except: - continue - - def _test_same_file(self, index): - generated_file = self.generated_files[index] - expected_file = self.expected_files[index] - generated = _sorted_file(generated_file) - expected = _sorted_file(expected_file) - - from difflib import unified_diff - files = "\n *** expected : %s, generated : %s \n" % ( - expected_file, generated_file) - self.assertEqual(expected, generated, '%s%s' % ( - files, '\n'.join(line for line in unified_diff( - expected.splitlines(), generated.splitlines() ))) ) - os.remove(generated_file) - - -def build_file_case(filetc): - for i in range(len(filetc.generated_files)): - setattr(filetc, 'test_same_file_%s' %i, - lambda self, index=i: self._test_same_file(index)) diff --git a/testutils.py b/testutils.py index e939e51..2c80f2a 100644 --- a/testutils.py +++ b/testutils.py @@ -175,7 +175,7 @@ class CheckerTestCase(testlib.TestCase): 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) + self.assertEqual(list(messages), got, msg) # Init @@ -246,14 +246,14 @@ class LintTestUsingModule(testlib.TestCase): ex.__str__ = exception_str raise got = self.linter.reporter.finalize() - self.assertMultiLineEqual(got, self._get_expected()) + self.assertMultiLineEqual(self._get_expected(), got) def _get_expected(self): if self.module.startswith('func_noerror_'): expected = '' else: - output = open(self.output) + output = open(self.output, 'U') expected = output.read().strip() + '\n' output.close() return expected @@ -0,0 +1,10 @@ +[tox] +# official list is +#envlist = py25, py26, py27, py32, py33 +# though 2.5 support is known to be broken... +envlist = py27, py33 +[testenv] +deps = + logilab-common + astroid +commands = pytest -t {envsitepackagesdir}/pylint/test/
\ No newline at end of file @@ -133,7 +133,7 @@ def build_message_def(checker, msgid, msg_tuple): class MessageDefinition(object): def __init__(self, checker, msgid, msg, descr, symbol, scope, - minversion=None, maxversion=None): + minversion=None, maxversion=None, old_names=None): self.checker = checker assert len(msgid) == 5, 'Invalid message id %s' % msgid assert msgid[0] in MSG_TYPES, \ @@ -145,6 +145,7 @@ class MessageDefinition(object): self.scope = scope self.minversion = minversion self.maxversion = maxversion + self.old_names = old_names or [] def may_be_emitted(self): """return True if message may be emitted using the current interpreter""" @@ -189,10 +190,15 @@ class MessagesHandlerMixIn(object): """ def __init__(self): - # dictionary of registered messages + # Primary registry for all active messages (i.e. all messages + # that can be emitted by pylint for the underlying Python + # version). It contains the 1:1 mapping from symbolic names + # to message definition objects. self._messages = {} - # dictionary from string symbolic id to Message object. - self._messages_by_symbol = {} + # Maps alternative names (numeric IDs, deprecated names) to + # message definitions. May contain several names for each definition + # object. + self._alternative_names = {} self._msgs_state = {} self._module_msgs_state = {} # None self._raw_module_msgs_state = {} @@ -201,6 +207,16 @@ class MessagesHandlerMixIn(object): self._ignored_msgs = {} self._suppression_mapping = {} + def add_renamed_message(self, old_id, old_symbol, new_symbol): + """Register the old ID and symbol for a warning that was renamed. + + This allows users to keep using the old ID/symbol in suppressions. + """ + msg = self.check_message_id(new_symbol) + msg.old_names.append((old_id, old_symbol)) + self._alternative_names[old_id] = msg + self._alternative_names[old_symbol] = msg + def register_messages(self, checker): """register a dictionary of messages @@ -213,10 +229,10 @@ class MessagesHandlerMixIn(object): chkid = None for msgid, msg_tuple in checker.msgs.iteritems(): msg = build_message_def(checker, msgid, msg_tuple) - assert msg.symbol not in self._messages_by_symbol, \ + assert msg.symbol not in self._messages, \ 'Message symbol %r is already defined' % msg.symbol # avoid duplicate / malformed ids - assert msg.msgid not in self._messages, \ + assert msg.msgid not in self._alternative_names, \ 'Message id %r is already defined' % msgid assert chkid is None or chkid == msg.msgid[1:3], \ 'Inconsistent checker part in message id %r' % msgid @@ -224,8 +240,11 @@ class MessagesHandlerMixIn(object): if not msg.may_be_emitted(): self._msgs_state[msg.msgid] = False continue - self._messages[msg.msgid] = msg - self._messages_by_symbol[msg.symbol] = msg + self._messages[msg.symbol] = msg + self._alternative_names[msg.msgid] = msg + for old_id, old_symbol in msg.old_names: + self._alternative_names[old_id] = msg + self._alternative_names[old_symbol] = msg self._msgs_by_category.setdefault(msg.msgid[0], []).append(msg.msgid) def disable(self, msgid, scope='package', line=None): @@ -246,7 +265,7 @@ class MessagesHandlerMixIn(object): if msgid.lower() in self._checkers: for checker in self._checkers[msgid.lower()]: for _msgid in checker.msgs: - if _msgid in self._messages: + if _msgid in self._alternative_names: self.disable(_msgid, scope, line) return # msgid is report id? @@ -312,13 +331,14 @@ class MessagesHandlerMixIn(object): Raises UnknownMessage if the message id is not defined. """ - if msgid in self._messages_by_symbol: - return self._messages_by_symbol[msgid] - msgid = msgid.upper() - try: - return self._messages[msgid] - except KeyError: - raise UnknownMessage('No such message id %s' % msgid) + if msgid[1:].isdigit(): + msgid = msgid.upper() + for source in (self._alternative_names, self._messages): + try: + return source[msgid] + except KeyError: + pass + raise UnknownMessage('No such message id %s' % msgid) def get_msg_display_string(self, msgid): """Generates a user-consumable representation of a message. @@ -335,14 +355,19 @@ class MessagesHandlerMixIn(object): except (KeyError, TypeError): return MSG_STATE_SCOPE_CONFIG - def is_message_enabled(self, msgid, line=None): + def is_message_enabled(self, msg_descr, line=None): """return true if the message associated to the given message id is enabled msgid may be either a numeric or symbolic message id. """ - if msgid in self._messages_by_symbol: - msgid = self._messages_by_symbol[msgid].msgid + try: + msgid = self.check_message_id(msg_descr).msgid + except UnknownMessage: + # The linter checks for messages that are not registered + # due to version mismatch, just treat them as message IDs + # for now. + msgid = msg_descr if line is None: return self._msgs_state.get(msgid, True) try: @@ -487,14 +512,16 @@ class MessagesHandlerMixIn(object): print print + @property + def messages(self): + """The list of all active messages.""" + return self._messages.itervalues() + def list_messages(self): """output full messages list documentation in ReST format""" - msgids = [] - for msgid in self._messages: - msgids.append(msgid) - msgids.sort() - for msgid in msgids: - print self.check_message_id(msgid).format_help(checkerref=False) + msgs = sorted(self._messages.itervalues(), key=lambda msg: msg.msgid) + for msg in msgs: + print msg.format_help(checkerref=False) print |
