mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-22 07:51:09 -05:00
okay fine
This commit is contained in:
34
.venv/lib/python3.12/site-packages/pyflakes/test/harness.py
Normal file
34
.venv/lib/python3.12/site-packages/pyflakes/test/harness.py
Normal file
@@ -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
|
||||
803
.venv/lib/python3.12/site-packages/pyflakes/test/test_api.py
Normal file
803
.venv/lib/python3.12/site-packages/pyflakes/test/test_api.py
Normal file
@@ -0,0 +1,803 @@
|
||||
"""
|
||||
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 and sys.version_info >= (3, 9):
|
||||
column = 18
|
||||
elif PYPY:
|
||||
column = 8
|
||||
elif sys.version_info >= (3, 10):
|
||||
column = 18
|
||||
elif sys.version_info >= (3, 9):
|
||||
column = 21
|
||||
else:
|
||||
column = 9
|
||||
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:
|
||||
if sys.version_info >= (3, 9):
|
||||
column = 17
|
||||
elif not PYPY:
|
||||
column = 14
|
||||
else:
|
||||
column = 13
|
||||
last_line = ' ' * (column - 1) + '^\n'
|
||||
columnstr = '%d:' % column
|
||||
|
||||
message = 'positional argument follows keyword argument'
|
||||
|
||||
self.assertHasErrors(
|
||||
sourcePath,
|
||||
["""\
|
||||
{}:1:{} {}
|
||||
foo(bar=baz, bax)
|
||||
{}""".format(sourcePath, columnstr, message, 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 and sys.version_info >= (3, 9):
|
||||
column = 7
|
||||
elif PYPY:
|
||||
column = 6
|
||||
elif (3, 9) <= 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]
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
expected_error = [
|
||||
"<stdin>:1:5: Generator expression must be parenthesized",
|
||||
"max(1 for i in range(10), key=lambda x: x+1)",
|
||||
" ^",
|
||||
]
|
||||
elif PYPY:
|
||||
expected_error = [
|
||||
"<stdin>:1:4: Generator expression must be parenthesized if not sole argument", # noqa: E501
|
||||
"max(1 for i in range(10), key=lambda x: x+1)",
|
||||
" ^",
|
||||
]
|
||||
else:
|
||||
expected_error = [
|
||||
"<stdin>:1:5: Generator expression must be parenthesized",
|
||||
]
|
||||
|
||||
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***REMOVED***iron)
|
||||
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])
|
||||
error_msg = '{0}:1:7: invalid syntax{1}import{1} ^{1}'.format(
|
||||
self.tempfilepath, 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)
|
||||
193
.venv/lib/python3.12/site-packages/pyflakes/test/test_dict.py
Normal file
193
.venv/lib/python3.12/site-packages/pyflakes/test/test_dict.py
Normal file
@@ -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."""
|
||||
1207
.venv/lib/python3.12/site-packages/pyflakes/test/test_imports.py
Normal file
1207
.venv/lib/python3.12/site-packages/pyflakes/test/test_imports.py
Normal file
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
|
||||
''')
|
||||
2046
.venv/lib/python3.12/site-packages/pyflakes/test/test_other.py
Normal file
2046
.venv/lib/python3.12/site-packages/pyflakes/test/test_other.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,799 @@
|
||||
"""
|
||||
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):
|
||||
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
|
||||
''', m.UndefinedName, m.UndefinedName)
|
||||
self.flakes('''
|
||||
def f(a: 'A') -> 'A': return a
|
||||
class A: pass
|
||||
''')
|
||||
self.flakes('''
|
||||
a: A
|
||||
class A: pass
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
a: 'A'
|
||||
class A: pass
|
||||
''')
|
||||
self.flakes('''
|
||||
T: object
|
||||
def f(t: T): pass
|
||||
''', m.UndefinedName)
|
||||
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_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
|
||||
""")
|
||||
@@ -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_[AWS-SECRET-REMOVED]able(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_[AWS-SECRET-REMOVED]able2(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_[AWS-SECRET-REMOVED]ableFalsePositive1(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_[AWS-SECRET-REMOVED]ableFalsePositive2(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_[AWS-SECRET-REMOVED]iable(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_[AWS-SECRET-REMOVED]iable2(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_[AWS-SECRET-REMOVED]iableFalsePositive1(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_[AWS-SECRET-REMOVED]iableFalsePositive2(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)
|
||||
|
||||
@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)
|
||||
|
||||
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_[AWS-SECRET-REMOVED]on(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_[AWS-SECRET-REMOVED]sion(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