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// golint lints the Go source files named on its command line.
8package main
9
10import (
11	"flag"
12	"fmt"
13	"go/build"
14	"io/ioutil"
15	"os"
16	"path/filepath"
17	"strings"
18
19	"golang.org/x/lint"
20)
21
22var (
23	minConfidence = flag.Float64("min_confidence", 0.8, "minimum confidence of a problem to print it")
24	setExitStatus = flag.Bool("set_exit_status", false, "set exit status to 1 if any issues are found")
25	suggestions   int
26)
27
28func usage() {
29	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
30	fmt.Fprintf(os.Stderr, "\tgolint [flags] # runs on package in current directory\n")
31	fmt.Fprintf(os.Stderr, "\tgolint [flags] [packages]\n")
32	fmt.Fprintf(os.Stderr, "\tgolint [flags] [directories] # where a '/...' suffix includes all sub-directories\n")
33	fmt.Fprintf(os.Stderr, "\tgolint [flags] [files] # all must belong to a single package\n")
34	fmt.Fprintf(os.Stderr, "Flags:\n")
35	flag.PrintDefaults()
36}
37
38func main() {
39	flag.Usage = usage
40	flag.Parse()
41
42	if flag.NArg() == 0 {
43		lintDir(".")
44	} else {
45		// dirsRun, filesRun, and pkgsRun indicate whether golint is applied to
46		// directory, file or package targets. The distinction affects which
47		// checks are run. It is no valid to mix target types.
48		var dirsRun, filesRun, pkgsRun int
49		var args []string
50		for _, arg := range flag.Args() {
51			if strings.HasSuffix(arg, "/...") && isDir(arg[:len(arg)-len("/...")]) {
52				dirsRun = 1
53				for _, dirname := range allPackagesInFS(arg) {
54					args = append(args, dirname)
55				}
56			} else if isDir(arg) {
57				dirsRun = 1
58				args = append(args, arg)
59			} else if exists(arg) {
60				filesRun = 1
61				args = append(args, arg)
62			} else {
63				pkgsRun = 1
64				args = append(args, arg)
65			}
66		}
67
68		if dirsRun+filesRun+pkgsRun != 1 {
69			usage()
70			os.Exit(2)
71		}
72		switch {
73		case dirsRun == 1:
74			for _, dir := range args {
75				lintDir(dir)
76			}
77		case filesRun == 1:
78			lintFiles(args...)
79		case pkgsRun == 1:
80			for _, pkg := range importPaths(args) {
81				lintPackage(pkg)
82			}
83		}
84	}
85
86	if *setExitStatus && suggestions > 0 {
87		fmt.Fprintf(os.Stderr, "Found %d lint suggestions; failing.\n", suggestions)
88		os.Exit(1)
89	}
90}
91
92func isDir(filename string) bool {
93	fi, err := os.Stat(filename)
94	return err == nil && fi.IsDir()
95}
96
97func exists(filename string) bool {
98	_, err := os.Stat(filename)
99	return err == nil
100}
101
102func lintFiles(filenames ...string) {
103	files := make(map[string][]byte)
104	for _, filename := range filenames {
105		src, err := ioutil.ReadFile(filename)
106		if err != nil {
107			fmt.Fprintln(os.Stderr, err)
108			continue
109		}
110		files[filename] = src
111	}
112
113	l := new(lint.Linter)
114	ps, err := l.LintFiles(files)
115	if err != nil {
116		fmt.Fprintf(os.Stderr, "%v\n", err)
117		return
118	}
119	for _, p := range ps {
120		if p.Confidence >= *minConfidence {
121			fmt.Printf("%v: %s\n", p.Position, p.Text)
122			suggestions++
123		}
124	}
125}
126
127func lintDir(dirname string) {
128	pkg, err := build.ImportDir(dirname, 0)
129	lintImportedPackage(pkg, err)
130}
131
132func lintPackage(pkgname string) {
133	pkg, err := build.Import(pkgname, ".", 0)
134	lintImportedPackage(pkg, err)
135}
136
137func lintImportedPackage(pkg *build.Package, err error) {
138	if err != nil {
139		if _, nogo := err.(*build.NoGoError); nogo {
140			// Don't complain if the failure is due to no Go source files.
141			return
142		}
143		fmt.Fprintln(os.Stderr, err)
144		return
145	}
146
147	var files []string
148	files = append(files, pkg.GoFiles...)
149	files = append(files, pkg.CgoFiles...)
150	files = append(files, pkg.TestGoFiles...)
151	if pkg.Dir != "." {
152		for i, f := range files {
153			files[i] = filepath.Join(pkg.Dir, f)
154		}
155	}
156	// TODO(dsymonds): Do foo_test too (pkg.XTestGoFiles)
157
158	lintFiles(files...)
159}
160