Coverage for dead_code / parsers / python.py: 54%
87 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
1"""Python dead code parser."""
3from __future__ import annotations
5from tree_sitter import Node
7from parsers.python import PY_LANGUAGE
8from patterns.common import create_query, run_captures
9from dead_code.parsers.base import (
10 SymbolDefinition,
11 ImportedSymbol,
12 VariableDefinition,
13 SymbolReference,
14 DeadCodeParser,
15)
18class PythonDeadCodeParser(DeadCodeParser):
19 def extract_imports(self, root: Node, source: str) -> list[ImportedSymbol]:
20 queries = [
21 "(import_statement (dotted_name (identifier) @import_name))",
22 "(import_statement (aliased_import name: (dotted_name (identifier) @import_name) alias: (identifier) @import_alias))",
23 "(import_from_statement name: (dotted_name (identifier) @import_name))",
24 "(import_from_statement (aliased_import name: (identifier) @import_name alias: (identifier) @import_alias))",
25 ]
27 imports = []
28 for query_str in queries:
29 try:
30 query = create_query(PY_LANGUAGE, query_str)
31 captures = run_captures(query, root)
33 for node, capture_name in captures:
34 text = node.text.decode() if node.text else ""
35 if capture_name == "import_name":
36 imports.append(
37 ImportedSymbol(
38 name=text,
39 line=node.start_point[0] + 1,
40 imported_from=None,
41 alias=None,
42 )
43 )
44 elif capture_name == "import_alias":
45 if imports:
46 imports[-1].alias = text
47 except Exception:
48 pass
49 return imports
51 def extract_definitions(self, root: Node, source: str) -> list[SymbolDefinition]:
52 query_str = "(function_definition name: (identifier) @func_name) (class_definition name: (identifier) @class_name)"
53 query = create_query(PY_LANGUAGE, query_str)
54 captures = run_captures(query, root)
56 defs = []
57 for node, capture_name in captures:
58 text = node.text.decode() if node.text else ""
59 if capture_name == "func_name":
60 defs.append(SymbolDefinition(text, node.start_point[0] + 1, "function"))
61 elif capture_name == "class_name":
62 defs.append(SymbolDefinition(text, node.start_point[0] + 1, "class"))
63 return defs
65 def extract_abstract_info(
66 self, root: Node, source: str
67 ) -> tuple[set[str], set[str]]:
68 """Extract abstract class names and abstract method names."""
69 abstract_classes = set()
70 abstract_methods = set()
72 # Source lines to method name mapping
73 source_lines = source.split("\n")
75 # Find classes that inherit from ABC
76 query_str = """(class_definition
77 name: (identifier) @class_name
78 (argument_list (identifier) @base_name))"""
80 try:
81 query = create_query(PY_LANGUAGE, query_str)
82 captures = run_captures(query, root)
84 current_class = None
85 for node, capture_name in captures:
86 text = node.text.decode() if node.text else ""
87 if capture_name == "class_name":
88 current_class = text
89 elif capture_name == "base_name" and text in ["ABC", "ABCMeta"]:
90 if current_class:
91 abstract_classes.add(current_class)
92 except Exception:
93 pass
95 # Find methods with @abstractmethod decorator
96 query_str = """(decorator (identifier) @decorator_name)"""
98 try:
99 query = create_query(PY_LANGUAGE, query_str)
100 captures = run_captures(query, root)
102 for node, capture_name in captures:
103 text = node.text.decode() if node.text else ""
104 if capture_name == "decorator_name" and text in [
105 "abstractmethod",
106 "abstractproperty",
107 "abstractclassmethod",
108 "abstractstaticmethod",
109 ]:
110 # Get line number of decorator
111 line_num = node.start_point[0] + 1
112 # Look for the next line for the function name
113 if line_num < len(source_lines):
114 next_line = source_lines[line_num] # +1 due to 0-index
115 match = None
116 if next_line:
117 # Simple logic: find "def " in the line
118 if "def " in next_line:
119 parts = next_line.split("def ")
120 if len(parts) > 1:
121 method_name = parts[1].split("(")[0].strip()
122 if method_name:
123 abstract_methods.add(method_name)
124 except Exception:
125 pass
127 return abstract_classes, abstract_methods
129 def extract_variable_definitions(
130 self, root: Node, source: str
131 ) -> dict[tuple, list[VariableDefinition]]:
132 return {}
134 def extract_references(self, root: Node, source: str) -> list[SymbolReference]:
135 query_str = "(identifier) @usage_ref"
136 query = create_query(PY_LANGUAGE, query_str)
137 captures = run_captures(query, root)
139 refs = []
140 for node, capture_name in captures:
141 if capture_name == "usage_ref" and node.text:
142 refs.append(
143 SymbolReference(node.text.decode(), node.start_point[0] + 1)
144 )
145 return refs
147 def extract_scope_references(
148 self, root: Node, source: str
149 ) -> dict[tuple, list[SymbolReference]]:
150 return {}