summaryrefslogtreecommitdiffstats
path: root/src/cmd/go/internal/vet/vetflag.go
blob: b5b3c462ff2acced306e495a173b43be76fd579b (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
// 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"
	"errors"
	"flag"
	"fmt"
	exec "internal/execabs"
	"log"
	"os"
	"path/filepath"
	"strings"

	"cmd/go/internal/base"
	"cmd/go/internal/cmdflag"
	"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() {
	work.AddBuildFlags(CmdVet, work.DefaultBuildFlags)
	CmdVet.Flag.StringVar(&vetTool, "vettool", "", "")
}

func parseVettoolFlag(args []string) {
	// 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 args {
		if arg == "-vettool" || arg == "--vettool" {
			if i+1 >= len(args) {
				log.Fatalf("%s requires a filename", arg)
			}
			vetTool = args[i+1]
			return
		} else if strings.HasPrefix(arg, "-vettool=") ||
			strings.HasPrefix(arg, "--vettool=") {
			vetTool = arg[strings.IndexByte(arg, '=')+1:]
			return
		}
	}
}

// vetFlags processes the command line, splitting it at the first non-flag
// into the list of flags and list of packages.
func vetFlags(args []string) (passToVet, packageNames []string) {
	parseVettoolFlag(args)

	// Query the vet command for its flags.
	var tool string
	if vetTool == "" {
		tool = base.Tool("vet")
	} else {
		var err error
		tool, err = filepath.Abs(vetTool)
		if err != nil {
			log.Fatal(err)
		}
	}
	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 CmdVet.Flag.
	//
	// Some flags, in particular -tags and -v, are known to vet but
	// also defined as build flags. This works fine, so we omit duplicates here.
	// However some, like -x, are known to the build but not to vet.
	isVetFlag := make(map[string]bool, len(analysisFlags))
	cf := CmdVet.Flag
	for _, f := range analysisFlags {
		isVetFlag[f.Name] = true
		if cf.Lookup(f.Name) == nil {
			if f.Bool {
				cf.Bool(f.Name, false, "")
			} else {
				cf.String(f.Name, "", "")
			}
		}
	}

	// Record the set of vet tool flags set by GOFLAGS. We want to pass them to
	// the vet tool, but only if they aren't overridden by an explicit argument.
	base.SetFromGOFLAGS(&CmdVet.Flag)
	addFromGOFLAGS := map[string]bool{}
	CmdVet.Flag.Visit(func(f *flag.Flag) {
		if isVetFlag[f.Name] {
			addFromGOFLAGS[f.Name] = true
		}
	})

	explicitFlags := make([]string, 0, len(args))
	for len(args) > 0 {
		f, remainingArgs, err := cmdflag.ParseOne(&CmdVet.Flag, args)

		if errors.Is(err, flag.ErrHelp) {
			exitWithUsage()
		}

		if errors.Is(err, cmdflag.ErrFlagTerminator) {
			// All remaining args must be package names, but the flag terminator is
			// not included.
			packageNames = remainingArgs
			break
		}

		if nf := (cmdflag.NonFlagError{}); errors.As(err, &nf) {
			// Everything from here on out — including the argument we just consumed —
			// must be a package name.
			packageNames = args
			break
		}

		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			exitWithUsage()
		}

		if isVetFlag[f.Name] {
			// Forward the raw arguments rather than cleaned equivalents, just in
			// case the vet tool parses them idiosyncratically.
			explicitFlags = append(explicitFlags, args[:len(args)-len(remainingArgs)]...)

			// This flag has been overridden explicitly, so don't forward its implicit
			// value from GOFLAGS.
			delete(addFromGOFLAGS, f.Name)
		}

		args = remainingArgs
	}

	// Prepend arguments from GOFLAGS before other arguments.
	CmdVet.Flag.Visit(func(f *flag.Flag) {
		if addFromGOFLAGS[f.Name] {
			passToVet = append(passToVet, fmt.Sprintf("-%s=%s", f.Name, f.Value))
		}
	})
	passToVet = append(passToVet, explicitFlags...)
	return passToVet, packageNames
}

func exitWithUsage() {
	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 a full list of flags and analyzers.\n", cmd)
	fmt.Fprintf(os.Stderr, "Run '%s -help' for an overview.\n", cmd)

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