Permit linting of entire packages.
This gives the typechecker free reign in the open source world.
Fixes #43.
diff --git a/README b/README
index c763bdd..81d1f83 100644
--- a/README
+++ b/README
@@ -3,7 +3,7 @@
To install, run
go get github.com/golang/lint/golint
-Invoke golint with one or more filenames or directories.
+Invoke golint with one or more filenames, a directory, or a package name.
The output of this tool is a list of suggestions in Vim quickfix format,
which is accepted by lots of different editors.
diff --git a/golint/golint.go b/golint/golint.go
index c5fd1e7..318c061 100644
--- a/golint/golint.go
+++ b/golint/golint.go
@@ -10,25 +10,44 @@
import (
"flag"
"fmt"
+ "go/build"
"io/ioutil"
"os"
"path/filepath"
- "strings"
"github.com/golang/lint"
)
var minConfidence = flag.Float64("min_confidence", 0.8, "minimum confidence of a problem to print it")
+func usage() {
+ fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
+ fmt.Fprintf(os.Stderr, "\tgolint [flags] # runs on package in current directory\n")
+ fmt.Fprintf(os.Stderr, "\tgolint [flags] package\n")
+ fmt.Fprintf(os.Stderr, "\tgolint [flags] directory\n")
+ fmt.Fprintf(os.Stderr, "\tgolint [flags] files... # must be a single package\n")
+ fmt.Fprintf(os.Stderr, "Flags:\n")
+ flag.PrintDefaults()
+}
+
func main() {
+ flag.Usage = usage
flag.Parse()
- for _, filename := range flag.Args() {
- if isDir(filename) {
- lintDir(filename)
+ switch flag.NArg() {
+ case 0:
+ lintDir(".")
+ case 1:
+ arg := flag.Arg(0)
+ if isDir(arg) {
+ lintDir(arg)
+ } else if exists(arg) {
+ lintFiles(arg)
} else {
- lintFile(filename)
+ lintPackage(arg)
}
+ default:
+ lintFiles(flag.Args()...)
}
}
@@ -37,31 +56,64 @@
return err == nil && fi.IsDir()
}
-func lintFile(filename string) {
- src, err := ioutil.ReadFile(filename)
- if err != nil {
- fmt.Fprintln(os.Stderr, err)
- return
+func exists(filename string) bool {
+ _, err := os.Stat(filename)
+ return err == nil
+}
+
+func lintFiles(filenames ...string) {
+ files := make(map[string][]byte)
+ for _, filename := range filenames {
+ src, err := ioutil.ReadFile(filename)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ continue
+ }
+ files[filename] = src
}
l := new(lint.Linter)
- ps, err := l.Lint(filename, src)
+ ps, err := l.LintFiles(files)
if err != nil {
- fmt.Fprintf(os.Stderr, "%v:%v\n", filename, err)
+ fmt.Fprintf(os.Stderr, "%v\n", err)
return
}
for _, p := range ps {
if p.Confidence >= *minConfidence {
- fmt.Printf("%s:%v: %s\n", filename, p.Position, p.Text)
+ fmt.Printf("%v: %s\n", p.Position, p.Text)
}
}
}
func lintDir(dirname string) {
- filepath.Walk(dirname, func(path string, info os.FileInfo, err error) error {
- if err == nil && !info.IsDir() && strings.HasSuffix(path, ".go") {
- lintFile(path)
+ pkg, err := build.ImportDir(dirname, 0)
+ lintImportedPackage(pkg, err)
+}
+
+func lintPackage(pkgname string) {
+ pkg, err := build.Import(pkgname, "", 0)
+ lintImportedPackage(pkg, err)
+}
+
+func lintImportedPackage(pkg *build.Package, err error) {
+ if err != nil {
+ if _, nogo := err.(*build.NoGoError); nogo {
+ // Don't complain if the failure is due to no Go source files.
+ return
}
- return err
- })
+ fmt.Fprintln(os.Stderr, err)
+ return
+ }
+
+ var files []string
+ files = append(files, pkg.GoFiles...)
+ files = append(files, pkg.TestGoFiles...)
+ if pkg.Dir != "." {
+ for i, f := range files {
+ files[i] = filepath.Join(pkg.Dir, f)
+ }
+ }
+ // TODO(dsymonds): Do foo_test too (pkg.XTestGoFiles)
+
+ lintFiles(files...)
}
diff --git a/lint.go b/lint.go
index 2829be5..bfff95c 100644
--- a/lint.go
+++ b/lint.go
@@ -49,39 +49,57 @@
// 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
+ return l.LintFiles(map[string][]byte{filename: src})
}
-// file represents a file being linted.
-type file struct {
- fset *token.FileSet
- f *ast.File
- src []byte
- filename string
+// LintFiles lints a set of files.
+// The argument is a map of filename to source.
+func (l *Linter) LintFiles(files map[string][]byte) ([]Problem, error) {
+ if len(files) == 0 {
+ return nil, nil
+ }
+ pkg := &pkg{
+ fset: token.NewFileSet(),
+ files: make(map[string]*file),
+ }
+ for filename, src := range files {
+ f, err := parser.ParseFile(pkg.fset, filename, src, parser.ParseComments)
+ if err != nil {
+ return nil, err
+ }
+ // TODO(dsymonds): Check for package name mismatch.
+ pkg.files[filename] = &file{
+ pkg: pkg,
+ f: f,
+ fset: pkg.fset,
+ src: src,
+ filename: filename,
+ }
+ }
+ return pkg.lint(), nil
+}
+
+// pkg represents a package being linted.
+type pkg struct {
+ fset *token.FileSet
+ files map[string]*file
typesPkg *types.Package
typesInfo *types.Info
- // sortable is the set of types in the file that implement sort.Interface.
+ // sortable is the set of types in the package that implement sort.Interface.
sortable map[string]bool
- // main is whether this file is in a "main" package.
+ // main is whether this is a "main" package.
main bool
problems []Problem
}
-func (f *file) isTest() bool { return strings.HasSuffix(f.filename, "_test.go") }
-
-func (f *file) lint() []Problem {
- if err := f.typeCheck(); err != nil {
+func (p *pkg) lint() []Problem {
+ if err := p.typeCheck(); err != nil {
/* TODO(dsymonds): Consider reporting these errors when golint operates on entire packages.
if e, ok := err.(types.Error); ok {
- p := f.fset.Position(e.Pos)
+ pos := p.fset.Position(e.Pos)
conf := 1.0
if strings.Contains(e.Msg, "can't find import: ") {
// Golint is probably being run in a context that doesn't support
@@ -89,7 +107,7 @@
conf = 0
}
if conf > 0 {
- f.errorfAt(p, conf, category("typechecking"), e.Msg)
+ p.errorfAt(pos, conf, category("typechecking"), e.Msg)
}
// TODO(dsymonds): Abort if !e.Soft?
@@ -97,9 +115,28 @@
*/
}
- f.scanSortable()
- f.main = f.isMain()
+ p.scanSortable()
+ p.main = p.isMain()
+ for _, f := range p.files {
+ f.lint()
+ }
+ // TODO(dsymonds): sort?
+ return p.problems
+}
+
+// file represents a file being linted.
+type file struct {
+ pkg *pkg
+ f *ast.File
+ fset *token.FileSet
+ src []byte
+ filename string
+}
+
+func (f *file) isTest() bool { return strings.HasSuffix(f.filename, "_test.go") }
+
+func (f *file) lint() {
f.lintPackageComment()
f.lintImports()
f.lintBlankImports()
@@ -115,8 +152,6 @@
f.lintIncDec()
f.lintMake()
f.lintErrorReturn()
-
- return f.problems
}
type link string
@@ -125,15 +160,20 @@
// The variadic arguments may start with link and category types,
// and must end with a format string and any arguments.
func (f *file) errorf(n ast.Node, confidence float64, args ...interface{}) {
- p := f.fset.Position(n.Pos())
- f.errorfAt(p, confidence, args...)
+ pos := f.fset.Position(n.Pos())
+ if pos.Filename == "" {
+ pos.Filename = f.filename
+ }
+ f.pkg.errorfAt(pos, confidence, args...)
}
-func (f *file) errorfAt(p token.Position, confidence float64, args ...interface{}) {
+func (p *pkg) errorfAt(pos token.Position, confidence float64, args ...interface{}) {
problem := Problem{
- Position: p,
+ Position: pos,
Confidence: confidence,
- LineText: srcLine(f.src, p),
+ }
+ if pos.Filename != "" {
+ problem.LineText = srcLine(p.files[pos.Filename].src, pos)
}
argLoop:
@@ -151,12 +191,12 @@
problem.Text = fmt.Sprintf(args[0].(string), args[1:]...)
- f.problems = append(f.problems, problem)
+ p.problems = append(p.problems, problem)
}
var gcImporter = gcimporter.Import
-func (f *file) typeCheck() error {
+func (p *pkg) typeCheck() error {
// Do typechecking without errors so we do as much as possible.
config := &types.Config{
Import: gcImporter,
@@ -166,24 +206,30 @@
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
}
- pkg, err := config.Check(f.f.Name.Name, f.fset, []*ast.File{f.f}, info)
+ var anyFile *file
+ var astFiles []*ast.File
+ for _, f := range p.files {
+ anyFile = f
+ astFiles = append(astFiles, f.f)
+ }
+ pkg, err := config.Check(anyFile.f.Name.Name, p.fset, astFiles, info)
if err != nil {
return err
}
- f.typesPkg = pkg
- f.typesInfo = info
+ p.typesPkg = pkg
+ p.typesInfo = info
return err
}
-func (f *file) typeOf(expr ast.Expr) types.Type {
- if f.typesInfo == nil {
+func (p *pkg) typeOf(expr ast.Expr) types.Type {
+ if p.typesInfo == nil {
return nil
}
- return f.typesInfo.TypeOf(expr)
+ return p.typesInfo.TypeOf(expr)
}
-func (f *file) scanSortable() {
- f.sortable = make(map[string]bool)
+func (p *pkg) scanSortable() {
+ p.sortable = make(map[string]bool)
// bitfield for which methods exist on each type.
const (
@@ -193,25 +239,36 @@
)
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 := receiverType(fn)
- if i, ok := nmap[fn.Name.Name]; ok {
- has[recv] |= i
- }
- return false
- })
+ for _, f := range p.files {
+ 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 := receiverType(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
+ p.sortable[typ] = true
}
}
}
+func (p *pkg) isMain() bool {
+ for _, f := range p.files {
+ if f.isMain() {
+ return true
+ }
+ }
+ return false
+}
+
func (f *file) isMain() bool {
if f.f.Name.Name == "main" {
return true
@@ -250,7 +307,7 @@
// not documented.
func (f *file) lintBlankImports() {
// In package main and in tests, we don't complain about blank imports.
- if f.main || f.isTest() {
+ if f.pkg.main || f.isTest() {
return
}
@@ -626,7 +683,7 @@
}
switch name {
case "Len", "Less", "Swap":
- if f.sortable[recv] {
+ if f.pkg.sortable[recv] {
return
}
}
@@ -764,8 +821,8 @@
f.errorf(rhs, 0.9, category("zero-value"), "should drop = %s from declaration of var %s; it is the zero value", f.render(rhs), v.Names[0])
return false
}
- lhsTyp := f.typeOf(v.Type)
- rhsTyp := f.typeOf(rhs)
+ lhsTyp := f.pkg.typeOf(v.Type)
+ rhsTyp := f.pkg.typeOf(rhs)
if lhsTyp != nil && rhsTyp != nil && !types.Identical(lhsTyp, rhsTyp) {
// Assignment to a different type is not redundant.
return false