init
This commit is contained in:
commit
38355d2442
9083 changed files with 1225834 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
72
.venv/lib/python3.8/site-packages/pyflakes/test/harness.py
Normal file
72
.venv/lib/python3.8/site-packages/pyflakes/test/harness.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
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))
|
||||
file_tokens = checker.make_tokens(textwrap.dedent(input))
|
||||
if kw.get('is_segment'):
|
||||
tree = tree.body[0]
|
||||
kw.pop('is_segment')
|
||||
w = checker.Checker(
|
||||
tree, file_tokens=file_tokens, 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:
|
||||
%s
|
||||
expected outputs:
|
||||
%r
|
||||
but got:
|
||||
%s''' % (input, expectedOutputs, '\n'.join([str(o) for o in w.messages])))
|
||||
return w
|
||||
|
||||
if not hasattr(unittest.TestCase, 'assertIs'):
|
||||
|
||||
def assertIs(self, expr1, expr2, msg=None):
|
||||
if expr1 is not expr2:
|
||||
self.fail(msg or '%r is not %r' % (expr1, expr2))
|
||||
|
||||
if not hasattr(unittest.TestCase, 'assertIsInstance'):
|
||||
|
||||
def assertIsInstance(self, obj, cls, msg=None):
|
||||
"""Same as self.assertTrue(isinstance(obj, cls))."""
|
||||
if not isinstance(obj, cls):
|
||||
self.fail(msg or '%r is not an instance of %r' % (obj, cls))
|
||||
|
||||
if not hasattr(unittest.TestCase, 'assertNotIsInstance'):
|
||||
|
||||
def assertNotIsInstance(self, obj, cls, msg=None):
|
||||
"""Same as self.assertFalse(isinstance(obj, cls))."""
|
||||
if isinstance(obj, cls):
|
||||
self.fail(msg or '%r is an instance of %r' % (obj, cls))
|
||||
|
||||
if not hasattr(unittest.TestCase, 'assertIn'):
|
||||
|
||||
def assertIn(self, member, container, msg=None):
|
||||
"""Just like self.assertTrue(a in b)."""
|
||||
if member not in container:
|
||||
self.fail(msg or '%r not found in %r' % (member, container))
|
||||
|
||||
if not hasattr(unittest.TestCase, 'assertNotIn'):
|
||||
|
||||
def assertNotIn(self, member, container, msg=None):
|
||||
"""Just like self.assertTrue(a not in b)."""
|
||||
if member in container:
|
||||
self.fail(msg or
|
||||
'%r unexpectedly found in %r' % (member, container))
|
||||
845
.venv/lib/python3.8/site-packages/pyflakes/test/test_api.py
Normal file
845
.venv/lib/python3.8/site-packages/pyflakes/test/test_api.py
Normal file
|
|
@ -0,0 +1,845 @@
|
|||
"""
|
||||
Tests for L{pyflakes.scripts.pyflakes}.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from pyflakes.messages import UnusedImport
|
||||
from pyflakes.reporter import Reporter
|
||||
from pyflakes.api import (
|
||||
main,
|
||||
checkPath,
|
||||
checkRecursive,
|
||||
iterSourceCode,
|
||||
)
|
||||
from pyflakes.test.harness import TestCase, skipIf
|
||||
|
||||
if sys.version_info < (3,):
|
||||
from cStringIO import StringIO
|
||||
else:
|
||||
from io import StringIO
|
||||
unichr = chr
|
||||
|
||||
try:
|
||||
sys.pypy_version_info
|
||||
PYPY = True
|
||||
except AttributeError:
|
||||
PYPY = False
|
||||
|
||||
try:
|
||||
WindowsError
|
||||
WIN = True
|
||||
except NameError:
|
||||
WIN = False
|
||||
|
||||
ERROR_HAS_COL_NUM = ERROR_HAS_LAST_LINE = sys.version_info >= (3, 2) or PYPY
|
||||
|
||||
|
||||
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(object):
|
||||
"""
|
||||
Mock an AST node.
|
||||
"""
|
||||
def __init__(self, lineno, col_offset=0):
|
||||
self.lineno = lineno
|
||||
self.col_offset = col_offset
|
||||
|
||||
|
||||
class SysStreamCapturing(object):
|
||||
|
||||
"""
|
||||
Context manager capturing sys.stdin, sys.stdout and sys.stderr.
|
||||
|
||||
The file handles are replaced with a StringIO object.
|
||||
On environments that support it, the StringIO object uses newlines
|
||||
set to os.linesep. Otherwise newlines are converted from \\n to
|
||||
os.linesep during __exit__.
|
||||
"""
|
||||
|
||||
def _create_StringIO(self, buffer=None):
|
||||
# Python 3 has a newline argument
|
||||
try:
|
||||
return StringIO(buffer, newline=os.linesep)
|
||||
except TypeError:
|
||||
self._newline = True
|
||||
# Python 2 creates an input only stream when buffer is not None
|
||||
if buffer is None:
|
||||
return StringIO()
|
||||
else:
|
||||
return StringIO(buffer)
|
||||
|
||||
def __init__(self, stdin):
|
||||
self._newline = False
|
||||
self._stdin = self._create_StringIO(stdin or '')
|
||||
|
||||
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 = self._create_StringIO()
|
||||
sys.stderr = self._stderr_stringio = self._create_StringIO()
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.output = self._stdout_stringio.getvalue()
|
||||
self.error = self._stderr_stringio.getvalue()
|
||||
|
||||
if self._newline and os.linesep != '\n':
|
||||
self.output = self.output.replace('\n', os.linesep)
|
||||
self.error = self.error.replace('\n', os.linesep)
|
||||
|
||||
sys.stdin = self._orig_stdin
|
||||
sys.stdout = self._orig_stdout
|
||||
sys.stderr = self._orig_stderr
|
||||
|
||||
|
||||
class LoggingReporter(object):
|
||||
"""
|
||||
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')
|
||||
|
||||
python2 = os.path.join(self.tempdir, 'd')
|
||||
with open(python2, 'w') as fd:
|
||||
fd.write('#!/usr/bin/env python2\n')
|
||||
|
||||
python3 = os.path.join(self.tempdir, 'e')
|
||||
with open(python3, 'w') as fd:
|
||||
fd.write('#!/usr/bin/env python3\n')
|
||||
|
||||
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')
|
||||
|
||||
python2u = os.path.join(self.tempdir, 'h')
|
||||
with open(python2u, 'w') as fd:
|
||||
fd.write('#!/usr/bin/python2u\n')
|
||||
|
||||
python3d = os.path.join(self.tempdir, 'i')
|
||||
with open(python3d, 'w') as fd:
|
||||
fd.write('#!/usr/local/bin/python3d\n')
|
||||
|
||||
python38m = os.path.join(self.tempdir, 'j')
|
||||
with open(python38m, 'w') as fd:
|
||||
fd.write('#! /usr/bin/env python3.8m\n')
|
||||
|
||||
python27 = os.path.join(self.tempdir, 'k')
|
||||
with open(python27, 'w') as fd:
|
||||
fd.write('#!/usr/bin/python2.7 \n')
|
||||
|
||||
# Should NOT be treated as Python source
|
||||
notfirst = os.path.join(self.tempdir, 'l')
|
||||
with open(notfirst, 'w') as fd:
|
||||
fd.write('#!/bin/sh\n#!/usr/bin/python\n')
|
||||
|
||||
self.assertEqual(
|
||||
sorted(iterSourceCode([self.tempdir])),
|
||||
sorted([python, python2, python3, pythonw, python3args, python2u,
|
||||
python3d, python38m, python27]))
|
||||
|
||||
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 = StringIO()
|
||||
reporter = Reporter(None, err)
|
||||
reporter.syntaxError('foo.py', 'a problem', 3,
|
||||
8 if sys.version_info >= (3, 8) else 7,
|
||||
'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 = 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_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 = 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))
|
||||
column = 25 if sys.version_info >= (3, 8) else 7
|
||||
self.assertEqual(
|
||||
("foo.py:3:%d: a problem\n" % column +
|
||||
lines[-1] + "\n" +
|
||||
" " * (column - 1) + "^\n"),
|
||||
err.getvalue())
|
||||
|
||||
def test_unexpectedError(self):
|
||||
"""
|
||||
C{unexpectedError} reports an error processing a source file.
|
||||
"""
|
||||
err = 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 = StringIO()
|
||||
reporter = Reporter(out, None)
|
||||
message = UnusedImport('foo.py', Node(42), 'bar')
|
||||
reporter.flake(message)
|
||||
self.assertEqual(out.getvalue(), "%s\n" % (message,))
|
||||
|
||||
|
||||
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 = 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:
|
||||
e = sys.exc_info()[1]
|
||||
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 sys.version_info >= (3, 10):
|
||||
column = 12
|
||||
elif sys.version_info >= (3, 8):
|
||||
column = 8
|
||||
else:
|
||||
column = 11
|
||||
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:
|
||||
column = 7
|
||||
elif 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:
|
||||
column = 6 if PYPY else 7
|
||||
last_line = '\t ^' if PYPY else '\t ^'
|
||||
|
||||
self.assertHasErrors(
|
||||
sourcePath,
|
||||
["""\
|
||||
%s:2:%s: invalid syntax
|
||||
\tfoo =
|
||||
%s
|
||||
""" % (sourcePath, column, last_line)])
|
||||
|
||||
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 ERROR_HAS_LAST_LINE:
|
||||
if PYPY:
|
||||
column = 7
|
||||
elif sys.version_info >= (3, 10):
|
||||
column = 18
|
||||
elif sys.version_info >= (3, 9):
|
||||
column = 21
|
||||
elif sys.version_info >= (3, 8):
|
||||
column = 9
|
||||
else:
|
||||
column = 8
|
||||
last_line = ' ' * (column - 1) + '^\n'
|
||||
columnstr = '%d:' % column
|
||||
else:
|
||||
last_line = columnstr = ''
|
||||
self.assertHasErrors(
|
||||
sourcePath,
|
||||
["""\
|
||||
%s:1:%s non-default argument follows default argument
|
||||
def foo(bar=baz, bax):
|
||||
%s""" % (sourcePath, columnstr, 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 ERROR_HAS_LAST_LINE:
|
||||
if PYPY:
|
||||
column = 12
|
||||
elif sys.version_info >= (3, 9):
|
||||
column = 17
|
||||
elif sys.version_info >= (3, 8):
|
||||
column = 14
|
||||
else:
|
||||
column = 13
|
||||
last_line = ' ' * (column - 1) + '^\n'
|
||||
columnstr = '%d:' % column
|
||||
else:
|
||||
last_line = columnstr = ''
|
||||
|
||||
if sys.version_info >= (3, 5):
|
||||
message = 'positional argument follows keyword argument'
|
||||
else:
|
||||
message = 'non-keyword arg after keyword arg'
|
||||
|
||||
self.assertHasErrors(
|
||||
sourcePath,
|
||||
["""\
|
||||
%s:1:%s %s
|
||||
foo(bar=baz, bax)
|
||||
%s""" % (sourcePath, columnstr, message, last_line)])
|
||||
|
||||
def test_invalidEscape(self):
|
||||
"""
|
||||
The invalid escape syntax raises ValueError in Python 2
|
||||
"""
|
||||
ver = sys.version_info
|
||||
# ValueError: invalid \x escape
|
||||
with self.makeTempFile(r"foo = '\xyz'") as sourcePath:
|
||||
if ver < (3,):
|
||||
decoding_error = "%s: problem decoding source\n" % (sourcePath,)
|
||||
else:
|
||||
position_end = 1
|
||||
if PYPY:
|
||||
column = 5
|
||||
elif ver >= (3, 9):
|
||||
column = 13
|
||||
else:
|
||||
column = 7
|
||||
# Column has been "fixed" since 3.2.4 and 3.3.1
|
||||
if ver < (3, 2, 4) or ver[:3] == (3, 3, 0):
|
||||
position_end = 2
|
||||
|
||||
if ERROR_HAS_LAST_LINE:
|
||||
last_line = '%s^\n' % (' ' * (column - 1))
|
||||
else:
|
||||
last_line = ''
|
||||
|
||||
decoding_error = """\
|
||||
%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 = unichr(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 = unichr(0x2603)
|
||||
source = ("""\
|
||||
# coding: ascii
|
||||
x = "%s"
|
||||
""" % SNOWMAN).encode('utf-8')
|
||||
with self.makeTempFile(source) as sourcePath:
|
||||
if PYPY and sys.version_info < (3, ):
|
||||
message = ('\'ascii\' codec can\'t decode byte 0xe2 '
|
||||
'in position 21: ordinal not in range(128)')
|
||||
result = """\
|
||||
%s:0:0: %s
|
||||
x = "\xe2\x98\x83"
|
||||
^\n""" % (sourcePath, message)
|
||||
|
||||
else:
|
||||
message = 'problem decoding source'
|
||||
result = "%s: problem decoding source\n" % (sourcePath,)
|
||||
|
||||
self.assertHasErrors(
|
||||
sourcePath, [result])
|
||||
|
||||
def test_misencodedFileUTF16(self):
|
||||
"""
|
||||
If a source file contains bytes which cannot be decoded, this is
|
||||
reported on stderr.
|
||||
"""
|
||||
SNOWMAN = unichr(0x2603)
|
||||
source = ("""\
|
||||
# coding: ascii
|
||||
x = "%s"
|
||||
""" % SNOWMAN).encode('utf-16')
|
||||
with self.makeTempFile(source) as sourcePath:
|
||||
self.assertHasErrors(
|
||||
sourcePath, ["%s: problem decoding source\n" % (sourcePath,)])
|
||||
|
||||
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("import baz\n".encode('ascii'))
|
||||
file2 = os.path.join(tempdir, 'baz.py')
|
||||
with open(file2, 'wb') as fd:
|
||||
fd.write("import contraband".encode('ascii'))
|
||||
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)
|
||||
|
||||
|
||||
class IntegrationTests(TestCase):
|
||||
"""
|
||||
Tests of the pyflakes script that actually spawn the script.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.tempdir = tempfile.mkdtemp()
|
||||
self.tempfilepath = os.path.join(self.tempdir, 'temp')
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tempdir)
|
||||
|
||||
def getPyflakesBinary(self):
|
||||
"""
|
||||
Return the path to the pyflakes binary.
|
||||
"""
|
||||
import pyflakes
|
||||
package_dir = os.path.dirname(pyflakes.__file__)
|
||||
return os.path.join(package_dir, '..', 'bin', 'pyflakes')
|
||||
|
||||
def runPyflakes(self, paths, stdin=None):
|
||||
"""
|
||||
Launch a subprocess running C{pyflakes}.
|
||||
|
||||
@param paths: Command-line arguments to pass to pyflakes.
|
||||
@param stdin: Text to use as stdin.
|
||||
@return: C{(returncode, stdout, stderr)} of the completed pyflakes
|
||||
process.
|
||||
"""
|
||||
env = dict(os.environ)
|
||||
env['PYTHONPATH'] = os.pathsep.join(sys.path)
|
||||
command = [sys.executable, self.getPyflakesBinary()]
|
||||
command.extend(paths)
|
||||
if stdin:
|
||||
p = subprocess.Popen(command, env=env, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(stdout, stderr) = p.communicate(stdin.encode('ascii'))
|
||||
else:
|
||||
p = subprocess.Popen(command, env=env,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(stdout, stderr) = p.communicate()
|
||||
rv = p.wait()
|
||||
if sys.version_info >= (3,):
|
||||
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("import contraband\n".encode('ascii'))
|
||||
d = self.runPyflakes([self.tempfilepath])
|
||||
expected = UnusedImport(self.tempfilepath, Node(1), 'contraband')
|
||||
self.assertEqual(d, ("%s%s" % (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 = '%s: No such file or directory%s' % (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("import".encode('ascii'))
|
||||
d = self.runPyflakes([self.tempfilepath])
|
||||
error_msg = '{0}:1:{2}: invalid syntax{1}import{1} {3}^{1}'.format(
|
||||
self.tempfilepath, os.linesep, 6 if PYPY else 7, '' if PYPY else ' ')
|
||||
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, ("%s%s" % (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,41 @@
|
|||
"""
|
||||
Tests for detecting redefinition of builtins.
|
||||
"""
|
||||
from sys import version_info
|
||||
|
||||
from pyflakes import messages as m
|
||||
from pyflakes.test.harness import TestCase, skipIf
|
||||
|
||||
|
||||
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()
|
||||
''')
|
||||
|
||||
@skipIf(version_info >= (3,), 'not an UnboundLocalError in Python 3')
|
||||
def test_builtin_in_comprehension(self):
|
||||
self.flakes('''
|
||||
def f():
|
||||
[range for range in range(1, 10)]
|
||||
|
||||
f()
|
||||
''', m.UndefinedLocal)
|
||||
186
.venv/lib/python3.8/site-packages/pyflakes/test/test_checker.py
Normal file
186
.venv/lib/python3.8/site-packages/pyflakes/test/test_checker.py
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
import ast
|
||||
import sys
|
||||
|
||||
from pyflakes import checker
|
||||
from pyflakes.test.harness import TestCase, skipIf
|
||||
|
||||
|
||||
class TypeableVisitorTests(TestCase):
|
||||
"""
|
||||
Tests of L{_TypeableVisitor}
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _run_visitor(s):
|
||||
"""
|
||||
Run L{_TypeableVisitor} on the parsed source and return the visitor.
|
||||
"""
|
||||
tree = ast.parse(s)
|
||||
visitor = checker._TypeableVisitor()
|
||||
visitor.visit(tree)
|
||||
return visitor
|
||||
|
||||
def test_node_types(self):
|
||||
"""
|
||||
Test that the typeable node types are collected
|
||||
"""
|
||||
visitor = self._run_visitor(
|
||||
"""\
|
||||
x = 1 # assignment
|
||||
for x in range(1): pass # for loop
|
||||
def f(): pass # function definition
|
||||
with a as b: pass # with statement
|
||||
"""
|
||||
)
|
||||
self.assertEqual(visitor.typeable_lines, [1, 2, 3, 4])
|
||||
self.assertIsInstance(visitor.typeable_nodes[1], ast.Assign)
|
||||
self.assertIsInstance(visitor.typeable_nodes[2], ast.For)
|
||||
self.assertIsInstance(visitor.typeable_nodes[3], ast.FunctionDef)
|
||||
self.assertIsInstance(visitor.typeable_nodes[4], ast.With)
|
||||
|
||||
def test_visitor_recurses(self):
|
||||
"""
|
||||
Test the common pitfall of missing `generic_visit` in visitors by
|
||||
ensuring that nested nodes are reported
|
||||
"""
|
||||
visitor = self._run_visitor(
|
||||
"""\
|
||||
def f():
|
||||
x = 1
|
||||
"""
|
||||
)
|
||||
self.assertEqual(visitor.typeable_lines, [1, 2])
|
||||
self.assertIsInstance(visitor.typeable_nodes[1], ast.FunctionDef)
|
||||
self.assertIsInstance(visitor.typeable_nodes[2], ast.Assign)
|
||||
|
||||
@skipIf(sys.version_info < (3, 5), 'async syntax introduced in py35')
|
||||
def test_py35_node_types(self):
|
||||
"""
|
||||
Test that the PEP 492 node types are collected
|
||||
"""
|
||||
visitor = self._run_visitor(
|
||||
"""\
|
||||
async def f(): # async def
|
||||
async for x in y: pass # async for
|
||||
async with a as b: pass # async with
|
||||
"""
|
||||
)
|
||||
self.assertEqual(visitor.typeable_lines, [1, 2, 3])
|
||||
self.assertIsInstance(visitor.typeable_nodes[1], ast.AsyncFunctionDef)
|
||||
self.assertIsInstance(visitor.typeable_nodes[2], ast.AsyncFor)
|
||||
self.assertIsInstance(visitor.typeable_nodes[3], ast.AsyncWith)
|
||||
|
||||
def test_last_node_wins(self):
|
||||
"""
|
||||
Test that when two typeable nodes are present on a line, the last
|
||||
typeable one wins.
|
||||
"""
|
||||
visitor = self._run_visitor('x = 1; y = 1')
|
||||
# detected both assignable nodes
|
||||
self.assertEqual(visitor.typeable_lines, [1, 1])
|
||||
# but the assignment to `y` wins
|
||||
self.assertEqual(visitor.typeable_nodes[1].targets[0].id, 'y')
|
||||
|
||||
|
||||
class CollectTypeCommentsTests(TestCase):
|
||||
"""
|
||||
Tests of L{_collect_type_comments}
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _collect(s):
|
||||
"""
|
||||
Run L{_collect_type_comments} on the parsed source and return the
|
||||
mapping from nodes to comments. The return value is converted to
|
||||
a set: {(node_type, tuple of comments), ...}
|
||||
"""
|
||||
tree = ast.parse(s)
|
||||
tokens = checker.make_tokens(s)
|
||||
ret = checker._collect_type_comments(tree, tokens)
|
||||
return {(type(k), tuple(s for _, s in v)) for k, v in ret.items()}
|
||||
|
||||
def test_bytes(self):
|
||||
"""
|
||||
Test that the function works for binary source
|
||||
"""
|
||||
ret = self._collect(b'x = 1 # type: int')
|
||||
self.assertSetEqual(ret, {(ast.Assign, ('# type: int',))})
|
||||
|
||||
def test_text(self):
|
||||
"""
|
||||
Test that the function works for text source
|
||||
"""
|
||||
ret = self._collect(u'x = 1 # type: int')
|
||||
self.assertEqual(ret, {(ast.Assign, ('# type: int',))})
|
||||
|
||||
def test_non_type_comment_ignored(self):
|
||||
"""
|
||||
Test that a non-type comment is ignored
|
||||
"""
|
||||
ret = self._collect('x = 1 # noqa')
|
||||
self.assertSetEqual(ret, set())
|
||||
|
||||
def test_type_comment_before_typeable(self):
|
||||
"""
|
||||
Test that a type comment before something typeable is ignored.
|
||||
"""
|
||||
ret = self._collect('# type: int\nx = 1')
|
||||
self.assertSetEqual(ret, set())
|
||||
|
||||
def test_type_ignore_comment_ignored(self):
|
||||
"""
|
||||
Test that `# type: ignore` comments are not collected.
|
||||
"""
|
||||
ret = self._collect('x = 1 # type: ignore')
|
||||
self.assertSetEqual(ret, set())
|
||||
|
||||
def test_type_ignore_with_other_things_ignored(self):
|
||||
"""
|
||||
Test that `# type: ignore` comments with more content are also not
|
||||
collected.
|
||||
"""
|
||||
ret = self._collect('x = 1 # type: ignore # noqa')
|
||||
self.assertSetEqual(ret, set())
|
||||
ret = self._collect('x = 1 #type:ignore#noqa')
|
||||
self.assertSetEqual(ret, set())
|
||||
|
||||
def test_type_comment_with_extra_still_collected(self):
|
||||
ret = self._collect('x = 1 # type: int # noqa')
|
||||
self.assertSetEqual(ret, {(ast.Assign, ('# type: int # noqa',))})
|
||||
|
||||
def test_type_comment_without_whitespace(self):
|
||||
ret = self._collect('x = 1 #type:int')
|
||||
self.assertSetEqual(ret, {(ast.Assign, ('#type:int',))})
|
||||
|
||||
def test_type_comment_starts_with_word_ignore(self):
|
||||
ret = self._collect('x = 1 # type: ignore[T]')
|
||||
self.assertSetEqual(ret, set())
|
||||
|
||||
def test_last_node_wins(self):
|
||||
"""
|
||||
Test that when two typeable nodes are present on a line, the last
|
||||
typeable one wins.
|
||||
"""
|
||||
ret = self._collect('def f(): x = 1 # type: int')
|
||||
self.assertSetEqual(ret, {(ast.Assign, ('# type: int',))})
|
||||
|
||||
def test_function_def_assigned_comments(self):
|
||||
"""
|
||||
Test that type comments for function arguments are all attributed to
|
||||
the function definition.
|
||||
"""
|
||||
ret = self._collect(
|
||||
"""\
|
||||
def f(
|
||||
a, # type: int
|
||||
b, # type: str
|
||||
):
|
||||
# type: (...) -> None
|
||||
pass
|
||||
"""
|
||||
)
|
||||
expected = {(
|
||||
ast.FunctionDef,
|
||||
('# type: int', '# type: str', '# type: (...) -> None'),
|
||||
)}
|
||||
self.assertSetEqual(ret, expected)
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
from sys import version_info
|
||||
|
||||
from pyflakes import messages as m
|
||||
from pyflakes.checker import (FunctionScope, ClassScope, ModuleScope,
|
||||
Argument, FunctionDefinition, Assignment)
|
||||
from pyflakes.test.harness import TestCase, skipIf
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@skipIf(version_info < (3, 5), 'new in Python 3.5')
|
||||
def test_scope_async_function(self):
|
||||
self.flakes('async def foo(): pass', is_segment=True)
|
||||
213
.venv/lib/python3.8/site-packages/pyflakes/test/test_dict.py
Normal file
213
.venv/lib/python3.8/site-packages/pyflakes/test/test_dict.py
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
"""
|
||||
Tests for dict duplicate keys Pyflakes behavior.
|
||||
"""
|
||||
|
||||
from sys import version_info
|
||||
|
||||
from pyflakes import messages as m
|
||||
from pyflakes.test.harness import TestCase, skipIf
|
||||
|
||||
|
||||
class Test(TestCase):
|
||||
|
||||
def test_duplicate_keys(self):
|
||||
self.flakes(
|
||||
"{'yes': 1, 'yes': 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
@skipIf(version_info < (3,),
|
||||
"bytes and strings with same 'value' are not equal in python3")
|
||||
def test_duplicate_keys_bytes_vs_unicode_py3(self):
|
||||
self.flakes("{b'a': 1, u'a': 2}")
|
||||
|
||||
@skipIf(version_info < (3,),
|
||||
"bytes and strings with same 'value' are not equal in python3")
|
||||
def test_duplicate_values_bytes_vs_unicode_py3(self):
|
||||
self.flakes(
|
||||
"{1: b'a', 1: u'a'}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
@skipIf(version_info >= (3,),
|
||||
"bytes and strings with same 'value' are equal in python2")
|
||||
def test_duplicate_keys_bytes_vs_unicode_py2(self):
|
||||
self.flakes(
|
||||
"{b'a': 1, u'a': 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
@skipIf(version_info >= (3,),
|
||||
"bytes and strings with same 'value' are equal in python2")
|
||||
def test_duplicate_values_bytes_vs_unicode_py2(self):
|
||||
self.flakes("{1: b'a', 1: u'a'}")
|
||||
|
||||
def test_multiple_duplicate_keys(self):
|
||||
self.flakes(
|
||||
"{'yes': 1, 'yes': 2, 'no': 2, 'no': 3}",
|
||||
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}
|
||||
''')
|
||||
465
.venv/lib/python3.8/site-packages/pyflakes/test/test_doctests.py
Normal file
465
.venv/lib/python3.8/site-packages/pyflakes/test/test_doctests.py
Normal file
|
|
@ -0,0 +1,465 @@
|
|||
import sys
|
||||
import textwrap
|
||||
|
||||
from pyflakes import messages as m
|
||||
from pyflakes.checker import (
|
||||
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
|
||||
|
||||
try:
|
||||
sys.pypy_version_info
|
||||
PYPY = True
|
||||
except AttributeError:
|
||||
PYPY = False
|
||||
|
||||
|
||||
class _DoctestMixin(object):
|
||||
|
||||
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(_DoctestMixin, self).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 PYPY:
|
||||
self.assertEqual(exc.col, 27)
|
||||
elif sys.version_info >= (3, 8):
|
||||
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, 14)
|
||||
else:
|
||||
self.assertEqual(exc.col, 16)
|
||||
exc = exceptions[2]
|
||||
self.assertEqual(exc.lineno, 6)
|
||||
if PYPY:
|
||||
self.assertEqual(exc.col, 14)
|
||||
elif sys.version_info >= (3, 8):
|
||||
self.assertEqual(exc.col, 13)
|
||||
else:
|
||||
self.assertEqual(exc.col, 18)
|
||||
|
||||
def test_indentationErrorInDoctest(self):
|
||||
exc = self.flakes('''
|
||||
def doctest_stuff():
|
||||
"""
|
||||
>>> if True:
|
||||
... pass
|
||||
"""
|
||||
''', m.DoctestSyntaxError).messages[0]
|
||||
self.assertEqual(exc.lineno, 5)
|
||||
if PYPY:
|
||||
self.assertEqual(exc.col, 14)
|
||||
elif sys.version_info >= (3, 8):
|
||||
self.assertEqual(exc.col, 13)
|
||||
else:
|
||||
self.assertEqual(exc.col, 16)
|
||||
|
||||
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)
|
||||
if PYPY:
|
||||
self.assertEqual(exc1.col, 20)
|
||||
else:
|
||||
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."""
|
||||
1230
.venv/lib/python3.8/site-packages/pyflakes/test/test_imports.py
Normal file
1230
.venv/lib/python3.8/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,83 @@
|
|||
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=}')
|
||||
''')
|
||||
2142
.venv/lib/python3.8/site-packages/pyflakes/test/test_other.py
Normal file
2142
.venv/lib/python3.8/site-packages/pyflakes/test/test_other.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
from sys import version_info
|
||||
|
||||
from pyflakes import messages as m
|
||||
from pyflakes.test.harness import TestCase, skipIf
|
||||
|
||||
|
||||
class Test(TestCase):
|
||||
@skipIf(version_info >= (3, 3), 'new in Python 3.3')
|
||||
def test_return(self):
|
||||
self.flakes('''
|
||||
class a:
|
||||
def b():
|
||||
for x in a.c:
|
||||
if x:
|
||||
yield x
|
||||
return a
|
||||
''', m.ReturnWithArgsInsideGenerator)
|
||||
|
||||
@skipIf(version_info >= (3, 3), 'new in Python 3.3')
|
||||
def test_returnNone(self):
|
||||
self.flakes('''
|
||||
def a():
|
||||
yield 12
|
||||
return None
|
||||
''', m.ReturnWithArgsInsideGenerator)
|
||||
|
||||
@skipIf(version_info >= (3, 3), 'new in Python 3.3')
|
||||
def test_returnYieldExpression(self):
|
||||
self.flakes('''
|
||||
def a():
|
||||
b = yield a
|
||||
return b
|
||||
''', m.ReturnWithArgsInsideGenerator)
|
||||
|
|
@ -0,0 +1,763 @@
|
|||
"""
|
||||
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): # type: (None) -> None
|
||||
pass
|
||||
|
||||
@overload
|
||||
def f(s): # type: (int) -> int
|
||||
pass
|
||||
|
||||
def f(s):
|
||||
return s
|
||||
|
||||
@typing.overload
|
||||
def g(s): # type: (None) -> None
|
||||
pass
|
||||
|
||||
@typing.overload
|
||||
def g(s): # type: (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): # type: (None) -> None
|
||||
pass
|
||||
|
||||
@overload
|
||||
def f(s): # type: (int) -> int
|
||||
pass
|
||||
|
||||
def f(s):
|
||||
return s
|
||||
|
||||
@typing_extensions.overload
|
||||
def g(s): # type: (None) -> None
|
||||
pass
|
||||
|
||||
@typing_extensions.overload
|
||||
def g(s): # type: (int) -> int
|
||||
pass
|
||||
|
||||
def g(s):
|
||||
return s
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 5), 'new in Python 3.5')
|
||||
def test_typingOverloadAsync(self):
|
||||
"""Allow intentional redefinitions via @typing.overload (async)"""
|
||||
self.flakes("""
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
async def f(s): # type: (None) -> None
|
||||
pass
|
||||
|
||||
@overload
|
||||
async def f(s): # type: (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): # type: (int) -> int
|
||||
pass
|
||||
|
||||
@dec
|
||||
@overload
|
||||
def f(x): # type: (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): # type: (int) -> int
|
||||
pass
|
||||
|
||||
@overload
|
||||
def f(self, x): # type: (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): # type: (None) -> None
|
||||
pass
|
||||
|
||||
@t.overload
|
||||
def f(s): # type: (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)
|
||||
|
||||
@skipIf(version_info < (3, 6), 'new in Python 3.6')
|
||||
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
|
||||
''')
|
||||
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
|
||||
''')
|
||||
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)
|
||||
|
||||
@skipIf(version_info < (3, 6), 'new in Python 3.6')
|
||||
def test_annotating_an_import(self):
|
||||
self.flakes('''
|
||||
from a import b, c
|
||||
b: c
|
||||
print(b)
|
||||
''')
|
||||
|
||||
@skipIf(version_info < (3, 6), 'new in Python 3.6')
|
||||
def test_unused_annotation(self):
|
||||
# Unused annotations are fine in module and class scope
|
||||
self.flakes('''
|
||||
x: int
|
||||
class Cls:
|
||||
y: int
|
||||
''')
|
||||
# TODO: this should print a UnusedVariable message
|
||||
self.flakes('''
|
||||
def f():
|
||||
x: int
|
||||
''')
|
||||
# This should only print one UnusedVariable message
|
||||
self.flakes('''
|
||||
def f():
|
||||
x: int
|
||||
x = 3
|
||||
''', m.UnusedVariable)
|
||||
|
||||
@skipIf(version_info < (3, 5), 'new in Python 3.5')
|
||||
def test_annotated_async_def(self):
|
||||
self.flakes('''
|
||||
class c: pass
|
||||
async def func(c: c) -> None: pass
|
||||
''')
|
||||
|
||||
@skipIf(version_info < (3, 7), 'new in Python 3.7')
|
||||
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
|
||||
''')
|
||||
|
||||
@skipIf(version_info < (3, 6), 'new in Python 3.6')
|
||||
def test_type_annotation_clobbers_all(self):
|
||||
self.flakes('''\
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from y import z
|
||||
|
||||
if not TYPE_CHECKING:
|
||||
__all__ = ("z",)
|
||||
else:
|
||||
__all__: List[str]
|
||||
''')
|
||||
|
||||
def test_typeCommentsMarkImportsAsUsed(self):
|
||||
self.flakes("""
|
||||
from mod import A, B, C, D, E, F, G
|
||||
|
||||
|
||||
def f(
|
||||
a, # type: A
|
||||
):
|
||||
# type: (...) -> B
|
||||
for b in a: # type: C
|
||||
with b as c: # type: D
|
||||
d = c.x # type: E
|
||||
return d
|
||||
|
||||
|
||||
def g(x): # type: (F) -> G
|
||||
return x.y
|
||||
""")
|
||||
|
||||
def test_typeCommentsFullSignature(self):
|
||||
self.flakes("""
|
||||
from mod import A, B, C, D
|
||||
def f(a, b):
|
||||
# type: (A, B[C]) -> D
|
||||
return a + b
|
||||
""")
|
||||
|
||||
def test_typeCommentsStarArgs(self):
|
||||
self.flakes("""
|
||||
from mod import A, B, C, D
|
||||
def f(a, *b, **c):
|
||||
# type: (A, *B, **C) -> D
|
||||
return a + b
|
||||
""")
|
||||
|
||||
def test_typeCommentsFullSignatureWithDocstring(self):
|
||||
self.flakes('''
|
||||
from mod import A, B, C, D
|
||||
def f(a, b):
|
||||
# type: (A, B[C]) -> D
|
||||
"""do the thing!"""
|
||||
return a + b
|
||||
''')
|
||||
|
||||
def test_typeCommentsAdditionalComment(self):
|
||||
self.flakes("""
|
||||
from mod import F
|
||||
|
||||
x = 1 # type: F # noqa
|
||||
""")
|
||||
|
||||
def test_typeCommentsNoWhitespaceAnnotation(self):
|
||||
self.flakes("""
|
||||
from mod import F
|
||||
|
||||
x = 1 #type:F
|
||||
""")
|
||||
|
||||
def test_typeCommentsInvalidDoesNotMarkAsUsed(self):
|
||||
self.flakes("""
|
||||
from mod import F
|
||||
|
||||
# type: F
|
||||
""", m.UnusedImport)
|
||||
|
||||
def test_typeCommentsSyntaxError(self):
|
||||
self.flakes("""
|
||||
def f(x): # type: (F[) -> None
|
||||
pass
|
||||
""", m.CommentAnnotationSyntaxError)
|
||||
|
||||
def test_typeCommentsSyntaxErrorCorrectLine(self):
|
||||
checker = self.flakes("""\
|
||||
x = 1
|
||||
# type: definitely not a PEP 484 comment
|
||||
""", m.CommentAnnotationSyntaxError)
|
||||
self.assertEqual(checker.messages[0].lineno, 2)
|
||||
|
||||
def test_typeCommentsAssignedToPreviousNode(self):
|
||||
# This test demonstrates an issue in the implementation which
|
||||
# associates the type comment with a node above it, however the type
|
||||
# comment isn't valid according to mypy. If an improved approach
|
||||
# which can detect these "invalid" type comments is implemented, this
|
||||
# test should be removed / improved to assert that new check.
|
||||
self.flakes("""
|
||||
from mod import F
|
||||
x = 1
|
||||
# type: F
|
||||
""")
|
||||
|
||||
def test_typeIgnore(self):
|
||||
self.flakes("""
|
||||
a = 0 # type: ignore
|
||||
b = 0 # type: ignore[excuse]
|
||||
c = 0 # type: ignore=excuse
|
||||
d = 0 # type: ignore [excuse]
|
||||
e = 0 # type: ignore whatever
|
||||
""")
|
||||
|
||||
def test_typeIgnoreBogus(self):
|
||||
self.flakes("""
|
||||
x = 1 # type: ignored
|
||||
""", m.UndefinedName)
|
||||
|
||||
def test_typeIgnoreBogusUnicode(self):
|
||||
error = (m.CommentAnnotationSyntaxError if version_info < (3,)
|
||||
else m.UndefinedName)
|
||||
self.flakes("""
|
||||
x = 2 # type: ignore\xc3
|
||||
""", error)
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
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
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
def test_return_annotation_is_function_body_variable(self):
|
||||
self.flakes("""
|
||||
class Test:
|
||||
def t(self) -> Y:
|
||||
Y = 2
|
||||
return Y
|
||||
""", m.UndefinedName)
|
||||
|
||||
@skipIf(version_info < (3, 8), 'new in Python 3.8')
|
||||
def test_positional_only_argument_annotations(self):
|
||||
self.flakes("""
|
||||
from x import C
|
||||
|
||||
def f(c: C, /): ...
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
def test_partially_quoted_type_annotation(self):
|
||||
self.flakes("""
|
||||
from queue import Queue
|
||||
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]')
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
def test_literal_type_typing(self):
|
||||
self.flakes("""
|
||||
from typing import Literal
|
||||
|
||||
def f(x: Literal['some string']) -> None:
|
||||
return None
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
def test_literal_type_typing_extensions(self):
|
||||
self.flakes("""
|
||||
from typing_extensions import Literal
|
||||
|
||||
def f(x: Literal['some string']) -> None:
|
||||
return None
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
def test_annotated_type_typing_missing_forward_type(self):
|
||||
self.flakes("""
|
||||
from typing import Annotated
|
||||
|
||||
def f(x: Annotated['integer']) -> None:
|
||||
return None
|
||||
""", m.UndefinedName)
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
def test_annotated_type_typing_missing_forward_type_multiple_args(self):
|
||||
self.flakes("""
|
||||
from typing import Annotated
|
||||
|
||||
def f(x: Annotated['integer', 1]) -> None:
|
||||
return None
|
||||
""", m.UndefinedName)
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
def test_annotated_type_typing_with_string_args(self):
|
||||
self.flakes("""
|
||||
from typing import Annotated
|
||||
|
||||
def f(x: Annotated[int, '> 0']) -> None:
|
||||
return None
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
def test_annotated_type_typing_with_string_args_in_union(self):
|
||||
self.flakes("""
|
||||
from typing import Annotated, Union
|
||||
|
||||
def f(x: Union[Annotated['int', '>0'], 'integer']) -> None:
|
||||
return None
|
||||
""", m.UndefinedName)
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
def test_literal_type_some_other_module(self):
|
||||
"""err on the side of false-negatives for types named Literal"""
|
||||
self.flakes("""
|
||||
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
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
def test_literal_union_type_typing(self):
|
||||
self.flakes("""
|
||||
from typing import Literal
|
||||
|
||||
def f(x: Literal['some string', 'foo bar']) -> None:
|
||||
return None
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
def test_deferred_twice_annotation(self):
|
||||
self.flakes("""
|
||||
from queue import Queue
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def f() -> "Optional['Queue[str]']":
|
||||
return None
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 7), 'new in Python 3.7')
|
||||
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_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(): # type: () -> T
|
||||
pass
|
||||
""")
|
||||
# False: the old, more-compatible approach
|
||||
self.flakes("""
|
||||
if False:
|
||||
from t import T
|
||||
|
||||
def f(): # type: () -> 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(): # type: () -> 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(): # type: () -> 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"], [])
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 6), 'new in Python 3.6')
|
||||
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)])
|
||||
""")
|
||||
|
|
@ -0,0 +1,871 @@
|
|||
import ast
|
||||
from sys import version_info
|
||||
|
||||
from pyflakes import messages as m, checker
|
||||
from pyflakes.test.harness import TestCase, skipIf, 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]')
|
||||
|
||||
@skipIf(version_info < (3,),
|
||||
'in Python 2 list comprehensions execute in the same scope')
|
||||
def test_undefinedInListComp(self):
|
||||
self.flakes('''
|
||||
[a for a in range(10)]
|
||||
a
|
||||
''',
|
||||
m.UndefinedName)
|
||||
|
||||
@skipIf(version_info < (3,),
|
||||
'in Python 2 exception names stay bound after the except: block')
|
||||
def test_undefinedExceptionName(self):
|
||||
"""Exception names can't be used after the except: block.
|
||||
|
||||
The exc variable is unused inside the exception handler."""
|
||||
self.flakes('''
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
pass
|
||||
exc
|
||||
''', m.UndefinedName, m.UnusedVariable)
|
||||
|
||||
def test_namesDeclaredInExceptBlocks(self):
|
||||
"""Locals declared in except: blocks can be used after the block.
|
||||
|
||||
This shows the example in test_undefinedExceptionName is
|
||||
different."""
|
||||
self.flakes('''
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
e = exc
|
||||
e
|
||||
''')
|
||||
|
||||
@skip('error reporting disabled due to false positives below')
|
||||
def test_undefinedExceptionNameObscuringLocalVariable(self):
|
||||
"""Exception names obscure locals, can't be used after.
|
||||
|
||||
Last line will raise UnboundLocalError on Python 3 after exiting
|
||||
the except: block. Note next two examples for false positives to
|
||||
watch out for."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
pass
|
||||
exc
|
||||
''',
|
||||
m.UndefinedName)
|
||||
|
||||
@skipIf(version_info < (3,),
|
||||
'in Python 2 exception names stay bound after the except: block')
|
||||
def test_undefinedExceptionNameObscuringLocalVariable2(self):
|
||||
"""Exception names are unbound after the `except:` block.
|
||||
|
||||
Last line will raise UnboundLocalError on Python 3 but would print out
|
||||
've' on Python 2. The exc variable is unused inside the exception
|
||||
handler."""
|
||||
self.flakes('''
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
pass
|
||||
print(exc)
|
||||
exc = 'Original value'
|
||||
''', m.UndefinedName, m.UnusedVariable)
|
||||
|
||||
def test_undefinedExceptionNameObscuringLocalVariableFalsePositive1(self):
|
||||
"""Exception names obscure locals, can't be used after. Unless.
|
||||
|
||||
Last line will never raise UnboundLocalError because it's only
|
||||
entered if no exception was raised."""
|
||||
# The exc variable is unused inside the exception handler.
|
||||
expected = [] if version_info < (3,) else [m.UnusedVariable]
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
print('exception logged')
|
||||
raise
|
||||
exc
|
||||
''', *expected)
|
||||
|
||||
def test_delExceptionInExcept(self):
|
||||
"""The exception name can be deleted in the except: block."""
|
||||
self.flakes('''
|
||||
try:
|
||||
pass
|
||||
except Exception as exc:
|
||||
del exc
|
||||
''')
|
||||
|
||||
def test_undefinedExceptionNameObscuringLocalVariableFalsePositive2(self):
|
||||
"""Exception names obscure locals, can't be used after. Unless.
|
||||
|
||||
Last line will never raise UnboundLocalError because `error` is
|
||||
only falsy if the `except:` block has not been entered."""
|
||||
# The exc variable is unused inside the exception handler.
|
||||
expected = [] if version_info < (3,) else [m.UnusedVariable]
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
error = None
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
error = 'exception logged'
|
||||
if error:
|
||||
print(error)
|
||||
else:
|
||||
exc
|
||||
''', *expected)
|
||||
|
||||
@skip('error reporting disabled due to false positives below')
|
||||
def test_undefinedExceptionNameObscuringGlobalVariable(self):
|
||||
"""Exception names obscure globals, can't be used after.
|
||||
|
||||
Last line will raise UnboundLocalError on both Python 2 and
|
||||
Python 3 because the existence of that exception name creates
|
||||
a local scope placeholder for it, obscuring any globals, etc."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
def func():
|
||||
try:
|
||||
pass # nothing is raised
|
||||
except ValueError as exc:
|
||||
pass # block never entered, exc stays unbound
|
||||
exc
|
||||
''',
|
||||
m.UndefinedLocal)
|
||||
|
||||
@skip('error reporting disabled due to false positives below')
|
||||
def test_undefinedExceptionNameObscuringGlobalVariable2(self):
|
||||
"""Exception names obscure globals, can't be used after.
|
||||
|
||||
Last line will raise NameError on Python 3 because the name is
|
||||
locally unbound after the `except:` block, even if it's
|
||||
nonlocal. We should issue an error in this case because code
|
||||
only working correctly if an exception isn't raised, is invalid.
|
||||
Unless it's explicitly silenced, see false positives below."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
def func():
|
||||
global exc
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
pass # block never entered, exc stays unbound
|
||||
exc
|
||||
''',
|
||||
m.UndefinedLocal)
|
||||
|
||||
def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive1(self):
|
||||
"""Exception names obscure globals, can't be used after. Unless.
|
||||
|
||||
Last line will never raise NameError because it's only entered
|
||||
if no exception was raised."""
|
||||
# The exc variable is unused inside the exception handler.
|
||||
expected = [] if version_info < (3,) else [m.UnusedVariable]
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
def func():
|
||||
global exc
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
print('exception logged')
|
||||
raise
|
||||
exc
|
||||
''', *expected)
|
||||
|
||||
def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive2(self):
|
||||
"""Exception names obscure globals, can't be used after. Unless.
|
||||
|
||||
Last line will never raise NameError because `error` is only
|
||||
falsy if the `except:` block has not been entered."""
|
||||
# The exc variable is unused inside the exception handler.
|
||||
expected = [] if version_info < (3,) else [m.UnusedVariable]
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
def func():
|
||||
global exc
|
||||
error = None
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
error = 'exception logged'
|
||||
if error:
|
||||
print(error)
|
||||
else:
|
||||
exc
|
||||
''', *expected)
|
||||
|
||||
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')
|
||||
|
||||
@skipIf(version_info < (3, 6), 'new feature in 3.6')
|
||||
def test_moduleAnnotations(self):
|
||||
"""
|
||||
Use of the C{__annotations__} in module scope should not emit
|
||||
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)
|
||||
|
||||
@skipIf(version_info < (3, 3), "Python >= 3.3 only")
|
||||
def test_magicQualnameInClassScope(self):
|
||||
"""
|
||||
Use of the C{__qualname__} magic builtin should not emit an undefined
|
||||
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)
|
||||
|
||||
@skipIf(version_info >= (3,), 'obsolete syntax')
|
||||
def test_localImportStar(self):
|
||||
"""
|
||||
A local import * still allows undefined names to be found
|
||||
in upper scopes.
|
||||
"""
|
||||
self.flakes('''
|
||||
def a():
|
||||
from fu import *
|
||||
bar
|
||||
''', m.ImportStarUsed, m.UndefinedName, m.UnusedImport)
|
||||
|
||||
@skipIf(version_info >= (3,), 'obsolete syntax')
|
||||
def test_unpackedParameter(self):
|
||||
"""Unpacked function parameters create bindings."""
|
||||
self.flakes('''
|
||||
def a((bar, baz)):
|
||||
bar; baz
|
||||
''')
|
||||
|
||||
def test_definedByGlobal(self):
|
||||
"""
|
||||
"global" can make an otherwise undefined name in another function
|
||||
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)
|
||||
''')
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
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)
|
||||
''')
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
def test_usedAsStarUnpack(self):
|
||||
"""
|
||||
Star names in unpack are used if RHS is not a tuple/list literal.
|
||||
"""
|
||||
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)
|
||||
''')
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
def test_unusedAsStarUnpack(self):
|
||||
"""
|
||||
Star names in unpack are unused if RHS is a tuple/list literal.
|
||||
"""
|
||||
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)
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
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)
|
||||
''')
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
def test_keywordOnlyArgsUndefined(self):
|
||||
"""Typo in kwonly name."""
|
||||
self.flakes('''
|
||||
def f(*, a, b=default_c):
|
||||
print(a, b)
|
||||
''', m.UndefinedName)
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
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
|
||||
''')
|
||||
|
||||
@skipIf(version_info < (3,), 'new in Python 3')
|
||||
def test_metaClassUndefined(self):
|
||||
self.flakes('''
|
||||
from abc import ABCMeta
|
||||
class A(metaclass=ABCMeta): pass
|
||||
''')
|
||||
|
||||
def test_definedInGenExp(self):
|
||||
"""
|
||||
Using the loop variable of a generator expression results in no
|
||||
warnings.
|
||||
"""
|
||||
self.flakes('(a for a in [1, 2, 3] if a)')
|
||||
|
||||
self.flakes('(b for b in (a for a in [1, 2, 3] if a) if b)')
|
||||
|
||||
def test_undefinedInGenExpNested(self):
|
||||
"""
|
||||
The loop variables of generator expressions nested together are
|
||||
not defined in the other generator.
|
||||
"""
|
||||
self.flakes('(b for b in (a for a in [1, 2, 3] if b) if b)',
|
||||
m.UndefinedName)
|
||||
|
||||
self.flakes('(b for b in (a for a in [1, 2, 3] if a) if a)',
|
||||
m.UndefinedName)
|
||||
|
||||
def test_undefinedWithErrorHandler(self):
|
||||
"""
|
||||
Some compatibility code checks explicitly for NameError.
|
||||
It should not trigger warnings.
|
||||
"""
|
||||
self.flakes('''
|
||||
try:
|
||||
socket_map
|
||||
except NameError:
|
||||
socket_map = {}
|
||||
''')
|
||||
self.flakes('''
|
||||
try:
|
||||
_memoryview.contiguous
|
||||
except (NameError, AttributeError):
|
||||
raise RuntimeError("Python >= 3.3 is required")
|
||||
''')
|
||||
# If NameError is not explicitly handled, generate a warning
|
||||
self.flakes('''
|
||||
try:
|
||||
socket_map
|
||||
except:
|
||||
socket_map = {}
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
try:
|
||||
socket_map
|
||||
except Exception:
|
||||
socket_map = {}
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_definedInClass(self):
|
||||
"""
|
||||
Defined name for generator expressions and dict/set comprehension.
|
||||
"""
|
||||
self.flakes('''
|
||||
class A:
|
||||
T = range(10)
|
||||
|
||||
Z = (x for x in T)
|
||||
L = [x for x in T]
|
||||
B = dict((i, str(i)) for i in T)
|
||||
''')
|
||||
|
||||
self.flakes('''
|
||||
class A:
|
||||
T = range(10)
|
||||
|
||||
X = {x for x in T}
|
||||
Y = {x:x for x in T}
|
||||
''')
|
||||
|
||||
def test_definedInClassNested(self):
|
||||
"""Defined name for nested generator expressions in a class."""
|
||||
self.flakes('''
|
||||
class A:
|
||||
T = range(10)
|
||||
|
||||
Z = (x for x in (a for a in T))
|
||||
''')
|
||||
|
||||
def test_undefinedInLoop(self):
|
||||
"""
|
||||
The loop variable is defined after the expression is computed.
|
||||
"""
|
||||
self.flakes('''
|
||||
for i in range(i):
|
||||
print(i)
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
[42 for i in range(i)]
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
(42 for i in range(i))
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_definedFromLambdaInDictionaryComprehension(self):
|
||||
"""
|
||||
Defined name referenced from a lambda function within a dict/set
|
||||
comprehension.
|
||||
"""
|
||||
self.flakes('''
|
||||
{lambda: id(x) for x in range(10)}
|
||||
''')
|
||||
|
||||
def test_definedFromLambdaInGenerator(self):
|
||||
"""
|
||||
Defined name referenced from a lambda function within a generator
|
||||
expression.
|
||||
"""
|
||||
self.flakes('''
|
||||
any(lambda: id(x) for x in range(10))
|
||||
''')
|
||||
|
||||
def test_undefinedFromLambdaInDictionaryComprehension(self):
|
||||
"""
|
||||
Undefined name referenced from a lambda function within a dict/set
|
||||
comprehension.
|
||||
"""
|
||||
self.flakes('''
|
||||
{lambda: id(y) for x in range(10)}
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_undefinedFromLambdaInComprehension(self):
|
||||
"""
|
||||
Undefined name referenced from a lambda function within a generator
|
||||
expression.
|
||||
"""
|
||||
self.flakes('''
|
||||
any(lambda: id(y) for x in range(10))
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_dunderClass(self):
|
||||
"""
|
||||
`__class__` is defined in class scope under Python 3, but is not
|
||||
in Python 2.
|
||||
"""
|
||||
code = '''
|
||||
class Test(object):
|
||||
def __init__(self):
|
||||
print(__class__.__name__)
|
||||
self.x = 1
|
||||
|
||||
t = Test()
|
||||
'''
|
||||
if version_info < (3,):
|
||||
self.flakes(code, m.UndefinedName)
|
||||
else:
|
||||
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")
|
||||
file_tokens = checker.make_tokens("x = 10")
|
||||
# Make it into something unrecognizable.
|
||||
tree.body[0].targets[0].ctx = object()
|
||||
self.assertRaises(RuntimeError, checker.Checker, tree, file_tokens=file_tokens)
|
||||
Loading…
Add table
Add a link
Reference in a new issue