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)