Initial export of golint.
diff --git a/README b/README
new file mode 100644
index 0000000..313889e
--- /dev/null
+++ b/README
@@ -0,0 +1,25 @@
+golint is a linter for Go source code.
+
+Invoke golint with one or more filenames.
+The output of this tool is a list of suggestions in Vim quickfix format,
+which is accepted by lots of different editors.
+
+The suggestions made by golint are exactly that: suggestions.
+golint is not perfect, and has both false positives and false negatives.
+Do not treat its output as a gold standard. We will not be adding pragmas
+or other knobs to suppress specific warnings, so do not expect or require
+code to be completely "lint-free".
+Unlimited effort will not be expended to make golint perfect.
+It is considered an abuse of this tool to run it for machine consumption.
+
+
+Vim
+---
+Add this to your ~/.vimrc:
+ function! s:GoLint()
+ cexpr system("golint " . shellescape(expand('%')))
+ copen
+ endfunction
+ command! GoLint :call s:GoLint()
+
+Running :GoLint will run golint on the current file and populate the quickfix list.
diff --git a/golint/golint.go b/golint/golint.go
new file mode 100644
index 0000000..d834c50
--- /dev/null
+++ b/golint/golint.go
@@ -0,0 +1,48 @@
+// Copyright (c) 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// golint lints the Go source files named on its command line.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+
+ "github.com/golang/lint"
+)
+
+var minConfidence = flag.Float64("min_confidence", 0.8, "minimum confidence of a problem to print it")
+
+func main() {
+ flag.Parse()
+
+ // TODO(dsymonds): Support linting of stdin.
+ for _, filename := range flag.Args() {
+ lintFile(filename)
+ }
+}
+
+func lintFile(filename string) {
+ src, err := ioutil.ReadFile(filename)
+ if err != nil {
+ log.Printf("Failed reading file: %v", err)
+ return
+ }
+
+ l := new(lint.Linter)
+ ps, err := l.Lint(filename, src)
+ if err != nil {
+ log.Printf("Failed parsing file: %v", err)
+ return
+ }
+ for _, p := range ps {
+ if p.Confidence >= *minConfidence {
+ fmt.Printf("%s:%v: %s\n", filename, p.Position, p.Text)
+ }
+ }
+}
diff --git a/lint.go b/lint.go
new file mode 100644
index 0000000..de2a2a1
--- /dev/null
+++ b/lint.go
@@ -0,0 +1,613 @@
+// Copyright (c) 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// Package lint contains a linter for Go source code.
+package lint
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/printer"
+ "go/token"
+ "strings"
+ "unicode"
+)
+
+// A Linter lints Go source code.
+type Linter struct {
+}
+
+// Problem represents a problem in some source code.
+type Problem struct {
+ Position token.Position // position in source file
+ Text string // the prose that describes the problem
+ Confidence float64 // a value in (0,1] estimating the confidence in this problem's correctness
+ LineText string // the source line
+}
+
+func (p *Problem) String() string {
+ return p.Text
+}
+
+// Lint lints src.
+func (l *Linter) Lint(filename string, src []byte) ([]Problem, error) {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "", src, parser.ParseComments)
+ if err != nil {
+ return nil, err
+ }
+ return (&file{fset: fset, f: f, src: src, filename: filename}).lint(), nil
+}
+
+// file represents a file being linted.
+type file struct {
+ fset *token.FileSet
+ f *ast.File
+ src []byte
+ filename string
+
+ // sortable is the set of types in the file that implement sort.Interface.
+ sortable map[string]bool
+
+ problems []Problem
+}
+
+func (f *file) isTest() bool { return strings.HasSuffix(f.filename, "_test.go") }
+
+func (f *file) lint() []Problem {
+ f.scanSortable()
+
+ f.lintPackageComment()
+ f.lintImports()
+ f.lintExported()
+ f.lintNames()
+ f.lintVarDecls()
+ f.lintElses()
+
+ return f.problems
+}
+
+func (f *file) errorf(n ast.Node, confidence float64, format string, a ...interface{}) {
+ p := f.fset.Position(n.Pos())
+ f.problems = append(f.problems, Problem{
+ Position: p,
+ Text: fmt.Sprintf(format, a...),
+ Confidence: confidence,
+ LineText: srcLine(f.src, p),
+ })
+}
+
+func (f *file) scanSortable() {
+ f.sortable = make(map[string]bool)
+
+ // bitfield for which methods exist on each type.
+ const (
+ Len = 1 << iota
+ Less
+ Swap
+ )
+ nmap := map[string]int{"Len": Len, "Less": Less, "Swap": Swap}
+ has := make(map[string]int)
+ f.walk(func(n ast.Node) bool {
+ fn, ok := n.(*ast.FuncDecl)
+ if !ok || fn.Recv == nil {
+ return true
+ }
+ // TODO(dsymonds): We could check the signature to be more precise.
+ recv := receiverName(fn)
+ if i, ok := nmap[fn.Name.Name]; ok {
+ has[recv] |= i
+ }
+ return false
+ })
+ for typ, ms := range has {
+ if ms == Len|Less|Swap {
+ f.sortable[typ] = true
+ }
+ }
+}
+
+// lintPackageComment checks package comments. It complains if
+// there is no package comment, or if it is not of the right form.
+// This has a notable false positive in that a package comment
+// could rightfully appear in a different file of the same package,
+// but that's not easy to fix since this linter is file-oriented.
+func (f *file) lintPackageComment() {
+ if f.isTest() {
+ return
+ }
+
+ if f.f.Doc == nil {
+ f.errorf(f.f, 0.2, "should have a package comment, unless it's in another file for this package")
+ return
+ }
+ s := f.f.Doc.Text()
+ prefix := "Package " + f.f.Name.Name + " "
+ if ts := strings.TrimLeft(s, " \t"); ts != s {
+ f.errorf(f.f.Doc, 1, "package comment should not have leading space")
+ s = ts
+ }
+ // Only non-main packages need to keep to this form.
+ if f.f.Name.Name != "main" && !strings.HasPrefix(s, prefix) {
+ f.errorf(f.f.Doc, 1, `package comment should be of the form "%s..."`, prefix)
+ }
+}
+
+// lintImports examines import blocks.
+func (f *file) lintImports() {
+
+ for i, is := range f.f.Imports {
+ _ = i
+ if is.Name != nil && is.Name.Name == "." && !f.isTest() {
+ f.errorf(is, 1, "should not use dot imports")
+ }
+
+ }
+
+}
+
+// lintExported examines the doc comments of exported names.
+// It complains if any required doc comments are missing,
+// or if they are not of the right form. The exact rules are in
+// lintFuncDoc, lintTypeDoc and lintValueSpecDoc; this function
+// also tracks the GenDecl structure being traversed to permit
+// doc comments for constants to be on top of the const block.
+func (f *file) lintExported() {
+ if f.isTest() {
+ return
+ }
+
+ var lastGen *ast.GenDecl // last GenDecl entered.
+
+ // Set of GenDecls that have already had missing comments flagged.
+ genDeclMissingComments := make(map[*ast.GenDecl]bool)
+
+ f.walk(func(node ast.Node) bool {
+ switch v := node.(type) {
+ case *ast.GenDecl:
+ if v.Tok == token.IMPORT {
+ return false
+ }
+ // token.CONST, token.TYPE or token.VAR
+ lastGen = v
+ return true
+ case *ast.FuncDecl:
+ f.lintFuncDoc(v)
+ // Don't proceed inside funcs.
+ return false
+ case *ast.TypeSpec:
+ // inside a GenDecl, which usually has the doc
+ doc := v.Doc
+ if doc == nil {
+ doc = lastGen.Doc
+ }
+ f.lintTypeDoc(v, doc)
+ // Don't proceed inside types.
+ return false
+ case *ast.ValueSpec:
+ f.lintValueSpecDoc(v, lastGen, genDeclMissingComments)
+ return false
+ }
+ return true
+ })
+}
+
+// lintNames examines all names in the file.
+// It complains if any use underscores or incorrect known initialisms.
+func (f *file) lintNames() {
+ // TODO(dsymonds): If in a test, don't ping ExampleFoo_Bar.
+
+ // Package names need slightly different handling than other names.
+ if strings.Contains(f.f.Name.Name, "_") && !strings.HasSuffix(f.f.Name.Name, "_test") {
+ f.errorf(f.f, 1, "don't use an underscore in package name")
+ }
+
+ check := func(id *ast.Ident, thing string) {
+ if id.Name == "_" {
+ return
+ }
+ should := lintName(id.Name)
+ if id.Name == should {
+ return
+ }
+ if strings.Contains(id.Name, "_") {
+ f.errorf(id, 0.9, "don't use underscores in Go names; %s %s should be %s", thing, id.Name, should)
+ return
+ }
+ f.errorf(id, 0.8, "%s %s should be %s", thing, id.Name, should)
+ }
+ f.walk(func(node ast.Node) bool {
+ switch v := node.(type) {
+ case *ast.AssignStmt:
+ for _, exp := range v.Lhs {
+ if id, ok := exp.(*ast.Ident); ok {
+ check(id, "var")
+ }
+ }
+ case *ast.FuncDecl:
+ check(v.Name, "func")
+ case *ast.GenDecl:
+ if v.Tok == token.IMPORT {
+ return true
+ }
+ var thing string
+ switch v.Tok {
+ case token.CONST:
+ thing = "const"
+ case token.TYPE:
+ thing = "type"
+ case token.VAR:
+ thing = "var"
+ }
+ for _, spec := range v.Specs {
+ switch s := spec.(type) {
+ case *ast.TypeSpec:
+ check(s.Name, thing)
+ case *ast.ValueSpec:
+ for _, id := range s.Names {
+ check(id, thing)
+ }
+ }
+ }
+ case *ast.StructType:
+ for _, f := range v.Fields.List {
+ for _, id := range f.Names {
+ check(id, "struct field")
+ }
+ }
+ }
+ return true
+ })
+}
+
+// lintName returns a different name if it should be different.
+func lintName(name string) (should string) {
+ // Fast path for simple cases: "_" and all lowercase.
+ if name == "_" {
+ return name
+ }
+ allLower := true
+ for _, r := range name {
+ if !unicode.IsLower(r) {
+ allLower = false
+ break
+ }
+ }
+ if allLower {
+ return name
+ }
+
+ // Split camelCase at any lower->upper transition, and split on underscores.
+ // Check each word for common initialisms.
+ runes := []rune(name)
+ w, i := 0, 0 // index of start of word, scan
+ for i+1 <= len(runes) {
+ eow := false // whether we hit the end of a word
+ if i+1 == len(runes) {
+ eow = true
+ } else if runes[i+1] == '_' {
+ // underscore; shift the remainder forward over any run of underscores
+ eow = true
+ n := 1
+ for i+n+1 < len(runes) && runes[i+n+1] == '_' {
+ n++
+ }
+ copy(runes[i+1:], runes[i+n+1:])
+ runes = runes[:len(runes)-n]
+ } else if unicode.IsLower(runes[i]) && unicode.IsUpper(runes[i+1]) {
+ // lower->upper
+ eow = true
+ }
+ i++
+ if !eow {
+ continue
+ }
+
+ // [w,i) is a word.
+ word := string(runes[w:i])
+ if u := strings.ToUpper(word); commonInitialisms[u] {
+ // Keep consistent case, which is lowercase only at the start.
+ if w == 0 && unicode.IsLower(runes[w]) {
+ u = strings.ToLower(u)
+ }
+ // All the common initialisms are ASCII,
+ // so we can replace the bytes exactly.
+ copy(runes[w:], []rune(u))
+ } else if w > 0 && strings.ToLower(word) == word {
+ // already all lowercase, and not the first word, so uppercase the first character.
+ runes[w] = unicode.ToUpper(runes[w])
+ }
+ w = i
+ }
+ return string(runes)
+}
+
+// commonInitialisms is a set of common initialisms.
+// Only add entries that are highly unlikely to be non-initialisms.
+// For instance, "ID" is fine (Freudian code is rare), but "AND" is not.
+var commonInitialisms = map[string]bool{
+ "ASCII": true,
+ "API": true,
+ "HTML": true,
+ "HTTP": true,
+ "ID": true,
+ "IP": true,
+ "JSON": true,
+ "URL": true,
+}
+
+// lintTypeDoc examines the doc comment on a type.
+// It complains if they are missing from an exported type,
+// or if they are not of the standard form.
+func (f *file) lintTypeDoc(t *ast.TypeSpec, doc *ast.CommentGroup) {
+ if !ast.IsExported(t.Name.Name) {
+ return
+ }
+ if doc == nil {
+ f.errorf(t, 1, "exported type %v should have comment", t.Name)
+ return
+ }
+
+ s := doc.Text()
+ articles := [...]string{"A", "An", "The"}
+ for _, a := range articles {
+ if strings.HasPrefix(s, a+" ") {
+ s = s[len(a)+1:]
+ break
+ }
+ }
+ if !strings.HasPrefix(s, t.Name.Name+" ") {
+ f.errorf(doc, 1, `comment on exported type %v should be of the form "%v ..." (with optional leading article)`, t.Name, t.Name)
+ }
+}
+
+var commonMethods = map[string]bool{
+ "Error": true,
+ "Read": true,
+ "ServeHTTP": true,
+ "String": true,
+ "Write": true,
+}
+
+// lintFuncDoc examines doc comments on functions and methods.
+// It complains if they are missing, or not of the right form.
+// It has specific exclusions for well-known methods (see commonMethods above).
+func (f *file) lintFuncDoc(fn *ast.FuncDecl) {
+ if !ast.IsExported(fn.Name.Name) {
+ // func is unexported
+ return
+ }
+ kind := "function"
+ name := fn.Name.Name
+ if fn.Recv != nil {
+ // method
+ kind = "method"
+ recv := receiverName(fn)
+ if !ast.IsExported(recv) {
+ // receiver is unexported
+ return
+ }
+ if commonMethods[name] {
+ return
+ }
+ switch name {
+ case "Len", "Less", "Swap":
+ if f.sortable[recv] {
+ return
+ }
+ }
+ name = recv + "." + name
+ }
+ if fn.Doc == nil {
+ f.errorf(fn, 1, "exported %s %s should have comment", kind, name)
+ return
+ }
+ s := fn.Doc.Text()
+ prefix := fn.Name.Name + " "
+ if !strings.HasPrefix(s, prefix) {
+ f.errorf(fn.Doc, 1, `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix)
+ }
+}
+
+// lintValueSpecDoc examines package-global variables and constants.
+// It complains if they are not individually declared,
+// or if they are not suitably documented in the right form (unless they are in a block that is commented).
+func (f *file) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genDeclMissingComments map[*ast.GenDecl]bool) {
+ kind := "var"
+ if gd.Tok == token.CONST {
+ kind = "const"
+ }
+
+ if len(vs.Names) > 1 {
+ // Check that none are exported.
+ for _, n := range vs.Names {
+ if ast.IsExported(n.Name) {
+ f.errorf(vs, 1, "exported %s %s should have its own declaration", kind, n.Name)
+ return
+ }
+ }
+ }
+
+ // Only one name.
+ name := vs.Names[0].Name
+ if !ast.IsExported(name) {
+ return
+ }
+
+ if vs.Doc == nil {
+ if gd.Doc == nil && !genDeclMissingComments[gd] {
+ f.errorf(vs, 1, "exported %s %s should have comment", kind, name)
+ genDeclMissingComments[gd] = true
+ }
+ return
+ }
+ prefix := name + " "
+ if !strings.HasPrefix(vs.Doc.Text(), prefix) {
+ f.errorf(vs.Doc, 1, `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix)
+ }
+}
+
+// zeroLiteral is a set of ast.BasicLit values that are zero values.
+// It is not exhaustive.
+var zeroLiteral = map[string]bool{
+ "false": true, // bool
+ // runes
+ `'\x00'`: true,
+ `'\000'`: true,
+ // strings
+ `""`: true,
+ "``": true,
+ // numerics
+ "0": true,
+ "0.": true,
+ "0.0": true,
+ "0i": true,
+}
+
+// lintVarDecls examines variable declarations. It complains about declarations with
+// redundant LHS types that can be inferred from the RHS.
+func (f *file) lintVarDecls() {
+ var lastGen *ast.GenDecl // last GenDecl entered.
+
+ f.walk(func(node ast.Node) bool {
+ switch v := node.(type) {
+ case *ast.GenDecl:
+ if v.Tok != token.CONST && v.Tok != token.VAR {
+ return false
+ }
+ lastGen = v
+ return true
+ case *ast.ValueSpec:
+ if lastGen.Tok == token.CONST {
+ return false
+ }
+ if len(v.Names) > 1 || v.Type == nil || len(v.Values) == 0 {
+ return false
+ }
+ rhs := v.Values[0]
+ // An underscore var appears in a common idiom for compile-time interface satisfaction,
+ // as in "var _ Interface = (*Concrete)(nil)".
+ if isIdent(v.Names[0], "_") {
+ return false
+ }
+ // If the RHS is a zero value, suggest dropping it.
+ zero := false
+ if lit, ok := rhs.(*ast.BasicLit); ok {
+ zero = zeroLiteral[lit.Value]
+ } else if isIdent(rhs, "nil") {
+ zero = true
+ }
+ if zero {
+ f.errorf(rhs, 0.9, "should drop = %s from declaration of var %s; it is the zero value", f.render(rhs), v.Names[0])
+ return false
+ }
+ // If the LHS type is an interface, don't warn, since it is probably a
+ // concrete type on the RHS. Note that our feeble lexical check here
+ // will only pick up interface{} and other literal interface types;
+ // that covers most of the cases we care to exclude right now.
+ // TODO(dsymonds): Use typechecker to make this heuristic more accurate.
+ if _, ok := v.Type.(*ast.InterfaceType); ok {
+ return false
+ }
+ // If the RHS is an int literal, only warn if the LHS type is "int".
+ if lit, ok := rhs.(*ast.BasicLit); ok && lit.Kind == token.INT {
+ if !isIdent(v.Type, "int") {
+ return false
+ }
+ }
+ f.errorf(v.Type, 0.8, "should omit type %s from declaration of var %s; it will be inferred from the right-hand side", f.render(v.Type), v.Names[0])
+ return false
+ }
+ return true
+ })
+}
+
+// lintElses examines else blocks. It complains about any else block whose if block ends in a return.
+func (f *file) lintElses() {
+ // We don't want to flag if { } else if { } else { } constructions.
+ // They will appear as an IfStmt whose Else field is also an IfStmt.
+ // Record such a node so we ignore it when we visit it.
+ ignore := make(map[*ast.IfStmt]bool)
+
+ f.walk(func(node ast.Node) bool {
+ ifStmt, ok := node.(*ast.IfStmt)
+ if !ok || ifStmt.Else == nil {
+ return true
+ }
+ if ignore[ifStmt] {
+ return true
+ }
+ if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok {
+ ignore[elseif] = true
+ return true
+ }
+ if _, ok := ifStmt.Else.(*ast.BlockStmt); !ok {
+ // only care about elses without conditions
+ return true
+ }
+ if len(ifStmt.Body.List) == 0 {
+ return true
+ }
+ lastStmt := ifStmt.Body.List[len(ifStmt.Body.List)-1]
+ if _, ok := lastStmt.(*ast.ReturnStmt); ok {
+ f.errorf(ifStmt.Else, 1, "if block ends with a return statement, so drop this else and outdent its block")
+ }
+ return true
+ })
+}
+
+func receiverName(fn *ast.FuncDecl) string {
+ switch e := fn.Recv.List[0].Type.(type) {
+ case *ast.Ident:
+ return e.Name
+ case *ast.StarExpr:
+ return e.X.(*ast.Ident).Name
+ }
+ panic(fmt.Sprintf("unknown method receiver AST node type %T", fn.Recv.List[0].Type))
+}
+
+func (f *file) walk(fn func(ast.Node) bool) {
+ ast.Walk(walker(fn), f.f)
+}
+
+func (f *file) render(x interface{}) string {
+ var buf bytes.Buffer
+ if err := printer.Fprint(&buf, f.fset, x); err != nil {
+ panic(err)
+ }
+ return buf.String()
+}
+
+// walker adapts a function to satisfy the ast.Visitor interface.
+// The function return whether the walk should proceed into the node's children.
+type walker func(ast.Node) bool
+
+func (w walker) Visit(node ast.Node) ast.Visitor {
+ if w(node) {
+ return w
+ }
+ return nil
+}
+
+func isIdent(expr ast.Expr, ident string) bool {
+ id, ok := expr.(*ast.Ident)
+ return ok && id.Name == ident
+}
+
+// srcLine returns the complete line at p, including the terminating newline.
+func srcLine(src []byte, p token.Position) string {
+ // Run to end of line in both directions if not at line start/end.
+ lo, hi := p.Offset, p.Offset+1
+ for lo > 0 && src[lo-1] != '\n' {
+ lo--
+ }
+ for hi < len(src) && src[hi-1] != '\n' {
+ hi++
+ }
+ return string(src[lo:hi])
+}
diff --git a/lint_test.go b/lint_test.go
new file mode 100644
index 0000000..4a06bca
--- /dev/null
+++ b/lint_test.go
@@ -0,0 +1,184 @@
+// Copyright (c) 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package lint
+
+import (
+ "bytes"
+ "go/parser"
+ "go/printer"
+ "go/token"
+ "io/ioutil"
+ "path"
+ "regexp"
+ "strings"
+ "testing"
+)
+
+func TestAll(t *testing.T) {
+ l := new(Linter)
+
+ baseDir := "testdata"
+ fis, err := ioutil.ReadDir(baseDir)
+ if err != nil {
+ t.Fatalf("ioutil.ReadDir: %v", err)
+ }
+ if len(fis) == 0 {
+ t.Fatalf("no files in %v", baseDir)
+ }
+ for _, fi := range fis {
+ //t.Logf("Testing %s", fi.Name())
+ src, err := ioutil.ReadFile(path.Join(baseDir, fi.Name()))
+ if err != nil {
+ t.Fatalf("Failed reading %s: %v", fi.Name(), err)
+ }
+
+ ins := parseInstructions(t, fi.Name(), src)
+ if ins == nil {
+ t.Errorf("Test file %v does not have instructions", fi.Name())
+ continue
+ }
+
+ ps, err := l.Lint(fi.Name(), src)
+ if err != nil {
+ t.Errorf("Linting %s: %v", fi.Name(), err)
+ continue
+ }
+
+ for _, in := range ins {
+ ok := false
+ for i, p := range ps {
+ if p.Position.Line != in.Line {
+ continue
+ }
+ if in.Match.MatchString(p.Text) {
+ // remove this problem from ps
+ copy(ps[i:], ps[i+1:])
+ ps = ps[:len(ps)-1]
+
+ //t.Logf("/%v/ matched at %s:%d", in.Match, fi.Name(), in.Line)
+ ok = true
+ break
+ }
+ }
+ if !ok {
+ t.Errorf("Lint failed at %s:%d; /%v/ did not match", fi.Name(), in.Line, in.Match)
+ }
+ }
+ for _, p := range ps {
+ t.Errorf("Unexpected problem at %s:%d: %v", fi.Name(), p.Position.Line, p.Text)
+ }
+ }
+}
+
+type instruction struct {
+ Line int // the line number this applies to
+ Match *regexp.Regexp // what pattern to match
+}
+
+// parseInstructions parses instructions from the comments in a Go source file.
+// It returns nil if none were parsed.
+func parseInstructions(t *testing.T, filename string, src []byte) []instruction {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
+ if err != nil {
+ t.Fatalf("Test file %v does not parse: %v", filename, err)
+ }
+ var ins []instruction
+ for _, cg := range f.Comments {
+ ln := fset.Position(cg.Pos()).Line
+ raw := cg.Text()
+ for _, line := range strings.Split(raw, "\n") {
+ if line == "" || strings.HasPrefix(line, "#") {
+ continue
+ }
+ if line == "OK" && ins == nil {
+ // so our return value will be non-nil
+ ins = make([]instruction, 0)
+ continue
+ }
+ if strings.Contains(line, "MATCH") {
+ a, b := strings.Index(line, "/"), strings.LastIndex(line, "/")
+ if a == -1 || a == b {
+ t.Fatalf("Malformed match instruction %q at %v:%d", line, filename, ln)
+ }
+ pat := line[a+1 : b]
+ rx, err := regexp.Compile(pat)
+ if err != nil {
+ t.Fatalf("Bad match pattern %q at %v:%d: %v", pat, filename, ln, err)
+ }
+ ins = append(ins, instruction{
+ Line: ln,
+ Match: rx,
+ })
+ }
+ }
+ }
+ return ins
+}
+
+func render(fset *token.FileSet, x interface{}) string {
+ var buf bytes.Buffer
+ if err := printer.Fprint(&buf, fset, x); err != nil {
+ panic(err)
+ }
+ return buf.String()
+}
+
+func TestLine(t *testing.T) {
+ tests := []struct {
+ src string
+ offset int
+ want string
+ }{
+ {"single line file", 5, "single line file"},
+ {"single line file with newline\n", 5, "single line file with newline\n"},
+ {"first\nsecond\nthird\n", 2, "first\n"},
+ {"first\nsecond\nthird\n", 9, "second\n"},
+ {"first\nsecond\nthird\n", 14, "third\n"},
+ {"first\nsecond\nthird with no newline", 16, "third with no newline"},
+ {"first byte\n", 0, "first byte\n"},
+ }
+ for _, test := range tests {
+ got := srcLine([]byte(test.src), token.Position{Offset: test.offset})
+ if got != test.want {
+ t.Errorf("srcLine(%q, offset=%d) = %q, want %q", test.src, test.offset, got, test.want)
+ }
+ }
+}
+
+func TestLintName(t *testing.T) {
+ tests := []struct {
+ name, want string
+ }{
+ {"foo_bar", "fooBar"},
+ {"foo_bar_baz", "fooBarBaz"},
+ {"Foo_bar", "FooBar"},
+ {"foo_WiFi", "fooWiFi"},
+ {"id", "id"},
+ {"Id", "ID"},
+ {"foo_id", "fooID"},
+ {"fooId", "fooID"},
+ {"idFoo", "idFoo"},
+ {"midIdDle", "midIDDle"},
+ {"APIProxy", "APIProxy"},
+ {"ApiProxy", "APIProxy"},
+ {"apiProxy", "apiProxy"},
+ {"_Leading", "_Leading"},
+ {"___Leading", "_Leading"},
+ {"trailing_", "trailing"},
+ {"trailing___", "trailing"},
+ {"a_b", "aB"},
+ {"a__b", "aB"},
+ {"a___b", "aB"},
+ }
+ for _, test := range tests {
+ got := lintName(test.name)
+ if got != test.want {
+ t.Errorf("lintName(%q) = %q, want %q", test.name, got, test.want)
+ }
+ }
+}
diff --git a/testdata/4.go b/testdata/4.go
new file mode 100644
index 0000000..b991738
--- /dev/null
+++ b/testdata/4.go
@@ -0,0 +1,29 @@
+// # Test that exported names have correct comments.
+
+// Package pkg does something.
+package pkg
+
+type T int // MATCH /exported type T.*should.*comment/
+
+func (T) F() {} // MATCH /exported method T\.F.*should.*comment/
+
+// this is a nice type.
+// MATCH /comment.*exported type U.*should.*form.*"U ..."/
+type U string
+
+// this is a neat function.
+// MATCH /comment.*exported method U\.G.*should.*form.*"G ..."/
+func (U) G() {}
+
+// A V is a string.
+type V string
+
+// V.H has a pointer receiver
+
+func (*V) H() {} // MATCH /exported method V\.H.*should.*comment/
+
+var W = "foo" // MATCH /exported var W.*should.*comment/
+
+const X = "bar" // MATCH /exported const X.*should.*comment/
+
+var Y, Z int // MATCH /exported var Y.*own declaration/
diff --git a/testdata/5_test.go b/testdata/5_test.go
new file mode 100644
index 0000000..75c4f55
--- /dev/null
+++ b/testdata/5_test.go
@@ -0,0 +1,11 @@
+// # This file ends in _test.go, so we should not warn about doc comments.
+// OK
+
+package pkg
+
+import "testing"
+
+type H int
+
+func TestSomething(t *testing.T) {
+}
diff --git a/testdata/common-methods.go b/testdata/common-methods.go
new file mode 100644
index 0000000..d8cbe75
--- /dev/null
+++ b/testdata/common-methods.go
@@ -0,0 +1,16 @@
+// # Test that we don't nag for comments on common methods.
+// OK
+
+// Package pkg ...
+package pkg
+
+import "net/http"
+
+// T is ...
+type T int
+
+func (T) Error() string { return "" }
+func (T) String() string { return "" }
+func (T) ServeHTTP(w http.ResponseWriter, r *http.Request) {}
+func (T) Read(p []byte) (n int, err error) { return 0, nil }
+func (T) Write(p []byte) (n int, err error) { return 0, nil }
diff --git a/testdata/const-block.go b/testdata/const-block.go
new file mode 100644
index 0000000..d328caf
--- /dev/null
+++ b/testdata/const-block.go
@@ -0,0 +1,28 @@
+// # Test for docs in const blocks
+
+// Package foo ...
+package foo
+
+const (
+ // Prefix for something.
+ // MATCH /InlineWhatever.*form/
+ InlineWhatever = "blah"
+
+ Whatsit = "missing_comment" // MATCH /Whatsit.*should have comment/
+
+ // We should only warn once per block for missing comments,
+ // but always complain about malformed comments.
+
+ WhosYourDaddy = "another_missing_one"
+
+ // Something
+ // MATCH /WhatDoesHeDo.*form/
+ WhatDoesHeDo = "it's not a tumor!"
+)
+
+// These shouldn't need doc comments.
+const (
+ Alpha = "a"
+ Beta = "b"
+ Gamma = "g"
+)
diff --git a/testdata/else-multi.go b/testdata/else-multi.go
new file mode 100644
index 0000000..e3f5cf9
--- /dev/null
+++ b/testdata/else-multi.go
@@ -0,0 +1,18 @@
+// # Test of return+else warning; should not trigger on multi-branch if/else.
+// OK
+
+// Package pkg ...
+package pkg
+
+import "log"
+
+func f(x int) bool {
+ if x == 0 {
+ log.Print("x is zero")
+ } else if x > 0 {
+ return true
+ } else {
+ log.Printf("non-positive x: %d", x)
+ }
+ return false
+}
diff --git a/testdata/else.go b/testdata/else.go
new file mode 100644
index 0000000..5e9e254
--- /dev/null
+++ b/testdata/else.go
@@ -0,0 +1,15 @@
+// # Test of return+else warning.
+
+// Package pkg ...
+package pkg
+
+import "log"
+
+func f(x int) bool {
+ if x > 0 {
+ return true
+ } else { // MATCH /if.*return.*else.*outdent/
+ log.Printf("non-positive x: %d", x)
+ }
+ return false
+}
diff --git a/testdata/import-dot.go b/testdata/import-dot.go
new file mode 100644
index 0000000..3f9c904
--- /dev/null
+++ b/testdata/import-dot.go
@@ -0,0 +1,6 @@
+// # Test that dot imports are flagged.
+
+// Package pkg ...
+package pkg
+
+import . "fmt" // MATCH /dot import/
diff --git a/testdata/names.go b/testdata/names.go
new file mode 100644
index 0000000..b2bd163
--- /dev/null
+++ b/testdata/names.go
@@ -0,0 +1,19 @@
+// Test for name linting.
+
+// Package pkg_with_underscores ...
+package pkg_with_underscores // MATCH /underscore.*package name/
+
+var var_name int // MATCH /underscore.*var.*var_name/
+
+type t_wow struct { // MATCH /underscore.*type.*t_wow/
+ x_damn int // MATCH /underscore.*field.*x_damn/
+ Url *url.URL // MATCH /struct field.*Url.*URL/
+}
+
+const fooId = "blah" // MATCH /fooId.*fooID/
+
+func f_it() { // MATCH /underscore.*func.*f_it/
+ more_underscore := 4 // MATCH /underscore.*var.*more_underscore/
+
+ x := foo_proto.Blah{} // should be okay
+}
diff --git a/testdata/pkg-doc1.go b/testdata/pkg-doc1.go
new file mode 100644
index 0000000..8fdb683
--- /dev/null
+++ b/testdata/pkg-doc1.go
@@ -0,0 +1,3 @@
+// # Test of missing package comment.
+
+package foo // MATCH /should.*package comment.*unless/
diff --git a/testdata/pkg-doc2.go b/testdata/pkg-doc2.go
new file mode 100644
index 0000000..9f3c63e
--- /dev/null
+++ b/testdata/pkg-doc2.go
@@ -0,0 +1,5 @@
+// # Test of package comment in an incorrect form.
+
+// Some random package doc that isn't in the right form.
+// MATCH /package comment should.*form.*"Package testdata .*"/
+package testdata
diff --git a/testdata/pkg-doc3.go b/testdata/pkg-doc3.go
new file mode 100644
index 0000000..55f6e76
--- /dev/null
+++ b/testdata/pkg-doc3.go
@@ -0,0 +1,7 @@
+// # Test of block package comment.
+// OK
+
+/*
+Package foo is pretty sweet.
+*/
+package foo
diff --git a/testdata/pkg-doc4.go b/testdata/pkg-doc4.go
new file mode 100644
index 0000000..c85e333
--- /dev/null
+++ b/testdata/pkg-doc4.go
@@ -0,0 +1,7 @@
+// # Test of block package comment with leading space.
+
+/*
+ Package foo is pretty sweet.
+MATCH /package comment.*leading space/
+*/
+package foo
diff --git a/testdata/pkg-main.go b/testdata/pkg-main.go
new file mode 100644
index 0000000..f5295bc
--- /dev/null
+++ b/testdata/pkg-main.go
@@ -0,0 +1,5 @@
+// # Test of package comment for package main.
+// OK
+
+// This binary does something awesome.
+package main
diff --git a/testdata/sort.go b/testdata/sort.go
new file mode 100644
index 0000000..8b5a40f
--- /dev/null
+++ b/testdata/sort.go
@@ -0,0 +1,20 @@
+// # Test that we don't ask for comments on sort.Interface methods.
+
+// Package pkg ...
+package pkg
+
+// T is ...
+type T []int
+
+// Len by itself should get documented.
+
+func (t T) Len() int { return len(t) } // MATCH /exported method T\.Len.*should.*comment/
+
+// U is ...
+type U []int
+
+func (u U) Len() int { return len(u) }
+func (u U) Less(i, j int) bool { return u[i] < u[j] }
+func (u U) Swap(i, j int) { u[i], u[j] = u[j], u[i] }
+
+func (u U) Other() {} // MATCH /exported method U\.Other.*should.*comment/
diff --git a/testdata/var-decl.go b/testdata/var-decl.go
new file mode 100644
index 0000000..21c35e7
--- /dev/null
+++ b/testdata/var-decl.go
@@ -0,0 +1,38 @@
+// # Test for redundant type declaration.
+
+// Package foo ...
+package foo
+
+import "fmt"
+import "net/http"
+
+var mux *http.ServeMux = http.NewServeMux() // MATCH /should.*\*http\.ServeMux.*inferred/
+var myInt int = 7 // MATCH /should.*int.*myInt.*inferred/
+
+var myZeroInt int = 0 // MATCH /should.*= 0.*myZeroInt.*zero value/
+var myZeroFlt float32 = 0. // MATCH /should.*= 0\..*myZeroFlt.*zero value/
+var myZeroF64 float64 = 0.0 // MATCH /should.*= 0\..*myZeroF64.*zero value/
+var myZeroImg complex = 0i // MATCH /should.*= 0i.*myZeroImg.*zero value/
+var myZeroStr string = "" // MATCH /should.*= "".*myZeroStr.*zero value/
+var myZeroRaw string = `` // MATCH /should.*= ``.*myZeroRaw.*zero value/
+var myZeroPtr *Q = nil // MATCH /should.*= nil.*myZeroPtr.*zero value/
+var myZeroRune rune = '\x00' // MATCH /should.*= '\\x00'.*myZeroRune.*zero value/
+var myZeroRune2 rune = '\000' // MATCH /should.*= '\\000'.*myZeroRune2.*zero value/
+
+// No warning because there's no type on the LHS
+var x = 0
+
+// This shouldn't get a warning because there's no initial values.
+var str fmt.Stringer
+
+// No warning because this is a const.
+const x uint64 = 7
+
+// No warnings because the RHS is an ideal int, and the LHS is a different int type.
+var userID int64 = 1235
+
+// No warning because the LHS names an interface type.
+var data interface{} = googleIPs
+
+// No warning because it's a common idiom for interface satisfaction.
+var _ Server = (*serverImpl)(nil)