pikeman 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +21 -0
  4. data/CONTRIBUTING.md +15 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +36 -0
  7. data/LICENSE +27 -0
  8. data/LICENSE.txt +21 -0
  9. data/Makefile +39 -0
  10. data/README.md +85 -0
  11. data/Rakefile +10 -0
  12. data/VERSION +1 -0
  13. data/bin/console +14 -0
  14. data/bin/pikeman +15 -0
  15. data/bin/setup +8 -0
  16. data/golint/golint.go +213 -0
  17. data/golint/import.go +309 -0
  18. data/golint/version.go +3 -0
  19. data/lib/pikeman.rb +34 -0
  20. data/lib/pikeman/version.rb +3 -0
  21. data/lint.go +1708 -0
  22. data/lint_test.go +317 -0
  23. data/misc/emacs/golint.el +54 -0
  24. data/misc/vim/ftplugin/go/lint.vim +31 -0
  25. data/pikeman.gemspec +28 -0
  26. data/testdata/4.go +38 -0
  27. data/testdata/5_test.go +17 -0
  28. data/testdata/blank-import-lib.go +39 -0
  29. data/testdata/blank-import-lib_test.go +25 -0
  30. data/testdata/blank-import-main.go +14 -0
  31. data/testdata/broken.go +9 -0
  32. data/testdata/common-methods.go +16 -0
  33. data/testdata/const-block.go +36 -0
  34. data/testdata/context.go +24 -0
  35. data/testdata/contextkeytypes.go +38 -0
  36. data/testdata/else-multi.go +18 -0
  37. data/testdata/else.go +23 -0
  38. data/testdata/error-return.go +43 -0
  39. data/testdata/errorf.go +40 -0
  40. data/testdata/errors.go +38 -0
  41. data/testdata/iferr.go +101 -0
  42. data/testdata/import-dot.go +8 -0
  43. data/testdata/inc.go +14 -0
  44. data/testdata/names.go +116 -0
  45. data/testdata/pkg-caps.go +4 -0
  46. data/testdata/pkg-doc1.go +3 -0
  47. data/testdata/pkg-doc2.go +5 -0
  48. data/testdata/pkg-doc3.go +7 -0
  49. data/testdata/pkg-doc4.go +7 -0
  50. data/testdata/pkg-doc5.go +9 -0
  51. data/testdata/pkg-main.go +5 -0
  52. data/testdata/range.go +43 -0
  53. data/testdata/receiver-names.go +49 -0
  54. data/testdata/sort.go +20 -0
  55. data/testdata/stutter.go +25 -0
  56. data/testdata/time.go +13 -0
  57. data/testdata/unexp-return.go +46 -0
  58. data/testdata/var-decl.go +86 -0
  59. metadata +172 -0
