Coverage for dead_code/parsers/go.py: 68%

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

2

3 from __future__ import annotations

4

5 from tree_sitter import Node

6

7 from parsers.go import GO_LANGUAGE

8 from patterns.common import create_query, run_captures

9 from dead_code.parsers.base import (

10 SymbolDefinition,

11 ImportedSymbol,

12 VariableDefinition,

13 SymbolReference,

14 DeadCodeParser,

15)

16

17

18 class 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()