diff options
author | Florent Xicluna <florent.xicluna@gmail.com> | 2014-03-25 01:13:47 +0100 |
---|---|---|
committer | Florent Xicluna <florent.xicluna@gmail.com> | 2014-03-25 01:13:47 +0100 |
commit | 3d8365a7c8ecd165cef630cc33244e6f3bc9085f (patch) | |
tree | 2e80e44db836ba38b5f10883ed92a0e5cac437d5 | |
parent | 69259deba15894a169d288da6c3dd9780b2451f9 (diff) | |
parent | 63a0a02d9dc521b6355210c60997d9e787814232 (diff) | |
download | pep8-3d8365a7c8ecd165cef630cc33244e6f3bc9085f.tar.gz |
Merge branch 'master' into issue126
-rw-r--r-- | .travis.yml | 1 | ||||
-rw-r--r-- | CHANGES.txt | 66 | ||||
-rw-r--r-- | docs/api.rst | 2 | ||||
-rw-r--r-- | docs/intro.rst | 32 | ||||
-rwxr-xr-x | pep8.py | 262 | ||||
-rw-r--r-- | testsuite/E10.py | 36 | ||||
-rw-r--r-- | testsuite/E12.py | 239 | ||||
-rw-r--r-- | testsuite/E12not.py | 88 | ||||
-rw-r--r-- | testsuite/E21.py | 2 | ||||
-rw-r--r-- | testsuite/E22.py | 18 | ||||
-rw-r--r-- | testsuite/E24.py | 2 | ||||
-rw-r--r-- | testsuite/E25.py | 4 | ||||
-rw-r--r-- | testsuite/E70.py | 4 | ||||
-rw-r--r-- | testsuite/E71.py | 36 | ||||
-rw-r--r-- | testsuite/E72.py | 2 | ||||
-rw-r--r-- | testsuite/E90.py | 4 | ||||
-rw-r--r-- | testsuite/W19.py | 29 | ||||
-rw-r--r-- | testsuite/W60.py | 1 | ||||
-rw-r--r-- | testsuite/support.py | 19 | ||||
-rw-r--r-- | testsuite/test_api.py | 70 | ||||
-rw-r--r-- | testsuite/test_shell.py | 9 | ||||
-rw-r--r-- | testsuite/utf-8-bom.py | 6 | ||||
-rw-r--r-- | tox.ini | 2 |
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. @@ -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 @@ -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 = |