@@ -0,0 +1,317 @@
1
+ // Copyright (c) 2013 The Go Authors. All rights reserved.
2
+ //
3
+ // Use of this source code is governed by a BSD-style
4
+ // license that can be found in the LICENSE file or at
5
+ // https://developers.google.com/open-source/licenses/bsd.
6
+
7
+ package lint
8
+
9
+ import (
10
+ "bytes"
11
+ "flag"
12
+ "fmt"
13
+ "go/ast"
14
+ "go/parser"
15
+ "go/printer"
16
+ "go/token"
17
+ "go/types"
18
+ "io/ioutil"
19
+ "path"
20
+ "regexp"
21
+ "strconv"
22
+ "strings"
23
+ "testing"
24
+ )
25
+
26
+ var lintMatch = flag.String("lint.match", "", "restrict testdata matches to this pattern")
27
+
28
+ func TestAll(t *testing.T) {
29
+ l := new(Linter)
30
+ rx, err := regexp.Compile(*lintMatch)
31
+ if err != nil {
32
+ t.Fatalf("Bad -lint.match value %q: %v", *lintMatch, err)
33
+ }
34
+
35
+ baseDir := "testdata"
36
+ fis, err := ioutil.ReadDir(baseDir)
37
+ if err != nil {
38
+ t.Fatalf("ioutil.ReadDir: %v", err)
39
+ }
40
+ if len(fis) == 0 {
41
+ t.Fatalf("no files in %v", baseDir)
42
+ }
43
+ for _, fi := range fis {
44
+ if !rx.MatchString(fi.Name()) {
45
+ continue
46
+ }
47
+ //t.Logf("Testing %s", fi.Name())
48
+ src, err := ioutil.ReadFile(path.Join(baseDir, fi.Name()))
49
+ if err != nil {
50
+ t.Fatalf("Failed reading %s: %v", fi.Name(), err)
51
+ }
52
+
53
+ ins := parseInstructions(t, fi.Name(), src)
54
+ if ins == nil {
55
+ t.Errorf("Test file %v does not have instructions", fi.Name())
56
+ continue
57
+ }
58
+
59
+ ps, err := l.Lint(fi.Name(), src)
60
+ if err != nil {
61
+ t.Errorf("Linting %s: %v", fi.Name(), err)
62
+ continue
63
+ }
64
+
65
+ for _, in := range ins {
66
+ ok := false
67
+ for i, p := range ps {
68
+ if p.Position.Line != in.Line {
69
+ continue
70
+ }
71
+ if in.Match.MatchString(p.Text) {
72
+ // check replacement if we are expecting one
73
+ if in.Replacement != "" {
74
+ // ignore any inline comments, since that would be recursive
75
+ r := p.ReplacementLine
76
+ if i := strings.Index(r, " //"); i >= 0 {
77
+ r = r[:i]
78
+ }
79
+ if r != in.Replacement {
80
+ t.Errorf("Lint failed at %s:%d; got replacement %q, want %q", fi.Name(), in.Line, r, in.Replacement)
81
+ }
82
+ }
83
+
84
+ // remove this problem from ps
85
+ copy(ps[i:], ps[i+1:])
86
+ ps = ps[:len(ps)-1]
87
+
88
+ //t.Logf("/%v/ matched at %s:%d", in.Match, fi.Name(), in.Line)
89
+ ok = true
90
+ break
91
+ }
92
+ }
93
+ if !ok {
94
+ t.Errorf("Lint failed at %s:%d; /%v/ did not match", fi.Name(), in.Line, in.Match)
95
+ }
96
+ }
97
+ for _, p := range ps {
98
+ t.Errorf("Unexpected problem at %s:%d: %v", fi.Name(), p.Position.Line, p.Text)
99
+ }
100
+ }
101
+ }
102
+
103
+ type instruction struct {
104
+ Line int // the line number this applies to
105
+ Match *regexp.Regexp // what pattern to match
106
+ Replacement string // what the suggested replacement line should be
107
+ }
108
+
109
+ // parseInstructions parses instructions from the comments in a Go source file.
110
+ // It returns nil if none were parsed.
111
+ func parseInstructions(t *testing.T, filename string, src []byte) []instruction {
112
+ fset := token.NewFileSet()
113
+ f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
114
+ if err != nil {
115
+ t.Fatalf("Test file %v does not parse: %v", filename, err)
116
+ }
117
+ var ins []instruction
118
+ for _, cg := range f.Comments {
119
+ ln := fset.Position(cg.Pos()).Line
120
+ raw := cg.Text()
121
+ for _, line := range strings.Split(raw, "\n") {
122
+ if line == "" || strings.HasPrefix(line, "#") {
123
+ continue
124
+ }
125
+ if line == "OK" && ins == nil {
126
+ // so our return value will be non-nil
127
+ ins = make([]instruction, 0)
128
+ continue
129
+ }
130
+ if strings.Contains(line, "MATCH") {
131
+ rx, err := extractPattern(line)
132
+ if err != nil {
133
+ t.Fatalf("At %v:%d: %v", filename, ln, err)
134
+ }
135
+ matchLine := ln
136
+ if i := strings.Index(line, "MATCH:"); i >= 0 {
137
+ // This is a match for a different line.
138
+ lns := strings.TrimPrefix(line[i:], "MATCH:")
139
+ lns = lns[:strings.Index(lns, " ")]
140
+ matchLine, err = strconv.Atoi(lns)
141
+ if err != nil {
142
+ t.Fatalf("Bad match line number %q at %v:%d: %v", lns, filename, ln, err)
143
+ }
144
+ }
145
+ var repl string
146
+ if r, ok := extractReplacement(line); ok {
147
+ repl = r
148
+ }
149
+ ins = append(ins, instruction{
150
+ Line: matchLine,
151
+ Match: rx,
152
+ Replacement: repl,
153
+ })
154
+ }
155
+ }
156
+ }
157
+ return ins
158
+ }
159
+
160
+ func extractPattern(line string) (*regexp.Regexp, error) {
161
+ a, b := strings.Index(line, "/"), strings.LastIndex(line, "/")
162
+ if a == -1 || a == b {
163
+ return nil, fmt.Errorf("malformed match instruction %q", line)
164
+ }
165
+ pat := line[a+1 : b]
166
+ rx, err := regexp.Compile(pat)
167
+ if err != nil {
168
+ return nil, fmt.Errorf("bad match pattern %q: %v", pat, err)
169
+ }
170
+ return rx, nil
171
+ }
172
+
173
+ func extractReplacement(line string) (string, bool) {
174
+ // Look for this: / -> `
175
+ // (the end of a match and start of a backtick string),
176
+ // and then the closing backtick.
177
+ const start = "/ -> `"
178
+ a, b := strings.Index(line, start), strings.LastIndex(line, "`")
179
+ if a < 0 || a > b {
180
+ return "", false
181
+ }
182
+ return line[a+len(start) : b], true
183
+ }
184
+
185
+ func render(fset *token.FileSet, x interface{}) string {
186
+ var buf bytes.Buffer
187
+ if err := printer.Fprint(&buf, fset, x); err != nil {
188
+ panic(err)
189
+ }
190
+ return buf.String()
191
+ }
192
+
193
+ func TestLine(t *testing.T) {
194
+ tests := []struct {
195
+ src string
196
+ offset int
197
+ want string
198
+ }{
199
+ {"single line file", 5, "single line file"},
200
+ {"single line file with newline\n", 5, "single line file with newline\n"},
201
+ {"first\nsecond\nthird\n", 2, "first\n"},
202
+ {"first\nsecond\nthird\n", 9, "second\n"},
203
+ {"first\nsecond\nthird\n", 14, "third\n"},
204
+ {"first\nsecond\nthird with no newline", 16, "third with no newline"},
205
+ {"first byte\n", 0, "first byte\n"},
206
+ }
207
+ for _, test := range tests {
208
+ got := srcLine([]byte(test.src), token.Position{Offset: test.offset})
209
+ if got != test.want {
210
+ t.Errorf("srcLine(%q, offset=%d) = %q, want %q", test.src, test.offset, got, test.want)
211
+ }
212
+ }
213
+ }
214
+
215
+ func TestLintName(t *testing.T) {
216
+ tests := []struct {
217
+ name, want string
218
+ }{
219
+ {"foo_bar", "fooBar"},
220
+ {"foo_bar_baz", "fooBarBaz"},
221
+ {"Foo_bar", "FooBar"},
222
+ {"foo_WiFi", "fooWiFi"},
223
+ {"id", "id"},
224
+ {"Id", "ID"},
225
+ {"foo_id", "fooID"},
226
+ {"fooId", "fooID"},
227
+ {"fooUid", "fooUID"},
228
+ {"idFoo", "idFoo"},
229
+ {"uidFoo", "uidFoo"},
230
+ {"midIdDle", "midIDDle"},
231
+ {"APIProxy", "APIProxy"},
232
+ {"ApiProxy", "APIProxy"},
233
+ {"apiProxy", "apiProxy"},
234
+ {"_Leading", "_Leading"},
235
+ {"___Leading", "_Leading"},
236
+ {"trailing_", "trailing"},
237
+ {"trailing___", "trailing"},
238
+ {"a_b", "aB"},
239
+ {"a__b", "aB"},
240
+ {"a___b", "aB"},
241
+ {"Rpc1150", "RPC1150"},
242
+ {"case3_1", "case3_1"},
243
+ {"case3__1", "case3_1"},
244
+ {"IEEE802_16bit", "IEEE802_16bit"},
245
+ {"IEEE802_16Bit", "IEEE802_16Bit"},
246
+ }
247
+ for _, test := range tests {
248
+ got := lintName(test.name)
249
+ if got != test.want {
250
+ t.Errorf("lintName(%q) = %q, want %q", test.name, got, test.want)
251
+ }
252
+ }
253
+ }
254
+
255
+ func TestExportedType(t *testing.T) {
256
+ tests := []struct {
257
+ typString string
258
+ exp bool
259
+ }{
260
+ {"int", true},
261
+ {"string", false}, // references the shadowed builtin "string"
262
+ {"T", true},
263
+ {"t", false},
264
+ {"*T", true},
265
+ {"*t", false},
266
+ {"map[int]complex128", true},
267
+ }
268
+ for _, test := range tests {
269
+ src := `package foo; type T int; type t int; type string struct{}`
270
+ fset := token.NewFileSet()
271
+ file, err := parser.ParseFile(fset, "foo.go", src, 0)
272
+ if err != nil {
273
+ t.Fatalf("Parsing %q: %v", src, err)
274
+ }
275
+ // use the package name as package path
276
+ config := &types.Config{}
277
+ pkg, err := config.Check(file.Name.Name, fset, []*ast.File{file}, nil)
278
+ if err != nil {
279
+ t.Fatalf("Type checking %q: %v", src, err)
280
+ }
281
+ tv, err := types.Eval(fset, pkg, token.NoPos, test.typString)
282
+ if err != nil {
283
+ t.Errorf("types.Eval(%q): %v", test.typString, err)
284
+ continue
285
+ }
286
+ if got := exportedType(tv.Type); got != test.exp {
287
+ t.Errorf("exportedType(%v) = %t, want %t", tv.Type, got, test.exp)
288
+ }
289
+ }
290
+ }
291
+
292
+ func TestIsGenerated(t *testing.T) {
293
+ tests := []struct {
294
+ source string
295
+ generated bool
296
+ }{
297
+ {"// Code Generated by some tool. DO NOT EDIT.", false},
298
+ {"// Code generated by some tool. DO NOT EDIT.", true},
299
+ {"// Code generated by some tool. DO NOT EDIT", false},
300
+ {"// Code generated DO NOT EDIT.", true},
301
+ {"// Code generated DO NOT EDIT.", false},
302
+ {"\t\t// Code generated by some tool. DO NOT EDIT.\npackage foo\n", false},
303
+ {"// Code generated by some tool. DO NOT EDIT.\npackage foo\n", true},
304
+ {"package foo\n// Code generated by some tool. DO NOT EDIT.\ntype foo int\n", true},
305
+ {"package foo\n // Code generated by some tool. DO NOT EDIT.\ntype foo int\n", false},
306
+ {"package foo\n// Code generated by some tool. DO NOT EDIT. \ntype foo int\n", false},
307
+ {"package foo\ntype foo int\n// Code generated by some tool. DO NOT EDIT.\n", true},
308
+ {"package foo\ntype foo int\n// Code generated by some tool. DO NOT EDIT.", true},
309
+ }
310
+
311
+ for i, test := range tests {
312
+ got := isGenerated([]byte(test.source))
313
+ if got != test.generated {
314
+ t.Errorf("test %d, isGenerated() = %v, want %v", i, got, test.generated)
315
+ }
316
+ }
317
+ }
@@ -0,0 +1,54 @@
1
+ ;;; golint.el --- lint for the Go source code
2
+
3
+ ;; Copyright 2013 The Go Authors. All rights reserved.
4
+
5
+ ;; License: BSD-3-clause
6
+ ;; URL: https://github.com/golang/lint
7
+
8
+ ;;; Commentary:
9
+
10
+ ;; To install golint, add the following lines to your .emacs file:
11
+ ;; (add-to-list 'load-path "PATH CONTAINING golint.el" t)
12
+ ;; (require 'golint)
13
+ ;;
14
+ ;; After this, type M-x golint on Go source code.
15
+ ;;
16
+ ;; Usage:
17
+ ;; C-x `
18
+ ;; Jump directly to the line in your code which caused the first message.
19
+ ;;
20
+ ;; For more usage, see Compilation-Mode:
21
+ ;; http://www.gnu.org/software/emacs/manual/html_node/emacs/Compilation-Mode.html
22
+
23
+ ;;; Code:
24
+
25
+ (require 'compile)
26
+
27
+ (defun go-lint-buffer-name (mode)
28
+ "*Golint*")
29
+
30
+ (defun golint-process-setup ()
31
+ "Setup compilation variables and buffer for `golint'."
32
+ (run-hooks 'golint-setup-hook))
33
+
34
+ (define-compilation-mode golint-mode "golint"
35
+ "Golint is a linter for Go source code."
36
+ (set (make-local-variable 'compilation-scroll-output) nil)
37
+ (set (make-local-variable 'compilation-disable-input) t)
38
+ (set (make-local-variable 'compilation-process-setup-function)
39
+ 'golint-process-setup))
40
+
41
+ ;;;###autoload
42
+ (defun golint ()
43
+ "Run golint on the current file and populate the fix list.
44
+ Pressing \"C-x `\" jumps directly to the line in your code which
45
+ caused the first message."
46
+ (interactive)
47
+ (compilation-start
48
+ (mapconcat #'shell-quote-argument
49
+ (list "golint" (expand-file-name buffer-file-name)) " ")
50
+ 'golint-mode))
51
+
52
+ (provide 'golint)
53
+
54
+ ;;; golint.el ends here
@@ -0,0 +1,31 @@
1
+ " Copyright 2013 The Go Authors. All rights reserved.
2
+ " Use of this source code is governed by a BSD-style
3
+ " license that can be found in the LICENSE file.
4
+ "
5
+ " lint.vim: Vim command to lint Go files with golint.
6
+ "
7
+ " https://github.com/golang/lint
8
+ "
9
+ " This filetype plugin add a new commands for go buffers:
10
+ "
11
+ " :Lint
12
+ "
13
+ " Run golint for the current Go file.
14
+ "
15
+ if exists("b:did_ftplugin_go_lint")
16
+ finish
17
+ endif
18
+
19
+ if !executable("golint")
20
+ finish
21
+ endif
22
+
23
+ command! -buffer Lint call s:GoLint()
24
+
25
+ function! s:GoLint() abort
26
+ cexpr system('golint ' . shellescape(expand('%')))
27
+ endfunction
28
+
29
+ let b:did_ftplugin_go_lint = 1
30
+
31
+ " vim:ts=4:sw=4:et
@@ -0,0 +1,28 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "pikeman/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "pikeman"
7
+ spec.version = Pikeman::VERSION
8
+ spec.authors = ["Maxime Bedard"]
9
+ spec.email = ["maxime.bedard@shopify.com"]
10
+
11
+ spec.summary = %q{Very opinionated version of google/golint}
12
+ spec.description = %q{Very opinionated version of google/golint}
13
+ spec.homepage = "https://github.com/maximebedard/pikeman"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+
20
+ spec.executables = ["pikeman"]
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency "cli-kit"
24
+ spec.add_development_dependency "bundler", "~> 1.16"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "minitest", "~> 5.0"
27
+ spec.add_development_dependency "pry-byebug"
28
+ end
@@ -0,0 +1,38 @@
1
+ // Test that exported names have correct comments.
2
+
3
+ // Package pkg does something.
4
+ package pkg
5
+
6
+ import "time"
7
+
8
+ type T int // MATCH /exported type T.*should.*comment.*or.*unexport/
9
+
10
+ func (T) F() {} // MATCH /exported method T\.F.*should.*comment.*or.*unexport/
11
+
12
+ // this is a nice type.
13
+ // MATCH /comment.*exported type U.*should.*form.*"U ..."/
14
+ type U string
15
+
16
+ // this is a neat function.
17
+ // MATCH /comment.*exported method U\.G.*should.*form.*"G ..."/
18
+ func (U) G() {}
19
+
20
+ // A V is a string.
21
+ type V string
22
+
23
+ // V.H has a pointer receiver
24
+
25
+ func (*V) H() {} // MATCH /exported method V\.H.*should.*comment.*or.*unexport/
26
+
27
+ var W = "foo" // MATCH /exported var W.*should.*comment.*or.*unexport/
28
+
29
+ const X = "bar" // MATCH /exported const X.*should.*comment.*or.*unexport/
30
+
31
+ var Y, Z int // MATCH /exported var Z.*own declaration/
32
+
33
+ // Location should be okay, since the other var name is an underscore.
34
+ var Location, _ = time.LoadLocation("Europe/Istanbul") // not Constantinople
35
+
36
+ // this is improperly documented
37
+ // MATCH /comment.*const.*Thing.*form.*"Thing ..."/
38
+ const Thing = "wonderful"