vulture-1.3/ 0000755 0001750 0001750 00000000000 13616004367 014167 5 ustar jendrik jendrik 0000000 0000000 vulture-1.3/tox.ini 0000644 0001750 0001750 00000001074 13612115215 015473 0 ustar jendrik jendrik 0000000 0000000 [tox]
envlist = cleanup, docs, py{27,35,36,37,38}, style
skip_missing_interpreters = true
# Erase old coverage results, then accumulate them during this tox run.
[testenv:cleanup]
deps =
coverage
commands =
coverage erase
[testenv]
deps =
coverage<5.0.0
pytest>=3.3
pytest-cov
commands =
py.test
[testenv:docs]
deps =
collective.checkdocs
pygments
commands =
python setup.py checkdocs
[testenv:style]
deps =
flake8
py36,py37: flake8-2020
py36,py37: flake8-bugbear
py36,py37: flake8-comprehensions
commands =
flake8 setup.py tests/ vulture/
vulture-1.3/setup.py 0000644 0001750 0001750 00000004527 13544320706 015707 0 ustar jendrik jendrik 0000000 0000000 #! /usr/bin/env python
import codecs
import os.path
import re
import sys
import setuptools
from setuptools.command.test import test as TestCommand
def read(*parts):
here = os.path.abspath(os.path.dirname(__file__))
with codecs.open(os.path.join(here, *parts), 'r') as f:
return f.read()
def find_version(*file_parts):
version_file = read(*file_parts)
version_match = re.search(
r"^__version__ = ['\"]([^'\"]*)['\"]$", version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError('Unable to find version string.')
class PyTest(TestCommand):
def finalize_options(self):
TestCommand.finalize_options(self)
self.test_args = []
self.test_suite = True
def run_tests(self):
# import here, cause outside the eggs aren't loaded
import pytest
sys.exit(pytest.main(self.test_args))
setuptools.setup(
name='vulture',
version=find_version('vulture', 'core.py'),
description='Find dead code',
long_description='\n\n'.join(
[open('README.rst').read(), open('NEWS.rst').read()]),
keywords='dead-code-removal',
author='Jendrik Seipp',
author_email='jendrikseipp@gmail.com',
url='https://github.com/jendrikseipp/vulture',
license='MIT',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Software Development :: Quality Assurance'
],
entry_points={
'console_scripts': ['vulture = vulture.core:main'],
},
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
tests_require=['pytest', 'pytest-cov'],
cmdclass={'test': PyTest},
packages=setuptools.find_packages(exclude=['tests']),
package_data={'vulture': ['whitelists/*.py']},
)
vulture-1.3/NEWS.rst 0000644 0001750 0001750 00000016273 13615776511 015514 0 ustar jendrik jendrik 0000000 0000000 News
====
1.3 (2020-02-03)
----------------
* Detect redundant 'if' conditions without 'else' blocks.
* Add whitelist for ``string.Formatter`` (Joseph Bylund, #183).
1.2 (2019-11-22)
----------------
* Fix tests for Python 3.8 (#166).
* Use new ``Constant`` AST node under Python 3.8+ (#175).
* Add test for f-strings (#177).
* Add whitelist for ``logging`` module.
1.1 (2019-09-23)
----------------
* Add ``sys.excepthook`` to ``sys`` whitelist.
* Add whitelist for ``ctypes`` module.
* Check that type annotations are parsed and type comments are ignored (thanks @kx-chen).
* Support checking files with BOM under Python 2.7 (#170).
1.0 (2018-10-23)
----------------
* Add ``--ignore-decorators`` flag (thanks @RJ722).
* Add whitelist for ``threading`` module (thanks @andrewhalle).
0.29 (2018-07-31)
-----------------
* Add ``--ignore-names`` flag for ignoring names matching the given glob patterns (thanks @RJ722).
0.28 (2018-07-05)
-----------------
* Add ``--make-whitelist`` flag for reporting output in whitelist format (thanks @RJ722).
* Ignore case of ``--exclude`` arguments on Windows.
* Add ``*-test.py`` to recognized test file patterns.
* Add ``failureException``, ``longMessage`` and ``maxDiff`` to ``unittest`` whitelist.
* Refer to actual objects rather than their mocks in default whitelists (thanks @RJ722).
* Don't import any Vulture modules in setup.py (thanks @RJ722).
0.27 (2018-06-05)
-----------------
* Report ``while (True): ... else: ...`` as unreachable (thanks @RJ722).
* Use ``argparse`` instead of ``optparse``.
* Whitelist Mock.return_value and Mock.side_effect in unittest.mock module.
* Drop support for Python 2.6 and 3.3.
* Improve documentation and test coverage (thanks @RJ722).
0.26 (2017-08-28)
-----------------
* Detect ``async`` function definitions (thanks @RJ722).
* Add ``Item.get_report()`` method (thanks @RJ722).
* Move method for finding Python modules out of Vulture class.
0.25 (2017-08-15)
-----------------
* Detect unsatisfiable statements containing ``and``, ``or`` and ``not``.
* Use filenames and line numbers as tie-breakers when sorting by size.
* Store first and last line numbers in Item objects.
* Pass relevant options directly to ``scavenge()`` and ``report()``.
0.24 (2017-08-14)
-----------------
* Detect unsatisfiable ``while``-conditions (thanks @RJ722).
* Detect unsatisfiable ``if``- and ``else``-conditions (thanks @RJ722).
* Handle null bytes in source code.
0.23 (2017-08-10)
-----------------
* Add ``--min-confidence`` flag (thanks @RJ722).
0.22 (2017-08-04)
-----------------
* Detect unreachable code after ``return``, ``break``, ``continue`` and
``raise`` (thanks @RJ722).
* Parse all variable and attribute names in new format strings.
* Extend ast whitelist.
0.21 (2017-07-26)
-----------------
* If an unused item is defined multiple times, report it multiple times.
* Make size estimates for function calls more accurate.
* Create wheel files for Vulture (thanks @RJ722).
0.20 (2017-07-26)
-----------------
* Report unused tuple assignments as dead code.
* Report attribute names that have the same names as variables as dead code.
* Let Item class inherit from ``object`` (thanks @RJ722).
* Handle names imported as aliases like all other used variable names.
* Rename Vulture.used_vars to Vulture.used_names.
* Use function for determining which imports to ignore.
* Only try to import each whitelist file once.
* Store used names and used attributes in sets instead of lists.
* Fix estimating the size of code containing ellipses (...).
* Refactor and simplify code.
0.19 (2017-07-20)
-----------------
* Don't ignore `__foo` variable names.
* Use separate methods for determining whether to ignore classes and functions.
* Only try to find a whitelist for each defined import once (thanks @roivanov).
* Fix finding the last child for many types of AST nodes.
0.18 (2017-07-17)
-----------------
* Make `--sort-by-size` faster and more accurate (thanks @RJ722).
0.17 (2017-07-17)
-----------------
* Add `get_unused_code()` method.
* Return with exit code 1 when syntax errors are found or files can't be read.
0.16 (2017-07-12)
-----------------
* Differentiate between unused classes and functions (thanks @RJ722).
* Add --sort-by-size option (thanks @jackric and @RJ722).
* Count imports as used if they are accessed as module attributes.
0.15 (2017-07-04)
-----------------
* Automatically include whitelists based on imported modules (thanks @RJ722).
* Add --version parameter (thanks @RJ722).
* Add appveyor tests for testing on Windows (thanks @RJ722).
0.14 (2017-04-06)
-----------------
* Add stub whitelist file for Python standard library (thanks @RJ722)
* Ignore class names starting with "Test" in "test\_" files (thanks @thisch).
* Ignore "test\_" functions only in "test\_" files.
0.13 (2017-03-06)
-----------------
* Ignore star-imported names since we cannot detect whether they are used.
* Move repository to GitHub.
0.12 (2017-01-05)
-----------------
* Detect unused imports.
* Use tokenize.open() on Python >= 3.2 for reading input files, assume
UTF-8 encoding on older Python versions.
0.11 (2016-11-27)
-----------------
* Use the system's default encoding when reading files.
* Report syntax errors instead of aborting.
0.10 (2016-07-14)
-----------------
* Detect unused function and method arguments (issue #15).
* Detect unused \*args and \*\*kwargs parameters.
* Change license from GPL to MIT.
0.9 (2016-06-29)
----------------
* Don't flag attributes as unused if they are used as global variables
in another module (thanks Florian Bruhin).
* Don't consider "True" and "False" variable names.
* Abort with error message when invoked on .pyc files.
0.8.1 (2015-09-28)
------------------
* Fix code for Python 3.
0.8 (2015-09-28)
----------------
* Do not flag names imported with "import as" as dead code (thanks Tom Terrace).
0.7 (2015-09-26)
----------------
* Exit with exitcode 1 if path on commandline can't be found.
* Test vulture with vulture using a whitelist module for false positives.
* Add tests that run vulture as a script.
* Add "python setup.py test" command for running tests.
* Add support for tox.
* Raise test coverage to 100%.
* Remove ez_setup.py.
0.6 (2014-09-06)
----------------
* Ignore function names starting with "test\_".
* Parse variable names in new format strings (e.g. "This is {x}".format(x="nice")).
* Only parse alphanumeric variable names in format strings and ignore types.
* Abort with exit code 1 on syntax errors.
* Support installation under Windows by using setuptools (thanks Reuben Fletcher-Costin).
0.5 (2014-05-09)
----------------
* If dead code is found, exit with 1.
0.4.1 (2013-09-17)
------------------
* Only warn if a path given on the command line cannot be found.
0.4 (2013-06-23)
----------------
* Ignore unused variables starting with an underscore.
* Show warning for syntax errors instead of aborting directly.
* Print warning if a file cannot be found.
0.3 (2012-03-19)
----------------
* Add support for python3
* Report unused attributes
* Find tuple assignments in comprehensions
* Scan files given on the command line even if they don't end with .py
0.2 (2012-03-18)
----------------
* Only format nodes in verbose mode (gives 4x speedup).
0.1 (2012-03-17)
----------------
* First release.
vulture-1.3/TODO.rst 0000644 0001750 0001750 00000002730 13615777105 015476 0 ustar jendrik jendrik 0000000 0000000 TODOs
=====
* Convert README.rst and NEWS.rst files to Markdown.
* Detect unused variables in assignment expressions under Python 3.8+.
* Use end_lineno and end_col_offset attributes when running Python 3.8+.
Non-TODOs
=========
* Ignore hidden files and directories (might be unexpected, use --exclude instead).
* Differentiate between functions and methods. For our purposes methods are
functions with a "self" parameter and they are stored as attributes, not as
variables. However, we can't differentiate between function and method executions.
* Use Assign instead of Name AST nodes for estimating the size of assignments (KISS).
* Only count lines for unused code by storing a function ``get_size`` in
Item for computing the size on demand. This is 1.5 times as slow as computing
no sizes.
* Compute sizes on demand. Storing nodes increases memory usage from
~120 MiB to ~580 MiB for tensorflow's Python code.
* Detect unreachable code for ``ast.IfExp`` (rarely used, even more rarely "unused").
* Detect unreachable code for ``ast.Assert`` (``assert False`` is common idiom
for aborting rogue code).
* Detect superfluous expressions like ``a <= b``, ``42``, ``foo and bar``
occuring outside of a statement (hard to detect if code is unneeded).
* Detect that body of ``if foo:`` is unreachable if foo is only assigned "false" values
(complicated: e.g., foo = []; foo.append(1); if foo: ...).
* Use coverage.py to detect false-positives (#109). Workflow too complicated.
vulture-1.3/README.rst 0000644 0001750 0001750 00000017505 13556024433 015665 0 ustar jendrik jendrik 0000000 0000000 Vulture - Find dead code
========================
.. image:: https://travis-ci.org/jendrikseipp/vulture.svg?branch=master
:target: https://travis-ci.org/jendrikseipp/vulture
:alt: Travis CI build status (Linux)
.. image:: https://ci.appveyor.com/api/projects/status/github/jendrikseipp/vulture?svg=true
:target: https://ci.appveyor.com/project/jendrikseipp96693/vulture
:alt: AppVeyor CI build status (Windows)
.. image:: https://coveralls.io/repos/github/jendrikseipp/vulture/badge.svg?branch=master
:target: https://coveralls.io/github/jendrikseipp/vulture?branch=master
Vulture finds unused code in Python programs. This is useful for
cleaning up and finding errors in large code bases. If you run Vulture
on both your library and test suite you can find untested code.
Due to Python's dynamic nature, static code analyzers like Vulture are
likely to miss some dead code. Also, code that is only called implicitly
may be reported as unused. Nonetheless, Vulture can be a very helpful
tool for higher code quality.
Features
--------
* fast: uses static code analysis
* tested: tests itself and has complete test coverage
* complements pyflakes and has the same output syntax
* sorts unused classes and functions by size with ``--sort-by-size``
* supports Python 2.7 and Python >= 3.5
Installation
------------
::
$ pip install vulture # from PyPI
$ pip install . # from cloned repo
Usage
-----
::
$ vulture myscript.py # or
$ python3 -m vulture myscript.py
$ vulture myscript.py mypackage/
$ vulture myscript.py --min-confidence 100 # Only report 100% dead code.
The provided arguments may be Python files or directories. For each
directory Vulture analyzes all contained `*.py` files.
Vulture assigns each chunk of dead code a confidence value. A confidence
value of 100% means that the code will never be executed. Values below
100% are only estimates for how likely it is that the code is unused.
After you have found and deleted dead code, run Vulture again, because
it may discover more dead code.
**Handling false positives**
You can add used code that is reported as unused to a Python module and
add it to the list of scanned paths. To obtain such a whitelist
automatically, pass ``--make-whitelist`` to Vulture. ::
$ vulture mydir --make-whitelist > whitelist.py
$ vulture mydir whitelist.py
We collect whitelists for common Python modules and packages in
``vulture/whitelists/`` (pull requests are welcome). If you want to
ignore a whole file or directory, use the ``--exclude`` parameter (e.g.,
``--exclude *settings.py,docs/``).
**Ignoring names**
You can use ``--ignore-names foo*,ba[rz]`` to let Vulture ignore all names
starting with ``foo`` and the names ``bar`` and ``baz``. Additionally, the
``--ignore-decorators`` option can be used to ignore functions decorated
with the given decorator. This is helpful for example in Flask projects,
where you can use ``--ignore-decorators "@app.route"`` to ignore all functions
with the ``@app.route`` decorator.
We recommend using whitelists instead of ``--ignore-names`` or
``--ignore-decorators`` whenever possible, since whitelists are automatically
checked for syntactic correctness when passed to Vulture and often you can
even pass them to your Python interpreter and let it check that all
whitelisted code actually still exists in your project.
**Marking unused variables**
There are situations where you can't just remove unused variables, e.g.,
in tuple assignments or function signatures. Vulture will ignore these
variables if they start with an underscore (e.g., ``_x, y = get_pos()``).
**Minimum confidence**
You can use the ``--min-confidence`` flag to set the minimum confidence
for code to be reported as unused. Use ``--min-confidence 100`` to only
report code that is guaranteed to be unused within the analyzed files.
How does it work?
-----------------
Vulture uses the ``ast`` module to build abstract syntax trees for all
given files. While traversing all syntax trees it records the names of
defined and used objects. Afterwards, it reports the objects which have
been defined, but not used. This analysis ignores scopes and only takes
object names into account.
Vulture also detects unreachable code by looking for code after
``return``, ``break``, ``continue`` and ``raise`` statements, and by
searching for unsatisfiable ``if``- and ``while``-conditions.
Sort by size
------------
When using the ``--sort-by-size`` option, Vulture sorts unused code by
its number of lines. This helps developers prioritize where to look for
dead code first.
Examples
--------
Consider the following Python script (``dead_code.py``):
.. code:: python
import os
class Greeter:
def greet(self):
print("Hi")
def hello_world():
message = "Hello, world!"
greeter = Greeter()
greet_func = getattr(greeter, "greet")
greet_func()
if __name__ == "__main__":
hello_world()
Calling ::
vulture dead_code.py
results in the following output::
dead_code.py:1: unused import 'os' (90% confidence)
dead_code.py:4: unused function 'greet' (60% confidence)
dead_code.py:8: unused variable 'message' (60% confidence)
Vulture correctly reports "os" and "message" as unused, but it fails to
detect that "greet" is actually used. The recommended method to deal with
false positives like this is to create a whitelist Python file.
**Preparing whitelists**
In a whitelist we simulate the usage of variables, attributes, etc. For
the program above, a whitelist could look as follows:
.. code:: python
# whitelist_dead_code.py
from dead_code import Greeter
Greeter.greet
Alternatively, you can pass ``--make-whitelist`` to Vulture and obtain
an automatically generated whitelist.
Passing both the original program and the whitelist to Vulture ::
vulture dead_code.py whitelist_dead_code.py
makes Vulture ignore the "greet" method::
dead_code.py:1: unused import 'os' (90% confidence)
dead_code.py:8: unused variable 'message' (60% confidence)
Exit codes
----------
+-----------+---------------------------------------------------------------+
| Exit code | Description |
+===========+===============================================================+
| 0 | No dead code found |
+-----------+---------------------------------------------------------------+
| 1 | Dead code found |
+-----------+---------------------------------------------------------------+
| 1 | Invalid input (file missing, syntax error, wrong encoding) |
+-----------+---------------------------------------------------------------+
| 2 | Invalid command line arguments |
+-----------+---------------------------------------------------------------+
Similar programs
----------------
* `pyflakes `_ finds unused imports and
unused local variables (in addition to many other programmatic errors).
* `coverage `_ finds unused
code more reliably than Vulture, but requires all branches of the code to
actually be run.
* `uncalled `_ finds dead code by using
the abstract syntax tree (like Vulture), regular expressions, or both.
* `dead `_ finds dead code by using
the abstract syntax tree (like Vulture).
Participate
-----------
Please visit https://github.com/jendrikseipp/vulture to report any
issues or to make pull requests.
* Contributing guide: `CONTRIBUTING.rst `_
* Changelog: `NEWS.rst `_
* Roadmap: `TODO.rst `_
vulture-1.3/LICENSE.txt 0000644 0001750 0001750 00000002123 13057221300 015773 0 ustar jendrik jendrik 0000000 0000000 The MIT License (MIT)
Copyright (c) 2012-2016 Jendrik Seipp (jendrikseipp@web.de)
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
vulture-1.3/tests/ 0000755 0001750 0001750 00000000000 13616004367 015331 5 ustar jendrik jendrik 0000000 0000000 vulture-1.3/tests/test_script.py 0000644 0001750 0001750 00000003033 13316633755 020253 0 ustar jendrik jendrik 0000000 0000000 import os.path
import subprocess
import sys
from . import call_vulture, REPO, WHITELISTS
def test_module_with_explicit_whitelists():
assert call_vulture(['vulture/'] + WHITELISTS) == 0
def test_module_with_implicit_whitelists():
assert call_vulture(['vulture/']) == 0
def test_module_without_whitelists():
assert call_vulture(['vulture/', '--exclude', 'whitelists']) == 1
def test_missing_file():
assert call_vulture(['missing.py']) == 1
def test_tests():
assert call_vulture(['tests/']) == 0
def test_whitelists_with_python():
for whitelist in WHITELISTS:
assert subprocess.call([sys.executable, whitelist], cwd=REPO) == 0
def test_pyc():
assert call_vulture(['missing.pyc']) == 1
def test_sort_by_size():
assert call_vulture(['vulture/utils.py', '--sort-by-size']) == 1
def test_min_confidence():
assert call_vulture([
'vulture/core.py', '--exclude', 'whitelists',
'--min-confidence', '100']) == 0
def test_exclude():
def get_csv(paths):
return ",".join(os.path.join('vulture', path) for path in paths)
def call_vulture_with_excludes(excludes):
return call_vulture(['vulture/', '--exclude', get_csv(excludes)])
assert call_vulture_with_excludes(['core.py', 'utils.py']) == 1
assert call_vulture_with_excludes(['core.py', 'utils.py', 'lines.py']) == 0
def test_make_whitelist():
assert call_vulture(
['vulture/', '--make-whitelist', '--exclude', 'whitelists']) == 1
assert call_vulture(['vulture/', '--make-whitelist']) == 0
vulture-1.3/tests/test_size.py 0000644 0001750 0001750 00000014077 13542223356 017724 0 ustar jendrik jendrik 0000000 0000000 import ast
import sys
from vulture import lines
from . import skip_if_not_has_async
def get_last_line_number_slow(node):
"""
A slower, but also simpler and slightly more accurate version of
get_last_line_number().
It traverses all descendants of node and records the highest line
number seen.
This code is 1.6 times slower than count_lines() on the Python
subset of tensorflow. It reports the same sizes for all test cases
and the functions and classes in tensorflow.
"""
return max(getattr(node, 'lineno', -1) for node in ast.walk(node))
def count_lines(node):
"""Estimate the number of lines of the given AST node."""
last_lineno = lines.get_last_line_number(node)
assert get_last_line_number_slow(node) == last_lineno
return last_lineno - node.lineno + 1
def check_size(example, size):
tree = ast.parse(example)
for node in tree.body:
if isinstance(node, ast.ClassDef) and node.name == 'Foo':
assert count_lines(node) == size
break
else:
raise AssertionError('Failed to find top-level class "Foo" in code')
def test_size_basic():
example = """
class Foo:
foo = 1
bar = 2
"""
check_size(example, 3)
def test_size_class():
example = """
class Foo(object):
def bar():
pass
@staticmethod
def func():
if "foo" == "bar":
return "xyz"
import sys
return len(sys.argv)
"""
check_size(example, 10)
def test_size_if_else():
example = """
@identity
class Foo(object):
@identity
@identity
def bar(self):
if "a" == "b":
pass
elif "b" == "c":
pass
else:
pass
"""
size = 11
if sys.version_info >= (3, 8):
size = 10
check_size(example, size)
def test_size_while():
example = """
class Foo:
while 1:
print(1)
"""
check_size(example, 3)
def test_size_while_else():
example = """
class Foo:
while "b" > "a":
pass
else:
pass
"""
check_size(example, 5)
def test_size_with():
example = """
class Foo:
with open("/dev/null") as f:
f.write("")
"""
check_size(example, 3)
def test_size_try_except_else():
example = """
class Foo:
try:
x = sys.argv[99]
except IndexError:
pass
except Exception:
pass
else:
pass
"""
check_size(example, 9)
def test_size_try_finally():
example = """
class Foo:
try:
1/0
finally:
return 99
"""
check_size(example, 5)
def test_size_try_except():
example = """
class Foo:
try:
foo()
except:
bar()
"""
check_size(example, 5)
def test_size_try_excepts():
example = """
class Foo:
try:
foo()
except IOError:
bar()
except AttributeError:
pass
"""
check_size(example, 7)
def test_size_for():
example = """
class Foo:
for i in range(10):
print(i)
"""
check_size(example, 3)
def test_size_for_else():
example = """
class Foo:
for arg in sys.argv:
print("loop")
else:
print("else")
"""
check_size(example, 5)
def test_size_class_nested():
example = """
class Foo:
class Bar:
pass
"""
check_size(example, 3)
# We currently cannot handle code ending with multiline strings.
def test_size_multi_line_return():
example = """
class Foo:
def foo():
return (
'very'
'long'
'string')
"""
check_size(example, 4)
# We currently cannot handle code ending with comment lines.
def test_size_comment_after_last_line():
example = """
class Foo:
def bar():
# A comment.
pass
# This comment won't be detected.
"""
check_size(example, 4)
def test_size_generator():
example = """
class Foo:
def bar():
yield something
"""
check_size(example, 3)
def test_size_exec():
example = """
class Foo:
exec('a')
"""
check_size(example, 2)
def test_size_print1():
example = """
class Foo:
print(
'foo')
"""
check_size(example, 3)
def test_size_print2():
example = """
class Foo:
print(
'foo',)
"""
check_size(example, 3)
def test_size_return():
example = """
class Foo:
return (True and
False)
"""
check_size(example, 3)
def test_size_import_from():
example = """
class Foo:
from a import b
"""
check_size(example, 2)
def test_size_delete():
example = """
class Foo:
del a[:
foo()]
"""
check_size(example, 3)
def test_size_list_comprehension():
example = """
class Foo:
[a
for a in
b]
"""
check_size(example, 4)
# We currently cannot handle closing brackets on a separate line.
def test_size_list():
example = """
class Foo:
[a, b
]
"""
check_size(example, 2)
# We currently cannot handle code ending with an ellipsis on Python 2.
def test_size_ellipsis():
example = """
class Foo:
bar[1:2,
...]
"""
if sys.version_info < (3, 0):
check_size(example, 2)
else:
# ast.Ellipsis is a subclass of ast.expr in Python 3.
check_size(example, 3)
def test_size_starargs():
example = """
class Foo:
def foo():
bar(*a,
b=c)
"""
check_size(example, 4)
# If we add a line break between a and b, the code is too greedy and moves
# down to the slice which has no line numbers. If we took b or c into
# account, the line count would be correct.
def test_size_assign():
example = """
class Foo:
bar = foo(a, b)[c,:]
"""
check_size(example, 2)
@skip_if_not_has_async
def test_size_async_function_def():
example = """
class Foo:
async def foo(some_attr):
pass
"""
check_size(example, 3)
@skip_if_not_has_async
def test_size_async_with():
example = """
class Foo:
async def bar():
async with x:
pass
"""
check_size(example, 4)
@skip_if_not_has_async
def test_size_async_for():
example = """
class Foo:
async def foo():
async for a in b:
pass
"""
check_size(example, 4)
vulture-1.3/tests/test_utils.py 0000644 0001750 0001750 00000003261 13533222652 020101 0 ustar jendrik jendrik 0000000 0000000 import ast
from vulture import utils
from . import skip_if_not_has_async
def check_decorator_names(code, expected_names):
decorator_names = []
def visit_FunctionDef(node):
for decorator in node.decorator_list:
decorator_names.append(utils.get_decorator_name(decorator))
node_visitor = ast.NodeVisitor()
node_visitor.visit_AsyncFunctionDef = visit_FunctionDef
node_visitor.visit_ClassDef = visit_FunctionDef
node_visitor.visit_FunctionDef = visit_FunctionDef
node_visitor.visit(ast.parse(code))
assert expected_names == decorator_names
def test_get_decorator_name_simple():
code = """\
@foobar
def hoo():
pass
"""
check_decorator_names(code, ['@foobar'])
def test_get_decorator_name_call():
code = """\
@xyz()
def bar():
pass
"""
check_decorator_names(code, ['@xyz'])
@skip_if_not_has_async
def test_get_decorator_name_async():
code = """\
@foo.bar.route('/foobar')
async def async_function(request):
print(reques)
"""
check_decorator_names(code, ['@foo.bar.route'])
def test_get_decorator_name_multiple_attrs():
code = """\
@x.y.z
def doo():
pass
"""
check_decorator_names(code, ['@x.y.z'])
def test_get_decorator_name_multiple_attrs_called():
code = """\
@a.b.c.d.foo("Foo and Bar")
def hoofoo():
pass
"""
check_decorator_names(code, ['@a.b.c.d.foo'])
def test_get_decorator_name_multiple_decorators():
code = """\
@foo
@bar()
@x.y.z.a('foobar')
def func():
pass
"""
check_decorator_names(code, ['@foo', '@bar', '@x.y.z.a'])
def test_get_decorator_name_class():
code = """\
@foo
@bar.yz
class Foo:
pass
"""
check_decorator_names(code, ['@foo', '@bar.yz'])
vulture-1.3/tests/test_errors.py 0000644 0001750 0001750 00000000566 13533157123 020262 0 ustar jendrik jendrik 0000000 0000000 import pytest
from . import v
assert v # Silence pyflakes.
def test_syntax_error(v):
v.scan("foo bar")
assert int(v.report()) == 1
def test_null_byte(v):
v.scan("\x00")
assert int(v.report()) == 1
def test_confidence_range(v):
v.scan("""\
def foo():
pass
""")
with pytest.raises(ValueError):
v.get_unused_code(min_confidence=150)
vulture-1.3/tests/test_imports.py 0000644 0001750 0001750 00000011411 13137201537 020431 0 ustar jendrik jendrik 0000000 0000000 from . import check, v
assert v # Silence pyflakes.
def test_import_star(v):
v.scan("""\
from a import *
from a.b import *
""")
check(v.defined_imports, [])
check(v.unused_imports, [])
def test_import_from_future(v):
v.scan("""from __future__ import division""")
check(v.defined_imports, [])
check(v.unused_imports, [])
def test_double_import(v):
v.scan("""\
import foo as bar
import foo
""")
check(v.defined_imports, ['bar', 'foo'])
# Once the bar import is removed, the foo import will be detected.
check(v.unused_imports, ['bar'])
def test_attribute_access(v):
v.scan("""\
# foo.py
class Foo:
pass
# bar.py
from foo import Foo
# main.py
import bar
bar.Foo
""")
check(v.defined_imports, ['Foo', 'bar'])
check(v.unused_imports, [])
def test_nested_import(v):
v.scan("""\
import os.path
os.path.expanduser("~")
""")
check(v.defined_imports, ['os'])
check(v.used_names, ['os'])
check(v.unused_funcs, [])
check(v.unused_imports, [])
check(v.unused_vars, [])
definitions = """\
class A(object):
pass
class B(object):
pass
def C():
pass
D = 42
"""
imports = """\
from any_module import A
import B, C
import D
"""
aliased_imports = """\
from any_module import A as AliasA
import B as AliasB, C as AliasC
import D as AliasD
"""
uses = """\
A()
B()
C()
D()
"""
aliased_uses = """\
AliasA()
AliasB()
AliasC()
AliasD()
"""
def test_definitions(v):
v.scan(definitions)
check(v.defined_classes, ['A', 'B'])
check(v.defined_funcs, ['C'])
check(v.defined_imports, [])
check(v.defined_vars, ['D'])
check(v.used_names, [])
check(v.unused_classes, ['A', 'B'])
check(v.unused_funcs, ['C'])
check(v.unused_imports, [])
check(v.unused_vars, ['D'])
def test_use_original(v):
v.scan(definitions + uses)
check(v.defined_classes, ['A', 'B'])
check(v.defined_funcs, ['C'])
check(v.defined_imports, [])
check(v.defined_vars, ['D'])
check(v.used_names, ['A', 'B', 'C', 'D'])
check(v.unused_funcs, [])
check(v.unused_classes, [])
check(v.unused_imports, [])
check(v.unused_vars, [])
def test_import_original(v):
v.scan(definitions + imports)
check(v.defined_classes, ['A', 'B'])
check(v.defined_funcs, ['C'])
check(v.defined_imports, ['A', 'B', 'C', 'D'])
check(v.defined_vars, ['D'])
check(v.used_names, [])
check(v.unused_classes, ['A', 'B'])
check(v.unused_funcs, ['C'])
check(v.unused_imports, ['A', 'B', 'C', 'D'])
check(v.unused_vars, ['D'])
def test_import_original_use_original(v):
v.scan(definitions + imports + uses)
check(v.defined_classes, ['A', 'B'])
check(v.defined_funcs, ['C'])
check(v.defined_imports, ['A', 'B', 'C', 'D'])
check(v.defined_vars, ['D'])
check(v.used_names, ['A', 'B', 'C', 'D'])
check(v.unused_classes, [])
check(v.unused_funcs, [])
check(v.unused_imports, [])
check(v.unused_vars, [])
def test_import_original_use_alias(v):
v.scan(definitions + imports + aliased_uses)
check(v.defined_classes, ['A', 'B'])
check(v.defined_funcs, ['C'])
check(v.defined_imports, ['A', 'B', 'C', 'D'])
check(v.defined_vars, ['D'])
check(v.used_names, ['AliasA', 'AliasB', 'AliasC', 'AliasD'])
check(v.unused_classes, ['A', 'B'])
check(v.unused_funcs, ['C'])
check(v.unused_imports, ['A', 'B', 'C', 'D'])
check(v.unused_vars, ['D'])
def test_import_alias(v):
v.scan(definitions + aliased_imports)
check(v.defined_classes, ['A', 'B'])
check(v.defined_funcs, ['C'])
check(v.defined_imports, ['AliasA', 'AliasB', 'AliasC', 'AliasD'])
check(v.defined_vars, ['D'])
check(v.used_names, ['A', 'B', 'C', 'D'])
check(v.unused_classes, [])
check(v.unused_funcs, [])
check(v.unused_imports, ['AliasA', 'AliasB', 'AliasC', 'AliasD'])
check(v.unused_vars, [])
def test_import_alias_use_original(v):
v.scan(definitions + aliased_imports + uses)
check(v.defined_classes, ['A', 'B'])
check(v.defined_funcs, ['C'])
check(v.defined_imports, ['AliasA', 'AliasB', 'AliasC', 'AliasD'])
check(v.defined_vars, ['D'])
check(v.used_names, ['A', 'B', 'C', 'D'])
check(v.unused_classes, [])
check(v.unused_funcs, [])
check(v.unused_imports, ['AliasA', 'AliasB', 'AliasC', 'AliasD'])
check(v.unused_vars, [])
def test_import_alias_use_alias(v):
v.scan(definitions + aliased_imports + aliased_uses)
check(v.defined_classes, ['A', 'B'])
check(v.defined_funcs, ['C'])
check(v.defined_imports, ['AliasA', 'AliasB', 'AliasC', 'AliasD'])
check(v.defined_vars, ['D'])
check(
v.used_names,
['A', 'B', 'C', 'D', 'AliasA', 'AliasB', 'AliasC', 'AliasD'])
check(v.unused_classes, [])
check(v.unused_funcs, [])
check(v.unused_imports, [])
check(v.unused_vars, [])
vulture-1.3/tests/test_make_whitelist.py 0000644 0001750 0001750 00000003015 13335401431 021741 0 ustar jendrik jendrik 0000000 0000000 import pytest
from . import check, v
assert v # silence pyflakes
@pytest.fixture
def check_whitelist(v):
def examine(code, results_before, results_after):
v.scan(code)
check(v.get_unused_code(), results_before)
for item in v.get_unused_code():
v.scan(item.get_whitelist_string())
check(v.get_unused_code(), results_after)
return examine
def test_unused_function(check_whitelist):
code = """\
def func():
pass
"""
check_whitelist(code, ['func'], [])
def test_unused_class(check_whitelist):
code = """\
class Foo:
def __init__(self):
pass
"""
check_whitelist(code, ['Foo'], [])
def test_unused_variables(check_whitelist):
code = """\
foo = 'unused'
bar = 'variable'
"""
check_whitelist(code, ['foo', 'bar'], [])
def test_unused_import(check_whitelist):
code = """\
import xyz
import foo as bar
from abc import iou
from lorem import ipsum as dolor
"""
check_whitelist(code, ['xyz', 'bar', 'iou', 'dolor'], [])
def test_unused_attribute(check_whitelist):
code = """\
class Foo:
def bar(self):
self.foobar = 'unused attr'
"""
check_whitelist(code, ['Foo', 'bar', 'foobar'], [])
def test_unused_property(check_whitelist):
code = """\
class Foo:
@property
def bar(self):
pass
"""
check_whitelist(code, ['Foo', 'bar'], [])
def test_unreachable_code(check_whitelist):
code = """\
def foo():
return "Foo Bar"
print("Hello")
"""
check_whitelist(code, ['foo', 'return'], ['return'])
vulture-1.3/tests/test_item.py 0000644 0001750 0001750 00000000374 13305474134 017702 0 ustar jendrik jendrik 0000000 0000000 from . import v
assert v # Silence pyflakes
def test_item_repr(v):
v.scan("""\
import os
message = "foobar"
class Foo:
def bar():
pass
""")
for item in v.get_unused_code():
assert repr(item) == "'{}'".format(item.name)
vulture-1.3/tests/__init__.py 0000644 0001750 0001750 00000002160 13533222047 017434 0 ustar jendrik jendrik 0000000 0000000 import ast
import glob
import os.path
import subprocess
import sys
import pytest
from vulture import core
DIR = os.path.dirname(os.path.abspath(__file__))
REPO = os.path.dirname(DIR)
WHITELISTS = glob.glob(os.path.join(REPO, 'vulture', 'whitelists', '*.py'))
skip_if_not_has_async = pytest.mark.skipif(
not hasattr(ast, 'AsyncFunctionDef'),
reason="needs async support (added in Python 3.5)")
def call_vulture(args, **kwargs):
return subprocess.call(
[sys.executable, '-m', 'vulture'] + args, cwd=REPO, **kwargs)
def check(items_or_names, expected_names):
"""items_or_names must be a collection of Items or a set of strings."""
try:
assert (sorted(item.name for item in items_or_names) ==
sorted(expected_names))
except AttributeError:
assert items_or_names == set(expected_names)
def check_unreachable(v, lineno, size, name):
assert len(v.unreachable_code) == 1
item = v.unreachable_code[0]
assert item.first_lineno == lineno
assert item.size == size
assert item.name == name
@pytest.fixture
def v():
return core.Vulture(verbose=True)
vulture-1.3/tests/test_encoding.py 0000644 0001750 0001750 00000001717 13533164661 020540 0 ustar jendrik jendrik 0000000 0000000 import codecs
import io
from . import v
assert v # Silence pyflakes.
def test_encoding1(v):
v.scan(u"""\
# -*- coding: utf-8 -*-
pass
""")
assert not v.found_dead_code_or_error
def test_encoding2(v):
v.scan(u"""\
#! /usr/bin/env python
# -*- coding: utf-8 -*-
pass
""")
assert not v.found_dead_code_or_error
def test_non_utf8_encoding(v, tmpdir):
code = ""
name = "non_utf8"
non_utf_8_file = str(tmpdir.mkdir(name).join(name + ".py"))
with open(non_utf_8_file, mode='wb') as f:
f.write(codecs.BOM_UTF16_LE)
f.write(code.encode('utf_16_le'))
v.scavenge([f.name])
assert v.found_dead_code_or_error
def test_utf8_with_bom(v, tmpdir):
name = "utf8_bom"
filename = str(tmpdir.mkdir(name).join(name + ".py"))
# utf8_sig prepends the BOM to the file.
with io.open(filename, mode='w', encoding="utf-8-sig") as f:
f.write(u"")
v.scavenge([f.name])
assert not v.found_dead_code_or_error
vulture-1.3/tests/test_sorting.py 0000644 0001750 0001750 00000000603 13305474134 020424 0 ustar jendrik jendrik 0000000 0000000 from . import v
assert v # Silence pyflakes
def test_sorting(v):
v.scan("""\
def foo():
print("Hello, I am a long function.")
return "World"
def bar():
pass
""")
assert [item.name for item in
v.get_unused_code(sort_by_size=True)] == ['bar', 'foo']
assert [item.name for item in
v.get_unused_code(sort_by_size=False)] == ['foo', 'bar']
vulture-1.3/tests/test_scavenging.py 0000644 0001750 0001750 00000024600 13556112722 021066 0 ustar jendrik jendrik 0000000 0000000 import sys
import pytest
from . import check, skip_if_not_has_async, v
assert v # Silence pyflakes.
def test_function_object1(v):
v.scan("""\
def func():
pass
a = func
""")
check(v.defined_funcs, ['func'])
check(v.unused_funcs, [])
def test_function_object2(v):
v.scan("""\
def func():
pass
func
""")
check(v.defined_funcs, ['func'])
check(v.unused_funcs, [])
def test_function1(v):
v.scan("""\
def func1(a):
pass
def func2(b):
func1(b)
""")
check(v.defined_funcs, ['func1', 'func2'])
check(v.unused_funcs, ['func2'])
def test_function2(v):
v.scan("""\
def func(a):
pass
func(5)
""")
check(v.unused_funcs, [])
check(v.defined_funcs, ['func'])
def test_function3(v):
v.scan("""\
def foo(a):
pass
b = foo(5)
""")
check(v.unused_funcs, [])
check(v.defined_funcs, ['foo'])
@skip_if_not_has_async
def test_async_function(v):
v.scan("""\
async def foo():
pass
""")
check(v.defined_funcs, ['foo'])
check(v.unused_funcs, ['foo'])
@skip_if_not_has_async
def test_async_method(v):
v.scan("""\
class Foo:
async def bar(self):
pass
""")
check(v.defined_classes, ['Foo'])
check(v.defined_funcs, ['bar'])
check(v.unused_classes, ['Foo'])
check(v.unused_funcs, ['bar'])
def test_function_and_method1(v):
v.scan("""\
class Bar(object):
def func(self):
pass
def func():
pass
func()
""")
check(v.defined_classes, ['Bar'])
check(v.defined_funcs, ['func', 'func'])
check(v.unused_classes, ['Bar'])
check(v.unused_funcs, [])
def test_attribute1(v):
v.scan("""\
foo.bar = 1
foo.bar = 2
""")
check(v.unused_funcs, [])
check(v.defined_funcs, [])
check(v.defined_attrs, ['bar', 'bar'])
check(v.used_attrs, [])
check(v.unused_attrs, ['bar', 'bar'])
def test_ignored_attributes(v):
v.scan("""\
A._ = 0
A._a = 1
A.__b = 2
A.__c__ = 3
A._d_ = 4
""")
check(v.defined_attrs, ['_', '_a', '__b', '__c__', '_d_'])
check(v.used_attrs, [])
check(v.unused_attrs, ['_', '__b', '__c__', '_a', '_d_'])
check(v.unused_vars, [])
def test_callback1(v):
v.scan("""\
class Bar(object):
def foo(self):
pass
b = Bar()
b.foo
""")
check(v.used_attrs, ['foo'])
check(v.defined_classes, ['Bar'])
check(v.defined_funcs, ['foo'])
check(v.unused_classes, [])
check(v.unused_funcs, [])
def test_class1(v):
v.scan("""\
class Bar(object):
pass
""")
check(v.used_attrs, [])
check(v.defined_classes, ['Bar'])
check(v.unused_classes, ['Bar'])
def test_class2(v):
v.scan("""\
class Bar():
pass
class Foo(Bar):
pass
Foo()
""")
check(v.used_attrs, [])
check(v.used_names, ['Bar', 'Foo'])
check(v.defined_classes, ['Bar', 'Foo'])
check(v.unused_classes, [])
def test_class3(v):
v.scan("""\
class Bar():
pass
[Bar]
""")
check(v.used_attrs, [])
check(v.used_names, ['Bar'])
check(v.defined_classes, ['Bar'])
check(v.unused_classes, [])
def test_class4(v):
v.scan("""\
class Bar():
pass
Bar()
""")
check(v.used_attrs, [])
check(v.defined_classes, ['Bar'])
check(v.unused_classes, [])
def test_class5(v):
v.scan("""\
class Bar():
pass
b = Bar()
""")
check(v.used_attrs, [])
check(v.defined_classes, ['Bar'])
check(v.unused_classes, [])
check(v.unused_vars, ['b'])
def test_class6(v):
v.scan("""\
class Bar():
pass
a = []
a.insert(0, Bar())
""")
check(v.defined_classes, ['Bar'])
check(v.unused_classes, [])
def test_class7(v):
v.scan("""\
class Bar(object):
pass
class Foo(object):
def __init__(self):
self.b = xyz.Bar(self)
""")
check(v.defined_classes, ['Bar', 'Foo'])
check(v.unused_classes, ['Foo'])
def test_method1(v):
v.scan("""\
def __init__(self):
self.a.foo()
class Bar(object):
def foo(self):
pass
""")
check(v.defined_classes, ['Bar'])
check(v.defined_funcs, ['foo'])
check(v.unused_classes, ['Bar'])
check(v.unused_funcs, [])
def test_token_types(v):
v.scan("""\
a
b = 2
c()
x.d
""")
check(v.defined_funcs, [])
check(v.defined_vars, ['b'])
check(v.used_names, ['a', 'c', 'x'])
check(v.used_attrs, ['d'])
check(v.unused_attrs, [])
check(v.unused_funcs, [])
check(v.unused_props, [])
check(v.unused_vars, ['b'])
def test_variable1(v):
v.scan("a = 1\nb = a")
check(v.defined_funcs, [])
check(v.used_names, ['a'])
check(v.defined_vars, ['a', 'b'])
check(v.unused_vars, ['b'])
def test_variable2(v):
v.scan("a = 1\nc = b.a")
check(v.defined_funcs, [])
check(v.defined_vars, ['a', 'c'])
check(v.used_names, ['b'])
check(v.unused_vars, ['c'])
def test_variable3(v):
v.scan("(a, b), c = (d, e, f)")
check(v.defined_funcs, [])
check(v.defined_vars, ['a', 'b', 'c'])
check(v.used_names, ['d', 'e', 'f'])
check(v.unused_vars, ['a', 'b', 'c'])
def test_variable4(v):
v.scan("for a, b in func(): a")
check(v.defined_funcs, [])
check(v.defined_vars, ['a', 'b'])
check(v.used_names, ['a', 'func'])
check(v.unused_vars, ['b'])
def test_variable5(v):
v.scan("[a for a, b in func()]")
check(v.defined_vars, ['a', 'b'])
check(v.used_names, ['a', 'func'])
check(v.unused_vars, ['b'])
def test_ignored_variables(v):
v.scan("""\
_ = 0
_a = 1
__b = 2
__c__ = 3
_d_ = 4
""")
check(v.defined_vars, ['__b'])
check(sorted(v.used_names), [])
check(v.unused_vars, ['__b'])
def test_prop1(v):
v.scan("""\
class Bar(object):
@property
def prop(self):
pass
c = Bar()
c.prop
""")
check(v.defined_classes, ['Bar'])
check(v.defined_props, ['prop'])
check(v.unused_classes, [])
check(v.unused_props, [])
def test_prop2(v):
v.scan("""\
class Bar(object):
@property
def prop(self):
pass
prop = 1
""")
check(v.defined_classes, ['Bar'])
check(v.defined_props, ['prop'])
check(v.defined_vars, ['prop'])
check(v.unused_classes, ['Bar'])
check(v.unused_props, ['prop'])
def test_object_attribute(v):
v.scan("""\
class Bar(object):
def __init__(self):
self.a = []
""")
check(v.defined_attrs, ['a'])
check(v.defined_classes, ['Bar'])
check(v.defined_vars, [])
check(v.used_attrs, [])
check(v.unused_attrs, ['a'])
check(v.unused_classes, ['Bar'])
def test_function_names_in_test_file(v):
v.scan("""\
def test_func():
pass
def other_func():
pass
class TestClass:
pass
class BasicTestCase:
pass
class OtherClass:
pass
""", filename='test_function_names.py')
check(v.defined_attrs, [])
check(v.defined_classes, ['OtherClass'])
check(v.defined_funcs, ['other_func'])
check(v.defined_vars, [])
check(v.used_attrs, [])
check(v.used_names, [])
check(v.unused_attrs, [])
check(v.unused_classes, ['OtherClass'])
check(v.unused_funcs, ['other_func'])
@skip_if_not_has_async
def test_async_function_name_in_test_file(v):
v.scan("""\
async def test_func():
pass
async def other_func():
pass
""", filename='test_function_names.py')
check(v.defined_funcs, ['other_func'])
check(v.unused_funcs, ['other_func'])
@skip_if_not_has_async
def test_async_function_name_in_normal_file(v):
v.scan("""\
async def test_func():
pass
async def other_func():
pass
""", filename='function_names.py')
check(v.defined_funcs, ['test_func', 'other_func'])
check(v.unused_funcs, ['other_func', 'test_func'])
def test_function_names_in_normal_file(v):
v.scan("""\
def test_func():
pass
def other_func():
pass
class TestClass:
pass
class BasicTestCase:
pass
class OtherClass:
pass
""")
check(v.defined_attrs, [])
check(v.defined_classes, ['BasicTestCase', 'OtherClass', 'TestClass'])
check(v.defined_funcs, ['test_func', 'other_func'])
check(v.defined_vars, [])
check(v.used_attrs, [])
check(v.used_names, [])
check(v.unused_attrs, [])
check(v.unused_classes, ['BasicTestCase', 'OtherClass', 'TestClass'])
check(v.unused_funcs, ['other_func', 'test_func'])
def test_global_attribute(v):
v.scan("""\
# Module foo:
a = 1
if a == 1:
pass
# Module bar:
import foo
foo.a = 2
""")
check(v.defined_attrs, ['a'])
check(v.defined_vars, ['a'])
check(v.used_attrs, [])
check(v.used_names, ['a', 'foo'])
check(v.unused_attrs, ['a'])
def test_boolean(v):
v.scan("""\
a = True
a
""")
check(v.defined_vars, ['a'])
check(v.used_names, ['a'])
check(v.unused_vars, [])
def test_builtin_types(v):
v.scan("""\
a = b
a = 1
a = "s"
a = object
a = False
""")
check(v.defined_vars, ['a'] * 5)
check(v.used_names, ['b'])
check(v.unused_vars, ['a'] * 5)
def test_unused_args(v):
v.scan("""\
def foo(x, y):
return x + 1
""")
check(v.defined_vars, ['x', 'y'])
check(v.used_names, ['x'])
check(v.unused_vars, ['y'])
def test_unused_kwargs(v):
v.scan("""\
def foo(x, y=3, **kwargs):
return x + 1
""")
check(v.defined_vars, ['kwargs', 'x', 'y'])
check(v.used_names, ['x'])
check(v.unused_vars, ['kwargs', 'y'])
def test_unused_kwargs_with_odd_name(v):
v.scan("""\
def foo(**bar):
pass
""")
check(v.defined_vars, ['bar'])
check(v.used_names, [])
check(v.unused_vars, ['bar'])
def test_unused_vararg(v):
v.scan("""\
def foo(*bar):
pass
""")
check(v.defined_vars, ['bar'])
check(v.used_names, [])
check(v.unused_vars, ['bar'])
def test_multiple_definition(v):
v.scan("""\
a = 1
a = 2
""")
check(v.defined_vars, ['a', 'a'])
check(v.used_names, [])
check(v.unused_vars, ['a', 'a'])
@pytest.mark.skipif(sys.version_info < (3, 5),
reason="requires Python 3.5+")
def test_arg_type_annotation(v):
v.scan("""\
from typing import Iterable
def f(n: int) -> Iterable[int]:
yield n
""")
check(v.unused_vars, [])
check(v.unused_funcs, ['f'])
check(v.unused_imports, [])
@pytest.mark.skipif(sys.version_info < (3, 6),
reason="requires Python 3.6+")
def test_var_type_annotation(v):
v.scan("""\
from typing import List
x: List[int] = [1]
""")
check(v.unused_vars, ['x'])
check(v.unused_funcs, [])
check(v.unused_imports, [])
def test_type_hint_comments(v):
v.scan("""\
import typing
if typing.TYPE_CHECKING:
from typing import List, Text
def foo(foo_li):
# type: (List[Text]) -> None
for bar in foo_li:
bar.xyz()
""")
check(v.unused_imports, ['List', 'Text'])
vulture-1.3/tests/test_ignore.py 0000644 0001750 0001750 00000005707 13533222643 020233 0 ustar jendrik jendrik 0000000 0000000 from vulture import core
from . import check, skip_if_not_has_async
def check_ignore(code, ignore_names, ignore_decorators, expected):
v = core.Vulture(verbose=True, ignore_names=ignore_names,
ignore_decorators=ignore_decorators)
v.scan(code)
check(v.get_unused_code(), expected)
def test_var():
code = """\
fio = 1
fao = 2
bar = 2
ftobar = 3
baz = 10000
funny = True
"""
check_ignore(code, ['f?o*', 'ba[rz]'], [], ['funny'])
def test_function():
code = """\
def foo_one():
pass
def foo_two():
pass
def foo():
pass
def bar():
pass
"""
check_ignore(code, ['foo*'], [], ['bar'])
@skip_if_not_has_async
def test_async_function():
code = """\
async def foobar():
pass
async def bar():
pass
"""
check_ignore(code, ['foo*'], [], ['bar'])
def test_class():
code = """\
class Foo:
def __init__(self):
pass
"""
check_ignore(code, ['Foo'], [], [])
def test_class_ignore():
code = """\
@bar
class Foo:
pass
class Bar:
pass
"""
check_ignore(code, [], [], ['Foo', 'Bar'])
def test_property():
code = """\
class Foo:
@property
def some_property(self, a):
return a
@property
@bar
def foo_bar(self):
return 'bar'
"""
check_ignore(code, ['Foo'], ['@property'], [])
check_ignore(code, ['Foo'], [], ['some_property', 'foo_bar'])
def test_attribute():
code = """\
class Foo:
def __init__(self, attr_foo, attr_bar):
self.attr_foo = attr_foo
self.attr_bar = attr_bar
"""
check_ignore(code, ['foo', '*_foo'], [], ['Foo', 'attr_bar'])
def test_decorated_functions():
code = """\
def decor():
return help
class FooBar:
def foobar(self):
return help
@property
def prop_one(self):
pass
f = FooBar()
@decor()
def bar():
pass
@f.foobar
def foo():
pass
@bar
@foo
@f.foobar()
def barfoo():
pass
"""
check_ignore(code, [], ['@decor', '*@f.foobar'], ['prop_one'])
check_ignore(code, [], ['*decor', '@*f.foobar'], ['prop_one'])
@skip_if_not_has_async
def test_decorated_async_functions():
code = """\
@app.route('something')
@foobar
async def async_function():
pass
@a.b.c
async def foo():
pass
"""
check_ignore(code, [], ['@app.route', '@a.b'], ['foo'])
def test_decorated_property():
code = """\
@bar
@property
def foo():
pass
"""
check_ignore(code, [], ['@bar'], [])
check_ignore(code, [], ['@baz'], ['foo'])
check_ignore(code, [], ['@property'], [])
def test_decorated_property_reversed():
code = """\
@property
@bar
def foo():
pass
"""
check_ignore(code, [], ['@bar'], [])
check_ignore(code, [], ['@property'], [])
check_ignore(code, [], ['@b*r'], [])
check_ignore(code, [], ['@barfoo'], ['foo'])
def test_decorated_class():
code = """\
@barfoo
@foo.bar('foo')
class Bar:
def __init__(self):
pass
"""
check_ignore(code, [], [], ['Bar'])
check_ignore(code, [], ['@bar*'], [])
vulture-1.3/tests/test_conditions.py 0000644 0001750 0001750 00000007643 13566040247 021126 0 ustar jendrik jendrik 0000000 0000000 import ast
import sys
from vulture import utils
from . import check_unreachable
from . import v
assert v # Silence pyflakes
def check_condition(code, result):
condition = ast.parse(code, mode='eval').body
if result:
assert utils.condition_is_always_true(condition)
else:
assert utils.condition_is_always_false(condition)
def test_false():
check_condition('False', False)
check_condition('None', False)
check_condition("0", False)
# Only Python 3.0-3.6 allows addition and subtraction in ast.literal_eval.
# (see https://bugs.python.org/issue31778)
if (3, 0) <= sys.version_info < (3, 7):
check_condition("1 - 1", False)
def test_empty():
check_condition("''", False)
check_condition("[]", False)
check_condition("{}", False)
def test_true():
check_condition("True", True)
check_condition("2", True)
check_condition("'s'", True)
check_condition("['foo', 'bar']", True)
check_condition("{'a': 1, 'b': 2}", True)
def test_complex_conditions():
conditions = [
('foo and False', True, False),
('foo or False', False, False),
('foo and True', False, False),
('foo or True', False, True),
('False and foo', True, False),
('False and 1', True, False),
('not False', False, True),
('not True', True, False),
('not foo', False, False),
('foo and (False or [])', True, False),
('(foo and bar) or {"a": 1}', False, True),
]
for condition, always_false, always_true in conditions:
condition = ast.parse(condition, mode='eval').body
assert not (always_false and always_true)
assert utils.condition_is_always_false(condition) == always_false
assert utils.condition_is_always_true(condition) == always_true
def test_errors():
conditions = [
'foo',
'__name__ == "__main__"',
'chr(-1)',
'getattr(True, "foo")',
'hasattr(str, "foo")',
'isinstance(True, True)',
'globals()',
'locals()',
'().__class__',
]
for condition in conditions:
condition = ast.parse(condition, mode='eval').body
assert not utils.condition_is_always_false(condition)
assert not utils.condition_is_always_true(condition)
def test_while(v):
v.scan("""\
while False:
pass
""")
check_unreachable(v, 1, 2, 'while')
def test_while_nested(v):
v.scan("""\
while True:
while False:
pass
""")
check_unreachable(v, 2, 2, 'while')
def test_if_false(v):
v.scan("""\
if False:
pass
""")
check_unreachable(v, 1, 2, 'if')
def test_elif_false(v):
v.scan("""\
if bar():
pass
elif False:
print("Unreachable")
""")
check_unreachable(v, 3, 2, 'if')
def test_nested_if_statements_false(v):
v.scan("""\
if foo():
if bar():
pass
elif False:
print("Unreachable")
pass
elif something():
print("Reachable")
else:
pass
else:
pass
""")
check_unreachable(v, 4, 3, 'if')
def test_if_false_same_line(v):
v.scan("""\
if False: a = 1
else: c = 3
""")
check_unreachable(v, 1, 1, 'if')
def test_if_true(v):
v.scan("""\
if True:
a = 1
b = 2
else:
c = 3
d = 3
""")
# For simplicity, we don't report the "else" line as dead code.
check_unreachable(v, 5, 2, 'else')
def test_if_true_same_line(v):
v.scan("""\
if True:
a = 1
b = 2
else: c = 3
d = 3
""")
check_unreachable(v, 4, 1, 'else')
def test_nested_if_statements_true(v):
v.scan("""\
if foo():
if bar():
pass
elif True:
if something():
pass
else:
pass
elif something_else():
print("foo")
else:
print("bar")
else:
pass
""")
check_unreachable(v, 9, 4, 'else')
def test_redundant_if(v):
v.scan("""\
if [5]:
pass
""")
print(v.unreachable_code[0].size)
check_unreachable(v, 1, 2, 'if')
vulture-1.3/tests/test_report.py 0000644 0001750 0001750 00000003446 13542223120 020250 0 ustar jendrik jendrik 0000000 0000000 import sys
import pytest
from . import v
assert v # Silence pyflakes
mock_code = """\
import foo
class Foo:
def __init__(self):
print("Initialized foo")
def bar(self):
self.foobar = "unused attribute"
foobar = "unused variable"
return
print("unreachable")
@property
def myprop(self):
pass
"""
@pytest.fixture
def check_report(v, capsys):
def test_report(code, expected, make_whitelist=False):
filename = 'foo.py'
v.scan(code, filename=filename)
capsys.readouterr()
v.report(make_whitelist=make_whitelist)
assert capsys.readouterr().out == expected.format(filename=filename)
return test_report
def test_item_report(check_report):
expected = """\
{filename}:1: unused import 'foo' (90% confidence)
{filename}:3: unused class 'Foo' (60% confidence)
{filename}:7: unused function 'bar' (60% confidence)
{filename}:8: unused attribute 'foobar' (60% confidence)
{filename}:9: unused variable 'foobar' (60% confidence)
{filename}:11: unreachable code after 'return' (100% confidence)
{filename}:13: unused property 'myprop' (60% confidence)
"""
if sys.version_info >= (3, 8):
expected = expected.replace("{filename}:13:", "{filename}:14:")
check_report(mock_code, expected)
def test_make_whitelist(check_report):
expected = """\
foo # unused import ({filename}:1)
Foo # unused class ({filename}:3)
bar # unused function ({filename}:7)
_.foobar # unused attribute ({filename}:8)
foobar # unused variable ({filename}:9)
# unreachable code after 'return' ({filename}:11)
_.myprop # unused property ({filename}:13)
"""
if sys.version_info >= (3, 8):
expected = expected.replace("{filename}:13)", "{filename}:14)")
check_report(mock_code, expected, make_whitelist=True)
vulture-1.3/tests/test_format_strings.py 0000664 0001750 0001750 00000002617 13562342743 022016 0 ustar jendrik jendrik 0000000 0000000 import sys
import pytest
from . import check, v
assert v # Silence pyflakes.
def test_old_format_string(v):
v.scan("'%(a)s, %(b)d' % locals()")
check(v.used_names, ['a', 'b', 'locals'])
def test_new_format_string(v):
v.scan("'{a}, {b:0d} {c:<30} {d:.2%}'.format(**locals())")
check(v.used_names, ['a', 'b', 'c', 'd', 'locals'])
@pytest.mark.skipif(
sys.version_info < (3, 6),
reason="needs f-string support (added in Python 3.6)")
def test_f_string(v):
v.scan('''\
f'{a}, {b:0d} {c:<30} {d:.2%} {e()} {f:{width}.{precision}}'
f'{ {x:y for (x, y) in ((1, 2), (3, 4))} }'
''')
check(v.used_names,
['a', 'b', 'c', 'd', 'e', 'f', 'precision', 'width', 'x', 'y'])
def test_new_format_string_access(v):
v.scan("'{a.b}, {c.d.e} {f[g]} {h[i][j]}'.format(**locals())")
check(v.used_names, ['a', 'c', 'f', 'h', 'locals'])
def test_new_format_string_attributes(v):
v.scan("'{a.b}, {c.d.e} {f[g]} {h[i][j].k}'.format(**locals())")
check(v.used_names, ['a', 'c', 'f', 'h', 'locals'])
check(v.used_attrs, ['b', 'd', 'e', 'k', 'format'])
def test_new_format_string_numbers(v):
v.scan("'{0.b}, {0.d.e} {0[1]} {0[1][1].k}'.format('foo')")
check(v.used_names, [])
check(v.used_attrs, ['b', 'd', 'e', 'k', 'format'])
def test_incorrect_format_string(v):
v.scan('"{"')
v.scan('"{!-a:}"')
check(v.used_names, [])
check(v.used_attrs, [])
vulture-1.3/tests/test_unreachable.py 0000644 0001750 0001750 00000011223 13175312744 021213 0 ustar jendrik jendrik 0000000 0000000 from . import check_unreachable
from . import v
assert v # Silence pyflakes
def test_return_assignment(v):
v.scan("""\
def foo():
print("Hello World")
return
a = 1
""")
check_unreachable(v, 4, 1, 'return')
def test_return_multiline_return_statements(v):
v.scan("""\
def foo():
print("Something")
return (something,
that,
spans,
over,
multiple,
lines)
print("Hello World")
""")
check_unreachable(v, 9, 1, 'return')
def test_return_multiple_return_statements(v):
v.scan("""\
def foo():
return something
return None
return (some, statement)
""")
check_unreachable(v, 3, 2, 'return')
def test_return_pass(v):
v.scan("""\
def foo():
return
pass
return something
""")
check_unreachable(v, 3, 2, 'return')
def test_return_multiline_return(v):
v.scan("""
def foo():
return \
"Hello"
print("Unreachable code")
""")
check_unreachable(v, 4, 1, 'return')
def test_return_recursive_functions(v):
v.scan("""\
def foo(a):
if a == 1:
return 1
else:
return foo(a - 1)
print("This line is never executed")
""")
check_unreachable(v, 6, 1, 'return')
def test_return_semicolon(v):
v.scan("""\
def foo():
return; a = 1
""")
check_unreachable(v, 2, 1, 'return')
def test_return_list(v):
v.scan("""\
def foo(a):
return
a[1:2]
""")
check_unreachable(v, 3, 1, 'return')
def test_return_continue(v):
v.scan("""\
def foo():
if foo():
return True
continue
else:
return False
""")
check_unreachable(v, 4, 1, 'return')
def test_raise_assignment(v):
v.scan("""\
def foo():
raise ValueError
li = []
""")
check_unreachable(v, 3, 1, 'raise')
def test_multiple_raise_statements(v):
v.scan("""\
def foo():
a = 1
raise
raise KeyError
# a comment
b = 2
raise CustomDefinedError
""")
check_unreachable(v, 4, 4, 'raise')
def test_return_with_raise(v):
v.scan("""\
def foo():
a = 1
return
raise ValueError
return
""")
check_unreachable(v, 4, 2, 'return')
def test_return_comment_and_code(v):
v.scan("""\
def foo():
return
# This is a comment
print("Hello World")
""")
check_unreachable(v, 4, 1, 'return')
def test_raise_with_return(v):
v.scan("""\
def foo():
a = 1
raise
return a
""")
check_unreachable(v, 4, 1, 'raise')
def test_raise_error_message(v):
v.scan("""\
def foo():
raise SomeError("There is a problem")
print("I am unreachable")
""")
check_unreachable(v, 3, 1, 'raise')
def test_raise_try_except(v):
v.scan("""\
def foo():
try:
a = 1
raise
except IOError as e:
print("We have some problem.")
raise
print(":-(")
""")
check_unreachable(v, 8, 1, 'raise')
def test_raise_with_comment_and_code(v):
v.scan("""\
def foo():
raise
# This is a comment
print("Something")
return None
""")
check_unreachable(v, 4, 2, 'raise')
def test_continue_basic(v):
v.scan("""\
def foo():
if bar():
a = 1
else:
continue
a = 2
""")
check_unreachable(v, 6, 1, 'continue')
def test_continue_one_liner(v):
v.scan("""\
def foo():
for i in range(1, 10):
if i == 5: continue
print(1 / i)
""")
assert v.unreachable_code == []
def test_continue_nested_loops(v):
v.scan("""\
def foo():
a = 0
if something():
foo()
if bar():
a = 2
continue
# This is unreachable
a = 1
elif a == 1:
pass
else:
a = 3
continue
else:
continue
""")
check_unreachable(v, 9, 1, 'continue')
def test_continue_with_comment_and_code(v):
v.scan("""\
def foo():
if bar1():
bar2()
else:
a = 1
continue
# Just a comment
raise ValueError
""")
check_unreachable(v, 8, 1, 'continue')
def test_break_basic(v):
v.scan("""\
def foo():
for i in range(123):
break
# A comment
return
dead = 1
""")
check_unreachable(v, 5, 2, 'break')
def test_break_one_liner(v):
v.scan("""\
def foo():
for i in range(10):
if i == 3: break
print(i)
""")
assert v.unreachable_code == []
def test_break_with_comment_and_code(v):
v.scan("""\
while True:
break
# some comment
print("Hello")
""")
check_unreachable(v, 4, 1, 'break')
def test_while_true_else(v):
v.scan("""\
while True:
print("I won't stop")
else:
print("I won't run")
""")
check_unreachable(v, 4, 1, 'else')
vulture-1.3/tests/test_confidence.py 0000644 0001750 0001750 00000003664 13533226354 021050 0 ustar jendrik jendrik 0000000 0000000 import sys
from vulture import core
from . import skip_if_not_has_async
dc = core.DEFAULT_CONFIDENCE
def check_min_confidence(code, min_confidence, expected):
v = core.Vulture(verbose=True)
v.scan(code)
detected = {
item.name: item.confidence
for item in v.get_unused_code(min_confidence=min_confidence)}
assert detected == expected
def test_confidence_import():
code = """\
import foo
"""
check_min_confidence(code, 50, {'foo': 90})
check_min_confidence(code, 100, {})
def test_confidence_unreachable():
code = """\
def foo():
return
bar()
foo()
"""
check_min_confidence(code, 50, {'return': 100})
check_min_confidence(code, 100, {'return': 100})
def test_function_arg():
code = """\
def foo(a):
b = 3
foo(5)
"""
if sys.version_info < (3, 0):
# Python 2
check_min_confidence(code, 50, {'a': dc, 'b': dc})
check_min_confidence(code, dc, {'a': dc, 'b': dc})
check_min_confidence(code, 100, {})
else:
# Python 3
check_min_confidence(code, 50, {'a': 100, 'b': dc})
check_min_confidence(code, dc, {'a': 100, 'b': dc})
check_min_confidence(code, 100, {'a': 100})
def test_confidence_class():
code = """\
class Foo:
pass
"""
check_min_confidence(code, 50, {'Foo': dc})
check_min_confidence(code, 100, {})
def test_confidence_attr():
code = "A.b = 'something'"
check_min_confidence(code, 50, {'b': dc})
check_min_confidence(code, 100, {})
def test_confidence_props():
code = """\
class Foo:
@property
def some_prop():
pass
Foo()
"""
check_min_confidence(code, 50, {'some_prop': dc})
check_min_confidence(code, 100, {})
@skip_if_not_has_async
def test_confidence_async_def():
code = """\
async def foo():
if bar():
pass
else:
print("Else")
"""
check_min_confidence(code, 50, {'foo': dc})
check_min_confidence(code, 75, {})
vulture-1.3/PKG-INFO 0000644 0001750 0001750 00000047407 13616004367 015300 0 ustar jendrik jendrik 0000000 0000000 Metadata-Version: 1.2
Name: vulture
Version: 1.3
Summary: Find dead code
Home-page: https://github.com/jendrikseipp/vulture
Author: Jendrik Seipp
Author-email: jendrikseipp@gmail.com
License: MIT
Description: Vulture - Find dead code
========================
.. image:: https://travis-ci.org/jendrikseipp/vulture.svg?branch=master
:target: https://travis-ci.org/jendrikseipp/vulture
:alt: Travis CI build status (Linux)
.. image:: https://ci.appveyor.com/api/projects/status/github/jendrikseipp/vulture?svg=true
:target: https://ci.appveyor.com/project/jendrikseipp96693/vulture
:alt: AppVeyor CI build status (Windows)
.. image:: https://coveralls.io/repos/github/jendrikseipp/vulture/badge.svg?branch=master
:target: https://coveralls.io/github/jendrikseipp/vulture?branch=master
Vulture finds unused code in Python programs. This is useful for
cleaning up and finding errors in large code bases. If you run Vulture
on both your library and test suite you can find untested code.
Due to Python's dynamic nature, static code analyzers like Vulture are
likely to miss some dead code. Also, code that is only called implicitly
may be reported as unused. Nonetheless, Vulture can be a very helpful
tool for higher code quality.
Features
--------
* fast: uses static code analysis
* tested: tests itself and has complete test coverage
* complements pyflakes and has the same output syntax
* sorts unused classes and functions by size with ``--sort-by-size``
* supports Python 2.7 and Python >= 3.5
Installation
------------
::
$ pip install vulture # from PyPI
$ pip install . # from cloned repo
Usage
-----
::
$ vulture myscript.py # or
$ python3 -m vulture myscript.py
$ vulture myscript.py mypackage/
$ vulture myscript.py --min-confidence 100 # Only report 100% dead code.
The provided arguments may be Python files or directories. For each
directory Vulture analyzes all contained `*.py` files.
Vulture assigns each chunk of dead code a confidence value. A confidence
value of 100% means that the code will never be executed. Values below
100% are only estimates for how likely it is that the code is unused.
After you have found and deleted dead code, run Vulture again, because
it may discover more dead code.
**Handling false positives**
You can add used code that is reported as unused to a Python module and
add it to the list of scanned paths. To obtain such a whitelist
automatically, pass ``--make-whitelist`` to Vulture. ::
$ vulture mydir --make-whitelist > whitelist.py
$ vulture mydir whitelist.py
We collect whitelists for common Python modules and packages in
``vulture/whitelists/`` (pull requests are welcome). If you want to
ignore a whole file or directory, use the ``--exclude`` parameter (e.g.,
``--exclude *settings.py,docs/``).
**Ignoring names**
You can use ``--ignore-names foo*,ba[rz]`` to let Vulture ignore all names
starting with ``foo`` and the names ``bar`` and ``baz``. Additionally, the
``--ignore-decorators`` option can be used to ignore functions decorated
with the given decorator. This is helpful for example in Flask projects,
where you can use ``--ignore-decorators "@app.route"`` to ignore all functions
with the ``@app.route`` decorator.
We recommend using whitelists instead of ``--ignore-names`` or
``--ignore-decorators`` whenever possible, since whitelists are automatically
checked for syntactic correctness when passed to Vulture and often you can
even pass them to your Python interpreter and let it check that all
whitelisted code actually still exists in your project.
**Marking unused variables**
There are situations where you can't just remove unused variables, e.g.,
in tuple assignments or function signatures. Vulture will ignore these
variables if they start with an underscore (e.g., ``_x, y = get_pos()``).
**Minimum confidence**
You can use the ``--min-confidence`` flag to set the minimum confidence
for code to be reported as unused. Use ``--min-confidence 100`` to only
report code that is guaranteed to be unused within the analyzed files.
How does it work?
-----------------
Vulture uses the ``ast`` module to build abstract syntax trees for all
given files. While traversing all syntax trees it records the names of
defined and used objects. Afterwards, it reports the objects which have
been defined, but not used. This analysis ignores scopes and only takes
object names into account.
Vulture also detects unreachable code by looking for code after
``return``, ``break``, ``continue`` and ``raise`` statements, and by
searching for unsatisfiable ``if``- and ``while``-conditions.
Sort by size
------------
When using the ``--sort-by-size`` option, Vulture sorts unused code by
its number of lines. This helps developers prioritize where to look for
dead code first.
Examples
--------
Consider the following Python script (``dead_code.py``):
.. code:: python
import os
class Greeter:
def greet(self):
print("Hi")
def hello_world():
message = "Hello, world!"
greeter = Greeter()
greet_func = getattr(greeter, "greet")
greet_func()
if __name__ == "__main__":
hello_world()
Calling ::
vulture dead_code.py
results in the following output::
dead_code.py:1: unused import 'os' (90% confidence)
dead_code.py:4: unused function 'greet' (60% confidence)
dead_code.py:8: unused variable 'message' (60% confidence)
Vulture correctly reports "os" and "message" as unused, but it fails to
detect that "greet" is actually used. The recommended method to deal with
false positives like this is to create a whitelist Python file.
**Preparing whitelists**
In a whitelist we simulate the usage of variables, attributes, etc. For
the program above, a whitelist could look as follows:
.. code:: python
# whitelist_dead_code.py
from dead_code import Greeter
Greeter.greet
Alternatively, you can pass ``--make-whitelist`` to Vulture and obtain
an automatically generated whitelist.
Passing both the original program and the whitelist to Vulture ::
vulture dead_code.py whitelist_dead_code.py
makes Vulture ignore the "greet" method::
dead_code.py:1: unused import 'os' (90% confidence)
dead_code.py:8: unused variable 'message' (60% confidence)
Exit codes
----------
+-----------+---------------------------------------------------------------+
| Exit code | Description |
+===========+===============================================================+
| 0 | No dead code found |
+-----------+---------------------------------------------------------------+
| 1 | Dead code found |
+-----------+---------------------------------------------------------------+
| 1 | Invalid input (file missing, syntax error, wrong encoding) |
+-----------+---------------------------------------------------------------+
| 2 | Invalid command line arguments |
+-----------+---------------------------------------------------------------+
Similar programs
----------------
* `pyflakes `_ finds unused imports and
unused local variables (in addition to many other programmatic errors).
* `coverage `_ finds unused
code more reliably than Vulture, but requires all branches of the code to
actually be run.
* `uncalled `_ finds dead code by using
the abstract syntax tree (like Vulture), regular expressions, or both.
* `dead `_ finds dead code by using
the abstract syntax tree (like Vulture).
Participate
-----------
Please visit https://github.com/jendrikseipp/vulture to report any
issues or to make pull requests.
* Contributing guide: `CONTRIBUTING.rst `_
* Changelog: `NEWS.rst `_
* Roadmap: `TODO.rst `_
News
====
1.3 (2020-02-03)
----------------
* Detect redundant 'if' conditions without 'else' blocks.
* Add whitelist for ``string.Formatter`` (Joseph Bylund, #183).
1.2 (2019-11-22)
----------------
* Fix tests for Python 3.8 (#166).
* Use new ``Constant`` AST node under Python 3.8+ (#175).
* Add test for f-strings (#177).
* Add whitelist for ``logging`` module.
1.1 (2019-09-23)
----------------
* Add ``sys.excepthook`` to ``sys`` whitelist.
* Add whitelist for ``ctypes`` module.
* Check that type annotations are parsed and type comments are ignored (thanks @kx-chen).
* Support checking files with BOM under Python 2.7 (#170).
1.0 (2018-10-23)
----------------
* Add ``--ignore-decorators`` flag (thanks @RJ722).
* Add whitelist for ``threading`` module (thanks @andrewhalle).
0.29 (2018-07-31)
-----------------
* Add ``--ignore-names`` flag for ignoring names matching the given glob patterns (thanks @RJ722).
0.28 (2018-07-05)
-----------------
* Add ``--make-whitelist`` flag for reporting output in whitelist format (thanks @RJ722).
* Ignore case of ``--exclude`` arguments on Windows.
* Add ``*-test.py`` to recognized test file patterns.
* Add ``failureException``, ``longMessage`` and ``maxDiff`` to ``unittest`` whitelist.
* Refer to actual objects rather than their mocks in default whitelists (thanks @RJ722).
* Don't import any Vulture modules in setup.py (thanks @RJ722).
0.27 (2018-06-05)
-----------------
* Report ``while (True): ... else: ...`` as unreachable (thanks @RJ722).
* Use ``argparse`` instead of ``optparse``.
* Whitelist Mock.return_value and Mock.side_effect in unittest.mock module.
* Drop support for Python 2.6 and 3.3.
* Improve documentation and test coverage (thanks @RJ722).
0.26 (2017-08-28)
-----------------
* Detect ``async`` function definitions (thanks @RJ722).
* Add ``Item.get_report()`` method (thanks @RJ722).
* Move method for finding Python modules out of Vulture class.
0.25 (2017-08-15)
-----------------
* Detect unsatisfiable statements containing ``and``, ``or`` and ``not``.
* Use filenames and line numbers as tie-breakers when sorting by size.
* Store first and last line numbers in Item objects.
* Pass relevant options directly to ``scavenge()`` and ``report()``.
0.24 (2017-08-14)
-----------------
* Detect unsatisfiable ``while``-conditions (thanks @RJ722).
* Detect unsatisfiable ``if``- and ``else``-conditions (thanks @RJ722).
* Handle null bytes in source code.
0.23 (2017-08-10)
-----------------
* Add ``--min-confidence`` flag (thanks @RJ722).
0.22 (2017-08-04)
-----------------
* Detect unreachable code after ``return``, ``break``, ``continue`` and
``raise`` (thanks @RJ722).
* Parse all variable and attribute names in new format strings.
* Extend ast whitelist.
0.21 (2017-07-26)
-----------------
* If an unused item is defined multiple times, report it multiple times.
* Make size estimates for function calls more accurate.
* Create wheel files for Vulture (thanks @RJ722).
0.20 (2017-07-26)
-----------------
* Report unused tuple assignments as dead code.
* Report attribute names that have the same names as variables as dead code.
* Let Item class inherit from ``object`` (thanks @RJ722).
* Handle names imported as aliases like all other used variable names.
* Rename Vulture.used_vars to Vulture.used_names.
* Use function for determining which imports to ignore.
* Only try to import each whitelist file once.
* Store used names and used attributes in sets instead of lists.
* Fix estimating the size of code containing ellipses (...).
* Refactor and simplify code.
0.19 (2017-07-20)
-----------------
* Don't ignore `__foo` variable names.
* Use separate methods for determining whether to ignore classes and functions.
* Only try to find a whitelist for each defined import once (thanks @roivanov).
* Fix finding the last child for many types of AST nodes.
0.18 (2017-07-17)
-----------------
* Make `--sort-by-size` faster and more accurate (thanks @RJ722).
0.17 (2017-07-17)
-----------------
* Add `get_unused_code()` method.
* Return with exit code 1 when syntax errors are found or files can't be read.
0.16 (2017-07-12)
-----------------
* Differentiate between unused classes and functions (thanks @RJ722).
* Add --sort-by-size option (thanks @jackric and @RJ722).
* Count imports as used if they are accessed as module attributes.
0.15 (2017-07-04)
-----------------
* Automatically include whitelists based on imported modules (thanks @RJ722).
* Add --version parameter (thanks @RJ722).
* Add appveyor tests for testing on Windows (thanks @RJ722).
0.14 (2017-04-06)
-----------------
* Add stub whitelist file for Python standard library (thanks @RJ722)
* Ignore class names starting with "Test" in "test\_" files (thanks @thisch).
* Ignore "test\_" functions only in "test\_" files.
0.13 (2017-03-06)
-----------------
* Ignore star-imported names since we cannot detect whether they are used.
* Move repository to GitHub.
0.12 (2017-01-05)
-----------------
* Detect unused imports.
* Use tokenize.open() on Python >= 3.2 for reading input files, assume
UTF-8 encoding on older Python versions.
0.11 (2016-11-27)
-----------------
* Use the system's default encoding when reading files.
* Report syntax errors instead of aborting.
0.10 (2016-07-14)
-----------------
* Detect unused function and method arguments (issue #15).
* Detect unused \*args and \*\*kwargs parameters.
* Change license from GPL to MIT.
0.9 (2016-06-29)
----------------
* Don't flag attributes as unused if they are used as global variables
in another module (thanks Florian Bruhin).
* Don't consider "True" and "False" variable names.
* Abort with error message when invoked on .pyc files.
0.8.1 (2015-09-28)
------------------
* Fix code for Python 3.
0.8 (2015-09-28)
----------------
* Do not flag names imported with "import as" as dead code (thanks Tom Terrace).
0.7 (2015-09-26)
----------------
* Exit with exitcode 1 if path on commandline can't be found.
* Test vulture with vulture using a whitelist module for false positives.
* Add tests that run vulture as a script.
* Add "python setup.py test" command for running tests.
* Add support for tox.
* Raise test coverage to 100%.
* Remove ez_setup.py.
0.6 (2014-09-06)
----------------
* Ignore function names starting with "test\_".
* Parse variable names in new format strings (e.g. "This is {x}".format(x="nice")).
* Only parse alphanumeric variable names in format strings and ignore types.
* Abort with exit code 1 on syntax errors.
* Support installation under Windows by using setuptools (thanks Reuben Fletcher-Costin).
0.5 (2014-05-09)
----------------
* If dead code is found, exit with 1.
0.4.1 (2013-09-17)
------------------
* Only warn if a path given on the command line cannot be found.
0.4 (2013-06-23)
----------------
* Ignore unused variables starting with an underscore.
* Show warning for syntax errors instead of aborting directly.
* Print warning if a file cannot be found.
0.3 (2012-03-19)
----------------
* Add support for python3
* Report unused attributes
* Find tuple assignments in comprehensions
* Scan files given on the command line even if they don't end with .py
0.2 (2012-03-18)
----------------
* Only format nodes in verbose mode (gives 4x speedup).
0.1 (2012-03-17)
----------------
* First release.
Keywords: dead-code-removal
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
vulture-1.3/vulture.egg-info/ 0000755 0001750 0001750 00000000000 13616004367 017367 5 ustar jendrik jendrik 0000000 0000000 vulture-1.3/vulture.egg-info/entry_points.txt 0000644 0001750 0001750 00000000057 13616004367 022667 0 ustar jendrik jendrik 0000000 0000000 [console_scripts]
vulture = vulture.core:main
vulture-1.3/vulture.egg-info/dependency_links.txt 0000644 0001750 0001750 00000000001 13616004367 023435 0 ustar jendrik jendrik 0000000 0000000
vulture-1.3/vulture.egg-info/SOURCES.txt 0000644 0001750 0001750 00000002146 13616004367 021256 0 ustar jendrik jendrik 0000000 0000000 CONTRIBUTING.rst
LICENSE.txt
MANIFEST.in
NEWS.rst
README.rst
TODO.rst
setup.cfg
setup.py
tox.ini
tests/__init__.py
tests/test_conditions.py
tests/test_confidence.py
tests/test_encoding.py
tests/test_errors.py
tests/test_format_strings.py
tests/test_ignore.py
tests/test_imports.py
tests/test_item.py
tests/test_make_whitelist.py
tests/test_report.py
tests/test_scavenging.py
tests/test_script.py
tests/test_size.py
tests/test_sorting.py
tests/test_unreachable.py
tests/test_utils.py
vulture/__init__.py
vulture/__main__.py
vulture/core.py
vulture/lines.py
vulture/utils.py
vulture.egg-info/PKG-INFO
vulture.egg-info/SOURCES.txt
vulture.egg-info/dependency_links.txt
vulture.egg-info/entry_points.txt
vulture.egg-info/top_level.txt
vulture/whitelists/argparse_whitelist.py
vulture/whitelists/ast_whitelist.py
vulture/whitelists/collections_whitelist.py
vulture/whitelists/ctypes_whitelist.py
vulture/whitelists/logging_whitelist.py
vulture/whitelists/string_whitelist.py
vulture/whitelists/sys_whitelist.py
vulture/whitelists/threading_whitelist.py
vulture/whitelists/unittest_whitelist.py
vulture/whitelists/whitelist_utils.py vulture-1.3/vulture.egg-info/top_level.txt 0000644 0001750 0001750 00000000010 13616004367 022110 0 ustar jendrik jendrik 0000000 0000000 vulture
vulture-1.3/vulture.egg-info/PKG-INFO 0000644 0001750 0001750 00000047407 13616004367 020500 0 ustar jendrik jendrik 0000000 0000000 Metadata-Version: 1.2
Name: vulture
Version: 1.3
Summary: Find dead code
Home-page: https://github.com/jendrikseipp/vulture
Author: Jendrik Seipp
Author-email: jendrikseipp@gmail.com
License: MIT
Description: Vulture - Find dead code
========================
.. image:: https://travis-ci.org/jendrikseipp/vulture.svg?branch=master
:target: https://travis-ci.org/jendrikseipp/vulture
:alt: Travis CI build status (Linux)
.. image:: https://ci.appveyor.com/api/projects/status/github/jendrikseipp/vulture?svg=true
:target: https://ci.appveyor.com/project/jendrikseipp96693/vulture
:alt: AppVeyor CI build status (Windows)
.. image:: https://coveralls.io/repos/github/jendrikseipp/vulture/badge.svg?branch=master
:target: https://coveralls.io/github/jendrikseipp/vulture?branch=master
Vulture finds unused code in Python programs. This is useful for
cleaning up and finding errors in large code bases. If you run Vulture
on both your library and test suite you can find untested code.
Due to Python's dynamic nature, static code analyzers like Vulture are
likely to miss some dead code. Also, code that is only called implicitly
may be reported as unused. Nonetheless, Vulture can be a very helpful
tool for higher code quality.
Features
--------
* fast: uses static code analysis
* tested: tests itself and has complete test coverage
* complements pyflakes and has the same output syntax
* sorts unused classes and functions by size with ``--sort-by-size``
* supports Python 2.7 and Python >= 3.5
Installation
------------
::
$ pip install vulture # from PyPI
$ pip install . # from cloned repo
Usage
-----
::
$ vulture myscript.py # or
$ python3 -m vulture myscript.py
$ vulture myscript.py mypackage/
$ vulture myscript.py --min-confidence 100 # Only report 100% dead code.
The provided arguments may be Python files or directories. For each
directory Vulture analyzes all contained `*.py` files.
Vulture assigns each chunk of dead code a confidence value. A confidence
value of 100% means that the code will never be executed. Values below
100% are only estimates for how likely it is that the code is unused.
After you have found and deleted dead code, run Vulture again, because
it may discover more dead code.
**Handling false positives**
You can add used code that is reported as unused to a Python module and
add it to the list of scanned paths. To obtain such a whitelist
automatically, pass ``--make-whitelist`` to Vulture. ::
$ vulture mydir --make-whitelist > whitelist.py
$ vulture mydir whitelist.py
We collect whitelists for common Python modules and packages in
``vulture/whitelists/`` (pull requests are welcome). If you want to
ignore a whole file or directory, use the ``--exclude`` parameter (e.g.,
``--exclude *settings.py,docs/``).
**Ignoring names**
You can use ``--ignore-names foo*,ba[rz]`` to let Vulture ignore all names
starting with ``foo`` and the names ``bar`` and ``baz``. Additionally, the
``--ignore-decorators`` option can be used to ignore functions decorated
with the given decorator. This is helpful for example in Flask projects,
where you can use ``--ignore-decorators "@app.route"`` to ignore all functions
with the ``@app.route`` decorator.
We recommend using whitelists instead of ``--ignore-names`` or
``--ignore-decorators`` whenever possible, since whitelists are automatically
checked for syntactic correctness when passed to Vulture and often you can
even pass them to your Python interpreter and let it check that all
whitelisted code actually still exists in your project.
**Marking unused variables**
There are situations where you can't just remove unused variables, e.g.,
in tuple assignments or function signatures. Vulture will ignore these
variables if they start with an underscore (e.g., ``_x, y = get_pos()``).
**Minimum confidence**
You can use the ``--min-confidence`` flag to set the minimum confidence
for code to be reported as unused. Use ``--min-confidence 100`` to only
report code that is guaranteed to be unused within the analyzed files.
How does it work?
-----------------
Vulture uses the ``ast`` module to build abstract syntax trees for all
given files. While traversing all syntax trees it records the names of
defined and used objects. Afterwards, it reports the objects which have
been defined, but not used. This analysis ignores scopes and only takes
object names into account.
Vulture also detects unreachable code by looking for code after
``return``, ``break``, ``continue`` and ``raise`` statements, and by
searching for unsatisfiable ``if``- and ``while``-conditions.
Sort by size
------------
When using the ``--sort-by-size`` option, Vulture sorts unused code by
its number of lines. This helps developers prioritize where to look for
dead code first.
Examples
--------
Consider the following Python script (``dead_code.py``):
.. code:: python
import os
class Greeter:
def greet(self):
print("Hi")
def hello_world():
message = "Hello, world!"
greeter = Greeter()
greet_func = getattr(greeter, "greet")
greet_func()
if __name__ == "__main__":
hello_world()
Calling ::
vulture dead_code.py
results in the following output::
dead_code.py:1: unused import 'os' (90% confidence)
dead_code.py:4: unused function 'greet' (60% confidence)
dead_code.py:8: unused variable 'message' (60% confidence)
Vulture correctly reports "os" and "message" as unused, but it fails to
detect that "greet" is actually used. The recommended method to deal with
false positives like this is to create a whitelist Python file.
**Preparing whitelists**
In a whitelist we simulate the usage of variables, attributes, etc. For
the program above, a whitelist could look as follows:
.. code:: python
# whitelist_dead_code.py
from dead_code import Greeter
Greeter.greet
Alternatively, you can pass ``--make-whitelist`` to Vulture and obtain
an automatically generated whitelist.
Passing both the original program and the whitelist to Vulture ::
vulture dead_code.py whitelist_dead_code.py
makes Vulture ignore the "greet" method::
dead_code.py:1: unused import 'os' (90% confidence)
dead_code.py:8: unused variable 'message' (60% confidence)
Exit codes
----------
+-----------+---------------------------------------------------------------+
| Exit code | Description |
+===========+===============================================================+
| 0 | No dead code found |
+-----------+---------------------------------------------------------------+
| 1 | Dead code found |
+-----------+---------------------------------------------------------------+
| 1 | Invalid input (file missing, syntax error, wrong encoding) |
+-----------+---------------------------------------------------------------+
| 2 | Invalid command line arguments |
+-----------+---------------------------------------------------------------+
Similar programs
----------------
* `pyflakes `_ finds unused imports and
unused local variables (in addition to many other programmatic errors).
* `coverage `_ finds unused
code more reliably than Vulture, but requires all branches of the code to
actually be run.
* `uncalled `_ finds dead code by using
the abstract syntax tree (like Vulture), regular expressions, or both.
* `dead `_ finds dead code by using
the abstract syntax tree (like Vulture).
Participate
-----------
Please visit https://github.com/jendrikseipp/vulture to report any
issues or to make pull requests.
* Contributing guide: `CONTRIBUTING.rst `_
* Changelog: `NEWS.rst `_
* Roadmap: `TODO.rst `_
News
====
1.3 (2020-02-03)
----------------
* Detect redundant 'if' conditions without 'else' blocks.
* Add whitelist for ``string.Formatter`` (Joseph Bylund, #183).
1.2 (2019-11-22)
----------------
* Fix tests for Python 3.8 (#166).
* Use new ``Constant`` AST node under Python 3.8+ (#175).
* Add test for f-strings (#177).
* Add whitelist for ``logging`` module.
1.1 (2019-09-23)
----------------
* Add ``sys.excepthook`` to ``sys`` whitelist.
* Add whitelist for ``ctypes`` module.
* Check that type annotations are parsed and type comments are ignored (thanks @kx-chen).
* Support checking files with BOM under Python 2.7 (#170).
1.0 (2018-10-23)
----------------
* Add ``--ignore-decorators`` flag (thanks @RJ722).
* Add whitelist for ``threading`` module (thanks @andrewhalle).
0.29 (2018-07-31)
-----------------
* Add ``--ignore-names`` flag for ignoring names matching the given glob patterns (thanks @RJ722).
0.28 (2018-07-05)
-----------------
* Add ``--make-whitelist`` flag for reporting output in whitelist format (thanks @RJ722).
* Ignore case of ``--exclude`` arguments on Windows.
* Add ``*-test.py`` to recognized test file patterns.
* Add ``failureException``, ``longMessage`` and ``maxDiff`` to ``unittest`` whitelist.
* Refer to actual objects rather than their mocks in default whitelists (thanks @RJ722).
* Don't import any Vulture modules in setup.py (thanks @RJ722).
0.27 (2018-06-05)
-----------------
* Report ``while (True): ... else: ...`` as unreachable (thanks @RJ722).
* Use ``argparse`` instead of ``optparse``.
* Whitelist Mock.return_value and Mock.side_effect in unittest.mock module.
* Drop support for Python 2.6 and 3.3.
* Improve documentation and test coverage (thanks @RJ722).
0.26 (2017-08-28)
-----------------
* Detect ``async`` function definitions (thanks @RJ722).
* Add ``Item.get_report()`` method (thanks @RJ722).
* Move method for finding Python modules out of Vulture class.
0.25 (2017-08-15)
-----------------
* Detect unsatisfiable statements containing ``and``, ``or`` and ``not``.
* Use filenames and line numbers as tie-breakers when sorting by size.
* Store first and last line numbers in Item objects.
* Pass relevant options directly to ``scavenge()`` and ``report()``.
0.24 (2017-08-14)
-----------------
* Detect unsatisfiable ``while``-conditions (thanks @RJ722).
* Detect unsatisfiable ``if``- and ``else``-conditions (thanks @RJ722).
* Handle null bytes in source code.
0.23 (2017-08-10)
-----------------
* Add ``--min-confidence`` flag (thanks @RJ722).
0.22 (2017-08-04)
-----------------
* Detect unreachable code after ``return``, ``break``, ``continue`` and
``raise`` (thanks @RJ722).
* Parse all variable and attribute names in new format strings.
* Extend ast whitelist.
0.21 (2017-07-26)
-----------------
* If an unused item is defined multiple times, report it multiple times.
* Make size estimates for function calls more accurate.
* Create wheel files for Vulture (thanks @RJ722).
0.20 (2017-07-26)
-----------------
* Report unused tuple assignments as dead code.
* Report attribute names that have the same names as variables as dead code.
* Let Item class inherit from ``object`` (thanks @RJ722).
* Handle names imported as aliases like all other used variable names.
* Rename Vulture.used_vars to Vulture.used_names.
* Use function for determining which imports to ignore.
* Only try to import each whitelist file once.
* Store used names and used attributes in sets instead of lists.
* Fix estimating the size of code containing ellipses (...).
* Refactor and simplify code.
0.19 (2017-07-20)
-----------------
* Don't ignore `__foo` variable names.
* Use separate methods for determining whether to ignore classes and functions.
* Only try to find a whitelist for each defined import once (thanks @roivanov).
* Fix finding the last child for many types of AST nodes.
0.18 (2017-07-17)
-----------------
* Make `--sort-by-size` faster and more accurate (thanks @RJ722).
0.17 (2017-07-17)
-----------------
* Add `get_unused_code()` method.
* Return with exit code 1 when syntax errors are found or files can't be read.
0.16 (2017-07-12)
-----------------
* Differentiate between unused classes and functions (thanks @RJ722).
* Add --sort-by-size option (thanks @jackric and @RJ722).
* Count imports as used if they are accessed as module attributes.
0.15 (2017-07-04)
-----------------
* Automatically include whitelists based on imported modules (thanks @RJ722).
* Add --version parameter (thanks @RJ722).
* Add appveyor tests for testing on Windows (thanks @RJ722).
0.14 (2017-04-06)
-----------------
* Add stub whitelist file for Python standard library (thanks @RJ722)
* Ignore class names starting with "Test" in "test\_" files (thanks @thisch).
* Ignore "test\_" functions only in "test\_" files.
0.13 (2017-03-06)
-----------------
* Ignore star-imported names since we cannot detect whether they are used.
* Move repository to GitHub.
0.12 (2017-01-05)
-----------------
* Detect unused imports.
* Use tokenize.open() on Python >= 3.2 for reading input files, assume
UTF-8 encoding on older Python versions.
0.11 (2016-11-27)
-----------------
* Use the system's default encoding when reading files.
* Report syntax errors instead of aborting.
0.10 (2016-07-14)
-----------------
* Detect unused function and method arguments (issue #15).
* Detect unused \*args and \*\*kwargs parameters.
* Change license from GPL to MIT.
0.9 (2016-06-29)
----------------
* Don't flag attributes as unused if they are used as global variables
in another module (thanks Florian Bruhin).
* Don't consider "True" and "False" variable names.
* Abort with error message when invoked on .pyc files.
0.8.1 (2015-09-28)
------------------
* Fix code for Python 3.
0.8 (2015-09-28)
----------------
* Do not flag names imported with "import as" as dead code (thanks Tom Terrace).
0.7 (2015-09-26)
----------------
* Exit with exitcode 1 if path on commandline can't be found.
* Test vulture with vulture using a whitelist module for false positives.
* Add tests that run vulture as a script.
* Add "python setup.py test" command for running tests.
* Add support for tox.
* Raise test coverage to 100%.
* Remove ez_setup.py.
0.6 (2014-09-06)
----------------
* Ignore function names starting with "test\_".
* Parse variable names in new format strings (e.g. "This is {x}".format(x="nice")).
* Only parse alphanumeric variable names in format strings and ignore types.
* Abort with exit code 1 on syntax errors.
* Support installation under Windows by using setuptools (thanks Reuben Fletcher-Costin).
0.5 (2014-05-09)
----------------
* If dead code is found, exit with 1.
0.4.1 (2013-09-17)
------------------
* Only warn if a path given on the command line cannot be found.
0.4 (2013-06-23)
----------------
* Ignore unused variables starting with an underscore.
* Show warning for syntax errors instead of aborting directly.
* Print warning if a file cannot be found.
0.3 (2012-03-19)
----------------
* Add support for python3
* Report unused attributes
* Find tuple assignments in comprehensions
* Scan files given on the command line even if they don't end with .py
0.2 (2012-03-18)
----------------
* Only format nodes in verbose mode (gives 4x speedup).
0.1 (2012-03-17)
----------------
* First release.
Keywords: dead-code-removal
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
vulture-1.3/setup.cfg 0000644 0001750 0001750 00000000305 13616004367 016006 0 ustar jendrik jendrik 0000000 0000000 [coverage:run]
omit =
setup.py
.tox/*
[tool:pytest]
addopts = --cov vulture --cov-report=html --cov-report=term --cov-append
[bdist_wheel]
universal = 1
[egg_info]
tag_build =
tag_date = 0
vulture-1.3/MANIFEST.in 0000644 0001750 0001750 00000000137 13133600745 015722 0 ustar jendrik jendrik 0000000 0000000 include *.rst
include *.txt
include tests/*.py
include tox.ini
include vulture/whitelists/*.py
vulture-1.3/CONTRIBUTING.rst 0000644 0001750 0001750 00000005607 13221037563 016634 0 ustar jendrik jendrik 0000000 0000000 Contributing to Vulture
=======================
Creating and cloning a fork
---------------------------
Fork the repository on GitHub and do the following:
::
$ git clone https://github.com/jendrikseipp/vulture.git
$ cd vulture
$ git remote add origin https://github.com/USERNAME/vulture # Use your GitHub username.
Installation
------------
We recommend using ``virtualenv`` to isolate the installation of vulture.
Setting up virtualenv
~~~~~~~~~~~~~~~~~~~~~
You can read more about ``virtualenv``
in the `virtualenv documentation `_.
To install the ``virtualenv`` package using ``pip``, run:
::
$ pip install virtualenv
Once you have ``virtualenv`` installed, create your own environment (named
``vulture_dev``):
::
$ virtualenv vulture_dev
Now, whenever you work on the project, activate the corresponding environment.
- On **Unix-based** systems, this can be done with:
::
$ source vulture_dev/bin/activate
- And on **Windows** this is done with:
::
$ vulture_dev\scripts\activate
For deactivation, use:
::
(vulture_dev)$ deactivate
Installing vulture
~~~~~~~~~~~~~~~~~~
Navigate to your cloned ``vulture`` directory, and run the following to install
in development mode:
::
$ pip install -e .
Installing tox
~~~~~~~~~~~~~~
Vulture uses tox for testing. You can read more about
it in the `tox documentation `_.
To install ``tox``, run:
::
$ pip install tox
Coding standards
----------------
Creating a new branch
~~~~~~~~~~~~~~~~~~~~~
To start working on a pull request, create a new branch to work on. You
should never develop on your master branch because your master branch should
always be synchronized with the main repo’s master branch, which is challenging
if it has new commits. Create a branch using:
::
$ git checkout -b your-new-branch
Naming branches
+++++++++++++++
Branch names should describe the feature/issue that you want to work on,
but at the same time be short.
Commits
~~~~~~~
Each commit should be atomic and its message should adequately describe the
change in a clear manner. Use imperative, e.g., "Fix issue12." instead of
"Fixed issue12.".
Testing
-------
Run ``tox`` using:
::
$ tox
Pull requests
-------------
How to send a pull request?
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Visit your fork on GitHub, change the branch to the one you commited to, and
click the ``New Pull Request`` button.
Follow-up
~~~~~~~~~
In case your PR needs to be updated (tests fail or reviewer requests some
changes), update it by either committing atop your branch or amending
your previous commit (using ``git commit --amend``, and then ``git push -f`` to
force push your changes).
Feedback
~~~~~~~~
Take reviewer feedback positively, it’s unlikely for a PR to be merged on
first attempt -- but don’t worry that’s just how it works. It helps keep
the code clean.
vulture-1.3/vulture/ 0000755 0001750 0001750 00000000000 13616004367 015675 5 ustar jendrik jendrik 0000000 0000000 vulture-1.3/vulture/__init__.py 0000644 0001750 0001750 00000000121 13315657163 020004 0 ustar jendrik jendrik 0000000 0000000 from vulture.core import __version__, Vulture
assert __version__
assert Vulture
vulture-1.3/vulture/utils.py 0000644 0001750 0001750 00000010031 13570521477 017407 0 ustar jendrik jendrik 0000000 0000000 import ast
import codecs
import os
import re
import sys
import tokenize
# Encoding to use when converting input files to unicode. Python 2 trips
# over the BOM, so we use "utf-8-sig" which drops the BOM.
ENCODING = 'utf-8-sig'
# The ast module in Python 2 trips over "coding" cookies, so strip them.
ENCODING_REGEX = re.compile(
r"^[ \t\v]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+).*?$", flags=re.M)
class VultureInputException(Exception):
pass
def _safe_eval(node, default):
"""
Safely evaluate the Boolean expression under the given AST node.
Substitute `default` for all sub-expressions that cannot be
evaluated (because variables or functions are undefined).
We could use eval() to evaluate more sub-expressions. However, this
function is not safe for arbitrary Python code. Even after
overwriting the "__builtins__" dictionary, the original dictionary
can be restored
(https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html).
"""
if isinstance(node, ast.BoolOp):
results = [_safe_eval(value, default) for value in node.values]
if isinstance(node.op, ast.And):
return all(results)
else:
return any(results)
elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not):
return not _safe_eval(node.operand, not default)
else:
try:
return ast.literal_eval(node)
except ValueError:
return default
def condition_is_always_false(condition):
return not _safe_eval(condition, True)
def condition_is_always_true(condition):
return _safe_eval(condition, False)
def format_path(path):
if not path:
return path
relpath = os.path.relpath(path)
return relpath if not relpath.startswith('..') else path
def get_decorator_name(decorator):
if isinstance(decorator, ast.Call):
decorator = decorator.func
parts = []
while isinstance(decorator, ast.Attribute):
parts.append(decorator.attr)
decorator = decorator.value
parts.append(decorator.id)
return '@' + '.'.join(reversed(parts))
def get_modules(paths, toplevel=True):
"""Take files from the command line even if they don't end with .py."""
modules = []
for path in paths:
path = os.path.abspath(path)
if toplevel and path.endswith('.pyc'):
sys.exit('.pyc files are not supported: {}'.format(path))
if os.path.isfile(path) and (path.endswith('.py') or toplevel):
modules.append(path)
elif os.path.isdir(path):
subpaths = [
os.path.join(path, filename)
for filename in sorted(os.listdir(path))]
modules.extend(get_modules(subpaths, toplevel=False))
elif toplevel:
sys.exit('Error: {} could not be found.'.format(path))
return modules
def read_file(filename):
# Python >= 3.2
try:
# Use encoding detected by tokenize.detect_encoding().
with tokenize.open(filename) as f:
return f.read()
except (SyntaxError, UnicodeDecodeError) as err:
raise VultureInputException(err)
except AttributeError:
# tokenize.open was added in Python 3.2.
pass
# Python < 3.2
try:
with codecs.open(filename, encoding=ENCODING) as f:
return f.read()
except UnicodeDecodeError as err:
raise VultureInputException(err)
def sanitize_code(code):
return ENCODING_REGEX.sub("", code, count=1)
class LoggingList(list):
def __init__(self, typ, verbose):
self.typ = typ
self._verbose = verbose
return list.__init__(self)
def append(self, item):
if self._verbose:
print('define {} "{}"'.format(self.typ, item.name))
list.append(self, item)
class LoggingSet(set):
def __init__(self, typ, verbose):
self.typ = typ
self._verbose = verbose
return set.__init__(self)
def add(self, name):
if self._verbose:
print('use {} "{}"'.format(self.typ, name))
set.add(self, name)
vulture-1.3/vulture/lines.py 0000644 0001750 0001750 00000004564 13533226440 017366 0 ustar jendrik jendrik 0000000 0000000 import ast
def _get_last_child_with_lineno(node):
"""
Return the last direct child of `node` that has a lineno attribute,
or None if `node` has no such children.
Almost all node._field lists are sorted by the order in which they
appear in source code. For some nodes however, we have to skip some
fields that either don't have line numbers (e.g., "ctx" and "names")
or that are in the wrong position (e.g., "decorator_list" and
"returns"). Then we choose the first field (i.e., the field with the
highest line number) that actually contains a node. If it contains a
list of nodes, we return the last one.
"""
ignored_fields = {'ctx', 'decorator_list', 'names', 'returns'}
fields = node._fields
# The fields of ast.Call are in the wrong order.
if isinstance(node, ast.Call):
fields = ('func', 'args', 'starargs', 'keywords', 'kwargs')
for name in reversed(fields):
if name in ignored_fields:
continue
try:
last_field = getattr(node, name)
except AttributeError:
continue
# Ignore non-AST objects like "is_async", "level" and "nl".
if isinstance(last_field, ast.AST):
return last_field
elif isinstance(last_field, list) and last_field:
return last_field[-1]
return None
def get_last_line_number(node):
"""Estimate last line number of the given AST node.
The estimate is based on the line number of the last descendant of
`node` that has a lineno attribute. Therefore, it underestimates the
size of code ending with, e.g., multiline strings and comments.
When traversing the tree, we may see a mix of nodes with line
numbers and nodes without line numbers. We therefore, store the
maximum line number seen so far and report it at the end. A more
accurate (but also slower to compute) estimate would traverse all
children, instead of just the last one, since choosing the last one
may lead to a path that ends with a node without line number.
"""
max_lineno = node.lineno
while True:
last_child = _get_last_child_with_lineno(node)
if last_child is None:
return max_lineno
else:
try:
max_lineno = max(max_lineno, last_child.lineno)
except AttributeError:
pass
node = last_child
vulture-1.3/vulture/core.py 0000644 0001750 0001750 00000052330 13616004366 017201 0 ustar jendrik jendrik 0000000 0000000 # -*- coding: utf-8 -*-
#
# vulture - Find dead code.
#
# Copyright (c) 2012-2019 Jendrik Seipp (jendrikseipp@gmail.com)
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function
import argparse
import ast
from fnmatch import fnmatch, fnmatchcase
import os.path
import pkgutil
import re
import string
import sys
from vulture import lines
from vulture import utils
__version__ = '1.3'
DEFAULT_CONFIDENCE = 60
IGNORED_VARIABLE_NAMES = {'object', 'self'}
# True and False are NameConstants since Python 3.4.
if sys.version_info < (3, 4):
IGNORED_VARIABLE_NAMES |= {'True', 'False'}
def _get_unused_items(defined_items, used_names):
unused_items = [item for item in set(defined_items)
if item.name not in used_names]
unused_items.sort(key=lambda item: item.name.lower())
return unused_items
def _is_special_name(name):
return name.startswith('__') and name.endswith('__')
def _match(name, patterns):
return any(fnmatchcase(name, pattern) for pattern in patterns)
def _is_test_file(filename):
return any(
fnmatch(os.path.basename(filename), pattern)
for pattern in ['test*.py', '*_test.py', '*-test.py'])
def _ignore_class(filename, class_name):
return _is_test_file(filename) and 'Test' in class_name
def _ignore_import(_filename, import_name):
# Ignore star-imported names, since we can't detect whether they are used.
return import_name == '*'
def _ignore_function(filename, function_name):
return (
_is_special_name(function_name) or
(function_name.startswith('test_') and _is_test_file(filename)))
def _ignore_variable(filename, varname):
"""
Ignore _ (Python idiom), _x (pylint convention) and
__x__ (special variable or method), but not __x.
"""
return (
varname in IGNORED_VARIABLE_NAMES or
(varname.startswith('_') and not varname.startswith('__')) or
_is_special_name(varname))
class Item(object):
"""
Hold the name, type and location of defined code.
"""
__slots__ = ('name', 'typ', 'filename', 'first_lineno',
'last_lineno', 'message', 'confidence')
def __init__(self, name, typ, filename, first_lineno, last_lineno,
message='',
confidence=DEFAULT_CONFIDENCE):
self.name = name
self.typ = typ
self.filename = filename
self.first_lineno = first_lineno
self.last_lineno = last_lineno
self.message = message or "unused {typ} '{name}'".format(**locals())
self.confidence = confidence
@property
def size(self):
assert self.last_lineno >= self.first_lineno
return self.last_lineno - self.first_lineno + 1
def get_report(self, add_size=False):
if add_size:
line_format = 'line' if self.size == 1 else 'lines'
size_report = ', {:d} {}'.format(self.size, line_format)
else:
size_report = ''
return "{}:{:d}: {} ({}% confidence{})".format(
utils.format_path(self.filename), self.first_lineno,
self.message, self.confidence, size_report)
def get_whitelist_string(self):
filename = utils.format_path(self.filename)
if self.typ == 'unreachable_code':
return ('# {} ({}:{})'.format(
self.message, filename, self.first_lineno))
else:
prefix = '_.' if self.typ in ['attribute', 'property'] else ''
return "{}{} # unused {} ({}:{:d})".format(
prefix, self.name, self.typ, filename, self.first_lineno)
def _tuple(self):
return (self.filename, self.first_lineno, self.name)
def __repr__(self):
return repr(self.name)
def __eq__(self, other):
return self._tuple() == other._tuple()
def __hash__(self):
return hash(self._tuple())
class Vulture(ast.NodeVisitor):
"""Find dead code."""
def __init__(self, verbose=False, ignore_names=None,
ignore_decorators=None):
self.verbose = verbose
def get_list(typ):
return utils.LoggingList(typ, self.verbose)
def get_set(typ):
return utils.LoggingSet(typ, self.verbose)
self.defined_attrs = get_list('attribute')
self.defined_classes = get_list('class')
self.defined_funcs = get_list('function')
self.defined_imports = get_list('import')
self.defined_props = get_list('property')
self.defined_vars = get_list('variable')
self.unreachable_code = get_list('unreachable_code')
self.used_attrs = get_set('attribute')
self.used_names = get_set('name')
self.ignore_names = ignore_names or []
self.ignore_decorators = ignore_decorators or []
self.filename = ''
self.code = []
self.found_dead_code_or_error = False
def scan(self, code, filename=''):
code = utils.sanitize_code(code)
self.code = code.splitlines()
self.filename = filename
try:
node = ast.parse(code, filename=self.filename)
except SyntaxError as err:
text = ' at "{}"'.format(err.text.strip()) if err.text else ''
print('{}:{:d}: {}{}'.format(
utils.format_path(filename), err.lineno, err.msg, text),
file=sys.stderr)
self.found_dead_code_or_error = True
except (TypeError, ValueError) as err:
# Python < 3.5 raises TypeError and Python >= 3.5 raises
# ValueError if source contains null bytes.
print('{}: invalid source code "{}"'.format(
utils.format_path(filename), err), file=sys.stderr)
self.found_dead_code_or_error = True
else:
self.visit(node)
def scavenge(self, paths, exclude=None):
def prepare_pattern(pattern):
if not any(char in pattern for char in ['*', '?', '[']):
pattern = '*{pattern}*'.format(**locals())
return pattern
exclude = [prepare_pattern(pattern) for pattern in (exclude or [])]
def exclude_file(name):
return any(fnmatch(name, pattern) for pattern in exclude)
for module in utils.get_modules(paths):
if exclude_file(module):
self._log('Excluded:', module)
continue
self._log('Scanning:', module)
try:
module_string = utils.read_file(module)
except utils.VultureInputException as err: # noqa: F841
print(
'Error: Could not read file {module} - {err}\n'
'Try to change the encoding to UTF-8.'.format(**locals()),
file=sys.stderr)
self.found_dead_code_or_error = True
else:
self.scan(module_string, filename=module)
unique_imports = {item.name for item in self.defined_imports}
for import_name in unique_imports:
path = os.path.join('whitelists', import_name) + '_whitelist.py'
if exclude_file(path):
self._log('Excluded whitelist:', path)
else:
try:
module_data = pkgutil.get_data('vulture', path)
self._log('Included whitelist:', path)
except IOError:
# Most imported modules don't have a whitelist.
continue
module_string = module_data.decode("utf-8")
self.scan(module_string, filename=path)
def get_unused_code(self, min_confidence=0, sort_by_size=False):
"""
Return ordered list of unused Item objects.
"""
if not 0 <= min_confidence <= 100:
raise ValueError('min_confidence must be between 0 and 100.')
def by_name(item):
return (item.filename.lower(), item.first_lineno)
def by_size(item):
return (item.size,) + by_name(item)
unused_code = (self.unused_attrs + self.unused_classes +
self.unused_funcs + self.unused_imports +
self.unused_props + self.unused_vars +
self.unreachable_code)
confidently_unused = [obj for obj in unused_code
if obj.confidence >= min_confidence]
return sorted(confidently_unused,
key=by_size if sort_by_size else by_name)
def report(self, min_confidence=0, sort_by_size=False,
make_whitelist=False):
"""
Print ordered list of Item objects to stdout.
"""
for item in self.get_unused_code(
min_confidence=min_confidence, sort_by_size=sort_by_size):
print(item.get_whitelist_string() if make_whitelist
else item.get_report(add_size=sort_by_size))
self.found_dead_code_or_error = True
return self.found_dead_code_or_error
@property
def unused_classes(self):
return _get_unused_items(
self.defined_classes,
self.used_attrs | self.used_names)
@property
def unused_funcs(self):
return _get_unused_items(
self.defined_funcs,
self.used_attrs | self.used_names)
@property
def unused_imports(self):
return _get_unused_items(
self.defined_imports,
self.used_names | self.used_attrs)
@property
def unused_props(self):
return _get_unused_items(self.defined_props, self.used_attrs)
@property
def unused_vars(self):
return _get_unused_items(
self.defined_vars,
self.used_attrs | self.used_names)
@property
def unused_attrs(self):
return _get_unused_items(self.defined_attrs, self.used_attrs)
def _log(self, *args):
if self.verbose:
print(*args)
def _add_aliases(self, node):
"""
We delegate to this method instead of using visit_alias() to have
access to line numbers and to filter imports from __future__.
"""
assert isinstance(node, (ast.Import, ast.ImportFrom))
for name_and_alias in node.names:
# Store only top-level module name ("os.path" -> "os").
# We can't easily detect when "os.path" is used.
name = name_and_alias.name.partition('.')[0]
alias = name_and_alias.asname
self._define(
self.defined_imports, alias or name, node,
confidence=90, ignore=_ignore_import)
if alias is not None:
self.used_names.add(name_and_alias.name)
def _handle_conditional_node(self, node, name):
if utils.condition_is_always_false(node.test):
self._define(
self.unreachable_code, name, node,
last_node=node.body[-1],
message="unsatisfiable '{name}' condition".format(**locals()),
confidence=100)
elif utils.condition_is_always_true(node.test):
else_body = node.orelse
if else_body:
self._define(
self.unreachable_code, 'else', else_body[0],
last_node=else_body[-1],
message="unreachable 'else' block",
confidence=100)
elif name == 'if':
# Redundant if-condition without else block.
self._define(
self.unreachable_code, name, node,
message="redundant if-condition".format(**locals()),
confidence=100)
def _handle_string(self, s):
"""
Parse variable names in format strings:
'%(my_var)s' % locals()
'{my_var}'.format(**locals())
"""
# Old format strings.
self.used_names |= set(re.findall(r'\%\((\w+)\)', s))
def is_identifier(name):
return bool(re.match(r'[a-zA-Z_][a-zA-Z0-9_]*', name))
# New format strings.
parser = string.Formatter()
try:
names = [name for _, name, _, _ in parser.parse(s) if name]
except ValueError:
# Invalid format string.
names = []
for field_name in names:
# Remove brackets and contents: "a[0][b].c[d].e" -> "a.c.e".
# "a.b.c" -> name = "a", attributes = ["b", "c"]
name_and_attrs = re.sub(r'\[\w*\]', '', field_name).split('.')
name = name_and_attrs[0]
if is_identifier(name):
self.used_names.add(name)
for attr in name_and_attrs[1:]:
if is_identifier(attr):
self.used_attrs.add(attr)
def _define(self, collection, name, first_node, last_node=None,
message='', confidence=DEFAULT_CONFIDENCE, ignore=None):
last_node = last_node or first_node
typ = collection.typ
if (ignore and ignore(self.filename, name)) or _match(
name, self.ignore_names):
self._log('Ignoring {typ} "{name}"'.format(**locals()))
else:
first_lineno = first_node.lineno
last_lineno = lines.get_last_line_number(last_node)
collection.append(
Item(name, typ, self.filename, first_lineno, last_lineno,
message=message, confidence=confidence))
def _define_variable(self, name, node, confidence=DEFAULT_CONFIDENCE):
self._define(self.defined_vars, name, node, confidence=confidence,
ignore=_ignore_variable)
def visit_arg(self, node):
"""Function argument.
ast.arg was added in Python 3.0.
ast.arg.lineno was added in Python 3.4.
"""
self._define_variable(node.arg, node, confidence=100)
def visit_AsyncFunctionDef(self, node):
return self.visit_FunctionDef(node)
def visit_Attribute(self, node):
if isinstance(node.ctx, ast.Store):
self._define(self.defined_attrs, node.attr, node)
elif isinstance(node.ctx, ast.Load):
self.used_attrs.add(node.attr)
def visit_ClassDef(self, node):
for decorator in node.decorator_list:
if _match(utils.get_decorator_name(decorator),
self.ignore_decorators):
self._log('Ignoring class "{}" (decorator whitelisted)'.format(
node.name))
break
else:
self._define(
self.defined_classes, node.name, node, ignore=_ignore_class)
def visit_FunctionDef(self, node):
decorator_names = [utils.get_decorator_name(
decorator) for decorator in node.decorator_list]
typ = 'property' if '@property' in decorator_names else 'function'
if any(_match(name, self.ignore_decorators)
for name in decorator_names):
self._log('Ignoring {} "{}" (decorator whitelisted)'.format(
typ, node.name))
elif typ == 'property':
self._define(self.defined_props, node.name, node)
else:
# Function is not a property.
self._define(
self.defined_funcs, node.name, node,
ignore=_ignore_function)
# Detect *args and **kwargs parameters. Python 3 recognizes them
# in visit_Name. For Python 2 we use this workaround. We can't
# use visit_arguments, because its node has no lineno.
for param in [node.args.vararg, node.args.kwarg]:
if param and isinstance(param, str):
self._define_variable(param, node, confidence=100)
def visit_If(self, node):
self._handle_conditional_node(node, 'if')
def visit_Import(self, node):
self._add_aliases(node)
def visit_ImportFrom(self, node):
if node.module != '__future__':
self._add_aliases(node)
def visit_Name(self, node):
if (isinstance(node.ctx, ast.Load) and
node.id not in IGNORED_VARIABLE_NAMES):
self.used_names.add(node.id)
elif isinstance(node.ctx, (ast.Param, ast.Store)):
self._define_variable(node.id, node)
if sys.version_info < (3, 8):
def visit_Str(self, node):
self._handle_string(node.s)
else:
def visit_Constant(self, node):
if isinstance(node.value, str):
self._handle_string(node.value)
def visit_While(self, node):
self._handle_conditional_node(node, 'while')
def visit(self, node):
method = 'visit_' + node.__class__.__name__
visitor = getattr(self, method, None)
if self.verbose:
lineno = getattr(node, 'lineno', 1)
line = self.code[lineno - 1] if self.code else ''
self._log(lineno, ast.dump(node), line)
if visitor:
visitor(node)
return self.generic_visit(node)
def _handle_ast_list(self, ast_list):
"""
Find unreachable nodes in the given sequence of ast nodes.
"""
for index, node in enumerate(ast_list):
if isinstance(node, (ast.Break, ast.Continue, ast.Raise,
ast.Return)):
try:
first_unreachable_node = ast_list[index + 1]
except IndexError:
continue
class_name = node.__class__.__name__.lower()
self._define(
self.unreachable_code,
class_name,
first_unreachable_node,
last_node=ast_list[-1],
message="unreachable code after '{class_name}'".format(
**locals()),
confidence=100)
return
def generic_visit(self, node):
"""Called if no explicit visitor function exists for a node."""
for _, value in ast.iter_fields(node):
if isinstance(value, list):
self._handle_ast_list(value)
for item in value:
if isinstance(item, ast.AST):
self.visit(item)
elif isinstance(value, ast.AST):
self.visit(value)
def _parse_args():
def csv(exclude):
return exclude.split(',')
usage = "%(prog)s [options] PATH [PATH ...]"
version = "vulture {}".format(__version__)
glob_help = 'Patterns may contain glob wildcards (*, ?, [abc], [!abc]).'
parser = argparse.ArgumentParser(prog='vulture', usage=usage)
parser.add_argument(
'paths', nargs='+', metavar='PATH',
help='Paths may be Python files or directories. For each directory'
' Vulture analyzes all contained *.py files.')
parser.add_argument(
'--exclude', metavar='PATTERNS', type=csv,
help='Comma-separated list of paths to ignore (e.g.,'
' "*settings.py,docs/*.py"). {glob_help} A PATTERN without glob'
' wildcards is treated as *PATTERN*.'.format(**locals()))
parser.add_argument(
'--ignore-decorators', metavar='PATTERNS', type=csv,
help='Comma-separated list of decorators. Functions and classes using'
' these decorators are ignored (e.g., "@app.route,@require_*").'
' {glob_help}'.format(**locals()))
parser.add_argument(
'--ignore-names', metavar='PATTERNS', type=csv, default=None,
help='Comma-separated list of names to ignore (e.g., "visit_*,do_*").'
' {glob_help}'.format(**locals()))
parser.add_argument(
'--make-whitelist', action='store_true',
help='Report unused code in a format that can be added to a'
' whitelist module.')
parser.add_argument(
'--min-confidence', type=int, default=0,
help='Minimum confidence (between 0 and 100) for code to be'
' reported as unused.')
parser.add_argument(
"--sort-by-size", action="store_true",
help='Sort unused functions and classes by their lines of code.')
parser.add_argument('-v', '--verbose', action='store_true')
parser.add_argument('--version', action='version', version=version)
return parser.parse_args()
def main():
args = _parse_args()
vulture = Vulture(verbose=args.verbose, ignore_names=args.ignore_names,
ignore_decorators=args.ignore_decorators)
vulture.scavenge(args.paths, exclude=args.exclude)
sys.exit(vulture.report(
min_confidence=args.min_confidence,
sort_by_size=args.sort_by_size,
make_whitelist=args.make_whitelist))
vulture-1.3/vulture/__main__.py 0000644 0001750 0001750 00000000046 13315657163 017773 0 ustar jendrik jendrik 0000000 0000000 from vulture.core import main
main()
vulture-1.3/vulture/whitelists/ 0000755 0001750 0001750 00000000000 13616004367 020074 5 ustar jendrik jendrik 0000000 0000000 vulture-1.3/vulture/whitelists/ctypes_whitelist.py 0000644 0001750 0001750 00000000205 13557375144 024056 0 ustar jendrik jendrik 0000000 0000000 from ctypes import _CFuncPtr
from ctypes import _Pointer
_CFuncPtr.argtypes
_CFuncPtr.errcheck
_CFuncPtr.restype
_Pointer.contents
vulture-1.3/vulture/whitelists/whitelist_utils.py 0000644 0001750 0001750 00000001551 13133600745 023700 0 ustar jendrik jendrik 0000000 0000000 """
Vulture sometimes reports used code as unused. To avoid these
false-positives, you can write a Python file that explicitly uses the
code and pass it to vulture:
vulture myscript.py mydir mywhitelist.py
When creating a whitelist file, you have to make sure not to write code
that hides unused code in other files. E.g., this is why we don't import
and access the "sys" module below. If we did import it, vulture would
not be able to detect whether other files import "sys" without using it.
This file explicitly uses code from the Python standard library that is
often incorrectly detected as unused.
"""
class Whitelist:
"""
Helper class that allows mocking Python objects.
Use it to create whitelist files that are not only syntactically
correct, but can also be executed.
"""
def __getattr__(self, _):
pass
assert Whitelist
vulture-1.3/vulture/whitelists/collections_whitelist.py 0000664 0001750 0001750 00000000204 13562326476 025066 0 ustar jendrik jendrik 0000000 0000000 import collections
# To free memory, the "default_factory" attribute can be set to None.
collections.defaultdict().default_factory
vulture-1.3/vulture/whitelists/string_whitelist.py 0000644 0001750 0001750 00000000355 13612116452 024046 0 ustar jendrik jendrik 0000000 0000000 import string
string.Formatter.check_unused_args
string.Formatter.convert_field
string.Formatter.format
string.Formatter.format_field
string.Formatter.get_field
string.Formatter.get_value
string.Formatter.parse
string.Formatter.vformat
vulture-1.3/vulture/whitelists/logging_whitelist.py 0000664 0001750 0001750 00000000101 13566032252 024160 0 ustar jendrik jendrik 0000000 0000000 import logging
logging.Filter.filter
logging.StreamHandler.emit
vulture-1.3/vulture/whitelists/ast_whitelist.py 0000644 0001750 0001750 00000004761 13557102776 023350 0 ustar jendrik jendrik 0000000 0000000 from whitelist_utils import Whitelist
# NodeVisitor methods are called implicitly.
whitelist_node_visitor = Whitelist()
whitelist_node_visitor.visit_Assert
whitelist_node_visitor.visit_Assign
whitelist_node_visitor.visit_AsyncFor
whitelist_node_visitor.visit_AsyncFunctionDef
whitelist_node_visitor.visit_AsyncWith
whitelist_node_visitor.visit_Attribute
whitelist_node_visitor.visit_AugAssign
whitelist_node_visitor.visit_Await
whitelist_node_visitor.visit_BinOp
whitelist_node_visitor.visit_BoolOp
whitelist_node_visitor.visit_Bytes
whitelist_node_visitor.visit_Call
whitelist_node_visitor.visit_ClassDef
whitelist_node_visitor.visit_Compare
whitelist_node_visitor.visit_Constant
whitelist_node_visitor.visit_Delete
whitelist_node_visitor.visit_Dict
whitelist_node_visitor.visit_DictComp
whitelist_node_visitor.visit_ExceptHandler
whitelist_node_visitor.visit_Exec
whitelist_node_visitor.visit_Expr
whitelist_node_visitor.visit_Expression
whitelist_node_visitor.visit_ExtSlice
whitelist_node_visitor.visit_For
whitelist_node_visitor.visit_FunctionDef
whitelist_node_visitor.visit_GeneratorExp
whitelist_node_visitor.visit_Global
whitelist_node_visitor.visit_If
whitelist_node_visitor.visit_IfExp
whitelist_node_visitor.visit_Import
whitelist_node_visitor.visit_ImportFrom
whitelist_node_visitor.visit_Index
whitelist_node_visitor.visit_Interactive
whitelist_node_visitor.visit_Lambda
whitelist_node_visitor.visit_List
whitelist_node_visitor.visit_ListComp
whitelist_node_visitor.visit_Module
whitelist_node_visitor.visit_Name
whitelist_node_visitor.visit_NameConstant
whitelist_node_visitor.visit_Nonlocal
whitelist_node_visitor.visit_Num
whitelist_node_visitor.visit_Print
whitelist_node_visitor.visit_Raise
whitelist_node_visitor.visit_Repr
whitelist_node_visitor.visit_Return
whitelist_node_visitor.visit_Set
whitelist_node_visitor.visit_SetComp
whitelist_node_visitor.visit_Slice
whitelist_node_visitor.visit_Starred
whitelist_node_visitor.visit_Str
whitelist_node_visitor.visit_Subscript
whitelist_node_visitor.visit_Suite
whitelist_node_visitor.visit_Try
whitelist_node_visitor.visit_TryExcept
whitelist_node_visitor.visit_TryFinally
whitelist_node_visitor.visit_Tuple
whitelist_node_visitor.visit_UnaryOp
whitelist_node_visitor.visit_While
whitelist_node_visitor.visit_With
whitelist_node_visitor.visit_Yield
whitelist_node_visitor.visit_YieldFrom
whitelist_node_visitor.visit_alias
whitelist_node_visitor.visit_arg
whitelist_node_visitor.visit_arguments
whitelist_node_visitor.visit_comprehension
whitelist_node_visitor.visit_keyword
vulture-1.3/vulture/whitelists/sys_whitelist.py 0000644 0001750 0001750 00000000151 13365576104 023361 0 ustar jendrik jendrik 0000000 0000000 import sys
sys.excepthook
# Never report redirected streams as unused.
sys.stderr
sys.stdin
sys.stdout
vulture-1.3/vulture/whitelists/unittest_whitelist.py 0000644 0001750 0001750 00000000730 13557375255 024434 0 ustar jendrik jendrik 0000000 0000000 from unittest import TestCase
TestCase.setUp
TestCase.tearDown
TestCase.setUpClass
TestCase.tearDownClass
TestCase.run
TestCase.skipTest
TestCase.debug
TestCase.failureException
TestCase.longMessage
TestCase.maxDiff
try:
# new in Python 3.4
TestCase.subTest
except AttributeError:
pass
try:
# unittest.mock was introduced in Python 3.3
from unittest import mock
except ImportError:
pass
else:
mock.Mock.return_value
mock.Mock.side_effect
vulture-1.3/vulture/whitelists/threading_whitelist.py 0000644 0001750 0001750 00000000125 13557375213 024512 0 ustar jendrik jendrik 0000000 0000000 import threading
threading.Thread.daemon
threading.Thread.name
threading.Thread.run
vulture-1.3/vulture/whitelists/argparse_whitelist.py 0000664 0001750 0001750 00000000255 13562326476 024362 0 ustar jendrik jendrik 0000000 0000000 import argparse
argparse.ArgumentParser().epilog
argparse.ArgumentDefaultsHelpFormatter('prog')._fill_text
argparse.ArgumentDefaultsHelpFormatter('prog')._get_help_string