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

1"""Go dead code parser.""" 

2 

3from __future__ import annotations 

4 

5from tree_sitter import Node 

6 

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) 

16 

17 

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 

26 

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 

35 

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) 

40 

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 ) 

66 

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 

84 

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 

122 

123 def extract_abstract_info(self, root, source): 

124 return set(), set() 

125 

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 

149 

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 

160 

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 

180 

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 

190 

191 def is_exported(self, name): 

192 return bool(name) and name[0].isupper()