lapsed 2.4.2__tar.gz → 2.6.0__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.
Files changed (33) hide show
  1. {lapsed-2.4.2 → lapsed-2.6.0}/PKG-INFO +12 -2
  2. {lapsed-2.4.2 → lapsed-2.6.0}/README.md +11 -1
  3. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/actions/parse_arguments.py +23 -0
  4. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/data_types.py +2 -0
  5. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/visitor/dead_code_visitor.py +18 -0
  6. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/visitor/ignore.py +6 -0
  7. {lapsed-2.4.2 → lapsed-2.6.0}/pyproject.toml +1 -1
  8. {lapsed-2.4.2 → lapsed-2.6.0}/.gitignore +0 -0
  9. {lapsed-2.4.2 → lapsed-2.6.0}/LICENSE +0 -0
  10. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/__init__.py +0 -0
  11. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/actions/__init__.py +0 -0
  12. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/actions/find_python_filenames.py +0 -0
  13. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/actions/find_unused_names.py +0 -0
  14. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/actions/fix_or_show_unused_code.py +0 -0
  15. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/actions/get_unused_names_error_message.py +0 -0
  16. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/actions/merge_overlaping_file_parts.py +0 -0
  17. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/actions/parse_abstract_syntax_tree.py +0 -0
  18. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/actions/parse_tach_config.py +0 -0
  19. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/actions/remove_file_parts_from_content.py +0 -0
  20. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/cli.py +0 -0
  21. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/constants.py +0 -0
  22. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/utils/__init__.py +0 -0
  23. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/utils/add_colors_to_diff.py +0 -0
  24. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/utils/base_test_case.py +0 -0
  25. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/utils/fix_indent.py +0 -0
  26. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/utils/flatten_lists.py +0 -0
  27. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/utils/nested_scopes.py +0 -0
  28. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/utils/print_ast.py +0 -0
  29. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/visitor/__init__.py +0 -0
  30. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/visitor/code_item.py +0 -0
  31. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/visitor/lines.py +0 -0
  32. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/visitor/noqa.py +0 -0
  33. {lapsed-2.4.2 → lapsed-2.6.0}/deadcode/visitor/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lapsed
3
- Version: 2.4.2
3
+ Version: 2.6.0
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/
@@ -94,7 +94,7 @@ Create a `.pre-commit-config.yaml` file in the root of your project directory, i
94
94
  # See https://pre-commit.com/hooks.html for more hooks
95
95
  repos:
96
96
  - repo: https://github.com/albertas/deadcode
97
- rev: 2.4.2
97
+ rev: 2.6.0
98
98
  hooks:
99
99
  - id: deadcode
100
100
  ```
@@ -117,6 +117,8 @@ repos:
117
117
  |`--ignore-definitions` | list | Ignores definition (including name and body) if a name of an expression matches any of the provided ones. |
118
118
  |`--ignore-definitions-if-inherits-from` | list | Ignores definition (including name and body) of a class if it inherits from any of the provided class names. |
119
119
  |`--ignore-definitions-if-decorated-with` | list | Ignores definition (including name and body) of an expression, which is decorated with any of the provided decorator names. |
120
+ |`--ignore-non-self-attributes` | - | Does not report unused attributes assigned on objects other than `self`, e.g. `foo.bar = 1`. Such assignments may have side effects or be consumed by code outside of the analysed files, so they cannot safely be assumed dead. |
121
+ |`--ignore-class-attributes` | - | Does not report unused attributes assigned directly in a class body, e.g. `THING = "blah"` inside a `class Foo:` block. Such attributes may control behaviour of inherited/overridden methods, external frameworks, or metaclasses, so they cannot safely be assumed dead. |
120
122
  |`--tach-config` | list | Paths to `tach.toml` files (see [Tach](https://docs.gauge.sh)). Names exposed via `[[interfaces]]` are treated as public API and never reported as unused; files in `unchecked = true` modules are skipped entirely. |
121
123
  |`--no-color` | - | Removes colors from the output. |
122
124
  |`--count` | - | Provides the count of the detected unused names instead of printing them all out. |
@@ -225,6 +227,14 @@ code base is implemented in.
225
227
  - [ ] Investigate ways of extracting and backporting Python3.10+ `ast` implementation to lower Python versions.
226
228
 
227
229
  ## Release notes
230
+ - v2.6.0:
231
+ - Add `--ignore-class-attributes` option to not report unused attributes assigned directly
232
+ in a class body (e.g. `THING = "blah"` inside `class Foo:`), since such attributes may
233
+ control behaviour of inherited/overridden methods, external frameworks, or metaclasses.
234
+ - v2.5.0:
235
+ - Add `--ignore-non-self-attributes` option to not report unused attributes assigned on
236
+ objects other than `self` (e.g. `foo.bar = 1`), since such assignments may have side
237
+ effects or be consumed by code outside of the analysed files.
228
238
  - v2.4.2:
229
239
  - Add `--tach-config` option to read `tach.toml` file(s) and treat names exposed via `[[interfaces]]`
230
240
  as public API, and skip `unchecked = true` modules entirely.
@@ -66,7 +66,7 @@ Create a `.pre-commit-config.yaml` file in the root of your project directory, i
66
66
  # See https://pre-commit.com/hooks.html for more hooks
67
67
  repos:
68
68
  - repo: https://github.com/albertas/deadcode
69
- rev: 2.4.2
69
+ rev: 2.6.0
70
70
  hooks:
71
71
  - id: deadcode
72
72
  ```
