Sat, 01 Oct 2022 19:42:50 +0200
Third Party packages
- upgraded pycodestyle to version 2.9.1
docs/changelog | file | annotate | diff | comparison | revisions | |
src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/pycodestyle.py | file | annotate | diff | comparison | revisions |
--- a/docs/changelog Sat Oct 01 17:37:10 2022 +0200 +++ b/docs/changelog Sat Oct 01 19:42:50 2022 +0200 @@ -4,6 +4,7 @@ - bug fixes - Third Party packages -- upgraded coverage to 6.5.0 + -- upgraded pycodestyle to version 2.9.1 Version 22.10: - bug fixes
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/pycodestyle.py Sat Oct 01 17:37:10 2022 +0200 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/pycodestyle.py Sat Oct 01 19:42:50 2022 +0200 @@ -71,17 +71,8 @@ import tokenize import warnings -try: - from functools import lru_cache -except ImportError: - def lru_cache(maxsize=128): # noqa as it's a fake implementation. - """Does not really need a real a lru_cache, it's just - optimization, so let's just do nothing here. Python 3.2+ will - just get better performances, time to upgrade? - """ - return lambda function: function - from fnmatch import fnmatch +from functools import lru_cache from optparse import OptionParser try: @@ -97,7 +88,7 @@ ): # pragma: no cover (<py310) tokenize._compile = lru_cache()(tokenize._compile) # type: ignore -__version__ = '2.8.0-eric' +__version__ = '2.9.1-eric' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503,W504' @@ -135,16 +126,13 @@ UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-', '@']) WS_OPTIONAL_OPERATORS = ARITHMETIC_OP.union(['^', '&', '|', '<<', '>>', '%']) -# Warn for -> function annotation operator in py3.5+ (issue 803) -FUNCTION_RETURN_ANNOTATION_OP = ['->'] if sys.version_info >= (3, 5) else [] ASSIGNMENT_EXPRESSION_OP = [':='] if sys.version_info >= (3, 8) else [] WS_NEEDED_OPERATORS = frozenset([ '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<>', '<', '>', '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=', - 'and', 'in', 'is', 'or'] + - FUNCTION_RETURN_ANNOTATION_OP + + 'and', 'in', 'is', 'or', '->'] + ASSIGNMENT_EXPRESSION_OP) -WHITESPACE = frozenset(' \t') +WHITESPACE = frozenset(' \t\xa0') NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE]) SKIP_TOKENS = NEWLINE.union([tokenize.INDENT, tokenize.DEDENT]) # ERRORTOKEN is triggered by backticks in Python 3 @@ -165,13 +153,13 @@ 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)) -OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)') +OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+|:=)(\s*)') LAMBDA_REGEX = re.compile(r'\blambda\b') HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)\b') STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def\s+|def\s+|class\s+|@)') STARTSWITH_INDENT_STATEMENT_REGEX = re.compile( - r'^\s*({0})\b'.format('|'.join(s.replace(' ', r'\s+') for s in ( + r'^\s*({})\b'.format('|'.join(s.replace(' ', r'\s+') for s in ( 'def', 'async def', 'for', 'async for', 'if', 'elif', 'else', @@ -188,13 +176,10 @@ def _get_parameters(function): - if sys.version_info >= (3, 3): - return [parameter.name - for parameter - in inspect.signature(function).parameters.values() - if parameter.kind == parameter.POSITIONAL_OR_KEYWORD] - else: - return inspect.getargspec(function)[0] + return [parameter.name + for parameter + in inspect.signature(function).parameters.values() + if parameter.kind == parameter.POSITIONAL_OR_KEYWORD] def register_check(check, codes=None): @@ -320,12 +305,6 @@ (len(chunks) == 2 and chunks[0] == '#')) and \ len(line) - len(chunks[-1]) < max_line_length - 7: return - if hasattr(line, 'decode'): # Python 2 - # The line could contain multi-byte characters - try: - 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), @@ -450,22 +429,22 @@ if nested or ancestor_level == 0: break if nested: - yield (0, "E306 expected %s blank lines before a " - "nested definition, found %d", method_lines, + yield (0, "E306 expected {} blank lines before a " + "nested definition, found {}", method_lines, blank_before) else: - yield (0, "E301 expected %s blank lines, found %d", + yield (0, "E301 expected {} blank lines, found %d", method_lines, blank_before) elif blank_before != top_level_lines: - yield (0, "E302 expected %s blank lines, found %d", + yield (0, "E302 expected {} blank lines, found {}", 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 ')) ): - yield (0, "E305 expected %s blank lines after " \ - "class or function definition, found %d", + yield (0, "E305 expected {} blank lines after " \ + "class or function definition, found {}", top_level_lines, blank_before) @@ -499,7 +478,7 @@ yield found + 1, "E201 whitespace after '%s'", char elif line[found - 1] != ',': code = ('E202' if char in '}])' else 'E203') # if char in ',;:' - yield found, "%s whitespace before '%s'" % (code, char), char + yield found, f"{code} whitespace before '{char}'", char @register_check @@ -527,21 +506,26 @@ @register_check -def missing_whitespace_after_import_keyword(logical_line): - r"""Multiple imports in form from x import (a, b, c) should have - space between import statement and parenthesised name list. +def missing_whitespace_after_keyword(logical_line, tokens): + r"""Keywords should be followed by whitespace. Okay: from foo import (bar, baz) E275: from foo import(bar, baz) E275: from importable.module import(bar, baz) + E275: if(foo): bar """ - line = logical_line - indicator = ' import(' - if line.startswith('from '): - found = line.find(indicator) - if -1 < found: - pos = found + len(indicator) - 1 - yield pos, "E275 missing whitespace after keyword" + for tok0, tok1 in zip(tokens, tokens[1:]): + # This must exclude the True/False/None singletons, which can + # appear e.g. as "if x is None:", and async/await, which were + # valid identifier names in old Python versions. + if (tok0.end == tok1.start and + keyword.iskeyword(tok0.string) and + tok0.string not in SINGLETONS and + tok0.string not in ('async', 'await') and + not (tok0.string == 'except' and tok1.string == '*') and + not (tok0.string == 'yield' and tok1.string == ')') and + tok1.string not in ':\n'): + yield tok0.end, "E275 missing whitespace after keyword" @register_check @@ -760,7 +744,7 @@ indent[depth] = start[1] indent_chances[start[1]] = True if verbose >= 4: - print("bracket depth %s indent to %s" % (depth, start[1])) + print(f"bracket depth {depth} indent to {start[1]}") # deal with implicit string concatenation elif (token_type in (tokenize.STRING, tokenize.COMMENT) or text in ('u', 'ur', 'b', 'br')): @@ -1089,21 +1073,24 @@ @register_check def whitespace_before_comment(logical_line, tokens): - r"""Separate inline comments by at least two spaces. + """Separate inline comments by at least two spaces. An inline comment is a comment on the same line as a statement. Inline comments should be separated by at least two spaces from the statement. They should start with a # and a single space. - Each line of a block comment starts with a # and a single space - (unless it is indented text inside the comment). + Each line of a block comment starts with a # and one or multiple + spaces as there can be indented text inside the comment. Okay: x = x + 1 # Increment x Okay: x = x + 1 # Increment x - Okay: # Block comment + Okay: # Block comments: + Okay: # - Block comment list + Okay: # \xa0- Block comment list E261: x = x + 1 # Increment x E262: x = x + 1 #Increment x E262: x = x + 1 # Increment x + E262: x = x + 1 # \xa0Increment x E265: #Block comment E266: ### Block comment """ @@ -1253,7 +1240,7 @@ lambda_kw = LAMBDA_REGEX.search(line, 0, found) if lambda_kw: before = line[:lambda_kw.start()].rstrip() - if before[-1:] == '=' and isidentifier(before[:-1].strip()): + if before[-1:] == '=' and before[:-1].strip().isidentifier(): yield 0, ("E731 do not assign a lambda expression, use a " "def") break @@ -1313,20 +1300,19 @@ parens -= 1 +# The % character is strictly speaking a binary operator, but the +# common usage seems to be to put it next to the format parameters, +# after a line break. _SYMBOLIC_OPS = frozenset("()[]{},:.;@=%~") | frozenset(("...",)) def _is_binary_operator(token_type, text): - is_op_token = token_type == tokenize.OP - is_conjunction = text in ['and', 'or'] - # NOTE(sigmavirus24): Previously the not_a_symbol check was executed - # conditionally. Since it is now *always* executed, text may be - # None. In that case we get a TypeError for `text not in str`. - not_a_symbol = text and text not in _SYMBOLIC_OPS - # The % character is strictly speaking a binary operator, but the - # common usage seems to be to put it next to the format parameters, - # after a line break. - return ((is_op_token or is_conjunction) and not_a_symbol) + return ( + token_type == tokenize.OP or + text in {'and', 'or'} + ) and ( + text not in _SYMBOLIC_OPS + ) def _break_around_binary_operators(tokens): @@ -1498,7 +1484,7 @@ match = COMPARE_TYPE_REGEX.search(logical_line) if match and not noqa: inst = match.group(1) - if inst and isidentifier(inst) and inst not in SINGLETONS: + if inst and inst.isidentifier() and inst not in SINGLETONS: return # Allow comparison for types which are not obvious yield match.start(), "E721 do not compare types, use 'isinstance()'" @@ -1816,12 +1802,6 @@ if prev_token is None or prev_token in SKIP_TOKENS: lines = line.splitlines() for line_num, physical_line in enumerate(lines): - if hasattr(physical_line, 'decode'): # Python 2 - # The line could contain multi-byte characters - try: - physical_line = physical_line.decode('utf-8') - except UnicodeError: - pass if start[0] + line_num == 1 and line.startswith('#!'): return length = len(physical_line) @@ -1847,30 +1827,19 @@ ######################################################################## -if sys.version_info < (3,): - # Python 2: implicit encoding. - def readlines(filename): - """Read the source code.""" - with open(filename, 'rU') as f: +def readlines(filename): + """Read the source code.""" + try: + with tokenize.open(filename) as f: return f.readlines() - isidentifier = re.compile(r'[a-zA-Z_]\w*$').match - stdin_get_value = sys.stdin.read -else: - # Python 3 - def readlines(filename): - """Read the source code.""" - try: - with tokenize.open(filename) as f: - return f.readlines() - except (LookupError, SyntaxError, UnicodeError): - # Fall back if file encoding is improperly declared - with open(filename, encoding='latin-1') as f: - return f.readlines() - isidentifier = str.isidentifier - - def stdin_get_value(): - """Read the value from stdin.""" - return TextIOWrapper(sys.stdin.buffer, errors='ignore').read() + except (LookupError, SyntaxError, UnicodeError): + # Fall back if file encoding is improperly declared + with open(filename, encoding='latin-1') as f: + return f.readlines() + +def stdin_get_value(): + """Read the value from stdin.""" + return TextIOWrapper(sys.stdin.buffer, errors='ignore').read() noqa = lru_cache(512)(re.compile(r'# no(?:qa|pep8)\b', re.I).search) @@ -1936,7 +1905,7 @@ continue if line[:3] == '@@ ': hunk_match = HUNK_REGEX.match(line) - (row, nrows) = [int(g or '1') for g in hunk_match.groups()] + (row, nrows) = (int(g or '1') for g in hunk_match.groups()) rv[path].update(range(row, row + nrows)) elif line[:3] == '+++': path = line[4:].split('\t', 1)[0] @@ -1997,7 +1966,7 @@ ######################################################################## -class Checker(object): +class Checker: """Load a Python source file, tokenize it, check coding style.""" def __init__(self, filename=None, lines=None, @@ -2031,7 +2000,7 @@ self.lines = readlines(filename) except OSError: (exc_type, exc) = sys.exc_info()[:2] - self._io_error = '%s: %s' % (exc_type.__name__, exc) + self._io_error = f'{exc_type.__name__}: {exc}' self.lines = [] else: self.lines = lines @@ -2260,7 +2229,7 @@ token_type, text = token[0:2] if self.verbose >= 3: if token[2][0] == token[3][0]: - pos = '[%s:%s]' % (token[2][1] or '', token[3][1]) + pos = '[{}:{}]'.format(token[2][1] or '', token[3][1]) else: pos = 'l.%s' % token[3][0] print('l.%s\t%s\t%s\t%r' % @@ -2287,7 +2256,7 @@ return self.report.get_file_results() -class BaseReport(object): +class BaseReport: """Collect the results of the checks.""" print_filename = False @@ -2388,7 +2357,7 @@ def print_benchmark(self): """Print benchmark numbers.""" - print('%-7.2f %s' % (self.elapsed, 'seconds elapsed')) + print('{:<7.2f} {}'.format(self.elapsed, 'seconds elapsed')) if self.elapsed: for key in self._benchmark_keys: print('%-7d %s per second (%d total)' % @@ -2421,8 +2390,7 @@ def error(self, line_number, offset, text, check): """Report an error, according to options.""" - code = super().error(line_number, offset, - text, check) + code = super().error(line_number, offset, text, check) if code and (self.counters[code] == 1 or self._repeat): self._deferred_print.append( (line_number, offset, code, text[5:], check.__doc__)) @@ -2479,7 +2447,7 @@ return super().error(line_number, offset, text, check) -class StyleGuide(object): +class StyleGuide: """Initialize a PEP-8 instance with few options.""" def __init__(self, *args, **kwargs): @@ -2572,8 +2540,10 @@ dirs.remove(subdir) for filename in sorted(files): # contain a pattern that matches? - if ((filename_match(filename, filepatterns) and - not self.excluded(filename, root))): + if ( + filename_match(filename, filepatterns) and + not self.excluded(filename, root) + ): runner(os.path.join(root, filename)) def excluded(self, filename, parent=None): @@ -2743,8 +2713,8 @@ print(" unknown option '%s' ignored" % opt) continue if options.verbose > 1: - print(" %s = %s" % (opt, - config.get(pycodestyle_section, opt))) + print(" {} = {}".format(opt, + config.get(pycodestyle_section, opt))) normalized_opt = opt.replace('-', '_') opt_type = option_list[normalized_opt] if opt_type in ('int', 'count'):