summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorent Xicluna <florent.xicluna@gmail.com>2014-03-25 01:13:47 +0100
committerFlorent Xicluna <florent.xicluna@gmail.com>2014-03-25 01:13:47 +0100
commit3d8365a7c8ecd165cef630cc33244e6f3bc9085f (patch)
tree2e80e44db836ba38b5f10883ed92a0e5cac437d5
parent69259deba15894a169d288da6c3dd9780b2451f9 (diff)
parent63a0a02d9dc521b6355210c60997d9e787814232 (diff)
downloadpep8-3d8365a7c8ecd165cef630cc33244e6f3bc9085f.tar.gz
Merge branch 'master' into issue126
-rw-r--r--.travis.yml1
-rw-r--r--CHANGES.txt66
-rw-r--r--docs/api.rst2
-rw-r--r--docs/intro.rst32
-rwxr-xr-xpep8.py262
-rw-r--r--testsuite/E10.py36
-rw-r--r--testsuite/E12.py239
-rw-r--r--testsuite/E12not.py88
-rw-r--r--testsuite/E21.py2
-rw-r--r--testsuite/E22.py18
-rw-r--r--testsuite/E24.py2
-rw-r--r--testsuite/E25.py4
-rw-r--r--testsuite/E70.py4
-rw-r--r--testsuite/E71.py36
-rw-r--r--testsuite/E72.py2
-rw-r--r--testsuite/E90.py4
-rw-r--r--testsuite/W19.py29
-rw-r--r--testsuite/W60.py1
-rw-r--r--testsuite/support.py19
-rw-r--r--testsuite/test_api.py70
-rw-r--r--testsuite/test_shell.py9
-rw-r--r--testsuite/utf-8-bom.py6
-rw-r--r--tox.ini2
23 files changed, 692 insertions, 242 deletions
diff --git a/.travis.yml b/.travis.yml
index fc3ad34..82d9d4c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,5 @@
language: python
python:
- - 2.5
- 2.6
- 2.7
- 3.2
diff --git a/CHANGES.txt b/CHANGES.txt
index 3e7228a..b9767ac 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -5,11 +5,73 @@ Changelog
1.x (unreleased)
----------------
-* Honor ``# noqa`` for errors E711 and E712. (Issue #180)
-
* Report E129 instead of E125 for visually indented line with same
indent as next logical line. (Issue #126)
+* Report E713 and E714 when operators ``not in`` and ``is not`` are
+ recommended. (Issue #236)
+
+* Allow the checkers to report errors on empty files. (Issue #240)
+
+* Fix ignoring too many checks when ``--select`` is used with codes
+ declared in a flake8 extension. (Issue #216)
+
+* Fix regression with multiple brackets. (Issue #214)
+
+* Fix ``StyleGuide`` to parse the local configuration if the
+ keyword argument ``paths`` is specified. (Issue #246)
+
+* Fix a false positive E124 for hanging indent. (Issue #254)
+
+* Fix a false positive E126 with embedded colon. (Issue #144)
+
+* Fix a false positive E126 when indenting with tabs. (Issue #204)
+
+* Fix behaviour when ``exclude`` is in the configuration file and
+ the current directory is not the project directory. (Issue #247)
+
+* The logical checks can return ``None`` instead of an empty iterator.
+ (Issue #250)
+
+* Do not report multiple E101 if only the first indentation starts
+ with a tab. (Issue #237)
+
+* Fix a rare false positive W602. (Issue #34)
+
+
+1.4.6 (2013-07-02)
+------------------
+
+* Honor ``# noqa`` for errors E711 and E712. (Issue #180)
+
+* When both a ``tox.ini`` and a ``setup.cfg`` are present in the project
+ directory, merge their contents. The ``tox.ini`` file takes
+ precedence (same as before). (Issue #182)
+
+* Give priority to ``--select`` over ``--ignore``. (Issue #188)
+
+* Compare full path when excluding a file. (Issue #186)
+
+* Correctly report other E12 errors when E123 is ignored. (Issue #103)
+
+* New option ``--hang-closing`` to switch to the alternative style of
+ closing bracket indentation for hanging indent. Add error E133 for
+ closing bracket which is missing indentation. (Issue #103)
+
+* Accept both styles of closing bracket indentation for hanging indent.
+ Do not report error E123 in the default configuration. (Issue #103)
+
+* Do not crash when running AST checks and the document contains null bytes.
+ (Issue #184)
+
+* Fix false positive E261/E262 when the file contains a BOM. (Issue #193)
+
+* Fix E701, E702 and E703 not detected sometimes. (Issue #196)
+
+* Fix E122 not detected in some cases. (Issue #201 and #208)
+
+* Fix false positive E121 with multiple brackets. (Issue #203)
+
1.4.5 (2013-03-06)
------------------
diff --git a/docs/api.rst b/docs/api.rst
index 6bd4a1a..317a8be 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -27,7 +27,7 @@ The :class:`Checker` class can be used to check a single file.
.. automethod:: check_files(paths=None)
.. automethod:: input_file(filename, lines=None, expected=None, line_offset=0)
.. automethod:: input_dir(dirname)
- .. automethod:: excluded(filename)
+ .. automethod:: excluded(filename, parent=None)
.. automethod:: ignore_code(code)
.. automethod:: get_checks(argument_name)
diff --git a/docs/intro.rst b/docs/intro.rst
index d8153e3..1a50424 100644
--- a/docs/intro.rst
+++ b/docs/intro.rst
@@ -134,6 +134,8 @@ Quick help is available on the command line::
--count print total number of errors and warnings to standard
error and set exit code to 1 if total is not null
--max-line-length=n set maximum allowed line length (default: 79)
+ --hang-closing hang closing bracket instead of matching indentation of
+ opening bracket's line
--format=format set the error format [default|pylint|<custom>]
--diff report only lines changed according to the unified diff
received on STDIN
@@ -145,8 +147,8 @@ Quick help is available on the command line::
The project options are read from the [pep8] section of the tox.ini
file or the setup.cfg file located in any parent folder of the path(s)
being processed. Allowed options are: exclude, filename, select,
- ignore, max-line-length, count, format, quiet, show-pep8, show-source,
- statistics, verbose.
+ ignore, max-line-length, hang-closing, count, format, quiet, show-pep8,
+ show-source, statistics, verbose.
--config=path user config file location (default: ~/.config/pep8)
@@ -163,13 +165,14 @@ Example::
ignore = E226,E302,E41
max-line-length = 160
-At the project level, a ``.pep8`` file, a ``tox.ini`` file or a ``setup.cfg``
-file is read if present. Only the first file is considered. If this file
-does not have a ``[pep8]`` section, no project specific configuration is
+At the project level, a ``tox.ini`` file or a ``setup.cfg`` file is read if
+present (``.pep8`` file is also supported, but it is deprecated). If none of
+these files have a ``[pep8]`` section, no project specific configuration is
loaded.
If the ``ignore`` option is not in the configuration and not in the arguments,
-only the error codes ``E226`` and ``E241/E242`` are ignored (see below).
+only the error codes ``E123/E133``, ``E226`` and ``E241/E242`` are ignored
+(see below).
Error codes
@@ -195,7 +198,7 @@ This is the current list of error and warning codes:
+----------+----------------------------------------------------------------------+
| E122 (^) | continuation line missing indentation or outdented |
+----------+----------------------------------------------------------------------+
-| E123 (^) | closing bracket does not match indentation of opening bracket's line |
+| E123 (*) | closing bracket does not match indentation of opening bracket's line |
+----------+----------------------------------------------------------------------+
| E124 (^) | closing bracket does not match visual indentation |
+----------+----------------------------------------------------------------------+
@@ -209,6 +212,8 @@ This is the current list of error and warning codes:
+----------+----------------------------------------------------------------------+
| E129 (^) | visually indented line with same indent as next logical line |
+----------+----------------------------------------------------------------------+
+| E133 (*) | closing bracket is missing indentation |
++----------+----------------------------------------------------------------------+
+----------+----------------------------------------------------------------------+
| **E2** | *Whitespace* |
+----------+----------------------------------------------------------------------+
@@ -299,6 +304,10 @@ This is the current list of error and warning codes:
+----------+----------------------------------------------------------------------+
| E712 (^) | comparison to True should be 'if cond is True:' or 'if cond:' |
+----------+----------------------------------------------------------------------+
+| E713 | evaluating membership should be 'elem not in collection' |
++----------+----------------------------------------------------------------------+
+| E714 | testing unequal identities should be 'x is not y' |
++----------+----------------------------------------------------------------------+
| E721 | do not compare types, use 'isinstance()' |
+----------+----------------------------------------------------------------------+
+----------+----------------------------------------------------------------------+
@@ -339,9 +348,12 @@ This is the current list of error and warning codes:
| W604 | backticks are deprecated, use 'repr()' |
+----------+----------------------------------------------------------------------+
-**(*)** In the default configuration, the checks **E226**, **E241**
-and **E242** are ignored because they are not rules unanimously accepted,
-and `PEP 8`_ does not enforce them.
+
+**(*)** In the default configuration, the checks **E123**, **E133**, **E226**,
+**E241** and **E242** are ignored because they are not rules unanimously
+accepted, and `PEP 8`_ does not enforce them. The check **E133** is mutually
+exclusive with check **E123**. Use switch ``--hang-closing`` to report **E133**
+instead of **E123**.
**(^)** These checks can be disabled at the line level using the ``# noqa``
special comment. This possibility should be reserved for special cases.
diff --git a/pep8.py b/pep8.py
index 87e76ac..1aad542 100755
--- a/pep8.py
+++ b/pep8.py
@@ -45,7 +45,7 @@ W warnings
700 statements
900 syntax error
"""
-__version__ = '1.4.6a0'
+__version__ = '1.4.7a0'
import os
import sys
@@ -63,13 +63,13 @@ except ImportError:
from ConfigParser import RawConfigParser
DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__'
-DEFAULT_IGNORE = 'E226,E24'
+DEFAULT_IGNORE = 'E123,E226,E24'
if sys.platform == 'win32':
DEFAULT_CONFIG = os.path.expanduser(r'~\.pep8')
else:
DEFAULT_CONFIG = os.path.join(os.getenv('XDG_CONFIG_HOME') or
os.path.expanduser('~/.config'), 'pep8')
-PROJECT_CONFIG = ('.pep8', 'tox.ini', 'setup.cfg')
+PROJECT_CONFIG = ('setup.cfg', 'tox.ini', '.pep8')
TESTSUITE_PATH = os.path.join(os.path.dirname(__file__), 'testsuite')
MAX_LINE_LENGTH = 79
REPORT_FORMAT = {
@@ -93,12 +93,13 @@ BENCHMARK_KEYS = ['directories', 'files', 'logical lines', 'physical lines']
INDENT_REGEX = re.compile(r'([ \t]*)')
RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,')
-RERAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,\s*\w+\s*,\s*\w+')
+RERAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,.*,\s*\w+\s*$')
ERRORCODE_REGEX = re.compile(r'\b[A-Z]\d{3}\b')
DOCSTRING_REGEX = re.compile(r'u?r?["\']')
EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]')
WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)')
COMPARE_SINGLETON_REGEX = re.compile(r'([=!]=)\s*(None|False|True)')
+COMPARE_NEGATIVE_REGEX = re.compile(r'\b(not)\s+[^[({ ]+\s+(in|is)\s')
COMPARE_TYPE_REGEX = re.compile(r'(?:[=!]=|is(?:\s+not)?)\s*type(?:s.\w+Type'
r'|\s*\(\s*([^)]*[^ )])\s*\))')
KEYWORD_REGEX = re.compile(r'(\s*)\b(?:%s)\b(\s*)' % r'|'.join(KEYWORDS))
@@ -381,7 +382,8 @@ def indentation(logical_line, previous_logical, indent_char,
yield 0, "E113 unexpected indentation"
-def continued_indentation(logical_line, tokens, indent_level, noqa, verbose):
+def continued_indentation(logical_line, tokens, indent_level, hang_closing,
+ indent_char, noqa, verbose):
r"""
Continuation lines should align wrapped elements either vertically using
Python's implicit line joining inside parentheses, brackets and braces, or
@@ -420,13 +422,17 @@ def continued_indentation(logical_line, tokens, indent_level, noqa, verbose):
indent_next = logical_line.endswith(':')
row = depth = 0
+ valid_hangs = (4,) if indent_char != '\t' else (4, 8)
# remember how many brackets were opened on each line
parens = [0] * nrows
# relative indents of physical lines
rel_indent = [0] * nrows
+ # for each depth, collect a list of opening rows
+ open_rows = [[0]]
# visual indents
indent_chances = {}
last_indent = tokens[0][2]
+ # for each depth, memorize the visual indent column
indent = [last_indent[1]]
if verbose >= 3:
print(">>> " + tokens[0][4].rstrip())
@@ -448,25 +454,36 @@ def continued_indentation(logical_line, tokens, indent_level, noqa, verbose):
# record the initial indent.
rel_indent[row] = expand_indent(line) - indent_level
- if depth:
- # a bracket expression in a continuation line.
- # find the line that it was opened on
- for open_row in range(row - 1, -1, -1):
- if parens[open_row]:
- break
- else:
- # an unbracketed continuation line (ie, backslash)
- open_row = 0
- hang = rel_indent[row] - rel_indent[open_row]
- visual_indent = indent_chances.get(start[1])
-
- if token_type == tokenize.OP and text in ']})':
- # this line starts with a closing bracket
- if indent[depth]:
- if start[1] != indent[depth]:
- yield (start, "E124 closing bracket does not match "
- "visual indentation")
- elif hang:
+ # identify closing bracket
+ close_bracket = (token_type == tokenize.OP and text in ']})')
+
+ # is the indent relative to an opening bracket line?
+ for open_row in reversed(open_rows[depth]):
+ hang = rel_indent[row] - rel_indent[open_row]
+ hanging_indent = hang in valid_hangs
+ if hanging_indent:
+ break
+ # is there any chance of visual indent?
+ visual_indent = (not close_bracket and hang > 0 and
+ indent_chances.get(start[1]))
+
+ if close_bracket and indent[depth]:
+ # closing bracket for visual indent
+ if start[1] != indent[depth]:
+ yield (start, "E124 closing bracket does not match "
+ "visual indentation")
+ elif close_bracket and not hang:
+ # closing bracket matches indentation of opening bracket's line
+ if hang_closing:
+ yield start, "E133 closing bracket is missing indentation"
+ elif indent[depth] and start[1] < indent[depth]:
+ if visual_indent is not True:
+ # visual indent is broken
+ yield (start, "E128 continuation line "
+ "under-indented for visual indent")
+ elif hanging_indent or (indent_next and rel_indent[row] == 8):
+ # hanging indent is verified
+ if close_bracket and not hang_closing:
yield (start, "E123 closing bracket does not match "
"indentation of opening bracket's line")
elif visual_indent is True:
@@ -476,13 +493,6 @@ def continued_indentation(logical_line, tokens, indent_level, noqa, verbose):
elif visual_indent in (text, str):
# ignore token lined up with matching one from a previous line
pass
- elif indent[depth] and start[1] < indent[depth]:
- # visual indent is broken
- yield (start, "E128 continuation line "
- "under-indented for visual indent")
- elif hang == 4 or (indent_next and rel_indent[row] == 8):
- # hanging indent is verified
- pass
else:
# indent is broken
if hang <= 0:
@@ -509,12 +519,17 @@ def continued_indentation(logical_line, tokens, indent_level, noqa, verbose):
# special case for the "if" statement because len("if (") == 4
elif not indent_chances and not row and not depth and text == 'if':
indent_chances[end[1] + 1] = True
+ elif text == ':' and line[end[1]:].isspace():
+ open_rows[depth].append(row)
# keep track of bracket depth
if token_type == tokenize.OP:
if text in '([{':
depth += 1
indent.append(0)
+ if len(open_rows) == depth:
+ open_rows.append([])
+ open_rows[depth].append(row)
parens[row] += 1
if verbose >= 4:
print("bracket depth %s seen, col %s, visual min = %s" %
@@ -528,6 +543,7 @@ def continued_indentation(logical_line, tokens, indent_level, noqa, verbose):
for ind in list(indent_chances):
if ind >= prev_indent:
del indent_chances[ind]
+ del open_rows[depth + 1:]
depth -= 1
if depth:
indent_chances[indent[depth]] = True
@@ -542,7 +558,7 @@ def continued_indentation(logical_line, tokens, indent_level, noqa, verbose):
last_token_multiline = (start[0] != end[0])
- if indent_next and rel_indent[-1] == 4:
+ if indent_next and expand_indent(line) == indent_level + 4:
if visual_indent:
code = "E129 visually indented line"
else:
@@ -844,19 +860,21 @@ def compound_statements(logical_line):
line = logical_line
last_char = len(line) - 1
found = line.find(':')
- if -1 < found < last_char:
+ while -1 < found < last_char:
before = line[:found]
if (before.count('{') <= before.count('}') and # {'a': 1} (dict)
before.count('[') <= before.count(']') and # [1:2] (slice)
before.count('(') <= before.count(')') and # (Python 3 annotation)
not LAMBDA_REGEX.search(before)): # lambda x: x
yield found, "E701 multiple statements on one line (colon)"
+ found = line.find(':', found + 1)
found = line.find(';')
- if -1 < found:
+ while -1 < found:
if found < last_char:
yield found, "E702 multiple statements on one line (semicolon)"
else:
yield found, "E703 statement ends with a semicolon"
+ found = line.find(';', found + 1)
def explicit_line_join(logical_line, tokens):
@@ -924,6 +942,31 @@ def comparison_to_singleton(logical_line, noqa):
(code, singleton, msg))
+def comparison_negative(logical_line):
+ r"""
+ Negative comparison, either identity or membership, should be
+ done using "not in" and "is not".
+
+ Okay: if x not in y:\n pass
+ Okay: assert (X in Y or X is Z)
+ Okay: if not (X in Y):\n pass
+ Okay: zz = x is not y
+ E713: Z = not X in Y
+ E713: if not X.B in Y:\n pass
+ E714: if not X is Y:\n pass
+ E714: Z = not X.B is Y
+ """
+ match = COMPARE_NEGATIVE_REGEX.search(logical_line)
+ if match:
+ if match.group(2) == 'in':
+ msg = ("E713: Use the 'not in' "
+ "operator for collection membership evaluation")
+ else:
+ msg = ("E714: Use the 'is not' "
+ "operator when testing for unequal identities")
+ yield match.start(1), msg
+
+
def comparison_type(logical_line):
"""
Object type comparisons should always use isinstance() instead of
@@ -947,7 +990,7 @@ def comparison_type(logical_line):
yield match.start(), "E721 do not compare types, use 'isinstance()'"
-def python_3000_has_key(logical_line):
+def python_3000_has_key(logical_line, noqa):
r"""
The {}.has_key() method is removed in the Python 3.
Use the 'in' operation instead.
@@ -956,7 +999,7 @@ def python_3000_has_key(logical_line):
W601: assert d.has_key('alph')
"""
pos = logical_line.find('.has_key(')
- if pos > -1:
+ if pos > -1 and not noqa:
yield pos, "W601 .has_key() is deprecated, use 'in'"
@@ -1018,7 +1061,6 @@ if '' == ''.encode():
return f.readlines()
finally:
f.close()
-
isidentifier = re.compile(r'[a-zA-Z_]\w*').match
stdin_get_value = sys.stdin.read
else:
@@ -1036,7 +1078,6 @@ else:
return f.readlines()
finally:
f.close()
-
isidentifier = str.isidentifier
def stdin_get_value():
@@ -1120,6 +1161,21 @@ def parse_udiff(diff, patterns=None, parent='.'):
if rows and filename_match(path, patterns)])
+def normalize_paths(value, parent=os.curdir):
+ """Parse a comma-separated list of paths.
+
+ Return a list of absolute paths.
+ """
+ if not value or isinstance(value, list):
+ return value
+ paths = []
+ for path in value.split(','):
+ if '/' in path:
+ path = os.path.abspath(os.path.join(parent, path))
+ paths.append(path.rstrip('/'))
+ return paths
+
+
def filename_match(filename, patterns, default=True):
"""
Check if patterns contains a pattern that matches filename.
@@ -1185,6 +1241,7 @@ class Checker(object):
self._logical_checks = options.logical_checks
self._ast_checks = options.ast_checks
self.max_line_length = options.max_line_length
+ self.hang_closing = options.hang_closing
self.verbose = options.verbose
self.filename = filename
if filename is None:
@@ -1197,19 +1254,29 @@ class Checker(object):
try:
self.lines = readlines(filename)
except IOError:
- exc_type, exc = sys.exc_info()[:2]
+ (exc_type, exc) = sys.exc_info()[:2]
self._io_error = '%s: %s' % (exc_type.__name__, exc)
self.lines = []
else:
self.lines = lines
+ if self.lines:
+ ord0 = ord(self.lines[0][0])
+ if ord0 in (0xef, 0xfeff): # Strip the UTF-8 BOM
+ if ord0 == 0xfeff:
+ self.lines[0] = self.lines[0][1:]
+ elif self.lines[0][:3] == '\xef\xbb\xbf':
+ self.lines[0] = self.lines[0][3:]
self.report = report or options.report
self.report_error = self.report.error
def report_invalid_syntax(self):
- exc_type, exc = sys.exc_info()[:2]
- offset = exc.args[1]
- if len(offset) > 2:
- offset = offset[1:3]
+ (exc_type, exc) = sys.exc_info()[:2]
+ if len(exc.args) > 1:
+ offset = exc.args[1]
+ if len(offset) > 2:
+ offset = offset[1:3]
+ else:
+ offset = (1, 0)
self.report_error(offset[0], offset[1] or 0,
'E901 %s: %s' % (exc_type.__name__, exc.args[0]),
self.report_invalid_syntax)
@@ -1253,8 +1320,10 @@ class Checker(object):
for name, check, argument_names in self._physical_checks:
result = self.run_check(check, argument_names)
if result is not None:
- offset, text = result
+ (offset, text) = result
self.report_error(self.line_number, offset, text, check)
+ if text[:4] == 'E101':
+ self.indent_char = line[0]
def build_tokens_line(self):
"""
@@ -1266,7 +1335,7 @@ class Checker(object):
length = 0
previous = None
for token in self.tokens:
- token_type, text = token[0:2]
+ (token_type, text) = token[0:2]
if token_type == tokenize.COMMENT:
comments.append(text)
continue
@@ -1275,8 +1344,8 @@ class Checker(object):
if token_type == tokenize.STRING:
text = mute_string(text)
if previous:
- end_row, end = previous[3]
- start_row, start = token[2]
+ (end_row, end) = previous[3]
+ (start_row, start) = token[2]
if end_row != start_row: # different row
prev_text = self.lines[end_row - 1][end - 1]
if prev_text == ',' or (prev_text not in '{[('
@@ -1311,10 +1380,10 @@ class Checker(object):
for name, check, argument_names in self._logical_checks:
if self.verbose >= 4:
print(' ' + name)
- for result in self.run_check(check, argument_names):
- offset, text = result
+ for result in self.run_check(check, argument_names) or ():
+ (offset, text) = result
if isinstance(offset, tuple):
- orig_number, orig_offset = offset
+ (orig_number, orig_offset) = offset
else:
for token_offset, token in self.mapping:
if offset >= token_offset:
@@ -1326,12 +1395,12 @@ class Checker(object):
def check_ast(self):
try:
tree = compile(''.join(self.lines), '', 'exec', PyCF_ONLY_AST)
- except SyntaxError:
+ except (SyntaxError, TypeError):
return self.report_invalid_syntax()
- for name, cls, _ in self._ast_checks:
+ for name, cls, __ in self._ast_checks:
checker = cls(tree, self.filename)
for lineno, offset, text, check in checker.run():
- if not noqa(self.lines[lineno - 1]):
+ if not self.lines or not noqa(self.lines[lineno - 1]):
self.report_error(lineno, offset, text, check)
def generate_tokens(self):
@@ -1559,11 +1628,12 @@ class StyleGuide(object):
parse_argv = kwargs.pop('parse_argv', False)
config_file = kwargs.pop('config_file', None)
parser = kwargs.pop('parser', None)
+ # build options from dict
+ options_dict = dict(*args, **kwargs)
+ arglist = None if parse_argv else options_dict.get('paths', None)
options, self.paths = process_options(
- parse_argv=parse_argv, config_file=config_file, parser=parser)
- if args or kwargs:
- # build options from dict
- options_dict = dict(*args, **kwargs)
+ arglist, parse_argv, config_file, parser)
+ if options_dict:
options.__dict__.update(options_dict)
if 'paths' in options_dict:
self.paths = options_dict['paths']
@@ -1574,8 +1644,6 @@ class StyleGuide(object):
if not options.reporter:
options.reporter = BaseReport if options.quiet else StandardReport
- for index, value in enumerate(options.exclude):
- options.exclude[index] = value.rstrip('/')
options.select = tuple(options.select or ())
if not (options.select or options.ignore or
options.testsuite or options.doctest) and DEFAULT_IGNORE:
@@ -1583,7 +1651,7 @@ class StyleGuide(object):
options.ignore = tuple(DEFAULT_IGNORE.split(','))
else:
# Ignore all checks which are not explicitly selected
- options.ignore = tuple(options.ignore or options.select and ('',))
+ options.ignore = ('',) if options.select else tuple(options.ignore)
options.benchmark_keys = BENCHMARK_KEYS[:]
options.ignore_code = self.ignore_code
options.physical_checks = self.get_checks('physical_line')
@@ -1636,23 +1704,27 @@ class StyleGuide(object):
print('directory ' + root)
counters['directories'] += 1
for subdir in sorted(dirs):
- if self.excluded(os.path.join(root, subdir)):
+ if self.excluded(subdir, root):
dirs.remove(subdir)
for filename in sorted(files):
# contain a pattern that matches?
if ((filename_match(filename, filepatterns) and
- not self.excluded(filename))):
+ not self.excluded(filename, root))):
runner(os.path.join(root, filename))
- def excluded(self, filename):
+ def excluded(self, filename, parent=None):
"""
Check if options.exclude contains a pattern that matches filename.
"""
+ if not self.options.exclude:
+ return False
basename = os.path.basename(filename)
- return any((filename_match(filename, self.options.exclude,
- default=False),
- filename_match(basename, self.options.exclude,
- default=False)))
+ if filename_match(basename, self.options.exclude):
+ return True
+ if parent:
+ filename = os.path.join(parent, filename)
+ filename = os.path.abspath(filename)
+ return filename_match(filename, self.options.exclude)
def ignore_code(self, code):
"""
@@ -1662,6 +1734,9 @@ class StyleGuide(object):
return False. Else, if 'options.ignore' contains a prefix of
the error code, return True.
"""
+ if len(code) < 4 and any(s.startswith(code)
+ for s in self.options.select):
+ return False
return (code.startswith(self.options.ignore) and
not code.startswith(self.options.select))
@@ -1682,8 +1757,9 @@ def get_parser(prog='pep8', version=__version__):
parser = OptionParser(prog=prog, version=version,
usage="%prog [options] input ...")
parser.config_options = [
- 'exclude', 'filename', 'select', 'ignore', 'max-line-length', 'count',
- 'format', 'quiet', 'show-pep8', 'show-source', 'statistics', 'verbose']
+ 'exclude', 'filename', 'select', 'ignore', 'max-line-length',
+ 'hang-closing', 'count', 'format', 'quiet', 'show-pep8',
+ 'show-source', 'statistics', 'verbose']
parser.add_option('-v', '--verbose', default=0, action='count',
help="print status messages, or debug with -vv")
parser.add_option('-q', '--quiet', default=0, action='count',
@@ -1718,6 +1794,9 @@ def get_parser(prog='pep8', version=__version__):
default=MAX_LINE_LENGTH,
help="set maximum allowed line length "
"(default: %default)")
+ parser.add_option('--hang-closing', action='store_true',
+ help="hang closing bracket instead of matching "
+ "indentation of opening bracket's line")
parser.add_option('--format', metavar='format', default='default',
help="set the error format [default|pylint|<custom>]")
parser.add_option('--diff', action='store_true',
@@ -1744,19 +1823,15 @@ def read_config(options, args, arglist, parser):
print('user configuration: %s' % user_conf)
config.read(user_conf)
+ local_dir = os.curdir
parent = tail = args and os.path.abspath(os.path.commonprefix(args))
while tail:
- for name in PROJECT_CONFIG:
- local_conf = os.path.join(parent, name)
- if os.path.isfile(local_conf):
- break
- else:
- parent, tail = os.path.split(parent)
- continue
- if options.verbose:
- print('local configuration: %s' % local_conf)
- config.read(local_conf)
- break
+ if config.read([os.path.join(parent, fn) for fn in PROJECT_CONFIG]):
+ local_dir = parent
+ if options.verbose:
+ print('local configuration: in %s' % parent)
+ break
+ (parent, tail) = os.path.split(parent)
pep8_section = parser.prog
if config.has_section(pep8_section):
@@ -1764,7 +1839,7 @@ def read_config(options, args, arglist, parser):
for o in parser.option_list])
# First, read the default values
- new_options, _ = parser.parse_args([])
+ (new_options, __) = parser.parse_args([])
# Second, parse the configuration
for opt in config.options(pep8_section):
@@ -1780,13 +1855,15 @@ def read_config(options, args, arglist, parser):
value = config.getint(pep8_section, opt)
elif opt_type == 'string':
value = config.get(pep8_section, opt)
+ if normalized_opt == 'exclude':
+ value = normalize_paths(value, local_dir)
else:
assert opt_type in ('store_true', 'store_false')
value = config.getboolean(pep8_section, opt)
setattr(new_options, normalized_opt, value)
# Third, overwrite with the command-line options
- options, _ = parser.parse_args(arglist, values=new_options)
+ (options, __) = parser.parse_args(arglist, values=new_options)
options.doctest = options.testsuite = False
return options
@@ -1794,9 +1871,6 @@ def read_config(options, args, arglist, parser):
def process_options(arglist=None, parse_argv=False, config_file=None,
parser=None):
"""Process options passed either via arglist or via command line args."""
- if not arglist and not parse_argv:
- # Don't read the command line if the module is used as a library.
- arglist = []
if not parser:
parser = get_parser()
if not parser.has_option('--config'):
@@ -1809,7 +1883,12 @@ def process_options(arglist=None, parse_argv=False, config_file=None,
(parser.prog, ', '.join(parser.config_options))))
group.add_option('--config', metavar='path', default=config_file,
help="user config file location (default: %default)")
- options, args = parser.parse_args(arglist)
+ # Don't read the command line if the module is used as a library.
+ if not arglist and not parse_argv:
+ arglist = []
+ # If parse_argv is True and arglist is None, arguments are
+ # parsed from the command line (sys.argv)
+ (options, args) = parser.parse_args(arglist)
options.reporter = None
if options.ensure_value('testsuite', False):
@@ -1824,13 +1903,10 @@ def process_options(arglist=None, parse_argv=False, config_file=None,
options = read_config(options, args, arglist, parser)
options.reporter = parse_argv and options.quiet == 1 and FileReport
- if options.filename:
- options.filename = options.filename.split(',')
- options.exclude = options.exclude.split(',')
- if options.select:
- options.select = options.select.split(',')
- if options.ignore:
- options.ignore = options.ignore.split(',')
+ options.filename = options.filename and options.filename.split(',')
+ options.exclude = normalize_paths(options.exclude)
+ options.select = options.select and options.select.split(',')
+ options.ignore = options.ignore and options.ignore.split(',')
if options.diff:
options.reporter = DiffReport
diff --git a/testsuite/E10.py b/testsuite/E10.py
index 784b59a..cd142e3 100644
--- a/testsuite/E10.py
+++ b/testsuite/E10.py
@@ -3,3 +3,39 @@ for a in 'abc':
for b in 'xyz':
print a # indented with 8 spaces
print b # indented with 1 tab
+#: E101 E122 W191 W191
+if True:
+ pass
+
+change_2_log = \
+"""Change 2 by slamb@testclient on 2006/04/13 21:46:23
+
+ creation
+"""
+
+p4change = {
+ 2: change_2_log,
+}
+
+
+class TestP4Poller(unittest.TestCase):
+ def setUp(self):
+ self.setUpGetProcessOutput()
+ return self.setUpChangeSource()
+
+ def tearDown(self):
+ pass
+
+#
+#: E101 W191 W191
+if True:
+ foo(1,
+ 2)
+#: E101 E101 W191 W191
+def test_keys(self):
+ """areas.json - All regions are accounted for."""
+ expected = set([
+ u'Norrbotten',
+ u'V\xe4sterbotten',
+ ])
+#:
diff --git a/testsuite/E12.py b/testsuite/E12.py
index 191e1f1..1328091 100644
--- a/testsuite/E12.py
+++ b/testsuite/E12.py
@@ -4,6 +4,11 @@ print "E121", (
#: E122
print "E122", (
"dent")
+#: E123
+my_list = [
+ 1, 2, 3,
+ 4, 5, 6,
+ ]
#: E124
print "E124", ("visual",
"indent_two"
@@ -37,38 +42,136 @@ print "E128", ("under-",
#:
-#: E123 W291
-print "E123", (
- "bad", "hanging", "close"
- )
-#
-#: E123
-result = {
- 'foo': [
- 'bar', {
- 'baz': 'frop',
- }
- ]
- }
+#: E121
+my_list = [
+ 1, 2, 3,
+ 4, 5, 6,
+ ]
#: E121
result = {
'key1': 'value',
'key2': 'value',
}
-#: E121
+#: E121 E121
rv.update(dict.fromkeys((
'qualif_nr', 'reasonComment_en', 'reasonComment_fr',
'reasonComment_de', 'reasonComment_it'),
'?'),
"foo")
-#: E121
+#: E121 E121
abricot = 3 + \
4 + \
5 + 6
-#: E126
-abris = 3 + \
- 4 + \
- 5 + 6
+#: E121
+print "hello", (
+
+ "there",
+ # "john",
+ "dude")
+#: E121
+part = set_mimetype((
+ a.get('mime_type', 'text')),
+ 'default')
+#:
+
+
+#: E122
+if True:
+ result = some_function_that_takes_arguments(
+ 'a', 'b', 'c',
+ 'd', 'e', 'f',
+)
+#: E122
+if some_very_very_very_long_variable_name or var \
+or another_very_long_variable_name:
+ raise Exception()
+#: E122
+if some_very_very_very_long_variable_name or var[0] \
+or another_very_long_variable_name:
+ raise Exception()
+#: E122
+if True:
+ if some_very_very_very_long_variable_name or var \
+ or another_very_long_variable_name:
+ raise Exception()
+#: E122
+if True:
+ if some_very_very_very_long_variable_name or var[0] \
+ or another_very_long_variable_name:
+ raise Exception()
+#: E122
+dictionary = [
+ "is": {
+ "nested": yes(),
+ },
+]
+#: E122
+setup('',
+ scripts=[''],
+ classifiers=[
+ 'Development Status :: 4 - Beta',
+ 'Environment :: Console',
+ 'Intended Audience :: Developers',
+ ])
+#:
+
+
+#: E123 W291
+print "E123", (
+ "bad", "hanging", "close"
+ )
+#
+#: E123 E123 E123
+result = {
+ 'foo': [
+ 'bar', {
+ 'baz': 'frop',
+ }
+ ]
+ }
+#: E123
+result = some_function_that_takes_arguments(
+ 'a', 'b', 'c',
+ 'd', 'e', 'f',
+ )
+#: E124
+my_list = [1, 2, 3,
+ 4, 5, 6,
+]
+#: E124
+my_list = [1, 2, 3,
+ 4, 5, 6,
+ ]
+#: E124
+result = some_function_that_takes_arguments('a', 'b', 'c',
+ 'd', 'e', 'f',
+)
+#: E124
+fooff(aaaa,
+ cca(
+ vvv,
+ dadd
+ ), fff,
+)
+#: E124
+fooff(aaaa,
+ ccaaa(
+ vvv,
+ dadd
+ ),
+ fff,
+)
+#: E124
+d = dict('foo',
+ help="exclude files or directories which match these "
+ "comma separated patterns (default: %s)" % DEFAULT_EXCLUDE
+ )
+#: E124 E128 E128
+if line_removed:
+ self.event(cr, uid,
+ name="Removing the option for contract",
+ description="contract line has been removed",
+ )
#:
@@ -101,9 +204,18 @@ if (a == 2 or
#: E126
+my_list = [
+ 1, 2, 3,
+ 4, 5, 6,
+ ]
+#: E126 E126
+abris = 3 + \
+ 4 + \
+ 5 + 6
+#: E126
fixed = re.sub(r'\t+', ' ', target[c::-1], 1)[::-1] + \
target[c + 1:]
-#: E126
+#: E126 E126
rv.update(dict.fromkeys((
'qualif_nr', 'reasonComment_en', 'reasonComment_fr',
'reasonComment_de', 'reasonComment_it'),
@@ -130,6 +242,12 @@ if (
) or
y == 4):
pass
+#: E126
+troublesome_hash = {
+ "hash": "value",
+ "long": "the quick brown fox jumps over the lazy dog before doing a "
+ "somersault",
+}
#:
@@ -150,43 +268,6 @@ def qualify_by_address(self, cr, uid, ids, context=None,
#:
-#: E121
-print "hello", (
-
- "there",
- # "john",
- "dude")
-#: E121
-part = set_mimetype((
- a.get('mime_type', 'text')),
- 'default')
-#: E124
-fooff(aaaa,
- cca(
- vvv,
- dadd
- ), fff,
-)
-#: E124
-fooff(aaaa,
- ccaaa(
- vvv,
- dadd
- ),
- fff,
-)
-#: E126
-troublesome_hash = {
- "hash": "value",
- "long": "the quick brown fox jumps over the lazy dog before doing a "
- "somersault",
-}
-#: E126
-# probably not easily fixed, without using 'ast'
-troublesome_hash_ii = {
- "long key that tends to happen more when you're indented":
- "stringwithalongtoken you don't want to break",
-}
#: E128
foo(1, 2, 3,
4, 5, 6)
@@ -226,24 +307,13 @@ foo(1, 2, 3,
#: E127
foo(1, 2, 3,
4, 5, 6)
-#: E124
-d = dict('foo',
- help="exclude files or directories which match these "
- "comma separated patterns (default: %s)" % DEFAULT_EXCLUDE
- )
-#: E124 E128
-if line_removed:
- self.event(cr, uid,
- name="Removing the option for contract",
- description="contract line has been removed",
- )
-#: E128
+#: E128 E128
if line_removed:
self.event(cr, uid,
name="Removing the option for contract",
description="contract line has been removed",
)
-#: E124 E127
+#: E124 E127 E127
if line_removed:
self.event(cr, uid,
name="Removing the option for contract",
@@ -264,22 +334,17 @@ input1 = {'a': {'calc': 1 + 2}, 'b': 1
rv.update(d=('a' + 'b', 'c'),
e=42, f=(42
+ 42))
-#: E122
-if some_very_very_very_long_variable_name or var \
-or another_very_long_variable_name:
- raise Exception()
-#: E122
-if some_very_very_very_long_variable_name or var[0] \
-or another_very_long_variable_name:
- raise Exception()
-#: E122
-if True:
- if some_very_very_very_long_variable_name or var \
- or another_very_long_variable_name:
- raise Exception()
-#: E122
+#: E123
if True:
- if some_very_very_very_long_variable_name or var[0] \
- or another_very_long_variable_name:
- raise Exception()
+ def example_issue254():
+ return [node.copy(
+ (
+ replacement
+ # First, look at all the node's current children.
+ for child in node.children
+ # Replace them.
+ for replacement in replace(child)
+ ),
+ dict(name=token.undefined)
+ )]
#:
diff --git a/testsuite/E12not.py b/testsuite/E12not.py
index 9509379..733b424 100644
--- a/testsuite/E12not.py
+++ b/testsuite/E12not.py
@@ -128,7 +128,7 @@ part = [-1, (2, 3,
fnct(1, 2, 3,
4, 5, 6)
-fnct(1, 2, 3
+fnct(1, 2, 3,
4, 5, 6,
7, 8, 9,
10, 11)
@@ -546,3 +546,89 @@ if a > b and \
c > d:
moo_like_a_cow()
#
+my_list = [
+ 1, 2, 3,
+ 4, 5, 6,
+]
+
+my_list = [1, 2, 3,
+ 4, 5, 6,
+ ]
+
+result = some_function_that_takes_arguments(
+ 'a', 'b', 'c',
+ 'd', 'e', 'f',
+)
+
+result = some_function_that_takes_arguments('a', 'b', 'c',
+ 'd', 'e', 'f',
+ )
+
+# issue 203
+dica = {
+ ('abc'
+ 'def'): (
+ 'abc'),
+}
+
+(abcdef[0]
+ [1]) = (
+ 'abc')
+
+('abc'
+ 'def') == (
+ 'abc')
+
+# issue 214
+bar(
+ 1).zap(
+ 2)
+
+bar(
+ 1).zap(
+ 2)
+#
+if True:
+
+ def example_issue254():
+ return [node.copy(
+ (
+ replacement
+ # First, look at all the node's current children.
+ for child in node.children
+ # Replace them.
+ for replacement in replace(child)
+ ),
+ dict(name=token.undefined)
+ )]
+
+
+def valid_example():
+ return [node.copy(properties=dict(
+ (key, val if val is not None else token.undefined)
+ for key, val in node.items()
+ ))]
+
+
+def other_example():
+ return [node.copy(properties=dict(
+ (key, val if val is not None else token.undefined)
+ for key, val in node.items()
+ ))]
+
+foo([
+ 'bug'
+])
+
+# issue 144, finally!
+some_hash = {
+ "long key that tends to happen more when you're indented":
+ "stringwithalongtoken you don't want to break",
+}
+
+{
+ 1:
+ 999999 if True
+ else 0,
+}
+#
diff --git a/testsuite/E21.py b/testsuite/E21.py
index e830895..96b55b8 100644
--- a/testsuite/E21.py
+++ b/testsuite/E21.py
@@ -1,6 +1,6 @@
#: E211
spam (1)
-#: E211
+#: E211 E211
dict ['key'] = list [index]
#: E211
dict['key'] ['subkey'] = list[index]
diff --git a/testsuite/E22.py b/testsuite/E22.py
index ba93d41..f7eeaa0 100644
--- a/testsuite/E22.py
+++ b/testsuite/E22.py
@@ -1,15 +1,15 @@
#: E221
a = 12 + 3
b = 4 + 5
-#: E221
+#: E221 E221
x = 1
y = 2
long_variable = 3
-#: E221
+#: E221 E221
x[0] = 1
x[1] = 2
long_variable = 3
-#: E221
+#: E221 E221
x = f(x) + 1
y = long_variable + 2
z = x[0] + 3
@@ -23,11 +23,11 @@ long_variable = 3
#: E222
a = a + 1
b = b + 10
-#: E222
+#: E222 E222
x = -1
y = -2
long_variable = 3
-#: E222
+#: E222 E222
x[0] = 1
x[1] = 2
long_variable = 3
@@ -68,9 +68,9 @@ z = (x + 1)** y
_1kB = _1MB >>10
#: E225
_1kB = _1MB>> 10
-#: E225
+#: E225 E225
i=i+ 1
-#: E225
+#: E225 E225
i=i +1
#: E225 E226
i=i+1
@@ -86,7 +86,7 @@ c = (a+ b)*(a - b)
#: E226
z = 2**30
-#: E226
+#: E226 E226
c = (a+b) * (a-b)
#: E226
norman = True+False
@@ -94,7 +94,7 @@ norman = True+False
x = x*2 - 1
#: E226
x = x/2 - 1
-#: E226
+#: E226 E226
hypot2 = x*x + y*y
#: E226
c = (a + b)*(a - b)
diff --git a/testsuite/E24.py b/testsuite/E24.py
index 9ca8e2b..36fb4aa 100644
--- a/testsuite/E24.py
+++ b/testsuite/E24.py
@@ -6,7 +6,7 @@ b = (1, 20)
a = (1, 2) # tab before 2
#: Okay
b = (1, 20) # space before 20
-#: E241
+#: E241 E241 E241
# issue 135
more_spaces = [a, b,
ef, +h,
diff --git a/testsuite/E25.py b/testsuite/E25.py
index 94154bf..b8031ec 100644
--- a/testsuite/E25.py
+++ b/testsuite/E25.py
@@ -1,4 +1,4 @@
-#: E251
+#: E251 E251
def foo(bar = False):
'''Test function with an error in declaration'''
pass
@@ -6,7 +6,7 @@ def foo(bar = False):
foo(bar= True)
#: E251
foo(bar =True)
-#: E251
+#: E251 E251
foo(bar = True)
#: E251
y = bar(root= "sdasd")
diff --git a/testsuite/E70.py b/testsuite/E70.py
index 8941ab1..64feefd 100644
--- a/testsuite/E70.py
+++ b/testsuite/E70.py
@@ -1,9 +1,13 @@
#: E701
if a: a = False
+#: E701
+if not header or header[:6] != 'bytes=': return
#: E702
a = False; b = True
#: E702
import bdist_egg; bdist_egg.write_safety_flag(cmd.egg_info, safe)
#: E703
import shlex;
+#: E702 E703
+del a[:]; a.append(42);
#:
diff --git a/testsuite/E71.py b/testsuite/E71.py
index 645d2d7..2ff5dea 100644
--- a/testsuite/E71.py
+++ b/testsuite/E71.py
@@ -1,9 +1,41 @@
+#: E711
+if res == None:
+ pass
#: E712
if res == True:
pass
#: E712
if res != False:
pass
-#: E711
-if res == None:
+
+#
+#: E713
+if not X in Y:
+ pass
+#: E713
+if not X.B in Y:
+ pass
+#: E713
+if not X in Y and Z == "zero":
+ pass
+#: E713
+if X == "zero" or not Y in Z:
+ pass
+
+#
+#: E714
+if not X is Y:
+ pass
+#: E714
+if not X.B is Y:
+ pass
+#: Okay
+if x not in y:
+ pass
+if not (X in Y or X is Z):
+ pass
+if not (X in Y):
+ pass
+if x is not y:
pass
+#:
diff --git a/testsuite/E72.py b/testsuite/E72.py
index 514995a..8eb34cb 100644
--- a/testsuite/E72.py
+++ b/testsuite/E72.py
@@ -32,7 +32,7 @@ assert type(res) is type((1, ))
assert type(res) is not type((1, ))
#: E211 E721
assert type(res) == type ([2, ])
-#: E201 E202 E721
+#: E201 E201 E202 E721
assert type(res) == type( ( ) )
#: E201 E202 E721
assert type(res) == type( (0, ) )
diff --git a/testsuite/E90.py b/testsuite/E90.py
index 1371e31..2d0b2dc 100644
--- a/testsuite/E90.py
+++ b/testsuite/E90.py
@@ -2,13 +2,13 @@
}
#: E901
= [x
-#: E901 E101 W191
+#: E901 E101 W191 W191
while True:
try:
pass
except:
print 'Whoops'
-#: E122 E225 E251 E701
+#: E122 E225 E251 E251 E701
# Do not crash if code is invalid
if msg:
diff --git a/testsuite/W19.py b/testsuite/W19.py
index c81f46f..3e303d9 100644
--- a/testsuite/W19.py
+++ b/testsuite/W19.py
@@ -4,7 +4,7 @@ if False:
#:
-#: E126 W191
+#: W191
y = x == 2 \
or x == 3
#: E101 W191
@@ -38,7 +38,7 @@ if (
pass
#:
-#: E101 W191
+#: E101 E101 W191 W191
if start[1] > end_col and not (
over_indent == 4 and indent_next):
return(0, "E121 continuation line over-"
@@ -56,7 +56,7 @@ def long_function_name(
if ((row < 0 or self.moduleCount <= row or
col < 0 or self.moduleCount <= col)):
raise Exception("%s,%s - %s" % (row, col, self.moduleCount))
-#: E101 W191
+#: E101 E101 E101 E101 W191 W191 W191 W191 W191 W191
if bar:
return(
start, 'E121 lines starting with a '
@@ -86,14 +86,14 @@ if (a == 2 or
b == """abc def ghi
jkl mno"""):
return True
-#: E101 W191
+#: E101 W191 W191
if length > options.max_line_length:
return options.max_line_length, \
"E501 line too long (%d characters)" % length
#
-#: E101 W191
+#: E101 W191 W191
if os.path.exists(os.path.join(path, PEP8_BIN)):
cmd = ([os.path.join(path, PEP8_BIN)] +
self._pep8_options(targetfile))
@@ -101,4 +101,23 @@ if os.path.exists(os.path.join(path, PEP8_BIN)):
if foo is None and bar is "frop" and \
blah == 'yeah':
blah = 'yeahnah'
+
+
+#
+#: W191 W191 W191
+if True:
+ foo(
+ 1,
+ 2)
+#: W191 W191 W191 W191 W191
+def test_keys(self):
+ """areas.json - All regions are accounted for."""
+ expected = set([
+ u'Norrbotten',
+ u'V\xe4sterbotten',
+ ])
+#: W191
+x = [
+ 'abc'
+]
#:
diff --git a/testsuite/W60.py b/testsuite/W60.py
index 6d819ee..973d22f 100644
--- a/testsuite/W60.py
+++ b/testsuite/W60.py
@@ -7,6 +7,7 @@ raise DummyError, "Message"
raise ValueError, "hello %s %s" % (1, 2)
#: Okay
raise type_, val, tb
+raise Exception, Exception("f"), t
#: W603
if x <> 0:
x = 0
diff --git a/testsuite/support.py b/testsuite/support.py
index 4bd3067..d4acba6 100644
--- a/testsuite/support.py
+++ b/testsuite/support.py
@@ -28,21 +28,28 @@ class TestReport(StandardReport):
def get_file_results(self):
# Check if the expected errors were found
label = '%s:%s:1' % (self.filename, self.line_offset)
- codes = sorted(self.expected)
- for code in codes:
+ for code in self.expected:
if not self.counters.get(code):
self.file_errors += 1
self.total_errors += 1
print('%s: error %s not found' % (label, code))
+ else:
+ self.counters[code] -= 1
+ for code, extra in sorted(self.counters.items()):
+ if code not in self._benchmark_keys:
+ if extra and code in self.expected:
+ self.file_errors += 1
+ self.total_errors += 1
+ print('%s: error %s found too many times (+%d)' %
+ (label, code, extra))
+ # Reset counters
+ del self.counters[code]
if self._verbose and not self.file_errors:
print('%s: passed (%s)' %
- (label, ' '.join(codes) or 'Okay'))
+ (label, ' '.join(self.expected) or 'Okay'))
self.counters['test cases'] += 1
if self.file_errors:
self.counters['failed tests'] += 1
- # Reset counters
- for key in set(self.counters) - set(self._benchmark_keys):
- del self.counters[key]
self.messages = {}
return super(TestReport, self).get_file_results()
diff --git a/testsuite/test_api.py b/testsuite/test_api.py
index a31ff42..313fd93 100644
--- a/testsuite/test_api.py
+++ b/testsuite/test_api.py
@@ -10,6 +10,15 @@ from testsuite.support import ROOT_DIR, PseudoFile
E11 = os.path.join(ROOT_DIR, 'testsuite', 'E11.py')
+class DummyChecker(object):
+ def __init__(self, tree, filename):
+ pass
+
+ def run(self):
+ if False:
+ yield
+
+
class APITestCase(unittest.TestCase):
"""Test the public methods."""
@@ -68,13 +77,6 @@ class APITestCase(unittest.TestCase):
for name, func, args in options.logical_checks))
def test_register_ast_check(self):
- class DummyChecker(object):
- def __init__(self, tree, filename):
- pass
-
- def run(self):
- if False:
- yield
pep8.register_check(DummyChecker, ['Z701'])
self.assertTrue(DummyChecker in pep8._checks['tree'])
@@ -87,18 +89,14 @@ class APITestCase(unittest.TestCase):
for name, cls, args in options.ast_checks))
def test_register_invalid_check(self):
- class DummyChecker(object):
+ class InvalidChecker(DummyChecker):
def __init__(self, filename):
pass
- def run(self):
- if False:
- yield
-
def check_dummy(logical, tokens):
if False:
yield
- pep8.register_check(DummyChecker, ['Z741'])
+ pep8.register_check(InvalidChecker, ['Z741'])
pep8.register_check(check_dummy, ['Z441'])
for checkers in pep8._checks.values():
@@ -180,7 +178,7 @@ class APITestCase(unittest.TestCase):
options = parse_argv('').options
self.assertEqual(options.select, ())
- self.assertEqual(options.ignore, ('E226', 'E24'))
+ self.assertEqual(options.ignore, ('E123', 'E226', 'E24'))
options = parse_argv('--doctest').options
self.assertEqual(options.select, ())
@@ -194,6 +192,18 @@ class APITestCase(unittest.TestCase):
self.assertEqual(options.select, ('E', 'W'))
self.assertEqual(options.ignore, ('',))
+ options = parse_argv('--select E --ignore E24').options
+ self.assertEqual(options.select, ('E',))
+ self.assertEqual(options.ignore, ('',))
+
+ options = parse_argv('--ignore E --select E24').options
+ self.assertEqual(options.select, ('E24',))
+ self.assertEqual(options.ignore, ('',))
+
+ options = parse_argv('--ignore W --select E24').options
+ self.assertEqual(options.select, ('E24',))
+ self.assertEqual(options.ignore, ('',))
+
pep8style = pep8.StyleGuide(paths=[E11])
self.assertFalse(pep8style.ignore_code('E112'))
self.assertFalse(pep8style.ignore_code('W191'))
@@ -209,6 +219,13 @@ class APITestCase(unittest.TestCase):
self.assertFalse(pep8style.ignore_code('W191'))
self.assertTrue(pep8style.ignore_code('E241'))
+ pep8style = pep8.StyleGuide(select=('F401',), paths=[E11])
+ self.assertEqual(pep8style.options.select, ('F401',))
+ self.assertEqual(pep8style.options.ignore, ('',))
+ self.assertFalse(pep8style.ignore_code('F'))
+ self.assertFalse(pep8style.ignore_code('F401'))
+ self.assertTrue(pep8style.ignore_code('F402'))
+
def test_styleguide_excluded(self):
pep8style = pep8.StyleGuide(paths=[E11])
@@ -292,5 +309,30 @@ class APITestCase(unittest.TestCase):
self.assertRaises(TypeError, pep8style.check_files, 42)
# < 3.3 raises TypeError; >= 3.3 raises AttributeError
self.assertRaises(Exception, pep8style.check_files, [42])
+
+ def test_check_unicode(self):
+ # Do not crash if lines are Unicode (Python 2.x)
+ pep8.register_check(DummyChecker, ['Z701'])
+ source = '#\n'
+ if hasattr(source, 'decode'):
+ source = source.decode('ascii')
+
+ pep8style = pep8.StyleGuide()
+ count_errors = pep8style.input_file('stdin', lines=[source])
+
+ self.assertFalse(sys.stdout)
+ self.assertFalse(sys.stderr)
+ self.assertEqual(count_errors, 0)
+
+ def test_check_nullbytes(self):
+ pep8.register_check(DummyChecker, ['Z701'])
+
+ pep8style = pep8.StyleGuide()
+ count_errors = pep8style.input_file('stdin', lines=['\x00\n'])
+
+ self.assertTrue(sys.stdout[0].startswith("stdin:1:1: E901 TypeError"))
+ self.assertFalse(sys.stderr)
+ self.assertEqual(count_errors, 1)
+
# TODO: runner
# TODO: input_file
diff --git a/testsuite/test_shell.py b/testsuite/test_shell.py
index 4317bca..1f12b44 100644
--- a/testsuite/test_shell.py
+++ b/testsuite/test_shell.py
@@ -15,14 +15,17 @@ class ShellTestCase(unittest.TestCase):
self._saved_stdout = sys.stdout
self._saved_stderr = sys.stderr
self._saved_pconfig = pep8.PROJECT_CONFIG
- self._saved_cpread = pep8.RawConfigParser.read
+ self._saved_cpread = pep8.RawConfigParser._read
self._saved_stdin_get_value = pep8.stdin_get_value
self._config_filenames = []
self.stdin = ''
sys.argv = ['pep8']
sys.stdout = PseudoFile()
sys.stderr = PseudoFile()
- pep8.RawConfigParser.read = self._config_filenames.append
+
+ def fake_config_parser_read(cp, fp, filename):
+ self._config_filenames.append(filename)
+ pep8.RawConfigParser._read = fake_config_parser_read
pep8.stdin_get_value = self.stdin_get_value
def tearDown(self):
@@ -30,7 +33,7 @@ class ShellTestCase(unittest.TestCase):
sys.stdout = self._saved_stdout
sys.stderr = self._saved_stderr
pep8.PROJECT_CONFIG = self._saved_pconfig
- pep8.RawConfigParser.read = self._saved_cpread
+ pep8.RawConfigParser._read = self._saved_cpread
pep8.stdin_get_value = self._saved_stdin_get_value
def stdin_get_value(self):
diff --git a/testsuite/utf-8-bom.py b/testsuite/utf-8-bom.py
new file mode 100644
index 0000000..9c065c9
--- /dev/null
+++ b/testsuite/utf-8-bom.py
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+hello = 'こんにちわ'
+
+# EOF
diff --git a/tox.ini b/tox.ini
index 1c3ae26..214441f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,7 +4,7 @@
# and then run "tox" from this directory.
[tox]
-envlist = py26, py27, py32, py33, pypy, jython
+envlist = py26, py27, py32, py33, py34, pypy, jython
[testenv]
commands =