summaryrefslogtreecommitdiff
path: root/libgo/go/cmd/go/internal/vet/vetflag.go
blob: e3de48bbffaa4b4be2252b1d95ab197d2a10db94 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// Copyright 2017 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.

package vet

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"cmd/go/internal/base"
	"cmd/go/internal/cmdflag"
	"cmd/go/internal/str"
	"cmd/go/internal/work"
)

// go vet flag processing
//
// We query the flags of the tool specified by -vettool and accept any
// of those flags plus any flag valid for 'go build'. The tool must
// support -flags, which prints a description of its flags in JSON to
// stdout.

// vetTool specifies the vet command to run.
// Any tool that supports the (still unpublished) vet
// command-line protocol may be supplied; see
// golang.org/x/tools/go/analysis/unitchecker for one
// implementation. It is also used by tests.
//
// The default behavior (vetTool=="") runs 'go tool vet'.
//
var vetTool string // -vettool

func init() {
	// Extract -vettool by ad hoc flag processing:
	// its value is needed even before we can declare
	// the flags available during main flag processing.
	for i, arg := range os.Args {
		if arg == "-vettool" || arg == "--vettool" {
			if i+1 >= len(os.Args) {
				log.Fatalf("%s requires a filename", arg)
			}
			vetTool = os.Args[i+1]
			break
		} else if strings.HasPrefix(arg, "-vettool=") ||
			strings.HasPrefix(arg, "--vettool=") {
			vetTool = arg[strings.IndexByte(arg, '=')+1:]
			break
		}
	}
}

// vetFlags processes the command line, splitting it at the first non-flag
// into the list of flags and list of packages.
func vetFlags(usage func(), args []string) (passToVet, packageNames []string) {
	// Query the vet command for its flags.
	tool := vetTool
	if tool != "" {
		var err error
		tool, err = filepath.Abs(tool)
		if err != nil {
			log.Fatal(err)
		}
	} else {
		tool = base.Tool("vet")
	}
	out := new(bytes.Buffer)
	vetcmd := exec.Command(tool, "-flags")
	vetcmd.Stdout = out
	if err := vetcmd.Run(); err != nil {
		fmt.Fprintf(os.Stderr, "go vet: can't execute %s -flags: %v\n", tool, err)
		base.SetExitStatus(2)
		base.Exit()
	}
	var analysisFlags []struct {
		Name  string
		Bool  bool
		Usage string
	}
	if err := json.Unmarshal(out.Bytes(), &analysisFlags); err != nil {
		fmt.Fprintf(os.Stderr, "go vet: can't unmarshal JSON from %s -flags: %v", tool, err)
		base.SetExitStatus(2)
		base.Exit()
	}

	// Add vet's flags to vetflagDefn.
	//
	// Some flags, in particular -tags and -v, are known to vet but
	// also defined as build flags. This works fine, so we don't
	// define them here but use AddBuildFlags to init them.
	// However some, like -x, are known to the build but not to vet.
	var vetFlagDefn []*cmdflag.Defn
	for _, f := range analysisFlags {
		switch f.Name {
		case "tags", "v":
			continue
		}
		defn := &cmdflag.Defn{
			Name:       f.Name,
			PassToTest: true,
		}
		if f.Bool {
			defn.BoolVar = new(bool)
		}
		vetFlagDefn = append(vetFlagDefn, defn)
	}

	// Add build flags to vetFlagDefn.
	var cmd base.Command
	work.AddBuildFlags(&cmd, work.DefaultBuildFlags)
	// This flag declaration is a placeholder:
	// -vettool is actually parsed by the init function above.
	cmd.Flag.StringVar(new(string), "vettool", "", "path to vet tool binary")
	cmd.Flag.VisitAll(func(f *flag.Flag) {
		vetFlagDefn = append(vetFlagDefn, &cmdflag.Defn{
			Name:  f.Name,
			Value: f.Value,
		})
	})

	// Process args.
	goflags := cmdflag.FindGOFLAGS(vetFlagDefn)
	args = str.StringList(goflags, args)
	for i := 0; i < len(args); i++ {
		if !strings.HasPrefix(args[i], "-") {
			return args[:i], args[i:]
		}

		f, value, extraWord := cmdflag.Parse("vet", usage, vetFlagDefn, args, i)
		if f == nil {
			fmt.Fprintf(os.Stderr, "vet: flag %q not defined\n", args[i])
			fmt.Fprintf(os.Stderr, "Run \"go help vet\" for more information\n")
			base.SetExitStatus(2)
			base.Exit()
		}
		if i < len(goflags) {
			f.Present = false // Not actually present on the command line.
		}
		if f.Value != nil {
			if err := f.Value.Set(value); err != nil {
				base.Fatalf("invalid flag argument for -%s: %v", f.Name, err)
			}
			keep := f.PassToTest
			if !keep {
				// A build flag, probably one we don't want to pass to vet.
				// Can whitelist.
				switch f.Name {
				case "tags", "v":
					keep = true
				}
			}
			if !keep {
				// Flags known to the build but not to vet, so must be dropped.
				if extraWord {
					args = append(args[:i], args[i+2:]...)
					extraWord = false
				} else {
					args = append(args[:i], args[i+1:]...)
				}
				i--
			}
		}
		if extraWord {
			i++
		}
	}
	return args, nil
}

var vetUsage func()

func init() { vetUsage = usage } // break initialization cycle

func usage() {
	fmt.Fprintf(os.Stderr, "usage: %s\n", CmdVet.UsageLine)
	fmt.Fprintf(os.Stderr, "Run 'go help %s' for details.\n", CmdVet.LongName())

	// This part is additional to what (*Command).Usage does:
	cmd := "go tool vet"
	if vetTool != "" {
		cmd = vetTool
	}
	fmt.Fprintf(os.Stderr, "Run '%s -help' for the vet tool's flags.\n", cmd)

	base.SetExitStatus(2)
	base.Exit()
}