Browse Source
Add gitea-vet (#10948)
Add gitea-vet (#10948)
* Add copyright Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add gitea-vet and fix non-compliance Signed-off-by: jolheiser <john.olheiser@gmail.com> * Combine tools.go into build.go and clean up Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove extra GO111MODULE=on Signed-off-by: jolheiser <john.olheiser@gmail.com>mj
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 2990 additions and 101 deletions
-
1.gitignore
-
8Makefile
-
6build.go
-
4contrib/pr/checkout.go
-
3go.mod
-
11go.sum
-
4models/action_test.go
-
4models/issue_stopwatch_test.go
-
4models/lfs.go
-
4models/main_test.go
-
4models/review_test.go
-
4models/u2f_test.go
-
4models/user_follow_test.go
-
4modules/base/tool_test.go
-
4modules/generate/generate_test.go
-
4modules/git/commit_info_test.go
-
4modules/lfs/content_store.go
-
4modules/lfs/server.go
-
4modules/process/manager_test.go
-
4modules/user/user_test.go
-
4routers/api/v1/misc/markdown_test.go
-
4routers/api/v1/misc/signing.go
-
4routers/repo/middlewares.go
-
4routers/swagger_json.go
-
10tools.go
-
5vendor/gitea.com/jolheiser/gitea-vet/.gitignore
-
19vendor/gitea.com/jolheiser/gitea-vet/LICENSE
-
7vendor/gitea.com/jolheiser/gitea-vet/Makefile
-
7vendor/gitea.com/jolheiser/gitea-vet/README.md
-
54vendor/gitea.com/jolheiser/gitea-vet/checks/imports.go
-
73vendor/gitea.com/jolheiser/gitea-vet/checks/license.go
-
5vendor/gitea.com/jolheiser/gitea-vet/go.mod
-
20vendor/gitea.com/jolheiser/gitea-vet/go.sum
-
17vendor/gitea.com/jolheiser/gitea-vet/main.go
-
6vendor/golang.org/x/mod/module/module.go
-
221vendor/golang.org/x/tools/go/analysis/analysis.go
-
61vendor/golang.org/x/tools/go/analysis/diagnostic.go
-
301vendor/golang.org/x/tools/go/analysis/doc.go
-
388vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go
-
92vendor/golang.org/x/tools/go/analysis/internal/analysisflags/help.go
-
323vendor/golang.org/x/tools/go/analysis/internal/facts/facts.go
-
88vendor/golang.org/x/tools/go/analysis/internal/facts/imports.go
-
396vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go
-
9vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker112.go
-
97vendor/golang.org/x/tools/go/analysis/validate.go
-
523vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
-
5vendor/golang.org/x/tools/imports/forward.go
-
5vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go
-
37vendor/golang.org/x/tools/internal/gocommand/invoke.go
-
32vendor/golang.org/x/tools/internal/gopathwalk/walk.go
-
19vendor/golang.org/x/tools/internal/imports/fix.go
-
7vendor/golang.org/x/tools/internal/imports/imports.go
-
6vendor/golang.org/x/tools/internal/imports/mod.go
-
138vendor/golang.org/x/xerrors/fmt.go
-
15vendor/modules.txt
@ -1,10 +0,0 @@ |
|||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|||
// Use of this source code is governed by a MIT-style
|
|||
// license that can be found in the LICENSE file.
|
|||
// +build tools
|
|||
|
|||
package tools |
|||
|
|||
import ( |
|||
_ "github.com/go-swagger/go-swagger/cmd/swagger" |
|||
) |
@ -0,0 +1,5 @@ |
|||
# GoLand |
|||
.idea/ |
|||
|
|||
# Binaries |
|||
/gitea-vet* |
@ -0,0 +1,19 @@ |
|||
Copyright (c) 2020 The Gitea Authors |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|||
THE SOFTWARE. |
@ -0,0 +1,7 @@ |
|||
.PHONY: build |
|||
build: |
|||
go build |
|||
|
|||
.PHONY: fmt |
|||
fmt: |
|||
go fmt ./... |
@ -0,0 +1,7 @@ |
|||
# gitea-vet |
|||
`go vet` tool for Gitea |
|||
|
|||
| Analyzer | Description | |
|||
|----------|---------------------------------------------------------------------| |
|||
| Imports | Checks for import sorting. stdlib->code.gitea.io->other | |
|||
| License | Checks file headers for some form of `Copyright...YYYY...Gitea/Gogs`| |
@ -0,0 +1,54 @@ |
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|||
// Use of this source code is governed by a MIT-style
|
|||
// license that can be found in the LICENSE file.
|
|||
|
|||
package checks |
|||
|
|||
import ( |
|||
"strings" |
|||
|
|||
"golang.org/x/tools/go/analysis" |
|||
) |
|||
|
|||
var Imports = &analysis.Analyzer{ |
|||
Name: "imports", |
|||
Doc: "check for import order.", |
|||
Run: runImports, |
|||
} |
|||
|
|||
func runImports(pass *analysis.Pass) (interface{}, error) { |
|||
for _, file := range pass.Files { |
|||
level := 0 |
|||
for _, im := range file.Imports { |
|||
var lvl int |
|||
val := im.Path.Value |
|||
if importHasPrefix(val, "code.gitea.io") { |
|||
lvl = 2 |
|||
} else if strings.Contains(val, ".") { |
|||
lvl = 3 |
|||
} else { |
|||
lvl = 1 |
|||
} |
|||
|
|||
if lvl < level { |
|||
pass.Reportf(file.Pos(), "Imports are sorted wrong") |
|||
break |
|||
} |
|||
level = lvl |
|||
} |
|||
} |
|||
return nil, nil |
|||
} |
|||
|
|||
func importHasPrefix(s, p string) bool { |
|||
return strings.HasPrefix(s, "\""+p) |
|||
} |
|||
|
|||
func sliceHasPrefix(s string, prefixes ...string) bool { |
|||
for _, p := range prefixes { |
|||
if importHasPrefix(s, p) { |
|||
return true |
|||
} |
|||
} |
|||
return false |
|||
} |
@ -0,0 +1,73 @@ |
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|||
// Use of this source code is governed by a MIT-style
|
|||
// license that can be found in the LICENSE file.
|
|||
|
|||
package checks |
|||
|
|||
import ( |
|||
"regexp" |
|||
"strings" |
|||
|
|||
"golang.org/x/tools/go/analysis" |
|||
) |
|||
|
|||
var ( |
|||
header = regexp.MustCompile(`.*Copyright.*\d{4}.*(Gitea|Gogs)`) |
|||
goGenerate = "//go:generate" |
|||
buildTag = "// +build" |
|||
) |
|||
|
|||
var License = &analysis.Analyzer{ |
|||
Name: "license", |
|||
Doc: "check for a copyright header.", |
|||
Run: runLicense, |
|||
} |
|||
|
|||
func runLicense(pass *analysis.Pass) (interface{}, error) { |
|||
for _, file := range pass.Files { |
|||
if len(file.Comments) == 0 { |
|||
pass.Reportf(file.Pos(), "Copyright not found") |
|||
continue |
|||
} |
|||
|
|||
if len(file.Comments[0].List) == 0 { |
|||
pass.Reportf(file.Pos(), "Copyright not found or wrong") |
|||
continue |
|||
} |
|||
|
|||
commentGroup := 0 |
|||
if strings.HasPrefix(file.Comments[0].List[0].Text, goGenerate) { |
|||
if len(file.Comments[0].List) > 1 { |
|||
pass.Reportf(file.Pos(), "Must be an empty line between the go:generate and the Copyright") |
|||
continue |
|||
} |
|||
commentGroup++ |
|||
} |
|||
|
|||
if strings.HasPrefix(file.Comments[0].List[0].Text, buildTag) { |
|||
commentGroup++ |
|||
} |
|||
|
|||
if len(file.Comments) < commentGroup+1 { |
|||
pass.Reportf(file.Pos(), "Copyright not found") |
|||
continue |
|||
} |
|||
|
|||
if len(file.Comments[commentGroup].List) < 1 { |
|||
pass.Reportf(file.Pos(), "Copyright not found or wrong") |
|||
continue |
|||
} |
|||
|
|||
var check bool |
|||
for _, comment := range file.Comments[commentGroup].List { |
|||
if header.MatchString(comment.Text) { |
|||
check = true |
|||
} |
|||
} |
|||
|
|||
if !check { |
|||
pass.Reportf(file.Pos(), "Copyright did not match check") |
|||
} |
|||
} |
|||
return nil, nil |
|||
} |
@ -0,0 +1,5 @@ |
|||
module gitea.com/jolheiser/gitea-vet |
|||
|
|||
go 1.14 |
|||
|
|||
require golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 |
@ -0,0 +1,20 @@ |
|||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= |
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
|||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |
|||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= |
|||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= |
|||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
|||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
|||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
|||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
|||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= |
|||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 h1:azwY/v0y0K4mFHVsg5+UrTgchqALYWpqVo6vL5OmkmI= |
|||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= |
|||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
|||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
|||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= |
|||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
@ -0,0 +1,17 @@ |
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|||
// Use of this source code is governed by a MIT-style
|
|||
// license that can be found in the LICENSE file.
|
|||
|
|||
package main |
|||
|
|||
import ( |
|||
"gitea.com/jolheiser/gitea-vet/checks" |
|||
"golang.org/x/tools/go/analysis/unitchecker" |
|||
) |
|||
|
|||
func main() { |
|||
unitchecker.Main( |
|||
checks.Imports, |
|||
checks.License, |
|||
) |
|||
} |
@ -0,0 +1,221 @@ |
|||
package analysis |
|||
|
|||
import ( |
|||
"flag" |
|||
"fmt" |
|||
"go/ast" |
|||
"go/token" |
|||
"go/types" |
|||
"reflect" |
|||
) |
|||
|
|||
// An Analyzer describes an analysis function and its options.
|
|||
type Analyzer struct { |
|||
// The Name of the analyzer must be a valid Go identifier
|
|||
// as it may appear in command-line flags, URLs, and so on.
|
|||
Name string |
|||
|
|||
// Doc is the documentation for the analyzer.
|
|||
// The part before the first "\n\n" is the title
|
|||
// (no capital or period, max ~60 letters).
|
|||
Doc string |
|||
|
|||
// Flags defines any flags accepted by the analyzer.
|
|||
// The manner in which these flags are exposed to the user
|
|||
// depends on the driver which runs the analyzer.
|
|||
Flags flag.FlagSet |
|||
|
|||
// Run applies the analyzer to a package.
|
|||
// It returns an error if the analyzer failed.
|
|||
//
|
|||
// On success, the Run function may return a result
|
|||
// computed by the Analyzer; its type must match ResultType.
|
|||
// The driver makes this result available as an input to
|
|||
// another Analyzer that depends directly on this one (see
|
|||
// Requires) when it analyzes the same package.
|
|||
//
|
|||
// To pass analysis results between packages (and thus
|
|||
// potentially between address spaces), use Facts, which are
|
|||
// serializable.
|
|||
Run func(*Pass) (interface{}, error) |
|||
|
|||
// RunDespiteErrors allows the driver to invoke
|
|||
// the Run method of this analyzer even on a
|
|||
// package that contains parse or type errors.
|
|||
RunDespiteErrors bool |
|||
|
|||
// Requires is a set of analyzers that must run successfully
|
|||
// before this one on a given package. This analyzer may inspect
|
|||
// the outputs produced by each analyzer in Requires.
|
|||
// The graph over analyzers implied by Requires edges must be acyclic.
|
|||
//
|
|||
// Requires establishes a "horizontal" dependency between
|
|||
// analysis passes (different analyzers, same package).
|
|||
Requires []*Analyzer |
|||
|
|||
// ResultType is the type of the optional result of the Run function.
|
|||
ResultType reflect.Type |
|||
|
|||
// FactTypes indicates that this analyzer imports and exports
|
|||
// Facts of the specified concrete types.
|
|||
// An analyzer that uses facts may assume that its import
|
|||
// dependencies have been similarly analyzed before it runs.
|
|||
// Facts must be pointers.
|
|||
//
|
|||
// FactTypes establishes a "vertical" dependency between
|
|||
// analysis passes (same analyzer, different packages).
|
|||
FactTypes []Fact |
|||
} |
|||
|
|||
func (a *Analyzer) String() string { return a.Name } |
|||
|
|||
// A Pass provides information to the Run function that
|
|||
// applies a specific analyzer to a single Go package.
|
|||
//
|
|||
// It forms the interface between the analysis logic and the driver
|
|||
// program, and has both input and an output components.
|
|||
//
|
|||
// As in a compiler, one pass may depend on the result computed by another.
|
|||
//
|
|||
// The Run function should not call any of the Pass functions concurrently.
|
|||
type Pass struct { |
|||
Analyzer *Analyzer // the identity of the current analyzer
|
|||
|
|||
// syntax and type information
|
|||
Fset *token.FileSet // file position information
|
|||
Files []*ast.File // the abstract syntax tree of each file
|
|||
OtherFiles []string // names of non-Go files of this package
|
|||
Pkg *types.Package // type information about the package
|
|||
TypesInfo *types.Info // type information about the syntax trees
|
|||
TypesSizes types.Sizes // function for computing sizes of types
|
|||
|
|||
// Report reports a Diagnostic, a finding about a specific location
|
|||
// in the analyzed source code such as a potential mistake.
|
|||
// It may be called by the Run function.
|
|||
Report func(Diagnostic) |
|||
|
|||
// ResultOf provides the inputs to this analysis pass, which are
|
|||
// the corresponding results of its prerequisite analyzers.
|
|||
// The map keys are the elements of Analysis.Required,
|
|||
// and the type of each corresponding value is the required
|
|||
// analysis's ResultType.
|
|||
ResultOf map[*Analyzer]interface{} |
|||
|
|||
// -- facts --
|
|||
|
|||
// ImportObjectFact retrieves a fact associated with obj.
|
|||
// Given a value ptr of type *T, where *T satisfies Fact,
|
|||
// ImportObjectFact copies the value to *ptr.
|
|||
//
|
|||
// ImportObjectFact panics if called after the pass is complete.
|
|||
// ImportObjectFact is not concurrency-safe.
|
|||
ImportObjectFact func(obj types.Object, fact Fact) bool |
|||
|
|||
// ImportPackageFact retrieves a fact associated with package pkg,
|
|||
// which must be this package or one of its dependencies.
|
|||
// See comments for ImportObjectFact.
|
|||
ImportPackageFact func(pkg *types.Package, fact Fact) bool |
|||
|
|||
// ExportObjectFact associates a fact of type *T with the obj,
|
|||
// replacing any previous fact of that type.
|
|||
//
|
|||
// ExportObjectFact panics if it is called after the pass is
|
|||
// complete, or if obj does not belong to the package being analyzed.
|
|||
// ExportObjectFact is not concurrency-safe.
|
|||
ExportObjectFact func(obj types.Object, fact Fact) |
|||
|
|||
// ExportPackageFact associates a fact with the current package.
|
|||
// See comments for ExportObjectFact.
|
|||
ExportPackageFact func(fact Fact) |
|||
|
|||
// AllPackageFacts returns a new slice containing all package facts of the analysis's FactTypes
|
|||
// in unspecified order.
|
|||
// WARNING: This is an experimental API and may change in the future.
|
|||
AllPackageFacts func() []PackageFact |
|||
|
|||
// AllObjectFacts returns a new slice containing all object facts of the analysis's FactTypes
|
|||
// in unspecified order.
|
|||
// WARNING: This is an experimental API and may change in the future.
|
|||
AllObjectFacts func() []ObjectFact |
|||
|
|||
/* Further fields may be added in future. */ |
|||
// For example, suggested or applied refactorings.
|
|||
} |
|||
|
|||
// PackageFact is a package together with an associated fact.
|
|||
// WARNING: This is an experimental API and may change in the future.
|
|||
type PackageFact struct { |
|||
Package *types.Package |
|||
Fact Fact |
|||
} |
|||
|
|||
// ObjectFact is an object together with an associated fact.
|
|||
// WARNING: This is an experimental API and may change in the future.
|
|||
type ObjectFact struct { |
|||
Object types.Object |
|||
Fact Fact |
|||
} |
|||
|
|||
// Reportf is a helper function that reports a Diagnostic using the
|
|||
// specified position and formatted error message.
|
|||
func (pass *Pass) Reportf(pos token.Pos, format string, args ...interface{}) { |
|||
msg := fmt.Sprintf(format, args...) |
|||
pass.Report(Diagnostic{Pos: pos, Message: msg}) |
|||
} |
|||
|
|||
// The Range interface provides a range. It's equivalent to and satisfied by
|
|||
// ast.Node.
|
|||
type Range interface { |
|||
Pos() token.Pos // position of first character belonging to the node
|
|||
End() token.Pos // position of first character immediately after the node
|
|||
} |
|||
|
|||
// ReportRangef is a helper function that reports a Diagnostic using the
|
|||
// range provided. ast.Node values can be passed in as the range because
|
|||
// they satisfy the Range interface.
|
|||
func (pass *Pass) ReportRangef(rng Range, format string, args ...interface{}) { |
|||
msg := fmt.Sprintf(format, args...) |
|||
pass.Report(Diagnostic{Pos: rng.Pos(), End: rng.End(), Message: msg}) |
|||
} |
|||
|
|||
func (pass *Pass) String() string { |
|||
return fmt.Sprintf("%s@%s", pass.Analyzer.Name, pass.Pkg.Path()) |
|||
} |
|||
|
|||
// A Fact is an intermediate fact produced during analysis.
|
|||
//
|
|||
// Each fact is associated with a named declaration (a types.Object) or
|
|||
// with a package as a whole. A single object or package may have
|
|||
// multiple associated facts, but only one of any particular fact type.
|
|||
//
|
|||
// A Fact represents a predicate such as "never returns", but does not
|
|||
// represent the subject of the predicate such as "function F" or "package P".
|
|||
//
|
|||
// Facts may be produced in one analysis pass and consumed by another
|
|||
// analysis pass even if these are in different address spaces.
|
|||
// If package P imports Q, all facts about Q produced during
|
|||
// analysis of that package will be available during later analysis of P.
|
|||
// Facts are analogous to type export data in a build system:
|
|||
// just as export data enables separate compilation of several passes,
|
|||
// facts enable "separate analysis".
|
|||
//
|
|||
// Each pass (a, p) starts with the set of facts produced by the
|
|||
// same analyzer a applied to the packages directly imported by p.
|
|||
// The analysis may add facts to the set, and they may be exported in turn.
|
|||
// An analysis's Run function may retrieve facts by calling
|
|||
// Pass.Import{Object,Package}Fact and update them using
|
|||
// Pass.Export{Object,Package}Fact.
|
|||
//
|
|||
// A fact is logically private to its Analysis. To pass values
|
|||
// between different analyzers, use the results mechanism;
|
|||
// see Analyzer.Requires, Analyzer.ResultType, and Pass.ResultOf.
|
|||
//
|
|||
// A Fact type must be a pointer.
|
|||
// Facts are encoded and decoded using encoding/gob.
|
|||
// A Fact may implement the GobEncoder/GobDecoder interfaces
|
|||
// to customize its encoding. Fact encoding should not fail.
|
|||
//
|
|||
// A Fact should not be modified once exported.
|
|||
type Fact interface { |
|||
AFact() // dummy method to avoid type errors
|
|||
} |
@ -0,0 +1,61 @@ |
|||
package analysis |
|||
|
|||
import "go/token" |
|||
|
|||
// A Diagnostic is a message associated with a source location or range.
|
|||
//
|
|||
// An Analyzer may return a variety of diagnostics; the optional Category,
|
|||
// which should be a constant, may be used to classify them.
|
|||
// It is primarily intended to make it easy to look up documentation.
|
|||
//
|
|||
// If End is provided, the diagnostic is specified to apply to the range between
|
|||
// Pos and End.
|
|||
type Diagnostic struct { |
|||
Pos token.Pos |
|||
End token.Pos // optional
|
|||
Category string // optional
|
|||
Message string |
|||
|
|||
// SuggestedFixes contains suggested fixes for a diagnostic which can be used to perform
|
|||
// edits to a file that address the diagnostic.
|
|||
// TODO(matloob): Should multiple SuggestedFixes be allowed for a diagnostic?
|
|||
// Diagnostics should not contain SuggestedFixes that overlap.
|
|||
// Experimental: This API is experimental and may change in the future.
|
|||
SuggestedFixes []SuggestedFix // optional
|
|||
|
|||
// Experimental: This API is experimental and may change in the future.
|
|||
Related []RelatedInformation // optional
|
|||
} |
|||
|
|||
// RelatedInformation contains information related to a diagnostic.
|
|||
// For example, a diagnostic that flags duplicated declarations of a
|
|||
// variable may include one RelatedInformation per existing
|
|||
// declaration.
|
|||
type RelatedInformation struct { |
|||
Pos token.Pos |
|||
End token.Pos |
|||
Message string |
|||
} |
|||
|
|||
// A SuggestedFix is a code change associated with a Diagnostic that a user can choose
|
|||
// to apply to their code. Usually the SuggestedFix is meant to fix the issue flagged
|
|||
// by the diagnostic.
|
|||
// TextEdits for a SuggestedFix should not overlap. TextEdits for a SuggestedFix
|
|||
// should not contain edits for other packages.
|
|||
// Experimental: This API is experimental and may change in the future.
|
|||
type SuggestedFix struct { |
|||
// A description for this suggested fix to be shown to a user deciding
|
|||
// whether to accept it.
|
|||
Message string |
|||
TextEdits []TextEdit |
|||
} |
|||
|
|||
// A TextEdit represents the replacement of the code between Pos and End with the new text.
|
|||
// Each TextEdit should apply to a single file. End should not be earlier in the file than Pos.
|
|||
// Experimental: This API is experimental and may change in the future.
|
|||
type TextEdit struct { |
|||
// For a pure insertion, End can either be set to Pos or token.NoPos.
|
|||
Pos token.Pos |
|||
End token.Pos |
|||
NewText []byte |
|||
} |
@ -0,0 +1,301 @@ |
|||
/* |
|||
|
|||
Package analysis defines the interface between a modular static |
|||
analysis and an analysis driver program. |
|||
|
|||
|
|||
Background |
|||
|
|||
A static analysis is a function that inspects a package of Go code and |
|||
reports a set of diagnostics (typically mistakes in the code), and |
|||
perhaps produces other results as well, such as suggested refactorings |
|||
or other facts. An analysis that reports mistakes is informally called a |
|||
"checker". For example, the printf checker reports mistakes in |
|||
fmt.Printf format strings. |
|||
|
|||
A "modular" analysis is one that inspects one package at a time but can |
|||
save information from a lower-level package and use it when inspecting a |
|||
higher-level package, analogous to separate compilation in a toolchain. |
|||
The printf checker is modular: when it discovers that a function such as |
|||
log.Fatalf delegates to fmt.Printf, it records this fact, and checks |
|||
calls to that function too, including calls made from another package. |
|||
|
|||
By implementing a common interface, checkers from a variety of sources |
|||
can be easily selected, incorporated, and reused in a wide range of |
|||
driver programs including command-line tools (such as vet), text editors and |
|||
IDEs, build and test systems (such as go build, Bazel, or Buck), test |
|||
frameworks, code review tools, code-base indexers (such as SourceGraph), |
|||
documentation viewers (such as godoc), batch pipelines for large code |
|||
bases, and so on. |
|||
|
|||
|
|||
Analyzer |
|||
|
|||
The primary type in the API is Analyzer. An Analyzer statically |
|||
describes an analysis function: its name, documentation, flags, |
|||
relationship to other analyzers, and of course, its logic. |
|||
|
|||
To define an analysis, a user declares a (logically constant) variable |
|||
of type Analyzer. Here is a typical example from one of the analyzers in |
|||
the go/analysis/passes/ subdirectory: |
|||
|
|||
package unusedresult |
|||
|
|||
var Analyzer = &analysis.Analyzer{ |
|||
Name: "unusedresult", |
|||
Doc: "check for unused results of calls to some functions", |
|||
Run: run, |
|||
... |
|||
} |
|||
|
|||
func run(pass *analysis.Pass) (interface{}, error) { |
|||
... |
|||
} |
|||
|
|||
An analysis driver is a program such as vet that runs a set of |
|||
analyses and prints the diagnostics that they report. |
|||
The driver program must import the list of Analyzers it needs. |
|||
Typically each Analyzer resides in a separate package. |
|||
To add a new Analyzer to an existing driver, add another item to the list: |
|||
|
|||
import ( "unusedresult"; "nilness"; "printf" ) |
|||
|
|||
var analyses = []*analysis.Analyzer{ |
|||
unusedresult.Analyzer, |
|||
nilness.Analyzer, |
|||
printf.Analyzer, |
|||
} |
|||
|
|||
A driver may use the name, flags, and documentation to provide on-line |
|||
help that describes the analyses it performs. |
|||
The doc comment contains a brief one-line summary, |
|||
optionally followed by paragraphs of explanation. |
|||
|
|||
The Analyzer type has more fields besides those shown above: |
|||
|
|||
type Analyzer struct { |
|||
Name string |
|||
Doc string |
|||
Flags flag.FlagSet |
|||
Run func(*Pass) (interface{}, error) |
|||
RunDespiteErrors bool |
|||
ResultType reflect.Type |
|||
Requires []*Analyzer |
|||
FactTypes []Fact |
|||
} |
|||
|
|||
The Flags field declares a set of named (global) flag variables that |
|||
control analysis behavior. Unlike vet, analysis flags are not declared |
|||
directly in the command line FlagSet; it is up to the driver to set the |
|||
flag variables. A driver for a single analysis, a, might expose its flag |
|||
f directly on the command line as -f, whereas a driver for multiple |
|||
analyses might prefix the flag name by the analysis name (-a.f) to avoid |
|||
ambiguity. An IDE might expose the flags through a graphical interface, |
|||
and a batch pipeline might configure them from a config file. |
|||
See the "findcall" analyzer for an example of flags in action. |
|||
|
|||
The RunDespiteErrors flag indicates whether the analysis is equipped to |
|||
handle ill-typed code. If not, the driver will skip the analysis if |
|||
there were parse or type errors. |
|||
The optional ResultType field specifies the type of the result value |
|||
computed by this analysis and made available to other analyses. |
|||
The Requires field specifies a list of analyses upon which |
|||
this one depends and whose results it may access, and it constrains the |
|||
order in which a driver may run analyses. |
|||
The FactTypes field is discussed in the section on Modularity. |
|||
The analysis package provides a Validate function to perform basic |
|||
sanity checks on an Analyzer, such as that its Requires graph is |
|||
acyclic, its fact and result types are unique, and so on. |
|||
|
|||
Finally, the Run field contains a function to be called by the driver to |
|||
execute the analysis on a single package. The driver passes it an |
|||
instance of the Pass type. |
|||
|
|||
|
|||
Pass |
|||
|
|||
A Pass describes a single unit of work: the application of a particular |
|||
Analyzer to a particular package of Go code. |
|||
The Pass provides information to the Analyzer's Run function about the |
|||
package being analyzed, and provides operations to the Run function for |
|||
reporting diagnostics and other information back to the driver. |
|||
|
|||
type Pass struct { |
|||
Fset *token.FileSet |
|||
Files []*ast.File |
|||
OtherFiles []string |
|||
Pkg *types.Package |
|||
TypesInfo *types.Info |
|||
ResultOf map[*Analyzer]interface{} |
|||
Report func(Diagnostic) |
|||
... |
|||
} |
|||
|
|||
The Fset, Files, Pkg, and TypesInfo fields provide the syntax trees, |
|||
type information, and source positions for a single package of Go code. |
|||
|
|||
The OtherFiles field provides the names, but not the contents, of non-Go |
|||
files such as assembly that are part of this package. See the "asmdecl" |
|||
or "buildtags" analyzers for examples of loading non-Go files and reporting |
|||
diagnostics against them. |
|||
|
|||
The ResultOf field provides the results computed by the analyzers |
|||
required by this one, as expressed in its Analyzer.Requires field. The |
|||
driver runs the required analyzers first and makes their results |
|||
available in this map. Each Analyzer must return a value of the type |
|||
described in its Analyzer.ResultType field. |
|||
For example, the "ctrlflow" analyzer returns a *ctrlflow.CFGs, which |
|||
provides a control-flow graph for each function in the package (see |
|||
golang.org/x/tools/go/cfg); the "inspect" analyzer returns a value that |
|||
enables other Analyzers to traverse the syntax trees of the package more |
|||
efficiently; and the "buildssa" analyzer constructs an SSA-form |
|||
intermediate representation. |
|||
Each of these Analyzers extends the capabilities of later Analyzers |
|||
without adding a dependency to the core API, so an analysis tool pays |
|||
only for the extensions it needs. |
|||
|
|||
The Report function emits a diagnostic, a message associated with a |
|||
source position. For most analyses, diagnostics are their primary |
|||
result. |
|||
For convenience, Pass provides a helper method, Reportf, to report a new |
|||
diagnostic by formatting a string. |
|||
Diagnostic is defined as: |
|||
|
|||
type Diagnostic struct { |
|||
Pos token.Pos |
|||
Category string // optional
|
|||
Message string |
|||
} |
|||
|
|||
The optional Category field is a short identifier that classifies the |
|||
kind of message when an analysis produces several kinds of diagnostic. |
|||
|
|||
Most Analyzers inspect typed Go syntax trees, but a few, such as asmdecl |
|||
and buildtag, inspect the raw text of Go source files or even non-Go |
|||
files such as assembly. To report a diagnostic against a line of a |
|||
raw text file, use the following sequence: |
|||
|
|||
content, err := ioutil.ReadFile(filename) |
|||
if err != nil { ... } |
|||
tf := fset.AddFile(filename, -1, len(content)) |
|||
tf.SetLinesForContent(content) |
|||
... |
|||
pass.Reportf(tf.LineStart(line), "oops") |
|||
|
|||
|
|||
Modular analysis with Facts |
|||
|
|||
To improve efficiency and scalability, large programs are routinely |
|||
built using separate compilation: units of the program are compiled |
|||
separately, and recompiled only when one of their dependencies changes; |
|||
independent modules may be compiled in parallel. The same technique may |
|||
be applied to static analyses, for the same benefits. Such analyses are |
|||
described as "modular". |
|||
|
|||
A compiler’s type checker is an example of a modular static analysis. |
|||
Many other checkers we would like to apply to Go programs can be |
|||
understood as alternative or non-standard type systems. For example, |
|||
vet's printf checker infers whether a function has the "printf wrapper" |
|||
type, and it applies stricter checks to calls of such functions. In |
|||
addition, it records which functions are printf wrappers for use by |
|||
later analysis passes to identify other printf wrappers by induction. |
|||
A result such as “f is a printf wrapper” that is not interesting by |
|||
itself but serves as a stepping stone to an interesting result (such as |
|||
a diagnostic) is called a "fact". |
|||
|
|||
The analysis API allows an analysis to define new types of facts, to |
|||
associate facts of these types with objects (named entities) declared |
|||
within the current package, or with the package as a whole, and to query |
|||
for an existing fact of a given type associated with an object or |
|||
package. |
|||
|
|||
An Analyzer that uses facts must declare their types: |
|||
|
|||
var Analyzer = &analysis.Analyzer{ |
|||
Name: "printf", |
|||
FactTypes: []analysis.Fact{new(isWrapper)}, |
|||
... |
|||
} |
|||
|
|||
type isWrapper struct{} // => *types.Func f “is a printf wrapper”
|
|||
|
|||
The driver program ensures that facts for a pass’s dependencies are |
|||
generated before analyzing the package and is responsible for propagating |
|||
facts from one package to another, possibly across address spaces. |
|||
Consequently, Facts must be serializable. The API requires that drivers |
|||
use the gob encoding, an efficient, robust, self-describing binary |
|||
protocol. A fact type may implement the GobEncoder/GobDecoder interfaces |
|||
if the default encoding is unsuitable. Facts should be stateless. |
|||
|
|||
The Pass type has functions to import and export facts, |
|||
associated either with an object or with a package: |
|||
|
|||
type Pass struct { |
|||
... |
|||
ExportObjectFact func(types.Object, Fact) |
|||
ImportObjectFact func(types.Object, Fact) bool |
|||
|
|||
ExportPackageFact func(fact Fact) |
|||
ImportPackageFact func(*types.Package, Fact) bool |
|||
} |
|||
|
|||
An Analyzer may only export facts associated with the current package or |
|||
its objects, though it may import facts from any package or object that |
|||
is an import dependency of the current package. |
|||
|
|||
Conceptually, ExportObjectFact(obj, fact) inserts fact into a hidden map keyed by |
|||
the pair (obj, TypeOf(fact)), and the ImportObjectFact function |
|||
retrieves the entry from this map and copies its value into the variable |
|||
pointed to by fact. This scheme assumes that the concrete type of fact |
|||
is a pointer; this assumption is checked by the Validate function. |
|||
See the "printf" analyzer for an example of object facts in action. |
|||
|
|||
Some driver implementations (such as those based on Bazel and Blaze) do |
|||
not currently apply analyzers to packages of the standard library. |
|||
Therefore, for best results, analyzer authors should not rely on |
|||
analysis facts being available for standard packages. |
|||
For example, although the printf checker is capable of deducing during |
|||
analysis of the log package that log.Printf is a printf wrapper, |
|||
this fact is built in to the analyzer so that it correctly checks |
|||
calls to log.Printf even when run in a driver that does not apply |
|||
it to standard packages. We would like to remove this limitation in future. |
|||
|
|||
|
|||
Testing an Analyzer |
|||
|
|||
The analysistest subpackage provides utilities for testing an Analyzer. |
|||
In a few lines of code, it is possible to run an analyzer on a package |
|||
of testdata files and check that it reported all the expected |
|||
diagnostics and facts (and no more). Expectations are expressed using |
|||
"// want ..." comments in the input code. |
|||
|
|||
|
|||
Standalone commands |
|||
|
|||
Analyzers are provided in the form of packages that a driver program is |
|||
expected to import. The vet command imports a set of several analyzers, |
|||
but users may wish to define their own analysis commands that perform |
|||
additional checks. To simplify the task of creating an analysis command, |
|||
either for a single analyzer or for a whole suite, we provide the |
|||
singlechecker and multichecker subpackages. |
|||
|
|||
The singlechecker package provides the main function for a command that |
|||
runs one analyzer. By convention, each analyzer such as |
|||
go/passes/findcall should be accompanied by a singlechecker-based |
|||
command such as go/analysis/passes/findcall/cmd/findcall, defined in its |
|||
entirety as: |
|||
|
|||
package main |
|||
|
|||
import ( |
|||
"golang.org/x/tools/go/analysis/passes/findcall" |
|||
"golang.org/x/tools/go/analysis/singlechecker" |
|||
) |
|||
|
|||
func main() { singlechecker.Main(findcall.Analyzer) } |
|||
|
|||
A tool that provides multiple analyzers can use multichecker in a |
|||
similar way, giving it the list of Analyzers. |
|||
|
|||
*/ |
|||
package analysis |
@ -0,0 +1,388 @@ |
|||
// Copyright 2018 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 analysisflags defines helpers for processing flags of
|
|||
// analysis driver tools.
|
|||
package analysisflags |
|||
|
|||
import ( |
|||
"crypto/sha256" |
|||
"encoding/gob" |
|||
"encoding/json" |
|||
"flag" |
|||
"fmt" |
|||
"go/token" |
|||
"io" |
|||
"io/ioutil" |
|||
"log" |
|||
"os" |
|||
"strconv" |
|||
"strings" |
|||
|
|||
"golang.org/x/tools/go/analysis" |
|||
) |
|||
|
|||
// flags common to all {single,multi,unit}checkers.
|
|||
var ( |
|||
JSON = false // -json
|
|||
Context = -1 // -c=N: if N>0, display offending line plus N lines of context
|
|||
) |
|||
|
|||
// Parse creates a flag for each of the analyzer's flags,
|
|||
// including (in multi mode) a flag named after the analyzer,
|
|||
// parses the flags, then filters and returns the list of
|
|||
// analyzers enabled by flags.
|
|||
//
|
|||
// The result is intended to be passed to unitchecker.Run or checker.Run.
|
|||
// Use in unitchecker.Run will gob.Register all fact types for the returned
|
|||
// graph of analyzers but of course not the ones only reachable from
|
|||
// dropped analyzers. To avoid inconsistency about which gob types are
|
|||
// registered from run to run, Parse itself gob.Registers all the facts
|
|||
// only reachable from dropped analyzers.
|
|||
// This is not a particularly elegant API, but this is an internal package.
|
|||
func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer { |
|||
// Connect each analysis flag to the command line as -analysis.flag.
|
|||
enabled := make(map[*analysis.Analyzer]*triState) |
|||
for _, a := range analyzers { |
|||
var prefix string |
|||
|
|||
// Add -NAME flag to enable it.
|
|||
if multi { |
|||
prefix = a.Name + "." |
|||
|
|||
enable := new(triState) |
|||
enableUsage := "enable " + a.Name + " analysis" |
|||
flag.Var(enable, a.Name, enableUsage) |
|||
enabled[a] = enable |
|||
} |
|||
|
|||
a.Flags.VisitAll(func(f *flag.Flag) { |
|||
if !multi && flag.Lookup(f.Name) != nil { |
|||
log.Printf("%s flag -%s would conflict with driver; skipping", a.Name, f.Name) |
|||
return |
|||
} |
|||
|
|||
name := prefix + f.Name |
|||
flag.Var(f.Value, name, f.Usage) |
|||
}) |
|||
} |
|||
|
|||
// standard flags: -flags, -V.
|
|||
printflags := flag.Bool("flags", false, "print analyzer flags in JSON") |
|||
addVersionFlag() |
|||
|
|||
// flags common to all checkers
|
|||
flag.BoolVar(&JSON, "json", JSON, "emit JSON output") |
|||
flag.IntVar(&Context, "c", Context, `display offending line with this many lines of context`) |
|||
|
|||
// Add shims for legacy vet flags to enable existing
|
|||
// scripts that run vet to continue to work.
|
|||
_ = flag.Bool("source", false, "no effect (deprecated)") |
|||
_ = flag.Bool("v", false, "no effect (deprecated)") |
|||
_ = flag.Bool("all", false, "no effect (deprecated)") |
|||
_ = flag.String("tags", "", "no effect (deprecated)") |
|||
for old, new := range vetLegacyFlags { |
|||
newFlag := flag.Lookup(new) |
|||
if newFlag != nil && flag.Lookup(old) == nil { |
|||
flag.Var(newFlag.Value, old, "deprecated alias for -"+new) |
|||
} |
|||
} |
|||
|
|||
flag.Parse() // (ExitOnError)
|
|||
|
|||
// -flags: print flags so that go vet knows which ones are legitimate.
|
|||
if *printflags { |
|||
printFlags() |
|||
os.Exit(0) |
|||
} |
|||
|
|||
everything := expand(analyzers) |
|||
|
|||
// If any -NAME flag is true, run only those analyzers. Otherwise,
|
|||
// if any -NAME flag is false, run all but those analyzers.
|
|||
if multi { |
|||
var hasTrue, hasFalse bool |
|||
for _, ts := range enabled { |
|||
switch *ts { |
|||
case setTrue: |
|||
hasTrue = true |
|||
case setFalse: |
|||
hasFalse = true |
|||
} |
|||
} |
|||
|
|||
var keep []*analysis.Analyzer |
|||
if hasTrue { |
|||
for _, a := range analyzers { |
|||
if *enabled[a] == setTrue { |
|||
keep = append(keep, a) |
|||
} |
|||
} |
|||
analyzers = keep |
|||
} else if hasFalse { |
|||
for _, a := range analyzers { |
|||
if *enabled[a] != setFalse { |
|||
keep = append(keep, a) |
|||
} |
|||
} |
|||
analyzers = keep |
|||
} |
|||
} |
|||
|
|||
// Register fact types of skipped analyzers
|
|||
// in case we encounter them in imported files.
|
|||
kept := expand(analyzers) |
|||
for a := range everything { |
|||
if !kept[a] { |
|||
for _, f := range a.FactTypes { |
|||
gob.Register(f) |
|||
} |
|||
} |
|||
} |
|||
|
|||
return analyzers |
|||
} |
|||
|
|||
func expand(analyzers []*analysis.Analyzer) map[*analysis.Analyzer]bool { |
|||
seen := make(map[*analysis.Analyzer]bool) |
|||
var visitAll func([]*analysis.Analyzer) |
|||
visitAll = func(analyzers []*analysis.Analyzer) { |
|||
for _, a := range analyzers { |
|||
if !seen[a] { |
|||
seen[a] = true |
|||
visitAll(a.Requires) |
|||
} |
|||
} |
|||
} |
|||
visitAll(analyzers) |
|||
return seen |
|||
} |
|||
|
|||
func printFlags() { |
|||
type jsonFlag struct { |
|||
Name string |
|||
Bool bool |
|||
Usage string |
|||
} |
|||
var flags []jsonFlag = nil |
|||
flag.VisitAll(func(f *flag.Flag) { |
|||
// Don't report {single,multi}checker debugging
|
|||
// flags or fix as these have no effect on unitchecker
|
|||
// (as invoked by 'go vet').
|
|||
switch f.Name { |
|||
case "debug", "cpuprofile", "memprofile", "trace", "fix": |
|||
return |
|||
} |
|||
|
|||
b, ok := f.Value.(interface{ IsBoolFlag() bool }) |
|||
isBool := ok && b.IsBoolFlag() |
|||
flags = append(flags, jsonFlag{f.Name, isBool, f.Usage}) |
|||
}) |
|||
data, err := json.MarshalIndent(flags, "", "\t") |
|||
if err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
os.Stdout.Write(data) |
|||
} |
|||
|
|||
// addVersionFlag registers a -V flag that, if set,
|
|||
// prints the executable version and exits 0.
|
|||
//
|
|||
// If the -V flag already exists — for example, because it was already
|
|||
// registered by a call to cmd/internal/objabi.AddVersionFlag — then
|
|||
// addVersionFlag does nothing.
|
|||
func addVersionFlag() { |
|||
if flag.Lookup("V") == nil { |
|||
flag.Var(versionFlag{}, "V", "print version and exit") |
|||
} |
|||
} |
|||
|
|||
// versionFlag minimally complies with the -V protocol required by "go vet".
|
|||
type versionFlag struct{} |
|||
|
|||
func (versionFlag) IsBoolFlag() bool { return true } |
|||
func (versionFlag) Get() interface{} { return nil } |
|||
func (versionFlag) String() string { return "" } |
|||
func (versionFlag) Set(s string) error { |
|||
if s != "full" { |
|||
log.Fatalf("unsupported flag value: -V=%s", s) |
|||
} |
|||
|
|||
// This replicates the minimal subset of
|
|||
// cmd/internal/objabi.AddVersionFlag, which is private to the
|
|||
// go tool yet forms part of our command-line interface.
|
|||
// TODO(adonovan): clarify the contract.
|
|||
|
|||
// Print the tool version so the build system can track changes.
|
|||
// Formats:
|
|||
// $progname version devel ... buildID=...
|
|||
// $progname version go1.9.1
|
|||
progname := os.Args[0] |
|||
f, err := os.Open(progname) |
|||
if err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
h := sha256.New() |
|||
if _, err := io.Copy(h, f); err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
f.Close() |
|||
fmt.Printf("%s version devel comments-go-here buildID=%02x\n", |
|||
progname, string(h.Sum(nil))) |
|||
os.Exit(0) |
|||
return nil |
|||
} |
|||
|
|||
// A triState is a boolean that knows whether
|
|||
// it has been set to either true or false.
|
|||
// It is used to identify whether a flag appears;
|
|||
// the standard boolean flag cannot
|
|||
// distinguish missing from unset.
|
|||
// It also satisfies flag.Value.
|
|||
type triState int |
|||
|
|||
const ( |
|||
unset triState = iota |
|||
setTrue |
|||
setFalse |
|||
) |
|||
|
|||
func triStateFlag(name string, value triState, usage string) *triState { |
|||
flag.Var(&value, name, usage) |
|||
return &value |
|||
} |
|||
|
|||
// triState implements flag.Value, flag.Getter, and flag.boolFlag.
|
|||
// They work like boolean flags: we can say vet -printf as well as vet -printf=true
|
|||
func (ts *triState) Get() interface{} { |
|||
return *ts == setTrue |
|||
} |
|||
|
|||
func (ts triState) isTrue() bool { |
|||
return ts == setTrue |
|||
} |
|||
|
|||
func (ts *triState) Set(value string) error { |
|||
b, err := strconv.ParseBool(value) |
|||
if err != nil { |
|||
// This error message looks poor but package "flag" adds
|
|||
// "invalid boolean value %q for -NAME: %s"
|
|||
return fmt.Errorf("want true or false") |
|||
} |
|||
if b { |
|||
*ts = setTrue |
|||
} else { |
|||
*ts = setFalse |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
func (ts *triState) String() string { |
|||
switch *ts { |
|||
case unset: |
|||
return "true" |
|||
case setTrue: |
|||
return "true" |
|||
case setFalse: |
|||
return "false" |
|||
} |
|||
panic("not reached") |
|||
} |
|||
|
|||
func (ts triState) IsBoolFlag() bool { |
|||
return true |
|||
} |
|||
|
|||
// Legacy flag support
|
|||
|
|||
// vetLegacyFlags maps flags used by legacy vet to their corresponding
|
|||
// new names. The old names will continue to work.
|
|||
var vetLegacyFlags = map[string]string{ |
|||
// Analyzer name changes
|
|||
"bool": "bools", |
|||
"buildtags": "buildtag", |
|||
"methods": "stdmethods", |
|||
"rangeloops": "loopclosure", |
|||
|
|||
// Analyzer flags
|
|||
"compositewhitelist": "composites.whitelist", |
|||
"printfuncs": "printf.funcs", |
|||
"shadowstrict": "shadow.strict", |
|||
"unusedfuncs": "unusedresult.funcs", |
|||
"unusedstringmethods": "unusedresult.stringmethods", |
|||
} |
|||
|
|||
// ---- output helpers common to all drivers ----
|
|||
|
|||
// PrintPlain prints a diagnostic in plain text form,
|
|||
// with context specified by the -c flag.
|
|||
func PrintPlain(fset *token.FileSet, diag analysis.Diagnostic) { |
|||
posn := fset.Position(diag.Pos) |
|||
fmt.Fprintf(os.Stderr, "%s: %s\n", posn, diag.Message) |
|||
|
|||
// -c=N: show offending line plus N lines of context.
|
|||
if Context >= 0 { |
|||
posn := fset.Position(diag.Pos) |
|||
end := fset.Position(diag.End) |
|||
if !end.IsValid() { |
|||
end = posn |
|||
} |
|||
data, _ := ioutil.ReadFile(posn.Filename) |
|||
lines := strings.Split(string(data), "\n") |
|||
for i := posn.Line - Context; i <= end.Line+Context; i++ { |
|||
if 1 <= i && i <= len(lines) { |
|||
fmt.Fprintf(os.Stderr, "%d\t%s\n", i, lines[i-1]) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// A JSONTree is a mapping from package ID to analysis name to result.
|
|||
// Each result is either a jsonError or a list of jsonDiagnostic.
|
|||
type JSONTree map[string]map[string]interface{} |
|||
|
|||
// Add adds the result of analysis 'name' on package 'id'.
|
|||
// The result is either a list of diagnostics or an error.
|
|||
func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.Diagnostic, err error) { |
|||
var v interface{} |
|||
if err != nil { |
|||
type jsonError struct { |
|||
Err string `json:"error"` |
|||
} |
|||
v = jsonError{err.Error()} |
|||
} else if len(diags) > 0 { |
|||
type jsonDiagnostic struct { |
|||
Category string `json:"category,omitempty"` |
|||
Posn string `json:"posn"` |
|||
Message string `json:"message"` |
|||
} |
|||
var diagnostics []jsonDiagnostic |
|||
// TODO(matloob): Should the JSON diagnostics contain ranges?
|
|||
// If so, how should they be formatted?
|
|||
for _, f := range diags { |
|||
diagnostics = append(diagnostics, jsonDiagnostic{ |
|||
Category: f.Category, |
|||
Posn: fset.Position(f.Pos).String(), |
|||
Message: f.Message, |
|||
}) |
|||