🙌

Goの循環的複雑度を測るツールを作ってみた

2022/08/18に公開

前書き

エキスパートたちのGo言語を読んでるときになんとなく作れそうだったので作ってみた。コマンドラインアプリとして実装した。

実装

  1. コマンドライン引数を取得する
  2. Goのソースコードから抽象構文木を取り出す
  3. 抽象構文木からif文とswitch文とfor文の数をカウントする

1. コマンドライン引数を取得する

flagパッケージを用いてコマンドライン引数を取得した。
抽象構文木を取り出すGoのプログラムが書かれたファイルを指定する。

filename := flag.String("i", "./sample.go", "file name")
flag.Parse()

_, err := os.Stat(*filename)
if err != nil {
	fmt.Println(*filename, err)
	return
}

2. Goのソースコードから抽象構文木を取り出す

このコードで抽象構文木が取得できる。astDataに格納される。

fset := token.NewFileSet()
astData, err := parser.ParseFile(fset, *filename, nil, parser.Mode(0))
if err != nil {
	fmt.Printf("get ast missed. err: %v\n", err)
	return
}

取得した抽象構文木を出力するとこのように出力される。

取り出した抽象構文木を出力した結果

0 *ast.FuncDecl {
1 . Name: *ast.Ident {
2 . . NamePos: ./sample.go:3:6
3 . . Name: "BubbleSort"
4 . . Obj: *ast.Object {
5 . . . Kind: func
6 . . . Name: "BubbleSort"
7 . . . Decl: *(obj @ 0)
8 . . }
9 . }
10 . Type: *ast.FuncType {
11 . . Func: ./sample.go:3:1
12 . . Params: *ast.FieldList {
13 . . . Opening: ./sample.go:3:16
14 . . . List: []*ast.Field (len = 1) {
15 . . . . 0: *ast.Field {
16 . . . . . Names: []*ast.Ident (len = 1) {
17 . . . . . . 0: *ast.Ident {
18 . . . . . . . NamePos: ./sample.go:3:17
19 . . . . . . . Name: "l"
20 . . . . . . . Obj: *ast.Object {
21 . . . . . . . . Kind: var
22 . . . . . . . . Name: "l"
23 . . . . . . . . Decl: *(obj @ 15)
24 . . . . . . . }
25 . . . . . . }
26 . . . . . }
27 . . . . . Type: *ast.ArrayType {
28 . . . . . . Lbrack: ./sample.go:3:19
29 . . . . . . Elt: *ast.Ident {
30 . . . . . . . NamePos: ./sample.go:3:21
31 . . . . . . . Name: "int"
32 . . . . . . }
33 . . . . . }
34 . . . . }
35 . . . }
36 . . . Closing: ./sample.go:3:24
37 . . }
38 . . Results: *ast.FieldList {
39 . . . Opening: -
40 . . . List: []*ast.Field (len = 1) {
41 . . . . 0: *ast.Field {
42 . . . . . Type: *ast.ArrayType {
43 . . . . . . Lbrack: ./sample.go:3:26
44 . . . . . . Elt: *ast.Ident {
45 . . . . . . . NamePos: ./sample.go:3:28
46 . . . . . . . Name: "int"
47 . . . . . . }
48 . . . . . }
49 . . . . }
50 . . . }
51 . . . Closing: -
52 . . }
53 . }
54 . Body: *ast.BlockStmt {
55 . . Lbrace: ./sample.go:3:32
56 . . List: []ast.Stmt (len = 2) {
57 . . . 0: *ast.ForStmt {
58 . . . . For: ./sample.go:4:2
59 . . . . Init: *ast.AssignStmt {
60 . . . . . Lhs: []ast.Expr (len = 1) {
61 . . . . . . 0: *ast.Ident {
62 . . . . . . . NamePos: ./sample.go:4:6
63 . . . . . . . Name: "i"
64 . . . . . . . Obj: *ast.Object {
65 . . . . . . . . Kind: var
66 . . . . . . . . Name: "i"
67 . . . . . . . . Decl: *(obj @ 59)
68 . . . . . . . }
69 . . . . . . }
70 . . . . . }
71 . . . . . TokPos: ./sample.go:4:8
72 . . . . . Tok: :=
73 . . . . . Rhs: []ast.Expr (len = 1) {
74 . . . . . . 0: *ast.BasicLit {
75 . . . . . . . ValuePos: ./sample.go:4:11
76 . . . . . . . Kind: INT
77 . . . . . . . Value: "0"
78 . . . . . . }
79 . . . . . }
80 . . . . }
81 . . . . Cond: *ast.BinaryExpr {
82 . . . . . X: *ast.Ident {
83 . . . . . . NamePos: ./sample.go:4:14
84 . . . . . . Name: "i"
85 . . . . . . Obj: *(obj @ 64)
86 . . . . . }
87 . . . . . OpPos: ./sample.go:4:16
88 . . . . . Op: <
89 . . . . . Y: *ast.BinaryExpr {
90 . . . . . . X: *ast.CallExpr {
91 . . . . . . . Fun: *ast.Ident {
92 . . . . . . . . NamePos: ./sample.go:4:18
93 . . . . . . . . Name: "len"
94 . . . . . . . }
95 . . . . . . . Lparen: ./sample.go:4:21
96 . . . . . . . Args: []ast.Expr (len = 1) {
97 . . . . . . . . 0: *ast.Ident {
98 . . . . . . . . . NamePos: ./sample.go:4:22
99 . . . . . . . . . Name: "l"
100 . . . . . . . . . Obj: *(obj @ 20)
101 . . . . . . . . }
102 . . . . . . . }
103 . . . . . . . Ellipsis: -
104 . . . . . . . Rparen: ./sample.go:4:23
105 . . . . . . }
106 . . . . . . OpPos: ./sample.go:4:24
107 . . . . . . Op: -
108 . . . . . . Y: *ast.BasicLit {
109 . . . . . . . ValuePos: ./sample.go:4:25
110 . . . . . . . Kind: INT
111 . . . . . . . Value: "1"
112 . . . . . . }
113 . . . . . }
114 . . . . }
115 . . . . Post: *ast.IncDecStmt {
116 . . . . . X: *ast.Ident {
117 . . . . . . NamePos: ./sample.go:4:28
118 . . . . . . Name: "i"
119 . . . . . . Obj: *(obj @ 64)
120 . . . . . }
121 . . . . . TokPos: ./sample.go:4:29
122 . . . . . Tok: ++
123 . . . . }
124 . . . . Body: *ast.BlockStmt {
125 . . . . . Lbrace: ./sample.go:4:32
126 . . . . . List: []ast.Stmt (len = 1) {
127 . . . . . . 0: *ast.ForStmt {
128 . . . . . . . For: ./sample.go:5:3
129 . . . . . . . Init: *ast.AssignStmt {
130 . . . . . . . . Lhs: []ast.Expr (len = 1) {
131 . . . . . . . . . 0: *ast.Ident {
132 . . . . . . . . . . NamePos: ./sample.go:5:7
133 . . . . . . . . . . Name: "j"
134 . . . . . . . . . . Obj: *ast.Object {
135 . . . . . . . . . . . Kind: var
136 . . . . . . . . . . . Name: "j"
137 . . . . . . . . . . . Decl: *(obj @ 129)
138 . . . . . . . . . . }
139 . . . . . . . . . }
140 . . . . . . . . }
141 . . . . . . . . TokPos: ./sample.go:5:9
142 . . . . . . . . Tok: :=
143 . . . . . . . . Rhs: []ast.Expr (len = 1) {
144 . . . . . . . . . 0: *ast.BasicLit {
145 . . . . . . . . . . ValuePos: ./sample.go:5:12
146 . . . . . . . . . . Kind: INT
147 . . . . . . . . . . Value: "0"
148 . . . . . . . . . }
149 . . . . . . . . }
150 . . . . . . . }
151 . . . . . . . Cond: *ast.BinaryExpr {
152 . . . . . . . . X: *ast.Ident {
153 . . . . . . . . . NamePos: ./sample.go:5:15
154 . . . . . . . . . Name: "j"
155 . . . . . . . . . Obj: *(obj @ 134)
156 . . . . . . . . }
157 . . . . . . . . OpPos: ./sample.go:5:17
158 . . . . . . . . Op: <
159 . . . . . . . . Y: *ast.CallExpr {
160 . . . . . . . . . Fun: *ast.Ident {
161 . . . . . . . . . . NamePos: ./sample.go:5:19
162 . . . . . . . . . . Name: "len"
163 . . . . . . . . . }
164 . . . . . . . . . Lparen: ./sample.go:5:22
165 . . . . . . . . . Args: []ast.Expr (len = 1) {
166 . . . . . . . . . . 0: *ast.Ident {
167 . . . . . . . . . . . NamePos: ./sample.go:5:23
168 . . . . . . . . . . . Name: "l"
169 . . . . . . . . . . . Obj: *(obj @ 20)
170 . . . . . . . . . . }
171 . . . . . . . . . }
172 . . . . . . . . . Ellipsis: -
173 . . . . . . . . . Rparen: ./sample.go:5:24
174 . . . . . . . . }
175 . . . . . . . }
176 . . . . . . . Post: *ast.IncDecStmt {
177 . . . . . . . . X: *ast.Ident {
178 . . . . . . . . . NamePos: ./sample.go:5:27
179 . . . . . . . . . Name: "j"
180 . . . . . . . . . Obj: *(obj @ 134)
181 . . . . . . . . }
182 . . . . . . . . TokPos: ./sample.go:5:28
183 . . . . . . . . Tok: ++
184 . . . . . . . }
185 . . . . . . . Body: *ast.BlockStmt {
186 . . . . . . . . Lbrace: ./sample.go:5:31
187 . . . . . . . . List: []ast.Stmt (len = 1) {
188 . . . . . . . . . 0: *ast.IfStmt {
189 . . . . . . . . . . If: ./sample.go:6:4
190 . . . . . . . . . . Cond: *ast.BinaryExpr {
191 . . . . . . . . . . . X: *ast.IndexExpr {
192 . . . . . . . . . . . . X: *ast.Ident {
193 . . . . . . . . . . . . . NamePos: ./sample.go:6:7
194 . . . . . . . . . . . . . Name: "l"
195 . . . . . . . . . . . . . Obj: *(obj @ 20)
196 . . . . . . . . . . . . }
197 . . . . . . . . . . . . Lbrack: ./sample.go:6:8
198 . . . . . . . . . . . . Index: *ast.Ident {
199 . . . . . . . . . . . . . NamePos: ./sample.go:6:9
200 . . . . . . . . . . . . . Name: "i"
201 . . . . . . . . . . . . . Obj: *(obj @ 64)
202 . . . . . . . . . . . . }
203 . . . . . . . . . . . . Rbrack: ./sample.go:6:10
204 . . . . . . . . . . . }
205 . . . . . . . . . . . OpPos: ./sample.go:6:12
206 . . . . . . . . . . . Op: >
207 . . . . . . . . . . . Y: *ast.IndexExpr {
208 . . . . . . . . . . . . X: *ast.Ident {
209 . . . . . . . . . . . . . NamePos: ./sample.go:6:14
210 . . . . . . . . . . . . . Name: "l"
211 . . . . . . . . . . . . . Obj: *(obj @ 20)
212 . . . . . . . . . . . . }
213 . . . . . . . . . . . . Lbrack: ./sample.go:6:15
214 . . . . . . . . . . . . Index: *ast.Ident {
215 . . . . . . . . . . . . . NamePos: ./sample.go:6:16
216 . . . . . . . . . . . . . Name: "j"
217 . . . . . . . . . . . . . Obj: *(obj @ 134)
218 . . . . . . . . . . . . }
219 . . . . . . . . . . . . Rbrack: ./sample.go:6:17
220 . . . . . . . . . . . }
221 . . . . . . . . . . }
222 . . . . . . . . . . Body: *ast.BlockStmt {
223 . . . . . . . . . . . Lbrace: ./sample.go:6:19
224 . . . . . . . . . . . List: []ast.Stmt (len = 1) {
225 . . . . . . . . . . . . 0: *ast.AssignStmt {
226 . . . . . . . . . . . . . Lhs: []ast.Expr (len = 2) {
227 . . . . . . . . . . . . . . 0: *ast.IndexExpr {
228 . . . . . . . . . . . . . . . X: *ast.Ident {
229 . . . . . . . . . . . . . . . . NamePos: ./sample.go:7:5
230 . . . . . . . . . . . . . . . . Name: "l"
231 . . . . . . . . . . . . . . . . Obj: *(obj @ 20)
232 . . . . . . . . . . . . . . . }
233 . . . . . . . . . . . . . . . Lbrack: ./sample.go:7:6
234 . . . . . . . . . . . . . . . Index: *ast.Ident {
235 . . . . . . . . . . . . . . . . NamePos: ./sample.go:7:7
236 . . . . . . . . . . . . . . . . Name: "i"
237 . . . . . . . . . . . . . . . . Obj: *(obj @ 64)
238 . . . . . . . . . . . . . . . }
239 . . . . . . . . . . . . . . . Rbrack: ./sample.go:7:8
240 . . . . . . . . . . . . . . }
241 . . . . . . . . . . . . . . 1: *ast.IndexExpr {
242 . . . . . . . . . . . . . . . X: *ast.Ident {
243 . . . . . . . . . . . . . . . . NamePos: ./sample.go:7:11
244 . . . . . . . . . . . . . . . . Name: "l"
245 . . . . . . . . . . . . . . . . Obj: *(obj @ 20)
246 . . . . . . . . . . . . . . . }
247 . . . . . . . . . . . . . . . Lbrack: ./sample.go:7:12
248 . . . . . . . . . . . . . . . Index: *ast.Ident {
249 . . . . . . . . . . . . . . . . NamePos: ./sample.go:7:13
250 . . . . . . . . . . . . . . . . Name: "j"
251 . . . . . . . . . . . . . . . . Obj: *(obj @ 134)
252 . . . . . . . . . . . . . . . }
253 . . . . . . . . . . . . . . . Rbrack: ./sample.go:7:14
254 . . . . . . . . . . . . . . }
255 . . . . . . . . . . . . . }
256 . . . . . . . . . . . . . TokPos: ./sample.go:7:16
257 . . . . . . . . . . . . . Tok: =
258 . . . . . . . . . . . . . Rhs: []ast.Expr (len = 2) {
259 . . . . . . . . . . . . . . 0: *ast.IndexExpr {
260 . . . . . . . . . . . . . . . X: *ast.Ident {
261 . . . . . . . . . . . . . . . . NamePos: ./sample.go:7:18
262 . . . . . . . . . . . . . . . . Name: "l"
263 . . . . . . . . . . . . . . . . Obj: *(obj @ 20)
264 . . . . . . . . . . . . . . . }
265 . . . . . . . . . . . . . . . Lbrack: ./sample.go:7:19
266 . . . . . . . . . . . . . . . Index: *ast.Ident {
267 . . . . . . . . . . . . . . . . NamePos: ./sample.go:7:20
268 . . . . . . . . . . . . . . . . Name: "j"
269 . . . . . . . . . . . . . . . . Obj: *(obj @ 134)
270 . . . . . . . . . . . . . . . }
271 . . . . . . . . . . . . . . . Rbrack: ./sample.go:7:21
272 . . . . . . . . . . . . . . }
273 . . . . . . . . . . . . . . 1: *ast.IndexExpr {
274 . . . . . . . . . . . . . . . X: *ast.Ident {
275 . . . . . . . . . . . . . . . . NamePos: ./sample.go:7:24
276 . . . . . . . . . . . . . . . . Name: "l"
277 . . . . . . . . . . . . . . . . Obj: *(obj @ 20)
278 . . . . . . . . . . . . . . . }
279 . . . . . . . . . . . . . . . Lbrack: ./sample.go:7:25
280 . . . . . . . . . . . . . . . Index: *ast.Ident {
281 . . . . . . . . . . . . . . . . NamePos: ./sample.go:7:26
282 . . . . . . . . . . . . . . . . Name: "i"
283 . . . . . . . . . . . . . . . . Obj: *(obj @ 64)
284 . . . . . . . . . . . . . . . }
285 . . . . . . . . . . . . . . . Rbrack: ./sample.go:7:27
286 . . . . . . . . . . . . . . }
287 . . . . . . . . . . . . . }
288 . . . . . . . . . . . . }
289 . . . . . . . . . . . }
290 . . . . . . . . . . . Rbrace: ./sample.go:8:4
291 . . . . . . . . . . }
292 . . . . . . . . . }
293 . . . . . . . . }
294 . . . . . . . . Rbrace: ./sample.go:9:3
295 . . . . . . . }
296 . . . . . . }
297 . . . . . }
298 . . . . . Rbrace: ./sample.go:10:2
299 . . . . }
300 . . . }
301 . . . 1: *ast.ReturnStmt {
302 . . . . Return: ./sample.go:11:2
303 . . . . Results: []ast.Expr (len = 1) {
304 . . . . . 0: *ast.Ident {
305 . . . . . . NamePos: ./sample.go:11:9
306 . . . . . . Name: "l"
307 . . . . . . Obj: *(obj @ 20)
308 . . . . . }
309 . . . . }
310 . . . }
311 . . }
312 . . Rbrace: ./sample.go:12:1
313 . }
314 }

元のソースコード(バブルソート)

※以下のソースコードはエキスパートたちのGo言語内にあるものを写経したもの。

package main

func BubbleSort(l []int) []int {
	for i := 0; i < len(l)-1; i++ {
		for j := 0; j < len(l); j++ {
			if l[i] > l[j] {
				l[i], l[j] = l[j], l[i]
			}
		}
	}
	return l
}

3. 抽象構文木からif文とswitch文の数をカウントする

cc := 1
ast.Inspect(astData, func(n ast.Node) bool {
	switch n.(type) {
	case *ast.IfStmt:
		cc++
	case *ast.ForStmt:
		cc++
	case *ast.SwitchStmt:
		cc++
	}
	return true
})

感想

select文はどういう扱いにするのがよかったのだろうか...

ソースコード

以下Githubのリンク
https://github.com/ystsbry/cc-checker

Discussion