Add unit tests for calculator
This commit is contained in:
1 parent
05bfcd65df
commit
2e854cbeb6
2227 files changed
+408232
-1
No files matched your search
@@ -0,0 +1 @@
|
||||
__version__ = '3.4.0'
|
||||
@@ -0,0 +1,5 @@
|
||||
from pyflakes.api import main
|
||||
|
||||
# python -m pyflakes
|
||||
if __name__ == '__main__':
|
||||
main(prog='pyflakes')
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,185 @@
|
||||
"""
|
||||
API for the command-line I{pyflakes} tool.
|
||||
"""
|
||||
import ast
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pyflakes import checker, __version__
|
||||
from pyflakes import reporter as modReporter
|
||||
|
||||
__all__ = ['check', 'checkPath', 'checkRecursive', 'iterSourceCode', 'main']
|
||||
|
||||
PYTHON_SHEBANG_REGEX = re.compile(br'^#!.*\bpython(3(\.\d+)?|w)?[dmu]?\s')
|
||||
|
||||
|
||||
def check(codeString, filename, reporter=None):
|
||||
"""
|
||||
Check the Python source given by C{codeString} for flakes.
|
||||
|
||||
@param codeString: The Python source to check.
|
||||
@type codeString: C{str}
|
||||
|
||||
@param filename: The name of the file the source came from, used to report
|
||||
errors.
|
||||
@type filename: C{str}
|
||||
|
||||
@param reporter: A L{Reporter} instance, where errors and warnings will be
|
||||
reported.
|
||||
|
||||
@return: The number of warnings emitted.
|
||||
@rtype: C{int}
|
||||
"""
|
||||
if reporter is None:
|
||||
reporter = modReporter._makeDefaultReporter()
|
||||
# First, compile into an AST and handle syntax errors.
|
||||
try:
|
||||
tree = ast.parse(codeString, filename=filename)
|
||||
except SyntaxError as e:
|
||||
reporter.syntaxError(filename, e.args[0], e.lineno, e.offset, e.text)
|
||||
return 1
|
||||
except Exception:
|
||||
reporter.unexpectedError(filename, 'problem decoding source')
|
||||
return 1
|
||||
# Okay, it's syntactically valid. Now check it.
|
||||
w = checker.Checker(tree, filename=filename)
|
||||
w.messages.sort(key=lambda m: m.lineno)
|
||||
for warning in w.messages:
|
||||
reporter.flake(warning)
|
||||
return len(w.messages)
|
||||
|
||||
|
||||
def checkPath(filename, reporter=None):
|
||||
"""
|
||||
Check the given path, printing out any warnings detected.
|
||||
|
||||
@param reporter: A L{Reporter} instance, where errors and warnings will be
|
||||
reported.
|
||||
|
||||
@return: the number of warnings printed
|
||||
"""
|
||||
if reporter is None:
|
||||
reporter = modReporter._makeDefaultReporter()
|
||||
try:
|
||||
with open(filename, 'rb') as f:
|
||||
codestr = f.read()
|
||||
except OSError as e:
|
||||
reporter.unexpectedError(filename, e.args[1])
|
||||
return 1
|
||||
return check(codestr, filename, reporter)
|
||||
|
||||
|
||||
def isPythonFile(filename):
|
||||
"""Return True if filename points to a Python file."""
|
||||
if filename.endswith('.py'):
|
||||
return True
|
||||
|
||||
# Avoid obvious Emacs backup files
|
||||
if filename.endswith("~"):
|
||||
return False
|
||||
|
||||
max_bytes = 128
|
||||
|
||||
try:
|
||||
with open(filename, 'rb') as f:
|
||||
text = f.read(max_bytes)
|
||||
if not text:
|
||||
return False
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
return PYTHON_SHEBANG_REGEX.match(text)
|
||||
|
||||
|
||||
def iterSourceCode(paths):
|
||||
"""
|
||||
Iterate over all Python source files in C{paths}.
|
||||
|
||||
@param paths: A list of paths. Directories will be recursed into and
|
||||
any .py files found will be yielded. Any non-directories will be
|
||||
yielded as-is.
|
||||
"""
|
||||
for path in paths:
|
||||
if os.path.isdir(path):
|
||||
for dirpath, dirnames, filenames in os.walk(path):
|
||||
for filename in filenames:
|
||||
full_path = os.path.join(dirpath, filename)
|
||||
if isPythonFile(full_path):
|
||||
yield full_path
|
||||
else:
|
||||
yield path
|
||||
|
||||
|
||||
def checkRecursive(paths, reporter):
|
||||
"""
|
||||
Recursively check all source files in C{paths}.
|
||||
|
||||
@param paths: A list of paths to Python source files and directories
|
||||
containing Python source files.
|
||||
@param reporter: A L{Reporter} where all of the warnings and errors
|
||||
will be reported to.
|
||||
@return: The number of warnings found.
|
||||
"""
|
||||
warnings = 0
|
||||
for sourcePath in iterSourceCode(paths):
|
||||
warnings += checkPath(sourcePath, reporter)
|
||||
return warnings
|
||||
|
||||
|
||||
def _exitOnSignal(sigName, message):
|
||||
"""Handles a signal with sys.exit.
|
||||
|
||||
Some of these signals (SIGPIPE, for example) don't exist or are invalid on
|
||||
Windows. So, ignore errors that might arise.
|
||||
"""
|
||||
import signal
|
||||
|
||||
try:
|
||||
sigNumber = getattr(signal, sigName)
|
||||
except AttributeError:
|
||||
# the signal constants defined in the signal module are defined by
|
||||
# whether the C library supports them or not. So, SIGPIPE might not
|
||||
# even be defined.
|
||||
return
|
||||
|
||||
def handler(sig, f):
|
||||
sys.exit(message)
|
||||
|
||||
try:
|
||||
signal.signal(sigNumber, handler)
|
||||
except ValueError:
|
||||
# It's also possible the signal is defined, but then it's invalid. In
|
||||
# this case, signal.signal raises ValueError.
|
||||
pass
|
||||
|
||||
|
||||
def _get_version():
|
||||
"""
|
||||
Retrieve and format package version along with python version & OS used
|
||||
"""
|
||||
return ('%s Python %s on %s' %
|
||||
(__version__, platform.python_version(), platform.system()))
|
||||
|
||||
|
||||
def main(prog=None, args=None):
|
||||
"""Entry point for the script "pyflakes"."""
|
||||
import argparse
|
||||
|
||||
# Handle "Keyboard Interrupt" and "Broken pipe" gracefully
|
||||
_exitOnSignal('SIGINT', '... stopped')
|
||||
_exitOnSignal('SIGPIPE', 1)
|
||||
|
||||
parser = argparse.ArgumentParser(prog=prog,
|
||||
description='Check Python source files for errors')
|
||||
parser.add_argument('-V', '--version', action='version', version=_get_version())
|
||||
parser.add_argument('path', nargs='*',
|
||||
help='Path(s) of Python file(s) to check. STDIN if not given.')
|
||||
args = parser.parse_args(args=args).path
|
||||
reporter = modReporter._makeDefaultReporter()
|
||||
if args:
|
||||
warnings = checkRecursive(args, reporter)
|
||||
else:
|
||||
warnings = check(sys.stdin.read(), '<stdin>', reporter)
|
||||
raise SystemExit(warnings > 0)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,362 @@
|
||||
"""
|
||||
Provide the class Message and its subclasses.
|
||||
"""
|
||||
|
||||
|
||||
class Message:
|
||||
message = ''
|
||||
message_args = ()
|
||||
|
||||
def __init__(self, filename, loc):
|
||||
self.filename = filename
|
||||
self.lineno = loc.lineno
|
||||
self.col = loc.col_offset
|
||||
|
||||
def __str__(self):
|
||||
return '{}:{}:{}: {}'.format(self.filename, self.lineno, self.col+1,
|
||||
self.message % self.message_args)
|
||||
|
||||
|
||||
class UnusedImport(Message):
|
||||
message = '%r imported but unused'
|
||||
|
||||
def __init__(self, filename, loc, name):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (name,)
|
||||
|
||||
|
||||
class RedefinedWhileUnused(Message):
|
||||
message = 'redefinition of unused %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'
|
||||
|
||||
def __init__(self, filename, loc, name, orig_loc):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (name, orig_loc.lineno)
|
||||
|
||||
|
||||
class ImportStarNotPermitted(Message):
|
||||
message = "'from %s import *' only allowed at module level"
|
||||
|
||||
def __init__(self, filename, loc, modname):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (modname,)
|
||||
|
||||
|
||||
class ImportStarUsed(Message):
|
||||
message = "'from %s import *' used; unable to detect undefined names"
|
||||
|
||||
def __init__(self, filename, loc, modname):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (modname,)
|
||||
|
||||
|
||||
class ImportStarUsage(Message):
|
||||
message = "%r may be undefined, or defined from star imports: %s"
|
||||
|
||||
def __init__(self, filename, loc, name, from_list):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (name, from_list)
|
||||
|
||||
|
||||
class UndefinedName(Message):
|
||||
message = 'undefined name %r'
|
||||
|
||||
def __init__(self, filename, loc, name):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (name,)
|
||||
|
||||
|
||||
class DoctestSyntaxError(Message):
|
||||
message = 'syntax error in doctest'
|
||||
|
||||
def __init__(self, filename, loc, position=None):
|
||||
Message.__init__(self, filename, loc)
|
||||
if position:
|
||||
(self.lineno, self.col) = position
|
||||
self.message_args = ()
|
||||
|
||||
|
||||
class UndefinedExport(Message):
|
||||
message = 'undefined name %r in __all__'
|
||||
|
||||
def __init__(self, filename, loc, name):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (name,)
|
||||
|
||||
|
||||
class UndefinedLocal(Message):
|
||||
message = 'local variable %r {0} referenced before assignment'
|
||||
|
||||
default = 'defined in enclosing scope on line %r'
|
||||
builtin = 'defined as a builtin'
|
||||
|
||||
def __init__(self, filename, loc, name, orig_loc):
|
||||
Message.__init__(self, filename, loc)
|
||||
if orig_loc is None:
|
||||
self.message = self.message.format(self.builtin)
|
||||
self.message_args = name
|
||||
else:
|
||||
self.message = self.message.format(self.default)
|
||||
self.message_args = (name, orig_loc.lineno)
|
||||
|
||||
|
||||
class DuplicateArgument(Message):
|
||||
message = 'duplicate argument %r in function definition'
|
||||
|
||||
def __init__(self, filename, loc, name):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (name,)
|
||||
|
||||
|
||||
class MultiValueRepeatedKeyLiteral(Message):
|
||||
message = 'dictionary key %r repeated with different values'
|
||||
|
||||
def __init__(self, filename, loc, key):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (key,)
|
||||
|
||||
|
||||
class MultiValueRepeatedKeyVariable(Message):
|
||||
message = 'dictionary key variable %s repeated with different values'
|
||||
|
||||
def __init__(self, filename, loc, key):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (key,)
|
||||
|
||||
|
||||
class LateFutureImport(Message):
|
||||
message = 'from __future__ imports must occur at the beginning of the file'
|
||||
|
||||
|
||||
class FutureFeatureNotDefined(Message):
|
||||
"""An undefined __future__ feature name was imported."""
|
||||
message = 'future feature %s is not defined'
|
||||
|
||||
def __init__(self, filename, loc, name):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (name,)
|
||||
|
||||
|
||||
class UnusedVariable(Message):
|
||||
"""
|
||||
Indicates that a variable has been explicitly assigned to but not actually
|
||||
used.
|
||||
"""
|
||||
message = 'local variable %r is assigned to but never used'
|
||||
|
||||
def __init__(self, filename, loc, names):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (names,)
|
||||
|
||||
|
||||
class UnusedAnnotation(Message):
|
||||
"""
|
||||
Indicates that a variable has been explicitly annotated to but not actually
|
||||
used.
|
||||
"""
|
||||
message = 'local variable %r is annotated but never used'
|
||||
|
||||
def __init__(self, filename, loc, names):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (names,)
|
||||
|
||||
|
||||
class UnusedIndirectAssignment(Message):
|
||||
"""A `global` or `nonlocal` statement where the name is never reassigned"""
|
||||
message = '`%s %s` is unused: name is never assigned in scope'
|
||||
|
||||
def __init__(self, filename, loc, name):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (type(loc).__name__.lower(), name)
|
||||
|
||||
|
||||
class ReturnOutsideFunction(Message):
|
||||
"""
|
||||
Indicates a return statement outside of a function/method.
|
||||
"""
|
||||
message = '\'return\' outside function'
|
||||
|
||||
|
||||
class YieldOutsideFunction(Message):
|
||||
"""
|
||||
Indicates a yield or yield from statement outside of a function/method.
|
||||
"""
|
||||
message = '\'yield\' outside function'
|
||||
|
||||
|
||||
# For whatever reason, Python gives different error messages for these two. We
|
||||
# match the Python error message exactly.
|
||||
class ContinueOutsideLoop(Message):
|
||||
"""
|
||||
Indicates a continue statement outside of a while or for loop.
|
||||
"""
|
||||
message = '\'continue\' not properly in loop'
|
||||
|
||||
|
||||
class BreakOutsideLoop(Message):
|
||||
"""
|
||||
Indicates a break statement outside of a while or for loop.
|
||||
"""
|
||||
message = '\'break\' outside loop'
|
||||
|
||||
|
||||
class DefaultExceptNotLast(Message):
|
||||
"""
|
||||
Indicates an except: block as not the last exception handler.
|
||||
"""
|
||||
message = 'default \'except:\' must be last'
|
||||
|
||||
|
||||
class TwoStarredExpressions(Message):
|
||||
"""
|
||||
Two or more starred expressions in an assignment (a, *b, *c = d).
|
||||
"""
|
||||
message = 'two starred expressions in assignment'
|
||||
|
||||
|
||||
class TooManyExpressionsInStarredAssignment(Message):
|
||||
"""
|
||||
Too many expressions in an assignment with star-unpacking
|
||||
"""
|
||||
message = 'too many expressions in star-unpacking assignment'
|
||||
|
||||
|
||||
class IfTuple(Message):
|
||||
"""
|
||||
Conditional test is a non-empty tuple literal, which are always True.
|
||||
"""
|
||||
message = '\'if tuple literal\' is always true, perhaps remove accidental comma?'
|
||||
|
||||
|
||||
class AssertTuple(Message):
|
||||
"""
|
||||
Assertion test is a non-empty tuple literal, which are always True.
|
||||
"""
|
||||
message = 'assertion is always true, perhaps remove parentheses?'
|
||||
|
||||
|
||||
class ForwardAnnotationSyntaxError(Message):
|
||||
message = 'syntax error in forward annotation %r'
|
||||
|
||||
def __init__(self, filename, loc, annotation):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (annotation,)
|
||||
|
||||
|
||||
class RaiseNotImplemented(Message):
|
||||
message = "'raise NotImplemented' should be 'raise NotImplementedError'"
|
||||
|
||||
|
||||
class InvalidPrintSyntax(Message):
|
||||
message = 'use of >> is invalid with print function'
|
||||
|
||||
|
||||
class IsLiteral(Message):
|
||||
message = 'use ==/!= to compare constant literals (str, bytes, int, float, tuple)'
|
||||
|
||||
|
||||
class FStringMissingPlaceholders(Message):
|
||||
message = 'f-string is missing placeholders'
|
||||
|
||||
|
||||
class TStringMissingPlaceholders(Message):
|
||||
message = 't-string is missing placeholders'
|
||||
|
||||
|
||||
class StringDotFormatExtraPositionalArguments(Message):
|
||||
message = "'...'.format(...) has unused arguments at position(s): %s"
|
||||
|
||||
def __init__(self, filename, loc, extra_positions):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (extra_positions,)
|
||||
|
||||
|
||||
class StringDotFormatExtraNamedArguments(Message):
|
||||
message = "'...'.format(...) has unused named argument(s): %s"
|
||||
|
||||
def __init__(self, filename, loc, extra_keywords):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (extra_keywords,)
|
||||
|
||||
|
||||
class StringDotFormatMissingArgument(Message):
|
||||
message = "'...'.format(...) is missing argument(s) for placeholder(s): %s"
|
||||
|
||||
def __init__(self, filename, loc, missing_arguments):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (missing_arguments,)
|
||||
|
||||
|
||||
class StringDotFormatMixingAutomatic(Message):
|
||||
message = "'...'.format(...) mixes automatic and manual numbering"
|
||||
|
||||
|
||||
class StringDotFormatInvalidFormat(Message):
|
||||
message = "'...'.format(...) has invalid format string: %s"
|
||||
|
||||
def __init__(self, filename, loc, error):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (error,)
|
||||
|
||||
|
||||
class PercentFormatInvalidFormat(Message):
|
||||
message = "'...' %% ... has invalid format string: %s"
|
||||
|
||||
def __init__(self, filename, loc, error):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (error,)
|
||||
|
||||
|
||||
class PercentFormatMixedPositionalAndNamed(Message):
|
||||
message = "'...' %% ... has mixed positional and named placeholders"
|
||||
|
||||
|
||||
class PercentFormatUnsupportedFormatCharacter(Message):
|
||||
message = "'...' %% ... has unsupported format character %r"
|
||||
|
||||
def __init__(self, filename, loc, c):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (c,)
|
||||
|
||||
|
||||
class PercentFormatPositionalCountMismatch(Message):
|
||||
message = "'...' %% ... has %d placeholder(s) but %d substitution(s)"
|
||||
|
||||
def __init__(self, filename, loc, n_placeholders, n_substitutions):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (n_placeholders, n_substitutions)
|
||||
|
||||
|
||||
class PercentFormatExtraNamedArguments(Message):
|
||||
message = "'...' %% ... has unused named argument(s): %s"
|
||||
|
||||
def __init__(self, filename, loc, extra_keywords):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (extra_keywords,)
|
||||
|
||||
|
||||
class PercentFormatMissingArgument(Message):
|
||||
message = "'...' %% ... is missing argument(s) for placeholder(s): %s"
|
||||
|
||||
def __init__(self, filename, loc, missing_arguments):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (missing_arguments,)
|
||||
|
||||
|
||||
class PercentFormatExpectedMapping(Message):
|
||||
message = "'...' %% ... expected mapping but got sequence"
|
||||
|
||||
|
||||
class PercentFormatExpectedSequence(Message):
|
||||
message = "'...' %% ... expected sequence but got mapping"
|
||||
|
||||
|
||||
class PercentFormatStarRequiresSequence(Message):
|
||||
message = "'...' %% ... `*` specifier requires sequence"
|
||||
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Provide the Reporter class.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
class Reporter:
|
||||
"""
|
||||
Formats the results of pyflakes checks to users.
|
||||
"""
|
||||
|
||||
def __init__(self, warningStream, errorStream):
|
||||
"""
|
||||
Construct a L{Reporter}.
|
||||
|
||||
@param warningStream: A file-like object where warnings will be
|
||||
written to. The stream's C{write} method must accept unicode.
|
||||
C{sys.stdout} is a good value.
|
||||
@param errorStream: A file-like object where error output will be
|
||||
written to. The stream's C{write} method must accept unicode.
|
||||
C{sys.stderr} is a good value.
|
||||
"""
|
||||
self._stdout = warningStream
|
||||
self._stderr = errorStream
|
||||
|
||||
def unexpectedError(self, filename, msg):
|
||||
"""
|
||||
An unexpected error occurred trying to process C{filename}.
|
||||
|
||||
@param filename: The path to a file that we could not process.
|
||||
@ptype filename: C{unicode}
|
||||
@param msg: A message explaining the problem.
|
||||
@ptype msg: C{unicode}
|
||||
"""
|
||||
self._stderr.write(f"{filename}: {msg}\n")
|
||||
|
||||
def syntaxError(self, filename, msg, lineno, offset, text):
|
||||
"""
|
||||
There was a syntax error in C{filename}.
|
||||
|
||||
@param filename: The path to the file with the syntax error.
|
||||
@ptype filename: C{unicode}
|
||||
@param msg: An explanation of the syntax error.
|
||||
@ptype msg: C{unicode}
|
||||
@param lineno: The line number where the syntax error occurred.
|
||||
@ptype lineno: C{int}
|
||||
@param offset: The column on which the syntax error occurred, or None.
|
||||
@ptype offset: C{int}
|
||||
@param text: The source code containing the syntax error.
|
||||
@ptype text: C{unicode}
|
||||
"""
|
||||
if text is None:
|
||||
line = None
|
||||
else:
|
||||
line = text.splitlines()[-1]
|
||||
|
||||
# lineno might be None if the error was during tokenization
|
||||
# lineno might be 0 if the error came from stdin
|
||||
lineno = max(lineno or 0, 1)
|
||||
|
||||
if offset is not None:
|
||||
# some versions of python emit an offset of -1 for certain encoding errors
|
||||
offset = max(offset, 1)
|
||||
self._stderr.write('%s:%d:%d: %s\n' %
|
||||
(filename, lineno, offset, msg))
|
||||
else:
|
||||
self._stderr.write('%s:%d: %s\n' % (filename, lineno, msg))
|
||||
|
||||
if line is not None:
|
||||
self._stderr.write(line)
|
||||
self._stderr.write('\n')
|
||||
if offset is not None:
|
||||
self._stderr.write(re.sub(r'\S', ' ', line[:offset - 1]) +
|
||||
"^\n")
|
||||
|
||||
def flake(self, message):
|
||||
"""
|
||||
pyflakes found something wrong with the code.
|
||||
|
||||
@param: A L{pyflakes.messages.Message}.
|
||||
"""
|
||||
self._stdout.write(str(message))
|
||||
self._stdout.write('\n')
|
||||
|
||||
|
||||
def _makeDefaultReporter():
|
||||
"""
|
||||
Make a reporter that can be used when no reporter is specified.
|
||||
"""
|
||||
return Reporter(sys.stdout, sys.stderr)
|
||||
Whitespace-only changes.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
Implementation of the command-line I{pyflakes} tool.
|
||||
"""
|
||||
|
||||
# For backward compatibility
|
||||
__all__ = ['check', 'checkPath', 'checkRecursive', 'iterSourceCode', 'main']
|
||||
from pyflakes.api import check, checkPath, checkRecursive, iterSourceCode, main
|
||||
Whitespace-only changes.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,34 @@
|
||||
import ast
|
||||
import textwrap
|
||||
import unittest
|
||||
|
||||
from pyflakes import checker
|
||||
|
||||
__all__ = ['TestCase', 'skip', 'skipIf']
|
||||
|
||||
skip = unittest.skip
|
||||
skipIf = unittest.skipIf
|
||||
|
||||
|
||||
class TestCase(unittest.TestCase):
|
||||
|
||||
withDoctest = False
|
||||
|
||||
def flakes(self, input, *expectedOutputs, **kw):
|
||||
tree = ast.parse(textwrap.dedent(input))
|
||||
if kw.get('is_segment'):
|
||||
tree = tree.body[0]
|
||||
kw.pop('is_segment')
|
||||
w = checker.Checker(tree, withDoctest=self.withDoctest, **kw)
|
||||
outputs = [type(o) for o in w.messages]
|
||||
expectedOutputs = list(expectedOutputs)
|
||||
outputs.sort(key=lambda t: t.__name__)
|
||||
expectedOutputs.sort(key=lambda t: t.__name__)
|
||||
self.assertEqual(outputs, expectedOutputs, '''\
|
||||
for input:
|
||||
{}
|
||||
expected outputs:
|
||||
{!r}
|
||||
but got:
|
||||
{}'''.format(input, expectedOutputs, '\n'.join([str(o) for o in w.messages])))
|
||||
return w
|
||||
@@ -0,0 +1,781 @@
|
||||
"""
|
||||
Tests for L{pyflakes.scripts.pyflakes}.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from pyflakes.checker import PYPY
|
||||
from pyflakes.messages import UnusedImport
|
||||
from pyflakes.reporter import Reporter
|
||||
from pyflakes.api import (
|
||||
main,
|
||||
check,
|
||||
checkPath,
|
||||
checkRecursive,
|
||||
iterSourceCode,
|
||||
)
|
||||
from pyflakes.test.harness import TestCase, skipIf
|
||||
|
||||
|
||||
def withStderrTo(stderr, f, *args, **kwargs):
|
||||
"""
|
||||
Call C{f} with C{sys.stderr} redirected to C{stderr}.
|
||||
"""
|
||||
(outer, sys.stderr) = (sys.stderr, stderr)
|
||||
try:
|
||||
return f(*args, **kwargs)
|
||||
finally:
|
||||
sys.stderr = outer
|
||||
|
||||
|
||||
class Node:
|
||||
"""
|
||||
Mock an AST node.
|
||||
"""
|
||||
def __init__(self, lineno, col_offset=0):
|
||||
self.lineno = lineno
|
||||
self.col_offset = col_offset
|
||||
|
||||
|
||||
class SysStreamCapturing:
|
||||
"""Context manager capturing sys.stdin, sys.stdout and sys.stderr.
|
||||
|
||||
The file handles are replaced with a StringIO object.
|
||||
"""
|
||||
|
||||
def __init__(self, stdin):
|
||||
self._stdin = io.StringIO(stdin or '', newline=os.linesep)
|
||||
|
||||
def __enter__(self):
|
||||
self._orig_stdin = sys.stdin
|
||||
self._orig_stdout = sys.stdout
|
||||
self._orig_stderr = sys.stderr
|
||||
|
||||
sys.stdin = self._stdin
|
||||
sys.stdout = self._stdout_stringio = io.StringIO(newline=os.linesep)
|
||||
sys.stderr = self._stderr_stringio = io.StringIO(newline=os.linesep)
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.output = self._stdout_stringio.getvalue()
|
||||
self.error = self._stderr_stringio.getvalue()
|
||||
|
||||
sys.stdin = self._orig_stdin
|
||||
sys.stdout = self._orig_stdout
|
||||
sys.stderr = self._orig_stderr
|
||||
|
||||
|
||||
class LoggingReporter:
|
||||
"""
|
||||
Implementation of Reporter that just appends any error to a list.
|
||||
"""
|
||||
|
||||
def __init__(self, log):
|
||||
"""
|
||||
Construct a C{LoggingReporter}.
|
||||
|
||||
@param log: A list to append log messages to.
|
||||
"""
|
||||
self.log = log
|
||||
|
||||
def flake(self, message):
|
||||
self.log.append(('flake', str(message)))
|
||||
|
||||
def unexpectedError(self, filename, message):
|
||||
self.log.append(('unexpectedError', filename, message))
|
||||
|
||||
def syntaxError(self, filename, msg, lineno, offset, line):
|
||||
self.log.append(('syntaxError', filename, msg, lineno, offset, line))
|
||||
|
||||
|
||||
class TestIterSourceCode(TestCase):
|
||||
"""
|
||||
Tests for L{iterSourceCode}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.tempdir = tempfile.mkdtemp()
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tempdir)
|
||||
|
||||
def makeEmptyFile(self, *parts):
|
||||
assert parts
|
||||
fpath = os.path.join(self.tempdir, *parts)
|
||||
open(fpath, 'a').close()
|
||||
return fpath
|
||||
|
||||
def test_emptyDirectory(self):
|
||||
"""
|
||||
There are no Python files in an empty directory.
|
||||
"""
|
||||
self.assertEqual(list(iterSourceCode([self.tempdir])), [])
|
||||
|
||||
def test_singleFile(self):
|
||||
"""
|
||||
If the directory contains one Python file, C{iterSourceCode} will find
|
||||
it.
|
||||
"""
|
||||
childpath = self.makeEmptyFile('foo.py')
|
||||
self.assertEqual(list(iterSourceCode([self.tempdir])), [childpath])
|
||||
|
||||
def test_onlyPythonSource(self):
|
||||
"""
|
||||
Files that are not Python source files are not included.
|
||||
"""
|
||||
self.makeEmptyFile('foo.pyc')
|
||||
self.assertEqual(list(iterSourceCode([self.tempdir])), [])
|
||||
|
||||
def test_recurses(self):
|
||||
"""
|
||||
If the Python files are hidden deep down in child directories, we will
|
||||
find them.
|
||||
"""
|
||||
os.mkdir(os.path.join(self.tempdir, 'foo'))
|
||||
apath = self.makeEmptyFile('foo', 'a.py')
|
||||
self.makeEmptyFile('foo', 'a.py~')
|
||||
os.mkdir(os.path.join(self.tempdir, 'bar'))
|
||||
bpath = self.makeEmptyFile('bar', 'b.py')
|
||||
cpath = self.makeEmptyFile('c.py')
|
||||
self.assertEqual(
|
||||
sorted(iterSourceCode([self.tempdir])),
|
||||
sorted([apath, bpath, cpath]))
|
||||
|
||||
def test_shebang(self):
|
||||
"""
|
||||
Find Python files that don't end with `.py`, but contain a Python
|
||||
shebang.
|
||||
"""
|
||||
python = os.path.join(self.tempdir, 'a')
|
||||
with open(python, 'w') as fd:
|
||||
fd.write('#!/usr/bin/env python\n')
|
||||
|
||||
self.makeEmptyFile('b')
|
||||
|
||||
with open(os.path.join(self.tempdir, 'c'), 'w') as fd:
|
||||
fd.write('hello\nworld\n')
|
||||
|
||||
python3 = os.path.join(self.tempdir, 'e')
|
||||
with open(python3, 'w') as fd:
|
||||
fd.write('#!/usr/bin/env python3\n')
|
||||
|
||||
pythonw = os.path.join(self.tempdir, 'f')
|
||||
with open(pythonw, 'w') as fd:
|
||||
fd.write('#!/usr/bin/env pythonw\n')
|
||||
|
||||
python3args = os.path.join(self.tempdir, 'g')
|
||||
with open(python3args, 'w') as fd:
|
||||
fd.write('#!/usr/bin/python3 -u\n')
|
||||
|
||||
python3d = os.path.join(self.tempdir, 'i')
|
||||
with open(python3d, 'w') as fd:
|
||||
fd.write('#!/usr/local/bin/python3d\n')
|
||||
|
||||
python38m = os.path.join(self.tempdir, 'j')
|
||||
with open(python38m, 'w') as fd:
|
||||
fd.write('#! /usr/bin/env python3.8m\n')
|
||||
|
||||
# Should NOT be treated as Python source
|
||||
notfirst = os.path.join(self.tempdir, 'l')
|
||||
with open(notfirst, 'w') as fd:
|
||||
fd.write('#!/bin/sh\n#!/usr/bin/python\n')
|
||||
|
||||
self.assertEqual(
|
||||
sorted(iterSourceCode([self.tempdir])),
|
||||
sorted([
|
||||
python, python3, pythonw, python3args, python3d,
|
||||
python38m,
|
||||
]))
|
||||
|
||||
def test_multipleDirectories(self):
|
||||
"""
|
||||
L{iterSourceCode} can be given multiple directories. It will recurse
|
||||
into each of them.
|
||||
"""
|
||||
foopath = os.path.join(self.tempdir, 'foo')
|
||||
barpath = os.path.join(self.tempdir, 'bar')
|
||||
os.mkdir(foopath)
|
||||
apath = self.makeEmptyFile('foo', 'a.py')
|
||||
os.mkdir(barpath)
|
||||
bpath = self.makeEmptyFile('bar', 'b.py')
|
||||
self.assertEqual(
|
||||
sorted(iterSourceCode([foopath, barpath])),
|
||||
sorted([apath, bpath]))
|
||||
|
||||
def test_explicitFiles(self):
|
||||
"""
|
||||
If one of the paths given to L{iterSourceCode} is not a directory but
|
||||
a file, it will include that in its output.
|
||||
"""
|
||||
epath = self.makeEmptyFile('e.py')
|
||||
self.assertEqual(list(iterSourceCode([epath])),
|
||||
[epath])
|
||||
|
||||
|
||||
class TestReporter(TestCase):
|
||||
"""
|
||||
Tests for L{Reporter}.
|
||||
"""
|
||||
|
||||
def test_syntaxError(self):
|
||||
"""
|
||||
C{syntaxError} reports that there was a syntax error in the source
|
||||
file. It reports to the error stream and includes the filename, line
|
||||
number, error message, actual line of source and a caret pointing to
|
||||
where the error is.
|
||||
"""
|
||||
err = io.StringIO()
|
||||
reporter = Reporter(None, err)
|
||||
reporter.syntaxError('foo.py', 'a problem', 3, 8, 'bad line of source')
|
||||
self.assertEqual(
|
||||
("foo.py:3:8: a problem\n"
|
||||
"bad line of source\n"
|
||||
" ^\n"),
|
||||
err.getvalue())
|
||||
|
||||
def test_syntaxErrorNoOffset(self):
|
||||
"""
|
||||
C{syntaxError} doesn't include a caret pointing to the error if
|
||||
C{offset} is passed as C{None}.
|
||||
"""
|
||||
err = io.StringIO()
|
||||
reporter = Reporter(None, err)
|
||||
reporter.syntaxError('foo.py', 'a problem', 3, None,
|
||||
'bad line of source')
|
||||
self.assertEqual(
|
||||
("foo.py:3: a problem\n"
|
||||
"bad line of source\n"),
|
||||
err.getvalue())
|
||||
|
||||
def test_syntaxErrorNoText(self):
|
||||
"""
|
||||
C{syntaxError} doesn't include text or nonsensical offsets if C{text} is C{None}.
|
||||
|
||||
This typically happens when reporting syntax errors from stdin.
|
||||
"""
|
||||
err = io.StringIO()
|
||||
reporter = Reporter(None, err)
|
||||
reporter.syntaxError('<stdin>', 'a problem', 0, 0, None)
|
||||
self.assertEqual(("<stdin>:1:1: a problem\n"), err.getvalue())
|
||||
|
||||
def test_multiLineSyntaxError(self):
|
||||
"""
|
||||
If there's a multi-line syntax error, then we only report the last
|
||||
line. The offset is adjusted so that it is relative to the start of
|
||||
the last line.
|
||||
"""
|
||||
err = io.StringIO()
|
||||
lines = [
|
||||
'bad line of source',
|
||||
'more bad lines of source',
|
||||
]
|
||||
reporter = Reporter(None, err)
|
||||
reporter.syntaxError('foo.py', 'a problem', 3, len(lines[0]) + 7,
|
||||
'\n'.join(lines))
|
||||
self.assertEqual(
|
||||
("foo.py:3:25: a problem\n" +
|
||||
lines[-1] + "\n" +
|
||||
" " * 24 + "^\n"),
|
||||
err.getvalue())
|
||||
|
||||
def test_unexpectedError(self):
|
||||
"""
|
||||
C{unexpectedError} reports an error processing a source file.
|
||||
"""
|
||||
err = io.StringIO()
|
||||
reporter = Reporter(None, err)
|
||||
reporter.unexpectedError('source.py', 'error message')
|
||||
self.assertEqual('source.py: error message\n', err.getvalue())
|
||||
|
||||
def test_flake(self):
|
||||
"""
|
||||
C{flake} reports a code warning from Pyflakes. It is exactly the
|
||||
str() of a L{pyflakes.messages.Message}.
|
||||
"""
|
||||
out = io.StringIO()
|
||||
reporter = Reporter(out, None)
|
||||
message = UnusedImport('foo.py', Node(42), 'bar')
|
||||
reporter.flake(message)
|
||||
self.assertEqual(out.getvalue(), f"{message}\n")
|
||||
|
||||
|
||||
class CheckTests(TestCase):
|
||||
"""
|
||||
Tests for L{check} and L{checkPath} which check a file for flakes.
|
||||
"""
|
||||
|
||||
@contextlib.contextmanager
|
||||
def makeTempFile(self, content):
|
||||
"""
|
||||
Make a temporary file containing C{content} and return a path to it.
|
||||
"""
|
||||
fd, name = tempfile.mkstemp()
|
||||
try:
|
||||
with os.fdopen(fd, 'wb') as f:
|
||||
if not hasattr(content, 'decode'):
|
||||
content = content.encode('ascii')
|
||||
f.write(content)
|
||||
yield name
|
||||
finally:
|
||||
os.remove(name)
|
||||
|
||||
def assertHasErrors(self, path, errorList):
|
||||
"""
|
||||
Assert that C{path} causes errors.
|
||||
|
||||
@param path: A path to a file to check.
|
||||
@param errorList: A list of errors expected to be printed to stderr.
|
||||
"""
|
||||
err = io.StringIO()
|
||||
count = withStderrTo(err, checkPath, path)
|
||||
self.assertEqual(
|
||||
(count, err.getvalue()), (len(errorList), ''.join(errorList)))
|
||||
|
||||
def getErrors(self, path):
|
||||
"""
|
||||
Get any warnings or errors reported by pyflakes for the file at C{path}.
|
||||
|
||||
@param path: The path to a Python file on disk that pyflakes will check.
|
||||
@return: C{(count, log)}, where C{count} is the number of warnings or
|
||||
errors generated, and log is a list of those warnings, presented
|
||||
as structured data. See L{LoggingReporter} for more details.
|
||||
"""
|
||||
log = []
|
||||
reporter = LoggingReporter(log)
|
||||
count = checkPath(path, reporter)
|
||||
return count, log
|
||||
|
||||
def test_legacyScript(self):
|
||||
from pyflakes.scripts import pyflakes as script_pyflakes
|
||||
self.assertIs(script_pyflakes.checkPath, checkPath)
|
||||
|
||||
def test_missingTrailingNewline(self):
|
||||
"""
|
||||
Source which doesn't end with a newline shouldn't cause any
|
||||
exception to be raised nor an error indicator to be returned by
|
||||
L{check}.
|
||||
"""
|
||||
with self.makeTempFile("def foo():\n\tpass\n\t") as fName:
|
||||
self.assertHasErrors(fName, [])
|
||||
|
||||
def test_checkPathNonExisting(self):
|
||||
"""
|
||||
L{checkPath} handles non-existing files.
|
||||
"""
|
||||
count, errors = self.getErrors('extremo')
|
||||
self.assertEqual(count, 1)
|
||||
self.assertEqual(
|
||||
errors,
|
||||
[('unexpectedError', 'extremo', 'No such file or directory')])
|
||||
|
||||
def test_multilineSyntaxError(self):
|
||||
"""
|
||||
Source which includes a syntax error which results in the raised
|
||||
L{SyntaxError.text} containing multiple lines of source are reported
|
||||
with only the last line of that source.
|
||||
"""
|
||||
source = """\
|
||||
def foo():
|
||||
'''
|
||||
|
||||
def bar():
|
||||
pass
|
||||
|
||||
def baz():
|
||||
'''quux'''
|
||||
"""
|
||||
|
||||
# Sanity check - SyntaxError.text should be multiple lines, if it
|
||||
# isn't, something this test was unprepared for has happened.
|
||||
def evaluate(source):
|
||||
exec(source)
|
||||
try:
|
||||
evaluate(source)
|
||||
except SyntaxError as e:
|
||||
if not PYPY and sys.version_info < (3, 10):
|
||||
self.assertTrue(e.text.count('\n') > 1)
|
||||
else:
|
||||
self.fail()
|
||||
|
||||
with self.makeTempFile(source) as sourcePath:
|
||||
if PYPY:
|
||||
message = 'end of file (EOF) while scanning triple-quoted string literal'
|
||||
elif sys.version_info >= (3, 10):
|
||||
message = 'unterminated triple-quoted string literal (detected at line 8)' # noqa: E501
|
||||
else:
|
||||
message = 'invalid syntax'
|
||||
|
||||
if PYPY or sys.version_info >= (3, 10):
|
||||
column = 12
|
||||
else:
|
||||
column = 8
|
||||
self.assertHasErrors(
|
||||
sourcePath,
|
||||
["""\
|
||||
%s:8:%d: %s
|
||||
'''quux'''
|
||||
%s^
|
||||
""" % (sourcePath, column, message, ' ' * (column - 1))])
|
||||
|
||||
def test_eofSyntaxError(self):
|
||||
"""
|
||||
The error reported for source files which end prematurely causing a
|
||||
syntax error reflects the cause for the syntax error.
|
||||
"""
|
||||
with self.makeTempFile("def foo(") as sourcePath:
|
||||
if PYPY:
|
||||
msg = 'parenthesis is never closed'
|
||||
elif sys.version_info >= (3, 10):
|
||||
msg = "'(' was never closed"
|
||||
else:
|
||||
msg = 'unexpected EOF while parsing'
|
||||
|
||||
if PYPY or sys.version_info >= (3, 10):
|
||||
column = 8
|
||||
else:
|
||||
column = 9
|
||||
|
||||
spaces = ' ' * (column - 1)
|
||||
expected = '{}:1:{}: {}\ndef foo(\n{}^\n'.format(
|
||||
sourcePath, column, msg, spaces
|
||||
)
|
||||
|
||||
self.assertHasErrors(sourcePath, [expected])
|
||||
|
||||
def test_eofSyntaxErrorWithTab(self):
|
||||
"""
|
||||
The error reported for source files which end prematurely causing a
|
||||
syntax error reflects the cause for the syntax error.
|
||||
"""
|
||||
with self.makeTempFile("if True:\n\tfoo =") as sourcePath:
|
||||
self.assertHasErrors(
|
||||
sourcePath,
|
||||
[f"""\
|
||||
{sourcePath}:2:7: invalid syntax
|
||||
\tfoo =
|
||||
\t ^
|
||||
"""])
|
||||
|
||||
def test_nonDefaultFollowsDefaultSyntaxError(self):
|
||||
"""
|
||||
Source which has a non-default argument following a default argument
|
||||
should include the line number of the syntax error. However these
|
||||
exceptions do not include an offset.
|
||||
"""
|
||||
source = """\
|
||||
def foo(bar=baz, bax):
|
||||
pass
|
||||
"""
|
||||
with self.makeTempFile(source) as sourcePath:
|
||||
if sys.version_info >= (3, 12):
|
||||
msg = 'parameter without a default follows parameter with a default' # noqa: E501
|
||||
else:
|
||||
msg = 'non-default argument follows default argument'
|
||||
|
||||
if PYPY:
|
||||
column = 18
|
||||
elif sys.version_info >= (3, 10):
|
||||
column = 18
|
||||
else:
|
||||
column = 21
|
||||
last_line = ' ' * (column - 1) + '^\n'
|
||||
self.assertHasErrors(
|
||||
sourcePath,
|
||||
[f"""\
|
||||
{sourcePath}:1:{column}: {msg}
|
||||
def foo(bar=baz, bax):
|
||||
{last_line}"""]
|
||||
)
|
||||
|
||||
def test_nonKeywordAfterKeywordSyntaxError(self):
|
||||
"""
|
||||
Source which has a non-keyword argument after a keyword argument should
|
||||
include the line number of the syntax error. However these exceptions
|
||||
do not include an offset.
|
||||
"""
|
||||
source = """\
|
||||
foo(bar=baz, bax)
|
||||
"""
|
||||
with self.makeTempFile(source) as sourcePath:
|
||||
last_line = ' ' * 16 + '^\n'
|
||||
self.assertHasErrors(
|
||||
sourcePath,
|
||||
[f"""\
|
||||
{sourcePath}:1:17: positional argument follows keyword argument
|
||||
foo(bar=baz, bax)
|
||||
{last_line}"""])
|
||||
|
||||
def test_invalidEscape(self):
|
||||
"""
|
||||
The invalid escape syntax raises ValueError in Python 2
|
||||
"""
|
||||
# ValueError: invalid \x escape
|
||||
with self.makeTempFile(r"foo = '\xyz'") as sourcePath:
|
||||
position_end = 1
|
||||
if PYPY:
|
||||
column = 7
|
||||
elif sys.version_info < (3, 12):
|
||||
column = 13
|
||||
else:
|
||||
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'
|
||||
%s""" % (sourcePath, column, position_end, last_line)
|
||||
|
||||
self.assertHasErrors(
|
||||
sourcePath, [decoding_error])
|
||||
|
||||
@skipIf(sys.platform == 'win32', 'unsupported on Windows')
|
||||
def test_permissionDenied(self):
|
||||
"""
|
||||
If the source file is not readable, this is reported on standard
|
||||
error.
|
||||
"""
|
||||
if os.getuid() == 0:
|
||||
self.skipTest('root user can access all files regardless of '
|
||||
'permissions')
|
||||
with self.makeTempFile('') as sourcePath:
|
||||
os.chmod(sourcePath, 0)
|
||||
count, errors = self.getErrors(sourcePath)
|
||||
self.assertEqual(count, 1)
|
||||
self.assertEqual(
|
||||
errors,
|
||||
[('unexpectedError', sourcePath, "Permission denied")])
|
||||
|
||||
def test_pyflakesWarning(self):
|
||||
"""
|
||||
If the source file has a pyflakes warning, this is reported as a
|
||||
'flake'.
|
||||
"""
|
||||
with self.makeTempFile("import foo") as sourcePath:
|
||||
count, errors = self.getErrors(sourcePath)
|
||||
self.assertEqual(count, 1)
|
||||
self.assertEqual(
|
||||
errors, [('flake', str(UnusedImport(sourcePath, Node(1), 'foo')))])
|
||||
|
||||
def test_encodedFileUTF8(self):
|
||||
"""
|
||||
If source file declares the correct encoding, no error is reported.
|
||||
"""
|
||||
SNOWMAN = chr(0x2603)
|
||||
source = ("""\
|
||||
# coding: utf-8
|
||||
x = "%s"
|
||||
""" % SNOWMAN).encode('utf-8')
|
||||
with self.makeTempFile(source) as sourcePath:
|
||||
self.assertHasErrors(sourcePath, [])
|
||||
|
||||
def test_CRLFLineEndings(self):
|
||||
"""
|
||||
Source files with Windows CR LF line endings are parsed successfully.
|
||||
"""
|
||||
with self.makeTempFile("x = 42\r\n") as sourcePath:
|
||||
self.assertHasErrors(sourcePath, [])
|
||||
|
||||
def test_misencodedFileUTF8(self):
|
||||
"""
|
||||
If a source file contains bytes which cannot be decoded, this is
|
||||
reported on stderr.
|
||||
"""
|
||||
SNOWMAN = chr(0x2603)
|
||||
source = ("""\
|
||||
# coding: ascii
|
||||
x = "%s"
|
||||
""" % SNOWMAN).encode('utf-8')
|
||||
with self.makeTempFile(source) as sourcePath:
|
||||
self.assertHasErrors(
|
||||
sourcePath,
|
||||
[f"{sourcePath}:1:1: 'ascii' codec can't decode byte 0xe2 in position 21: ordinal not in range(128)\n"]) # noqa: E501
|
||||
|
||||
def test_misencodedFileUTF16(self):
|
||||
"""
|
||||
If a source file contains bytes which cannot be decoded, this is
|
||||
reported on stderr.
|
||||
"""
|
||||
SNOWMAN = chr(0x2603)
|
||||
source = ("""\
|
||||
# coding: ascii
|
||||
x = "%s"
|
||||
""" % SNOWMAN).encode('utf-16')
|
||||
with self.makeTempFile(source) as sourcePath:
|
||||
if sys.version_info < (3, 11, 4):
|
||||
expected = f"{sourcePath}: problem decoding source\n"
|
||||
else:
|
||||
expected = f"{sourcePath}:1: source code string cannot contain null bytes\n" # noqa: E501
|
||||
|
||||
self.assertHasErrors(sourcePath, [expected])
|
||||
|
||||
def test_checkRecursive(self):
|
||||
"""
|
||||
L{checkRecursive} descends into each directory, finding Python files
|
||||
and reporting problems.
|
||||
"""
|
||||
tempdir = tempfile.mkdtemp()
|
||||
try:
|
||||
os.mkdir(os.path.join(tempdir, 'foo'))
|
||||
file1 = os.path.join(tempdir, 'foo', 'bar.py')
|
||||
with open(file1, 'wb') as fd:
|
||||
fd.write(b"import baz\n")
|
||||
file2 = os.path.join(tempdir, 'baz.py')
|
||||
with open(file2, 'wb') as fd:
|
||||
fd.write(b"import contraband")
|
||||
log = []
|
||||
reporter = LoggingReporter(log)
|
||||
warnings = checkRecursive([tempdir], reporter)
|
||||
self.assertEqual(warnings, 2)
|
||||
self.assertEqual(
|
||||
sorted(log),
|
||||
sorted([('flake', str(UnusedImport(file1, Node(1), 'baz'))),
|
||||
('flake',
|
||||
str(UnusedImport(file2, Node(1), 'contraband')))]))
|
||||
finally:
|
||||
shutil.rmtree(tempdir)
|
||||
|
||||
def test_stdinReportsErrors(self):
|
||||
"""
|
||||
L{check} reports syntax errors from stdin
|
||||
"""
|
||||
source = "max(1 for i in range(10), key=lambda x: x+1)\n"
|
||||
err = io.StringIO()
|
||||
count = withStderrTo(err, check, source, "<stdin>")
|
||||
self.assertEqual(count, 1)
|
||||
errlines = err.getvalue().split("\n")[:-1]
|
||||
|
||||
expected_error = [
|
||||
"<stdin>:1:5: Generator expression must be parenthesized",
|
||||
"max(1 for i in range(10), key=lambda x: x+1)",
|
||||
" ^",
|
||||
]
|
||||
self.assertEqual(errlines, expected_error)
|
||||
|
||||
|
||||
class IntegrationTests(TestCase):
|
||||
"""
|
||||
Tests of the pyflakes script that actually spawn the script.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.tempdir = tempfile.mkdtemp()
|
||||
self.tempfilepath = os.path.join(self.tempdir, 'temp')
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tempdir)
|
||||
|
||||
def getPyflakesBinary(self):
|
||||
"""
|
||||
Return the path to the pyflakes binary.
|
||||
"""
|
||||
import pyflakes
|
||||
package_dir = os.path.dirname(pyflakes.__file__)
|
||||
return os.path.join(package_dir, '..', 'bin', 'pyflakes')
|
||||
|
||||
def runPyflakes(self, paths, stdin=None):
|
||||
"""
|
||||
Launch a subprocess running C{pyflakes}.
|
||||
|
||||
@param paths: Command-line arguments to pass to pyflakes.
|
||||
@param stdin: Text to use as stdin.
|
||||
@return: C{(returncode, stdout, stderr)} of the completed pyflakes
|
||||
process.
|
||||
"""
|
||||
env = dict(os.environ)
|
||||
env['PYTHONPATH'] = os.pathsep.join(sys.path)
|
||||
command = [sys.executable, self.getPyflakesBinary()]
|
||||
command.extend(paths)
|
||||
if stdin:
|
||||
p = subprocess.Popen(command, env=env, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(stdout, stderr) = p.communicate(stdin.encode('ascii'))
|
||||
else:
|
||||
p = subprocess.Popen(command, env=env,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(stdout, stderr) = p.communicate()
|
||||
rv = p.wait()
|
||||
stdout = stdout.decode('utf-8')
|
||||
stderr = stderr.decode('utf-8')
|
||||
return (stdout, stderr, rv)
|
||||
|
||||
def test_goodFile(self):
|
||||
"""
|
||||
When a Python source file is all good, the return code is zero and no
|
||||
messages are printed to either stdout or stderr.
|
||||
"""
|
||||
open(self.tempfilepath, 'a').close()
|
||||
d = self.runPyflakes([self.tempfilepath])
|
||||
self.assertEqual(d, ('', '', 0))
|
||||
|
||||
def test_fileWithFlakes(self):
|
||||
"""
|
||||
When a Python source file has warnings, the return code is non-zero
|
||||
and the warnings are printed to stdout.
|
||||
"""
|
||||
with open(self.tempfilepath, 'wb') as fd:
|
||||
fd.write(b"import contraband\n")
|
||||
d = self.runPyflakes([self.tempfilepath])
|
||||
expected = UnusedImport(self.tempfilepath, Node(1), 'contraband')
|
||||
self.assertEqual(d, (f"{expected}{os.linesep}", '', 1))
|
||||
|
||||
def test_errors_io(self):
|
||||
"""
|
||||
When pyflakes finds errors with the files it's given, (if they don't
|
||||
exist, say), then the return code is non-zero and the errors are
|
||||
printed to stderr.
|
||||
"""
|
||||
d = self.runPyflakes([self.tempfilepath])
|
||||
error_msg = '{}: No such file or directory{}'.format(self.tempfilepath,
|
||||
os.linesep)
|
||||
self.assertEqual(d, ('', error_msg, 1))
|
||||
|
||||
def test_errors_syntax(self):
|
||||
"""
|
||||
When pyflakes finds errors with the files it's given, (if they don't
|
||||
exist, say), then the return code is non-zero and the errors are
|
||||
printed to stderr.
|
||||
"""
|
||||
with open(self.tempfilepath, 'wb') as fd:
|
||||
fd.write(b"import")
|
||||
d = self.runPyflakes([self.tempfilepath])
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
message = "Expected one or more names after 'import'"
|
||||
else:
|
||||
message = 'invalid syntax'
|
||||
|
||||
error_msg = '{0}:1:7: {1}{2}import{2} ^{2}'.format(
|
||||
self.tempfilepath, message, os.linesep)
|
||||
self.assertEqual(d, ('', error_msg, 1))
|
||||
|
||||
def test_readFromStdin(self):
|
||||
"""
|
||||
If no arguments are passed to C{pyflakes} then it reads from stdin.
|
||||
"""
|
||||
d = self.runPyflakes([], stdin='import contraband')
|
||||
expected = UnusedImport('<stdin>', Node(1), 'contraband')
|
||||
self.assertEqual(d, (f"{expected}{os.linesep}", '', 1))
|
||||
|
||||
|
||||
class TestMain(IntegrationTests):
|
||||
"""
|
||||
Tests of the pyflakes main function.
|
||||
"""
|
||||
def runPyflakes(self, paths, stdin=None):
|
||||
try:
|
||||
with SysStreamCapturing(stdin) as capture:
|
||||
main(args=paths)
|
||||
except SystemExit as e:
|
||||
self.assertIsInstance(e.code, bool)
|
||||
rv = int(e.code)
|
||||
return (capture.output, capture.error, rv)
|
||||
else:
|
||||
raise RuntimeError('SystemExit not raised')
|
||||
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
Tests for detecting redefinition of builtins.
|
||||
"""
|
||||
from pyflakes import messages as m
|
||||
from pyflakes.test.harness import TestCase
|
||||
|
||||
|
||||
class TestBuiltins(TestCase):
|
||||
|
||||
def test_builtin_unbound_local(self):
|
||||
self.flakes('''
|
||||
def foo():
|
||||
a = range(1, 10)
|
||||
range = a
|
||||
return range
|
||||
|
||||
foo()
|
||||
|
||||
print(range)
|
||||
''', m.UndefinedLocal)
|
||||
|
||||
def test_global_shadowing_builtin(self):
|
||||
self.flakes('''
|
||||
def f():
|
||||
global range
|
||||
range = None
|
||||
print(range)
|
||||
|
||||
f()
|
||||
''')
|
||||
@@ -0,0 +1,129 @@
|
||||
from pyflakes import messages as m
|
||||
from pyflakes.checker import (FunctionScope, ClassScope, ModuleScope,
|
||||
Argument, FunctionDefinition, Assignment)
|
||||
from pyflakes.test.harness import TestCase
|
||||
|
||||
|
||||
class TestCodeSegments(TestCase):
|
||||
"""
|
||||
Tests for segments of a module
|
||||
"""
|
||||
|
||||
def test_function_segment(self):
|
||||
self.flakes('''
|
||||
def foo():
|
||||
def bar():
|
||||
pass
|
||||
''', is_segment=True)
|
||||
|
||||
self.flakes('''
|
||||
def foo():
|
||||
def bar():
|
||||
x = 0
|
||||
''', m.UnusedVariable, is_segment=True)
|
||||
|
||||
def test_class_segment(self):
|
||||
self.flakes('''
|
||||
class Foo:
|
||||
class Bar:
|
||||
pass
|
||||
''', is_segment=True)
|
||||
|
||||
self.flakes('''
|
||||
class Foo:
|
||||
def bar():
|
||||
x = 0
|
||||
''', m.UnusedVariable, is_segment=True)
|
||||
|
||||
def test_scope_class(self):
|
||||
checker = self.flakes('''
|
||||
class Foo:
|
||||
x = 0
|
||||
def bar(a, b=1, *d, **e):
|
||||
pass
|
||||
''', is_segment=True)
|
||||
|
||||
scopes = checker.deadScopes
|
||||
module_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is ModuleScope]
|
||||
class_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is ClassScope]
|
||||
function_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is FunctionScope]
|
||||
|
||||
# Ensure module scope is not present because we are analysing
|
||||
# the inner contents of Foo
|
||||
self.assertEqual(len(module_scopes), 0)
|
||||
self.assertEqual(len(class_scopes), 1)
|
||||
self.assertEqual(len(function_scopes), 1)
|
||||
|
||||
class_scope = class_scopes[0]
|
||||
function_scope = function_scopes[0]
|
||||
|
||||
self.assertIsInstance(class_scope, ClassScope)
|
||||
self.assertIsInstance(function_scope, FunctionScope)
|
||||
|
||||
self.assertIn('x', class_scope)
|
||||
self.assertIn('bar', class_scope)
|
||||
|
||||
self.assertIn('a', function_scope)
|
||||
self.assertIn('b', function_scope)
|
||||
self.assertIn('d', function_scope)
|
||||
self.assertIn('e', function_scope)
|
||||
|
||||
self.assertIsInstance(class_scope['bar'], FunctionDefinition)
|
||||
self.assertIsInstance(class_scope['x'], Assignment)
|
||||
|
||||
self.assertIsInstance(function_scope['a'], Argument)
|
||||
self.assertIsInstance(function_scope['b'], Argument)
|
||||
self.assertIsInstance(function_scope['d'], Argument)
|
||||
self.assertIsInstance(function_scope['e'], Argument)
|
||||
|
||||
def test_scope_function(self):
|
||||
checker = self.flakes('''
|
||||
def foo(a, b=1, *d, **e):
|
||||
def bar(f, g=1, *h, **i):
|
||||
pass
|
||||
''', is_segment=True)
|
||||
|
||||
scopes = checker.deadScopes
|
||||
module_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is ModuleScope]
|
||||
function_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is FunctionScope]
|
||||
|
||||
# Ensure module scope is not present because we are analysing
|
||||
# the inner contents of foo
|
||||
self.assertEqual(len(module_scopes), 0)
|
||||
self.assertEqual(len(function_scopes), 2)
|
||||
|
||||
function_scope_foo = function_scopes[1]
|
||||
function_scope_bar = function_scopes[0]
|
||||
|
||||
self.assertIsInstance(function_scope_foo, FunctionScope)
|
||||
self.assertIsInstance(function_scope_bar, FunctionScope)
|
||||
|
||||
self.assertIn('a', function_scope_foo)
|
||||
self.assertIn('b', function_scope_foo)
|
||||
self.assertIn('d', function_scope_foo)
|
||||
self.assertIn('e', function_scope_foo)
|
||||
self.assertIn('bar', function_scope_foo)
|
||||
|
||||
self.assertIn('f', function_scope_bar)
|
||||
self.assertIn('g', function_scope_bar)
|
||||
self.assertIn('h', function_scope_bar)
|
||||
self.assertIn('i', function_scope_bar)
|
||||
|
||||
self.assertIsInstance(function_scope_foo['bar'], FunctionDefinition)
|
||||
self.assertIsInstance(function_scope_foo['a'], Argument)
|
||||
self.assertIsInstance(function_scope_foo['b'], Argument)
|
||||
self.assertIsInstance(function_scope_foo['d'], Argument)
|
||||
self.assertIsInstance(function_scope_foo['e'], Argument)
|
||||
|
||||
self.assertIsInstance(function_scope_bar['f'], Argument)
|
||||
self.assertIsInstance(function_scope_bar['g'], Argument)
|
||||
self.assertIsInstance(function_scope_bar['h'], Argument)
|
||||
self.assertIsInstance(function_scope_bar['i'], Argument)
|
||||
|
||||
def test_scope_async_function(self):
|
||||
self.flakes('async def foo(): pass', is_segment=True)
|
||||
@@ -0,0 +1,193 @@
|
||||
"""
|
||||
Tests for dict duplicate keys Pyflakes behavior.
|
||||
"""
|
||||
|
||||
from pyflakes import messages as m
|
||||
from pyflakes.test.harness import TestCase
|
||||
|
||||
|
||||
class Test(TestCase):
|
||||
|
||||
def test_duplicate_keys(self):
|
||||
self.flakes(
|
||||
"{'yes': 1, 'yes': 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_bytes_vs_unicode_py3(self):
|
||||
self.flakes("{b'a': 1, u'a': 2}")
|
||||
|
||||
def test_duplicate_values_bytes_vs_unicode_py3(self):
|
||||
self.flakes(
|
||||
"{1: b'a', 1: u'a'}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_multiple_duplicate_keys(self):
|
||||
self.flakes(
|
||||
"{'yes': 1, 'yes': 2, 'no': 2, 'no': 3}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_in_function(self):
|
||||
self.flakes(
|
||||
'''
|
||||
def f(thing):
|
||||
pass
|
||||
f({'yes': 1, 'yes': 2})
|
||||
''',
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_in_lambda(self):
|
||||
self.flakes(
|
||||
"lambda x: {(0,1): 1, (0,1): 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_tuples(self):
|
||||
self.flakes(
|
||||
"{(0,1): 1, (0,1): 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_tuples_int_and_float(self):
|
||||
self.flakes(
|
||||
"{(0,1): 1, (0,1.0): 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_ints(self):
|
||||
self.flakes(
|
||||
"{1: 1, 1: 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_bools(self):
|
||||
self.flakes(
|
||||
"{True: 1, True: 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_bools_false(self):
|
||||
# Needed to ensure 2.x correctly coerces these from variables
|
||||
self.flakes(
|
||||
"{False: 1, False: 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_none(self):
|
||||
self.flakes(
|
||||
"{None: 1, None: 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_variable_keys(self):
|
||||
self.flakes(
|
||||
'''
|
||||
a = 1
|
||||
{a: 1, a: 2}
|
||||
''',
|
||||
m.MultiValueRepeatedKeyVariable,
|
||||
m.MultiValueRepeatedKeyVariable,
|
||||
)
|
||||
|
||||
def test_duplicate_variable_values(self):
|
||||
self.flakes(
|
||||
'''
|
||||
a = 1
|
||||
b = 2
|
||||
{1: a, 1: b}
|
||||
''',
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_variable_values_same_value(self):
|
||||
# Current behaviour is not to look up variable values. This is to
|
||||
# confirm that.
|
||||
self.flakes(
|
||||
'''
|
||||
a = 1
|
||||
b = 1
|
||||
{1: a, 1: b}
|
||||
''',
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_key_float_and_int(self):
|
||||
"""
|
||||
These do look like different values, but when it comes to their use as
|
||||
keys, they compare as equal and so are actually duplicates.
|
||||
The literal dict {1: 1, 1.0: 1} actually becomes {1.0: 1}.
|
||||
"""
|
||||
self.flakes(
|
||||
'''
|
||||
{1: 1, 1.0: 2}
|
||||
''',
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_no_duplicate_key_error_same_value(self):
|
||||
self.flakes('''
|
||||
{'yes': 1, 'yes': 1}
|
||||
''')
|
||||
|
||||
def test_no_duplicate_key_errors(self):
|
||||
self.flakes('''
|
||||
{'yes': 1, 'no': 2}
|
||||
''')
|
||||
|
||||
def test_no_duplicate_keys_tuples_same_first_element(self):
|
||||
self.flakes("{(0,1): 1, (0,2): 1}")
|
||||
|
||||
def test_no_duplicate_key_errors_func_call(self):
|
||||
self.flakes('''
|
||||
def test(thing):
|
||||
pass
|
||||
test({True: 1, None: 2, False: 1})
|
||||
''')
|
||||
|
||||
def test_no_duplicate_key_errors_bool_or_none(self):
|
||||
self.flakes("{True: 1, None: 2, False: 1}")
|
||||
|
||||
def test_no_duplicate_key_errors_ints(self):
|
||||
self.flakes('''
|
||||
{1: 1, 2: 1}
|
||||
''')
|
||||
|
||||
def test_no_duplicate_key_errors_vars(self):
|
||||
self.flakes('''
|
||||
test = 'yes'
|
||||
rest = 'yes'
|
||||
{test: 1, rest: 2}
|
||||
''')
|
||||
|
||||
def test_no_duplicate_key_errors_tuples(self):
|
||||
self.flakes('''
|
||||
{(0,1): 1, (0,2): 1}
|
||||
''')
|
||||
|
||||
def test_no_duplicate_key_errors_instance_attributes(self):
|
||||
self.flakes('''
|
||||
class Test():
|
||||
pass
|
||||
f = Test()
|
||||
f.a = 1
|
||||
{f.a: 1, f.a: 1}
|
||||
''')
|
||||
@@ -0,0 +1,444 @@
|
||||
import textwrap
|
||||
|
||||
from pyflakes import messages as m
|
||||
from pyflakes.checker import (
|
||||
PYPY,
|
||||
DoctestScope,
|
||||
FunctionScope,
|
||||
ModuleScope,
|
||||
)
|
||||
from pyflakes.test.test_other import Test as TestOther
|
||||
from pyflakes.test.test_imports import Test as TestImports
|
||||
from pyflakes.test.test_undefined_names import Test as TestUndefinedNames
|
||||
from pyflakes.test.harness import TestCase, skip
|
||||
|
||||
|
||||
class _DoctestMixin:
|
||||
|
||||
withDoctest = True
|
||||
|
||||
def doctestify(self, input):
|
||||
lines = []
|
||||
for line in textwrap.dedent(input).splitlines():
|
||||
if line.strip() == '':
|
||||
pass
|
||||
elif (line.startswith(' ') or
|
||||
line.startswith('except:') or
|
||||
line.startswith('except ') or
|
||||
line.startswith('finally:') or
|
||||
line.startswith('else:') or
|
||||
line.startswith('elif ') or
|
||||
(lines and lines[-1].startswith(('>>> @', '... @')))):
|
||||
line = "... %s" % line
|
||||
else:
|
||||
line = ">>> %s" % line
|
||||
lines.append(line)
|
||||
doctestificator = textwrap.dedent('''\
|
||||
def doctest_something():
|
||||
"""
|
||||
%s
|
||||
"""
|
||||
''')
|
||||
return doctestificator % "\n ".join(lines)
|
||||
|
||||
def flakes(self, input, *args, **kw):
|
||||
return super().flakes(self.doctestify(input), *args, **kw)
|
||||
|
||||
|
||||
class Test(TestCase):
|
||||
|
||||
withDoctest = True
|
||||
|
||||
def test_scope_class(self):
|
||||
"""Check that a doctest is given a DoctestScope."""
|
||||
checker = self.flakes("""
|
||||
m = None
|
||||
|
||||
def doctest_stuff():
|
||||
'''
|
||||
>>> d = doctest_stuff()
|
||||
'''
|
||||
f = m
|
||||
return f
|
||||
""")
|
||||
|
||||
scopes = checker.deadScopes
|
||||
module_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is ModuleScope]
|
||||
doctest_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is DoctestScope]
|
||||
function_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is FunctionScope]
|
||||
|
||||
self.assertEqual(len(module_scopes), 1)
|
||||
self.assertEqual(len(doctest_scopes), 1)
|
||||
|
||||
module_scope = module_scopes[0]
|
||||
doctest_scope = doctest_scopes[0]
|
||||
|
||||
self.assertIsInstance(doctest_scope, DoctestScope)
|
||||
self.assertIsInstance(doctest_scope, ModuleScope)
|
||||
self.assertNotIsInstance(doctest_scope, FunctionScope)
|
||||
self.assertNotIsInstance(module_scope, DoctestScope)
|
||||
|
||||
self.assertIn('m', module_scope)
|
||||
self.assertIn('doctest_stuff', module_scope)
|
||||
|
||||
self.assertIn('d', doctest_scope)
|
||||
|
||||
self.assertEqual(len(function_scopes), 1)
|
||||
self.assertIn('f', function_scopes[0])
|
||||
|
||||
def test_nested_doctest_ignored(self):
|
||||
"""Check that nested doctests are ignored."""
|
||||
checker = self.flakes("""
|
||||
m = None
|
||||
|
||||
def doctest_stuff():
|
||||
'''
|
||||
>>> def function_in_doctest():
|
||||
... \"\"\"
|
||||
... >>> ignored_undefined_name
|
||||
... \"\"\"
|
||||
... df = m
|
||||
... return df
|
||||
...
|
||||
>>> function_in_doctest()
|
||||
'''
|
||||
f = m
|
||||
return f
|
||||
""")
|
||||
|
||||
scopes = checker.deadScopes
|
||||
module_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is ModuleScope]
|
||||
doctest_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is DoctestScope]
|
||||
function_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is FunctionScope]
|
||||
|
||||
self.assertEqual(len(module_scopes), 1)
|
||||
self.assertEqual(len(doctest_scopes), 1)
|
||||
|
||||
module_scope = module_scopes[0]
|
||||
doctest_scope = doctest_scopes[0]
|
||||
|
||||
self.assertIn('m', module_scope)
|
||||
self.assertIn('doctest_stuff', module_scope)
|
||||
self.assertIn('function_in_doctest', doctest_scope)
|
||||
|
||||
self.assertEqual(len(function_scopes), 2)
|
||||
|
||||
self.assertIn('f', function_scopes[0])
|
||||
self.assertIn('df', function_scopes[1])
|
||||
|
||||
def test_global_module_scope_pollution(self):
|
||||
"""Check that global in doctest does not pollute module scope."""
|
||||
checker = self.flakes("""
|
||||
def doctest_stuff():
|
||||
'''
|
||||
>>> def function_in_doctest():
|
||||
... global m
|
||||
... m = 50
|
||||
... df = 10
|
||||
... m = df
|
||||
...
|
||||
>>> function_in_doctest()
|
||||
'''
|
||||
f = 10
|
||||
return f
|
||||
|
||||
""")
|
||||
|
||||
scopes = checker.deadScopes
|
||||
module_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is ModuleScope]
|
||||
doctest_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is DoctestScope]
|
||||
function_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is FunctionScope]
|
||||
|
||||
self.assertEqual(len(module_scopes), 1)
|
||||
self.assertEqual(len(doctest_scopes), 1)
|
||||
|
||||
module_scope = module_scopes[0]
|
||||
doctest_scope = doctest_scopes[0]
|
||||
|
||||
self.assertIn('doctest_stuff', module_scope)
|
||||
self.assertIn('function_in_doctest', doctest_scope)
|
||||
|
||||
self.assertEqual(len(function_scopes), 2)
|
||||
|
||||
self.assertIn('f', function_scopes[0])
|
||||
self.assertIn('df', function_scopes[1])
|
||||
self.assertIn('m', function_scopes[1])
|
||||
|
||||
self.assertNotIn('m', module_scope)
|
||||
|
||||
def test_global_undefined(self):
|
||||
self.flakes("""
|
||||
global m
|
||||
|
||||
def doctest_stuff():
|
||||
'''
|
||||
>>> m
|
||||
'''
|
||||
""", m.UndefinedName)
|
||||
|
||||
def test_nested_class(self):
|
||||
"""Doctest within nested class are processed."""
|
||||
self.flakes("""
|
||||
class C:
|
||||
class D:
|
||||
'''
|
||||
>>> m
|
||||
'''
|
||||
def doctest_stuff(self):
|
||||
'''
|
||||
>>> m
|
||||
'''
|
||||
return 1
|
||||
""", m.UndefinedName, m.UndefinedName)
|
||||
|
||||
def test_ignore_nested_function(self):
|
||||
"""Doctest module does not process doctest in nested functions."""
|
||||
# 'syntax error' would cause a SyntaxError if the doctest was processed.
|
||||
# However doctest does not find doctest in nested functions
|
||||
# (https://bugs.python.org/issue1650090). If nested functions were
|
||||
# processed, this use of m should cause UndefinedName, and the
|
||||
# name inner_function should probably exist in the doctest scope.
|
||||
self.flakes("""
|
||||
def doctest_stuff():
|
||||
def inner_function():
|
||||
'''
|
||||
>>> syntax error
|
||||
>>> inner_function()
|
||||
1
|
||||
>>> m
|
||||
'''
|
||||
return 1
|
||||
m = inner_function()
|
||||
return m
|
||||
""")
|
||||
|
||||
def test_inaccessible_scope_class(self):
|
||||
"""Doctest may not access class scope."""
|
||||
self.flakes("""
|
||||
class C:
|
||||
def doctest_stuff(self):
|
||||
'''
|
||||
>>> m
|
||||
'''
|
||||
return 1
|
||||
m = 1
|
||||
""", m.UndefinedName)
|
||||
|
||||
def test_importBeforeDoctest(self):
|
||||
self.flakes("""
|
||||
import foo
|
||||
|
||||
def doctest_stuff():
|
||||
'''
|
||||
>>> foo
|
||||
'''
|
||||
""")
|
||||
|
||||
@skip("todo")
|
||||
def test_importBeforeAndInDoctest(self):
|
||||
self.flakes('''
|
||||
import foo
|
||||
|
||||
def doctest_stuff():
|
||||
"""
|
||||
>>> import foo
|
||||
>>> foo
|
||||
"""
|
||||
|
||||
foo
|
||||
''', m.RedefinedWhileUnused)
|
||||
|
||||
def test_importInDoctestAndAfter(self):
|
||||
self.flakes('''
|
||||
def doctest_stuff():
|
||||
"""
|
||||
>>> import foo
|
||||
>>> foo
|
||||
"""
|
||||
|
||||
import foo
|
||||
foo()
|
||||
''')
|
||||
|
||||
def test_offsetInDoctests(self):
|
||||
exc = self.flakes('''
|
||||
|
||||
def doctest_stuff():
|
||||
"""
|
||||
>>> x # line 5
|
||||
"""
|
||||
|
||||
''', m.UndefinedName).messages[0]
|
||||
self.assertEqual(exc.lineno, 5)
|
||||
self.assertEqual(exc.col, 12)
|
||||
|
||||
def test_offsetInLambdasInDoctests(self):
|
||||
exc = self.flakes('''
|
||||
|
||||
def doctest_stuff():
|
||||
"""
|
||||
>>> lambda: x # line 5
|
||||
"""
|
||||
|
||||
''', m.UndefinedName).messages[0]
|
||||
self.assertEqual(exc.lineno, 5)
|
||||
self.assertEqual(exc.col, 20)
|
||||
|
||||
def test_offsetAfterDoctests(self):
|
||||
exc = self.flakes('''
|
||||
|
||||
def doctest_stuff():
|
||||
"""
|
||||
>>> x = 5
|
||||
"""
|
||||
|
||||
x
|
||||
|
||||
''', m.UndefinedName).messages[0]
|
||||
self.assertEqual(exc.lineno, 8)
|
||||
self.assertEqual(exc.col, 0)
|
||||
|
||||
def test_syntaxErrorInDoctest(self):
|
||||
exceptions = self.flakes(
|
||||
'''
|
||||
def doctest_stuff():
|
||||
"""
|
||||
>>> from # line 4
|
||||
>>> fortytwo = 42
|
||||
>>> except Exception:
|
||||
"""
|
||||
''',
|
||||
m.DoctestSyntaxError,
|
||||
m.DoctestSyntaxError,
|
||||
m.DoctestSyntaxError).messages
|
||||
exc = exceptions[0]
|
||||
self.assertEqual(exc.lineno, 4)
|
||||
if not PYPY:
|
||||
self.assertEqual(exc.col, 18)
|
||||
else:
|
||||
self.assertEqual(exc.col, 26)
|
||||
|
||||
# PyPy error column offset is 0,
|
||||
# for the second and third line of the doctest
|
||||
# i.e. at the beginning of the line
|
||||
exc = exceptions[1]
|
||||
self.assertEqual(exc.lineno, 5)
|
||||
if PYPY:
|
||||
self.assertEqual(exc.col, 13)
|
||||
else:
|
||||
self.assertEqual(exc.col, 16)
|
||||
exc = exceptions[2]
|
||||
self.assertEqual(exc.lineno, 6)
|
||||
self.assertEqual(exc.col, 13)
|
||||
|
||||
def test_indentationErrorInDoctest(self):
|
||||
exc = self.flakes('''
|
||||
def doctest_stuff():
|
||||
"""
|
||||
>>> if True:
|
||||
... pass
|
||||
"""
|
||||
''', m.DoctestSyntaxError).messages[0]
|
||||
self.assertEqual(exc.lineno, 5)
|
||||
self.assertEqual(exc.col, 13)
|
||||
|
||||
def test_offsetWithMultiLineArgs(self):
|
||||
(exc1, exc2) = self.flakes(
|
||||
'''
|
||||
def doctest_stuff(arg1,
|
||||
arg2,
|
||||
arg3):
|
||||
"""
|
||||
>>> assert
|
||||
>>> this
|
||||
"""
|
||||
''',
|
||||
m.DoctestSyntaxError,
|
||||
m.UndefinedName).messages
|
||||
self.assertEqual(exc1.lineno, 6)
|
||||
self.assertEqual(exc1.col, 19)
|
||||
self.assertEqual(exc2.lineno, 7)
|
||||
self.assertEqual(exc2.col, 12)
|
||||
|
||||
def test_doctestCanReferToFunction(self):
|
||||
self.flakes("""
|
||||
def foo():
|
||||
'''
|
||||
>>> foo
|
||||
'''
|
||||
""")
|
||||
|
||||
def test_doctestCanReferToClass(self):
|
||||
self.flakes("""
|
||||
class Foo():
|
||||
'''
|
||||
>>> Foo
|
||||
'''
|
||||
def bar(self):
|
||||
'''
|
||||
>>> Foo
|
||||
'''
|
||||
""")
|
||||
|
||||
def test_noOffsetSyntaxErrorInDoctest(self):
|
||||
exceptions = self.flakes(
|
||||
'''
|
||||
def buildurl(base, *args, **kwargs):
|
||||
"""
|
||||
>>> buildurl('/blah.php', ('a', '&'), ('b', '=')
|
||||
'/blah.php?a=%26&b=%3D'
|
||||
>>> buildurl('/blah.php', a='&', 'b'='=')
|
||||
'/blah.php?b=%3D&a=%26'
|
||||
"""
|
||||
pass
|
||||
''',
|
||||
m.DoctestSyntaxError,
|
||||
m.DoctestSyntaxError).messages
|
||||
exc = exceptions[0]
|
||||
self.assertEqual(exc.lineno, 4)
|
||||
exc = exceptions[1]
|
||||
self.assertEqual(exc.lineno, 6)
|
||||
|
||||
def test_singleUnderscoreInDoctest(self):
|
||||
self.flakes('''
|
||||
def func():
|
||||
"""A docstring
|
||||
|
||||
>>> func()
|
||||
1
|
||||
>>> _
|
||||
1
|
||||
"""
|
||||
return 1
|
||||
''')
|
||||
|
||||
def test_globalUnderscoreInDoctest(self):
|
||||
self.flakes("""
|
||||
from gettext import ugettext as _
|
||||
|
||||
def doctest_stuff():
|
||||
'''
|
||||
>>> pass
|
||||
'''
|
||||
""", m.UnusedImport)
|
||||
|
||||
|
||||
class TestOther(_DoctestMixin, TestOther):
|
||||
"""Run TestOther with each test wrapped in a doctest."""
|
||||
|
||||
|
||||
class TestImports(_DoctestMixin, TestImports):
|
||||
"""Run TestImports with each test wrapped in a doctest."""
|
||||
|
||||
|
||||
class TestUndefinedNames(_DoctestMixin, TestUndefinedNames):
|
||||
"""Run TestUndefinedNames with each test wrapped in a doctest."""
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,222 @@
|
||||
from pyflakes.messages import IsLiteral
|
||||
from pyflakes.test.harness import TestCase
|
||||
|
||||
|
||||
class Test(TestCase):
|
||||
def test_is_str(self):
|
||||
self.flakes("""
|
||||
x = 'foo'
|
||||
if x is 'foo':
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_bytes(self):
|
||||
self.flakes("""
|
||||
x = b'foo'
|
||||
if x is b'foo':
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_unicode(self):
|
||||
self.flakes("""
|
||||
x = u'foo'
|
||||
if x is u'foo':
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_int(self):
|
||||
self.flakes("""
|
||||
x = 10
|
||||
if x is 10:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_true(self):
|
||||
self.flakes("""
|
||||
x = True
|
||||
if x is True:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_is_false(self):
|
||||
self.flakes("""
|
||||
x = False
|
||||
if x is False:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_is_not_str(self):
|
||||
self.flakes("""
|
||||
x = 'foo'
|
||||
if x is not 'foo':
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_not_bytes(self):
|
||||
self.flakes("""
|
||||
x = b'foo'
|
||||
if x is not b'foo':
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_not_unicode(self):
|
||||
self.flakes("""
|
||||
x = u'foo'
|
||||
if x is not u'foo':
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_not_int(self):
|
||||
self.flakes("""
|
||||
x = 10
|
||||
if x is not 10:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_not_true(self):
|
||||
self.flakes("""
|
||||
x = True
|
||||
if x is not True:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_is_not_false(self):
|
||||
self.flakes("""
|
||||
x = False
|
||||
if x is not False:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_left_is_str(self):
|
||||
self.flakes("""
|
||||
x = 'foo'
|
||||
if 'foo' is x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_bytes(self):
|
||||
self.flakes("""
|
||||
x = b'foo'
|
||||
if b'foo' is x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_unicode(self):
|
||||
self.flakes("""
|
||||
x = u'foo'
|
||||
if u'foo' is x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_int(self):
|
||||
self.flakes("""
|
||||
x = 10
|
||||
if 10 is x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_true(self):
|
||||
self.flakes("""
|
||||
x = True
|
||||
if True is x:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_left_is_false(self):
|
||||
self.flakes("""
|
||||
x = False
|
||||
if False is x:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_left_is_not_str(self):
|
||||
self.flakes("""
|
||||
x = 'foo'
|
||||
if 'foo' is not x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_not_bytes(self):
|
||||
self.flakes("""
|
||||
x = b'foo'
|
||||
if b'foo' is not x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_not_unicode(self):
|
||||
self.flakes("""
|
||||
x = u'foo'
|
||||
if u'foo' is not x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_not_int(self):
|
||||
self.flakes("""
|
||||
x = 10
|
||||
if 10 is not x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_not_true(self):
|
||||
self.flakes("""
|
||||
x = True
|
||||
if True is not x:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_left_is_not_false(self):
|
||||
self.flakes("""
|
||||
x = False
|
||||
if False is not x:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_chained_operators_is_true(self):
|
||||
self.flakes("""
|
||||
x = 5
|
||||
if x is True < 4:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_chained_operators_is_str(self):
|
||||
self.flakes("""
|
||||
x = 5
|
||||
if x is 'foo' < 4:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_chained_operators_is_true_end(self):
|
||||
self.flakes("""
|
||||
x = 5
|
||||
if 4 < x is True:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_chained_operators_is_str_end(self):
|
||||
self.flakes("""
|
||||
x = 5
|
||||
if 4 < x is 'foo':
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_tuple_constant(self):
|
||||
self.flakes('''\
|
||||
x = 5
|
||||
if x is ():
|
||||
pass
|
||||
''', IsLiteral)
|
||||
|
||||
def test_is_tuple_constant_containing_constants(self):
|
||||
self.flakes('''\
|
||||
x = 5
|
||||
if x is (1, '2', True, (1.5, ())):
|
||||
pass
|
||||
''', IsLiteral)
|
||||
|
||||
def test_is_tuple_containing_variables_ok(self):
|
||||
# a bit nonsensical, but does not trigger a SyntaxWarning
|
||||
self.flakes('''\
|
||||
x = 5
|
||||
if x is (x,):
|
||||
pass
|
||||
''')
|
||||
@@ -0,0 +1,94 @@
|
||||
from sys import version_info
|
||||
|
||||
from pyflakes.test.harness import TestCase, skipIf
|
||||
|
||||
|
||||
@skipIf(version_info < (3, 10), "Python >= 3.10 only")
|
||||
class TestMatch(TestCase):
|
||||
def test_match_bindings(self):
|
||||
self.flakes('''
|
||||
def f():
|
||||
x = 1
|
||||
match x:
|
||||
case 1 as y:
|
||||
print(f'matched as {y}')
|
||||
''')
|
||||
self.flakes('''
|
||||
def f():
|
||||
x = [1, 2, 3]
|
||||
match x:
|
||||
case [1, y, 3]:
|
||||
print(f'matched {y}')
|
||||
''')
|
||||
self.flakes('''
|
||||
def f():
|
||||
x = {'foo': 1}
|
||||
match x:
|
||||
case {'foo': y}:
|
||||
print(f'matched {y}')
|
||||
''')
|
||||
|
||||
def test_match_pattern_matched_class(self):
|
||||
self.flakes('''
|
||||
from a import B
|
||||
|
||||
match 1:
|
||||
case B(x=1) as y:
|
||||
print(f'matched {y}')
|
||||
''')
|
||||
self.flakes('''
|
||||
from a import B
|
||||
|
||||
match 1:
|
||||
case B(a, x=z) as y:
|
||||
print(f'matched {y} {a} {z}')
|
||||
''')
|
||||
|
||||
def test_match_placeholder(self):
|
||||
self.flakes('''
|
||||
def f():
|
||||
match 1:
|
||||
case _:
|
||||
print('catchall!')
|
||||
''')
|
||||
|
||||
def test_match_singleton(self):
|
||||
self.flakes('''
|
||||
match 1:
|
||||
case True:
|
||||
print('true')
|
||||
''')
|
||||
|
||||
def test_match_or_pattern(self):
|
||||
self.flakes('''
|
||||
match 1:
|
||||
case 1 | 2:
|
||||
print('one or two')
|
||||
''')
|
||||
|
||||
def test_match_star(self):
|
||||
self.flakes('''
|
||||
x = [1, 2, 3]
|
||||
match x:
|
||||
case [1, *y]:
|
||||
print(f'captured: {y}')
|
||||
''')
|
||||
|
||||
def test_match_double_star(self):
|
||||
self.flakes('''
|
||||
x = {'foo': 'bar', 'baz': 'womp'}
|
||||
match x:
|
||||
case {'foo': k1, **rest}:
|
||||
print(f'{k1=} {rest=}')
|
||||
''')
|
||||
|
||||
def test_defined_in_different_branches(self):
|
||||
self.flakes('''
|
||||
def f(x):
|
||||
match x:
|
||||
case 1:
|
||||
def y(): pass
|
||||
case _:
|
||||
def y(): print(1)
|
||||
return y
|
||||
''')
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,825 @@
|
||||
"""
|
||||
Tests for behaviour related to type annotations.
|
||||
"""
|
||||
|
||||
from sys import version_info
|
||||
|
||||
from pyflakes import messages as m
|
||||
from pyflakes.test.harness import TestCase, skipIf
|
||||
|
||||
|
||||
class TestTypeAnnotations(TestCase):
|
||||
|
||||
def test_typingOverload(self):
|
||||
"""Allow intentional redefinitions via @typing.overload"""
|
||||
self.flakes("""
|
||||
import typing
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def f(s: None) -> None:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def f(s: int) -> int:
|
||||
pass
|
||||
|
||||
def f(s):
|
||||
return s
|
||||
|
||||
@typing.overload
|
||||
def g(s: None) -> None:
|
||||
pass
|
||||
|
||||
@typing.overload
|
||||
def g(s: int) -> int:
|
||||
pass
|
||||
|
||||
def g(s):
|
||||
return s
|
||||
""")
|
||||
|
||||
def test_typingExtensionsOverload(self):
|
||||
"""Allow intentional redefinitions via @typing_extensions.overload"""
|
||||
self.flakes("""
|
||||
import typing_extensions
|
||||
from typing_extensions import overload
|
||||
|
||||
@overload
|
||||
def f(s: None) -> None:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def f(s: int) -> int:
|
||||
pass
|
||||
|
||||
def f(s):
|
||||
return s
|
||||
|
||||
@typing_extensions.overload
|
||||
def g(s: None) -> None:
|
||||
pass
|
||||
|
||||
@typing_extensions.overload
|
||||
def g(s: int) -> int:
|
||||
pass
|
||||
|
||||
def g(s):
|
||||
return s
|
||||
""")
|
||||
|
||||
def test_typingOverloadAsync(self):
|
||||
"""Allow intentional redefinitions via @typing.overload (async)"""
|
||||
self.flakes("""
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
async def f(s: None) -> None:
|
||||
pass
|
||||
|
||||
@overload
|
||||
async def f(s: int) -> int:
|
||||
pass
|
||||
|
||||
async def f(s):
|
||||
return s
|
||||
""")
|
||||
|
||||
def test_overload_with_multiple_decorators(self):
|
||||
self.flakes("""
|
||||
from typing import overload
|
||||
dec = lambda f: f
|
||||
|
||||
@dec
|
||||
@overload
|
||||
def f(x: int) -> int:
|
||||
pass
|
||||
|
||||
@dec
|
||||
@overload
|
||||
def f(x: str) -> str:
|
||||
pass
|
||||
|
||||
@dec
|
||||
def f(x): return x
|
||||
""")
|
||||
|
||||
def test_overload_in_class(self):
|
||||
self.flakes("""
|
||||
from typing import overload
|
||||
|
||||
class C:
|
||||
@overload
|
||||
def f(self, x: int) -> int:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def f(self, x: str) -> str:
|
||||
pass
|
||||
|
||||
def f(self, x): return x
|
||||
""")
|
||||
|
||||
def test_aliased_import(self):
|
||||
"""Detect when typing is imported as another name"""
|
||||
self.flakes("""
|
||||
import typing as t
|
||||
|
||||
@t.overload
|
||||
def f(s: None) -> None:
|
||||
pass
|
||||
|
||||
@t.overload
|
||||
def f(s: int) -> int:
|
||||
pass
|
||||
|
||||
def f(s):
|
||||
return s
|
||||
""")
|
||||
|
||||
def test_not_a_typing_overload(self):
|
||||
"""regression test for @typing.overload detection bug in 2.1.0"""
|
||||
self.flakes("""
|
||||
def foo(x):
|
||||
return x
|
||||
|
||||
@foo
|
||||
def bar():
|
||||
pass
|
||||
|
||||
def bar():
|
||||
pass
|
||||
""", m.RedefinedWhileUnused)
|
||||
|
||||
def test_variable_annotations(self):
|
||||
def undefined_names_before_py314(*, n: int):
|
||||
return (m.UndefinedName,) * n if version_info < (3, 14) else ()
|
||||
|
||||
self.flakes('''
|
||||
name: str
|
||||
age: int
|
||||
''')
|
||||
self.flakes('''
|
||||
name: str = 'Bob'
|
||||
age: int = 18
|
||||
''')
|
||||
self.flakes('''
|
||||
class C:
|
||||
name: str
|
||||
age: int
|
||||
''')
|
||||
self.flakes('''
|
||||
class C:
|
||||
name: str = 'Bob'
|
||||
age: int = 18
|
||||
''')
|
||||
self.flakes('''
|
||||
def f():
|
||||
name: str
|
||||
age: int
|
||||
''', m.UnusedAnnotation, m.UnusedAnnotation)
|
||||
self.flakes('''
|
||||
def f():
|
||||
name: str = 'Bob'
|
||||
age: int = 18
|
||||
foo: not_a_real_type = None
|
||||
''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable, m.UndefinedName)
|
||||
self.flakes('''
|
||||
def f():
|
||||
name: str
|
||||
print(name)
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
from typing import Any
|
||||
def f():
|
||||
a: Any
|
||||
''', m.UnusedAnnotation)
|
||||
self.flakes('''
|
||||
foo: not_a_real_type
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
foo: not_a_real_type = None
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
class C:
|
||||
foo: not_a_real_type
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
class C:
|
||||
foo: not_a_real_type = None
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
def f():
|
||||
class C:
|
||||
foo: not_a_real_type
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
def f():
|
||||
class C:
|
||||
foo: not_a_real_type = None
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
bar: Bar
|
||||
''')
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
bar: 'Bar'
|
||||
''')
|
||||
self.flakes('''
|
||||
import foo
|
||||
bar: foo.Bar
|
||||
''')
|
||||
self.flakes('''
|
||||
import foo
|
||||
bar: 'foo.Bar'
|
||||
''')
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
def f(bar: Bar): pass
|
||||
''')
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
def f(bar: 'Bar'): pass
|
||||
''')
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
def f(bar) -> Bar: return bar
|
||||
''')
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
def f(bar) -> 'Bar': return bar
|
||||
''')
|
||||
self.flakes('''
|
||||
bar: 'Bar'
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
bar: 'foo.Bar'
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
bar: str
|
||||
''', m.UnusedImport)
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
def f(bar: str): pass
|
||||
''', m.UnusedImport)
|
||||
self.flakes('''
|
||||
def f(a: A) -> A: pass
|
||||
class A: pass
|
||||
''', *undefined_names_before_py314(n=2))
|
||||
|
||||
self.flakes('''
|
||||
def f(a: 'A') -> 'A': return a
|
||||
class A: pass
|
||||
''')
|
||||
self.flakes('''
|
||||
a: A
|
||||
class A: pass
|
||||
''', *undefined_names_before_py314(n=1))
|
||||
self.flakes('''
|
||||
a: 'A'
|
||||
class A: pass
|
||||
''')
|
||||
self.flakes('''
|
||||
T: object
|
||||
def f(t: T): pass
|
||||
''', *undefined_names_before_py314(n=1))
|
||||
self.flakes('''
|
||||
T: object
|
||||
def g(t: 'T'): pass
|
||||
''')
|
||||
self.flakes('''
|
||||
a: 'A B'
|
||||
''', m.ForwardAnnotationSyntaxError)
|
||||
self.flakes('''
|
||||
a: 'A; B'
|
||||
''', m.ForwardAnnotationSyntaxError)
|
||||
self.flakes('''
|
||||
a: '1 + 2'
|
||||
''')
|
||||
self.flakes('''
|
||||
a: 'a: "A"'
|
||||
''', m.ForwardAnnotationSyntaxError)
|
||||
|
||||
def test_variable_annotation_references_self_name_undefined(self):
|
||||
self.flakes("""
|
||||
x: int = x
|
||||
""", m.UndefinedName)
|
||||
|
||||
def test_TypeAlias_annotations(self):
|
||||
self.flakes("""
|
||||
from typing_extensions import TypeAlias
|
||||
from foo import Bar
|
||||
|
||||
bar: TypeAlias = Bar
|
||||
""")
|
||||
self.flakes("""
|
||||
from typing_extensions import TypeAlias
|
||||
from foo import Bar
|
||||
|
||||
bar: TypeAlias = 'Bar'
|
||||
""")
|
||||
self.flakes("""
|
||||
from typing_extensions import TypeAlias
|
||||
from foo import Bar
|
||||
|
||||
class A:
|
||||
bar: TypeAlias = Bar
|
||||
""")
|
||||
self.flakes("""
|
||||
from typing_extensions import TypeAlias
|
||||
from foo import Bar
|
||||
|
||||
class A:
|
||||
bar: TypeAlias = 'Bar'
|
||||
""")
|
||||
self.flakes("""
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
bar: TypeAlias
|
||||
""")
|
||||
self.flakes("""
|
||||
from typing_extensions import TypeAlias
|
||||
from foo import Bar
|
||||
|
||||
bar: TypeAlias
|
||||
""", m.UnusedImport)
|
||||
|
||||
def test_annotating_an_import(self):
|
||||
self.flakes('''
|
||||
from a import b, c
|
||||
b: c
|
||||
print(b)
|
||||
''')
|
||||
|
||||
def test_unused_annotation(self):
|
||||
# Unused annotations are fine in module and class scope
|
||||
self.flakes('''
|
||||
x: int
|
||||
class Cls:
|
||||
y: int
|
||||
''')
|
||||
self.flakes('''
|
||||
def f():
|
||||
x: int
|
||||
''', m.UnusedAnnotation)
|
||||
# This should only print one UnusedVariable message
|
||||
self.flakes('''
|
||||
def f():
|
||||
x: int
|
||||
x = 3
|
||||
''', m.UnusedVariable)
|
||||
|
||||
def test_unused_annotation_in_outer_scope_reassigned_in_local_scope(self):
|
||||
self.flakes('''
|
||||
x: int
|
||||
x.__dict__
|
||||
def f(): x = 1
|
||||
''', m.UndefinedName, m.UnusedVariable)
|
||||
|
||||
def test_unassigned_annotation_is_undefined(self):
|
||||
self.flakes('''
|
||||
name: str
|
||||
print(name)
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_annotated_async_def(self):
|
||||
self.flakes('''
|
||||
class c: pass
|
||||
async def func(c: c) -> None: pass
|
||||
''')
|
||||
|
||||
def test_postponed_annotations(self):
|
||||
self.flakes('''
|
||||
from __future__ import annotations
|
||||
def f(a: A) -> A: pass
|
||||
class A:
|
||||
b: B
|
||||
class B: pass
|
||||
''')
|
||||
|
||||
self.flakes('''
|
||||
from __future__ import annotations
|
||||
def f(a: A) -> A: pass
|
||||
class A:
|
||||
b: Undefined
|
||||
class B: pass
|
||||
''', m.UndefinedName)
|
||||
|
||||
self.flakes('''
|
||||
from __future__ import annotations
|
||||
T: object
|
||||
def f(t: T): pass
|
||||
def g(t: 'T'): pass
|
||||
''')
|
||||
|
||||
def test_annotations_do_not_define_names_with_future_annotations(self):
|
||||
self.flakes('''
|
||||
from __future__ import annotations
|
||||
def f():
|
||||
x: str
|
||||
print(x)
|
||||
''', m.UndefinedName)
|
||||
|
||||
@skipIf(version_info < (3, 14), 'new in Python 3.14')
|
||||
def test_postponed_annotations_py314(self):
|
||||
self.flakes('''
|
||||
def f(x: C) -> None: pass
|
||||
class C: pass
|
||||
''')
|
||||
|
||||
def test_type_annotation_clobbers_all(self):
|
||||
self.flakes('''\
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from y import z
|
||||
|
||||
if not TYPE_CHECKING:
|
||||
__all__ = ("z",)
|
||||
else:
|
||||
__all__: List[str]
|
||||
''')
|
||||
|
||||
def test_return_annotation_is_class_scope_variable(self):
|
||||
self.flakes("""
|
||||
from typing import TypeVar
|
||||
class Test:
|
||||
Y = TypeVar('Y')
|
||||
|
||||
def t(self, x: Y) -> Y:
|
||||
return x
|
||||
""")
|
||||
|
||||
def test_return_annotation_is_function_body_variable(self):
|
||||
self.flakes("""
|
||||
class Test:
|
||||
def t(self) -> Y:
|
||||
Y = 2
|
||||
return Y
|
||||
""", m.UndefinedName)
|
||||
|
||||
def test_positional_only_argument_annotations(self):
|
||||
self.flakes("""
|
||||
from x import C
|
||||
|
||||
def f(c: C, /): ...
|
||||
""")
|
||||
|
||||
def test_partially_quoted_type_annotation(self):
|
||||
self.flakes("""
|
||||
from queue import Queue
|
||||
from typing import Optional
|
||||
|
||||
def f() -> Optional['Queue[str]']:
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_partially_quoted_type_assignment(self):
|
||||
self.flakes("""
|
||||
from queue import Queue
|
||||
from typing import Optional
|
||||
|
||||
MaybeQueue = Optional['Queue[str]']
|
||||
""")
|
||||
|
||||
def test_nested_partially_quoted_type_assignment(self):
|
||||
self.flakes("""
|
||||
from queue import Queue
|
||||
from typing import Callable
|
||||
|
||||
Func = Callable[['Queue[str]'], None]
|
||||
""")
|
||||
|
||||
def test_quoted_type_cast(self):
|
||||
self.flakes("""
|
||||
from typing import cast, Optional
|
||||
|
||||
maybe_int = cast('Optional[int]', 42)
|
||||
""")
|
||||
|
||||
def test_type_cast_literal_str_to_str(self):
|
||||
# Checks that our handling of quoted type annotations in the first
|
||||
# argument to `cast` doesn't cause issues when (only) the _second_
|
||||
# argument is a literal str which looks a bit like a type annotation.
|
||||
self.flakes("""
|
||||
from typing import cast
|
||||
|
||||
a_string = cast(str, 'Optional[int]')
|
||||
""")
|
||||
|
||||
def test_quoted_type_cast_renamed_import(self):
|
||||
self.flakes("""
|
||||
from typing import cast as tsac, Optional as Maybe
|
||||
|
||||
maybe_int = tsac('Maybe[int]', 42)
|
||||
""")
|
||||
|
||||
def test_quoted_TypeVar_constraints(self):
|
||||
self.flakes("""
|
||||
from typing import TypeVar, Optional
|
||||
|
||||
T = TypeVar('T', 'str', 'Optional[int]', bytes)
|
||||
""")
|
||||
|
||||
def test_quoted_TypeVar_bound(self):
|
||||
self.flakes("""
|
||||
from typing import TypeVar, Optional, List
|
||||
|
||||
T = TypeVar('T', bound='Optional[int]')
|
||||
S = TypeVar('S', int, bound='List[int]')
|
||||
""")
|
||||
|
||||
def test_literal_type_typing(self):
|
||||
self.flakes("""
|
||||
from typing import Literal
|
||||
|
||||
def f(x: Literal['some string']) -> None:
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_literal_type_typing_extensions(self):
|
||||
self.flakes("""
|
||||
from typing_extensions import Literal
|
||||
|
||||
def f(x: Literal['some string']) -> None:
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_annotated_type_typing_missing_forward_type(self):
|
||||
self.flakes("""
|
||||
from typing import Annotated
|
||||
|
||||
def f(x: Annotated['integer']) -> None:
|
||||
return None
|
||||
""", m.UndefinedName)
|
||||
|
||||
def test_annotated_type_typing_missing_forward_type_multiple_args(self):
|
||||
self.flakes("""
|
||||
from typing import Annotated
|
||||
|
||||
def f(x: Annotated['integer', 1]) -> None:
|
||||
return None
|
||||
""", m.UndefinedName)
|
||||
|
||||
def test_annotated_type_typing_with_string_args(self):
|
||||
self.flakes("""
|
||||
from typing import Annotated
|
||||
|
||||
def f(x: Annotated[int, '> 0']) -> None:
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_annotated_type_typing_with_string_args_in_union(self):
|
||||
self.flakes("""
|
||||
from typing import Annotated, Union
|
||||
|
||||
def f(x: Union[Annotated['int', '>0'], 'integer']) -> None:
|
||||
return None
|
||||
""", m.UndefinedName)
|
||||
|
||||
def test_literal_type_some_other_module(self):
|
||||
"""err on the side of false-negatives for types named Literal"""
|
||||
self.flakes("""
|
||||
from my_module import compat
|
||||
from my_module.compat import Literal
|
||||
|
||||
def f(x: compat.Literal['some string']) -> None:
|
||||
return None
|
||||
def g(x: Literal['some string']) -> None:
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_literal_union_type_typing(self):
|
||||
self.flakes("""
|
||||
from typing import Literal
|
||||
|
||||
def f(x: Literal['some string', 'foo bar']) -> None:
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_deferred_twice_annotation(self):
|
||||
self.flakes("""
|
||||
from queue import Queue
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def f() -> "Optional['Queue[str]']":
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_partial_string_annotations_with_future_annotations(self):
|
||||
self.flakes("""
|
||||
from __future__ import annotations
|
||||
|
||||
from queue import Queue
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def f() -> Optional['Queue[str]']:
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_forward_annotations_for_classes_in_scope(self):
|
||||
# see #749
|
||||
self.flakes("""
|
||||
from typing import Optional
|
||||
|
||||
def f():
|
||||
class C:
|
||||
a: "D"
|
||||
b: Optional["D"]
|
||||
c: "Optional[D]"
|
||||
|
||||
class D: pass
|
||||
""")
|
||||
|
||||
def test_idomiatic_typing_guards(self):
|
||||
# typing.TYPE_CHECKING: python3.5.3+
|
||||
self.flakes("""
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from t import T
|
||||
|
||||
def f() -> T:
|
||||
pass
|
||||
""")
|
||||
# False: the old, more-compatible approach
|
||||
self.flakes("""
|
||||
if False:
|
||||
from t import T
|
||||
|
||||
def f() -> T:
|
||||
pass
|
||||
""")
|
||||
# some choose to assign a constant and do it that way
|
||||
self.flakes("""
|
||||
MYPY = False
|
||||
|
||||
if MYPY:
|
||||
from t import T
|
||||
|
||||
def f() -> T:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_typing_guard_for_protocol(self):
|
||||
self.flakes("""
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Protocol
|
||||
else:
|
||||
Protocol = object
|
||||
|
||||
class C(Protocol):
|
||||
def f() -> int:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_typednames_correct_forward_ref(self):
|
||||
self.flakes("""
|
||||
from typing import TypedDict, List, NamedTuple
|
||||
|
||||
List[TypedDict("x", {})]
|
||||
List[TypedDict("x", x=int)]
|
||||
List[NamedTuple("a", a=int)]
|
||||
List[NamedTuple("a", [("a", int)])]
|
||||
""")
|
||||
self.flakes("""
|
||||
from typing import TypedDict, List, NamedTuple, TypeVar
|
||||
|
||||
List[TypedDict("x", {"x": "Y"})]
|
||||
List[TypedDict("x", x="Y")]
|
||||
List[NamedTuple("a", [("a", "Y")])]
|
||||
List[NamedTuple("a", a="Y")]
|
||||
List[TypedDict("x", {"x": List["a"]})]
|
||||
List[TypeVar("A", bound="C")]
|
||||
List[TypeVar("A", List["C"])]
|
||||
""", *[m.UndefinedName]*7)
|
||||
self.flakes("""
|
||||
from typing import NamedTuple, TypeVar, cast
|
||||
from t import A, B, C, D, E
|
||||
|
||||
NamedTuple("A", [("a", A["C"])])
|
||||
TypeVar("A", bound=A["B"])
|
||||
TypeVar("A", A["D"])
|
||||
cast(A["E"], [])
|
||||
""")
|
||||
|
||||
def test_namedtypes_classes(self):
|
||||
self.flakes("""
|
||||
from typing import TypedDict, NamedTuple
|
||||
class X(TypedDict):
|
||||
y: TypedDict("z", {"zz":int})
|
||||
|
||||
class Y(NamedTuple):
|
||||
y: NamedTuple("v", [("vv", int)])
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 11), 'new in Python 3.11')
|
||||
def test_variadic_generics(self):
|
||||
self.flakes("""
|
||||
from typing import Generic
|
||||
from typing import TypeVarTuple
|
||||
|
||||
Ts = TypeVarTuple('Ts')
|
||||
|
||||
class Shape(Generic[*Ts]): pass
|
||||
|
||||
def f(*args: *Ts) -> None: ...
|
||||
|
||||
def g(x: Shape[*Ts]) -> Shape[*Ts]: ...
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 12), 'new in Python 3.12')
|
||||
def test_type_statements(self):
|
||||
self.flakes("""
|
||||
type ListOrSet[T] = list[T] | set[T]
|
||||
|
||||
def f(x: ListOrSet[str]) -> None: ...
|
||||
|
||||
type RecursiveType = int | list[RecursiveType]
|
||||
|
||||
type ForwardRef = int | C
|
||||
|
||||
type ForwardRefInBounds[T: C] = T
|
||||
|
||||
class C: pass
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 12), 'new in Python 3.12')
|
||||
def test_type_parameters_functions(self):
|
||||
self.flakes("""
|
||||
def f[T](t: T) -> T: return t
|
||||
|
||||
async def g[T](t: T) -> T: return t
|
||||
|
||||
def with_forward_ref[T: C](t: T) -> T: return t
|
||||
|
||||
def can_access_inside[T](t: T) -> T:
|
||||
print(T)
|
||||
return t
|
||||
|
||||
class C: pass
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 12), 'new in Python 3.12')
|
||||
def test_type_parameters_do_not_escape_function_scopes(self):
|
||||
self.flakes("""
|
||||
from x import g
|
||||
|
||||
@g(T) # not accessible in decorators
|
||||
def f[T](t: T) -> T: return t
|
||||
|
||||
T # not accessible afterwards
|
||||
""", m.UndefinedName, m.UndefinedName)
|
||||
|
||||
@skipIf(version_info < (3, 12), 'new in Python 3.12')
|
||||
def test_type_parameters_classes(self):
|
||||
self.flakes("""
|
||||
class C[T](list[T]): pass
|
||||
|
||||
class UsesForward[T: Forward](list[T]): pass
|
||||
|
||||
class Forward: pass
|
||||
|
||||
class WithinBody[T](list[T]):
|
||||
t = T
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 12), 'new in Python 3.12')
|
||||
def test_type_parameters_do_not_escape_class_scopes(self):
|
||||
self.flakes("""
|
||||
from x import g
|
||||
|
||||
@g(T) # not accessible in decorators
|
||||
class C[T](list[T]): pass
|
||||
|
||||
T # not accessible afterwards
|
||||
""", m.UndefinedName, m.UndefinedName)
|
||||
|
||||
@skipIf(version_info < (3, 12), 'new in Python 3.12')
|
||||
def test_type_parameters_TypeVarTuple(self):
|
||||
self.flakes("""
|
||||
def f[*T](*args: *T) -> None: ...
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 12), 'new in Python 3.12')
|
||||
def test_type_parameters_ParamSpec(self):
|
||||
self.flakes("""
|
||||
from typing import Callable
|
||||
|
||||
def f[R, **P](f: Callable[P, R]) -> Callable[P, R]:
|
||||
def g(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
return f(*args, **kwargs)
|
||||
return g
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 13), 'new in Python 3.13')
|
||||
def test_type_parameter_defaults(self):
|
||||
self.flakes("""
|
||||
def f[T = int](u: T) -> T:
|
||||
return u
|
||||
""")
|
||||
@@ -0,0 +1,819 @@
|
||||
import ast
|
||||
|
||||
from pyflakes import messages as m, checker
|
||||
from pyflakes.test.harness import TestCase, skip
|
||||
|
||||
|
||||
class Test(TestCase):
|
||||
def test_undefined(self):
|
||||
self.flakes('bar', m.UndefinedName)
|
||||
|
||||
def test_definedInListComp(self):
|
||||
self.flakes('[a for a in range(10) if a]')
|
||||
|
||||
def test_undefinedInListComp(self):
|
||||
self.flakes('''
|
||||
[a for a in range(10)]
|
||||
a
|
||||
''',
|
||||
m.UndefinedName)
|
||||
|
||||
def test_undefinedExceptionName(self):
|
||||
"""Exception names can't be used after the except: block.
|
||||
|
||||
The exc variable is unused inside the exception handler."""
|
||||
self.flakes('''
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
pass
|
||||
exc
|
||||
''', m.UndefinedName, m.UnusedVariable)
|
||||
|
||||
def test_namesDeclaredInExceptBlocks(self):
|
||||
"""Locals declared in except: blocks can be used after the block.
|
||||
|
||||
This shows the example in test_undefinedExceptionName is
|
||||
different."""
|
||||
self.flakes('''
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
e = exc
|
||||
e
|
||||
''')
|
||||
|
||||
@skip('error reporting disabled due to false positives below')
|
||||
def test_undefinedExceptionNameObscuringLocalVariable(self):
|
||||
"""Exception names obscure locals, can't be used after.
|
||||
|
||||
Last line will raise UnboundLocalError on Python 3 after exiting
|
||||
the except: block. Note next two examples for false positives to
|
||||
watch out for."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
pass
|
||||
exc
|
||||
''',
|
||||
m.UndefinedName)
|
||||
|
||||
def test_undefinedExceptionNameObscuringLocalVariable2(self):
|
||||
"""Exception names are unbound after the `except:` block.
|
||||
|
||||
Last line will raise UnboundLocalError.
|
||||
The exc variable is unused inside the exception handler.
|
||||
"""
|
||||
self.flakes('''
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
pass
|
||||
print(exc)
|
||||
exc = 'Original value'
|
||||
''', m.UndefinedName, m.UnusedVariable)
|
||||
|
||||
def test_undefinedExceptionNameObscuringLocalVariableFalsePositive1(self):
|
||||
"""Exception names obscure locals, can't be used after. Unless.
|
||||
|
||||
Last line will never raise UnboundLocalError because it's only
|
||||
entered if no exception was raised."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
print('exception logged')
|
||||
raise
|
||||
exc
|
||||
''', m.UnusedVariable)
|
||||
|
||||
def test_delExceptionInExcept(self):
|
||||
"""The exception name can be deleted in the except: block."""
|
||||
self.flakes('''
|
||||
try:
|
||||
pass
|
||||
except Exception as exc:
|
||||
del exc
|
||||
''')
|
||||
|
||||
def test_undefinedExceptionNameObscuringLocalVariableFalsePositive2(self):
|
||||
"""Exception names obscure locals, can't be used after. Unless.
|
||||
|
||||
Last line will never raise UnboundLocalError because `error` is
|
||||
only falsy if the `except:` block has not been entered."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
error = None
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
error = 'exception logged'
|
||||
if error:
|
||||
print(error)
|
||||
else:
|
||||
exc
|
||||
''', 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 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():
|
||||
try:
|
||||
pass # nothing is raised
|
||||
except ValueError as exc:
|
||||
pass # block never entered, exc stays unbound
|
||||
exc
|
||||
''',
|
||||
m.UndefinedLocal)
|
||||
|
||||
@skip('error reporting disabled due to false positives below')
|
||||
def test_undefinedExceptionNameObscuringGlobalVariable2(self):
|
||||
"""Exception names obscure globals, can't be used after.
|
||||
|
||||
Last line will raise NameError on Python 3 because the name is
|
||||
locally unbound after the `except:` block, even if it's
|
||||
nonlocal. We should issue an error in this case because code
|
||||
only working correctly if an exception isn't raised, is invalid.
|
||||
Unless it's explicitly silenced, see false positives below."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
def func():
|
||||
global exc
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
pass # block never entered, exc stays unbound
|
||||
exc
|
||||
''',
|
||||
m.UndefinedLocal)
|
||||
|
||||
def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive1(self):
|
||||
"""Exception names obscure globals, can't be used after. Unless.
|
||||
|
||||
Last line will never raise NameError because it's only entered
|
||||
if no exception was raised."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
def func():
|
||||
global exc
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
print('exception logged')
|
||||
raise
|
||||
exc
|
||||
''', 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."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
def func():
|
||||
global exc
|
||||
error = None
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
error = 'exception logged'
|
||||
if error:
|
||||
print(error)
|
||||
else:
|
||||
exc
|
||||
''', m.UnusedVariable)
|
||||
|
||||
def test_functionsNeedGlobalScope(self):
|
||||
self.flakes('''
|
||||
class a:
|
||||
def b():
|
||||
fu
|
||||
fu = 1
|
||||
''')
|
||||
|
||||
def test_builtins(self):
|
||||
self.flakes('range(10)')
|
||||
|
||||
def test_builtinWindowsError(self):
|
||||
"""
|
||||
C{WindowsError} is sometimes a builtin name, so no warning is emitted
|
||||
for using it.
|
||||
"""
|
||||
self.flakes('WindowsError')
|
||||
|
||||
def test_moduleAnnotations(self):
|
||||
"""
|
||||
Use of the C{__annotations__} in module scope should not emit
|
||||
an undefined name warning when version is greater than or equal to 3.6.
|
||||
"""
|
||||
self.flakes('__annotations__')
|
||||
|
||||
def test_magicGlobalsFile(self):
|
||||
"""
|
||||
Use of the C{__file__} magic global should not emit an undefined name
|
||||
warning.
|
||||
"""
|
||||
self.flakes('__file__')
|
||||
|
||||
def test_magicGlobalsBuiltins(self):
|
||||
"""
|
||||
Use of the C{__builtins__} magic global should not emit an undefined
|
||||
name warning.
|
||||
"""
|
||||
self.flakes('__builtins__')
|
||||
|
||||
def test_magicGlobalsName(self):
|
||||
"""
|
||||
Use of the C{__name__} magic global should not emit an undefined name
|
||||
warning.
|
||||
"""
|
||||
self.flakes('__name__')
|
||||
|
||||
def test_magicGlobalsPath(self):
|
||||
"""
|
||||
Use of the C{__path__} magic global should not emit an undefined name
|
||||
warning, if you refer to it from a file called __init__.py.
|
||||
"""
|
||||
self.flakes('__path__', m.UndefinedName)
|
||||
self.flakes('__path__', filename='package/__init__.py')
|
||||
|
||||
def test_magicModuleInClassScope(self):
|
||||
"""
|
||||
Use of the C{__module__} magic builtin should not emit an undefined
|
||||
name warning if used in class scope.
|
||||
"""
|
||||
self.flakes('__module__', m.UndefinedName)
|
||||
self.flakes('''
|
||||
class Foo:
|
||||
__module__
|
||||
''')
|
||||
self.flakes('''
|
||||
class Foo:
|
||||
def bar(self):
|
||||
__module__
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_magicQualnameInClassScope(self):
|
||||
"""
|
||||
Use of the C{__qualname__} magic builtin should not emit an undefined
|
||||
name warning if used in class scope.
|
||||
"""
|
||||
self.flakes('__qualname__', m.UndefinedName)
|
||||
self.flakes('''
|
||||
class Foo:
|
||||
__qualname__
|
||||
''')
|
||||
self.flakes('''
|
||||
class Foo:
|
||||
def bar(self):
|
||||
__qualname__
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_globalImportStar(self):
|
||||
"""Can't find undefined names with import *."""
|
||||
self.flakes('from fu import *; bar',
|
||||
m.ImportStarUsed, m.ImportStarUsage)
|
||||
|
||||
def test_definedByGlobal(self):
|
||||
"""
|
||||
"global" can make an otherwise undefined name in another function
|
||||
defined.
|
||||
"""
|
||||
self.flakes('''
|
||||
def a(): global fu; fu = 1
|
||||
def b(): fu
|
||||
''')
|
||||
self.flakes('''
|
||||
def c(): bar
|
||||
def b(): global bar; bar = 1
|
||||
''')
|
||||
|
||||
def test_definedByGlobalMultipleNames(self):
|
||||
"""
|
||||
"global" can accept multiple names.
|
||||
"""
|
||||
self.flakes('''
|
||||
def a(): global fu, bar; fu = 1; bar = 2
|
||||
def b(): fu; bar
|
||||
''')
|
||||
|
||||
def test_globalInGlobalScope(self):
|
||||
"""
|
||||
A global statement in the global scope is ignored.
|
||||
"""
|
||||
self.flakes('''
|
||||
global x
|
||||
def foo():
|
||||
print(x)
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_global_reset_name_only(self):
|
||||
"""A global statement does not prevent other names being undefined."""
|
||||
# Only different undefined names are reported.
|
||||
# See following test that fails where the same name is used.
|
||||
self.flakes('''
|
||||
def f1():
|
||||
s
|
||||
|
||||
def f2():
|
||||
global m
|
||||
''', m.UndefinedName, m.UnusedIndirectAssignment)
|
||||
|
||||
@skip("todo")
|
||||
def test_unused_global(self):
|
||||
"""An unused global statement does not define the name."""
|
||||
self.flakes('''
|
||||
def f1():
|
||||
m
|
||||
|
||||
def f2():
|
||||
global m
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_del(self):
|
||||
"""Del deletes bindings."""
|
||||
self.flakes('a = 1; del a; a', m.UndefinedName)
|
||||
|
||||
def test_delGlobal(self):
|
||||
"""Del a global binding from a function."""
|
||||
self.flakes('''
|
||||
a = 1
|
||||
def f():
|
||||
global a
|
||||
del a
|
||||
a
|
||||
''')
|
||||
|
||||
def test_delUndefined(self):
|
||||
"""Del an undefined name."""
|
||||
self.flakes('del a', m.UndefinedName)
|
||||
|
||||
def test_delConditional(self):
|
||||
"""
|
||||
Ignores conditional bindings deletion.
|
||||
"""
|
||||
self.flakes('''
|
||||
context = None
|
||||
test = True
|
||||
if False:
|
||||
del(test)
|
||||
assert(test)
|
||||
''')
|
||||
|
||||
def test_delConditionalNested(self):
|
||||
"""
|
||||
Ignored conditional bindings deletion even if they are nested in other
|
||||
blocks.
|
||||
"""
|
||||
self.flakes('''
|
||||
context = None
|
||||
test = True
|
||||
if False:
|
||||
with context():
|
||||
del(test)
|
||||
assert(test)
|
||||
''')
|
||||
|
||||
def test_delWhile(self):
|
||||
"""
|
||||
Ignore bindings deletion if called inside the body of a while
|
||||
statement.
|
||||
"""
|
||||
self.flakes('''
|
||||
def test():
|
||||
foo = 'bar'
|
||||
while False:
|
||||
del foo
|
||||
assert(foo)
|
||||
''')
|
||||
|
||||
def test_delWhileTestUsage(self):
|
||||
"""
|
||||
Ignore bindings deletion if called inside the body of a while
|
||||
statement and name is used inside while's test part.
|
||||
"""
|
||||
self.flakes('''
|
||||
def _worker():
|
||||
o = True
|
||||
while o is not True:
|
||||
del o
|
||||
o = False
|
||||
''')
|
||||
|
||||
def test_delWhileNested(self):
|
||||
"""
|
||||
Ignore bindings deletions if node is part of while's test, even when
|
||||
del is in a nested block.
|
||||
"""
|
||||
self.flakes('''
|
||||
context = None
|
||||
def _worker():
|
||||
o = True
|
||||
while o is not True:
|
||||
while True:
|
||||
with context():
|
||||
del o
|
||||
o = False
|
||||
''')
|
||||
|
||||
def test_globalFromNestedScope(self):
|
||||
"""Global names are available from nested scopes."""
|
||||
self.flakes('''
|
||||
a = 1
|
||||
def b():
|
||||
def c():
|
||||
a
|
||||
''')
|
||||
|
||||
def test_laterRedefinedGlobalFromNestedScope(self):
|
||||
"""
|
||||
Test that referencing a local name that shadows a global, before it is
|
||||
defined, generates a warning.
|
||||
"""
|
||||
self.flakes('''
|
||||
a = 1
|
||||
def fun():
|
||||
a
|
||||
a = 2
|
||||
return a
|
||||
''', m.UndefinedLocal)
|
||||
|
||||
def test_laterRedefinedGlobalFromNestedScope2(self):
|
||||
"""
|
||||
Test that referencing a local name in a nested scope that shadows a
|
||||
global declared in an enclosing scope, before it is defined, generates
|
||||
a warning.
|
||||
"""
|
||||
self.flakes('''
|
||||
a = 1
|
||||
def fun():
|
||||
global a
|
||||
def fun2():
|
||||
a
|
||||
a = 2
|
||||
return a
|
||||
''', m.UndefinedLocal, m.UnusedIndirectAssignment)
|
||||
|
||||
def test_intermediateClassScopeIgnored(self):
|
||||
"""
|
||||
If a name defined in an enclosing scope is shadowed by a local variable
|
||||
and the name is used locally before it is bound, an unbound local
|
||||
warning is emitted, even if there is a class scope between the enclosing
|
||||
scope and the local scope.
|
||||
"""
|
||||
self.flakes('''
|
||||
def f():
|
||||
x = 1
|
||||
class g:
|
||||
def h(self):
|
||||
a = x
|
||||
x = None
|
||||
print(x, a)
|
||||
print(x)
|
||||
''', m.UndefinedLocal)
|
||||
|
||||
def test_doubleNestingReportsClosestName(self):
|
||||
"""
|
||||
Test that referencing a local name in a nested scope that shadows a
|
||||
variable declared in two different outer scopes before it is defined
|
||||
in the innermost scope generates an UnboundLocal warning which
|
||||
refers to the nearest shadowed name.
|
||||
"""
|
||||
exc = self.flakes('''
|
||||
def a():
|
||||
x = 1
|
||||
def b():
|
||||
x = 2 # line 5
|
||||
def c():
|
||||
x
|
||||
x = 3
|
||||
return x
|
||||
return x
|
||||
return x
|
||||
''', m.UndefinedLocal).messages[0]
|
||||
|
||||
# _DoctestMixin.flakes adds two lines preceding the code above.
|
||||
expected_line_num = 7 if self.withDoctest else 5
|
||||
|
||||
self.assertEqual(exc.message_args, ('x', expected_line_num))
|
||||
|
||||
def test_laterRedefinedGlobalFromNestedScope3(self):
|
||||
"""
|
||||
Test that referencing a local name in a nested scope that shadows a
|
||||
global, before it is defined, generates a warning.
|
||||
"""
|
||||
self.flakes('''
|
||||
def fun():
|
||||
a = 1
|
||||
def fun2():
|
||||
a
|
||||
a = 1
|
||||
return a
|
||||
return a
|
||||
''', m.UndefinedLocal)
|
||||
|
||||
def test_undefinedAugmentedAssignment(self):
|
||||
self.flakes(
|
||||
'''
|
||||
def f(seq):
|
||||
a = 0
|
||||
seq[a] += 1
|
||||
seq[b] /= 2
|
||||
c[0] *= 2
|
||||
a -= 3
|
||||
d += 4
|
||||
e[any] = 5
|
||||
''',
|
||||
m.UndefinedName, # b
|
||||
m.UndefinedName, # c
|
||||
m.UndefinedName, m.UnusedVariable, # d
|
||||
m.UndefinedName, # e
|
||||
)
|
||||
|
||||
def test_nestedClass(self):
|
||||
"""Nested classes can access enclosing scope."""
|
||||
self.flakes('''
|
||||
def f(foo):
|
||||
class C:
|
||||
bar = foo
|
||||
def f(self):
|
||||
return foo
|
||||
return C()
|
||||
|
||||
f(123).f()
|
||||
''')
|
||||
|
||||
def test_badNestedClass(self):
|
||||
"""Free variables in nested classes must bind at class creation."""
|
||||
self.flakes('''
|
||||
def f():
|
||||
class C:
|
||||
bar = foo
|
||||
foo = 456
|
||||
return foo
|
||||
f()
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_definedAsStarArgs(self):
|
||||
"""Star and double-star arg names are defined."""
|
||||
self.flakes('''
|
||||
def f(a, *b, **c):
|
||||
print(a, b, c)
|
||||
''')
|
||||
|
||||
def test_definedAsStarUnpack(self):
|
||||
"""Star names in unpack are defined."""
|
||||
self.flakes('''
|
||||
a, *b = range(10)
|
||||
print(a, b)
|
||||
''')
|
||||
self.flakes('''
|
||||
*a, b = range(10)
|
||||
print(a, b)
|
||||
''')
|
||||
self.flakes('''
|
||||
a, *b, c = range(10)
|
||||
print(a, b, c)
|
||||
''')
|
||||
|
||||
def test_usedAsStarUnpack(self):
|
||||
"""
|
||||
Star names in unpack are used if RHS is not a tuple/list literal.
|
||||
"""
|
||||
self.flakes('''
|
||||
def f():
|
||||
a, *b = range(10)
|
||||
''')
|
||||
self.flakes('''
|
||||
def f():
|
||||
(*a, b) = range(10)
|
||||
''')
|
||||
self.flakes('''
|
||||
def f():
|
||||
[a, *b, c] = range(10)
|
||||
''')
|
||||
|
||||
def test_unusedAsStarUnpack(self):
|
||||
"""
|
||||
Star names in unpack are unused if RHS is a tuple/list literal.
|
||||
"""
|
||||
self.flakes('''
|
||||
def f():
|
||||
a, *b = any, all, 4, 2, 'un'
|
||||
''', m.UnusedVariable, m.UnusedVariable)
|
||||
self.flakes('''
|
||||
def f():
|
||||
(*a, b) = [bool, int, float, complex]
|
||||
''', m.UnusedVariable, m.UnusedVariable)
|
||||
self.flakes('''
|
||||
def f():
|
||||
[a, *b, c] = 9, 8, 7, 6, 5, 4
|
||||
''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable)
|
||||
|
||||
def test_keywordOnlyArgs(self):
|
||||
"""Keyword-only arg names are defined."""
|
||||
self.flakes('''
|
||||
def f(*, a, b=None):
|
||||
print(a, b)
|
||||
''')
|
||||
|
||||
self.flakes('''
|
||||
import default_b
|
||||
def f(*, a, b=default_b):
|
||||
print(a, b)
|
||||
''')
|
||||
|
||||
def test_keywordOnlyArgsUndefined(self):
|
||||
"""Typo in kwonly name."""
|
||||
self.flakes('''
|
||||
def f(*, a, b=default_c):
|
||||
print(a, b)
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_annotationUndefined(self):
|
||||
"""Undefined annotations."""
|
||||
self.flakes('''
|
||||
from abc import note1, note2, note3, note4, note5
|
||||
def func(a: note1, *args: note2,
|
||||
b: note3=12, **kw: note4) -> note5: pass
|
||||
''')
|
||||
|
||||
self.flakes('''
|
||||
def func():
|
||||
d = e = 42
|
||||
def func(a: {1, d}) -> (lambda c: e): pass
|
||||
''')
|
||||
|
||||
def test_metaClassUndefined(self):
|
||||
self.flakes('''
|
||||
from abc import ABCMeta
|
||||
class A(metaclass=ABCMeta): pass
|
||||
''')
|
||||
|
||||
def test_definedInGenExp(self):
|
||||
"""
|
||||
Using the loop variable of a generator expression results in no
|
||||
warnings.
|
||||
"""
|
||||
self.flakes('(a for a in [1, 2, 3] if a)')
|
||||
|
||||
self.flakes('(b for b in (a for a in [1, 2, 3] if a) if b)')
|
||||
|
||||
def test_undefinedInGenExpNested(self):
|
||||
"""
|
||||
The loop variables of generator expressions nested together are
|
||||
not defined in the other generator.
|
||||
"""
|
||||
self.flakes('(b for b in (a for a in [1, 2, 3] if b) if b)',
|
||||
m.UndefinedName)
|
||||
|
||||
self.flakes('(b for b in (a for a in [1, 2, 3] if a) if a)',
|
||||
m.UndefinedName)
|
||||
|
||||
def test_undefinedWithErrorHandler(self):
|
||||
"""
|
||||
Some compatibility code checks explicitly for NameError.
|
||||
It should not trigger warnings.
|
||||
"""
|
||||
self.flakes('''
|
||||
try:
|
||||
socket_map
|
||||
except NameError:
|
||||
socket_map = {}
|
||||
''')
|
||||
self.flakes('''
|
||||
try:
|
||||
_memoryview.contiguous
|
||||
except (NameError, AttributeError):
|
||||
raise RuntimeError("Python >= 3.3 is required")
|
||||
''')
|
||||
# If NameError is not explicitly handled, generate a warning
|
||||
self.flakes('''
|
||||
try:
|
||||
socket_map
|
||||
except:
|
||||
socket_map = {}
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
try:
|
||||
socket_map
|
||||
except Exception:
|
||||
socket_map = {}
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_definedInClass(self):
|
||||
"""
|
||||
Defined name for generator expressions and dict/set comprehension.
|
||||
"""
|
||||
self.flakes('''
|
||||
class A:
|
||||
T = range(10)
|
||||
|
||||
Z = (x for x in T)
|
||||
L = [x for x in T]
|
||||
B = dict((i, str(i)) for i in T)
|
||||
''')
|
||||
|
||||
self.flakes('''
|
||||
class A:
|
||||
T = range(10)
|
||||
|
||||
X = {x for x in T}
|
||||
Y = {x:x for x in T}
|
||||
''')
|
||||
|
||||
def test_definedInClassNested(self):
|
||||
"""Defined name for nested generator expressions in a class."""
|
||||
self.flakes('''
|
||||
class A:
|
||||
T = range(10)
|
||||
|
||||
Z = (x for x in (a for a in T))
|
||||
''')
|
||||
|
||||
def test_undefinedInLoop(self):
|
||||
"""
|
||||
The loop variable is defined after the expression is computed.
|
||||
"""
|
||||
self.flakes('''
|
||||
for i in range(i):
|
||||
print(i)
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
[42 for i in range(i)]
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
(42 for i in range(i))
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_definedFromLambdaInDictionaryComprehension(self):
|
||||
"""
|
||||
Defined name referenced from a lambda function within a dict/set
|
||||
comprehension.
|
||||
"""
|
||||
self.flakes('''
|
||||
{lambda: id(x) for x in range(10)}
|
||||
''')
|
||||
|
||||
def test_definedFromLambdaInGenerator(self):
|
||||
"""
|
||||
Defined name referenced from a lambda function within a generator
|
||||
expression.
|
||||
"""
|
||||
self.flakes('''
|
||||
any(lambda: id(x) for x in range(10))
|
||||
''')
|
||||
|
||||
def test_undefinedFromLambdaInDictionaryComprehension(self):
|
||||
"""
|
||||
Undefined name referenced from a lambda function within a dict/set
|
||||
comprehension.
|
||||
"""
|
||||
self.flakes('''
|
||||
{lambda: id(y) for x in range(10)}
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_undefinedFromLambdaInComprehension(self):
|
||||
"""
|
||||
Undefined name referenced from a lambda function within a generator
|
||||
expression.
|
||||
"""
|
||||
self.flakes('''
|
||||
any(lambda: id(y) for x in range(10))
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_dunderClass(self):
|
||||
code = '''
|
||||
class Test(object):
|
||||
def __init__(self):
|
||||
print(__class__.__name__)
|
||||
self.x = 1
|
||||
|
||||
t = Test()
|
||||
'''
|
||||
self.flakes(code)
|
||||
|
||||
|
||||
class NameTests(TestCase):
|
||||
"""
|
||||
Tests for some extra cases of name handling.
|
||||
"""
|
||||
def test_impossibleContext(self):
|
||||
"""
|
||||
A Name node with an unrecognized context results in a RuntimeError being
|
||||
raised.
|
||||
"""
|
||||
tree = ast.parse("x = 10")
|
||||
# Make it into something unrecognizable.
|
||||
tree.body[0].targets[0].ctx = object()
|
||||
self.assertRaises(RuntimeError, checker.Checker, tree)
|
||||
Reference in new issue
Block a user