github.com/serversong/goreporter@v0.0.0-20200325104552-3cfaf44fd178/linters/simpler/lint/lintutil/util.go (about)

     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 lintutil provides helpers for writing linter command lines.
     8  package lintutil // import "github.com/360EntSecGroup-Skylar/goreporter/linters/simpler/lint/lintutil"
     9  
    10  import (
    11  	"errors"
    12  	"flag"
    13  	"fmt"
    14  	"go/build"
    15  	"go/parser"
    16  	"go/token"
    17  	"log"
    18  	"os"
    19  	"path/filepath"
    20  	"strconv"
    21  	"strings"
    22  
    23  	"github.com/360EntSecGroup-Skylar/goreporter/linters/simpler/lint"
    24  
    25  	"github.com/kisielk/gotool"
    26  	"golang.org/x/tools/go/loader"
    27  )
    28  
    29  func usage(name string, flags *flag.FlagSet) func() {
    30  	return func() {
    31  		fmt.Fprintf(os.Stderr, "Usage of %s:\n", name)
    32  		fmt.Fprintf(os.Stderr, "\t%s [flags] # runs on package in current directory\n", name)
    33  		fmt.Fprintf(os.Stderr, "\t%s [flags] packages\n", name)
    34  		fmt.Fprintf(os.Stderr, "\t%s [flags] directory\n", name)
    35  		fmt.Fprintf(os.Stderr, "\t%s [flags] files... # must be a single package\n", name)
    36  		fmt.Fprintf(os.Stderr, "Flags:\n")
    37  		flags.PrintDefaults()
    38  	}
    39  }
    40  
    41  type runner struct {
    42  	checker lint.Checker
    43  	tags    []string
    44  	ignores []lint.Ignore
    45  	version int
    46  }
    47  
    48  func (runner runner) resolveRelative(importPaths []string) (goFiles bool, err error) {
    49  	if len(importPaths) == 0 {
    50  		return false, nil
    51  	}
    52  	if strings.HasSuffix(importPaths[0], ".go") {
    53  		// User is specifying a package in terms of .go files, don't resolve
    54  		return true, nil
    55  	}
    56  	wd, err := os.Getwd()
    57  	if err != nil {
    58  		return false, err
    59  	}
    60  	ctx := build.Default
    61  	ctx.BuildTags = runner.tags
    62  	for i, path := range importPaths {
    63  		bpkg, err := ctx.Import(path, wd, build.FindOnly)
    64  		if err != nil {
    65  			return false, fmt.Errorf("can't load package %q: %v", path, err)
    66  		}
    67  		importPaths[i] = bpkg.ImportPath
    68  	}
    69  	return false, nil
    70  }
    71  
    72  func parseIgnore(s string) ([]lint.Ignore, error) {
    73  	var out []lint.Ignore
    74  	if len(s) == 0 {
    75  		return nil, nil
    76  	}
    77  	for _, part := range strings.Fields(s) {
    78  		p := strings.Split(part, ":")
    79  		if len(p) != 2 {
    80  			return nil, errors.New("malformed ignore string")
    81  		}
    82  		path := p[0]
    83  		checks := strings.Split(p[1], ",")
    84  		out = append(out, lint.Ignore{Pattern: path, Checks: checks})
    85  	}
    86  	return out, nil
    87  }
    88  
    89  type versionFlag int
    90  
    91  func (v *versionFlag) String() string {
    92  	return fmt.Sprintf("1.%d", *v)
    93  }
    94  
    95  func (v *versionFlag) Set(s string) error {
    96  	if len(s) < 3 {
    97  		return errors.New("invalid Go version")
    98  	}
    99  	if s[0] != '1' {
   100  		return errors.New("invalid Go version")
   101  	}
   102  	if s[1] != '.' {
   103  		return errors.New("invalid Go version")
   104  	}
   105  	i, err := strconv.Atoi(s[2:])
   106  	*v = versionFlag(i)
   107  	return err
   108  }
   109  
   110  func (v *versionFlag) Get() interface{} {
   111  	return int(*v)
   112  }
   113  
   114  func FlagSet(name string) *flag.FlagSet {
   115  	flags := flag.NewFlagSet("", flag.ExitOnError)
   116  	flags.Usage = usage(name, flags)
   117  	flags.Float64("min_confidence", 0, "Deprecated; use -ignore instead")
   118  	flags.String("tags", "", "List of `build tags`")
   119  	flags.String("ignore", "", "Space separated list of checks to ignore, in the following format: 'import/path/file.go:Check1,Check2,...' Both the import path and file name sections support globbing, e.g. 'os/exec/*_test.go'")
   120  	flags.Bool("tests", true, "Include tests")
   121  
   122  	tags := build.Default.ReleaseTags
   123  	v := tags[len(tags)-1][2:]
   124  	version := new(versionFlag)
   125  	if err := version.Set(v); err != nil {
   126  		panic(fmt.Sprintf("internal error: %s", err))
   127  	}
   128  
   129  	flags.Var(version, "go", "Target Go `version` in the format '1.x'")
   130  	return flags
   131  }
   132  
   133  func ProcessFlagSet(c lint.Checker, fs *flag.FlagSet) (results []string) {
   134  	tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string)
   135  	ignore := fs.Lookup("ignore").Value.(flag.Getter).Get().(string)
   136  	tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool)
   137  	version := fs.Lookup("go").Value.(flag.Getter).Get().(int)
   138  
   139  	ps, lprog, err := Lint(c, fs.Args(), &Options{
   140  		Tags:      strings.Fields(tags),
   141  		LintTests: tests,
   142  		Ignores:   ignore,
   143  		GoVersion: version,
   144  	})
   145  	if err != nil {
   146  		l := log.New(os.Stderr, "", log.LstdFlags)
   147  		l.Println(err)
   148  	}
   149  
   150  	for _, p := range ps {
   151  		pos := lprog.Fset.Position(p.Position)
   152  		results = append(results, fmt.Sprintf("%v: %s", relativePositionString(pos), p.Text))
   153  	}
   154  	return
   155  }
   156  
   157  type Options struct {
   158  	Tags      []string
   159  	LintTests bool
   160  	Ignores   string
   161  	GoVersion int
   162  }
   163  
   164  func Lint(c lint.Checker, pkgs []string, opt *Options) ([]lint.Problem, *loader.Program, error) {
   165  	// TODO(dh): Instead of returning the loader.Program, we should
   166  	// store token.Position instead of token.Pos in lint.Problem.
   167  	if opt == nil {
   168  		opt = &Options{}
   169  	}
   170  	ignores, err := parseIgnore(opt.Ignores)
   171  	if err != nil {
   172  		return nil, nil, err
   173  	}
   174  	runner := &runner{
   175  		checker: c,
   176  		tags:    opt.Tags,
   177  		ignores: ignores,
   178  		version: opt.GoVersion,
   179  	}
   180  	paths := gotool.ImportPaths(pkgs)
   181  	goFiles, err := runner.resolveRelative(paths)
   182  	if err != nil {
   183  		return nil, nil, err
   184  	}
   185  	ctx := build.Default
   186  	ctx.BuildTags = runner.tags
   187  	conf := &loader.Config{
   188  		Build:      &ctx,
   189  		ParserMode: parser.ParseComments,
   190  		ImportPkgs: map[string]bool{},
   191  	}
   192  	if goFiles {
   193  		conf.CreateFromFilenames("adhoc", paths...)
   194  	} else {
   195  		for _, path := range paths {
   196  			conf.ImportPkgs[path] = opt.LintTests
   197  		}
   198  	}
   199  	lprog, err := conf.Load()
   200  	if err != nil {
   201  		return nil, nil, err
   202  	}
   203  	return runner.lint(lprog), lprog, nil
   204  }
   205  
   206  func shortPath(path string) string {
   207  	cwd, err := os.Getwd()
   208  	if err != nil {
   209  		return path
   210  	}
   211  	if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) {
   212  		return rel
   213  	}
   214  	return path
   215  }
   216  
   217  func relativePositionString(pos token.Position) string {
   218  	s := shortPath(pos.Filename)
   219  	if pos.IsValid() {
   220  		if s != "" {
   221  			s += ":"
   222  		}
   223  		s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)
   224  	}
   225  	if s == "" {
   226  		s = "-"
   227  	}
   228  	return s
   229  }
   230  
   231  func ProcessArgs(name string, c lint.Checker, args []string) {
   232  	flags := FlagSet(name)
   233  	flags.Parse(args)
   234  
   235  	ProcessFlagSet(c, flags)
   236  }
   237  
   238  func (runner *runner) lint(lprog *loader.Program) []lint.Problem {
   239  	l := &lint.Linter{
   240  		Checker:   runner.checker,
   241  		Ignores:   runner.ignores,
   242  		GoVersion: runner.version,
   243  	}
   244  	return l.Lint(lprog)
   245  }