diff --git a/pycodestyle.py b/pycodestyle.py index dc86d30d..9a418749 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -172,31 +172,32 @@ def _get_parameters(function): return inspect.getargspec(function)[0] -def register_check(check, codes=None): +def register_check(kind): """Register a new check object.""" - def _add_check(check, kind, codes, args): - if check in _checks[kind]: - _checks[kind][check][0].extend(codes or []) - else: - _checks[kind][check] = (codes or [''], args) - if inspect.isfunction(check): - args = _get_parameters(check) - if args and args[0] in ('physical_line', 'logical_line'): - if codes is None: - codes = ERRORCODE_REGEX.findall(check.__doc__ or '') - _add_check(check, args[0], codes, args) - elif inspect.isclass(check): - if _get_parameters(check.__init__)[:2] == ['self', 'tree']: - _add_check(check, 'tree', codes, None) - return check + def _register_check(check, codes=None): + def _add_check(check, kind, codes): + if check in _checks[kind]: + _checks[kind][check].extend(codes or []) + else: + _checks[kind][check] = codes or [''] + if inspect.isfunction(check): + if kind in ('physical_line', 'logical_line'): + if codes is None: + codes = ERRORCODE_REGEX.findall(check.__doc__ or '') + _add_check(check, kind, codes) + elif inspect.isclass(check): + if _get_parameters(check.__init__)[:2] == ['self', 'tree']: + _add_check(check, 'tree', codes) + return check + return _register_check ############################################################################## # Plugins (check functions) for physical lines ############################################################################## -@register_check -def tabs_or_spaces(physical_line, indent_char): +@register_check("physical_line") +def tabs_or_spaces(self): r"""Never mix tabs and spaces. The most popular way of indenting Python is with spaces only. The @@ -209,26 +210,26 @@ def tabs_or_spaces(physical_line, indent_char): Okay: if a == 0:\n a = 1\n b = 1 E101: if a == 0:\n a = 1\n\tb = 1 """ - indent = INDENT_REGEX.match(physical_line).group(1) + indent = INDENT_REGEX.match(self.physical_line).group(1) for offset, char in enumerate(indent): - if char != indent_char: + if char != self.indent_char: return offset, "E101 indentation contains mixed spaces and tabs" -@register_check -def tabs_obsolete(physical_line): +@register_check("physical_line") +def tabs_obsolete(self): r"""For new projects, spaces-only are strongly recommended over tabs. Okay: if True:\n return W191: if True:\n\treturn """ - indent = INDENT_REGEX.match(physical_line).group(1) + indent = INDENT_REGEX.match(self.physical_line).group(1) if '\t' in indent: return indent.index('\t'), "W191 indentation contains tabs" -@register_check -def trailing_whitespace(physical_line): +@register_check("physical_line") +def trailing_whitespace(self): r"""Trailing whitespace is superfluous. The warning returned varies on whether the line itself is blank, for easier @@ -238,19 +239,22 @@ def trailing_whitespace(physical_line): W291: spam(1) \n# W293: class Foo(object):\n \n bang = 12 """ - physical_line = physical_line.rstrip('\n') # chr(10), newline - physical_line = physical_line.rstrip('\r') # chr(13), carriage return - physical_line = physical_line.rstrip('\x0c') # chr(12), form feed, ^L - stripped = physical_line.rstrip(' \t\v') - if physical_line != stripped: + # chr(10), newline + self.physical_line = self.physical_line.rstrip('\n') + # chr(13), carriage return + self.physical_line = self.physical_line.rstrip('\r') + # chr(12), form feed, ^L + self.physical_line = self.physical_line.rstrip('\x0c') + stripped = self.physical_line.rstrip(' \t\v') + if self.physical_line != stripped: if stripped: return len(stripped), "W291 trailing whitespace" else: return 0, "W293 blank line contains whitespace" -@register_check -def trailing_blank_lines(physical_line, lines, line_number, total_lines): +@register_check("physical_line") +def trailing_blank_lines(self): r"""Trailing blank lines are superfluous. Okay: spam(1) @@ -258,17 +262,16 @@ def trailing_blank_lines(physical_line, lines, line_number, total_lines): However the last line should end with a new line (warning W292). """ - if line_number == total_lines: - stripped_last_line = physical_line.rstrip() + if self.line_number == self.total_lines: + stripped_last_line = self.physical_line.rstrip() if not stripped_last_line: return 0, "W391 blank line at end of file" - if stripped_last_line == physical_line: - return len(physical_line), "W292 no newline at end of file" + if stripped_last_line == self.physical_line: + return len(self.physical_line), "W292 no newline at end of file" -@register_check -def maximum_line_length(physical_line, max_line_length, multiline, - line_number, noqa): +@register_check("physical_line") +def maximum_line_length(self): r"""Limit all lines to a maximum of 79 characters. There are still many devices around that are limited to 80 character @@ -280,18 +283,18 @@ def maximum_line_length(physical_line, max_line_length, multiline, Reports error E501. """ - line = physical_line.rstrip() + line = self.physical_line.rstrip() length = len(line) - if length > max_line_length and not noqa: + if length > self.max_line_length and not self.noqa: # Special case: ignore long shebang lines. - if line_number == 1 and line.startswith('#!'): + if self.line_number == 1 and line.startswith('#!'): return # Special case for long URLs in multi-line docstrings or comments, # but still report the error when the 72 first chars are whitespaces. chunks = line.split() - if ((len(chunks) == 1 and multiline) or + if ((len(chunks) == 1 and self.multiline) or (len(chunks) == 2 and chunks[0] == '#')) and \ - len(line) - len(chunks[-1]) < max_line_length - 7: + len(line) - len(chunks[-1]) < self.max_line_length - 7: return if hasattr(line, 'decode'): # Python 2 # The line could contain multi-byte characters @@ -299,9 +302,9 @@ def maximum_line_length(physical_line, max_line_length, multiline, length = len(line.decode('utf-8')) except UnicodeError: pass - if length > max_line_length: - return (max_line_length, "E501 line too long " - "(%d > %d characters)" % (length, max_line_length)) + if length > self.max_line_length: + return (self.max_line_length, "E501 line too long " + "(%d > %d characters)" % (length, self.max_line_length)) ############################################################################## @@ -309,11 +312,8 @@ def maximum_line_length(physical_line, max_line_length, multiline, ############################################################################## -@register_check -def blank_lines(logical_line, blank_lines, indent_level, line_number, - blank_before, previous_logical, - previous_unindented_logical_line, previous_indent_level, - lines): +@register_check("logical_line") +def blank_lines(self): r"""Separate top-level function and class definitions with two blank lines. Method definitions inside a class are separated by a single blank line. @@ -342,25 +342,25 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, top_level_lines = BLANK_LINES_CONFIG['top_level'] method_lines = BLANK_LINES_CONFIG['method'] - if line_number < top_level_lines + 1 and not previous_logical: + if self.line_number < top_level_lines + 1 and not self.previous_logical: return # Don't expect blank lines before the first line - if previous_logical.startswith('@'): - if blank_lines: + if self.previous_logical.startswith('@'): + if self.blank_lines: yield 0, "E304 blank lines found after function decorator" - elif (blank_lines > top_level_lines or - (indent_level and blank_lines == method_lines + 1) + elif (self.blank_lines > top_level_lines or + (self.indent_level and self.blank_lines == method_lines + 1) ): - yield 0, "E303 too many blank lines (%d)" % blank_lines - elif STARTSWITH_TOP_LEVEL_REGEX.match(logical_line): - if indent_level: - if not (blank_before == method_lines or - previous_indent_level < indent_level or - DOCSTRING_REGEX.match(previous_logical) + yield 0, "E303 too many blank lines (%d)" % self.blank_lines + elif STARTSWITH_TOP_LEVEL_REGEX.match(self.logical_line): + if self.indent_level: + if not (self.blank_before == method_lines or + self.previous_indent_level < self.indent_level or + DOCSTRING_REGEX.match(self.previous_logical) ): - ancestor_level = indent_level + ancestor_level = self.indent_level nested = False # Search backwards for a def ancestor or tree root (top level). - for line in lines[line_number - top_level_lines::-1]: + for line in self.lines[self.line_number - top_level_lines::-1]: if line.strip() and expand_indent(line) < ancestor_level: ancestor_level = expand_indent(line) nested = line.lstrip().startswith('def ') @@ -372,21 +372,22 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, else: yield 0, "E301 expected %s blank line, found 0" % ( method_lines,) - elif blank_before != top_level_lines: + elif self.blank_before != top_level_lines: yield 0, "E302 expected %s blank lines, found %d" % ( - top_level_lines, blank_before) - elif (logical_line and - not indent_level and - blank_before != top_level_lines and - previous_unindented_logical_line.startswith(('def ', 'class ')) + top_level_lines, self.blank_before) + elif (self.logical_line and + not self.indent_level and + self.blank_before != top_level_lines and + self.previous_unindented_logical_line.startswith( + ('def ', 'class ')) ): yield 0, "E305 expected %s blank lines after " \ "class or function definition, found %d" % ( - top_level_lines, blank_before) + top_level_lines, self.blank_before) -@register_check -def extraneous_whitespace(logical_line): +@register_check("logical_line") +def extraneous_whitespace(self): r"""Avoid extraneous whitespace. Avoid extraneous whitespace in these situations: @@ -405,7 +406,7 @@ def extraneous_whitespace(logical_line): E203: if x == 4: print x, y ; x, y = y, x E203: if x == 4 : print x, y; x, y = y, x """ - line = logical_line + line = self.logical_line for match in EXTRANEOUS_WHITESPACE_REGEX.finditer(line): text = match.group() char = text.strip() @@ -418,8 +419,8 @@ def extraneous_whitespace(logical_line): yield found, "%s whitespace before '%s'" % (code, char) -@register_check -def whitespace_around_keywords(logical_line): +@register_check("logical_line") +def whitespace_around_keywords(self): r"""Avoid extraneous whitespace around keywords. Okay: True and False @@ -428,7 +429,7 @@ def whitespace_around_keywords(logical_line): E273: True and\tFalse E274: True\tand False """ - for match in KEYWORD_REGEX.finditer(logical_line): + for match in KEYWORD_REGEX.finditer(self.logical_line): before, after = match.groups() if '\t' in before: @@ -442,8 +443,8 @@ def whitespace_around_keywords(logical_line): yield match.start(2), "E271 multiple spaces after keyword" -@register_check -def missing_whitespace_after_import_keyword(logical_line): +@register_check("logical_line") +def missing_whitespace_after_import_keyword(self): r"""Multiple imports in form from x import (a, b, c) should have space between import statement and parenthesised name list. @@ -451,7 +452,7 @@ def missing_whitespace_after_import_keyword(logical_line): E275: from foo import(bar, baz) E275: from importable.module import(bar, baz) """ - line = logical_line + line = self.logical_line indicator = ' import(' if line.startswith('from '): found = line.find(indicator) @@ -460,8 +461,8 @@ def missing_whitespace_after_import_keyword(logical_line): yield pos, "E275 missing whitespace after keyword" -@register_check -def missing_whitespace(logical_line): +@register_check("logical_line") +def missing_whitespace(self): r"""Each comma, semicolon or colon should be followed by whitespace. Okay: [a, b] @@ -474,7 +475,7 @@ def missing_whitespace(logical_line): E231: foo(bar,baz) E231: [{'a':'b'}] """ - line = logical_line + line = self.logical_line for index in range(len(line) - 1): char = line[index] if char in ',;:' and line[index + 1] not in WHITESPACE: @@ -487,9 +488,8 @@ def missing_whitespace(logical_line): yield index, "E231 missing whitespace after '%s'" % char -@register_check -def indentation(logical_line, previous_logical, indent_char, - indent_level, previous_indent_level): +@register_check("logical_line") +def indentation(self): r"""Use 4 spaces per indentation level. For really old code that you don't want to mess up, you can continue to @@ -508,20 +508,19 @@ def indentation(logical_line, previous_logical, indent_char, E113: a = 1\n b = 2 E116: a = 1\n # b = 2 """ - c = 0 if logical_line else 3 - tmpl = "E11%d %s" if logical_line else "E11%d %s (comment)" - if indent_level % 4: + c = 0 if self.logical_line else 3 + tmpl = "E11%d %s" if self.logical_line else "E11%d %s (comment)" + if self.indent_level % 4: yield 0, tmpl % (1 + c, "indentation is not a multiple of four") - indent_expect = previous_logical.endswith(':') - if indent_expect and indent_level <= previous_indent_level: + indent_expect = self.previous_logical.endswith(':') + if indent_expect and self.indent_level <= self.previous_indent_level: yield 0, tmpl % (2 + c, "expected an indented block") - elif not indent_expect and indent_level > previous_indent_level: + elif not indent_expect and self.indent_level > self.previous_indent_level: yield 0, tmpl % (3 + c, "unexpected indentation") -@register_check -def continued_indentation(logical_line, tokens, indent_level, hang_closing, - indent_char, noqa, verbose): +@register_check("logical_line") +def continued_indentation(self): r"""Continuation lines indentation. Continuation lines should align wrapped elements either vertically @@ -548,19 +547,19 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, E129: if (a or\n b):\n pass E131: a = (\n 42\n 24) """ - first_row = tokens[0][2][0] - nrows = 1 + tokens[-1][2][0] - first_row - if noqa or nrows == 1: + first_row = self.tokens[0][2][0] + nrows = 1 + self.tokens[-1][2][0] - first_row + if self.noqa or nrows == 1: return # indent_next tells us whether the next block is indented; assuming # that it is indented by 4 spaces, then we should not allow 4-space # indents on the final continuation line; in turn, some other # indents are allowed to have an extra 4 spaces. - indent_next = logical_line.endswith(':') + indent_next = self.logical_line.endswith(':') row = depth = 0 - valid_hangs = (4,) if indent_char != '\t' else (4, 8) + valid_hangs = (4,) if self.indent_char != '\t' else (4, 8) # remember how many brackets were opened on each line parens = [0] * nrows # relative indents of physical lines @@ -571,15 +570,15 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, hangs = [None] # visual indents indent_chances = {} - last_indent = tokens[0][2] + last_indent = self.tokens[0][2] visual_indent = None last_token_multiline = False # for each depth, memorize the visual indent column indent = [last_indent[1]] - if verbose >= 3: - print(">>> " + tokens[0][4].rstrip()) + if self.verbose >= 3: + print(">>> " + self.tokens[0][4].rstrip()) - for token_type, text, start, end, line in tokens: + for token_type, text, start, end, line in self.tokens: newline = row < start[0] - first_row if newline: @@ -589,11 +588,11 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, if newline: # this is the beginning of a continuation line. last_indent = start - if verbose >= 3: + if self.verbose >= 3: print("... " + line.rstrip()) # record the initial indent. - rel_indent[row] = expand_indent(line) - indent_level + rel_indent[row] = expand_indent(line) - self.indent_level # identify closing bracket close_bracket = (token_type == tokenize.OP and text in ']})') @@ -617,7 +616,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, "visual indentation") elif close_bracket and not hang: # closing bracket matches indentation of opening bracket's line - if hang_closing: + if self.hang_closing: yield start, "E133 closing bracket is missing indentation" elif indent[depth] and start[1] < indent[depth]: if visual_indent is not True: @@ -626,7 +625,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, "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: + if close_bracket and not self.hang_closing: yield (start, "E123 closing bracket does not match " "indentation of opening bracket's line") hangs[depth] = hang @@ -658,7 +657,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, not indent[depth]): indent[depth] = start[1] indent_chances[start[1]] = True - if verbose >= 4: + if self.verbose >= 4: print("bracket depth %s indent to %s" % (depth, start[1])) # deal with implicit string concatenation elif (token_type in (tokenize.STRING, tokenize.COMMENT) or @@ -680,7 +679,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, open_rows.append([]) open_rows[depth].append(row) parens[row] += 1 - if verbose >= 4: + if self.verbose >= 4: print("bracket depth %s seen, col %s, visual min = %s" % (depth, start[1], indent[depth])) elif text in ')]}' and depth > 0: @@ -710,7 +709,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, if last_token_multiline: rel_indent[end[0] - first_row] = rel_indent[row] - if indent_next and expand_indent(line) == indent_level + 4: + if indent_next and expand_indent(line) == self.indent_level + 4: pos = (start[0], indent[0] + 4) if visual_indent: code = "E129 visually indented line" @@ -719,8 +718,8 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, yield pos, "%s with same indent as next logical line" % code -@register_check -def whitespace_before_parameters(logical_line, tokens): +@register_check("logical_line") +def whitespace_before_parameters(self): r"""Avoid extraneous whitespace. Avoid extraneous whitespace in the following situations: @@ -735,15 +734,15 @@ def whitespace_before_parameters(logical_line, tokens): E211: dict ['key'] = list[index] E211: dict['key'] = list [index] """ - prev_type, prev_text, __, prev_end, __ = tokens[0] - for index in range(1, len(tokens)): - token_type, text, start, end, __ = tokens[index] + prev_type, prev_text, __, prev_end, __ = self.tokens[0] + for index in range(1, len(self.tokens)): + token_type, text, start, end, __ = self.tokens[index] if (token_type == tokenize.OP and text in '([' and start != prev_end and (prev_type == tokenize.NAME or prev_text in '}])') and # Syntax "class A (B):" is allowed, but avoid it - (index < 2 or tokens[index - 2][1] != 'class') and + (index < 2 or self.tokens[index - 2][1] != 'class') and # Allow "return (a.foo for a in range(5))" not keyword.iskeyword(prev_text)): yield prev_end, "E211 whitespace before '%s'" % text @@ -752,8 +751,8 @@ def whitespace_before_parameters(logical_line, tokens): prev_end = end -@register_check -def whitespace_around_operator(logical_line): +@register_check("logical_line") +def whitespace_around_operator(self): r"""Avoid extraneous whitespace around an operator. Okay: a = 12 + 3 @@ -762,7 +761,7 @@ def whitespace_around_operator(logical_line): E223: a = 4\t+ 5 E224: a = 4 +\t5 """ - for match in OPERATOR_REGEX.finditer(logical_line): + for match in OPERATOR_REGEX.finditer(self.logical_line): before, after = match.groups() if '\t' in before: @@ -776,8 +775,8 @@ def whitespace_around_operator(logical_line): yield match.start(2), "E222 multiple spaces after operator" -@register_check -def missing_whitespace_around_operator(logical_line, tokens): +@register_check("logical_line") +def missing_whitespace_around_operator(self): r"""Surround operators with a single space on either side. - Always surround these binary operators with a single space on @@ -809,7 +808,7 @@ def missing_whitespace_around_operator(logical_line, tokens): need_space = False prev_type = tokenize.OP prev_text = prev_end = None - for token_type, text, start, end, line in tokens: + for token_type, text, start, end, line in self.tokens: if token_type in SKIP_COMMENTS: continue if text in ('(', 'lambda'): @@ -869,8 +868,8 @@ def missing_whitespace_around_operator(logical_line, tokens): prev_end = end -@register_check -def whitespace_around_comma(logical_line): +@register_check("logical_line") +def whitespace_around_comma(self): r"""Avoid extraneous whitespace after a comma or a colon. Note: these checks are disabled by default @@ -879,7 +878,7 @@ def whitespace_around_comma(logical_line): E241: a = (1, 2) E242: a = (1,\t2) """ - line = logical_line + line = self.logical_line for m in WHITESPACE_AFTER_COMMA_REGEX.finditer(line): found = m.start() + 1 if '\t' in m.group(): @@ -888,8 +887,8 @@ def whitespace_around_comma(logical_line): yield found, "E241 multiple spaces after '%s'" % m.group()[0] -@register_check -def whitespace_around_named_parameter_equals(logical_line, tokens): +@register_check("logical_line") +def whitespace_around_named_parameter_equals(self): r"""Don't use spaces around the '=' sign in function arguments. Don't use spaces around the '=' sign when used to indicate a @@ -914,12 +913,12 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): require_space = False prev_end = None annotated_func_arg = False - in_def = bool(STARTSWITH_DEF_REGEX.match(logical_line)) + in_def = bool(STARTSWITH_DEF_REGEX.match(self.logical_line)) message = "E251 unexpected spaces around keyword / parameter equals" missing_message = "E252 missing whitespace around parameter equals" - for token_type, text, start, end, line in tokens: + for token_type, text, start, end, line in self.tokens: if token_type == tokenize.NL: continue if no_space: @@ -954,8 +953,8 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): prev_end = end -@register_check -def whitespace_before_comment(logical_line, tokens): +@register_check("logical_line") +def whitespace_before_comment(self): r"""Separate inline comments by at least two spaces. An inline comment is a comment on the same line as a statement. Inline @@ -975,7 +974,7 @@ def whitespace_before_comment(logical_line, tokens): E266: ### Block comment """ prev_end = (0, 0) - for token_type, text, start, end, line in tokens: + for token_type, text, start, end, line in self.tokens: if token_type == tokenize.COMMENT: inline_comment = line[:start[1]].strip() if inline_comment: @@ -996,8 +995,8 @@ def whitespace_before_comment(logical_line, tokens): prev_end = end -@register_check -def imports_on_separate_lines(logical_line): +@register_check("logical_line") +def imports_on_separate_lines(self): r"""Place imports on separate lines. Okay: import os\nimport sys @@ -1009,16 +1008,15 @@ def imports_on_separate_lines(logical_line): Okay: import myclass Okay: import foo.bar.yourclass """ - line = logical_line + line = self.logical_line if line.startswith('import '): found = line.find(',') if -1 < found and ';' not in line[:found]: yield found, "E401 multiple imports on one line" -@register_check -def module_imports_on_top_of_file( - logical_line, indent_level, checker_state, noqa): +@register_check("logical_line") +def module_imports_on_top_of_file(self): r"""Place imports at the top of the file. Always put imports at the top of the file, just after any module comments @@ -1047,15 +1045,17 @@ def is_string_literal(line): allowed_try_keywords = ('try', 'except', 'else', 'finally') - if indent_level: # Allow imports in conditional statements or functions + # Allow imports in conditional statements or functions + if self.indent_level: return - if not logical_line: # Allow empty lines or comments + # Allow empty lines or comments + if not self.logical_line: return - if noqa: + if self.noqa: return - line = logical_line + line = self.logical_line if line.startswith('import ') or line.startswith('from '): - if checker_state.get('seen_non_imports', False): + if self.checker_state.get('seen_non_imports', False): yield 0, "E402 module level import not at top of file" elif re.match(DUNDER_REGEX, line): return @@ -1065,16 +1065,16 @@ def is_string_literal(line): return elif is_string_literal(line): # The first literal is a docstring, allow it. Otherwise, report error. - if checker_state.get('seen_docstring', False): - checker_state['seen_non_imports'] = True + if self.checker_state.get('seen_docstring', False): + self.checker_state['seen_non_imports'] = True else: - checker_state['seen_docstring'] = True + self.checker_state['seen_docstring'] = True else: - checker_state['seen_non_imports'] = True + self.checker_state['seen_non_imports'] = True -@register_check -def compound_statements(logical_line): +@register_check("logical_line") +def compound_statements(self): r"""Compound statements (on the same line) are generally discouraged. While sometimes it's okay to put an if/for/while with a small body @@ -1102,7 +1102,7 @@ def compound_statements(logical_line): E704: def f(x): return 2*x E731: f = lambda x: 2*x """ - line = logical_line + line = self.logical_line last_char = len(line) - 1 found = line.find(':') prev_found = 0 @@ -1134,8 +1134,8 @@ def compound_statements(logical_line): found = line.find(';', found + 1) -@register_check -def explicit_line_join(logical_line, tokens): +@register_check("logical_line") +def explicit_line_join(self): r"""Avoid explicit line join between brackets. The preferred way of wrapping long lines is by using Python's implied line @@ -1154,7 +1154,7 @@ def explicit_line_join(logical_line, tokens): prev_start = prev_end = parens = 0 comment = False backslash = None - for token_type, text, start, end, line in tokens: + for token_type, text, start, end, line in self.tokens: if token_type == tokenize.COMMENT: comment = True if start[0] != prev_start and parens and backslash and not comment: @@ -1213,8 +1213,8 @@ def _break_around_binary_operators(tokens): previous_text = text -@register_check -def break_before_binary_operator(logical_line, tokens): +@register_check("logical_line") +def break_before_binary_operator(self): r""" Avoid breaks before binary operators. @@ -1233,7 +1233,7 @@ def break_before_binary_operator(logical_line, tokens): Okay: foo(x,\n -y) Okay: foo(x, # comment\n -y) """ - for context in _break_around_binary_operators(tokens): + for context in _break_around_binary_operators(self.tokens): (token_type, text, previous_token_type, previous_text, line_break, unary_context, start) = context if (_is_binary_operator(token_type, text) and line_break and @@ -1243,8 +1243,8 @@ def break_before_binary_operator(logical_line, tokens): yield start, "W503 line break before binary operator" -@register_check -def break_after_binary_operator(logical_line, tokens): +@register_check("logical_line") +def break_after_binary_operator(self): r""" Avoid breaks after binary operators. @@ -1266,7 +1266,7 @@ def break_after_binary_operator(logical_line, tokens): Okay: var = (1 /\n -2) Okay: var = (1 +\n -1 +\n -2) """ - for context in _break_around_binary_operators(tokens): + for context in _break_around_binary_operators(self.tokens): (token_type, text, previous_token_type, previous_text, line_break, unary_context, start) = context if (_is_binary_operator(previous_token_type, previous_text) and @@ -1277,8 +1277,8 @@ def break_after_binary_operator(logical_line, tokens): yield error_pos, "W504 line break after binary operator" -@register_check -def comparison_to_singleton(logical_line, noqa): +@register_check("logical_line") +def comparison_to_singleton(self): r"""Comparison to singletons should use "is" or "is not". Comparisons to singletons like None should always be done @@ -1295,7 +1295,7 @@ def comparison_to_singleton(logical_line, noqa): set to some other value. The other value might have a type (such as a container) that could be false in a boolean context! """ - match = not noqa and COMPARE_SINGLETON_REGEX.search(logical_line) + match = not self.noqa and COMPARE_SINGLETON_REGEX.search(self.logical_line) if match: singleton = match.group(1) or match.group(3) same = (match.group(2) == '==') @@ -1312,8 +1312,8 @@ def comparison_to_singleton(logical_line, noqa): (code, singleton, msg)) -@register_check -def comparison_negative(logical_line): +@register_check("logical_line") +def comparison_negative(self): r"""Negative comparison should be done using "not in" and "is not". Okay: if x not in y:\n pass @@ -1325,7 +1325,7 @@ def comparison_negative(logical_line): E714: if not X is Y:\n pass E714: Z = not X.B is Y """ - match = COMPARE_NEGATIVE_REGEX.search(logical_line) + match = COMPARE_NEGATIVE_REGEX.search(self.logical_line) if match: pos = match.start(1) if match.group(2) == 'in': @@ -1334,8 +1334,8 @@ def comparison_negative(logical_line): yield pos, "E714 test for object identity should be 'is not'" -@register_check -def comparison_type(logical_line, noqa): +@register_check("logical_line") +def comparison_type(self): r"""Object type comparisons should always use isinstance(). Do not compare types directly. @@ -1350,33 +1350,33 @@ def comparison_type(logical_line, noqa): Okay: if isinstance(obj, basestring): Okay: if type(a1) is type(b1): """ - match = COMPARE_TYPE_REGEX.search(logical_line) - if match and not noqa: + match = COMPARE_TYPE_REGEX.search(self.logical_line) + if match and not self.noqa: inst = match.group(1) if inst and isidentifier(inst) and inst not in SINGLETONS: return # Allow comparison for types which are not obvious yield match.start(), "E721 do not compare types, use 'isinstance()'" -@register_check -def bare_except(logical_line, noqa): +@register_check("logical_line") +def bare_except(self): r"""When catching exceptions, mention specific exceptions when possible. Okay: except Exception: Okay: except BaseException: E722: except: """ - if noqa: + if self.noqa: return regex = re.compile(r"except\s*:") - match = regex.match(logical_line) + match = regex.match(self.logical_line) if match: yield match.start(), "E722 do not use bare 'except'" -@register_check -def ambiguous_identifier(logical_line, tokens): +@register_check("logical_line") +def ambiguous_identifier(self): r"""Never use the characters 'l', 'O', or 'I' as variable names. In some fonts, these characters are indistinguishable from the numerals @@ -1403,8 +1403,8 @@ def ambiguous_identifier(logical_line, tokens): E743: def l(x): """ idents_to_avoid = ('l', 'O', 'I') - prev_type, prev_text, prev_start, prev_end, __ = tokens[0] - for token_type, text, start, end, line in tokens[1:]: + prev_type, prev_text, prev_start, prev_end, __ = self.tokens[0] + for token_type, text, start, end, line in self.tokens[1:]: ident = pos = None # identifiers on the lhs of an assignment operator if token_type == tokenize.OP and '=' in text: @@ -1428,20 +1428,20 @@ def ambiguous_identifier(logical_line, tokens): prev_start = start -@register_check -def python_3000_has_key(logical_line, noqa): +@register_check("logical_line") +def python_3000_has_key(self): r"""The {}.has_key() method is removed in Python 3: use the 'in' operator. Okay: if "alph" in d:\n print d["alph"] W601: assert d.has_key('alph') """ - pos = logical_line.find('.has_key(') - if pos > -1 and not noqa: + pos = self.logical_line.find('.has_key(') + if pos > -1 and not self.noqa: yield pos, "W601 .has_key() is deprecated, use 'in'" -@register_check -def python_3000_raise_comma(logical_line): +@register_check("logical_line") +def python_3000_raise_comma(self): r"""When raising an exception, use "raise ValueError('message')". The older form is removed in Python 3. @@ -1449,13 +1449,13 @@ def python_3000_raise_comma(logical_line): Okay: raise DummyError("Message") W602: raise DummyError, "Message" """ - match = RAISE_COMMA_REGEX.match(logical_line) - if match and not RERAISE_COMMA_REGEX.match(logical_line): + match = RAISE_COMMA_REGEX.match(self.logical_line) + if match and not RERAISE_COMMA_REGEX.match(self.logical_line): yield match.end() - 1, "W602 deprecated form of raising exception" -@register_check -def python_3000_not_equal(logical_line): +@register_check("logical_line") +def python_3000_not_equal(self): r"""New code should always use != instead of <>. The older syntax is removed in Python 3. @@ -1463,25 +1463,25 @@ def python_3000_not_equal(logical_line): Okay: if a != 'no': W603: if a <> 'no': """ - pos = logical_line.find('<>') + pos = self.logical_line.find('<>') if pos > -1: yield pos, "W603 '<>' is deprecated, use '!='" -@register_check -def python_3000_backticks(logical_line): +@register_check("logical_line") +def python_3000_backticks(self): r"""Use repr() instead of backticks in Python 3. Okay: val = repr(1 + 2) W604: val = `1 + 2` """ - pos = logical_line.find('`') + pos = self.logical_line.find('`') if pos > -1: yield pos, "W604 backticks are deprecated, use 'repr()'" -@register_check -def python_3000_invalid_escape_sequence(logical_line, tokens): +@register_check("logical_line") +def python_3000_invalid_escape_sequence(self): r"""Invalid escape sequences are deprecated in Python 3.6. Okay: regex = r'\.png$' @@ -1509,7 +1509,7 @@ def python_3000_invalid_escape_sequence(logical_line, tokens): 'U', ] - for token_type, text, start, end, line in tokens: + for token_type, text, start, end, line in self.tokens: if token_type == tokenize.STRING: quote = text[-3:] if text[-3:] in ('"""', "'''") else text[-1] # Extract string modifiers (e.g. u or r) @@ -1531,8 +1531,8 @@ def python_3000_invalid_escape_sequence(logical_line, tokens): pos = string.find('\\', pos + 1) -@register_check -def python_3000_async_await_keywords(logical_line, tokens): +@register_check("logical_line") +def python_3000_async_await_keywords(self): """'async' and 'await' are reserved keywords starting with Python 3.7 W606: async = 42 @@ -1545,7 +1545,7 @@ def python_3000_async_await_keywords(logical_line, tokens): # https://docs.python.org/3/reference/grammar.html state = None - for token_type, text, start, end, line in tokens: + for token_type, text, start, end, line in self.tokens: error = False if state is None: @@ -1764,6 +1764,8 @@ def __init__(self, filename=None, lines=None, self.filename = filename # Dictionary where a checker can store its custom state. self._checker_states = {} + self.checker_state = self._checker_states.setdefault( + 'module_imports_on_top_of_file', {}) if filename is None: self.filename = 'stdin' self.lines = lines or [] @@ -1813,24 +1815,11 @@ def readline(self): self.indent_char = line[0] return line - def run_check(self, check, argument_names): - """Run a check plugin.""" - arguments = [] - for name in argument_names: - arguments.append(getattr(self, name)) - return check(*arguments) - - def init_checker_state(self, name, argument_names): - """Prepare custom state for the specific checker plugin.""" - if 'checker_state' in argument_names: - self.checker_state = self._checker_states.setdefault(name, {}) - def check_physical(self, line): """Run all physical checks on a raw input line.""" self.physical_line = line - for name, check, argument_names in self._physical_checks: - self.init_checker_state(name, argument_names) - result = self.run_check(check, argument_names) + for name, check in self._physical_checks: + result = check(self) if result is not None: (offset, text) = result self.report_error(self.line_number, offset, text, check) @@ -1885,11 +1874,10 @@ def check_logical(self): self.blank_before = self.blank_lines if self.verbose >= 2: print(self.logical_line[:80].rstrip()) - for name, check, argument_names in self._logical_checks: + for name, check in self._logical_checks: if self.verbose >= 4: print(' ' + name) - self.init_checker_state(name, argument_names) - for offset, text in self.run_check(check, argument_names) or (): + for offset, text in check(self) or (): if not isinstance(offset, tuple): # As mappings are ordered, bisecting is a fast way # to find a given offset in them. @@ -1911,7 +1899,7 @@ def check_ast(self): tree = compile(''.join(self.lines), '', 'exec', PyCF_ONLY_AST) except (ValueError, 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 self.lines or not noqa(self.lines[lineno - 1]): @@ -2311,10 +2299,9 @@ def get_checks(self, argument_name): starts with argument_name and which contain selected tests. """ checks = [] - for check, attrs in _checks[argument_name].items(): - (codes, args) = attrs + for check, codes in _checks[argument_name].items(): if any(not (code and self.ignore_code(code)) for code in codes): - checks.append((check.__name__, check, args)) + checks.append((check.__name__, check)) return sorted(checks) diff --git a/testsuite/support.py b/testsuite/support.py index 825def14..e9698f4f 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -111,7 +111,7 @@ def selftest(options): report = BaseReport(options) counters = report.counters checks = options.physical_checks + options.logical_checks - for name, check, argument_names in checks: + for name, check in checks: for line in check.__doc__.splitlines(): line = line.lstrip() match = SELFTEST_REGEX.match(line) diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 6eb9f041..91fc688e 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -29,8 +29,8 @@ def setUp(self): sys.stdout = PseudoFile() sys.stderr = PseudoFile() pycodestyle._checks = dict( - (k, dict((f, (vals[0][:], vals[1])) for (f, vals) in v.items())) - for (k, v) in self._saved_checks.items() + (kind, dict((f, val) for (f, val) in checks.items())) + for (kind, checks) in self._saved_checks.items() ) def tearDown(self): @@ -42,52 +42,51 @@ def reset(self): del sys.stdout[:], sys.stderr[:] def test_register_physical_check(self): - def check_dummy(physical_line, line_number): + def check_dummy(self): if False: yield - pycodestyle.register_check(check_dummy, ['Z001']) + pycodestyle.register_check("physical_line")( + check_dummy, ['Z001']) self.assertTrue(check_dummy in pycodestyle._checks['physical_line']) - codes, args = pycodestyle._checks['physical_line'][check_dummy] + codes = pycodestyle._checks['physical_line'][check_dummy] self.assertTrue('Z001' in codes) - self.assertEqual(args, ['physical_line', 'line_number']) options = pycodestyle.StyleGuide().options self.assertTrue(any(func == check_dummy - for name, func, args in options.physical_checks)) + for name, func in options.physical_checks)) def test_register_logical_check(self): - def check_dummy(logical_line, tokens): + def check_dummy(self): if False: yield - pycodestyle.register_check(check_dummy, ['Z401']) + pycodestyle.register_check("logical_line")( + check_dummy, ['Z401']) self.assertTrue(check_dummy in pycodestyle._checks['logical_line']) - codes, args = pycodestyle._checks['logical_line'][check_dummy] + codes = pycodestyle._checks['logical_line'][check_dummy] self.assertTrue('Z401' in codes) - self.assertEqual(args, ['logical_line', 'tokens']) - pycodestyle.register_check(check_dummy, []) - pycodestyle.register_check(check_dummy, ['Z402', 'Z403']) - codes, args = pycodestyle._checks['logical_line'][check_dummy] + pycodestyle.register_check("logical_line")(check_dummy, []) + pycodestyle.register_check("logical_line")( + check_dummy, ['Z402', 'Z403']) + codes = pycodestyle._checks['logical_line'][check_dummy] self.assertEqual(codes, ['Z401', 'Z402', 'Z403']) - self.assertEqual(args, ['logical_line', 'tokens']) options = pycodestyle.StyleGuide().options self.assertTrue(any(func == check_dummy - for name, func, args in options.logical_checks)) + for name, func in options.logical_checks)) def test_register_ast_check(self): - pycodestyle.register_check(DummyChecker, ['Z701']) + pycodestyle.register_check("tree")(DummyChecker, ['Z701']) self.assertTrue(DummyChecker in pycodestyle._checks['tree']) - codes, args = pycodestyle._checks['tree'][DummyChecker] + codes = pycodestyle._checks['tree'][DummyChecker] self.assertTrue('Z701' in codes) - self.assertTrue(args is None) options = pycodestyle.StyleGuide().options self.assertTrue(any(cls == DummyChecker - for name, cls, args in options.ast_checks)) + for name, cls in options.ast_checks)) def test_register_invalid_check(self): class InvalidChecker(DummyChecker): @@ -97,14 +96,14 @@ def __init__(self, filename): def check_dummy(logical, tokens): if False: yield - pycodestyle.register_check(InvalidChecker, ['Z741']) - pycodestyle.register_check(check_dummy, ['Z441']) + pycodestyle.register_check("filename")(InvalidChecker, ['Z741']) + pycodestyle.register_check("filename")(check_dummy, ['Z441']) for checkers in pycodestyle._checks.values(): self.assertTrue(DummyChecker not in checkers) self.assertTrue(check_dummy not in checkers) - self.assertRaises(TypeError, pycodestyle.register_check) + self.assertRaises(TypeError, pycodestyle.register_check("filename")) def test_styleguide(self): report = pycodestyle.StyleGuide().check_files() @@ -257,37 +256,35 @@ def test_styleguide_checks(self): self.assertEqual(len(pep8style.options.ast_checks), 0) # Sanity check - for name, check, args in pep8style.options.physical_checks: + for name, check in pep8style.options.physical_checks: self.assertEqual(check.__name__, name) - self.assertEqual(args[0], 'physical_line') - for name, check, args in pep8style.options.logical_checks: + for name, check in pep8style.options.logical_checks: self.assertEqual(check.__name__, name) - self.assertEqual(args[0], 'logical_line') # Do run E11 checks options = pycodestyle.StyleGuide().options self.assertTrue(any(func == pycodestyle.indentation - for name, func, args in options.logical_checks)) + for name, func in options.logical_checks)) options = pycodestyle.StyleGuide(select=['E']).options self.assertTrue(any(func == pycodestyle.indentation - for name, func, args in options.logical_checks)) + for name, func in options.logical_checks)) options = pycodestyle.StyleGuide(ignore=['W']).options self.assertTrue(any(func == pycodestyle.indentation - for name, func, args in options.logical_checks)) + for name, func in options.logical_checks)) options = pycodestyle.StyleGuide(ignore=['E12']).options self.assertTrue(any(func == pycodestyle.indentation - for name, func, args in options.logical_checks)) + for name, func in options.logical_checks)) # Do not run E11 checks options = pycodestyle.StyleGuide(select=['W']).options self.assertFalse(any(func == pycodestyle.indentation - for name, func, args in options.logical_checks)) + for name, func in options.logical_checks)) options = pycodestyle.StyleGuide(ignore=['E']).options self.assertFalse(any(func == pycodestyle.indentation - for name, func, args in options.logical_checks)) + for name, func in options.logical_checks)) options = pycodestyle.StyleGuide(ignore=['E11']).options self.assertFalse(any(func == pycodestyle.indentation - for name, func, args in options.logical_checks)) + for name, func in options.logical_checks)) def test_styleguide_init_report(self): style = pycodestyle.StyleGuide(paths=[E11]) @@ -320,7 +317,7 @@ def test_styleguide_check_files(self): def test_check_unicode(self): # Do not crash if lines are Unicode (Python 2.x) - pycodestyle.register_check(DummyChecker, ['Z701']) + pycodestyle.register_check("tree")(DummyChecker, ['Z701']) source = '#\n' if hasattr(source, 'decode'): source = source.decode('ascii') @@ -333,7 +330,7 @@ def test_check_unicode(self): self.assertEqual(count_errors, 0) def test_check_nullbytes(self): - pycodestyle.register_check(DummyChecker, ['Z701']) + pycodestyle.register_check("tree")(DummyChecker, ['Z701']) pep8style = pycodestyle.StyleGuide() count_errors = pep8style.input_file('stdin', lines=['\x00\n']) @@ -354,7 +351,7 @@ def test_check_nullbytes(self): self.assertEqual(count_errors, 1) def test_styleguide_unmatched_triple_quotes(self): - pycodestyle.register_check(DummyChecker, ['Z701']) + pycodestyle.register_check("tree")(DummyChecker, ['Z701']) lines = [ 'def foo():\n', ' """test docstring""\'\n', @@ -368,7 +365,7 @@ def test_styleguide_unmatched_triple_quotes(self): self.assertTrue(expected in stdout) def test_styleguide_continuation_line_outdented(self): - pycodestyle.register_check(DummyChecker, ['Z701']) + pycodestyle.register_check("tree")(DummyChecker, ['Z701']) lines = [ 'def foo():\n', ' pass\n',