vulture-1.3/0000755000175000017500000000000013616004367014167 5ustar jendrikjendrik00000000000000vulture-1.3/tox.ini0000644000175000017500000000107413612115215015473 0ustar jendrikjendrik00000000000000[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.py0000644000175000017500000000452713544320706015707 0ustar jendrikjendrik00000000000000#! /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.rst0000644000175000017500000001627313615776511015514 0ustar jendrikjendrik00000000000000News ==== 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.rst0000644000175000017500000000273013615777105015476 0ustar jendrikjendrik00000000000000TODOs ===== * 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.rst0000644000175000017500000001750513556024433015665 0ustar jendrikjendrik00000000000000Vulture - 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.txt0000644000175000017500000000212313057221300015773 0ustar jendrikjendrik00000000000000The 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/0000755000175000017500000000000013616004367015331 5ustar jendrikjendrik00000000000000vulture-1.3/tests/test_script.py0000644000175000017500000000303313316633755020253 0ustar jendrikjendrik00000000000000import 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.py0000644000175000017500000001407713542223356017724 0ustar jendrikjendrik00000000000000import 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.py0000644000175000017500000000326113533222652020101 0ustar jendrikjendrik00000000000000import 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.py0000644000175000017500000000056613533157123020262 0ustar jendrikjendrik00000000000000import 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.py0000644000175000017500000001141113137201537020431 0ustar jendrikjendrik00000000000000from . 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.py0000644000175000017500000000301513335401431021741 0ustar jendrikjendrik00000000000000import 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.py0000644000175000017500000000037413305474134017702 0ustar jendrikjendrik00000000000000from . 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__.py0000644000175000017500000000216013533222047017434 0ustar jendrikjendrik00000000000000import 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.py0000644000175000017500000000171713533164661020540 0ustar jendrikjendrik00000000000000import 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.py0000644000175000017500000000060313305474134020424 0ustar jendrikjendrik00000000000000from . 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.py0000644000175000017500000002460013556112722021066 0ustar jendrikjendrik00000000000000import 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.py0000644000175000017500000000570713533222643020233 0ustar jendrikjendrik00000000000000from 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.py0000644000175000017500000000764313566040247021126 0ustar jendrikjendrik00000000000000import 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.py0000644000175000017500000000344613542223120020250 0ustar jendrikjendrik00000000000000import 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.py0000664000175000017500000000261713562342743022016 0ustar jendrikjendrik00000000000000import 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.py0000644000175000017500000001122313175312744021213 0ustar jendrikjendrik00000000000000from . 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.py0000644000175000017500000000366413533226354021050 0ustar jendrikjendrik00000000000000import 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-INFO0000644000175000017500000004740713616004367015300 0ustar jendrikjendrik00000000000000Metadata-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/0000755000175000017500000000000013616004367017367 5ustar jendrikjendrik00000000000000vulture-1.3/vulture.egg-info/entry_points.txt0000644000175000017500000000005713616004367022667 0ustar jendrikjendrik00000000000000[console_scripts] vulture = vulture.core:main vulture-1.3/vulture.egg-info/dependency_links.txt0000644000175000017500000000000113616004367023435 0ustar jendrikjendrik00000000000000 vulture-1.3/vulture.egg-info/SOURCES.txt0000644000175000017500000000214613616004367021256 0ustar jendrikjendrik00000000000000CONTRIBUTING.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.pyvulture-1.3/vulture.egg-info/top_level.txt0000644000175000017500000000001013616004367022110 0ustar jendrikjendrik00000000000000vulture vulture-1.3/vulture.egg-info/PKG-INFO0000644000175000017500000004740713616004367020500 0ustar jendrikjendrik00000000000000Metadata-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.cfg0000644000175000017500000000030513616004367016006 0ustar jendrikjendrik00000000000000[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.in0000644000175000017500000000013713133600745015722 0ustar jendrikjendrik00000000000000include *.rst include *.txt include tests/*.py include tox.ini include vulture/whitelists/*.py vulture-1.3/CONTRIBUTING.rst0000644000175000017500000000560713221037563016634 0ustar jendrikjendrik00000000000000Contributing 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/0000755000175000017500000000000013616004367015675 5ustar jendrikjendrik00000000000000vulture-1.3/vulture/__init__.py0000644000175000017500000000012113315657163020004 0ustar jendrikjendrik00000000000000from vulture.core import __version__, Vulture assert __version__ assert Vulture vulture-1.3/vulture/utils.py0000644000175000017500000001003113570521477017407 0ustar jendrikjendrik00000000000000import 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.py0000644000175000017500000000456413533226440017366 0ustar jendrikjendrik00000000000000import 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.py0000644000175000017500000005233013616004366017201 0ustar jendrikjendrik00000000000000# -*- 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__.py0000644000175000017500000000004613315657163017773 0ustar jendrikjendrik00000000000000from vulture.core import main main() vulture-1.3/vulture/whitelists/0000755000175000017500000000000013616004367020074 5ustar jendrikjendrik00000000000000vulture-1.3/vulture/whitelists/ctypes_whitelist.py0000644000175000017500000000020513557375144024056 0ustar jendrikjendrik00000000000000from ctypes import _CFuncPtr from ctypes import _Pointer _CFuncPtr.argtypes _CFuncPtr.errcheck _CFuncPtr.restype _Pointer.contents vulture-1.3/vulture/whitelists/whitelist_utils.py0000644000175000017500000000155113133600745023700 0ustar jendrikjendrik00000000000000""" 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.py0000664000175000017500000000020413562326476025066 0ustar jendrikjendrik00000000000000import collections # To free memory, the "default_factory" attribute can be set to None. collections.defaultdict().default_factory vulture-1.3/vulture/whitelists/string_whitelist.py0000644000175000017500000000035513612116452024046 0ustar jendrikjendrik00000000000000import 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.py0000664000175000017500000000010113566032252024160 0ustar jendrikjendrik00000000000000import logging logging.Filter.filter logging.StreamHandler.emit vulture-1.3/vulture/whitelists/ast_whitelist.py0000644000175000017500000000476113557102776023350 0ustar jendrikjendrik00000000000000from 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.py0000644000175000017500000000015113365576104023361 0ustar jendrikjendrik00000000000000import sys sys.excepthook # Never report redirected streams as unused. sys.stderr sys.stdin sys.stdout vulture-1.3/vulture/whitelists/unittest_whitelist.py0000644000175000017500000000073013557375255024434 0ustar jendrikjendrik00000000000000from 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.py0000644000175000017500000000012513557375213024512 0ustar jendrikjendrik00000000000000import threading threading.Thread.daemon threading.Thread.name threading.Thread.run vulture-1.3/vulture/whitelists/argparse_whitelist.py0000664000175000017500000000025513562326476024362 0ustar jendrikjendrik00000000000000import argparse argparse.ArgumentParser().epilog argparse.ArgumentDefaultsHelpFormatter('prog')._fill_text argparse.ArgumentDefaultsHelpFormatter('prog')._get_help_string