From 2246217295dc8cb30ef4a7b9d8dc449ce32e603a Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 12 Jun 2022 17:07:21 -0400 Subject: burn the bridges with python 2.x (#707) * pyupgrade --py36-plus * remove handling of PY2 * remove handling of PY35_PLUS * remove handling of PY36_PLUS * remove obsolete version_info checks in pyflakes/ * adjust skips in tests for 3.6+ * is_py3_func -> has_annotations (specifically for lambda) * remove references to py 2 * remove references to unichr * clean up version-specific getattrs * remove unused ReturnWithArgsInsideGenerator * remove unused ast handlers * remove unused RedefinedInListComp --- pyflakes/api.py | 8 +- pyflakes/checker.py | 295 +++++++-------------- pyflakes/messages.py | 23 +- pyflakes/reporter.py | 4 +- pyflakes/scripts/pyflakes.py | 1 - pyflakes/test/harness.py | 6 +- pyflakes/test/test_api.py | 215 +++++---------- pyflakes/test/test_builtin.py | 13 +- pyflakes/test/test_checker.py | 6 +- pyflakes/test/test_code_segment.py | 5 +- pyflakes/test/test_dict.py | 22 +- pyflakes/test/test_doctests.py | 4 +- pyflakes/test/test_imports.py | 37 +-- pyflakes/test/test_other.py | 70 ----- .../test_return_with_arguments_inside_generator.py | 34 --- pyflakes/test/test_type_annotations.py | 24 +- pyflakes/test/test_undefined_names.py | 75 +----- 17 files changed, 203 insertions(+), 639 deletions(-) delete mode 100644 pyflakes/test/test_return_with_arguments_inside_generator.py diff --git a/pyflakes/api.py b/pyflakes/api.py index 14e3347..6cc226b 100644 --- a/pyflakes/api.py +++ b/pyflakes/api.py @@ -12,7 +12,7 @@ from pyflakes import reporter as modReporter __all__ = ['check', 'checkPath', 'checkRecursive', 'iterSourceCode', 'main'] -PYTHON_SHEBANG_REGEX = re.compile(br'^#!.*\bpython([23](\.\d+)?|w)?[dmu]?\s') +PYTHON_SHEBANG_REGEX = re.compile(br'^#!.*\bpython(3(\.\d+)?|w)?[dmu]?\s') def check(codeString, filename, reporter=None): @@ -48,7 +48,7 @@ def check(codeString, filename, reporter=None): lines = codeString.splitlines() if len(lines) >= lineno: text = lines[lineno - 1] - if sys.version_info >= (3, ) and isinstance(text, bytes): + if isinstance(text, bytes): try: text = text.decode('ascii') except UnicodeDecodeError: @@ -90,7 +90,7 @@ def checkPath(filename, reporter=None): try: with open(filename, 'rb') as f: codestr = f.read() - except IOError: + except OSError: msg = sys.exc_info()[1] reporter.unexpectedError(filename, msg.args[1]) return 1 @@ -113,7 +113,7 @@ def isPythonFile(filename): text = f.read(max_bytes) if not text: return False - except IOError: + except OSError: return False return PYTHON_SHEBANG_REGEX.match(text) diff --git a/pyflakes/checker.py b/pyflakes/checker.py index 6dd4d89..42e5649 100644 --- a/pyflakes/checker.py +++ b/pyflakes/checker.py @@ -5,6 +5,7 @@ Implement the central Checker class. Also, it models the Bindings and Scopes. """ import __future__ +import builtins import ast import bisect import collections @@ -19,66 +20,22 @@ import tokenize from pyflakes import messages -PY2 = sys.version_info < (3, 0) -PY35_PLUS = sys.version_info >= (3, 5) # Python 3.5 and above -PY36_PLUS = sys.version_info >= (3, 6) # Python 3.6 and above PY38_PLUS = sys.version_info >= (3, 8) PYPY = hasattr(sys, 'pypy_version_info') -builtin_vars = dir(__import__('__builtin__' if PY2 else 'builtins')) +builtin_vars = dir(builtins) parse_format_string = string.Formatter().parse -if PY2: - tokenize_tokenize = tokenize.generate_tokens -else: - tokenize_tokenize = tokenize.tokenize - -if PY2: - def getNodeType(node_class): - # workaround str.upper() which is locale-dependent - return str(unicode(node_class.__name__).upper()) - def get_raise_argument(node): - return node.type +def getAlternatives(n): + if isinstance(n, ast.If): + return [n.body] + if isinstance(n, ast.Try): + return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] -else: - def getNodeType(node_class): - return node_class.__name__.upper() - - def get_raise_argument(node): - return node.exc - - # Silence `pyflakes` from reporting `undefined name 'unicode'` in Python 3. - unicode = str - -# Python >= 3.3 uses ast.Try instead of (ast.TryExcept + ast.TryFinally) -if PY2: - def getAlternatives(n): - if isinstance(n, (ast.If, ast.TryFinally)): - return [n.body] - if isinstance(n, ast.TryExcept): - return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] -else: - def getAlternatives(n): - if isinstance(n, ast.If): - return [n.body] - if isinstance(n, ast.Try): - return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] - -if PY35_PLUS: - FOR_TYPES = (ast.For, ast.AsyncFor) - LOOP_TYPES = (ast.While, ast.For, ast.AsyncFor) - FUNCTION_TYPES = (ast.FunctionDef, ast.AsyncFunctionDef) -else: - FOR_TYPES = (ast.For,) - LOOP_TYPES = (ast.While, ast.For) - FUNCTION_TYPES = (ast.FunctionDef,) -if PY36_PLUS: - ANNASSIGN_TYPES = (ast.AnnAssign,) -else: - ANNASSIGN_TYPES = () +FOR_TYPES = (ast.For, ast.AsyncFor) if PY38_PLUS: def _is_singleton(node): # type: (ast.AST) -> bool @@ -86,15 +43,9 @@ if PY38_PLUS: isinstance(node, ast.Constant) and isinstance(node.value, (bool, type(Ellipsis), type(None))) ) -elif not PY2: - def _is_singleton(node): # type: (ast.AST) -> bool - return isinstance(node, (ast.NameConstant, ast.Ellipsis)) else: def _is_singleton(node): # type: (ast.AST) -> bool - return ( - isinstance(node, ast.Name) and - node.id in {'True', 'False', 'Ellipsis', 'None'} - ) + return isinstance(node, (ast.NameConstant, ast.Ellipsis)) def _is_tuple_constant(node): # type: (ast.AST) -> bool @@ -108,13 +59,9 @@ if PY38_PLUS: def _is_constant(node): return isinstance(node, ast.Constant) or _is_tuple_constant(node) else: - _const_tps = (ast.Str, ast.Num) - if not PY2: - _const_tps += (ast.Bytes,) - def _is_constant(node): return ( - isinstance(node, _const_tps) or + isinstance(node, (ast.Str, ast.Num, ast.Bytes)) or _is_singleton(node) or _is_tuple_constant(node) ) @@ -136,7 +83,7 @@ TYPE_COMMENT_RE = re.compile(r'^#\s*type:\s*') # https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L1408-L1413 ASCII_NON_ALNUM = ''.join([chr(i) for i in range(128) if not chr(i).isalnum()]) TYPE_IGNORE_RE = re.compile( - TYPE_COMMENT_RE.pattern + r'ignore([{}]|$)'.format(ASCII_NON_ALNUM)) + TYPE_COMMENT_RE.pattern + fr'ignore([{ASCII_NON_ALNUM}]|$)') # https://github.com/python/typed_ast/blob/1.4.0/ast27/Grammar/Grammar#L147 TYPE_FUNC_RE = re.compile(r'^(\(.*?\))\s*->\s*(.*)$') @@ -293,8 +240,7 @@ def convert_to_value(item): result.name, result, ) - elif (not PY2) and isinstance(item, ast.NameConstant): - # None, True, False are nameconstants in python3, but names in 2 + elif isinstance(item, ast.NameConstant): return item.value else: return UnhandledKeyType() @@ -304,7 +250,7 @@ def is_notimplemented_name_node(node): return isinstance(node, ast.Name) and getNodeName(node) == 'NotImplemented' -class Binding(object): +class Binding: """ Represents the binding of a value to a name. @@ -325,10 +271,12 @@ class Binding(object): return self.name def __repr__(self): - return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__, - self.name, - self.source.lineno, - id(self)) + return '<{} object {!r} from line {!r} at 0x{:x}>'.format( + self.__class__.__name__, + self.name, + self.source.lineno, + id(self), + ) def redefines(self, other): return isinstance(other, Definition) and self.name == other.name @@ -344,21 +292,23 @@ class Builtin(Definition): """A definition created for all Python builtins.""" def __init__(self, name): - super(Builtin, self).__init__(name, None) + super().__init__(name, None) def __repr__(self): - return '<%s object %r at 0x%x>' % (self.__class__.__name__, - self.name, - id(self)) + return '<{} object {!r} at 0x{:x}>'.format( + self.__class__.__name__, + self.name, + id(self) + ) -class UnhandledKeyType(object): +class UnhandledKeyType: """ A dictionary key of a type that we cannot or do not check for duplicates. """ -class VariableKey(object): +class VariableKey: """ A dictionary key which is a variable. @@ -389,7 +339,7 @@ class Importation(Definition): def __init__(self, name, source, full_name=None): self.fullName = full_name or name self.redefined = [] - super(Importation, self).__init__(name, source) + super().__init__(name, source) def redefines(self, other): if isinstance(other, SubmoduleImportation): @@ -405,7 +355,7 @@ class Importation(Definition): def source_statement(self): """Generate a source statement equivalent to the import.""" if self._has_alias(): - return 'import %s as %s' % (self.fullName, self.name) + return f'import {self.fullName} as {self.name}' else: return 'import %s' % self.fullName @@ -438,13 +388,13 @@ class SubmoduleImportation(Importation): # A dot should only appear in the name when it is a submodule import assert '.' in name and (not source or isinstance(source, ast.Import)) package_name = name.split('.')[0] - super(SubmoduleImportation, self).__init__(package_name, source) + super().__init__(package_name, source) self.fullName = name def redefines(self, other): if isinstance(other, Importation): return self.fullName == other.fullName - return super(SubmoduleImportation, self).redefines(other) + return super().redefines(other) def __str__(self): return self.fullName @@ -465,7 +415,7 @@ class ImportationFrom(Importation): else: full_name = module + '.' + self.real_name - super(ImportationFrom, self).__init__(name, source, full_name) + super().__init__(name, source, full_name) def __str__(self): """Return import full name with alias.""" @@ -477,18 +427,16 @@ class ImportationFrom(Importation): @property def source_statement(self): if self.real_name != self.name: - return 'from %s import %s as %s' % (self.module, - self.real_name, - self.name) + return f'from {self.module} import {self.real_name} as {self.name}' else: - return 'from %s import %s' % (self.module, self.name) + return f'from {self.module} import {self.name}' class StarImportation(Importation): """A binding created by a 'from x import *' statement.""" def __init__(self, name, source): - super(StarImportation, self).__init__('*', source) + super().__init__('*', source) # Each star importation needs a unique name, and # may not be the module name otherwise it will be deemed imported self.name = name + '.*' @@ -514,7 +462,7 @@ class FutureImportation(ImportationFrom): """ def __init__(self, name, source, scope): - super(FutureImportation, self).__init__(name, source, '__future__') + super().__init__(name, source, '__future__') self.used = (scope, source) @@ -608,7 +556,7 @@ class ExportBinding(Binding): # If not list concatenation else: break - super(ExportBinding, self).__init__(name, source) + super().__init__(name, source) class Scope(dict): @@ -616,7 +564,7 @@ class Scope(dict): def __repr__(self): scope_cls = self.__class__.__name__ - return '<%s at 0x%x %s>' % (scope_cls, id(self), dict.__repr__(self)) + return f'<{scope_cls} at 0x{id(self):x} {dict.__repr__(self)}>' class ClassScope(Scope): @@ -634,7 +582,7 @@ class FunctionScope(Scope): '__traceback_supplement__'} def __init__(self): - super(FunctionScope, self).__init__() + super().__init__() # Simplify: manage the special locals as globals self.globals = self.alwaysUsed.copy() self.returnValue = None # First non-empty return @@ -667,7 +615,7 @@ class DoctestScope(ModuleScope): """Scope for a doctest.""" -class DummyNode(object): +class DummyNode: """Used in place of an `ast.AST` to set error message positions""" def __init__(self, lineno, col_offset): self.lineno = lineno @@ -680,10 +628,7 @@ class DetectClassScopedMagic: # Globally defined names which are not attributes of the builtins module, or # are only present on some platforms. -_MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError'] -# module scope annotation will store in `__annotations__`, see also PEP 526. -if PY36_PLUS: - _MAGIC_GLOBALS.append('__annotations__') +_MAGIC_GLOBALS = ['__file__', '__builtins__', '__annotations__', 'WindowsError'] def getNodeName(node): @@ -766,7 +711,7 @@ def _is_any_typing_member(node, scope_stack): def is_typing_overload(value, scope_stack): return ( - isinstance(value.source, FUNCTION_TYPES) and + isinstance(value.source, (ast.FunctionDef, ast.AsyncFunctionDef)) and any( _is_typing(dec, 'overload', scope_stack) for dec in value.source.decorator_list @@ -802,7 +747,7 @@ def make_tokens(code): code = code.encode('UTF-8') lines = iter(code.splitlines(True)) # next(lines, b'') is to prevent an error in pypy3 - return tuple(tokenize_tokenize(lambda: next(lines, b''))) + return tuple(tokenize.tokenize(lambda: next(lines, b''))) class _TypeableVisitor(ast.NodeVisitor): @@ -853,7 +798,7 @@ def _collect_type_comments(tree, tokens): return type_comments -class Checker(object): +class Checker: """ I check the cleanliness and sanity of Python code. @@ -870,14 +815,13 @@ class Checker(object): ast.Module: ModuleScope, ast.ClassDef: ClassScope, ast.FunctionDef: FunctionScope, + ast.AsyncFunctionDef: FunctionScope, ast.Lambda: FunctionScope, ast.ListComp: GeneratorScope, ast.SetComp: GeneratorScope, ast.GeneratorExp: GeneratorScope, ast.DictComp: GeneratorScope, } - if PY35_PLUS: - _ast_node_scope[ast.AsyncFunctionDef] = FunctionScope nodeDepth = 0 offset = None @@ -1138,16 +1082,13 @@ class Checker(object): node, value.name, existing.source) elif scope is self.scope: - if (isinstance(parent_stmt, ast.comprehension) and - not isinstance(self.getParent(existing.source), - (FOR_TYPES, ast.comprehension))): - self.report(messages.RedefinedInListComp, + if ( + (not existing.used and value.redefines(existing)) and + (value.name != '_' or isinstance(existing, Importation)) and + not is_typing_overload(existing, self.scopeStack) + ): + self.report(messages.RedefinedWhileUnused, node, value.name, existing.source) - elif not existing.used and value.redefines(existing): - if value.name != '_' or isinstance(existing, Importation): - if not is_typing_overload(existing, self.scopeStack): - self.report(messages.RedefinedWhileUnused, - node, value.name, existing.source) elif isinstance(existing, Importation) and value.redefines(existing): existing.redefined.append(node) @@ -1180,7 +1121,7 @@ class Checker(object): # in the pyflakes testsuite (so more specific handling can be added if # needed). if os.environ.get('PYFLAKES_ERROR_UNKNOWN'): - raise NotImplementedError('Unexpected type: {}'.format(type(node))) + raise NotImplementedError(f'Unexpected type: {type(node)}') else: self.handleChildren(node) @@ -1188,7 +1129,7 @@ class Checker(object): try: return self._nodeHandlers[node_class] except KeyError: - nodeType = getNodeType(node_class) + nodeType = node_class.__name__.upper() self._nodeHandlers[node_class] = handler = getattr( self, nodeType, self._unknown_handler, ) @@ -1205,7 +1146,7 @@ class Checker(object): # try enclosing function scopes and global scope for scope in self.scopeStack[-1::-1]: if isinstance(scope, ClassScope): - if not PY2 and name == '__class__': + if name == '__class__': return elif in_generators is False: # only generators used in a class scope can access the @@ -1292,7 +1233,7 @@ class Checker(object): break parent_stmt = self.getParent(node) - if isinstance(parent_stmt, ANNASSIGN_TYPES) and parent_stmt.value is None: + if isinstance(parent_stmt, ast.AnnAssign) and parent_stmt.value is None: binding = Annotation(name, node) elif isinstance(parent_stmt, (FOR_TYPES, ast.comprehension)) or ( parent_stmt != node._pyflakes_parent and @@ -1307,8 +1248,6 @@ class Checker(object): ) ): binding = ExportBinding(name, node._pyflakes_parent, self.scope) - elif PY2 and isinstance(getattr(node, 'ctx', None), ast.Param): - binding = Argument(name, self.getScopeNode(node)) elif PY38_PLUS and isinstance(parent_stmt, ast.NamedExpr): binding = NamedExprAssignment(name, node) else: @@ -1373,8 +1312,6 @@ class Checker(object): parts = (comment,) for part in parts: - if PY2: - part = part.replace('...', 'Ellipsis') self.deferFunction(functools.partial( self.handleStringAnnotation, part, DummyNode(lineno, col_offset), lineno, col_offset, @@ -1517,16 +1454,14 @@ class Checker(object): pass # "stmt" type nodes - DELETE = PRINT = FOR = ASYNCFOR = WHILE = WITH = WITHITEM = \ - ASYNCWITH = ASYNCWITHITEM = TRYFINALLY = EXEC = \ + DELETE = FOR = ASYNCFOR = WHILE = WITH = WITHITEM = ASYNCWITH = \ EXPR = ASSIGN = handleChildren PASS = ignore # "expr" type nodes - BOOLOP = UNARYOP = SET = \ - REPR = ATTRIBUTE = \ - STARRED = NAMECONSTANT = NAMEDEXPR = handleChildren + BOOLOP = UNARYOP = SET = ATTRIBUTE = STARRED = NAMECONSTANT = \ + NAMEDEXPR = handleChildren def SUBSCRIPT(self, node): if _is_name_or_attr(node.value, 'Literal'): @@ -1573,15 +1508,16 @@ class Checker(object): self.report(messages.StringDotFormatInvalidFormat, node, e) return - class state: # py2-compatible `nonlocal` - auto = None - next_auto = 0 + auto = None + next_auto = 0 placeholder_positional = set() placeholder_named = set() def _add_key(fmtkey): """Returns True if there is an error which should early-exit""" + nonlocal auto, next_auto + if fmtkey is None: # end of string or `{` / `}` escapes return False @@ -1594,21 +1530,21 @@ class Checker(object): except ValueError: pass else: # fmtkey was an integer - if state.auto is True: + if auto is True: self.report(messages.StringDotFormatMixingAutomatic, node) return True else: - state.auto = False + auto = False if fmtkey == '': - if state.auto is False: + if auto is False: self.report(messages.StringDotFormatMixingAutomatic, node) return True else: - state.auto = True + auto = True - fmtkey = state.next_auto - state.next_auto += 1 + fmtkey = next_auto + next_auto += 1 if isinstance(fmtkey, int): placeholder_positional.add(fmtkey) @@ -1643,15 +1579,9 @@ class Checker(object): # bail early if there is *args or **kwargs if ( - # python 2.x *args / **kwargs - getattr(node, 'starargs', None) or - getattr(node, 'kwargs', None) or - # python 3.x *args - any( - isinstance(arg, getattr(ast, 'Starred', ())) - for arg in node.args - ) or - # python 3.x **kwargs + # *args + any(isinstance(arg, ast.Starred) for arg in node.args) or + # **kwargs any(kwd.arg is None for kwd in node.keywords) ): return @@ -1832,7 +1762,7 @@ class Checker(object): isinstance(node.right, (ast.List, ast.Tuple)) and # does not have any *splats (py35+ feature) not any( - isinstance(elt, getattr(ast, 'Starred', ())) + isinstance(elt, ast.Starred) for elt in node.right.elts ) ): @@ -1916,7 +1846,7 @@ class Checker(object): def RAISE(self, node): self.handleChildren(node) - arg = get_raise_argument(node) + arg = node.exc if isinstance(arg, ast.Call): if is_notimplemented_name_node(arg.func): @@ -2031,9 +1961,7 @@ class Checker(object): self.handleChildren(node) self.popScope() - LISTCOMP = handleChildren if PY2 else GENERATOREXP - - DICTCOMP = SETCOMP = GENERATOREXP + LISTCOMP = DICTCOMP = SETCOMP = GENERATOREXP def NAME(self, node): """ @@ -2048,13 +1976,11 @@ class Checker(object): self.scope.usesLocals = True elif isinstance(node.ctx, ast.Store): self.handleNodeStore(node) - elif PY2 and isinstance(node.ctx, ast.Param): - self.handleNodeStore(node) elif isinstance(node.ctx, ast.Del): self.handleNodeDelete(node) else: # Unknown context - raise RuntimeError("Got impossible expression context: %r" % (node.ctx,)) + raise RuntimeError(f"Got impossible expression context: {node.ctx!r}") def CONTINUE(self, node): # Walk the tree up until we see a loop (OK), a function or class @@ -2063,7 +1989,7 @@ class Checker(object): n = node while hasattr(n, '_pyflakes_parent'): n, n_child = n._pyflakes_parent, n - if isinstance(n, LOOP_TYPES): + if isinstance(n, (ast.While, ast.For, ast.AsyncFor)): # Doesn't apply unless it's in the loop itself if n_child not in n.orelse: return @@ -2122,41 +2048,26 @@ class Checker(object): args = [] annotations = [] - if PY2: - def addArgs(arglist): - for arg in arglist: - if isinstance(arg, ast.Tuple): - addArgs(arg.elts) - else: - args.append(arg.id) - addArgs(node.args.args) - defaults = node.args.defaults - else: - if PY38_PLUS: - for arg in node.args.posonlyargs: - args.append(arg.arg) - annotations.append(arg.annotation) - for arg in node.args.args + node.args.kwonlyargs: + if PY38_PLUS: + for arg in node.args.posonlyargs: args.append(arg.arg) annotations.append(arg.annotation) - defaults = node.args.defaults + node.args.kw_defaults + for arg in node.args.args + node.args.kwonlyargs: + args.append(arg.arg) + annotations.append(arg.annotation) + defaults = node.args.defaults + node.args.kw_defaults - # Only for Python3 FunctionDefs - is_py3_func = hasattr(node, 'returns') + has_annotations = not isinstance(node, ast.Lambda) for arg_name in ('vararg', 'kwarg'): wildcard = getattr(node.args, arg_name) if not wildcard: continue - args.append(wildcard if PY2 else wildcard.arg) - if is_py3_func: - if PY2: # Python 2.7 - argannotation = arg_name + 'annotation' - annotations.append(getattr(node.args, argannotation)) - else: # Python >= 3.4 - annotations.append(wildcard.annotation) - - if is_py3_func: + args.append(wildcard.arg) + if has_annotations: + annotations.append(wildcard.annotation) + + if has_annotations: annotations.append(node.returns) if len(set(args)) < len(args): @@ -2184,28 +2095,12 @@ class Checker(object): self.report(messages.UnusedVariable, binding.source, name) self.deferAssignment(checkUnusedAssignments) - if PY2: - def checkReturnWithArgumentInsideGenerator(): - """ - Check to see if there is any return statement with - arguments but the function is a generator. - """ - if self.scope.isGenerator and self.scope.returnValue: - self.report(messages.ReturnWithArgsInsideGenerator, - self.scope.returnValue) - self.deferAssignment(checkReturnWithArgumentInsideGenerator) self.popScope() self.deferFunction(runFunction) def ARGUMENTS(self, node): self.handleChildren(node, omit=('defaults', 'kw_defaults')) - if PY2: - scope_node = self.getScopeNode(node) - if node.vararg: - self.addBinding(node, Argument(node.vararg, scope_node)) - if node.kwarg: - self.addBinding(node, Argument(node.kwarg, scope_node)) def ARG(self, node): self.addBinding(node, Argument(node.arg, self.getScopeNode(node))) @@ -2220,9 +2115,8 @@ class Checker(object): self.handleNode(deco, node) for baseNode in node.bases: self.handleNode(baseNode, node) - if not PY2: - for keywordNode in node.keywords: - self.handleNode(keywordNode, node) + for keywordNode in node.keywords: + self.handleNode(keywordNode, node) self.pushScope(ClassScope) # doctest does not process doctest within a doctest # classes within classes are processed. @@ -2241,7 +2135,7 @@ class Checker(object): self.handleNode(node.target, node) def TUPLE(self, node): - if not PY2 and isinstance(node.ctx, ast.Store): + if isinstance(node.ctx, ast.Store): # Python 3 advanced tuple unpacking: a, *b, c = d. # Only one starred expression is allowed, and no more than 1<<8 # assignments are allowed before a stared expression. There is @@ -2293,8 +2187,7 @@ class Checker(object): if alias.name == 'annotations': self.annotationsFutureEnabled = True elif alias.name == '*': - # Only Python 2, local import * is a SyntaxWarning - if not PY2 and not isinstance(self.scope, ModuleScope): + if not isinstance(self.scope, ModuleScope): self.report(messages.ImportStarNotPermitted, node, module) continue @@ -2327,10 +2220,10 @@ class Checker(object): # Process the other nodes: "except:", "else:", "finally:" self.handleChildren(node, omit='body') - TRYEXCEPT = TRYSTAR = TRY + TRYSTAR = TRY def EXCEPTHANDLER(self, node): - if PY2 or node.name is None: + if node.name is None: self.handleChildren(node) return diff --git a/pyflakes/messages.py b/pyflakes/messages.py index 5a2f0ce..2d08112 100644 --- a/pyflakes/messages.py +++ b/pyflakes/messages.py @@ -3,18 +3,18 @@ Provide the class Message and its subclasses. """ -class Message(object): +class Message: message = '' message_args = () def __init__(self, filename, loc): self.filename = filename self.lineno = loc.lineno - self.col = getattr(loc, 'col_offset', 0) + self.col = loc.col_offset def __str__(self): - return '%s:%s:%s: %s' % (self.filename, self.lineno, self.col+1, - self.message % self.message_args) + return '{}:{}:{}: {}'.format(self.filename, self.lineno, self.col+1, + self.message % self.message_args) class UnusedImport(Message): @@ -33,14 +33,6 @@ class RedefinedWhileUnused(Message): self.message_args = (name, orig_loc.lineno) -class RedefinedInListComp(Message): - message = 'list comprehension redefines %r from line %r' - - def __init__(self, filename, loc, name, orig_loc): - Message.__init__(self, filename, loc) - self.message_args = (name, orig_loc.lineno) - - class ImportShadowedByLoopVar(Message): message = 'import %r from line %r shadowed by loop variable' @@ -168,13 +160,6 @@ class UnusedVariable(Message): self.message_args = (names,) -class ReturnWithArgsInsideGenerator(Message): - """ - Indicates a return statement with arguments inside a generator. - """ - message = '\'return\' with argument inside generator' - - class ReturnOutsideFunction(Message): """ Indicates a return statement outside of a function/method. diff --git a/pyflakes/reporter.py b/pyflakes/reporter.py index 0faef65..3555185 100644 --- a/pyflakes/reporter.py +++ b/pyflakes/reporter.py @@ -6,7 +6,7 @@ import re import sys -class Reporter(object): +class Reporter: """ Formats the results of pyflakes checks to users. """ @@ -34,7 +34,7 @@ class Reporter(object): @param msg: A message explaining the problem. @ptype msg: C{unicode} """ - self._stderr.write("%s: %s\n" % (filename, msg)) + self._stderr.write(f"{filename}: {msg}\n") def syntaxError(self, filename, msg, lineno, offset, text): """ diff --git a/pyflakes/scripts/pyflakes.py b/pyflakes/scripts/pyflakes.py index 4a18e79..2d6f6aa 100644 --- a/pyflakes/scripts/pyflakes.py +++ b/pyflakes/scripts/pyflakes.py @@ -1,7 +1,6 @@ """ Implementation of the command-line I{pyflakes} tool. """ -from __future__ import absolute_import # For backward compatibility __all__ = ['check', 'checkPath', 'checkRecursive', 'iterSourceCode', 'main'] diff --git a/pyflakes/test/harness.py b/pyflakes/test/harness.py index 1cdb809..9bcc0bb 100644 --- a/pyflakes/test/harness.py +++ b/pyflakes/test/harness.py @@ -29,9 +29,9 @@ class TestCase(unittest.TestCase): expectedOutputs.sort(key=lambda t: t.__name__) self.assertEqual(outputs, expectedOutputs, '''\ for input: -%s +{} expected outputs: -%r +{!r} but got: -%s''' % (input, expectedOutputs, '\n'.join([str(o) for o in w.messages]))) +{}'''.format(input, expectedOutputs, '\n'.join([str(o) for o in w.messages]))) return w diff --git a/pyflakes/test/test_api.py b/pyflakes/test/test_api.py index 3d93591..d0795a0 100644 --- a/pyflakes/test/test_api.py +++ b/pyflakes/test/test_api.py @@ -3,6 +3,7 @@ Tests for L{pyflakes.scripts.pyflakes}. """ import contextlib +import io import os import sys import shutil @@ -20,14 +21,6 @@ from pyflakes.api import ( ) from pyflakes.test.harness import TestCase, skipIf -if sys.version_info < (3,): - from cStringIO import StringIO -else: - from io import StringIO - unichr = chr - -ERROR_HAS_COL_NUM = ERROR_HAS_LAST_LINE = sys.version_info >= (3, 2) or PYPY - def withStderrTo(stderr, f, *args, **kwargs): """ @@ -40,7 +33,7 @@ def withStderrTo(stderr, f, *args, **kwargs): sys.stderr = outer -class Node(object): +class Node: """ Mock an AST node. """ @@ -49,32 +42,14 @@ class Node(object): self.col_offset = col_offset -class SysStreamCapturing(object): - - """ - Context manager capturing sys.stdin, sys.stdout and sys.stderr. +class SysStreamCapturing: + """Context manager capturing sys.stdin, sys.stdout and sys.stderr. The file handles are replaced with a StringIO object. - On environments that support it, the StringIO object uses newlines - set to os.linesep. Otherwise newlines are converted from \\n to - os.linesep during __exit__. """ - def _create_StringIO(self, buffer=None): - # Python 3 has a newline argument - try: - return StringIO(buffer, newline=os.linesep) - except TypeError: - self._newline = True - # Python 2 creates an input only stream when buffer is not None - if buffer is None: - return StringIO() - else: - return StringIO(buffer) - def __init__(self, stdin): - self._newline = False - self._stdin = self._create_StringIO(stdin or '') + self._stdin = io.StringIO(stdin or '', newline=os.linesep) def __enter__(self): self._orig_stdin = sys.stdin @@ -82,8 +57,8 @@ class SysStreamCapturing(object): self._orig_stderr = sys.stderr sys.stdin = self._stdin - sys.stdout = self._stdout_stringio = self._create_StringIO() - sys.stderr = self._stderr_stringio = self._create_StringIO() + sys.stdout = self._stdout_stringio = io.StringIO(newline=os.linesep) + sys.stderr = self._stderr_stringio = io.StringIO(newline=os.linesep) return self @@ -91,16 +66,12 @@ class SysStreamCapturing(object): self.output = self._stdout_stringio.getvalue() self.error = self._stderr_stringio.getvalue() - if self._newline and os.linesep != '\n': - self.output = self.output.replace('\n', os.linesep) - self.error = self.error.replace('\n', os.linesep) - sys.stdin = self._orig_stdin sys.stdout = self._orig_stdout sys.stderr = self._orig_stderr -class LoggingReporter(object): +class LoggingReporter: """ Implementation of Reporter that just appends any error to a list. """ @@ -190,10 +161,6 @@ class TestIterSourceCode(TestCase): with open(os.path.join(self.tempdir, 'c'), 'w') as fd: fd.write('hello\nworld\n') - python2 = os.path.join(self.tempdir, 'd') - with open(python2, 'w') as fd: - fd.write('#!/usr/bin/env python2\n') - python3 = os.path.join(self.tempdir, 'e') with open(python3, 'w') as fd: fd.write('#!/usr/bin/env python3\n') @@ -206,10 +173,6 @@ class TestIterSourceCode(TestCase): with open(python3args, 'w') as fd: fd.write('#!/usr/bin/python3 -u\n') - python2u = os.path.join(self.tempdir, 'h') - with open(python2u, 'w') as fd: - fd.write('#!/usr/bin/python2u\n') - python3d = os.path.join(self.tempdir, 'i') with open(python3d, 'w') as fd: fd.write('#!/usr/local/bin/python3d\n') @@ -218,10 +181,6 @@ class TestIterSourceCode(TestCase): with open(python38m, 'w') as fd: fd.write('#! /usr/bin/env python3.8m\n') - python27 = os.path.join(self.tempdir, 'k') - with open(python27, 'w') as fd: - fd.write('#!/usr/bin/python2.7 \n') - # Should NOT be treated as Python source notfirst = os.path.join(self.tempdir, 'l') with open(notfirst, 'w') as fd: @@ -229,8 +188,10 @@ class TestIterSourceCode(TestCase): self.assertEqual( sorted(iterSourceCode([self.tempdir])), - sorted([python, python2, python3, pythonw, python3args, python2u, - python3d, python38m, python27])) + sorted([ + python, python3, pythonw, python3args, python3d, + python38m, + ])) def test_multipleDirectories(self): """ @@ -269,7 +230,7 @@ class TestReporter(TestCase): number, error message, actual line of source and a caret pointing to where the error is. """ - err = StringIO() + err = io.StringIO() reporter = Reporter(None, err) reporter.syntaxError('foo.py', 'a problem', 3, 8 if sys.version_info >= (3, 8) else 7, @@ -285,7 +246,7 @@ class TestReporter(TestCase): C{syntaxError} doesn't include a caret pointing to the error if C{offset} is passed as C{None}. """ - err = StringIO() + err = io.StringIO() reporter = Reporter(None, err) reporter.syntaxError('foo.py', 'a problem', 3, None, 'bad line of source') @@ -300,7 +261,7 @@ class TestReporter(TestCase): line. The offset is adjusted so that it is relative to the start of the last line. """ - err = StringIO() + err = io.StringIO() lines = [ 'bad line of source', 'more bad lines of source', @@ -319,7 +280,7 @@ class TestReporter(TestCase): """ C{unexpectedError} reports an error processing a source file. """ - err = StringIO() + err = io.StringIO() reporter = Reporter(None, err) reporter.unexpectedError('source.py', 'error message') self.assertEqual('source.py: error message\n', err.getvalue()) @@ -329,11 +290,11 @@ class TestReporter(TestCase): C{flake} reports a code warning from Pyflakes. It is exactly the str() of a L{pyflakes.messages.Message}. """ - out = StringIO() + out = io.StringIO() reporter = Reporter(out, None) message = UnusedImport('foo.py', Node(42), 'bar') reporter.flake(message) - self.assertEqual(out.getvalue(), "%s\n" % (message,)) + self.assertEqual(out.getvalue(), f"{message}\n") class CheckTests(TestCase): @@ -363,7 +324,7 @@ class CheckTests(TestCase): @param path: A path to a file to check. @param errorList: A list of errors expected to be printed to stderr. """ - err = StringIO() + err = io.StringIO() count = withStderrTo(err, checkPath, path) self.assertEqual( (count, err.getvalue()), (len(errorList), ''.join(errorList))) @@ -496,10 +457,10 @@ def baz(): self.assertHasErrors( sourcePath, ["""\ -%s:2:%s: invalid syntax +{}:2:{}: invalid syntax \tfoo = -%s -""" % (sourcePath, column, last_line)]) +{} +""".format(sourcePath, column, last_line)]) def test_nonDefaultFollowsDefaultSyntaxError(self): """ @@ -512,27 +473,24 @@ def foo(bar=baz, bax): pass """ with self.makeTempFile(source) as sourcePath: - if ERROR_HAS_LAST_LINE: - if PYPY: - column = 7 - elif sys.version_info >= (3, 10): - column = 18 - elif sys.version_info >= (3, 9): - column = 21 - elif sys.version_info >= (3, 8): - column = 9 - else: - column = 8 - last_line = ' ' * (column - 1) + '^\n' - columnstr = '%d:' % column + if PYPY: + column = 7 + elif sys.version_info >= (3, 10): + column = 18 + elif sys.version_info >= (3, 9): + column = 21 + elif sys.version_info >= (3, 8): + column = 9 else: - last_line = columnstr = '' + column = 8 + last_line = ' ' * (column - 1) + '^\n' + columnstr = '%d:' % column self.assertHasErrors( sourcePath, ["""\ -%s:1:%s non-default argument follows default argument +{}:1:{} non-default argument follows default argument def foo(bar=baz, bax): -%s""" % (sourcePath, columnstr, last_line)]) +{}""".format(sourcePath, columnstr, last_line)]) def test_nonKeywordAfterKeywordSyntaxError(self): """ @@ -544,59 +502,43 @@ def foo(bar=baz, bax): foo(bar=baz, bax) """ with self.makeTempFile(source) as sourcePath: - if ERROR_HAS_LAST_LINE: - if PYPY: - column = 12 - elif sys.version_info >= (3, 9): - column = 17 - elif sys.version_info >= (3, 8): - column = 14 - else: - column = 13 - last_line = ' ' * (column - 1) + '^\n' - columnstr = '%d:' % column + if PYPY: + column = 12 + elif sys.version_info >= (3, 9): + column = 17 + elif sys.version_info >= (3, 8): + column = 14 else: - last_line = columnstr = '' + column = 13 + last_line = ' ' * (column - 1) + '^\n' + columnstr = '%d:' % column - if sys.version_info >= (3, 5): - message = 'positional argument follows keyword argument' - else: - message = 'non-keyword arg after keyword arg' + message = 'positional argument follows keyword argument' self.assertHasErrors( sourcePath, ["""\ -%s:1:%s %s +{}:1:{} {} foo(bar=baz, bax) -%s""" % (sourcePath, columnstr, message, last_line)]) +{}""".format(sourcePath, columnstr, message, last_line)]) def test_invalidEscape(self): """ The invalid escape syntax raises ValueError in Python 2 """ - ver = sys.version_info # ValueError: invalid \x escape with self.makeTempFile(r"foo = '\xyz'") as sourcePath: - if ver < (3,): - decoding_error = "%s: problem decoding source\n" % (sourcePath,) + position_end = 1 + if PYPY: + column = 5 + elif sys.version_info >= (3, 9): + column = 13 else: - position_end = 1 - if PYPY: - column = 5 - elif ver >= (3, 9): - column = 13 - else: - column = 7 - # Column has been "fixed" since 3.2.4 and 3.3.1 - if ver < (3, 2, 4) or ver[:3] == (3, 3, 0): - position_end = 2 - - if ERROR_HAS_LAST_LINE: - last_line = '%s^\n' % (' ' * (column - 1)) - else: - last_line = '' - - decoding_error = """\ + column = 7 + + last_line = '%s^\n' % (' ' * (column - 1)) + + decoding_error = """\ %s:1:%d: (unicode error) 'unicodeescape' codec can't decode bytes \ in position 0-%d: truncated \\xXX escape foo = '\\xyz' @@ -637,7 +579,7 @@ foo = '\\xyz' """ If source file declares the correct encoding, no error is reported. """ - SNOWMAN = unichr(0x2603) + SNOWMAN = chr(0x2603) source = ("""\ # coding: utf-8 x = "%s" @@ -657,40 +599,28 @@ x = "%s" If a source file contains bytes which cannot be decoded, this is reported on stderr. """ - SNOWMAN = unichr(0x2603) + SNOWMAN = chr(0x2603) source = ("""\ # coding: ascii x = "%s" """ % SNOWMAN).encode('utf-8') with self.makeTempFile(source) as sourcePath: - if PYPY and sys.version_info < (3, ): - message = ('\'ascii\' codec can\'t decode byte 0xe2 ' - 'in position 21: ordinal not in range(128)') - result = """\ -%s:0:0: %s -x = "\xe2\x98\x83" - ^\n""" % (sourcePath, message) - - else: - message = 'problem decoding source' - result = "%s: problem decoding source\n" % (sourcePath,) - self.assertHasErrors( - sourcePath, [result]) + sourcePath, [f"{sourcePath}: problem decoding source\n"]) def test_misencodedFileUTF16(self): """ If a source file contains bytes which cannot be decoded, this is reported on stderr. """ - SNOWMAN = unichr(0x2603) + SNOWMAN = chr(0x2603) source = ("""\ # coding: ascii x = "%s" """ % SNOWMAN).encode('utf-16') with self.makeTempFile(source) as sourcePath: self.assertHasErrors( - sourcePath, ["%s: problem decoding source\n" % (sourcePath,)]) + sourcePath, [f"{sourcePath}: problem decoding source\n"]) def test_checkRecursive(self): """ @@ -702,10 +632,10 @@ x = "%s" os.mkdir(os.path.join(tempdir, 'foo')) file1 = os.path.join(tempdir, 'foo', 'bar.py') with open(file1, 'wb') as fd: - fd.write("import baz\n".encode('ascii')) + fd.write(b"import baz\n") file2 = os.path.join(tempdir, 'baz.py') with open(file2, 'wb') as fd: - fd.write("import contraband".encode('ascii')) + fd.write(b"import contraband") log = [] reporter = LoggingReporter(log) warnings = checkRecursive([tempdir], reporter) @@ -760,9 +690,8 @@ class IntegrationTests(TestCase): stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = p.communicate() rv = p.wait() - if sys.version_info >= (3,): - stdout = stdout.decode('utf-8') - stderr = stderr.decode('utf-8') + stdout = stdout.decode('utf-8') + stderr = stderr.decode('utf-8') return (stdout, stderr, rv) def test_goodFile(self): @@ -780,10 +709,10 @@ class IntegrationTests(TestCase): and the warnings are printed to stdout. """ with open(self.tempfilepath, 'wb') as fd: - fd.write("import contraband\n".encode('ascii')) + fd.write(b"import contraband\n") d = self.runPyflakes([self.tempfilepath]) expected = UnusedImport(self.tempfilepath, Node(1), 'contraband') - self.assertEqual(d, ("%s%s" % (expected, os.linesep), '', 1)) + self.assertEqual(d, (f"{expected}{os.linesep}", '', 1)) def test_errors_io(self): """ @@ -792,8 +721,8 @@ class IntegrationTests(TestCase): printed to stderr. """ d = self.runPyflakes([self.tempfilepath]) - error_msg = '%s: No such file or directory%s' % (self.tempfilepath, - os.linesep) + error_msg = '{}: No such file or directory{}'.format(self.tempfilepath, + os.linesep) self.assertEqual(d, ('', error_msg, 1)) def test_errors_syntax(self): @@ -803,7 +732,7 @@ class IntegrationTests(TestCase): printed to stderr. """ with open(self.tempfilepath, 'wb') as fd: - fd.write("import".encode('ascii')) + fd.write(b"import") d = self.runPyflakes([self.tempfilepath]) error_msg = '{0}:1:{2}: invalid syntax{1}import{1} {3}^{1}'.format( self.tempfilepath, os.linesep, 6 if PYPY else 7, '' if PYPY else ' ') @@ -815,7 +744,7 @@ class IntegrationTests(TestCase): """ d = self.runPyflakes([], stdin='import contraband') expected = UnusedImport('', Node(1), 'contraband') - self.assertEqual(d, ("%s%s" % (expected, os.linesep), '', 1)) + self.assertEqual(d, (f"{expected}{os.linesep}", '', 1)) class TestMain(IntegrationTests): diff --git a/pyflakes/test/test_builtin.py b/pyflakes/test/test_builtin.py index 7150ddb..19fe949 100644 --- a/pyflakes/test/test_builtin.py +++ b/pyflakes/test/test_builtin.py @@ -1,10 +1,8 @@ """ Tests for detecting redefinition of builtins. """ -from sys import version_info - from pyflakes import messages as m -from pyflakes.test.harness import TestCase, skipIf +from pyflakes.test.harness import TestCase class TestBuiltins(TestCase): @@ -30,12 +28,3 @@ class TestBuiltins(TestCase): f() ''') - - @skipIf(version_info >= (3,), 'not an UnboundLocalError in Python 3') - def test_builtin_in_comprehension(self): - self.flakes(''' - def f(): - [range for range in range(1, 10)] - - f() - ''', m.UndefinedLocal) diff --git a/pyflakes/test/test_checker.py b/pyflakes/test/test_checker.py index b527572..66e3501 100644 --- a/pyflakes/test/test_checker.py +++ b/pyflakes/test/test_checker.py @@ -1,8 +1,7 @@ import ast -import sys from pyflakes import checker -from pyflakes.test.harness import TestCase, skipIf +from pyflakes.test.harness import TestCase class TypeableVisitorTests(TestCase): @@ -53,7 +52,6 @@ def f(): self.assertIsInstance(visitor.typeable_nodes[1], ast.FunctionDef) self.assertIsInstance(visitor.typeable_nodes[2], ast.Assign) - @skipIf(sys.version_info < (3, 5), 'async syntax introduced in py35') def test_py35_node_types(self): """ Test that the PEP 492 node types are collected @@ -110,7 +108,7 @@ class CollectTypeCommentsTests(TestCase): """ Test that the function works for text source """ - ret = self._collect(u'x = 1 # type: int') + ret = self._collect('x = 1 # type: int') self.assertEqual(ret, {(ast.Assign, ('# type: int',))}) def test_non_type_comment_ignored(self): diff --git a/pyflakes/test/test_code_segment.py b/pyflakes/test/test_code_segment.py index 131a74d..79532b9 100644 --- a/pyflakes/test/test_code_segment.py +++ b/pyflakes/test/test_code_segment.py @@ -1,9 +1,7 @@ -from sys import version_info - from pyflakes import messages as m from pyflakes.checker import (FunctionScope, ClassScope, ModuleScope, Argument, FunctionDefinition, Assignment) -from pyflakes.test.harness import TestCase, skipIf +from pyflakes.test.harness import TestCase class TestCodeSegments(TestCase): @@ -127,6 +125,5 @@ class TestCodeSegments(TestCase): self.assertIsInstance(function_scope_bar['h'], Argument) self.assertIsInstance(function_scope_bar['i'], Argument) - @skipIf(version_info < (3, 5), 'new in Python 3.5') def test_scope_async_function(self): self.flakes('async def foo(): pass', is_segment=True) diff --git a/pyflakes/test/test_dict.py b/pyflakes/test/test_dict.py index b9059c2..c8ee484 100644 --- a/pyflakes/test/test_dict.py +++ b/pyflakes/test/test_dict.py @@ -2,10 +2,8 @@ Tests for dict duplicate keys Pyflakes behavior. """ -from sys import version_info - from pyflakes import messages as m -from pyflakes.test.harness import TestCase, skipIf +from pyflakes.test.harness import TestCase class Test(TestCase): @@ -17,13 +15,9 @@ class Test(TestCase): m.MultiValueRepeatedKeyLiteral, ) - @skipIf(version_info < (3,), - "bytes and strings with same 'value' are not equal in python3") def test_duplicate_keys_bytes_vs_unicode_py3(self): self.flakes("{b'a': 1, u'a': 2}") - @skipIf(version_info < (3,), - "bytes and strings with same 'value' are not equal in python3") def test_duplicate_values_bytes_vs_unicode_py3(self): self.flakes( "{1: b'a', 1: u'a'}", @@ -31,20 +25,6 @@ class Test(TestCase): m.MultiValueRepeatedKeyLiteral, ) - @skipIf(version_info >= (3,), - "bytes and strings with same 'value' are equal in python2") - def test_duplicate_keys_bytes_vs_unicode_py2(self): - self.flakes( - "{b'a': 1, u'a': 2}", - m.MultiValueRepeatedKeyLiteral, - m.MultiValueRepeatedKeyLiteral, - ) - - @skipIf(version_info >= (3,), - "bytes and strings with same 'value' are equal in python2") - def test_duplicate_values_bytes_vs_unicode_py2(self): - self.flakes("{1: b'a', 1: u'a'}") - def test_multiple_duplicate_keys(self): self.flakes( "{'yes': 1, 'yes': 2, 'no': 2, 'no': 3}", diff --git a/pyflakes/test/test_doctests.py b/pyflakes/test/test_doctests.py index de796b9..197e7fb 100644 --- a/pyflakes/test/test_doctests.py +++ b/pyflakes/test/test_doctests.py @@ -14,7 +14,7 @@ from pyflakes.test.test_undefined_names import Test as TestUndefinedNames from pyflakes.test.harness import TestCase, skip -class _DoctestMixin(object): +class _DoctestMixin: withDoctest = True @@ -43,7 +43,7 @@ class _DoctestMixin(object): return doctestificator % "\n ".join(lines) def flakes(self, input, *args, **kw): - return super(_DoctestMixin, self).flakes(self.doctestify(input), *args, **kw) + return super().flakes(self.doctestify(input), *args, **kw) class Test(TestCase): diff --git a/pyflakes/test/test_imports.py b/pyflakes/test/test_imports.py index 07504d9..fb5d2fd 100644 --- a/pyflakes/test/test_imports.py +++ b/pyflakes/test/test_imports.py @@ -1,5 +1,3 @@ -from sys import version_info - from pyflakes import messages as m from pyflakes.checker import ( FutureImportation, @@ -8,7 +6,7 @@ from pyflakes.checker import ( StarImportation, SubmoduleImportation, ) -from pyflakes.test.harness import TestCase, skip, skipIf +from pyflakes.test.harness import TestCase, skip class TestImportationObject(TestCase): @@ -577,9 +575,8 @@ class Test(TestCase): def test_redefinedByExcept(self): expected = [m.RedefinedWhileUnused] - if version_info >= (3,): - # The exc variable is unused inside the exception handler. - expected.append(m.UnusedVariable) + # The exc variable is unused inside the exception handler. + expected.append(m.UnusedVariable) self.flakes(''' import fu try: pass @@ -624,12 +621,6 @@ class Test(TestCase): self.flakes('import fu; [fu for _ in range(1)]') self.flakes('import fu; [1 for _ in range(1) if fu]') - @skipIf(version_info >= (3,), - 'in Python 3 list comprehensions execute in a separate scope') - def test_redefinedByListComp(self): - self.flakes('import fu; [1 for fu in range(1)]', - m.RedefinedInListComp) - def test_usedInTryFinally(self): self.flakes(''' import fu @@ -686,15 +677,8 @@ class Test(TestCase): def g(): foo.is_used() ''') - @skipIf(version_info >= (3,), 'deprecated syntax') - def test_usedInBackquote(self): - self.flakes('import fu; `fu`') - def test_usedInExec(self): - if version_info < (3,): - exec_stmt = 'exec "print 1" in fu.bar' - else: - exec_stmt = 'exec("print(1)", fu.bar)' + exec_stmt = 'exec("print(1)", fu.bar)' self.flakes('import fu; %s' % exec_stmt) def test_usedInLambda(self): @@ -793,8 +777,6 @@ class Test(TestCase): assert error.message == '%r imported but unused' assert error.message_args == ('from .. import *', ) - @skipIf(version_info < (3,), - 'import * below module level is a warning on Python 2') def test_localImportStar(self): """import * is only allowed at module level.""" self.flakes(''' @@ -814,17 +796,6 @@ class Test(TestCase): assert error.message == "'from %s import *' only allowed at module level" assert error.message_args == ('..', ) - @skipIf(version_info > (3,), - 'import * below module level is an error on Python 3') - def test_importStarNested(self): - """All star imports are marked as used by an undefined variable.""" - self.flakes(''' - from fu import * - def f(): - from bar import * - x - ''', m.ImportStarUsed, m.ImportStarUsed, m.ImportStarUsage) - def test_packageImport(self): """ If a dotted name is imported and used, no warning is reported. diff --git a/pyflakes/test/test_other.py b/pyflakes/test/test_other.py index 97685ef..b138cf6 100644 --- a/pyflakes/test/test_other.py +++ b/pyflakes/test/test_other.py @@ -21,37 +21,6 @@ class Test(TestCase): f() ''', m.UndefinedLocal, m.UnusedVariable) - @skipIf(version_info >= (3,), - 'in Python 3 list comprehensions execute in a separate scope') - def test_redefinedInListComp(self): - """ - Test that shadowing a variable in a list comprehension raises - a warning. - """ - self.flakes(''' - a = 1 - [1 for a, b in [(1, 2)]] - ''', m.RedefinedInListComp) - self.flakes(''' - class A: - a = 1 - [1 for a, b in [(1, 2)]] - ''', m.RedefinedInListComp) - self.flakes(''' - def f(): - a = 1 - [1 for a, b in [(1, 2)]] - ''', m.RedefinedInListComp) - self.flakes(''' - [1 for a, b in [(1, 2)]] - [1 for a, b in [(1, 2)]] - ''') - self.flakes(''' - for a, b in [(1, 2)]: - pass - [1 for a, b in [(1, 2)]] - ''') - def test_redefinedInGenerator(self): """ Test that reusing a variable in a generator does not raise @@ -239,21 +208,6 @@ class Test(TestCase): [a for a in '12'] ''') - @skipIf(version_info >= (3,), - 'in Python 3 list comprehensions execute in a separate scope') - def test_redefinedElseInListComp(self): - """ - Test that shadowing a variable in a list comprehension in - an else (or if) block raises a warning. - """ - self.flakes(''' - if False: - pass - else: - a = 1 - [a for a in '12'] - ''', m.RedefinedInListComp) - def test_functionDecorator(self): """ Test that shadowing a function definition with a decorated version of @@ -394,7 +348,6 @@ class Test(TestCase): yield ''', m.YieldOutsideFunction) - @skipIf(version_info < (3, 3), "Python >= 3.3 only") def test_classWithYieldFrom(self): """ If a yield from is used inside a class, a warning is emitted. @@ -404,7 +357,6 @@ class Test(TestCase): yield from range(10) ''', m.YieldOutsideFunction) - @skipIf(version_info < (3, 3), "Python >= 3.3 only") def test_moduleWithYieldFrom(self): """ If a yield from is used at the module level, a warning is emitted. @@ -903,7 +855,6 @@ class Test(TestCase): pass ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast) - @skipIf(version_info < (3,), "Python 3 only") def test_starredAssignmentNoError(self): """ Python 3 extended iterable unpacking @@ -957,7 +908,6 @@ class Test(TestCase): ", *rest] = range(1<<8)" self.flakes(s) - @skipIf(version_info < (3, ), "Python 3 only") def test_starredAssignmentErrors(self): """ SyntaxErrors (not encoded in the ast) surrounding Python 3 extended @@ -1185,7 +1135,6 @@ class Test(TestCase): pass ''') - @skipIf(version_info < (3, 3), "Python >= 3.3 only") def test_function_arguments_python3(self): self.flakes(''' def foo(a, b, c=0, *args, d=0, **kwargs): @@ -1276,7 +1225,6 @@ class TestUnusedAssignment(TestCase): b = 1 ''') - @skipIf(version_info < (3,), 'new in Python 3') def test_assignToNonlocal(self): """ Assigning to a nonlocal and then not using that binding is perfectly @@ -1660,8 +1608,6 @@ class TestUnusedAssignment(TestCase): except Exception as e: e ''') - @skipIf(version_info < (3,), - "In Python 2 exception names stay bound after the exception handler") def test_exceptionUnusedInExcept(self): self.flakes(''' try: pass @@ -1754,7 +1700,6 @@ class TestUnusedAssignment(TestCase): assert 1 ''') - @skipIf(version_info < (3, 3), 'new in Python 3.3') def test_yieldFromUndefined(self): """ Test C{yield from} statement @@ -1764,7 +1709,6 @@ class TestUnusedAssignment(TestCase): yield from foo() ''', m.UndefinedName) - @skipIf(version_info < (3, 6), 'new in Python 3.6') def test_f_string(self): """Test PEP 498 f-strings are treated as a usage.""" self.flakes(''' @@ -1801,7 +1745,6 @@ class TestUnusedAssignment(TestCase): class TestStringFormatting(TestCase): - @skipIf(version_info < (3, 6), 'new in Python 3.6') def test_f_string_without_placeholders(self): self.flakes("f'foo'", m.FStringMissingPlaceholders) self.flakes(''' @@ -1939,7 +1882,6 @@ class TestStringFormatting(TestCase): '%*.*f' % (5, 2, 3.1234) ''') - @skipIf(version_info < (3, 5), 'new in Python 3.5') def test_ok_percent_format_cannot_determine_element_count(self): self.flakes(''' a = [] @@ -1954,28 +1896,24 @@ class TestStringFormatting(TestCase): class TestAsyncStatements(TestCase): - @skipIf(version_info < (3, 5), 'new in Python 3.5') def test_asyncDef(self): self.flakes(''' async def bar(): return 42 ''') - @skipIf(version_info < (3, 5), 'new in Python 3.5') def test_asyncDefAwait(self): self.flakes(''' async def read_data(db): await db.fetch('SELECT ...') ''') - @skipIf(version_info < (3, 5), 'new in Python 3.5') def test_asyncDefUndefined(self): self.flakes(''' async def bar(): return foo() ''', m.UndefinedName) - @skipIf(version_info < (3, 5), 'new in Python 3.5') def test_asyncFor(self): self.flakes(''' async def read_data(db): @@ -1985,7 +1923,6 @@ class TestAsyncStatements(TestCase): return output ''') - @skipIf(version_info < (3, 5), 'new in Python 3.5') def test_asyncForUnderscoreLoopVar(self): self.flakes(''' async def coro(it): @@ -1993,7 +1930,6 @@ class TestAsyncStatements(TestCase): pass ''') - @skipIf(version_info < (3, 5), 'new in Python 3.5') def test_loopControlInAsyncFor(self): self.flakes(''' async def read_data(db): @@ -2015,7 +1951,6 @@ class TestAsyncStatements(TestCase): return output ''') - @skipIf(version_info < (3, 5), 'new in Python 3.5') def test_loopControlInAsyncForElse(self): self.flakes(''' async def read_data(db): @@ -2037,7 +1972,6 @@ class TestAsyncStatements(TestCase): return output ''', m.BreakOutsideLoop) - @skipIf(version_info < (3, 5), 'new in Python 3.5') @skipIf(version_info > (3, 8), "Python <= 3.8 only") def test_continueInAsyncForFinally(self): self.flakes(''' @@ -2051,7 +1985,6 @@ class TestAsyncStatements(TestCase): return output ''', m.ContinueInFinally) - @skipIf(version_info < (3, 5), 'new in Python 3.5') def test_asyncWith(self): self.flakes(''' async def commit(session, data): @@ -2059,7 +1992,6 @@ class TestAsyncStatements(TestCase): await session.update(data) ''') - @skipIf(version_info < (3, 5), 'new in Python 3.5') def test_asyncWithItem(self): self.flakes(''' async def commit(session, data): @@ -2069,14 +2001,12 @@ class TestAsyncStatements(TestCase): await trans.end() ''') - @skipIf(version_info < (3, 5), 'new in Python 3.5') def test_matmul(self): self.flakes(''' def foo(a, b): return a @ b ''') - @skipIf(version_info < (3, 6), 'new in Python 3.6') def test_formatstring(self): self.flakes(''' hi = 'hi' diff --git a/pyflakes/test/test_return_with_arguments_inside_generator.py b/pyflakes/test/test_return_with_arguments_inside_generator.py deleted file mode 100644 index fc1272a..0000000 --- a/pyflakes/test/test_return_with_arguments_inside_generator.py +++ /dev/null @@ -1,34 +0,0 @@ - -from sys import version_info - -from pyflakes import messages as m -from pyflakes.test.harness import TestCase, skipIf - - -class Test(TestCase): - @skipIf(version_info >= (3, 3), 'new in Python 3.3') - def test_return(self): - self.flakes(''' - class a: - def b(): - for x in a.c: - if x: - yield x - return a - ''', m.ReturnWithArgsInsideGenerator) - - @skipIf(version_info >= (3, 3), 'new in Python 3.3') - def test_returnNone(self): - self.flakes(''' - def a(): - yield 12 - return None - ''', m.ReturnWithArgsInsideGenerator) - - @skipIf(version_info >= (3, 3), 'new in Python 3.3') - def test_returnYieldExpression(self): - self.flakes(''' - def a(): - b = yield a - return b - ''', m.ReturnWithArgsInsideGenerator) diff --git a/pyflakes/test/test_type_annotations.py b/pyflakes/test/test_type_annotations.py index 1caecb4..3775cd3 100644 --- a/pyflakes/test/test_type_annotations.py +++ b/pyflakes/test/test_type_annotations.py @@ -68,7 +68,6 @@ class TestTypeAnnotations(TestCase): return s """) - @skipIf(version_info < (3, 5), 'new in Python 3.5') def test_typingOverloadAsync(self): """Allow intentional redefinitions via @typing.overload (async)""" self.flakes(""" @@ -152,7 +151,6 @@ class TestTypeAnnotations(TestCase): pass """, m.RedefinedWhileUnused) - @skipIf(version_info < (3, 6), 'new in Python 3.6') def test_variable_annotations(self): self.flakes(''' name: str @@ -300,7 +298,6 @@ class TestTypeAnnotations(TestCase): a: 'a: "A"' ''', m.ForwardAnnotationSyntaxError) - @skipIf(version_info < (3, 6), 'new in Python 3.6') def test_TypeAlias_annotations(self): self.flakes(""" from typing_extensions import TypeAlias @@ -340,7 +337,6 @@ class TestTypeAnnotations(TestCase): bar: TypeAlias """, m.UnusedImport) - @skipIf(version_info < (3, 6), 'new in Python 3.6') def test_annotating_an_import(self): self.flakes(''' from a import b, c @@ -348,7 +344,6 @@ class TestTypeAnnotations(TestCase): print(b) ''') - @skipIf(version_info < (3, 6), 'new in Python 3.6') def test_unused_annotation(self): # Unused annotations are fine in module and class scope self.flakes(''' @@ -368,7 +363,6 @@ class TestTypeAnnotations(TestCase): x = 3 ''', m.UnusedVariable) - @skipIf(version_info < (3, 5), 'new in Python 3.5') def test_annotated_async_def(self): self.flakes(''' class c: pass @@ -400,7 +394,6 @@ class TestTypeAnnotations(TestCase): def g(t: 'T'): pass ''') - @skipIf(version_info < (3, 6), 'new in Python 3.6') def test_type_annotation_clobbers_all(self): self.flakes('''\ from typing import TYPE_CHECKING, List @@ -518,13 +511,10 @@ class TestTypeAnnotations(TestCase): """, m.UndefinedName) def test_typeIgnoreBogusUnicode(self): - error = (m.CommentAnnotationSyntaxError if version_info < (3,) - else m.UndefinedName) self.flakes(""" x = 2 # type: ignore\xc3 - """, error) + """, m.UndefinedName) - @skipIf(version_info < (3,), 'new in Python 3') def test_return_annotation_is_class_scope_variable(self): self.flakes(""" from typing import TypeVar @@ -535,7 +525,6 @@ class TestTypeAnnotations(TestCase): return x """) - @skipIf(version_info < (3,), 'new in Python 3') def test_return_annotation_is_function_body_variable(self): self.flakes(""" class Test: @@ -552,7 +541,6 @@ class TestTypeAnnotations(TestCase): def f(c: C, /): ... """) - @skipIf(version_info < (3,), 'new in Python 3') def test_partially_quoted_type_annotation(self): self.flakes(""" from queue import Queue @@ -617,7 +605,6 @@ class TestTypeAnnotations(TestCase): S = TypeVar('S', int, bound='List[int]') """) - @skipIf(version_info < (3,), 'new in Python 3') def test_literal_type_typing(self): self.flakes(""" from typing import Literal @@ -626,7 +613,6 @@ class TestTypeAnnotations(TestCase): return None """) - @skipIf(version_info < (3,), 'new in Python 3') def test_literal_type_typing_extensions(self): self.flakes(""" from typing_extensions import Literal @@ -635,7 +621,6 @@ class TestTypeAnnotations(TestCase): return None """) - @skipIf(version_info < (3,), 'new in Python 3') def test_annotated_type_typing_missing_forward_type(self): self.flakes(""" from typing import Annotated @@ -644,7 +629,6 @@ class TestTypeAnnotations(TestCase): return None """, m.UndefinedName) - @skipIf(version_info < (3,), 'new in Python 3') def test_annotated_type_typing_missing_forward_type_multiple_args(self): self.flakes(""" from typing import Annotated @@ -653,7 +637,6 @@ class TestTypeAnnotations(TestCase): return None """, m.UndefinedName) - @skipIf(version_info < (3,), 'new in Python 3') def test_annotated_type_typing_with_string_args(self): self.flakes(""" from typing import Annotated @@ -662,7 +645,6 @@ class TestTypeAnnotations(TestCase): return None """) - @skipIf(version_info < (3,), 'new in Python 3') def test_annotated_type_typing_with_string_args_in_union(self): self.flakes(""" from typing import Annotated, Union @@ -671,7 +653,6 @@ class TestTypeAnnotations(TestCase): return None """, m.UndefinedName) - @skipIf(version_info < (3,), 'new in Python 3') def test_literal_type_some_other_module(self): """err on the side of false-negatives for types named Literal""" self.flakes(""" @@ -684,7 +665,6 @@ class TestTypeAnnotations(TestCase): return None """) - @skipIf(version_info < (3,), 'new in Python 3') def test_literal_union_type_typing(self): self.flakes(""" from typing import Literal @@ -693,7 +673,6 @@ class TestTypeAnnotations(TestCase): return None """) - @skipIf(version_info < (3,), 'new in Python 3') def test_deferred_twice_annotation(self): self.flakes(""" from queue import Queue @@ -791,7 +770,6 @@ class TestTypeAnnotations(TestCase): cast(A["E"], []) """) - @skipIf(version_info < (3, 6), 'new in Python 3.6') def test_namedtypes_classes(self): self.flakes(""" from typing import TypedDict, NamedTuple diff --git a/pyflakes/test/test_undefined_names.py b/pyflakes/test/test_undefined_names.py index e0e628d..f3b89ea 100644 --- a/pyflakes/test/test_undefined_names.py +++ b/pyflakes/test/test_undefined_names.py @@ -1,8 +1,7 @@ import ast -from sys import version_info from pyflakes import messages as m, checker -from pyflakes.test.harness import TestCase, skipIf, skip +from pyflakes.test.harness import TestCase, skip class Test(TestCase): @@ -12,8 +11,6 @@ class Test(TestCase): def test_definedInListComp(self): self.flakes('[a for a in range(10) if a]') - @skipIf(version_info < (3,), - 'in Python 2 list comprehensions execute in the same scope') def test_undefinedInListComp(self): self.flakes(''' [a for a in range(10)] @@ -21,8 +18,6 @@ class Test(TestCase): ''', m.UndefinedName) - @skipIf(version_info < (3,), - 'in Python 2 exception names stay bound after the except: block') def test_undefinedExceptionName(self): """Exception names can't be used after the except: block. @@ -65,14 +60,12 @@ class Test(TestCase): ''', m.UndefinedName) - @skipIf(version_info < (3,), - 'in Python 2 exception names stay bound after the except: block') def test_undefinedExceptionNameObscuringLocalVariable2(self): """Exception names are unbound after the `except:` block. - Last line will raise UnboundLocalError on Python 3 but would print out - 've' on Python 2. The exc variable is unused inside the exception - handler.""" + Last line will raise UnboundLocalError. + The exc variable is unused inside the exception handler. + """ self.flakes(''' try: raise ValueError('ve') @@ -87,8 +80,6 @@ class Test(TestCase): Last line will never raise UnboundLocalError because it's only entered if no exception was raised.""" - # The exc variable is unused inside the exception handler. - expected = [] if version_info < (3,) else [m.UnusedVariable] self.flakes(''' exc = 'Original value' try: @@ -97,7 +88,7 @@ class Test(TestCase): print('exception logged') raise exc - ''', *expected) + ''', m.UnusedVariable) def test_delExceptionInExcept(self): """The exception name can be deleted in the except: block.""" @@ -113,8 +104,6 @@ class Test(TestCase): Last line will never raise UnboundLocalError because `error` is only falsy if the `except:` block has not been entered.""" - # The exc variable is unused inside the exception handler. - expected = [] if version_info < (3,) else [m.UnusedVariable] self.flakes(''' exc = 'Original value' error = None @@ -126,15 +115,15 @@ class Test(TestCase): print(error) else: exc - ''', *expected) + ''', m.UnusedVariable) @skip('error reporting disabled due to false positives below') def test_undefinedExceptionNameObscuringGlobalVariable(self): """Exception names obscure globals, can't be used after. - Last line will raise UnboundLocalError on both Python 2 and - Python 3 because the existence of that exception name creates - a local scope placeholder for it, obscuring any globals, etc.""" + Last line will raise UnboundLocalError because the existence of that + exception name creates a local scope placeholder for it, obscuring any + globals, etc.""" self.flakes(''' exc = 'Original value' def func(): @@ -172,8 +161,6 @@ class Test(TestCase): Last line will never raise NameError because it's only entered if no exception was raised.""" - # The exc variable is unused inside the exception handler. - expected = [] if version_info < (3,) else [m.UnusedVariable] self.flakes(''' exc = 'Original value' def func(): @@ -184,15 +171,13 @@ class Test(TestCase): print('exception logged') raise exc - ''', *expected) + ''', m.UnusedVariable) def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive2(self): """Exception names obscure globals, can't be used after. Unless. Last line will never raise NameError because `error` is only falsy if the `except:` block has not been entered.""" - # The exc variable is unused inside the exception handler. - expected = [] if version_info < (3,) else [m.UnusedVariable] self.flakes(''' exc = 'Original value' def func(): @@ -206,7 +191,7 @@ class Test(TestCase): print(error) else: exc - ''', *expected) + ''', m.UnusedVariable) def test_functionsNeedGlobalScope(self): self.flakes(''' @@ -226,7 +211,6 @@ class Test(TestCase): """ self.flakes('WindowsError') - @skipIf(version_info < (3, 6), 'new feature in 3.6') def test_moduleAnnotations(self): """ Use of the C{__annotations__} in module scope should not emit @@ -279,7 +263,6 @@ class Test(TestCase): __module__ ''', m.UndefinedName) - @skipIf(version_info < (3, 3), "Python >= 3.3 only") def test_magicQualnameInClassScope(self): """ Use of the C{__qualname__} magic builtin should not emit an undefined @@ -301,26 +284,6 @@ class Test(TestCase): self.flakes('from fu import *; bar', m.ImportStarUsed, m.ImportStarUsage) - @skipIf(version_info >= (3,), 'obsolete syntax') - def test_localImportStar(self): - """ - A local import * still allows undefined names to be found - in upper scopes. - """ - self.flakes(''' - def a(): - from fu import * - bar - ''', m.ImportStarUsed, m.UndefinedName, m.UnusedImport) - - @skipIf(version_info >= (3,), 'obsolete syntax') - def test_unpackedParameter(self): - """Unpacked function parameters create bindings.""" - self.flakes(''' - def a((bar, baz)): - bar; baz - ''') - def test_definedByGlobal(self): """ "global" can make an otherwise undefined name in another function @@ -608,7 +571,6 @@ class Test(TestCase): print(a, b, c) ''') - @skipIf(version_info < (3,), 'new in Python 3') def test_definedAsStarUnpack(self): """Star names in unpack are defined.""" self.flakes(''' @@ -624,7 +586,6 @@ class Test(TestCase): print(a, b, c) ''') - @skipIf(version_info < (3,), 'new in Python 3') def test_usedAsStarUnpack(self): """ Star names in unpack are used if RHS is not a tuple/list literal. @@ -642,7 +603,6 @@ class Test(TestCase): [a, *b, c] = range(10) ''') - @skipIf(version_info < (3,), 'new in Python 3') def test_unusedAsStarUnpack(self): """ Star names in unpack are unused if RHS is a tuple/list literal. @@ -660,7 +620,6 @@ class Test(TestCase): [a, *b, c] = 9, 8, 7, 6, 5, 4 ''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable) - @skipIf(version_info < (3,), 'new in Python 3') def test_keywordOnlyArgs(self): """Keyword-only arg names are defined.""" self.flakes(''' @@ -674,7 +633,6 @@ class Test(TestCase): print(a, b) ''') - @skipIf(version_info < (3,), 'new in Python 3') def test_keywordOnlyArgsUndefined(self): """Typo in kwonly name.""" self.flakes(''' @@ -682,7 +640,6 @@ class Test(TestCase): print(a, b) ''', m.UndefinedName) - @skipIf(version_info < (3,), 'new in Python 3') def test_annotationUndefined(self): """Undefined annotations.""" self.flakes(''' @@ -697,7 +654,6 @@ class Test(TestCase): def func(a: {1, d}) -> (lambda c: e): pass ''') - @skipIf(version_info < (3,), 'new in Python 3') def test_metaClassUndefined(self): self.flakes(''' from abc import ABCMeta @@ -837,10 +793,6 @@ class Test(TestCase): ''', m.UndefinedName) def test_dunderClass(self): - """ - `__class__` is defined in class scope under Python 3, but is not - in Python 2. - """ code = ''' class Test(object): def __init__(self): @@ -849,10 +801,7 @@ class Test(TestCase): t = Test() ''' - if version_info < (3,): - self.flakes(code, m.UndefinedName) - else: - self.flakes(code) + self.flakes(code) class NameTests(TestCase): -- cgit v1.2.1