summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Balparda <balparda@google.com>2013-12-04 16:15:46 -0800
committerDaniel Balparda <balparda@google.com>2013-12-04 16:15:46 -0800
commitef4fb237a94b74fef2d542c2aa1de7995c60a94f (patch)
treedfb6d3a27dbea02d86eb8a4cf79b7d962bf93b81
parentffd9fa5d4d549949ebf25c58e9a703a08c80fc11 (diff)
parent48fc04a7eaabe69dcc30740d138e3e73ff5db2b9 (diff)
downloadpylint-git-ef4fb237a94b74fef2d542c2aa1de7995c60a94f.tar.gz
Merged logilab/pylint into default
-rw-r--r--ChangeLog12
-rw-r--r--__pkginfo__.py24
-rw-r--r--checkers/format.py414
-rw-r--r--checkers/utils.py2
-rw-r--r--checkers/variables.py24
-rw-r--r--lint.py20
-rw-r--r--test/input/func_assert_2uple.py2
-rw-r--r--test/input/func_block_disable_msg.py2
-rw-r--r--test/input/func_dangerous_default.py14
-rw-r--r--test/input/func_format.py21
-rw-r--r--test/input/func_noerror_classes_meth_signature.py2
-rw-r--r--test/input/func_noerror_defined_and_used_on_same_line.py8
-rw-r--r--test/input/func_noerror_inner_classes.py2
-rw-r--r--test/input/func_noerror_new_style_class_py_30.py2
-rw-r--r--test/input/func_noerror_nonregr.py2
-rw-r--r--test/input/func_noerror_static_method.py4
-rw-r--r--test/input/func_set_literal_as_default_py27.py3
-rw-r--r--test/input/func_superfluous_parens.py19
-rw-r--r--test/input/func_unpacking_non_sequence.py2
-rw-r--r--test/input/func_use_for_or_listcomp_var.py2
-rw-r--r--test/input/func_w0108.py2
-rw-r--r--test/input/func_w0623_py_30.py3
-rw-r--r--test/input/func_w0711.py2
-rw-r--r--test/messages/func_format.txt43
-rw-r--r--test/messages/func_superfluous_parens.txt5
-rw-r--r--test/messages/func_w0623_py_30.txt7
-rw-r--r--test/test_format.py365
-rw-r--r--test/test_func.py2
-rw-r--r--test/unittest_lint.py24
-rw-r--r--utils.py77
30 files changed, 779 insertions, 332 deletions
diff --git a/ChangeLog b/ChangeLog
index fbba321a7..194600ef3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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, ))
diff --git a/lint.py b/lint.py
index 823e94b17..26212407d 100644
--- a/lint.py
+++ b/lint.py
@@ -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):
diff --git a/utils.py b/utils.py
index 875ac10fc..05e8b41e5 100644
--- a/utils.py
+++ b/utils.py
@@ -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