├── .gitignore
├── .gostyle
├── .travis.yml
├── LICENSE
├── Makefile
├── README.md
├── checkstyle.go
├── checkstyle_test.go
├── gocheckstyle
├── config.example
└── gocheckstyle.go
└── testdata
├── camel_name.go
├── caps_pkg.go
├── fileline.go
├── formated.go
├── functionline.go
├── params_num.go
├── results_num.go
├── underscore_name.go
├── underscore_pkg.go
└── unformated.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 |
--------------------------------------------------------------------------------
/.gostyle:
--------------------------------------------------------------------------------
1 | {
2 | "file_line":500,
3 | "func_line":50,
4 | "params_num":4,
5 | "results_num":3,
6 | "formated": true,
7 | "pkg_name": true,
8 | "camel_name": true,
9 | "ignore":[
10 | "testdata/*",
11 | "temp/*"
12 | ],
13 | "fatal":[
14 | "formated",
15 | "file_line",
16 | "func_line",
17 | "params_num",
18 | "results_num",
19 | "formated",
20 | "pkg_name",
21 | "camel_name"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | branches:
4 | only:
5 | - master
6 | - develop
7 |
8 | script: go test
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 longbai
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | bash -c "mkdir -p temp; cd temp && export GOPATH=`pwd`/temp && go get github.com/qiniu/checkstyle/gocheckstyle"
3 | temp/bin/gocheckstyle -config=.gostyle .
4 | @rm -fr temp
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | go-checkstyle
2 | =============
3 | [](https://travis-ci.org/qiniu/checkstyle)
4 |
5 | checkstyle is a style check tool like java checkstyle. This tool inspired by [java checkstyle](https://github.com/checkstyle/checkstyle), [golint](https://github.com/golang/lint). The style refered to some points in [Go Code Review Comments](https://code.google.com/p/go-wiki/wiki/CodeReviewComments).
6 |
7 | # Install
8 | go get github.com/qiniu/checkstyle/gocheckstyle
9 |
10 | # Run
11 | gocheckstyle -config=.go_style dir1 dir2
12 |
13 | # Config
14 | config is json file like the following:
15 | ```
16 | {
17 | "file_line": 500,
18 | "func_line": 50,
19 | "params_num":4,
20 | "results_num":3,
21 | "formated": true,
22 | "pkg_name": true,
23 | "camel_name":true,
24 | "ignore":[
25 | "a/*",
26 | "b/*/c/*.go"
27 | ],
28 | "fatal":[
29 | "formated"
30 | ]
31 | }
32 |
33 | ```
34 |
35 | # Add to makefile
36 | ```
37 | check_go_style:
38 | bash -c "mkdir -p checkstyle; cd checkstyle && export GOPATH=`pwd` && go get github.com/qiniu/checkstyle/gocheckstyle"
39 | checkstyle/bin/gocheckstyle -config=.go_style dir1 dir2
40 |
41 | ```
42 |
43 | # Integrate with jenkins checkstyle plugin
44 | excute in shell
45 | ```
46 | mkdir -p checkstyle; cd checkstyle && export GOPATH=`pwd` && go get github.com/qiniu/checkstyle/gocheckstyle"
47 | checkstyle/bin/gocheckstyle -reporter=xml -config=.go_style dir1 dir2 2>gostyle.xml
48 | ```
49 | then add postbuild checkstyle file gostyle.xml
50 |
51 | Run checkstyle with one or more filenames or directories. The output of this tool is a list of suggestions. If you need to force obey the rule, place it in fatal.
52 |
53 | # Checkstyle's difference with other tools
54 | Checkstyle differs from gofmt. Gofmt reformats Go source code, whereas checkstyle prints out coding style suggestion.
55 |
56 | Checkstyle differs from golint. Checkstyle check file line/function line/param number, could be configed by user.
57 |
--------------------------------------------------------------------------------
/checkstyle.go:
--------------------------------------------------------------------------------
1 | package checkstyle
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "go/ast"
7 | "go/format"
8 | "go/parser"
9 | "go/token"
10 | "strconv"
11 | "strings"
12 | )
13 |
14 | type ProblemType string
15 |
16 | const (
17 | FileLine ProblemType = "file_line"
18 | FunctionLine ProblemType = "func_line"
19 | ParamsNum ProblemType = "params_num"
20 | ResultsNum ProblemType = "results_num"
21 | Formated ProblemType = "formated"
22 | PackageName ProblemType = "pkg_name"
23 | CamelName ProblemType = "camel_name"
24 | )
25 |
26 | type Problem struct {
27 | Position *token.Position
28 | Description string
29 | // SourceLine string
30 | Type ProblemType
31 | }
32 |
33 | type Checker interface {
34 | Check(fileName string, src []byte) ([]Problem, error)
35 | IsFatal(p *Problem) bool
36 | }
37 |
38 | type checker struct {
39 | FunctionComment bool `json:"func_comment"`
40 | FileLine int `json:"file_line"`
41 | FunctionLine int `json:"func_line"`
42 | MaxIndent int `json:"max_indent"`
43 | Formated bool `json:"formated"`
44 | Fatal []string `json:"fatal"`
45 | ParamsNum int `json:"params_num"`
46 | ResultsNum int `json:"results_num"`
47 | PackageName bool `json:"pkg_name"`
48 | CamelName bool `json:"camel_name"`
49 | }
50 |
51 | func New(config []byte) (Checker, error) {
52 | var _checker checker
53 | err := json.Unmarshal(config, &_checker)
54 | if err != nil {
55 | return nil, err
56 | }
57 | return &_checker, nil
58 | }
59 |
60 | func (c *checker) Check(fileName string, src []byte) (ps []Problem, err error) {
61 | fset := token.NewFileSet()
62 | f, err := parser.ParseFile(fset, fileName, src, parser.ParseComments)
63 | if err != nil {
64 | return nil, err
65 | }
66 | return (&file{fileName, src, c, f, fset, []Problem{}}).check(), nil
67 | }
68 |
69 | func (c *checker) IsFatal(p *Problem) bool {
70 | for _, v := range c.Fatal {
71 | if v == string(p.Type) {
72 | return true
73 | }
74 | }
75 | return false
76 | }
77 |
78 | type file struct {
79 | fileName string
80 | src []byte
81 |
82 | config *checker
83 |
84 | ast *ast.File
85 | fset *token.FileSet
86 |
87 | problems []Problem
88 | }
89 |
90 | func (f *file) isTest() bool {
91 | return strings.HasSuffix(f.fileName, "_test.go")
92 | }
93 |
94 | func (f *file) check() (ps []Problem) {
95 | f.checkFormat()
96 | f.checkFileLine()
97 | f.checkFileContent()
98 | return f.problems
99 | }
100 |
101 | func (f *file) checkFormat() {
102 | if !f.config.Formated {
103 | return
104 | }
105 | src, err := format.Source(f.src)
106 | if err != nil {
107 | panic(f.fileName + err.Error())
108 | }
109 | if len(src) != len(f.src) || bytes.Compare(src, f.src) != 0 {
110 | desc := "source is not formated"
111 | pos := f.fset.Position(f.ast.Pos())
112 | problem := Problem{Description: desc, Position: &pos, Type: Formated}
113 | f.problems = append(f.problems, problem)
114 | }
115 | }
116 |
117 | func (f *file) checkFileLine() {
118 | if f.isTest() {
119 | return
120 | }
121 |
122 | lineLimit := f.config.FileLine
123 | if lineLimit == 0 {
124 | return
125 | }
126 |
127 | f.fset.Iterate(func(_file *token.File) bool {
128 | lineCount := _file.LineCount()
129 | if lineCount > lineLimit {
130 | desc := strconv.Itoa(lineCount) + " lines more than " + strconv.Itoa(lineLimit)
131 | pos := f.fset.Position(f.ast.End())
132 | problem := Problem{Description: desc, Position: &pos, Type: FileLine}
133 | f.problems = append(f.problems, problem)
134 | }
135 | return true
136 | })
137 | }
138 |
139 | func genFuncLineProblem(name string, lineCount, lineLimit int, start token.Position) Problem {
140 | desc := "func " + name + "() body lines num " + strconv.Itoa(lineCount) +
141 | " more than " + strconv.Itoa(lineLimit)
142 | return Problem{Description: desc, Position: &start, Type: FunctionLine}
143 | }
144 |
145 | func genParamsNumProblem(name string, paramsNum, limit int, start token.Position) Problem {
146 | desc := "func " + name + "() params num " + strconv.Itoa(paramsNum) +
147 | " more than " + strconv.Itoa(limit)
148 | return Problem{Description: desc, Position: &start, Type: ParamsNum}
149 | }
150 |
151 | func genResultsNumProblem(name string, resultsNum, limit int, start token.Position) Problem {
152 | desc := "func " + name + "() results num " + strconv.Itoa(resultsNum) +
153 | " more than " + strconv.Itoa(limit)
154 | return Problem{Description: desc, Position: &start, Type: ResultsNum}
155 | }
156 |
157 | func genFuncBodyProblem(name string, start token.Position) Problem {
158 | desc := "func " + name + " expected block '{}'"
159 | return Problem{Description: desc, Position: &start, Type: ResultsNum}
160 | }
161 |
162 | func (f *file) checkPkgName(pkg *ast.Ident) {
163 | //ref "http://golang.org/doc/effective_go.html#package-names"
164 | pkgName := pkg.Name
165 | var desc string
166 | if strings.Contains(pkgName, "_") {
167 | suggestName := strings.Replace(pkgName, "_", "/", -1)
168 | desc = "don't use an underscore in package name, " + pkgName + " should be " + suggestName
169 | } else if strings.ToLower(pkgName) != pkgName {
170 | desc = "don't use capital letters in package name: " + pkgName
171 | }
172 | if desc != "" {
173 | start := f.fset.Position(pkg.Pos())
174 | problem := Problem{Description: desc, Position: &start, Type: PackageName}
175 | f.problems = append(f.problems, problem)
176 | }
177 | }
178 |
179 | func (f *file) checkFunctionParams(fType *ast.FuncType, funcName string) {
180 | paramsNumLimit := f.config.ParamsNum
181 | resultsNumLimit := f.config.ResultsNum
182 | params := fType.Params
183 | if params != nil {
184 | if paramsNumLimit != 0 && params.NumFields() > paramsNumLimit {
185 | start := f.fset.Position(params.Pos())
186 | problem := genParamsNumProblem(funcName, params.NumFields(), paramsNumLimit, start)
187 | f.problems = append(f.problems, problem)
188 | }
189 | for _, v := range params.List {
190 | for _, pName := range v.Names {
191 | f.checkName(pName, "param", true)
192 | }
193 | }
194 | }
195 |
196 | results := fType.Results
197 | if results != nil {
198 | if resultsNumLimit != 0 && results != nil && results.NumFields() > resultsNumLimit {
199 | start := f.fset.Position(results.Pos())
200 | problem := genResultsNumProblem(funcName, results.NumFields(), resultsNumLimit, start)
201 | f.problems = append(f.problems, problem)
202 | }
203 |
204 | for _, v := range results.List {
205 | for _, rName := range v.Names {
206 | f.checkName(rName, "return param", true)
207 | }
208 | }
209 | }
210 | }
211 |
212 | func (f *file) checkFunctionLine(funcDecl *ast.FuncDecl) {
213 | lineLimit := f.config.FunctionLine
214 | if lineLimit <= 0 {
215 | return
216 | }
217 | start := f.fset.Position(funcDecl.Pos())
218 |
219 | startLine := start.Line
220 | endLine := f.fset.Position(funcDecl.End()).Line
221 | lineCount := endLine - startLine
222 | if lineCount > lineLimit {
223 | problem := genFuncLineProblem(funcDecl.Name.Name, lineCount, lineLimit, start)
224 | f.problems = append(f.problems, problem)
225 | }
226 | }
227 |
228 | func (f *file) checkFunctionBody(funcDecl *ast.FuncDecl) {
229 | if funcDecl.Body != nil {
230 | return
231 | }
232 | start := f.fset.Position(funcDecl.Pos())
233 | problem := genFuncBodyProblem(funcDecl.Name.Name, start)
234 | f.problems = append(f.problems, problem)
235 | }
236 |
237 | func (f *file) checkFunctionDeclare(funcDecl *ast.FuncDecl) {
238 | f.checkFunctionLine(funcDecl)
239 | f.checkName(funcDecl.Name, "func", false)
240 | f.checkFunctionParams(funcDecl.Type, funcDecl.Name.Name)
241 | f.checkFunctionBody(funcDecl)
242 | receiver := funcDecl.Recv
243 | if receiver != nil && len(receiver.List) != 0 && len(receiver.List[0].Names) != 0 {
244 | f.checkName(receiver.List[0].Names[0], "receiver", true)
245 | }
246 | }
247 |
248 | func trimUnderscorePrefix(name string) string {
249 | if name[0] == '_' {
250 | return name[1:]
251 | }
252 | return name
253 | }
254 |
255 | func (f *file) checkName(id *ast.Ident, kind string, notFirstCap bool) {
256 | if !f.config.CamelName {
257 | return
258 | }
259 | name := trimUnderscorePrefix(id.Name)
260 | if name == "" {
261 | return
262 | }
263 | start := f.fset.Position(id.Pos())
264 |
265 | if strings.Contains(name, "_") {
266 | desc := "don't use non-prefix underscores in " + kind + " name: " + id.Name + ", please use camel name"
267 | problem := Problem{Description: desc, Position: &start, Type: CamelName}
268 | f.problems = append(f.problems, problem)
269 | } else if len(name) >= 5 && strings.ToUpper(name) == name {
270 | desc := "don't use all captial letters in " + kind + " name: " + id.Name + ", please use camel name"
271 | problem := Problem{Description: desc, Position: &start, Type: CamelName}
272 | f.problems = append(f.problems, problem)
273 | } else if notFirstCap && name[0:1] == strings.ToUpper(name[0:1]) {
274 | desc := "in function ,don't use first captial letter in " + kind + " name: " + id.Name + ", please use small letter"
275 | problem := Problem{Description: desc, Position: &start, Type: CamelName}
276 | f.problems = append(f.problems, problem)
277 | }
278 | }
279 |
280 | func (f *file) checkStruct(st *ast.StructType) {
281 | if st.Fields == nil {
282 | return
283 | }
284 | for _, v := range st.Fields.List {
285 | for _, v2 := range v.Names {
286 | f.checkName(v2, "struct field", false)
287 | }
288 | }
289 | }
290 |
291 | func (f *file) checkInterface(it *ast.InterfaceType) {
292 | if it.Methods == nil {
293 | return
294 | }
295 | for _, v := range it.Methods.List {
296 | for _, v2 := range v.Names {
297 | f.checkName(v2, "interface method", false)
298 | }
299 | if v3, ok := v.Type.(*ast.FuncType); ok {
300 | f.checkFunctionParams(v3, v.Names[0].Name)
301 | }
302 | }
303 | }
304 |
305 | func (f *file) checkValueName(decl *ast.GenDecl, kind string, top bool) {
306 | for _, spec := range decl.Specs {
307 | if vSpec, ok := spec.(*ast.ValueSpec); ok {
308 | for _, name := range vSpec.Names {
309 | f.checkName(name, kind, !top)
310 | }
311 | } else if tSpec, ok := spec.(*ast.TypeSpec); ok {
312 | f.checkName(tSpec.Name, kind, false)
313 | ast.Inspect(tSpec.Type, func(node ast.Node) bool {
314 | switch decl2 := node.(type) {
315 | case *ast.GenDecl:
316 | f.checkGenDecl(decl2, false)
317 | case *ast.FuncDecl:
318 | f.checkFunctionDeclare(decl2)
319 | case *ast.StructType:
320 | f.checkStruct(decl2)
321 | case *ast.InterfaceType:
322 | f.checkInterface(decl2)
323 | }
324 | return true
325 | })
326 | } else if iSpec, ok := spec.(*ast.ImportSpec); ok && iSpec.Name != nil {
327 | f.checkName(iSpec.Name, "import", true)
328 | }
329 | }
330 | }
331 |
332 | func (f *file) checkGenDecl(decl *ast.GenDecl, top bool) {
333 | if decl.Tok == token.CONST {
334 | f.checkValueName(decl, "const", top)
335 | } else if decl.Tok == token.VAR {
336 | f.checkValueName(decl, "var", top)
337 | } else if decl.Tok == token.TYPE {
338 | f.checkValueName(decl, "type", top)
339 | } else if decl.Tok == token.IMPORT {
340 | f.checkValueName(decl, "import", true)
341 | }
342 | }
343 |
344 | func (f *file) checkAssign(assign *ast.AssignStmt) {
345 | if assign.Tok != token.DEFINE {
346 | return
347 | }
348 |
349 | for _, v2 := range assign.Lhs {
350 | if assignName, ok := v2.(*ast.Ident); ok {
351 | f.checkName(assignName, "var", true)
352 | }
353 | }
354 | }
355 |
356 | func (f *file) checkFileContent() {
357 | if f.isTest() {
358 | return
359 | }
360 |
361 | if f.config.PackageName {
362 | f.checkPkgName(f.ast.Name)
363 | }
364 |
365 | for _, v := range f.ast.Decls {
366 | switch decl := v.(type) {
367 | case *ast.FuncDecl:
368 | f.checkFunctionDeclare(decl)
369 | if decl.Body == nil {
370 | break
371 | }
372 | ast.Inspect(decl.Body, func(node ast.Node) bool {
373 | switch decl2 := node.(type) {
374 | case *ast.GenDecl:
375 | f.checkGenDecl(decl2, false)
376 | case *ast.FuncDecl:
377 | f.checkFunctionDeclare(decl2)
378 | case *ast.AssignStmt:
379 | f.checkAssign(decl2)
380 | case *ast.StructType:
381 | f.checkStruct(decl2)
382 | }
383 | return true
384 | })
385 | case *ast.GenDecl:
386 | f.checkGenDecl(decl, true)
387 | }
388 | }
389 | }
390 |
--------------------------------------------------------------------------------
/checkstyle_test.go:
--------------------------------------------------------------------------------
1 | package checkstyle
2 |
3 | import (
4 | "go/parser"
5 | "go/token"
6 | "io/ioutil"
7 | "reflect"
8 | "testing"
9 | )
10 |
11 | const baseDir = "testdata/"
12 |
13 | func readFile(fileName string) []byte {
14 | file, _ := ioutil.ReadFile(baseDir + fileName)
15 | return file
16 | }
17 |
18 | func TestFileLine(t *testing.T) {
19 | fileName := "fileline.go"
20 | file := readFile(fileName)
21 | _checkerOk := checker{FileLine: 9}
22 | ps, err := _checkerOk.Check(fileName, file)
23 | if err != nil {
24 | t.Fatal(err)
25 | }
26 | if len(ps) != 0 {
27 | t.Fatal("expect no error")
28 | }
29 |
30 | _checkerFail := checker{FileLine: 8}
31 | ps, _ = _checkerFail.Check(fileName, file)
32 | if len(ps) != 1 || ps[0].Type != FileLine {
33 | t.Fatal("expect an error")
34 | }
35 |
36 | //pos is at file end
37 | fset := token.NewFileSet()
38 | f, _ := parser.ParseFile(fset, fileName, file, parser.ParseComments)
39 | if reflect.DeepEqual(ps[0], fset.Position(f.End())) {
40 | t.Fatal("file line problem position not match")
41 | }
42 | }
43 |
44 | func TestFunctionLine(t *testing.T) {
45 | fileName := "functionline.go"
46 | file := readFile(fileName)
47 | _checkerOk := checker{FunctionLine: 9}
48 | ps, err := _checkerOk.Check(fileName, file)
49 | if err != nil {
50 | t.Fatal(err)
51 | }
52 | if len(ps) != 0 {
53 | t.Fatal("expect no error")
54 | }
55 |
56 | _checkerFail := checker{FunctionLine: 8}
57 | ps, _ = _checkerFail.Check(fileName, file)
58 | if len(ps) != 1 || ps[0].Type != FunctionLine {
59 | t.Fatal("expect an error")
60 | }
61 |
62 | if ps[0].Position.Filename != fileName {
63 | t.Fatal("file name is not correct")
64 | }
65 |
66 | if ps[0].Position.Line != 7 {
67 | t.Fatal("start position is not correct")
68 | }
69 | }
70 |
71 | func TestParamsNum(t *testing.T) {
72 | fileName := "params_num.go"
73 | file := readFile(fileName)
74 | _checkerOk := checker{ParamsNum: 4}
75 | ps, err := _checkerOk.Check(fileName, file)
76 | if err != nil {
77 | t.Fatal(err)
78 | }
79 | if len(ps) != 0 {
80 | t.Fatal("expect no error")
81 | }
82 |
83 | _checkerFail := checker{ParamsNum: 3}
84 | ps, _ = _checkerFail.Check(fileName, file)
85 | if len(ps) != 1 || ps[0].Type != ParamsNum {
86 | t.Fatal("expect an error")
87 | }
88 |
89 | if ps[0].Position.Filename != fileName {
90 | t.Fatal("file name is not correct")
91 | }
92 |
93 | if ps[0].Position.Line != 7 {
94 | t.Fatal("start position is not correct")
95 | }
96 |
97 | _checkerFail = checker{ParamsNum: 2}
98 | ps, _ = _checkerFail.Check(fileName, file)
99 | if len(ps) != 2 {
100 | t.Fatal("expect 2 error")
101 | }
102 | }
103 |
104 | func TestResulsNum(t *testing.T) {
105 | fileName := "results_num.go"
106 | file := readFile(fileName)
107 | _checkerOk := checker{ResultsNum: 4}
108 | ps, err := _checkerOk.Check(fileName, file)
109 | if err != nil {
110 | t.Fatal(err)
111 | }
112 | if len(ps) != 0 {
113 | t.Fatal("expect no error")
114 | }
115 |
116 | _checkerFail := checker{ResultsNum: 3}
117 | ps, _ = _checkerFail.Check(fileName, file)
118 | if len(ps) != 1 || ps[0].Type != ResultsNum {
119 | t.Fatal("expect an error")
120 | }
121 |
122 | if ps[0].Position.Filename != fileName {
123 | t.Fatal("file name is not correct")
124 | }
125 |
126 | if ps[0].Position.Line != 7 {
127 | t.Fatal("start position is not correct")
128 | }
129 |
130 | _checkerFail = checker{ResultsNum: 2}
131 | ps, _ = _checkerFail.Check(fileName, file)
132 | if len(ps) != 2 {
133 | t.Fatal("expect 2 error")
134 | }
135 | }
136 |
137 | func TestFormated(t *testing.T) {
138 | fileName := "formated.go"
139 | file := readFile(fileName)
140 | _checker := checker{Formated: true}
141 | ps, err := _checker.Check(fileName, file)
142 | if err != nil {
143 | t.Fatal(err)
144 | }
145 | if len(ps) != 0 {
146 | t.Fatal("expect no error")
147 | }
148 |
149 | fileName = "unformated.go"
150 | file = readFile(fileName)
151 | ps, _ = _checker.Check(fileName, file)
152 | if len(ps) != 1 || ps[0].Type != Formated {
153 | t.Fatal("expect an error")
154 | }
155 |
156 | //pos is at file begin
157 | fset := token.NewFileSet()
158 | f, _ := parser.ParseFile(fset, fileName, file, parser.ParseComments)
159 | if reflect.DeepEqual(ps[0], fset.Position(f.Pos())) {
160 | t.Fatal("file line problem position not match")
161 | }
162 | }
163 |
164 | func TestPackageName(t *testing.T) {
165 | fileName := "caps_pkg.go"
166 | file := readFile(fileName)
167 | _checker := checker{PackageName: false}
168 | ps, err := _checker.Check(fileName, file)
169 | if err != nil {
170 | t.Fatal(err)
171 | }
172 | if len(ps) != 0 {
173 | t.Fatal("expect no error")
174 | }
175 | fileName = "underscore_pkg.go"
176 | file = readFile(fileName)
177 | ps, err = _checker.Check(fileName, file)
178 | if err != nil {
179 | t.Fatal(err)
180 | }
181 | if len(ps) != 0 {
182 | t.Fatal("expect no error")
183 | }
184 |
185 | fileName = "caps_pkg.go"
186 | file = readFile(fileName)
187 | _checkerFail := checker{PackageName: true}
188 | ps, err = _checkerFail.Check(fileName, file)
189 | if err != nil {
190 | t.Fatal(err)
191 | }
192 | if len(ps) == 0 {
193 | t.Fatal("expect 1 error")
194 | }
195 | fileName = "underscore_pkg.go"
196 | file = readFile(fileName)
197 | ps, err = _checkerFail.Check(fileName, file)
198 | if err != nil {
199 | t.Fatal(err)
200 | }
201 | if len(ps) == 0 {
202 | t.Fatal("expect 1 error")
203 | }
204 | }
205 |
206 | func TestCamelName(t *testing.T) {
207 | fileName := "underscore_name.go"
208 | file := readFile(fileName)
209 | _checker := checker{CamelName: false}
210 | ps, err := _checker.Check(fileName, file)
211 | if err != nil {
212 | t.Fatal(err)
213 | }
214 | if len(ps) != 0 {
215 | t.Fatal("expect no error")
216 | }
217 | _checkerFail := checker{CamelName: true}
218 | ps, err = _checkerFail.Check(fileName, file)
219 | if err != nil {
220 | t.Fatal(err)
221 | }
222 | if len(ps) != 30 {
223 | t.Fatal("expect 30 error but ", len(ps))
224 | }
225 | fileName = "camel_name.go"
226 | file = readFile(fileName)
227 | ps, err = _checkerFail.Check(fileName, file)
228 | if err != nil {
229 | t.Fatal(err)
230 | }
231 | if len(ps) != 0 {
232 | t.Fatal("expect no error")
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/gocheckstyle/config.example:
--------------------------------------------------------------------------------
1 | {
2 | "file_line":200,
3 | "_file_line_comment": "file line count limit",
4 | "func_line":50,
5 | "_func_line_comment": "function line count limit",
6 | "params_num":4,
7 | "_params_num_comment": "function parameter count limit",
8 | "results_num":3,
9 | "_results_num_comment": "function return variable count limit",
10 | "formated": true,
11 | "_formated_comment": "gofmt",
12 | "pkg_name": true,
13 | "_pkg_name_comment": "package name should not contain _ and camel",
14 | "camel_name": true,
15 | "_camel_name_comment": "const/var/function/import name should use camel name",
16 | "ignore":[
17 | "tmp/*",
18 | "src/tmp.go"
19 | ],
20 | "_ignore_comment":"ignore file",
21 | "fatal":[
22 | "formated"
23 | ],
24 | "_fatal_comment": "put the check rule of error level here"
25 | }
26 |
--------------------------------------------------------------------------------
/gocheckstyle/gocheckstyle.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "encoding/xml"
6 | "flag"
7 | "io/ioutil"
8 | "log"
9 | "os"
10 | "path/filepath"
11 | "strings"
12 |
13 | "github.com/qiniu/checkstyle"
14 | )
15 |
16 | const defaultConfig = `{
17 | "file_line":200,
18 | "_file_line_comment": "file line count limit",
19 | "func_line":50,
20 | "_func_line_comment": "function line count limit",
21 | "params_num":4,
22 | "_params_num_comment": "function parameter count limit",
23 | "results_num":3,
24 | "_results_num_comment": "function return variable count limit",
25 | "formated": true,
26 | "_formated_comment": "gofmt",
27 | "pkg_name": true,
28 | "_pkg_name_comment": "package name should not contain _ and camel",
29 | "camel_name": true,
30 | "_camel_name_comment": "const/var/function/import name should use camel name",
31 | "ignore":[
32 | "tmp/*",
33 | "src/tmp.go"
34 | ],
35 | "_ignore_comment":"ignore file",
36 | "fatal":[
37 | "formated"
38 | ],
39 | "_fatal_comment": "put the check rule of error level here"
40 | }`
41 |
42 | var config = flag.String("config", "", "config json file")
43 | var reporterOption = flag.String("reporter", "plain", "report output format, plain or xml")
44 |
45 | var checker checkstyle.Checker
46 | var reporter Reporter
47 |
48 | type Ignore struct {
49 | Files []string `json:"ignore"`
50 | }
51 |
52 | var ignore Ignore
53 |
54 | type Reporter interface {
55 | ReceiveProblems(checker checkstyle.Checker, file string, problems []checkstyle.Problem)
56 | Report()
57 | }
58 |
59 | type plainReporter struct {
60 | normalProblems []*checkstyle.Problem
61 | fatalProblems []*checkstyle.Problem
62 | }
63 |
64 | func (_ *plainReporter) printProblems(ps []*checkstyle.Problem) {
65 | for _, p := range ps {
66 | log.Printf("%v: %s\n", p.Position, p.Description)
67 | }
68 | }
69 |
70 | func (p *plainReporter) Report() {
71 | if len(p.normalProblems) != 0 {
72 | log.Printf(" ========= There are %d normal problems ========= \n", len(p.normalProblems))
73 | p.printProblems(p.normalProblems)
74 | }
75 |
76 | if len(p.fatalProblems) != 0 {
77 | log.Printf(" ========= There are %d fatal problems ========= \n", len(p.fatalProblems))
78 | p.printProblems(p.fatalProblems)
79 | os.Exit(1)
80 | }
81 | if len(p.normalProblems) == 0 && len(p.fatalProblems) == 0 {
82 | log.Println(" ========= There are no problems ========= ")
83 | }
84 | }
85 |
86 | func (p *plainReporter) ReceiveProblems(checker checkstyle.Checker, file string, problems []checkstyle.Problem) {
87 | for i, problem := range problems {
88 | if checker.IsFatal(&problem) {
89 | p.fatalProblems = append(p.fatalProblems, &problems[i])
90 | } else {
91 | p.normalProblems = append(p.normalProblems, &problems[i])
92 | }
93 | }
94 | }
95 |
96 | type xmlReporter struct {
97 | problems map[string][]checkstyle.Problem
98 | hasFatal bool
99 | }
100 |
101 | func (x *xmlReporter) printProblems(ps []checkstyle.Problem) {
102 | format := "\t\t\n"
103 | for _, p := range ps {
104 | severity := "warning"
105 | if checker.IsFatal(&p) {
106 | severity = "error"
107 | x.hasFatal = true
108 | }
109 | log.Printf(format, p.Position.Line, p.Position.Column, severity, p.Description, p.Type)
110 | }
111 | }
112 |
113 | func (x *xmlReporter) Report() {
114 | log.SetFlags(0)
115 | log.Print(xml.Header)
116 | log.Println(``)
117 | for k, v := range x.problems {
118 | log.Printf("\t\n", k)
119 | x.printProblems(v)
120 | log.Println("\t")
121 | }
122 | log.Println("")
123 | if x.hasFatal {
124 | os.Exit(1)
125 | }
126 | }
127 |
128 | func (x *xmlReporter) ReceiveProblems(checker checkstyle.Checker, file string, problems []checkstyle.Problem) {
129 | if len(problems) == 0 {
130 | return
131 | }
132 | x.problems[file] = problems
133 | }
134 |
135 | func main() {
136 | flag.Parse()
137 |
138 | files := flag.Args()
139 |
140 | if reporterOption == nil || *reporterOption != "xml" {
141 | reporter = &plainReporter{}
142 | } else {
143 | reporter = &xmlReporter{problems: map[string][]checkstyle.Problem{}}
144 | }
145 | var err error
146 | var conf []byte
147 | if *config == "" {
148 | conf = []byte(defaultConfig)
149 | } else {
150 | conf, err = ioutil.ReadFile(*config)
151 | if err != nil {
152 | log.Fatalf("Open config %v fail %v\n", *config, err)
153 | }
154 | }
155 | err = json.Unmarshal(conf, &ignore)
156 | if err != nil {
157 | log.Fatalf("Parse config %v fail \n", *config, err)
158 | }
159 | checker, err = checkstyle.New(conf)
160 | if err != nil {
161 | log.Fatalf("New checker fail %v\n", err)
162 | }
163 |
164 | if len(files) == 0 {
165 | files = []string{"."}
166 | }
167 | for _, v := range files {
168 | if isDir(v) {
169 | checkDir(v)
170 | } else {
171 | checkFile(v)
172 | }
173 | }
174 | reporter.Report()
175 | }
176 |
177 | func isDir(filename string) bool {
178 | fi, err := os.Stat(filename)
179 | return err == nil && fi.IsDir()
180 | }
181 |
182 | func checkFile(fileName string) {
183 | file, err := ioutil.ReadFile(fileName)
184 | if err != nil {
185 | log.Fatalf("Read File Fail %v %v\n", fileName, err)
186 | }
187 |
188 | ps, err := checker.Check(fileName, file)
189 | if err != nil {
190 | log.Fatalf("Parse File Fail %v %v\n", fileName, err)
191 | }
192 |
193 | reporter.ReceiveProblems(checker, fileName, ps)
194 | }
195 |
196 | func isIgnoreFile(fileName string) bool {
197 | for _, v := range ignore.Files {
198 | if ok, _ := filepath.Match(v, fileName); ok {
199 | return true
200 | }
201 | }
202 | return false
203 | }
204 |
205 | func isIgnoreDir(dir string) bool {
206 | for _, v := range ignore.Files {
207 | if ok, _ := filepath.Match(v, dir); ok {
208 | return true
209 | }
210 | }
211 | return false
212 | }
213 |
214 | func checkDir(dir string) {
215 | if isIgnoreDir(dir) {
216 | return
217 | }
218 | filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
219 | if err == nil && info.IsDir() && isIgnoreDir(path) {
220 | return filepath.SkipDir
221 | }
222 | if err == nil && !info.IsDir() && strings.HasSuffix(path, ".go") && !isIgnoreFile(path) {
223 | checkFile(path)
224 | }
225 | return err
226 | })
227 | }
228 |
--------------------------------------------------------------------------------
/testdata/camel_name.go:
--------------------------------------------------------------------------------
1 | package testdata
2 |
3 | import "fmt"
4 |
5 | const (
6 | Abc = 0
7 | _Abc = 0
8 | )
9 |
10 | const _AB = 0
11 |
12 | var _Bc = 0
13 |
14 | var (
15 | _CdE = 0
16 | _DE = 0
17 | )
18 |
19 | type _Ac struct {
20 | AAAA int
21 | _BBBB int
22 | _AAAAa int
23 | }
24 |
25 | type x interface {
26 | }
27 |
28 | func (xz *_Ac) HelloWorld(ab, _ab int) (bc int) {
29 | var _xyz int
30 | fmt.Println(_xyz)
31 | return
32 | }
33 |
34 | func (_ *_Ac) helloWorld(ab, _ab int) (bc int) {
35 | var _xyz int
36 | fmt.Println(_xyz)
37 | return
38 | }
39 |
--------------------------------------------------------------------------------
/testdata/caps_pkg.go:
--------------------------------------------------------------------------------
1 | package testData
2 |
--------------------------------------------------------------------------------
/testdata/fileline.go:
--------------------------------------------------------------------------------
1 | package testdata
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func hello() {
8 | fmt.Println("hello")
9 | }
10 |
--------------------------------------------------------------------------------
/testdata/formated.go:
--------------------------------------------------------------------------------
1 | package testdata
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func hello() {
8 | fmt.Println("hello")
9 | }
10 |
--------------------------------------------------------------------------------
/testdata/functionline.go:
--------------------------------------------------------------------------------
1 | package testdata
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func hello() {
8 | fmt.Println("hello1")
9 | fmt.Println("hello2")
10 | fmt.Println("hello3")
11 | fmt.Println("hello4")
12 | fmt.Println("hello5")
13 | fmt.Println("hello6")
14 | fmt.Println("hello7")
15 | fmt.Println("hello8")
16 | }
17 |
--------------------------------------------------------------------------------
/testdata/params_num.go:
--------------------------------------------------------------------------------
1 | package testdata
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func hello(a, b, c, _ int) {
8 | fmt.Println("hello")
9 | }
10 |
11 | func hello2(a, b int, c ...int) {
12 | fmt.Println("... args")
13 | }
14 |
15 | type z int
16 |
17 | func (_ *z) hello(a, b int) {
18 | fmt.Println("one receiver")
19 | }
20 |
--------------------------------------------------------------------------------
/testdata/results_num.go:
--------------------------------------------------------------------------------
1 | package testdata
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func hello(a, b int) (int, int, int, int) {
8 | fmt.Println("hello")
9 | }
10 |
11 | func hello2(a, b int, c ...int) (int, int, int) {
12 | fmt.Println("... args")
13 | }
14 |
15 | type z int
16 |
17 | func (_ *z) hello3(a, b, c int) (int, int) {
18 | fmt.Println("receiver")
19 | }
20 |
--------------------------------------------------------------------------------
/testdata/underscore_name.go:
--------------------------------------------------------------------------------
1 | package testdata
2 |
3 | import (
4 | Fmt "fmt"
5 | )
6 |
7 | const (
8 | A_B_C = 0
9 | )
10 |
11 | const A_B = 0
12 |
13 | var B_C = 0
14 |
15 | var (
16 | _C_D = 0
17 | _D_E = 0
18 | __ = 0
19 | __a = 0
20 | )
21 |
22 | type A_C struct {
23 | AAAAA int
24 | _BBBB int
25 | }
26 |
27 | type I_A interface {
28 | F_A(P_A int) (R_A int)
29 | F_B(int) int
30 | }
31 |
32 | func (Z *A_C) Hello_World(_B, A int) (_B_C, B int) {
33 | var X_Y_Z, _C int
34 | _C = 1
35 | Fmt.Println(X_Y_Z, __C)
36 | Zx := struct {
37 | S_A int
38 | }{A_B}
39 | Fmt.Println(Zx)
40 | F := func() {
41 | TX, TY := 0
42 | Fmt.Println("TX")
43 | }
44 | for I := 0; i < 1; i++ {
45 | }
46 | if X := _C; X == 1 {
47 | }
48 | F()
49 | return
50 | }
51 |
--------------------------------------------------------------------------------
/testdata/underscore_pkg.go:
--------------------------------------------------------------------------------
1 | package test_data
2 |
--------------------------------------------------------------------------------
/testdata/unformated.go:
--------------------------------------------------------------------------------
1 | package testdata
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func hello() {
8 | fmt.Println("hello")}
9 |
--------------------------------------------------------------------------------