Coverage for dead_code / parsers / go.py: 68%
130 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"""Go dead code parser."""
3from __future__ import annotations
5from tree_sitter import Node
7from parsers.go import GO_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 GoDeadCodeParser(DeadCodeParser):
19 def extract_imports(self, root, source):
20 query = create_query(GO_LANGUAGE, "(import_declaration) @import_decl")
21 captures = run_captures(query, root)
22 imports = []
23 for decl_node, _ in captures:
24 self._process_import_declaration(decl_node, imports)
25 return imports
27 def _process_import_declaration(self, decl_node, imports):
28 for child in decl_node.children:
29 if child.type == "import_spec_list":
30 self._process_import_spec_list(child, imports)
31 return
32 elif child.type == "import_spec":
33 self._process_import_spec(child, imports)
34 return
36 def _process_import_spec_list(self, list_node, imports):
37 for child in list_node.children:
38 if child.type == "import_spec":
39 self._process_import_spec(child, imports)
41 def _process_import_spec(self, spec_node, imports):
42 path = None
43 has_blank_identifier = False
44 has_dot_import = False
45 for child in spec_node.children:
46 if child.type == "blank_identifier":
47 has_blank_identifier = True
48 elif child.type == "dot":
49 has_dot_import = True
50 elif child.type == "interpreted_string_literal":
51 path = child.text.decode().strip('"')
52 if has_blank_identifier or has_dot_import:
53 return
54 pkg_name = (
55 path.split("/")[-1] if path and "/" in path else (path if path else "")
56 )
57 if pkg_name and path:
58 imports.append(
59 ImportedSymbol(
60 name=pkg_name,
61 line=spec_node.start_point[0] + 1,
62 imported_from=path,
63 alias=None,
64 )
65 )
67 def extract_definitions(self, root, source):
68 defs = []
69 query_str = "(function_declaration name: (identifier) @func_name)"
70 try:
71 query = create_query(GO_LANGUAGE, query_str)
72 captures = run_captures(query, root)
73 for node, name in captures:
74 if name == "func_name" and node.text:
75 func_name = node.text.decode()
76 is_exported = bool(func_name) and func_name[0].isupper()
77 defs.append(
78 SymbolDefinition(
79 func_name, node.start_point[0] + 1, "function", is_exported
80 )
81 )
82 except Exception:
83 pass
85 query_str = "(method_declaration name: (field_identifier) @method_name)"
86 try:
87 query = create_query(GO_LANGUAGE, query_str)
88 captures = run_captures(query, root)
89 for node, name in captures:
90 if name == "method_name" and node.text:
91 method_decl = node.parent
92 receiver_type = ""
93 if method_decl and method_decl.child_count >= 2:
94 param_list = method_decl.children[1]
95 if param_list and param_list.type == "parameter_list":
96 for child in param_list.children:
97 if child.type == "parameter_declaration":
98 identifiers = [
99 c
100 for c in child.children
101 if c.type in ("identifier", "type_identifier")
102 ]
103 if len(identifiers) >= 2:
104 receiver_type = (
105 identifiers[1].text.decode()
106 if identifiers[1].text
107 else ""
108 )
109 break
110 prefix = f"{receiver_type}." if receiver_type else ""
111 full_name = f"{prefix}{node.text.decode()}"
112 method_name = node.text.decode()
113 is_exported = bool(method_name) and method_name[0].isupper()
114 defs.append(
115 SymbolDefinition(
116 full_name, node.start_point[0] + 1, "function", is_exported
117 )
118 )
119 except Exception:
120 pass
121 return defs
123 def extract_abstract_info(self, root, source):
124 return set(), set()
126 def extract_variable_definitions(self, root, source):
127 result = {}
128 query_str = "(var_declaration (var_spec name: (identifier) @var_name))"
129 query = create_query(GO_LANGUAGE, query_str)
130 captures = run_captures(query, root)
131 source_lines = source.split("\n")
132 for node, capture_name in captures:
133 if capture_name == "var_name":
134 text = node.text.decode() if node.text else ""
135 scope = self._find_enclosing_scope(node, source_lines)
136 if scope is None:
137 continue
138 if scope not in result:
139 result[scope] = []
140 result[scope].append(
141 VariableDefinition(
142 name=text,
143 line=node.start_point[0] + 1,
144 scope_type="local",
145 definition_type="variable",
146 )
147 )
148 return result
150 def extract_references(self, root, source):
151 query = create_query(GO_LANGUAGE, "(identifier) @usage_ref")
152 captures = run_captures(query, root)
153 refs = []
154 for node, name in captures:
155 if name == "usage_ref" and node.text:
156 refs.append(
157 SymbolReference(node.text.decode(), node.start_point[0] + 1)
158 )
159 return refs
161 def extract_scope_references(self, root, source):
162 query = create_query(GO_LANGUAGE, "(identifier) @usage_ref")
163 captures = run_captures(query, root)
164 result = {}
165 source_lines = source.split("\n")
166 for node, name in captures:
167 if name == "usage_ref" and node.text:
168 scope = self._find_enclosing_scope(node, source_lines)
169 if scope is None:
170 continue
171 if scope not in result:
172 result[scope] = []
173 result[scope].append(
174 SymbolReference(
175 name=node.text.decode(),
176 line=node.start_point[0] + 1,
177 )
178 )
179 return result
181 def _find_enclosing_scope(self, node, source_lines):
182 if node is None:
183 return None
184 parent = node.parent
185 while parent:
186 if parent.type in ("function_declaration", "func_lit", "block"):
187 return (None, parent.start_point[0] + 1)
188 parent = parent.parent
189 return None
191 def is_exported(self, name):
192 return bool(name) and name[0].isupper()