@@ -89,6 +89,8 @@ repos:
89
89
  |`--ignore-definitions` | list | Ignores definition (including name and body) if a name of an expression matches any of the provided ones. |
90
90
  |`--ignore-definitions-if-inherits-from` | list | Ignores definition (including name and body) of a class if it inherits from any of the provided class names. |
91
91
  |`--ignore-definitions-if-decorated-with` | list | Ignores definition (including name and body) of an expression, which is decorated with any of the provided decorator names. |
92
+ |`--ignore-non-self-attributes` | - | Does not report unused attributes assigned on objects other than `self`, e.g. `foo.bar = 1`. Such assignments may have side effects or be consumed by code outside of the analysed files, so they cannot safely be assumed dead. |
93
+ |`--ignore-class-attributes` | - | Does not report unused attributes assigned directly in a class body, e.g. `THING = "blah"` inside a `class Foo:` block. Such attributes may control behaviour of inherited/overridden methods, external frameworks, or metaclasses, so they cannot safely be assumed dead. |
92
94
  |`--tach-config` | list | Paths to `tach.toml` files (see [Tach](https://docs.gauge.sh)). Names exposed via `[[interfaces]]` are treated as public API and never reported as unused; files in `unchecked = true` modules are skipped entirely. |
93
95
  |`--no-color` | - | Removes colors from the output. |
94
96
  |`--count` | - | Provides the count of the detected unused names instead of printing them all out. |
@@ -197,6 +199,14 @@ code base is implemented in.
197
199
  - [ ] Investigate ways of extracting and backporting Python3.10+ `ast` implementation to lower Python versions.
198
200
 
199
201
  ## Release notes
202
+ - v2.6.0:
203
+ - Add `--ignore-class-attributes` option to not report unused attributes assigned directly
204
+ in a class body (e.g. `THING = "blah"` inside `class Foo:`), since such attributes may
205
+ control behaviour of inherited/overridden methods, external frameworks, or metaclasses.
206
+ - v2.5.0:
207
+ - Add `--ignore-non-self-attributes` option to not report unused attributes assigned on
208
+ objects other than `self` (e.g. `foo.bar = 1`), since such assignments may have side
209
+ effects or be consumed by code outside of the analysed files.
200
210
  - v2.4.2:
201
211
  - Add `--tach-config` option to read `tach.toml` file(s) and treat names exposed via `[[interfaces]]`
202
212
  as public API, and skip `unchecked = true` modules entirely.
@@ -167,6 +167,29 @@ def parse_arguments(args: Optional[List[str]]) -> Args:
167
167
  type=str,
168
168
  )
169
169
 
170
+ parser.add_argument(
171
+ '--ignore-non-self-attributes',
172
+ help=(
173
+ 'Does not report unused attributes assigned on objects other than `self`, e.g. `foo.bar = 1` '
174
+ 'where `foo` is not the first argument of the enclosing method. Such assignments may have '
175
+ 'side effects or be consumed by code outside of the analysed files, so they cannot safely '
176
+ 'be assumed dead.'
177
+ ),
178
+ action='store_true',
179
+ default=False,
180
+ )
181
+
182
+ parser.add_argument(
183
+ '--ignore-class-attributes',
184
+ help=(
185
+ 'Does not report unused attributes assigned directly in a class body, e.g. `THING = "blah"` '
186
+ 'inside a `class Foo:` block. Such attributes may control behaviour of inherited or overridden '
187
+ 'methods, external frameworks, or metaclasses, so they cannot safely be assumed dead.'
188
+ ),
189
+ action='store_true',
190
+ default=False,
191
+ )
192
+
170
193
  parser.add_argument(
171
194
  '--tach-config',
172
195
  help=(
@@ -31,6 +31,8 @@ class Args:
31
31
  ignore_names_if_decorated_with: Iterable[Pathname] = ()
32
32
  ignore_names_if_inherits_from: Iterable[Pathname] = ()
33
33
  ignore_names_in_files: Iterable[Pathname] = ()
34
+ ignore_non_self_attributes: bool = False
35
+ ignore_class_attributes: bool = False
34
36
  tach_config: Iterable[Pathname] = ()
35
37
  no_color: bool = False
36
38
  quiet: bool = False
@@ -35,6 +35,7 @@ from deadcode.visitor.ignore import (
35
35
  _ignore_method,
36
36
  _ignore_pytest_fixture,
37
37
  _ignore_variable,
38
+ _is_self_attribute,
38
39
  )
39
40
 
40
41
  logger = getLogger()
@@ -72,6 +73,11 @@ class DeadCodeVisitor(ast.NodeVisitor):
72
73
  # Note: scope is a stack containing current module name, class names, function names
73
74
  self.scope_parts: List[str] = []
74
75
 
76
+ # Parallel stack to scope_parts (module name excluded), tracking whether each
77
+ # pushed scope is a 'class' or a 'function', so that class-level attributes
78
+ # (assigned directly in a class body) can be told apart from local variables.
79
+ self.scope_kinds: List[str] = []
80
+
75
81
  # This flag is used to stop registering code definitions in a code item
76
82
  # during recursive its parsing
77
83
  self.should_ignore_new_definitions = False
@@ -100,6 +106,7 @@ class DeadCodeVisitor(ast.NodeVisitor):
100
106
  filename = os.path.basename(file_path)
101
107
  module_name = os.path.splitext(filename)[0]
102
108
  self.scope_parts = [module_name]
109
+ self.scope_kinds = []
103
110
 
104
111
  file_content = f.read()
105
112
  if file_content.strip() or (filename.startswith('__') and filename.endswith('__.py')):
@@ -284,7 +291,13 @@ class DeadCodeVisitor(ast.NodeVisitor):
284
291
  else:
285
292
  collection.append(code_item)
286
293
 
294
+ def _is_directly_in_class_body(self) -> bool:
295
+ return bool(self.scope_kinds) and self.scope_kinds[-1] == 'class'
296
+
287
297
  def _define_variable(self, name: str, node: ast.AST) -> None:
298
+ if self.args.ignore_class_attributes and self._is_directly_in_class_body():
299
+ return
300
+
288
301
  self._define(
289
302
  self.defined_vars,
290
303
  name,
@@ -301,6 +314,8 @@ class DeadCodeVisitor(ast.NodeVisitor):
301
314
 
302
315
  def visit_Attribute(self, node: ast.Attribute) -> None:
303
316
  if isinstance(node.ctx, ast.Store):
317
+ if self.args.ignore_non_self_attributes and not _is_self_attribute(node):
318
+ return
304
319
  self._define(self.defined_attrs, node.attr, node)
305
320
  elif isinstance(node.ctx, ast.Load):
306
321
  self.add_used_name(node.attr)
@@ -502,8 +517,10 @@ class DeadCodeVisitor(ast.NodeVisitor):
502
517
  was_scope_increased = True
503
518
  if isinstance(node, ast.ClassDef):
504
519
  self.scope_parts.append(node.name)
520
+ self.scope_kinds.append('class')
505
521
  elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
506
522
  self.scope_parts.append(node.name)
523
+ self.scope_kinds.append('function')
507
524
  else:
508
525
  was_scope_increased = False
509
526
 
@@ -541,6 +558,7 @@ class DeadCodeVisitor(ast.NodeVisitor):
541
558
  # TODO: use decorator for this code chunk
542
559
  if was_scope_increased:
543
560
  self.scope_parts.pop()
561
+ self.scope_kinds.pop()
544
562
 
545
563
  def _handle_ast_list(self, ast_list: List[ast.AST]) -> None:
546
564
  """
@@ -123,6 +123,12 @@ def _ignore_method(filename: Path, method_name: str) -> bool:
123
123
  )
124
124
 
125
125
 
126
+ def _is_self_attribute(node: ast.Attribute) -> bool:
127
+ """Whether an attribute assignment target is `self.attr` (as opposed to
128
+ e.g. `foo.attr`, where `foo` is some other object)."""
129
+ return isinstance(node.value, ast.Name) and node.value.id == 'self'
130
+
131
+
126
132
  def _ignore_variable(filename: Path, varname: str) -> bool:
127
133
  """
128
134
  Ignore _ (Python idiom), _x (pylint convention) and
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lapsed"
3
- version = "2.4.2"
3
+ version = "2.6.0"
4
4
  authors = [
5
5
  {name = "Albertas Gimbutas", email = "albertasgim@gmail.com"},
6
6
  ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes