diff options
author | Daniel Balparda <balparda@google.com> | 2013-12-04 16:15:46 -0800 |
---|---|---|
committer | Daniel Balparda <balparda@google.com> | 2013-12-04 16:15:46 -0800 |
commit | ef4fb237a94b74fef2d542c2aa1de7995c60a94f (patch) | |
tree | dfb6d3a27dbea02d86eb8a4cf79b7d962bf93b81 | |
parent | ffd9fa5d4d549949ebf25c58e9a703a08c80fc11 (diff) | |
parent | 48fc04a7eaabe69dcc30740d138e3e73ff5db2b9 (diff) | |
download | pylint-git-ef4fb237a94b74fef2d542c2aa1de7995c60a94f.tar.gz |
Merged logilab/pylint into default
30 files changed, 779 insertions, 332 deletions
@@ -10,6 +10,18 @@ ChangeLog for Pylint * 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. diff --git a/__pkginfo__.py b/__pkginfo__.py index 997b9a59e..614828ee7 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/format.py b/checkers/format.py index 1cf0edc61..f307f33dc 100644 --- a/checkers/format.py +++ b/checkers/format.py @@ -21,19 +21,41 @@ 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', + 'print', 'raise', 'return', 'while', 'yield'] + +_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 +86,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 +119,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 - -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'), +def _underline_token(token): + length = token[3][1] - token[2][1] + offset = token[2][1] + return token[4] + (' ' * offset) + ('^' * length) - (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 +144,7 @@ class FormatChecker(BaseTokenChecker): * use of <> instead of != """ - __implements__ = (ITokenChecker, IAstroidChecker) + __implements__ = (ITokenChecker, IAstroidChecker, IRawChecker) # configuration section name name = 'format' @@ -193,6 +160,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 +190,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 +418,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 +430,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 +490,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 +512,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 +540,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/utils.py b/checkers/utils.py index a06440496..72a9733d9 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): diff --git a/checkers/variables.py b/checkers/variables.py index 0d35884cc..7f4ff1be7 100644 --- a/checkers/variables.py +++ b/checkers/variables.py @@ -556,7 +556,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,19 +572,19 @@ 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 isinstance(infered, (astroid.Tuple, astroid.List)): values = infered.itered() if len(targets) != len(values): if node.root().name == infered.root().name: - location = infered.lineno or 'unknown' + location = infered.lineno or 'unknown' else: - location = '%s (%s)' % (infered.lineno or 'unknown', - infered.root().name) + location = '%s (%s)' % (infered.lineno or 'unknown', + infered.root().name) self.add_message('unbalanced-tuple-unpacking', node=node, args=(location, - len(targets), + len(targets), len(values))) else: if infered is astroid.YES: @@ -596,14 +596,14 @@ builtins. Remember that you should avoid to define new builtins when possible.' except astroid.NotFoundError: continue else: - break - else: + break + else: if node.root().name == infered.root().name: - location = infered.lineno or 'unknown' + location = infered.lineno or 'unknown' else: - location = '%s (%s)' % (infered.lineno or 'unknown', - infered.root().name) - + location = '%s (%s)' % (infered.lineno or 'unknown', + infered.root().name) + self.add_message('unpacking-non-sequence', node=node, args=(location, )) @@ -521,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) @@ -566,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. @@ -585,6 +601,8 @@ 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')) diff --git a/test/input/func_assert_2uple.py b/test/input/func_assert_2uple.py index a6746ce0b..de93d3b37 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 cce04a454..2551823c7 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 a68aaec9f..3e07415c7 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 a4eed23cc..828a18025 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_noerror_classes_meth_signature.py b/test/input/func_noerror_classes_meth_signature.py index 8f120cdb5..2aa5b181c 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 71e67b9da..2ed0b1f56 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 02391a2e4..84fb43d8a 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 d08ab4682..87bfb9e99 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 992f9f39d..c4c8c3820 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 d109d2eaa..ea5f7d873 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_set_literal_as_default_py27.py b/test/input/func_set_literal_as_default_py27.py index 04fb9b588..5c7200e6f 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 000000000..112ab2bcf --- /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 973c40d75..cd35328ef 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 a8918df7d..3de5a723d 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 0da9a96ce..0d4cc62f4 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 7c489cc60..9bccbc6d4 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 9cc791ebc..97c369721 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/messages/func_format.txt b/test/messages/func_format.txt index 27b810774..ce5df813e 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_superfluous_parens.txt b/test/messages/func_superfluous_parens.txt new file mode 100644 index 000000000..77ff1866c --- /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_w0623_py_30.txt b/test/messages/func_w0623_py_30.txt index b764def81..a2923f1f9 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 f14b4763d..b6826719a 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 56bb82c7e..833d72779 100644 --- a/test/test_func.py +++ b/test/test_func.py @@ -54,7 +54,7 @@ class TestTests(testlib.TestCase): @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) diff --git a/test/unittest_lint.py b/test/unittest_lint.py index f6ed6a252..6442eb4d9 100644 --- a/test/unittest_lint.py +++ b/test/unittest_lint.py @@ -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): @@ -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 |