deadcode 0.1.0__tar.gz → 1.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {deadcode-0.1.0 → deadcode-1.1.1}/PKG-INFO +25 -14
- deadcode-1.1.1/README.md +64 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/actions/find_unused_names.py +14 -2
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/actions/get_unused_names_error_message.py +6 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/actions/parse_abstract_syntax_trees.py +4 -3
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/actions/parse_arguments.py +12 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/cli.py +3 -2
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/data_types.py +2 -0
- deadcode-1.1.1/deadcode/tests/base.py +9 -0
- deadcode-1.1.1/deadcode/tests/test_command_line_argument_parsing.py +62 -0
- deadcode-1.1.1/deadcode/tests/test_count_output.py +49 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/tests/test_deadcode.py +4 -71
- deadcode-1.1.1/deadcode/tests/test_ignore_names_by_pattern.py +52 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/pyproject.toml +2 -2
- deadcode-0.1.0/README.md +0 -53
- {deadcode-0.1.0 → deadcode-1.1.1}/.gitignore +0 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/LICENSE +0 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/__init__.py +0 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/actions/__init__.py +0 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/actions/find_python_filenames.py +0 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/actions/parse_global_names.py +0 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/actions/read_files.py +0 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/tests/files/classes.py +0 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/tests/files/functions.py +0 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/tests/files/subdir/another_subdir/__init__.py +0 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/tests/files/subdir/one_more_file.py +0 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/tests/files/variables.py +0 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/utils/__init__.py +0 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/utils/flatten_lists.py +0 -0
- {deadcode-0.1.0 → deadcode-1.1.1}/deadcode/utils/path_matching.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: deadcode
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: Find and remove dead code.
|
|
5
5
|
Project-URL: Homepage, https://github.com/albertas/deadcode
|
|
6
6
|
Project-URL: Documentation, https://deadcode.readthedocs.io/
|
|
@@ -22,7 +22,7 @@ Description-Content-Type: text/markdown
|
|
|
22
22
|
`deadcode` package implements `DC100` - `unused-global-name` check for detecting
|
|
23
23
|
variable/function/class names which are never used in a whole code base.
|
|
24
24
|
Only globally defined names are being checked.
|
|
25
|
-
Unused local names can be detected by other tools like
|
|
25
|
+
Unused local names can be detected by other tools like [ruff](https://pypi.org/project/ruff/).
|
|
26
26
|
|
|
27
27
|
## Installation
|
|
28
28
|
```shell
|
|
@@ -36,7 +36,7 @@ deadcode .
|
|
|
36
36
|
|
|
37
37
|
Or with command line options:
|
|
38
38
|
```
|
|
39
|
-
deadcode . --exclude=venv,tests --ignore-names=BaseTestCase --ignore-names-in-files=migrations
|
|
39
|
+
deadcode . --exclude=venv,tests --ignore-names=BaseTestCase,.*Mixin --ignore-names-in-files=migrations
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
The same options can be provided in `pyproject.toml` settings file:
|
|
@@ -48,26 +48,37 @@ ignore-names-in-files = ["migrations"]
|
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
Command line options:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
|
|
52
|
+
| Option | Type | Meaning |
|
|
53
|
+
|-----------------------------------|------|----------|
|
|
54
|
+
|<pre>--exclude</pre> | list | Path expressions to completely skip files from being analysed. |
|
|
55
|
+
|<pre>--ignore-names</pre> | list | Removes provided list of names from the error output. Regexp expressions to match multiple names can also be provided, e.g. `.*Mixin` will match all classes ending with `Mixin`. |
|
|
56
|
+
|<pre>--ignore-names-in-files</pre> | list | Unused names from files matching provided path expressions. |
|
|
57
|
+
|<pre>--no-color</pre> | None | Removes colors from the output. |
|
|
58
|
+
|<pre>--count</pre> | None | Provides the count of the detected unused names instead of printing them all out. |
|
|
59
|
+
|<pre>--quiet</pre> | None | Does not output anything. Makefile still fails with exit code 1 if unused names are found. |
|
|
60
|
+
|
|
57
61
|
|
|
58
62
|
## Contributing
|
|
59
63
|
- `make check` - runs unit tests and other checks using virtual environment.
|
|
60
64
|
|
|
61
65
|
## Rationale
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
[ruff](https://pypi.org/project/ruff/) and
|
|
67
|
+
[flake8](https://pypi.org/project/flake8/) - don't have rules for unused global
|
|
68
|
+
code detection, only for local ones `F823`, `F841`, `F842`. `deadcode` package
|
|
69
|
+
tries to add a new `DC100` check for detecting variables/functions/classes
|
|
70
|
+
which are not used in a whole code base.
|
|
64
71
|
|
|
65
|
-
There is an alternative
|
|
72
|
+
There is an alternative [vulture](https://pypi.org/project/vulture/) package,
|
|
73
|
+
which provides many false positives.
|
|
74
|
+
`deadcode` - tries to find less, but findings are with higher confidence.
|
|
66
75
|
`deadcode` - is supposed to be used inline with other static code checkers like `ruff`.
|
|
67
76
|
|
|
68
77
|
## Known limitations
|
|
69
78
|
If the same unused name is repeated in several files - it wont be detected.
|
|
70
79
|
|
|
71
|
-
Files with syntax errors will be ignored, because `deadcode` uses `ast` to
|
|
80
|
+
Files with syntax errors will be ignored, because `deadcode` uses `ast` to
|
|
81
|
+
build abstract syntax tree for name usage detection.
|
|
72
82
|
|
|
73
|
-
It is assumed that `deadcode` will be run using the same Python version as the
|
|
83
|
+
It is assumed that `deadcode` will be run using the same Python version as the
|
|
84
|
+
checked code base is implemented in.
|
deadcode-1.1.1/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# deadcode
|
|
2
|
+
`deadcode` package implements `DC100` - `unused-global-name` check for detecting
|
|
3
|
+
variable/function/class names which are never used in a whole code base.
|
|
4
|
+
Only globally defined names are being checked.
|
|
5
|
+
Unused local names can be detected by other tools like [ruff](https://pypi.org/project/ruff/).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
```shell
|
|
9
|
+
pip install deadcode
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
```shell
|
|
14
|
+
deadcode .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or with command line options:
|
|
18
|
+
```
|
|
19
|
+
deadcode . --exclude=venv,tests --ignore-names=BaseTestCase,.*Mixin --ignore-names-in-files=migrations
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The same options can be provided in `pyproject.toml` settings file:
|
|
23
|
+
```
|
|
24
|
+
[tool.deadcode]
|
|
25
|
+
exclude = ["venv", "tests"]
|
|
26
|
+
ignore-names = ["BaseTestCase"]
|
|
27
|
+
ignore-names-in-files = ["migrations"]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Command line options:
|
|
31
|
+
|
|
32
|
+
| Option | Type | Meaning |
|
|
33
|
+
|-----------------------------------|------|----------|
|
|
34
|
+
|<pre>--exclude</pre> | list | Path expressions to completely skip files from being analysed. |
|
|
35
|
+
|<pre>--ignore-names</pre> | list | Removes provided list of names from the error output. Regexp expressions to match multiple names can also be provided, e.g. `.*Mixin` will match all classes ending with `Mixin`. |
|
|
36
|
+
|<pre>--ignore-names-in-files</pre> | list | Unused names from files matching provided path expressions. |
|
|
37
|
+
|<pre>--no-color</pre> | None | Removes colors from the output. |
|
|
38
|
+
|<pre>--count</pre> | None | Provides the count of the detected unused names instead of printing them all out. |
|
|
39
|
+
|<pre>--quiet</pre> | None | Does not output anything. Makefile still fails with exit code 1 if unused names are found. |
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## Contributing
|
|
43
|
+
- `make check` - runs unit tests and other checks using virtual environment.
|
|
44
|
+
|
|
45
|
+
## Rationale
|
|
46
|
+
[ruff](https://pypi.org/project/ruff/) and
|
|
47
|
+
[flake8](https://pypi.org/project/flake8/) - don't have rules for unused global
|
|
48
|
+
code detection, only for local ones `F823`, `F841`, `F842`. `deadcode` package
|
|
49
|
+
tries to add a new `DC100` check for detecting variables/functions/classes
|
|
50
|
+
which are not used in a whole code base.
|
|
51
|
+
|
|
52
|
+
There is an alternative [vulture](https://pypi.org/project/vulture/) package,
|
|
53
|
+
which provides many false positives.
|
|
54
|
+
`deadcode` - tries to find less, but findings are with higher confidence.
|
|
55
|
+
`deadcode` - is supposed to be used inline with other static code checkers like `ruff`.
|
|
56
|
+
|
|
57
|
+
## Known limitations
|
|
58
|
+
If the same unused name is repeated in several files - it wont be detected.
|
|
59
|
+
|
|
60
|
+
Files with syntax errors will be ignored, because `deadcode` uses `ast` to
|
|
61
|
+
build abstract syntax tree for name usage detection.
|
|
62
|
+
|
|
63
|
+
It is assumed that `deadcode` will be run using the same Python version as the
|
|
64
|
+
checked code base is implemented in.
|
|
@@ -11,16 +11,28 @@ def find_unused_names(
|
|
|
11
11
|
args: Args,
|
|
12
12
|
) -> Dict[VariableName, Filename]:
|
|
13
13
|
name_occourencies: Dict[str, int] = defaultdict(int)
|
|
14
|
+
|
|
14
15
|
for name in global_names.keys():
|
|
15
16
|
pattern = re.compile(
|
|
16
|
-
|
|
17
|
+
"|".join(
|
|
18
|
+
[
|
|
19
|
+
rf"FunctionDef\(name=\'{name}\'",
|
|
20
|
+
rf"ClassDef\(name=\'{name}\'",
|
|
21
|
+
rf"Name\(id=\'{name}\'",
|
|
22
|
+
rf"alias\(name=\'{name}\'",
|
|
23
|
+
]
|
|
24
|
+
)
|
|
17
25
|
)
|
|
18
26
|
for abstract_syntax_tree in files.values():
|
|
19
27
|
name_occourencies[name] += len(re.findall(pattern, abstract_syntax_tree))
|
|
20
28
|
|
|
29
|
+
# Filter out used and ignored names
|
|
30
|
+
ignore_names_pattern = "|".join(args.ignore_names) or "^$"
|
|
31
|
+
ignore_names_re = re.compile(ignore_names_pattern)
|
|
32
|
+
|
|
21
33
|
unused_variables = {
|
|
22
34
|
name: filename
|
|
23
35
|
for name, filename in global_names.items()
|
|
24
|
-
if name_occourencies[name] == 1 and
|
|
36
|
+
if name_occourencies[name] == 1 and not re.findall(ignore_names_re, name)
|
|
25
37
|
}
|
|
26
38
|
return unused_variables
|
|
@@ -8,6 +8,12 @@ def get_unused_names_error_message(unused_names: Dict[VariableName, Filename], a
|
|
|
8
8
|
if not unused_names:
|
|
9
9
|
return None
|
|
10
10
|
|
|
11
|
+
if args.quiet:
|
|
12
|
+
return ""
|
|
13
|
+
|
|
14
|
+
if args.count:
|
|
15
|
+
return f"{len(unused_names)}"
|
|
16
|
+
|
|
11
17
|
if args.no_color:
|
|
12
18
|
return "\n".join([f"{filename} DC100 Global {name} is never used" for name, filename in unused_names.items()])
|
|
13
19
|
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import ast
|
|
2
2
|
from typing import Dict
|
|
3
3
|
|
|
4
|
-
from deadcode.data_types import FileContent, Filename, AbstractSyntaxTree
|
|
4
|
+
from deadcode.data_types import Args, FileContent, Filename, AbstractSyntaxTree
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def parse_abstract_syntax_trees(files: Dict[Filename, FileContent]) -> Dict[Filename, AbstractSyntaxTree]:
|
|
7
|
+
def parse_abstract_syntax_trees(files: Dict[Filename, FileContent], args: Args) -> Dict[Filename, AbstractSyntaxTree]:
|
|
8
8
|
"""Did my code coverage just increased? Answer is no: a doc string was ignored :tada:"""
|
|
9
9
|
abstract_syntax_trees_for_files = {}
|
|
10
10
|
for filename, file_content in files.items():
|
|
11
11
|
try:
|
|
12
12
|
abstract_syntax_trees_for_files[filename] = ast.dump(ast.parse(file_content)) # Note: indent=1 for debug
|
|
13
13
|
except: # noqa: E722
|
|
14
|
-
|
|
14
|
+
if not args.count and not args.quiet:
|
|
15
|
+
print(f"Error: Failed to parse {filename} file, ignoring it.")
|
|
15
16
|
abstract_syntax_trees_for_files[filename] = ""
|
|
16
17
|
return abstract_syntax_trees_for_files
|
|
@@ -57,6 +57,18 @@ def parse_arguments(args: Optional[List[str]]) -> Args:
|
|
|
57
57
|
action="store_true",
|
|
58
58
|
default=False,
|
|
59
59
|
)
|
|
60
|
+
parser.add_argument(
|
|
61
|
+
"--quiet",
|
|
62
|
+
help="Does not output anything. Makefile still fails with exit code 1 if unused names are found.",
|
|
63
|
+
action="store_true",
|
|
64
|
+
default=False,
|
|
65
|
+
)
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
"--count",
|
|
68
|
+
help="Provides the count of the detected unused names instead of printing them all out.",
|
|
69
|
+
action="store_true",
|
|
70
|
+
default=False,
|
|
71
|
+
)
|
|
60
72
|
|
|
61
73
|
parsed_args = parser.parse_args(args).__dict__
|
|
62
74
|
|
|
@@ -21,13 +21,14 @@ def main(
|
|
|
21
21
|
files = read_files(python_filenames)
|
|
22
22
|
|
|
23
23
|
global_names = parse_global_names(files, args=args)
|
|
24
|
-
abstract_syntax_trees_of_files = parse_abstract_syntax_trees(files)
|
|
24
|
+
abstract_syntax_trees_of_files = parse_abstract_syntax_trees(files, args=args)
|
|
25
25
|
unused_names = find_unused_names(abstract_syntax_trees_of_files, global_names, args=args)
|
|
26
26
|
|
|
27
27
|
if error_message := get_unused_names_error_message(unused_names, args=args):
|
|
28
28
|
return error_message
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
if not args.count and not args.quiet:
|
|
31
|
+
print("\033[1mWell done!\033[0m ✨ 🚀 ✨")
|
|
31
32
|
return None
|
|
32
33
|
|
|
33
34
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from deadcode.actions.parse_arguments import parse_arguments
|
|
2
|
+
from deadcode.tests.base import BaseTestCase
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CommandLineArgParsingTests(BaseTestCase):
|
|
6
|
+
def setUp(self):
|
|
7
|
+
self.tomlib_load_mock = self.patch("deadcode.actions.parse_arguments.tomllib.load")
|
|
8
|
+
self.tomlib_load_mock.return_value = {
|
|
9
|
+
"tool": {
|
|
10
|
+
"deadcode": {
|
|
11
|
+
"exclude": [],
|
|
12
|
+
"ignore_names": [],
|
|
13
|
+
"ignore_names_in_files": [],
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def test_calling_with_one_paths_argument(self):
|
|
19
|
+
args = parse_arguments(["."])
|
|
20
|
+
self.assertEqual(args.paths, ["."])
|
|
21
|
+
self.assertEqual(args.exclude, [])
|
|
22
|
+
self.assertEqual(args.ignore_names, [])
|
|
23
|
+
self.assertEqual(args.ignore_names_in_files, [])
|
|
24
|
+
|
|
25
|
+
def test_calling_with_several_paths_argument(self):
|
|
26
|
+
args = parse_arguments([".", "tests"])
|
|
27
|
+
self.assertEqual(args.paths, [".", "tests"])
|
|
28
|
+
self.assertEqual(args.exclude, [])
|
|
29
|
+
self.assertEqual(args.ignore_names, [])
|
|
30
|
+
self.assertEqual(args.ignore_names_in_files, [])
|
|
31
|
+
|
|
32
|
+
def test_calling_with_exclude(self):
|
|
33
|
+
args = parse_arguments([".", "--exclude=tests,venv"])
|
|
34
|
+
self.assertEqual(args.paths, ["."])
|
|
35
|
+
self.assertEqual(args.exclude, ["tests", "venv"])
|
|
36
|
+
self.assertEqual(args.ignore_names, [])
|
|
37
|
+
self.assertEqual(args.ignore_names_in_files, [])
|
|
38
|
+
|
|
39
|
+
def test_calling_with_several_exclude_options(self):
|
|
40
|
+
args = parse_arguments([".", "--exclude=tests,venv", "--exclude=migrations"])
|
|
41
|
+
self.assertEqual(args.paths, ["."])
|
|
42
|
+
self.assertEqual(args.exclude, ["tests", "venv", "migrations"])
|
|
43
|
+
self.assertEqual(args.ignore_names, [])
|
|
44
|
+
self.assertEqual(args.ignore_names_in_files, [])
|
|
45
|
+
|
|
46
|
+
def test_calling_with_no_color_option(self):
|
|
47
|
+
args = parse_arguments([".", "--no-color"])
|
|
48
|
+
self.assertEqual(args.paths, ["."])
|
|
49
|
+
self.assertEqual(args.no_color, True)
|
|
50
|
+
|
|
51
|
+
def test_ignore_names_and_ignore_files_command_line_argument_parsing(self):
|
|
52
|
+
args = parse_arguments(
|
|
53
|
+
[
|
|
54
|
+
".",
|
|
55
|
+
"--ignore-names-in-files=tests,venv",
|
|
56
|
+
"--ignore-names=BaseTestCase,lambda_handler",
|
|
57
|
+
]
|
|
58
|
+
)
|
|
59
|
+
self.assertEqual(args.paths, ["."])
|
|
60
|
+
self.assertEqual(args.exclude, [])
|
|
61
|
+
self.assertEqual(args.ignore_names, ["BaseTestCase", "lambda_handler"])
|
|
62
|
+
self.assertEqual(args.ignore_names_in_files, ["tests", "venv"])
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from deadcode.cli import main
|
|
2
|
+
from deadcode.tests.base import BaseTestCase
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class IgnoreNamesByPatternTests(BaseTestCase):
|
|
6
|
+
def test_count_several_classes(self):
|
|
7
|
+
# Having
|
|
8
|
+
self.read_files_mock = self.patch("deadcode.cli.read_files")
|
|
9
|
+
self.read_files_mock.return_value = {
|
|
10
|
+
"ignore_names_by_pattern.py": """
|
|
11
|
+
class MyModel:
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
class MyUserModel:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
class Unused:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
class ThisClassShouldBeIgnored:
|
|
21
|
+
pass
|
|
22
|
+
"""
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# When
|
|
26
|
+
unused_name_count = main(["ignore_names_by_pattern.py", "--no-color", "--count"])
|
|
27
|
+
|
|
28
|
+
# Then
|
|
29
|
+
self.assertEqual(unused_name_count, "4")
|
|
30
|
+
|
|
31
|
+
def test_count_variables_function_and_class(self):
|
|
32
|
+
# Having
|
|
33
|
+
self.read_files_mock = self.patch("deadcode.cli.read_files")
|
|
34
|
+
self.read_files_mock.return_value = {
|
|
35
|
+
"ignore_names_by_pattern.py": """
|
|
36
|
+
first_variable = 0
|
|
37
|
+
def this_is_a_function():
|
|
38
|
+
pass
|
|
39
|
+
class MyModel:
|
|
40
|
+
def __init__(self):
|
|
41
|
+
pass
|
|
42
|
+
"""
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# When
|
|
46
|
+
unused_name_count = main(["ignore_names_by_pattern.py", "--no-color", "--count"])
|
|
47
|
+
|
|
48
|
+
# Then
|
|
49
|
+
self.assertEqual(unused_name_count, "3")
|
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
from unittest import TestCase
|
|
2
|
-
from unittest.mock import MagicMock, patch
|
|
3
|
-
|
|
4
1
|
from deadcode.cli import main
|
|
5
|
-
from deadcode.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class BaseTestCase(TestCase):
|
|
9
|
-
def patch(self, path: str) -> MagicMock:
|
|
10
|
-
patcher = patch(path)
|
|
11
|
-
self.addCleanup(patcher.stop)
|
|
12
|
-
return patcher.start()
|
|
2
|
+
from deadcode.tests.base import BaseTestCase
|
|
13
3
|
|
|
14
4
|
|
|
15
|
-
class
|
|
5
|
+
class DeadCodeIntegrationTests(BaseTestCase):
|
|
16
6
|
def test_unused_variable_name_found(self):
|
|
17
7
|
unused_names = main(["tests/files/variables.py", "--no-color"])
|
|
18
8
|
|
|
@@ -137,62 +127,5 @@ print(THIS_ONE_IS_USED)"""
|
|
|
137
127
|
),
|
|
138
128
|
)
|
|
139
129
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def setUp(self):
|
|
143
|
-
self.tomlib_load_mock = self.patch("deadcode.actions.parse_arguments.tomllib.load")
|
|
144
|
-
self.tomlib_load_mock.return_value = {
|
|
145
|
-
"tool": {
|
|
146
|
-
"deadcode": {
|
|
147
|
-
"exclude": [],
|
|
148
|
-
"ignore_names": [],
|
|
149
|
-
"ignore_names_in_files": [],
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
def test_calling_with_one_paths_argument(self):
|
|
155
|
-
args = parse_arguments(["."])
|
|
156
|
-
self.assertEqual(args.paths, ["."])
|
|
157
|
-
self.assertEqual(args.exclude, [])
|
|
158
|
-
self.assertEqual(args.ignore_names, [])
|
|
159
|
-
self.assertEqual(args.ignore_names_in_files, [])
|
|
160
|
-
|
|
161
|
-
def test_calling_with_several_paths_argument(self):
|
|
162
|
-
args = parse_arguments([".", "tests"])
|
|
163
|
-
self.assertEqual(args.paths, [".", "tests"])
|
|
164
|
-
self.assertEqual(args.exclude, [])
|
|
165
|
-
self.assertEqual(args.ignore_names, [])
|
|
166
|
-
self.assertEqual(args.ignore_names_in_files, [])
|
|
167
|
-
|
|
168
|
-
def test_calling_with_exclude(self):
|
|
169
|
-
args = parse_arguments([".", "--exclude=tests,venv"])
|
|
170
|
-
self.assertEqual(args.paths, ["."])
|
|
171
|
-
self.assertEqual(args.exclude, ["tests", "venv"])
|
|
172
|
-
self.assertEqual(args.ignore_names, [])
|
|
173
|
-
self.assertEqual(args.ignore_names_in_files, [])
|
|
174
|
-
|
|
175
|
-
def test_calling_with_several_exclude_options(self):
|
|
176
|
-
args = parse_arguments([".", "--exclude=tests,venv", "--exclude=migrations"])
|
|
177
|
-
self.assertEqual(args.paths, ["."])
|
|
178
|
-
self.assertEqual(args.exclude, ["tests", "venv", "migrations"])
|
|
179
|
-
self.assertEqual(args.ignore_names, [])
|
|
180
|
-
self.assertEqual(args.ignore_names_in_files, [])
|
|
181
|
-
|
|
182
|
-
def test_calling_with_no_color_option(self):
|
|
183
|
-
args = parse_arguments([".", "--no-color"])
|
|
184
|
-
self.assertEqual(args.paths, ["."])
|
|
185
|
-
self.assertEqual(args.no_color, True)
|
|
186
|
-
|
|
187
|
-
def test_ignore_names_and_ignore_files_command_line_argument_parsing(self):
|
|
188
|
-
args = parse_arguments(
|
|
189
|
-
[
|
|
190
|
-
".",
|
|
191
|
-
"--ignore-names-in-files=tests,venv",
|
|
192
|
-
"--ignore-names=BaseTestCase,lambda_handler",
|
|
193
|
-
]
|
|
194
|
-
)
|
|
195
|
-
self.assertEqual(args.paths, ["."])
|
|
196
|
-
self.assertEqual(args.exclude, [])
|
|
197
|
-
self.assertEqual(args.ignore_names, ["BaseTestCase", "lambda_handler"])
|
|
198
|
-
self.assertEqual(args.ignore_names_in_files, ["tests", "venv"])
|
|
130
|
+
def test_variable_is_marked_as_used_even_if_its_imported_as_a_different_name(self):
|
|
131
|
+
pass
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from deadcode.cli import main
|
|
2
|
+
from deadcode.tests.base import BaseTestCase
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class IgnoreNamesByPatternTests(BaseTestCase):
|
|
6
|
+
def test_ignore_names_matched_by_word_and_group_regexp_patterns(self):
|
|
7
|
+
self.read_files_mock = self.patch("deadcode.cli.read_files")
|
|
8
|
+
self.read_files_mock.return_value = {
|
|
9
|
+
"ignore_names_by_pattern.py": """
|
|
10
|
+
class MyModel:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
class MyUserModel:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
class Unused:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
class ThisClassShouldBeIgnored:
|
|
20
|
+
pass
|
|
21
|
+
"""
|
|
22
|
+
}
|
|
23
|
+
unused_names = main(["ignore_names_by_pattern.py", "--no-color", "--ignore-names=\\w*Model,.*[Ii]{1}gnore.*"])
|
|
24
|
+
|
|
25
|
+
self.assertEqual(
|
|
26
|
+
unused_names,
|
|
27
|
+
("ignore_names_by_pattern.py:8:6: DC100 Global Unused is never used"),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def test_ignore_names_matched_by_dot_regexp_pattern(self):
|
|
31
|
+
self.read_files_mock = self.patch("deadcode.cli.read_files")
|
|
32
|
+
self.read_files_mock.return_value = {
|
|
33
|
+
"ignore_names_by_pattern.py": """
|
|
34
|
+
class MyModel:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
class MyUserModel:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
class Unused:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
class ThisClassShouldBeIgnored:
|
|
44
|
+
pass
|
|
45
|
+
"""
|
|
46
|
+
}
|
|
47
|
+
unused_names = main(["ignore_names_by_pattern.py", "--no-color", "--ignore-names=.*Model,.*Ignore.*"])
|
|
48
|
+
|
|
49
|
+
self.assertEqual(
|
|
50
|
+
unused_names,
|
|
51
|
+
("ignore_names_by_pattern.py:8:6: DC100 Global Unused is never used"),
|
|
52
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "deadcode"
|
|
3
|
-
version = "
|
|
3
|
+
version = "1.1.1"
|
|
4
4
|
authors = [
|
|
5
5
|
{name = "Albertas Gimbutas", email = "albertasgim@gmail.com"},
|
|
6
6
|
]
|
|
@@ -69,4 +69,4 @@ target_version = ["py38"]
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
[tool.pytest.ini_options]
|
|
72
|
-
addopts = "--cov=. --cov-fail-under=
|
|
72
|
+
addopts = "--cov=. --cov-fail-under=92.0"
|
deadcode-0.1.0/README.md
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# deadcode
|
|
2
|
-
`deadcode` package implements `DC100` - `unused-global-name` check for detecting
|
|
3
|
-
variable/function/class names which are never used in a whole code base.
|
|
4
|
-
Only globally defined names are being checked.
|
|
5
|
-
Unused local names can be detected by other tools like `ruff`.
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
8
|
-
```shell
|
|
9
|
-
pip install deadcode
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## Usage
|
|
13
|
-
```shell
|
|
14
|
-
deadcode .
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Or with command line options:
|
|
18
|
-
```
|
|
19
|
-
deadcode . --exclude=venv,tests --ignore-names=BaseTestCase --ignore-names-in-files=migrations
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
The same options can be provided in `pyproject.toml` settings file:
|
|
23
|
-
```
|
|
24
|
-
[tool.deadcode]
|
|
25
|
-
exclude = ["venv", "tests"]
|
|
26
|
-
ignore-names = ["BaseTestCase"]
|
|
27
|
-
ignore-names-in-files = ["migrations"]
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Command line options:
|
|
31
|
-
| Option | Type | Meaning |
|
|
32
|
-
|---------------------------|-------|----------|
|
|
33
|
-
|`--exclude` | list | Path expressions to completely skips files from being analysed. |
|
|
34
|
-
|`--ignore-names` | list | Removes provided list of names from the error output. |
|
|
35
|
-
|`--ignore-names-in-files` | list | Unused names from files matching provided path expressions. |
|
|
36
|
-
|`--no-color` | None | Removes colors from the output. |
|
|
37
|
-
|
|
38
|
-
## Contributing
|
|
39
|
-
- `make check` - runs unit tests and other checks using virtual environment.
|
|
40
|
-
|
|
41
|
-
## Rationale
|
|
42
|
-
`ruff` and `flake8` - don't have rules for unused global code detection, only for local ones `F823`, `F841`, `F842`.
|
|
43
|
-
`deadcode` package tries to add a new `DC100` check for detecting variables/functions/classes which are not used in a whole code base.
|
|
44
|
-
|
|
45
|
-
There is an alternative `vulture` package, which provides many false positives. `deadcode` - tries to find less, but findings are with higher confidence.
|
|
46
|
-
`deadcode` - is supposed to be used inline with other static code checkers like `ruff`.
|
|
47
|
-
|
|
48
|
-
## Known limitations
|
|
49
|
-
If the same unused name is repeated in several files - it wont be detected.
|
|
50
|
-
|
|
51
|
-
Files with syntax errors will be ignored, because `deadcode` uses `ast` to build abstract syntax tree for name usage detection.
|
|
52
|
-
|
|
53
|
-
It is assumed that `deadcode` will be run using the same Python version as the checked code base is implemented in.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|