Coverage for tests/test_dead_code_go.py: 100%

1"""Tests for dead code detection in Go."""

2

3 from __future__ import annotations

4

5 from pathlib import Path

6

7 from parsers.go import parse

8 from dead_code.parsers.go import GoDeadCodeParser

9 from dead_code.core import find_unused_imports, build_cross_reference_graph

10

11

12 def test_unused_imports_basic():

13 code = """

14 package main

15

16 import (

17 "fmt"

18 "os"

19)

20

21 func main() {

22 fmt.Println("Hello")

23}

24"""

25 parser = GoDeadCodeParser()

26 root = parse(code)

27 imports = parser.extract_imports(root, code)

28

29 import_names = [imp.name for imp in imports]

30 assert "fmt" in import_names

31 assert "os" in import_names

32

33

34 def test_blank_identifier_imports_skipped():

35 code = """

36 package main

37

38 import (

39 _ "github.com/pkg/errors"

40 "fmt"

41)

42

43 func main() {

44 fmt.Println("Hello")

45}

46"""

47 parser = GoDeadCodeParser()

48 root = parse(code)

49 imports = parser.extract_imports(root, code)

50

51 import_names = [imp.name for imp in imports]

52 assert "fmt" in import_names

53 assert "errors" not in import_names # _ identifier is skipped

54

55

56 def test_dot_imports_skipped():

57 code = """

58 package main

59

60 import (

61 . "fmt"

62)

63

64 func main() {

65 Println("Hello")

66}

67"""

68 parser = GoDeadCodeParser()

69 root = parse(code)

70 imports = parser.extract_imports(root, code)

71

72 # Dot imports should be skipped (name shows as 'dot')

73 import_names = [imp.name for imp in imports]

74 assert len(imports) == 0

75

76

77 def test_extract_definitions():

78 code = """

79 package main

80

81 func foo() {

82 bar()

83}

84

85 func bar() {

86}

87"""

88 parser = GoDeadCodeParser()

89 root = parse(code)

90 defs = parser.extract_definitions(root, code)

91

92 assert len(defs) == 2

93 def_names = [d.name for d in defs]

94 assert "foo" in def_names

95 assert "bar" in def_names

96

97

98 def test_extract_method_definitions():

99 code = """

100 package main

101

102 type MyType struct{}

103

104 func (m MyType) Method() {}

105"""

106 parser = GoDeadCodeParser()

107 root = parse(code)

108 defs = parser.extract_definitions(root, code)

109

110 assert len(defs) == 1

111 assert defs[0].name == "MyType.Method"

112

113

114 def test_unused_short_var_declarations():

115 code = """

116 package main

117

118 func foo() {

119 x := 1

120 y := 2

121 return x

122 }

123 """

124 parser = GoDeadCodeParser()

125 root = parse(code)

126 refs = parser.extract_references(root, code)

127 ref_names = [r.name for r in refs]

128

129 assert "x" in ref_names

130 assert "y" in ref_names # y is declared, appears in references

131

132

133 def test_variables_in_loops():

134 code = """

135 package main

136

137 func foo() {

138 for i := 0; i < 10; i++ {

139 println(i)

140 }

141}

142"""

143 parser = GoDeadCodeParser()

144 root = parse(code)

145 refs = parser.extract_references(root, code)

146 ref_names = [r.name for r in refs]

147

148 assert "i" in ref_names # used in loop condition and body

149

150

151 def test_exported_symbols_detection():

152 code = """

153 package main

154

155 func PublicFunc() {}

156

157 func privateFunc() {}

158"""

159 assert GoDeadCodeParser().is_exported("PublicFunc")

160 assert not GoDeadCodeParser().is_exported("privateFunc")

161

162

163 def test_extract_references():

164 code = """

165 package main

166

167 func foo(x int) int {

168 y := x + 1

169 return y

170}

171"""

172 parser = GoDeadCodeParser()

173 root = parse(code)

174 refs = parser.extract_references(root, code)

175

176 ref_names = [r.name for r in refs]

177 assert "x" in ref_names

178 assert "y" in ref_names

179 assert "foo" in ref_names

180

181

182 def test_dead_functions_cross_reference():

183 code1 = """

184 package main

185

186 func Used() {}

187 func Unused() {}

188

189 func main() {

190 Used()

191 }

192 """

193 parser = GoDeadCodeParser()

194 root = parse(code1)

195

196 defs = parser.extract_definitions(root, code1)

197 refs = parser.extract_references(root, code1)

198

199 def_names = [d.name for d in defs]

200 ref_names = [r.name for r in refs]

201

202 assert "Used" in def_names

203 assert "Unused" in def_names

204 assert "Used" in ref_names

205 assert "Unused" in ref_names # Unused appears in declaration

206

207

208 def test_interface_method_implementations():

209 code = """

210 package main

211

212 type MyInterface interface {

213 Method(x int) int

214}

215

216 type MyType struct{}

217

218 func (m MyType) Method(x int) int {

219 return x

220}

221"""

222 parser = GoDeadCodeParser()

223 root = parse(code)

224 defs = parser.extract_definitions(root, code)

225

226 assert len(defs) == 1

227 assert defs[0].name == "MyType.Method"

228

229

230 def test_unused_import_detection_with_cross_ref():

231 code = """

232 package main

233

234 import "fmt"

235

236 func main() {

237 _ = fmt.Sprintf

238}

239"""

240 parser = GoDeadCodeParser()

241 root = parse(code)

242 imports = parser.extract_imports(root, code)

243 refs = parser.extract_references(root, code)

244

245 import_names = [imp.name for imp in imports]

246 ref_names = [r.name for r in refs]

247

248 # fmt is used in _ assignment

249 assert "fmt" in import_names

250 assert "fmt" in ref_names