GoPLS Viewer

Home|gopls/go/analysis/internal/analysisflags/flags.go
1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package analysisflags defines helpers for processing flags of
6// analysis driver tools.
7package analysisflags
8
9import (
10    "crypto/sha256"
11    "encoding/gob"
12    "encoding/json"
13    "flag"
14    "fmt"
15    "go/token"
16    "io"
17    "io/ioutil"
18    "log"
19    "os"
20    "strconv"
21    "strings"
22
23    "golang.org/x/tools/go/analysis"
24)
25
26// flags common to all {single,multi,unit}checkers.
27var (
28    JSON    = false // -json
29    Context = -1    // -c=N: if N>0, display offending line plus N lines of context
30)
31
32// Parse creates a flag for each of the analyzer's flags,
33// including (in multi mode) a flag named after the analyzer,
34// parses the flags, then filters and returns the list of
35// analyzers enabled by flags.
36//
37// The result is intended to be passed to unitchecker.Run or checker.Run.
38// Use in unitchecker.Run will gob.Register all fact types for the returned
39// graph of analyzers but of course not the ones only reachable from
40// dropped analyzers. To avoid inconsistency about which gob types are
41// registered from run to run, Parse itself gob.Registers all the facts
42// only reachable from dropped analyzers.
43// This is not a particularly elegant API, but this is an internal package.
44func Parse(analyzers []*analysis.Analyzermulti bool) []*analysis.Analyzer {
45    // Connect each analysis flag to the command line as -analysis.flag.
46    enabled := make(map[*analysis.Analyzer]*triState)
47    for _a := range analyzers {
48        var prefix string
49
50        // Add -NAME flag to enable it.
51        if multi {
52            prefix = a.Name + "."
53
54            enable := new(triState)
55            enableUsage := "enable " + a.Name + " analysis"
56            flag.Var(enablea.NameenableUsage)
57            enabled[a] = enable
58        }
59
60        a.Flags.VisitAll(func(f *flag.Flag) {
61            if !multi && flag.Lookup(f.Name) != nil {
62                log.Printf("%s flag -%s would conflict with driver; skipping"a.Namef.Name)
63                return
64            }
65
66            name := prefix + f.Name
67            flag.Var(f.Valuenamef.Usage)
68        })
69    }
70
71    // standard flags: -flags, -V.
72    printflags := flag.Bool("flags"false"print analyzer flags in JSON")
73    addVersionFlag()
74
75    // flags common to all checkers
76    flag.BoolVar(&JSON"json"JSON"emit JSON output")
77    flag.IntVar(&Context"c"Context`display offending line with this many lines of context`)
78
79    // Add shims for legacy vet flags to enable existing
80    // scripts that run vet to continue to work.
81    _ = flag.Bool("source"false"no effect (deprecated)")
82    _ = flag.Bool("v"false"no effect (deprecated)")
83    _ = flag.Bool("all"false"no effect (deprecated)")
84    _ = flag.String("tags""""no effect (deprecated)")
85    for oldnew := range vetLegacyFlags {
86        newFlag := flag.Lookup(new)
87        if newFlag != nil && flag.Lookup(old) == nil {
88            flag.Var(newFlag.Valueold"deprecated alias for -"+new)
89        }
90    }
91
92    flag.Parse() // (ExitOnError)
93
94    // -flags: print flags so that go vet knows which ones are legitimate.
95    if *printflags {
96        printFlags()
97        os.Exit(0)
98    }
99
100    everything := expand(analyzers)
101
102    // If any -NAME flag is true,  run only those analyzers. Otherwise,
103    // if any -NAME flag is false, run all but those analyzers.
104    if multi {
105        var hasTruehasFalse bool
106        for _ts := range enabled {
107            switch *ts {
108            case setTrue:
109                hasTrue = true
110            case setFalse:
111                hasFalse = true
112            }
113        }
114
115        var keep []*analysis.Analyzer
116        if hasTrue {
117            for _a := range analyzers {
118                if *enabled[a] == setTrue {
119                    keep = append(keepa)
120                }
121            }
122            analyzers = keep
123        } else if hasFalse {
124            for _a := range analyzers {
125                if *enabled[a] != setFalse {
126                    keep = append(keepa)
127                }
128            }
129            analyzers = keep
130        }
131    }
132
133    // Register fact types of skipped analyzers
134    // in case we encounter them in imported files.
135    kept := expand(analyzers)
136    for a := range everything {
137        if !kept[a] {
138            for _f := range a.FactTypes {
139                gob.Register(f)
140            }
141        }
142    }
143
144    return analyzers
145}
146
147func expand(analyzers []*analysis.Analyzer) map[*analysis.Analyzer]bool {
148    seen := make(map[*analysis.Analyzer]bool)
149    var visitAll func([]*analysis.Analyzer)
150    visitAll = func(analyzers []*analysis.Analyzer) {
151        for _a := range analyzers {
152            if !seen[a] {
153                seen[a] = true
154                visitAll(a.Requires)
155            }
156        }
157    }
158    visitAll(analyzers)
159    return seen
160}
161
162func printFlags() {
163    type jsonFlag struct {
164        Name  string
165        Bool  bool
166        Usage string
167    }
168    var flags []jsonFlag = nil
169    flag.VisitAll(func(f *flag.Flag) {
170        // Don't report {single,multi}checker debugging
171        // flags or fix as these have no effect on unitchecker
172        // (as invoked by 'go vet').
173        switch f.Name {
174        case "debug""cpuprofile""memprofile""trace""fix":
175            return
176        }
177
178        bok := f.Value.(interface{ IsBoolFlag() bool })
179        isBool := ok && b.IsBoolFlag()
180        flags = append(flagsjsonFlag{f.NameisBoolf.Usage})
181    })
182    dataerr := json.MarshalIndent(flags"""\t")
183    if err != nil {
184        log.Fatal(err)
185    }
186    os.Stdout.Write(data)
187}
188
189// addVersionFlag registers a -V flag that, if set,
190// prints the executable version and exits 0.
191//
192// If the -V flag already exists — for example, because it was already
193// registered by a call to cmd/internal/objabi.AddVersionFlag — then
194// addVersionFlag does nothing.
195func addVersionFlag() {
196    if flag.Lookup("V") == nil {
197        flag.Var(versionFlag{}, "V""print version and exit")
198    }
199}
200
201// versionFlag minimally complies with the -V protocol required by "go vet".
202type versionFlag struct{}
203
204func (versionFlagIsBoolFlag() bool { return true }
205func (versionFlagGet() interface{} { return nil }
206func (versionFlagString() string   { return "" }
207func (versionFlagSet(s stringerror {
208    if s != "full" {
209        log.Fatalf("unsupported flag value: -V=%s"s)
210    }
211
212    // This replicates the minimal subset of
213    // cmd/internal/objabi.AddVersionFlag, which is private to the
214    // go tool yet forms part of our command-line interface.
215    // TODO(adonovan): clarify the contract.
216
217    // Print the tool version so the build system can track changes.
218    // Formats:
219    //   $progname version devel ... buildID=...
220    //   $progname version go1.9.1
221    progname := os.Args[0]
222    ferr := os.Open(progname)
223    if err != nil {
224        log.Fatal(err)
225    }
226    h := sha256.New()
227    if _err := io.Copy(hf); err != nil {
228        log.Fatal(err)
229    }
230    f.Close()
231    fmt.Printf("%s version devel comments-go-here buildID=%02x\n",
232        prognamestring(h.Sum(nil)))
233    os.Exit(0)
234    return nil
235}
236
237// A triState is a boolean that knows whether
238// it has been set to either true or false.
239// It is used to identify whether a flag appears;
240// the standard boolean flag cannot
241// distinguish missing from unset.
242// It also satisfies flag.Value.
243type triState int
244
245const (
246    unset triState = iota
247    setTrue
248    setFalse
249)
250
251func triStateFlag(name stringvalue triStateusage string) *triState {
252    flag.Var(&valuenameusage)
253    return &value
254}
255
256// triState implements flag.Value, flag.Getter, and flag.boolFlag.
257// They work like boolean flags: we can say vet -printf as well as vet -printf=true
258func (ts *triStateGet() interface{} {
259    return *ts == setTrue
260}
261
262func (ts triStateisTrue() bool {
263    return ts == setTrue
264}
265
266func (ts *triStateSet(value stringerror {
267    berr := strconv.ParseBool(value)
268    if err != nil {
269        // This error message looks poor but package "flag" adds
270        // "invalid boolean value %q for -NAME: %s"
271        return fmt.Errorf("want true or false")
272    }
273    if b {
274        *ts = setTrue
275    } else {
276        *ts = setFalse
277    }
278    return nil
279}
280
281func (ts *triStateString() string {
282    switch *ts {
283    case unset:
284        return "true"
285    case setTrue:
286        return "true"
287    case setFalse:
288        return "false"
289    }
290    panic("not reached")
291}
292
293func (ts triStateIsBoolFlag() bool {
294    return true
295}
296
297// Legacy flag support
298
299// vetLegacyFlags maps flags used by legacy vet to their corresponding
300// new names. The old names will continue to work.
301var vetLegacyFlags = map[string]string{
302    // Analyzer name changes
303    "bool":       "bools",
304    "buildtags":  "buildtag",
305    "methods":    "stdmethods",
306    "rangeloops""loopclosure",
307
308    // Analyzer flags
309    "compositewhitelist":  "composites.whitelist",
310    "printfuncs":          "printf.funcs",
311    "shadowstrict":        "shadow.strict",
312    "unusedfuncs":         "unusedresult.funcs",
313    "unusedstringmethods""unusedresult.stringmethods",
314}
315
316// ---- output helpers common to all drivers ----
317
318// PrintPlain prints a diagnostic in plain text form,
319// with context specified by the -c flag.
320func PrintPlain(fset *token.FileSetdiag analysis.Diagnostic) {
321    posn := fset.Position(diag.Pos)
322    fmt.Fprintf(os.Stderr"%s: %s\n"posndiag.Message)
323
324    // -c=N: show offending line plus N lines of context.
325    if Context >= 0 {
326        posn := fset.Position(diag.Pos)
327        end := fset.Position(diag.End)
328        if !end.IsValid() {
329            end = posn
330        }
331        data_ := ioutil.ReadFile(posn.Filename)
332        lines := strings.Split(string(data), "\n")
333        for i := posn.Line - Contexti <= end.Line+Contexti++ {
334            if 1 <= i && i <= len(lines) {
335                fmt.Fprintf(os.Stderr"%d\t%s\n"ilines[i-1])
336            }
337        }
338    }
339}
340
341// A JSONTree is a mapping from package ID to analysis name to result.
342// Each result is either a jsonError or a list of JSONDiagnostic.
343type JSONTree map[string]map[string]interface{}
344
345// A TextEdit describes the replacement of a portion of a file.
346// Start and End are zero-based half-open indices into the original byte
347// sequence of the file, and New is the new text.
348type JSONTextEdit struct {
349    Filename string `json:"filename"`
350    Start    int    `json:"start"`
351    End      int    `json:"end"`
352    New      string `json:"new"`
353}
354
355// A JSONSuggestedFix describes an edit that should be applied as a whole or not
356// at all. It might contain multiple TextEdits/text_edits if the SuggestedFix
357// consists of multiple non-contiguous edits.
358type JSONSuggestedFix struct {
359    Message string         `json:"message"`
360    Edits   []JSONTextEdit `json:"edits"`
361}
362
363// A JSONDiagnostic can be used to encode and decode analysis.Diagnostics to and
364// from JSON.
365// TODO(matloob): Should the JSON diagnostics contain ranges?
366// If so, how should they be formatted?
367type JSONDiagnostic struct {
368    Category       string             `json:"category,omitempty"`
369    Posn           string             `json:"posn"`
370    Message        string             `json:"message"`
371    SuggestedFixes []JSONSuggestedFix `json:"suggested_fixes,omitempty"`
372}
373
374// Add adds the result of analysis 'name' on package 'id'.
375// The result is either a list of diagnostics or an error.
376func (tree JSONTreeAdd(fset *token.FileSetidname stringdiags []analysis.Diagnosticerr error) {
377    var v interface{}
378    if err != nil {
379        type jsonError struct {
380            Err string `json:"error"`
381        }
382        v = jsonError{err.Error()}
383    } else if len(diags) > 0 {
384        diagnostics := make([]JSONDiagnostic0len(diags))
385        for _f := range diags {
386            var fixes []JSONSuggestedFix
387            for _fix := range f.SuggestedFixes {
388                var edits []JSONTextEdit
389                for _edit := range fix.TextEdits {
390                    edits = append(editsJSONTextEdit{
391                        Filenamefset.Position(edit.Pos).Filename,
392                        Start:    fset.Position(edit.Pos).Offset,
393                        End:      fset.Position(edit.End).Offset,
394                        New:      string(edit.NewText),
395                    })
396                }
397                fixes = append(fixesJSONSuggestedFix{
398                    Messagefix.Message,
399                    Edits:   edits,
400                })
401            }
402            jdiag := JSONDiagnostic{
403                Category:       f.Category,
404                Posn:           fset.Position(f.Pos).String(),
405                Message:        f.Message,
406                SuggestedFixesfixes,
407            }
408            diagnostics = append(diagnosticsjdiag)
409        }
410        v = diagnostics
411    }
412    if v != nil {
413        mok := tree[id]
414        if !ok {
415            m = make(map[string]interface{})
416            tree[id] = m
417        }
418        m[name] = v
419    }
420}
421
422func (tree JSONTreePrint() {
423    dataerr := json.MarshalIndent(tree"""\t")
424    if err != nil {
425        log.Panicf("internal error: JSON marshaling failed: %v"err)
426    }
427    fmt.Printf("%s\n"data)
428}
429
MembersX
triStateFlag.value
triStateFlag.usage
triState.Get
ioutil
Parse.RangeStmt_2671.old
triState.Get.ts
PrintPlain.BlockStmt.end
json
printFlags.err
JSONTree.Add.BlockStmt.RangeStmt_10678.f
unset
JSONTree
triState.Set.value
triState.IsBoolFlag
PrintPlain.BlockStmt.posn
fmt
printFlags.jsonFlag
PrintPlain.BlockStmt.data
Parse.RangeStmt_2671.new
triState.isTrue.ts
expand.seen
JSONTextEdit.New
JSONDiagnostic.SuggestedFixes
io
PrintPlain
JSONTree.Add.BlockStmt.RangeStmt_10678.BlockStmt.jdiag
Parse.BlockStmt.BlockStmt.RangeStmt_3399.a
JSONSuggestedFix.Message
JSONSuggestedFix
versionFlag.Set._
triState.String.ts
versionFlag.Set.f
JSONTree.Add.tree
Parse.BlockStmt.hasFalse
versionFlag.Set.s
PrintPlain.fset
JSONDiagnostic.Posn
JSONTree.Print.data
Parse.RangeStmt_3798.BlockStmt.BlockStmt.RangeStmt_3845.f
triState.Set
JSONTree.Add.BlockStmt.RangeStmt_10678.BlockStmt.RangeStmt_10739.BlockStmt.RangeStmt_10811.edit
printFlags.jsonFlag.Name
triStateFlag
Parse.BlockStmt.BlockStmt.RangeStmt_3546.a
printFlags.jsonFlag.Bool
Parse
Parse.RangeStmt_1513.BlockStmt.BlockStmt.enable
flag
versionFlag.Set.err
JSONTextEdit.Start
JSONTree.Add.diags
Parse.RangeStmt_2671.BlockStmt.newFlag
JSONTree.Add.id
expand.analyzers
printFlags.data
triState.Set.ts
JSONTextEdit
JSONTextEdit.End
JSONTree.Print
Parse.BlockStmt.keep
Parse.kept
versionFlag
versionFlag.Set
triState.IsBoolFlag.ts
JSONSuggestedFix.Edits
Parse.RangeStmt_1513.BlockStmt.prefix
expand.BlockStmt.RangeStmt_4142.a
Parse.everything
JSONTree.Add
JSONDiagnostic
JSONTree.Print.err
triState.Set.err
JSONTree.Print.tree
token
versionFlag.Get
triState.Set.b
JSONTree.Add.BlockStmt.diagnostics
JSONTree.Add.BlockStmt.RangeStmt_10678.BlockStmt.fixes
log
Parse.BlockStmt.RangeStmt_3220.ts
printFlags.jsonFlag.Usage
printFlags.flags
os
strings
JSONDiagnostic.Category
versionFlag.IsBoolFlag
PrintPlain.BlockStmt._
JSONTree.Add.BlockStmt.jsonError
Parse.RangeStmt_3798.a
addVersionFlag
triStateFlag.name
triState.isTrue
PrintPlain.diag
JSONTree.Add.BlockStmt.RangeStmt_10678.BlockStmt.RangeStmt_10739.BlockStmt.edits
gob
JSON
versionFlag.String
JSONDiagnostic.Message
JSONTree.Add.err
analysis
Parse.multi
Parse.BlockStmt.hasTrue
printFlags
PrintPlain.posn
strconv
Parse.RangeStmt_1513.a
PrintPlain.BlockStmt.lines
JSONTree.Add.name
triState
triState.String
versionFlag.Set.h
JSONTextEdit.Filename
JSONTree.Add.fset
sha256
Parse.printflags
expand
JSONTree.Add.BlockStmt.jsonError.Err
JSONTree.Add.BlockStmt.RangeStmt_10678.BlockStmt.RangeStmt_10739.fix
Parse.analyzers
Parse.enabled
Members
X