diff --git a/GNUmakefile b/GNUmakefile index 186e0197175e..e029fe4b99e5 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -33,8 +33,10 @@ websitefmtcheck: lint: @echo "==> Checking source code against linters..." @GOGC=30 golangci-lint run ./$(PKG_NAME) + @tfproviderlint -c 1 -S001 -S002 -S003 -S004 -S005 ./$(PKG_NAME) tools: + GO111MODULE=on go install github.com/bflad/tfproviderlint/cmd/tfproviderlint GO111MODULE=on go install github.com/client9/misspell/cmd/misspell GO111MODULE=on go install github.com/golangci/golangci-lint/cmd/golangci-lint diff --git a/go.mod b/go.mod index 511cccf5e17e..bf8899282399 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/terraform-providers/terraform-provider-aws require ( github.com/aws/aws-sdk-go v1.19.42 github.com/beevik/etree v1.1.0 + github.com/bflad/tfproviderlint v0.2.0 github.com/client9/misspell v0.3.4 github.com/go-toolsmith/pkgload v0.0.0-20181120203407-5122569a890b // indirect github.com/golangci/go-tools v0.0.0-20190124090046-35a9f45a5db0 // indirect diff --git a/go.sum b/go.sum index 7cbb3189b457..960401b830f5 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,8 @@ github.com/aws/aws-sdk-go v1.19.42/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/bflad/tfproviderlint v0.2.0 h1:cW544e23PuPCZ8jibkJdkEth+QncaKx2VbjW5B5IJxw= +github.com/bflad/tfproviderlint v0.2.0/go.mod h1:dhfYzvaPNGm7yzUXj/wEwVzwe+LQAOkmXnwQw8D4rio= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= @@ -273,6 +275,7 @@ github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/memberlist v0.1.0/go.mod h1:ncdBp14cuox2iFOq3kDiquKU6fqsTBc3W6JvZwjxxsE= github.com/hashicorp/serf v0.0.0-20160124182025-e4ec8cc423bb/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE= +github.com/hashicorp/terraform v0.12.0/go.mod h1:Ke0ig9gGZ8rhV6OddAhBYt5nXmpvXsuNQQ8w9qYBZfU= github.com/hashicorp/terraform v0.12.1 h1:A56pUiGbDZ372cvDKWixzVciTGZkqZ01hH58GTNQ4lg= github.com/hashicorp/terraform v0.12.1/go.mod h1:T3xp83QKah46oRMg37OPBbEVjEwokcGdn7+aNQtQbKo= github.com/hashicorp/terraform-config-inspect v0.0.0-20190327195015-8022a2663a70 h1:oZm5nE11yhzsTRz/YrUyDMSvixePqjoZihwn8ipuOYI= @@ -605,6 +608,8 @@ golang.org/x/tools v0.0.0-20190314010720-f0bfdbff1f9c h1:nE2ID2IbO0sUUG/3vWMz0LS golang.org/x/tools v0.0.0-20190314010720-f0bfdbff1f9c/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262 h1:qsl9y/CJx34tuA7QCPNp86JNJe4spst6Ff8MjvPUdPg= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190510151030-63859f3815cb h1:fuQCchFfDC5laXoMg4zQC0+xO4n1EJi6jYaXMdAu5q0= +golang.org/x/tools v0.0.0-20190510151030-63859f3815cb/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0 h1:K6z2u68e86TPdSdefXdzvXgR1zEMa+459vBSfWYAZkI= diff --git a/tools.go b/tools.go index ea88daa1890e..3b3ba268d83b 100644 --- a/tools.go +++ b/tools.go @@ -3,6 +3,7 @@ package main import ( + _ "github.com/bflad/tfproviderlint/cmd/tfproviderlint" _ "github.com/client9/misspell/cmd/misspell" _ "github.com/golangci/golangci-lint/cmd/golangci-lint" ) diff --git a/vendor/github.com/bflad/tfproviderlint/cmd/tfproviderlint/tfproviderlint.go b/vendor/github.com/bflad/tfproviderlint/cmd/tfproviderlint/tfproviderlint.go new file mode 100644 index 000000000000..5d3b01633579 --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/cmd/tfproviderlint/tfproviderlint.go @@ -0,0 +1,45 @@ +// The tfproviderlint command is a static checker for Terraform Providers. +// +// Each analyzer flag name is preceded by the analyzer name: -NAME.flag. +// In addition, the -NAME flag itself controls whether the +// diagnostics of that analyzer are displayed. (A disabled analyzer may yet +// be run if it is required by some other analyzer that is enabled.) +package main + +import ( + "golang.org/x/tools/go/analysis/multichecker" + + "github.com/bflad/tfproviderlint/passes/AT001" + "github.com/bflad/tfproviderlint/passes/AT002" + "github.com/bflad/tfproviderlint/passes/AT003" + "github.com/bflad/tfproviderlint/passes/AT004" + "github.com/bflad/tfproviderlint/passes/R001" + "github.com/bflad/tfproviderlint/passes/R002" + "github.com/bflad/tfproviderlint/passes/R003" + "github.com/bflad/tfproviderlint/passes/R004" + "github.com/bflad/tfproviderlint/passes/S001" + "github.com/bflad/tfproviderlint/passes/S002" + "github.com/bflad/tfproviderlint/passes/S003" + "github.com/bflad/tfproviderlint/passes/S004" + "github.com/bflad/tfproviderlint/passes/S005" + "github.com/bflad/tfproviderlint/passes/S006" +) + +func main() { + multichecker.Main( + AT001.Analyzer, + AT002.Analyzer, + AT003.Analyzer, + AT004.Analyzer, + R001.Analyzer, + R002.Analyzer, + R003.Analyzer, + R004.Analyzer, + S001.Analyzer, + S002.Analyzer, + S003.Analyzer, + S004.Analyzer, + S005.Analyzer, + S006.Analyzer, + ) +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/AT001/AT001.go b/vendor/github.com/bflad/tfproviderlint/passes/AT001/AT001.go new file mode 100644 index 000000000000..2a80481a07da --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/AT001/AT001.go @@ -0,0 +1,63 @@ +// Package AT001 defines an Analyzer that checks for +// TestCase missing CheckDestroy +package AT001 + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/bflad/tfproviderlint/passes/acctestcase" + "github.com/bflad/tfproviderlint/passes/commentignore" +) + +const Doc = `check for TestCase missing CheckDestroy + +The AT001 analyzer reports likely incorrect uses of TestCase +which do not define a CheckDestroy function. CheckDestroy is used to verify +that test infrastructure has been removed at the end of an acceptance test. + +More information can be found at: +https://www.terraform.io/docs/extend/testing/acceptance-tests/testcase.html#checkdestroy` + +const analyzerName = "AT001" + +var Analyzer = &analysis.Analyzer{ + Name: analyzerName, + Doc: Doc, + Requires: []*analysis.Analyzer{ + acctestcase.Analyzer, + commentignore.Analyzer, + }, + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + ignorer := pass.ResultOf[commentignore.Analyzer].(*commentignore.Ignorer) + testCases := pass.ResultOf[acctestcase.Analyzer].([]*ast.CompositeLit) + for _, testCase := range testCases { + if ignorer.ShouldIgnore(analyzerName, testCase) { + continue + } + + var found bool + + for _, elt := range testCase.Elts { + switch v := elt.(type) { + default: + continue + case *ast.KeyValueExpr: + if v.Key.(*ast.Ident).Name == "CheckDestroy" { + found = true + break + } + } + } + + if !found { + pass.Reportf(testCase.Type.(*ast.SelectorExpr).Sel.Pos(), "%s: missing CheckDestroy", analyzerName) + } + } + + return nil, nil +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/AT002/AT002.go b/vendor/github.com/bflad/tfproviderlint/passes/AT002/AT002.go new file mode 100644 index 000000000000..9f588f5e8f58 --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/AT002/AT002.go @@ -0,0 +1,51 @@ +// Package AT002 defines an Analyzer that checks for +// acceptance test names including the word import +package AT002 + +import ( + "go/ast" + "strings" + + "golang.org/x/tools/go/analysis" + + "github.com/bflad/tfproviderlint/passes/acctestfunc" + "github.com/bflad/tfproviderlint/passes/commentignore" +) + +const Doc = `check for acceptance test function names including the word import + +The AT002 analyzer reports where the word import or Import is used +in an acceptance test function name, which generally means there is an extraneous +acceptance test. ImportState testing should be included as a TestStep with each +applicable acceptance test, rather than a separate test that only verifies import +of a single test configuration.` + +const analyzerName = "AT002" + +var Analyzer = &analysis.Analyzer{ + Name: analyzerName, + Doc: Doc, + Requires: []*analysis.Analyzer{ + acctestfunc.Analyzer, + commentignore.Analyzer, + }, + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + ignorer := pass.ResultOf[commentignore.Analyzer].(*commentignore.Ignorer) + testAccFuncs := pass.ResultOf[acctestfunc.Analyzer].([]*ast.FuncDecl) + for _, testAccFunc := range testAccFuncs { + if ignorer.ShouldIgnore(analyzerName, testAccFunc) { + continue + } + + funcName := testAccFunc.Name.Name + + if strings.Contains(funcName, "_import") || strings.Contains(funcName, "_Import") { + pass.Reportf(testAccFunc.Name.NamePos, "%s: acceptance test function name should not include import", analyzerName) + } + } + + return nil, nil +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/AT003/AT003.go b/vendor/github.com/bflad/tfproviderlint/passes/AT003/AT003.go new file mode 100644 index 000000000000..680d49e76f12 --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/AT003/AT003.go @@ -0,0 +1,47 @@ +// Package AT003 defines an Analyzer that checks for +// acceptance test names missing an underscore +package AT003 + +import ( + "go/ast" + "strings" + + "golang.org/x/tools/go/analysis" + + "github.com/bflad/tfproviderlint/passes/acctestfunc" + "github.com/bflad/tfproviderlint/passes/commentignore" +) + +const Doc = `check for acceptance test function names missing an underscore + +The AT003 analyzer reports where an underscore is not +present in the function name, which could make per-resource testing harder to +execute in larger providers or those with overlapping resource names.` + +const analyzerName = "AT003" + +var Analyzer = &analysis.Analyzer{ + Name: analyzerName, + Doc: Doc, + Requires: []*analysis.Analyzer{ + acctestfunc.Analyzer, + commentignore.Analyzer, + }, + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + ignorer := pass.ResultOf[commentignore.Analyzer].(*commentignore.Ignorer) + testAccFuncs := pass.ResultOf[acctestfunc.Analyzer].([]*ast.FuncDecl) + for _, testAccFunc := range testAccFuncs { + if ignorer.ShouldIgnore(analyzerName, testAccFunc) { + continue + } + + if !strings.Contains(testAccFunc.Name.Name, "_") { + pass.Reportf(testAccFunc.Name.NamePos, "%s: acceptance test function name should include underscore", analyzerName) + } + } + + return nil, nil +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/AT004/AT004.go b/vendor/github.com/bflad/tfproviderlint/passes/AT004/AT004.go new file mode 100644 index 000000000000..dee5310ac00d --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/AT004/AT004.go @@ -0,0 +1,50 @@ +// Package AT004 defines an Analyzer that checks for +// TestStep Config containing provider configuration +package AT004 + +import ( + "go/ast" + "go/token" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +const Doc = `check for TestStep Config containing provider configuration + +The AT004 analyzer reports likely incorrect uses of TestStep +Config which define a provider configuration. Provider configurations should +be handled outside individual test configurations (e.g. environment variables).` + +const analyzerName = "AT004" + +var Analyzer = &analysis.Analyzer{ + Name: analyzerName, + Doc: Doc, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + + nodeFilter := []ast.Node{ + (*ast.BasicLit)(nil), + } + inspect.Preorder(nodeFilter, func(n ast.Node) { + x := n.(*ast.BasicLit) + + if x.Kind != token.STRING { + return + } + + if !strings.Contains(x.Value, `provider "`) { + return + } + + pass.Reportf(x.ValuePos, "%s: provider declaration should be omitted", analyzerName) + }) + return nil, nil +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/R001/R001.go b/vendor/github.com/bflad/tfproviderlint/passes/R001/R001.go new file mode 100644 index 000000000000..bfb6f0108667 --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/R001/R001.go @@ -0,0 +1,52 @@ +// Package R001 defines an Analyzer that checks for +// ResourceData.Set() calls using complex key argument +package R001 + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/bflad/tfproviderlint/passes/commentignore" + "github.com/bflad/tfproviderlint/passes/resourcedataset" +) + +const Doc = `check for ResourceData.Set() calls using complex key argument + +The R001 analyzer reports a complex key argument for a Set() +call. It is preferred to explicitly use a string literal as the key argument.` + +const analyzerName = "R001" + +var Analyzer = &analysis.Analyzer{ + Name: analyzerName, + Doc: Doc, + Requires: []*analysis.Analyzer{ + resourcedataset.Analyzer, + commentignore.Analyzer, + }, + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + ignorer := pass.ResultOf[commentignore.Analyzer].(*commentignore.Ignorer) + sets := pass.ResultOf[resourcedataset.Analyzer].([]*ast.CallExpr) + for _, set := range sets { + if ignorer.ShouldIgnore(analyzerName, set) { + continue + } + + if len(set.Args) < 2 { + continue + } + + switch v := set.Args[0].(type) { + default: + pass.Reportf(v.Pos(), "%s: ResourceData.Set() key argument should be string literal", analyzerName) + case *ast.BasicLit: + continue + } + } + + return nil, nil +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/R002/R002.go b/vendor/github.com/bflad/tfproviderlint/passes/R002/R002.go new file mode 100644 index 000000000000..2e30626ea225 --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/R002/R002.go @@ -0,0 +1,53 @@ +// Package R002 defines an Analyzer that checks for +// ResourceData.Set() calls using * dereferences +package R002 + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/bflad/tfproviderlint/passes/commentignore" + "github.com/bflad/tfproviderlint/passes/resourcedataset" +) + +const Doc = `check for ResourceData.Set() calls using * dereferences + +The R002 analyzer reports likely extraneous uses of +star (*) dereferences for a Set() call. The Set() function automatically +handles pointers and * dereferences without nil checks can panic.` + +const analyzerName = "R002" + +var Analyzer = &analysis.Analyzer{ + Name: analyzerName, + Doc: Doc, + Requires: []*analysis.Analyzer{ + resourcedataset.Analyzer, + commentignore.Analyzer, + }, + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + ignorer := pass.ResultOf[commentignore.Analyzer].(*commentignore.Ignorer) + sets := pass.ResultOf[resourcedataset.Analyzer].([]*ast.CallExpr) + for _, set := range sets { + if ignorer.ShouldIgnore(analyzerName, set) { + continue + } + + if len(set.Args) < 2 { + continue + } + + switch v := set.Args[1].(type) { + default: + continue + case *ast.StarExpr: + pass.Reportf(v.Pos(), "%s: ResourceData.Set() pointer value dereference is extraneous", analyzerName) + } + } + + return nil, nil +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/R003/R003.go b/vendor/github.com/bflad/tfproviderlint/passes/R003/R003.go new file mode 100644 index 000000000000..b8a0d8f8ff17 --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/R003/R003.go @@ -0,0 +1,54 @@ +// Package R003 defines an Analyzer that checks for +// Resource having Exists functions +package R003 + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/bflad/tfproviderlint/passes/commentignore" + "github.com/bflad/tfproviderlint/passes/schemaresource" +) + +const Doc = `check for Resource having Exists functions + +The R003 analyzer reports likely extraneous uses of Exists +functions for a resource. Exists logic can be handled inside the Read function +to prevent logic duplication.` + +const analyzerName = "R003" + +var Analyzer = &analysis.Analyzer{ + Name: analyzerName, + Doc: Doc, + Requires: []*analysis.Analyzer{ + schemaresource.Analyzer, + commentignore.Analyzer, + }, + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + ignorer := pass.ResultOf[commentignore.Analyzer].(*commentignore.Ignorer) + resources := pass.ResultOf[schemaresource.Analyzer].([]*ast.CompositeLit) + for _, resource := range resources { + if ignorer.ShouldIgnore(analyzerName, resource) { + continue + } + + for _, elt := range resource.Elts { + switch v := elt.(type) { + default: + continue + case *ast.KeyValueExpr: + if v.Key.(*ast.Ident).Name == "Exists" { + pass.Reportf(v.Key.Pos(), "%s: resource should not include Exists function", analyzerName) + break + } + } + } + } + + return nil, nil +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/R004/R004.go b/vendor/github.com/bflad/tfproviderlint/passes/R004/R004.go new file mode 100644 index 000000000000..42abb784c887 --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/R004/R004.go @@ -0,0 +1,116 @@ +// Package R004 defines an Analyzer that checks for +// ResourceData.Set() calls using incompatible value types +package R004 + +import ( + "go/ast" + "go/types" + "strings" + + "golang.org/x/tools/go/analysis" + + "github.com/bflad/tfproviderlint/passes/commentignore" + "github.com/bflad/tfproviderlint/passes/resourcedataset" +) + +const Doc = `check for ResourceData.Set() calls using incompatible value types + +The R004 analyzer reports incorrect types for a Set() call value. +The Set() function only supports a subset of basic types, slices and maps of that +subset of basic types, and the schema.Set type.` + +const analyzerName = "R004" + +var Analyzer = &analysis.Analyzer{ + Name: analyzerName, + Doc: Doc, + Requires: []*analysis.Analyzer{ + resourcedataset.Analyzer, + commentignore.Analyzer, + }, + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + ignorer := pass.ResultOf[commentignore.Analyzer].(*commentignore.Ignorer) + sets := pass.ResultOf[resourcedataset.Analyzer].([]*ast.CallExpr) + for _, set := range sets { + if ignorer.ShouldIgnore(analyzerName, set) { + continue + } + + if len(set.Args) < 2 { + continue + } + + pos := set.Args[1].Pos() + t := pass.TypesInfo.TypeOf(set.Args[1]).Underlying() + + if !isAllowedType(t) { + pass.Reportf(pos, "%s: ResourceData.Set() incompatible value type: %s", analyzerName, t.String()) + } + } + + return nil, nil +} + +func isAllowedType(t types.Type) bool { + switch t := t.(type) { + default: + return false + case *types.Basic: + if !isAllowedBasicType(t) { + return false + } + case *types.Interface: + return true + case *types.Map: + switch k := t.Key().Underlying().(type) { + default: + return false + case *types.Basic: + if k.Kind() != types.String { + return false + } + + return isAllowedType(t.Elem().Underlying()) + } + case *types.Named: + if t.Obj().Name() != "Set" { + return false + } + // HasSuffix here due to vendoring + if !strings.HasSuffix(t.Obj().Pkg().Path(), "github.com/hashicorp/terraform/helper/schema") { + return false + } + case *types.Pointer: + return isAllowedType(t.Elem()) + case *types.Slice: + return isAllowedType(t.Elem().Underlying()) + } + + return true +} + +var allowedBasicKindTypes = []types.BasicKind{ + types.Bool, + types.Float32, + types.Float64, + types.Int, + types.Int8, + types.Int16, + types.Int32, + types.Int64, + types.String, + types.UntypedNil, +} + +func isAllowedBasicType(b *types.Basic) bool { + for _, allowedBasicKindType := range allowedBasicKindTypes { + if b.Kind() == allowedBasicKindType { + return true + } + } + + return false +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/S001/S001.go b/vendor/github.com/bflad/tfproviderlint/passes/S001/S001.go new file mode 100644 index 000000000000..9397ebf49857 --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/S001/S001.go @@ -0,0 +1,89 @@ +// Package S001 defines an Analyzer that checks for +// Schema of TypeList or TypeSet missing Elem +package S001 + +import ( + "go/ast" + "go/types" + "strings" + + "golang.org/x/tools/go/analysis" + + "github.com/bflad/tfproviderlint/passes/commentignore" + "github.com/bflad/tfproviderlint/passes/schemaschema" +) + +const Doc = `check for Schema of TypeList or TypeSet missing Elem + +The S001 analyzer reports cases of TypeList or TypeSet schemas missing Elem, +which will fail schema validation.` + +const analyzerName = "S001" + +var Analyzer = &analysis.Analyzer{ + Name: analyzerName, + Doc: Doc, + Requires: []*analysis.Analyzer{ + schemaschema.Analyzer, + commentignore.Analyzer, + }, + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + ignorer := pass.ResultOf[commentignore.Analyzer].(*commentignore.Ignorer) + schemas := pass.ResultOf[schemaschema.Analyzer].([]*ast.CompositeLit) + for _, schema := range schemas { + if ignorer.ShouldIgnore(analyzerName, schema) { + continue + } + + var elemFound, typeListOrSet bool + + for _, elt := range schema.Elts { + switch v := elt.(type) { + default: + continue + case *ast.KeyValueExpr: + name := v.Key.(*ast.Ident).Name + + if name == "Elem" { + elemFound = true + continue + } + + if name != "Type" { + continue + } + + switch v := v.Value.(type) { + default: + continue + case *ast.SelectorExpr: + // Use AST over TypesInfo here as schema uses ValueType + if v.Sel.Name != "TypeList" && v.Sel.Name != "TypeSet" { + continue + } + + switch t := pass.TypesInfo.TypeOf(v).(type) { + default: + continue + case *types.Named: + // HasSuffix here due to vendoring + if !strings.HasSuffix(t.Obj().Pkg().Path(), "github.com/hashicorp/terraform/helper/schema") { + continue + } + + typeListOrSet = true + } + } + } + } + + if typeListOrSet && !elemFound { + pass.Reportf(schema.Type.(*ast.SelectorExpr).Sel.Pos(), "%s: schema of TypeList or TypeSet should include Elem", analyzerName) + } + } + + return nil, nil +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/S002/S002.go b/vendor/github.com/bflad/tfproviderlint/passes/S002/S002.go new file mode 100644 index 000000000000..276224d1478e --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/S002/S002.go @@ -0,0 +1,78 @@ +// Package S002 defines an Analyzer that checks for +// Schema with both Required and Optional enabled +package S002 + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/bflad/tfproviderlint/passes/commentignore" + "github.com/bflad/tfproviderlint/passes/schemaschema" +) + +const Doc = `check for Schema with both Required and Optional enabled + +The S002 analyzer reports cases of schemas which enables both Required +and Optional, which will fail provider schema validation.` + +const analyzerName = "S002" + +var Analyzer = &analysis.Analyzer{ + Name: analyzerName, + Doc: Doc, + Requires: []*analysis.Analyzer{ + schemaschema.Analyzer, + commentignore.Analyzer, + }, + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + ignorer := pass.ResultOf[commentignore.Analyzer].(*commentignore.Ignorer) + schemas := pass.ResultOf[schemaschema.Analyzer].([]*ast.CompositeLit) + for _, schema := range schemas { + if ignorer.ShouldIgnore(analyzerName, schema) { + continue + } + + var optionalEnabled, requiredEnabled bool + + for _, elt := range schema.Elts { + switch v := elt.(type) { + default: + continue + case *ast.KeyValueExpr: + name := v.Key.(*ast.Ident).Name + + if name != "Optional" && name != "Required" { + continue + } + + switch v := v.Value.(type) { + default: + continue + case *ast.Ident: + value := v.Name + + if value != "true" { + continue + } + + if name == "Optional" { + optionalEnabled = true + continue + } + + requiredEnabled = true + } + } + } + + if optionalEnabled && requiredEnabled { + pass.Reportf(schema.Type.(*ast.SelectorExpr).Sel.Pos(), "%s: schema should not enable Required and Optional", analyzerName) + } + } + + return nil, nil +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/S003/S003.go b/vendor/github.com/bflad/tfproviderlint/passes/S003/S003.go new file mode 100644 index 000000000000..acf370c13428 --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/S003/S003.go @@ -0,0 +1,78 @@ +// Package S003 defines an Analyzer that checks for +// Schema with both Required and Computed enabled +package S003 + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/bflad/tfproviderlint/passes/commentignore" + "github.com/bflad/tfproviderlint/passes/schemaschema" +) + +const Doc = `check for Schema with both Required and Computed enabled + +The S003 analyzer reports cases of schemas which enables both Required +and Computed, which will fail provider schema validation.` + +const analyzerName = "S003" + +var Analyzer = &analysis.Analyzer{ + Name: analyzerName, + Doc: Doc, + Requires: []*analysis.Analyzer{ + schemaschema.Analyzer, + commentignore.Analyzer, + }, + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + ignorer := pass.ResultOf[commentignore.Analyzer].(*commentignore.Ignorer) + schemas := pass.ResultOf[schemaschema.Analyzer].([]*ast.CompositeLit) + for _, schema := range schemas { + if ignorer.ShouldIgnore(analyzerName, schema) { + continue + } + + var computedEnabled, requiredEnabled bool + + for _, elt := range schema.Elts { + switch v := elt.(type) { + default: + continue + case *ast.KeyValueExpr: + name := v.Key.(*ast.Ident).Name + + if name != "Computed" && name != "Required" { + continue + } + + switch v := v.Value.(type) { + default: + continue + case *ast.Ident: + value := v.Name + + if value != "true" { + continue + } + + if name == "Computed" { + computedEnabled = true + continue + } + + requiredEnabled = true + } + } + } + + if computedEnabled && requiredEnabled { + pass.Reportf(schema.Type.(*ast.SelectorExpr).Sel.Pos(), "%s: schema should not enable Required and Computed", analyzerName) + } + } + + return nil, nil +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/S004/S004.go b/vendor/github.com/bflad/tfproviderlint/passes/S004/S004.go new file mode 100644 index 000000000000..42632d48e653 --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/S004/S004.go @@ -0,0 +1,82 @@ +// Package S004 defines an Analyzer that checks for +// Schema with Required enabled and Default configured +package S004 + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/bflad/tfproviderlint/passes/commentignore" + "github.com/bflad/tfproviderlint/passes/schemaschema" +) + +const Doc = `check for Schema with Required enabled and Default configured + +The S004 analyzer reports cases of schemas which enables Required +and configures Default, which will fail provider schema validation.` + +const analyzerName = "S004" + +var Analyzer = &analysis.Analyzer{ + Name: analyzerName, + Doc: Doc, + Requires: []*analysis.Analyzer{ + schemaschema.Analyzer, + commentignore.Analyzer, + }, + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + ignorer := pass.ResultOf[commentignore.Analyzer].(*commentignore.Ignorer) + schemas := pass.ResultOf[schemaschema.Analyzer].([]*ast.CompositeLit) + for _, schema := range schemas { + if ignorer.ShouldIgnore(analyzerName, schema) { + continue + } + + var defaultConfigured, requiredEnabled bool + + for _, elt := range schema.Elts { + switch v := elt.(type) { + default: + continue + case *ast.KeyValueExpr: + name := v.Key.(*ast.Ident).Name + + if name != "Default" && name != "Required" { + continue + } + + switch v := v.Value.(type) { + default: + if name == "Default" { + defaultConfigured = true + } + + continue + case *ast.Ident: + value := v.Name + + if name == "Default" && value != "nil" { + defaultConfigured = true + continue + } + + if value != "true" { + continue + } + + requiredEnabled = true + } + } + } + + if defaultConfigured && requiredEnabled { + pass.Reportf(schema.Type.(*ast.SelectorExpr).Sel.Pos(), "%s: schema should not enable Required and configure Default", analyzerName) + } + } + + return nil, nil +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/S005/S005.go b/vendor/github.com/bflad/tfproviderlint/passes/S005/S005.go new file mode 100644 index 000000000000..9da967752c6a --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/S005/S005.go @@ -0,0 +1,82 @@ +// Package S005 defines an Analyzer that checks for +// Schema with Computed enabled and Default configured +package S005 + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/bflad/tfproviderlint/passes/commentignore" + "github.com/bflad/tfproviderlint/passes/schemaschema" +) + +const Doc = `check for Schema with Computed enabled and Default configured + +The S005 analyzer reports cases of schemas which enables Computed +and configures Default, which will fail provider schema validation.` + +const analyzerName = "S005" + +var Analyzer = &analysis.Analyzer{ + Name: analyzerName, + Doc: Doc, + Requires: []*analysis.Analyzer{ + schemaschema.Analyzer, + commentignore.Analyzer, + }, + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + ignorer := pass.ResultOf[commentignore.Analyzer].(*commentignore.Ignorer) + schemas := pass.ResultOf[schemaschema.Analyzer].([]*ast.CompositeLit) + for _, schema := range schemas { + if ignorer.ShouldIgnore(analyzerName, schema) { + continue + } + + var computedEnabled, defaultConfigured bool + + for _, elt := range schema.Elts { + switch v := elt.(type) { + default: + continue + case *ast.KeyValueExpr: + name := v.Key.(*ast.Ident).Name + + if name != "Default" && name != "Computed" { + continue + } + + switch v := v.Value.(type) { + default: + if name == "Default" { + defaultConfigured = true + } + + continue + case *ast.Ident: + value := v.Name + + if name == "Default" && value != "nil" { + defaultConfigured = true + continue + } + + if value != "true" { + continue + } + + computedEnabled = true + } + } + } + + if computedEnabled && defaultConfigured { + pass.Reportf(schema.Type.(*ast.SelectorExpr).Sel.Pos(), "%s: schema should not enable Computed and configure Default", analyzerName) + } + } + + return nil, nil +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/S006/S006.go b/vendor/github.com/bflad/tfproviderlint/passes/S006/S006.go new file mode 100644 index 000000000000..aacfff3368b5 --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/S006/S006.go @@ -0,0 +1,91 @@ +// Package S006 defines an Analyzer that checks for +// Schema of TypeMap missing Elem +package S006 + +import ( + "go/ast" + "go/types" + "strings" + + "golang.org/x/tools/go/analysis" + + "github.com/bflad/tfproviderlint/passes/commentignore" + "github.com/bflad/tfproviderlint/passes/schemaschema" +) + +const Doc = `check for Schema of TypeMap missing Elem + +The S006 analyzer reports cases of TypeMap schemas missing Elem, +which currently passes Terraform schema validation, but breaks downstream tools +and may be required in the future.` + +const analyzerName = "S006" + +var Analyzer = &analysis.Analyzer{ + Name: analyzerName, + Doc: Doc, + Requires: []*analysis.Analyzer{ + schemaschema.Analyzer, + commentignore.Analyzer, + }, + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + ignorer := pass.ResultOf[commentignore.Analyzer].(*commentignore.Ignorer) + schemas := pass.ResultOf[schemaschema.Analyzer].([]*ast.CompositeLit) + for _, schema := range schemas { + if ignorer.ShouldIgnore(analyzerName, schema) { + continue + } + + var elemFound bool + var typeMap bool + + for _, elt := range schema.Elts { + switch v := elt.(type) { + default: + continue + case *ast.KeyValueExpr: + name := v.Key.(*ast.Ident).Name + + if name == "Elem" { + elemFound = true + continue + } + + if name != "Type" { + continue + } + + switch v := v.Value.(type) { + default: + continue + case *ast.SelectorExpr: + // Use AST over TypesInfo here as schema uses ValueType + if v.Sel.Name != "TypeMap" { + continue + } + + switch t := pass.TypesInfo.TypeOf(v).(type) { + default: + continue + case *types.Named: + // HasSuffix here due to vendoring + if !strings.HasSuffix(t.Obj().Pkg().Path(), "github.com/hashicorp/terraform/helper/schema") { + continue + } + + typeMap = true + } + } + } + } + + if typeMap && !elemFound { + pass.Reportf(schema.Type.(*ast.SelectorExpr).Sel.Pos(), "%s: schema of TypeMap should include Elem", analyzerName) + } + } + + return nil, nil +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/acctestcase/acctestcase.go b/vendor/github.com/bflad/tfproviderlint/passes/acctestcase/acctestcase.go new file mode 100644 index 000000000000..b16c4820728d --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/acctestcase/acctestcase.go @@ -0,0 +1,63 @@ +package acctestcase + +import ( + "go/ast" + "go/types" + "reflect" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var Analyzer = &analysis.Analyzer{ + Name: "acctestcase", + Doc: "find github.com/hashicorp/terraform/helper/resource.TestCase literals for later passes", + Requires: []*analysis.Analyzer{ + inspect.Analyzer, + }, + Run: run, + ResultType: reflect.TypeOf([]*ast.CompositeLit{}), +} + +func run(pass *analysis.Pass) (interface{}, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter := []ast.Node{ + (*ast.CompositeLit)(nil), + } + var result []*ast.CompositeLit + + inspect.Preorder(nodeFilter, func(n ast.Node) { + x := n.(*ast.CompositeLit) + + if !isResourceTestCase(pass, x) { + return + } + + result = append(result, x) + }) + + return result, nil +} + +func isResourceTestCase(pass *analysis.Pass, cl *ast.CompositeLit) bool { + switch v := cl.Type.(type) { + default: + return false + case *ast.SelectorExpr: + switch t := pass.TypesInfo.TypeOf(v).(type) { + default: + return false + case *types.Named: + if t.Obj().Name() != "TestCase" { + return false + } + // HasSuffix here due to vendoring + if !strings.HasSuffix(t.Obj().Pkg().Path(), "github.com/hashicorp/terraform/helper/resource") { + return false + } + } + } + return true +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/acctestfunc/acctestfunc.go b/vendor/github.com/bflad/tfproviderlint/passes/acctestfunc/acctestfunc.go new file mode 100644 index 000000000000..3ccc3f2ca267 --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/acctestfunc/acctestfunc.go @@ -0,0 +1,41 @@ +package acctestfunc + +import ( + "go/ast" + "reflect" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var Analyzer = &analysis.Analyzer{ + Name: "acctestfunc", + Doc: "find function names starting with TestAcc for later passes", + Requires: []*analysis.Analyzer{ + inspect.Analyzer, + }, + Run: run, + ResultType: reflect.TypeOf([]*ast.FuncDecl{}), +} + +func run(pass *analysis.Pass) (interface{}, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter := []ast.Node{ + (*ast.FuncDecl)(nil), + } + var result []*ast.FuncDecl + + inspect.Preorder(nodeFilter, func(n ast.Node) { + x := n.(*ast.FuncDecl) + + if !strings.HasPrefix(x.Name.Name, "TestAcc") { + return + } + + result = append(result, x) + }) + + return result, nil +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/commentignore/ignore.go b/vendor/github.com/bflad/tfproviderlint/passes/commentignore/ignore.go new file mode 100644 index 000000000000..ba4778d28a9f --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/commentignore/ignore.go @@ -0,0 +1,60 @@ +package commentignore + +import ( + "go/ast" + "go/token" + "reflect" + "strings" + + "golang.org/x/tools/go/analysis" +) + +const commentIgnorePrefix = "lintignore:" + +var Analyzer = &analysis.Analyzer{ + Name: "commentignore", + Doc: "find ignore comments for later passes", + Run: run, + ResultType: reflect.TypeOf(new(Ignorer)), +} + +type ignore struct { + Pos token.Pos + End token.Pos +} + +type Ignorer struct { + ignores map[string][]ignore +} + +func (ignorer *Ignorer) ShouldIgnore(key string, n ast.Node) bool { + for _, ig := range ignorer.ignores[key] { + if ig.Pos <= n.Pos() && ig.End >= n.End() { + return true + } + } + + return false +} + +func run(pass *analysis.Pass) (interface{}, error) { + ignores := map[string][]ignore{} + for _, f := range pass.Files { + cmap := ast.NewCommentMap(pass.Fset, f, f.Comments) + for n, cgs := range cmap { + for _, cg := range cgs { + if strings.HasPrefix(cg.Text(), commentIgnorePrefix) { + key := strings.TrimPrefix(cg.Text(), commentIgnorePrefix) + key = strings.TrimSpace(key) + + // is it possible for nested pos/end to be outside the largest nodes? + ignores[key] = append(ignores[key], ignore{n.Pos(), n.End()}) + } + } + } + } + + return &Ignorer{ + ignores: ignores, + }, nil +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/resourcedataset/resourcedataset.go b/vendor/github.com/bflad/tfproviderlint/passes/resourcedataset/resourcedataset.go new file mode 100644 index 000000000000..28d74f0055ce --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/resourcedataset/resourcedataset.go @@ -0,0 +1,113 @@ +package resourcedataset + +import ( + "go/ast" + "go/types" + "reflect" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var Analyzer = &analysis.Analyzer{ + Name: "resourcedataset", + Doc: "find github.com/hashicorp/terraform/helper/schema.ResourceData.Set() calls for later passes", + Requires: []*analysis.Analyzer{ + inspect.Analyzer, + }, + Run: run, + ResultType: reflect.TypeOf([]*ast.CallExpr{}), +} + +func run(pass *analysis.Pass) (interface{}, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter := []ast.Node{ + (*ast.CallExpr)(nil), + } + var result []*ast.CallExpr + + inspect.Preorder(nodeFilter, func(n ast.Node) { + x := n.(*ast.CallExpr) + + if !isResourceDataSet(pass, x) { + return + } + + result = append(result, x) + }) + + return result, nil +} + +func isResourceDataSet(pass *analysis.Pass, ce *ast.CallExpr) bool { + switch f := ce.Fun.(type) { + default: + return false + case *ast.SelectorExpr: + if f.Sel.Name != "Set" { + return false + } + + switch x := f.X.(type) { + default: + return false + case *ast.Ident: + if x.Obj == nil { + return false + } + + switch decl := x.Obj.Decl.(type) { + default: + return false + case *ast.Field: + switch t := decl.Type.(type) { + default: + return false + case *ast.StarExpr: + switch t := pass.TypesInfo.TypeOf(t.X).(type) { + default: + return false + case *types.Named: + if !isSchemaResourceData(t) { + return false + } + } + case *ast.SelectorExpr: + switch t := pass.TypesInfo.TypeOf(t).(type) { + default: + return false + case *types.Named: + if !isSchemaResourceData(t) { + return false + } + } + } + case *ast.ValueSpec: + switch t := pass.TypesInfo.TypeOf(decl.Type).(type) { + default: + return false + case *types.Named: + if !isSchemaResourceData(t) { + return false + } + } + } + } + } + return true +} + +func isSchemaResourceData(t *types.Named) bool { + if t.Obj().Name() != "ResourceData" { + return false + } + + // HasSuffix here due to vendoring + if !strings.HasSuffix(t.Obj().Pkg().Path(), "github.com/hashicorp/terraform/helper/schema") { + return false + } + + return true +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/schemaresource/README.md b/vendor/github.com/bflad/tfproviderlint/passes/schemaresource/README.md new file mode 100644 index 000000000000..63b33438ff5d --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/schemaresource/README.md @@ -0,0 +1,9 @@ +# passes/schemaresource + +This pass only works with Terraform resources that are fully defined in a single function: + +```go +func someResourceFunc() *schema.Resource { + return &schema.Resource{ /* ... entire resource ... */ } +} +``` diff --git a/vendor/github.com/bflad/tfproviderlint/passes/schemaresource/schemaresource.go b/vendor/github.com/bflad/tfproviderlint/passes/schemaresource/schemaresource.go new file mode 100644 index 000000000000..d341038898c6 --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/schemaresource/schemaresource.go @@ -0,0 +1,63 @@ +package schemaresource + +import ( + "go/ast" + "go/types" + "reflect" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var Analyzer = &analysis.Analyzer{ + Name: "schemaresource", + Doc: "find github.com/hashicorp/terraform/helper/schema.Resource literals for later passes", + Requires: []*analysis.Analyzer{ + inspect.Analyzer, + }, + Run: run, + ResultType: reflect.TypeOf([]*ast.CompositeLit{}), +} + +func run(pass *analysis.Pass) (interface{}, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter := []ast.Node{ + (*ast.CompositeLit)(nil), + } + var result []*ast.CompositeLit + + inspect.Preorder(nodeFilter, func(n ast.Node) { + x := n.(*ast.CompositeLit) + + if !isSchemaResource(pass, x) { + return + } + + result = append(result, x) + }) + + return result, nil +} + +func isSchemaResource(pass *analysis.Pass, cl *ast.CompositeLit) bool { + switch v := cl.Type.(type) { + default: + return false + case *ast.SelectorExpr: + switch t := pass.TypesInfo.TypeOf(v).(type) { + default: + return false + case *types.Named: + if t.Obj().Name() != "Resource" { + return false + } + // HasSuffix here due to vendoring + if !strings.HasSuffix(t.Obj().Pkg().Path(), "github.com/hashicorp/terraform/helper/schema") { + return false + } + } + } + return true +} diff --git a/vendor/github.com/bflad/tfproviderlint/passes/schemaschema/README.md b/vendor/github.com/bflad/tfproviderlint/passes/schemaschema/README.md new file mode 100644 index 000000000000..18fdcb8a96d8 --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/schemaschema/README.md @@ -0,0 +1,12 @@ +# passes/schemaschema + +This pass only works with Terraform schema that are fully defined: + +```go +&schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: /* ... */, +}, +``` diff --git a/vendor/github.com/bflad/tfproviderlint/passes/schemaschema/schemaschema.go b/vendor/github.com/bflad/tfproviderlint/passes/schemaschema/schemaschema.go new file mode 100644 index 000000000000..d193498a04b0 --- /dev/null +++ b/vendor/github.com/bflad/tfproviderlint/passes/schemaschema/schemaschema.go @@ -0,0 +1,63 @@ +package schemaschema + +import ( + "go/ast" + "go/types" + "reflect" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var Analyzer = &analysis.Analyzer{ + Name: "schemaschema", + Doc: "find github.com/hashicorp/terraform/helper/schema.Schema literals for later passes", + Requires: []*analysis.Analyzer{ + inspect.Analyzer, + }, + Run: run, + ResultType: reflect.TypeOf([]*ast.CompositeLit{}), +} + +func run(pass *analysis.Pass) (interface{}, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter := []ast.Node{ + (*ast.CompositeLit)(nil), + } + var result []*ast.CompositeLit + + inspect.Preorder(nodeFilter, func(n ast.Node) { + x := n.(*ast.CompositeLit) + + if !isSchemaSchema(pass, x) { + return + } + + result = append(result, x) + }) + + return result, nil +} + +func isSchemaSchema(pass *analysis.Pass, cl *ast.CompositeLit) bool { + switch v := cl.Type.(type) { + default: + return false + case *ast.SelectorExpr: + switch t := pass.TypesInfo.TypeOf(v).(type) { + default: + return false + case *types.Named: + if t.Obj().Name() != "Schema" { + return false + } + // HasSuffix here due to vendoring + if !strings.HasSuffix(t.Obj().Pkg().Path(), "github.com/hashicorp/terraform/helper/schema") { + return false + } + } + } + return true +} diff --git a/vendor/golang.org/x/tools/go/analysis/analysis.go b/vendor/golang.org/x/tools/go/analysis/analysis.go index 4d8a6e5e7d9c..8eb73162593e 100644 --- a/vendor/golang.org/x/tools/go/analysis/analysis.go +++ b/vendor/golang.org/x/tools/go/analysis/analysis.go @@ -128,10 +128,32 @@ type Pass struct { // See comments for ExportObjectFact. ExportPackageFact func(fact Fact) + // AllPackageFacts returns a new slice containing all package facts 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 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{}) { diff --git a/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go b/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go new file mode 100644 index 000000000000..a03a185fc0a1 --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go @@ -0,0 +1,344 @@ +// 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/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. +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) + } + + // 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 + } + } + + return analyzers +} + +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 as these have no effect on unitchecker + // (as invoked by 'go vet'). + switch f.Name { + case "debug", "cpuprofile", "memprofile", "trace": + 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 miminal 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 { + data, _ := ioutil.ReadFile(posn.Filename) + lines := strings.Split(string(data), "\n") + for i := posn.Line - Context; i <= posn.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 + for _, f := range diags { + diagnostics = append(diagnostics, jsonDiagnostic{ + Category: f.Category, + Posn: fset.Position(f.Pos).String(), + Message: f.Message, + }) + } + v = diagnostics + } + if v != nil { + m, ok := tree[id] + if !ok { + m = make(map[string]interface{}) + tree[id] = m + } + m[name] = v + } +} + +func (tree JSONTree) Print() { + data, err := json.MarshalIndent(tree, "", "\t") + if err != nil { + log.Panicf("internal error: JSON marshalling failed: %v", err) + } + fmt.Printf("%s\n", data) +} diff --git a/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/help.go b/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/help.go new file mode 100644 index 000000000000..c5a70f3b7d65 --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/help.go @@ -0,0 +1,92 @@ +package analysisflags + +import ( + "flag" + "fmt" + "log" + "os" + "sort" + "strings" + + "golang.org/x/tools/go/analysis" +) + +const help = `PROGNAME is a tool for static analysis of Go programs. + +PROGNAME examines Go source code and reports suspicious constructs, +such as Printf calls whose arguments do not align with the format +string. It uses heuristics that do not guarantee all reports are +genuine problems, but it can find errors not caught by the compilers. +` + +// Help implements the help subcommand for a multichecker or unitchecker +// style command. The optional args specify the analyzers to describe. +// Help calls log.Fatal if no such analyzer exists. +func Help(progname string, analyzers []*analysis.Analyzer, args []string) { + // No args: show summary of all analyzers. + if len(args) == 0 { + fmt.Println(strings.Replace(help, "PROGNAME", progname, -1)) + fmt.Println("Registered analyzers:") + fmt.Println() + sort.Slice(analyzers, func(i, j int) bool { + return analyzers[i].Name < analyzers[j].Name + }) + for _, a := range analyzers { + title := strings.Split(a.Doc, "\n\n")[0] + fmt.Printf(" %-12s %s\n", a.Name, title) + } + fmt.Println("\nBy default all analyzers are run.") + fmt.Println("To select specific analyzers, use the -NAME flag for each one,") + fmt.Println(" or -NAME=false to run all analyzers not explicitly disabled.") + + // Show only the core command-line flags. + fmt.Println("\nCore flags:") + fmt.Println() + fs := flag.NewFlagSet("", flag.ExitOnError) + flag.VisitAll(func(f *flag.Flag) { + if !strings.Contains(f.Name, ".") { + fs.Var(f.Value, f.Name, f.Usage) + } + }) + fs.SetOutput(os.Stdout) + fs.PrintDefaults() + + fmt.Printf("\nTo see details and flags of a specific analyzer, run '%s help name'.\n", progname) + + return + } + + // Show help on specific analyzer(s). +outer: + for _, arg := range args { + for _, a := range analyzers { + if a.Name == arg { + paras := strings.Split(a.Doc, "\n\n") + title := paras[0] + fmt.Printf("%s: %s\n", a.Name, title) + + // Show only the flags relating to this analysis, + // properly prefixed. + first := true + fs := flag.NewFlagSet(a.Name, flag.ExitOnError) + a.Flags.VisitAll(func(f *flag.Flag) { + if first { + first = false + fmt.Println("\nAnalyzer flags:") + fmt.Println() + } + fs.Var(f.Value, a.Name+"."+f.Name, f.Usage) + }) + fs.SetOutput(os.Stdout) + fs.PrintDefaults() + + if len(paras) > 1 { + fmt.Printf("\n%s\n", strings.Join(paras[1:], "\n\n")) + } + + continue outer + } + } + log.Fatalf("Analyzer %q not registered", arg) + } +} diff --git a/vendor/golang.org/x/tools/go/analysis/internal/checker/checker.go b/vendor/golang.org/x/tools/go/analysis/internal/checker/checker.go new file mode 100644 index 000000000000..4cade7770542 --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/internal/checker/checker.go @@ -0,0 +1,726 @@ +// 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 checker defines the implementation of the checker commands. +// The same code drives the multi-analysis driver, the single-analysis +// driver that is conventionally provided for convenience along with +// each analysis package, and the test driver. +package checker + +import ( + "bytes" + "encoding/gob" + "flag" + "fmt" + "go/token" + "go/types" + "log" + "os" + "reflect" + "runtime" + "runtime/pprof" + "runtime/trace" + "sort" + "strings" + "sync" + "time" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/internal/analysisflags" + "golang.org/x/tools/go/packages" +) + +var ( + // Debug is a set of single-letter flags: + // + // f show [f]acts as they are created + // p disable [p]arallel execution of analyzers + // s do additional [s]anity checks on fact types and serialization + // t show [t]iming info (NB: use 'p' flag to avoid GC/scheduler noise) + // v show [v]erbose logging + // + Debug = "" + + // Log files for optional performance tracing. + CPUProfile, MemProfile, Trace string +) + +// RegisterFlags registers command-line flags used by the analysis driver. +func RegisterFlags() { + // When adding flags here, remember to update + // the list of suppressed flags in analysisflags. + + flag.StringVar(&Debug, "debug", Debug, `debug flags, any subset of "fpstv"`) + + flag.StringVar(&CPUProfile, "cpuprofile", "", "write CPU profile to this file") + flag.StringVar(&MemProfile, "memprofile", "", "write memory profile to this file") + flag.StringVar(&Trace, "trace", "", "write trace log to this file") +} + +// Run loads the packages specified by args using go/packages, +// then applies the specified analyzers to them. +// Analysis flags must already have been set. +// It provides most of the logic for the main functions of both the +// singlechecker and the multi-analysis commands. +// It returns the appropriate exit code. +func Run(args []string, analyzers []*analysis.Analyzer) (exitcode int) { + if CPUProfile != "" { + f, err := os.Create(CPUProfile) + if err != nil { + log.Fatal(err) + } + if err := pprof.StartCPUProfile(f); err != nil { + log.Fatal(err) + } + // NB: profile won't be written in case of error. + defer pprof.StopCPUProfile() + } + + if Trace != "" { + f, err := os.Create(Trace) + if err != nil { + log.Fatal(err) + } + if err := trace.Start(f); err != nil { + log.Fatal(err) + } + // NB: trace log won't be written in case of error. + defer func() { + trace.Stop() + log.Printf("To view the trace, run:\n$ go tool trace view %s", Trace) + }() + } + + if MemProfile != "" { + f, err := os.Create(MemProfile) + if err != nil { + log.Fatal(err) + } + // NB: memprofile won't be written in case of error. + defer func() { + runtime.GC() // get up-to-date statistics + if err := pprof.WriteHeapProfile(f); err != nil { + log.Fatalf("Writing memory profile: %v", err) + } + f.Close() + }() + } + + // Load the packages. + if dbg('v') { + log.SetPrefix("") + log.SetFlags(log.Lmicroseconds) // display timing + log.Printf("load %s", args) + } + + // Optimization: if the selected analyzers don't produce/consume + // facts, we need source only for the initial packages. + allSyntax := needFacts(analyzers) + initial, err := load(args, allSyntax) + if err != nil { + log.Print(err) + return 1 // load errors + } + + // Print the results. + roots := analyze(initial, analyzers) + + return printDiagnostics(roots) +} + +// load loads the initial packages. +func load(patterns []string, allSyntax bool) ([]*packages.Package, error) { + mode := packages.LoadSyntax + if allSyntax { + mode = packages.LoadAllSyntax + } + conf := packages.Config{ + Mode: mode, + Tests: true, + } + initial, err := packages.Load(&conf, patterns...) + if err == nil { + if n := packages.PrintErrors(initial); n > 1 { + err = fmt.Errorf("%d errors during loading", n) + } else if n == 1 { + err = fmt.Errorf("error during loading") + } else if len(initial) == 0 { + err = fmt.Errorf("%s matched no packages", strings.Join(patterns, " ")) + } + } + + return initial, err +} + +// TestAnalyzer applies an analysis to a set of packages (and their +// dependencies if necessary) and returns the results. +// +// Facts about pkg are returned in a map keyed by object; package facts +// have a nil key. +// +// This entry point is used only by analysistest. +func TestAnalyzer(a *analysis.Analyzer, pkgs []*packages.Package) []*TestAnalyzerResult { + var results []*TestAnalyzerResult + for _, act := range analyze(pkgs, []*analysis.Analyzer{a}) { + facts := make(map[types.Object][]analysis.Fact) + for key, fact := range act.objectFacts { + if key.obj.Pkg() == act.pass.Pkg { + facts[key.obj] = append(facts[key.obj], fact) + } + } + for key, fact := range act.packageFacts { + if key.pkg == act.pass.Pkg { + facts[nil] = append(facts[nil], fact) + } + } + + results = append(results, &TestAnalyzerResult{act.pass, act.diagnostics, facts, act.result, act.err}) + } + return results +} + +type TestAnalyzerResult struct { + Pass *analysis.Pass + Diagnostics []analysis.Diagnostic + Facts map[types.Object][]analysis.Fact + Result interface{} + Err error +} + +func analyze(pkgs []*packages.Package, analyzers []*analysis.Analyzer) []*action { + // Construct the action graph. + if dbg('v') { + log.Printf("building graph of analysis passes") + } + + // Each graph node (action) is one unit of analysis. + // Edges express package-to-package (vertical) dependencies, + // and analysis-to-analysis (horizontal) dependencies. + type key struct { + *analysis.Analyzer + *packages.Package + } + actions := make(map[key]*action) + + var mkAction func(a *analysis.Analyzer, pkg *packages.Package) *action + mkAction = func(a *analysis.Analyzer, pkg *packages.Package) *action { + k := key{a, pkg} + act, ok := actions[k] + if !ok { + act = &action{a: a, pkg: pkg} + + // Add a dependency on each required analyzers. + for _, req := range a.Requires { + act.deps = append(act.deps, mkAction(req, pkg)) + } + + // An analysis that consumes/produces facts + // must run on the package's dependencies too. + if len(a.FactTypes) > 0 { + paths := make([]string, 0, len(pkg.Imports)) + for path := range pkg.Imports { + paths = append(paths, path) + } + sort.Strings(paths) // for determinism + for _, path := range paths { + dep := mkAction(a, pkg.Imports[path]) + act.deps = append(act.deps, dep) + } + } + + actions[k] = act + } + return act + } + + // Build nodes for initial packages. + var roots []*action + for _, a := range analyzers { + for _, pkg := range pkgs { + root := mkAction(a, pkg) + root.isroot = true + roots = append(roots, root) + } + } + + // Execute the graph in parallel. + execAll(roots) + + return roots +} + +// printDiagnostics prints the diagnostics for the root packages in either +// plain text or JSON format. JSON format also includes errors for any +// dependencies. +// +// It returns the exitcode: in plain mode, 0 for success, 1 for analysis +// errors, and 3 for diagnostics. We avoid 2 since the flag package uses +// it. JSON mode always succeeds at printing errors and diagnostics in a +// structured form to stdout. +func printDiagnostics(roots []*action) (exitcode int) { + // Print the output. + // + // Print diagnostics only for root packages, + // but errors for all packages. + printed := make(map[*action]bool) + var print func(*action) + var visitAll func(actions []*action) + visitAll = func(actions []*action) { + for _, act := range actions { + if !printed[act] { + printed[act] = true + visitAll(act.deps) + print(act) + } + } + } + + if analysisflags.JSON { + // JSON output + tree := make(analysisflags.JSONTree) + print = func(act *action) { + var diags []analysis.Diagnostic + if act.isroot { + diags = act.diagnostics + } + tree.Add(act.pkg.Fset, act.pkg.ID, act.a.Name, diags, act.err) + } + visitAll(roots) + tree.Print() + } else { + // plain text output + + // De-duplicate diagnostics by position (not token.Pos) to + // avoid double-reporting in source files that belong to + // multiple packages, such as foo and foo.test. + type key struct { + token.Position + *analysis.Analyzer + message string + } + seen := make(map[key]bool) + + print = func(act *action) { + if act.err != nil { + fmt.Fprintf(os.Stderr, "%s: %v\n", act.a.Name, act.err) + exitcode = 1 // analysis failed, at least partially + return + } + if act.isroot { + for _, diag := range act.diagnostics { + // We don't display a.Name/f.Category + // as most users don't care. + + posn := act.pkg.Fset.Position(diag.Pos) + k := key{posn, act.a, diag.Message} + if seen[k] { + continue // duplicate + } + seen[k] = true + + analysisflags.PrintPlain(act.pkg.Fset, diag) + } + } + } + visitAll(roots) + + if exitcode == 0 && len(seen) > 0 { + exitcode = 3 // successfuly produced diagnostics + } + } + + // Print timing info. + if dbg('t') { + if !dbg('p') { + log.Println("Warning: times are mostly GC/scheduler noise; use -debug=tp to disable parallelism") + } + var all []*action + var total time.Duration + for act := range printed { + all = append(all, act) + total += act.duration + } + sort.Slice(all, func(i, j int) bool { + return all[i].duration > all[j].duration + }) + + // Print actions accounting for 90% of the total. + var sum time.Duration + for _, act := range all { + fmt.Fprintf(os.Stderr, "%s\t%s\n", act.duration, act) + sum += act.duration + if sum >= total*9/10 { + break + } + } + } + + return exitcode +} + +// needFacts reports whether any analysis required by the specified set +// needs facts. If so, we must load the entire program from source. +func needFacts(analyzers []*analysis.Analyzer) bool { + seen := make(map[*analysis.Analyzer]bool) + var q []*analysis.Analyzer // for BFS + q = append(q, analyzers...) + for len(q) > 0 { + a := q[0] + q = q[1:] + if !seen[a] { + seen[a] = true + if len(a.FactTypes) > 0 { + return true + } + q = append(q, a.Requires...) + } + } + return false +} + +// An action represents one unit of analysis work: the application of +// one analysis to one package. Actions form a DAG, both within a +// package (as different analyzers are applied, either in sequence or +// parallel), and across packages (as dependencies are analyzed). +type action struct { + once sync.Once + a *analysis.Analyzer + pkg *packages.Package + pass *analysis.Pass + isroot bool + deps []*action + objectFacts map[objectFactKey]analysis.Fact + packageFacts map[packageFactKey]analysis.Fact + inputs map[*analysis.Analyzer]interface{} + result interface{} + diagnostics []analysis.Diagnostic + err error + duration time.Duration +} + +type objectFactKey struct { + obj types.Object + typ reflect.Type +} + +type packageFactKey struct { + pkg *types.Package + typ reflect.Type +} + +func (act *action) String() string { + return fmt.Sprintf("%s@%s", act.a, act.pkg) +} + +func execAll(actions []*action) { + sequential := dbg('p') + var wg sync.WaitGroup + for _, act := range actions { + wg.Add(1) + work := func(act *action) { + act.exec() + wg.Done() + } + if sequential { + work(act) + } else { + go work(act) + } + } + wg.Wait() +} + +func (act *action) exec() { act.once.Do(act.execOnce) } + +func (act *action) execOnce() { + // Analyze dependencies. + execAll(act.deps) + + // TODO(adonovan): uncomment this during profiling. + // It won't build pre-go1.11 but conditional compilation + // using build tags isn't warranted. + // + // ctx, task := trace.NewTask(context.Background(), "exec") + // trace.Log(ctx, "pass", act.String()) + // defer task.End() + + // Record time spent in this node but not its dependencies. + // In parallel mode, due to GC/scheduler contention, the + // time is 5x higher than in sequential mode, even with a + // semaphore limiting the number of threads here. + // So use -debug=tp. + if dbg('t') { + t0 := time.Now() + defer func() { act.duration = time.Since(t0) }() + } + + // Report an error if any dependency failed. + var failed []string + for _, dep := range act.deps { + if dep.err != nil { + failed = append(failed, dep.String()) + } + } + if failed != nil { + sort.Strings(failed) + act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", ")) + return + } + + // Plumb the output values of the dependencies + // into the inputs of this action. Also facts. + inputs := make(map[*analysis.Analyzer]interface{}) + act.objectFacts = make(map[objectFactKey]analysis.Fact) + act.packageFacts = make(map[packageFactKey]analysis.Fact) + for _, dep := range act.deps { + if dep.pkg == act.pkg { + // Same package, different analysis (horizontal edge): + // in-memory outputs of prerequisite analyzers + // become inputs to this analysis pass. + inputs[dep.a] = dep.result + + } else if dep.a == act.a { // (always true) + // Same analysis, different package (vertical edge): + // serialized facts produced by prerequisite analysis + // become available to this analysis pass. + inheritFacts(act, dep) + } + } + + // Run the analysis. + pass := &analysis.Pass{ + Analyzer: act.a, + Fset: act.pkg.Fset, + Files: act.pkg.Syntax, + OtherFiles: act.pkg.OtherFiles, + Pkg: act.pkg.Types, + TypesInfo: act.pkg.TypesInfo, + TypesSizes: act.pkg.TypesSizes, + ResultOf: inputs, + Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) }, + ImportObjectFact: act.importObjectFact, + ExportObjectFact: act.exportObjectFact, + ImportPackageFact: act.importPackageFact, + ExportPackageFact: act.exportPackageFact, + AllObjectFacts: act.allObjectFacts, + AllPackageFacts: act.allPackageFacts, + } + act.pass = pass + + var err error + if act.pkg.IllTyped && !pass.Analyzer.RunDespiteErrors { + err = fmt.Errorf("analysis skipped due to errors in package") + } else { + act.result, err = pass.Analyzer.Run(pass) + if err == nil { + if got, want := reflect.TypeOf(act.result), pass.Analyzer.ResultType; got != want { + err = fmt.Errorf( + "internal error: on package %s, analyzer %s returned a result of type %v, but declared ResultType %v", + pass.Pkg.Path(), pass.Analyzer, got, want) + } + } + } + act.err = err + + // disallow calls after Run + pass.ExportObjectFact = nil + pass.ExportPackageFact = nil +} + +// inheritFacts populates act.facts with +// those it obtains from its dependency, dep. +func inheritFacts(act, dep *action) { + serialize := dbg('s') + + for key, fact := range dep.objectFacts { + // Filter out facts related to objects + // that are irrelevant downstream + // (equivalently: not in the compiler export data). + if !exportedFrom(key.obj, dep.pkg.Types) { + if false { + log.Printf("%v: discarding %T fact from %s for %s: %s", act, fact, dep, key.obj, fact) + } + continue + } + + // Optionally serialize/deserialize fact + // to verify that it works across address spaces. + if serialize { + encodedFact, err := codeFact(fact) + if err != nil { + log.Panicf("internal error: encoding of %T fact failed in %v", fact, act) + } + fact = encodedFact + } + + if false { + log.Printf("%v: inherited %T fact for %s: %s", act, fact, key.obj, fact) + } + act.objectFacts[key] = fact + } + + for key, fact := range dep.packageFacts { + // TODO: filter out facts that belong to + // packages not mentioned in the export data + // to prevent side channels. + + // Optionally serialize/deserialize fact + // to verify that it works across address spaces + // and is deterministic. + if serialize { + encodedFact, err := codeFact(fact) + if err != nil { + log.Panicf("internal error: encoding of %T fact failed in %v", fact, act) + } + fact = encodedFact + } + + if false { + log.Printf("%v: inherited %T fact for %s: %s", act, fact, key.pkg.Path(), fact) + } + act.packageFacts[key] = fact + } +} + +// codeFact encodes then decodes a fact, +// just to exercise that logic. +func codeFact(fact analysis.Fact) (analysis.Fact, error) { + // We encode facts one at a time. + // A real modular driver would emit all facts + // into one encoder to improve gob efficiency. + var buf bytes.Buffer + if err := gob.NewEncoder(&buf).Encode(fact); err != nil { + return nil, err + } + + // Encode it twice and assert that we get the same bits. + // This helps detect nondeterministic Gob encoding (e.g. of maps). + var buf2 bytes.Buffer + if err := gob.NewEncoder(&buf2).Encode(fact); err != nil { + return nil, err + } + if !bytes.Equal(buf.Bytes(), buf2.Bytes()) { + return nil, fmt.Errorf("encoding of %T fact is nondeterministic", fact) + } + + new := reflect.New(reflect.TypeOf(fact).Elem()).Interface().(analysis.Fact) + if err := gob.NewDecoder(&buf).Decode(new); err != nil { + return nil, err + } + return new, nil +} + +// exportedFrom reports whether obj may be visible to a package that imports pkg. +// This includes not just the exported members of pkg, but also unexported +// constants, types, fields, and methods, perhaps belonging to oether packages, +// that find there way into the API. +// This is an overapproximation of the more accurate approach used by +// gc export data, which walks the type graph, but it's much simpler. +// +// TODO(adonovan): do more accurate filtering by walking the type graph. +func exportedFrom(obj types.Object, pkg *types.Package) bool { + switch obj := obj.(type) { + case *types.Func: + return obj.Exported() && obj.Pkg() == pkg || + obj.Type().(*types.Signature).Recv() != nil + case *types.Var: + return obj.Exported() && obj.Pkg() == pkg || + obj.IsField() + case *types.TypeName, *types.Const: + return true + } + return false // Nil, Builtin, Label, or PkgName +} + +// importObjectFact implements Pass.ImportObjectFact. +// Given a non-nil pointer ptr of type *T, where *T satisfies Fact, +// importObjectFact copies the fact value to *ptr. +func (act *action) importObjectFact(obj types.Object, ptr analysis.Fact) bool { + if obj == nil { + panic("nil object") + } + key := objectFactKey{obj, factType(ptr)} + if v, ok := act.objectFacts[key]; ok { + reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem()) + return true + } + return false +} + +// exportObjectFact implements Pass.ExportObjectFact. +func (act *action) exportObjectFact(obj types.Object, fact analysis.Fact) { + if act.pass.ExportObjectFact == nil { + log.Panicf("%s: Pass.ExportObjectFact(%s, %T) called after Run", act, obj, fact) + } + + if obj.Pkg() != act.pkg.Types { + log.Panicf("internal error: in analysis %s of package %s: Fact.Set(%s, %T): can't set facts on objects belonging another package", + act.a, act.pkg, obj, fact) + } + + key := objectFactKey{obj, factType(fact)} + act.objectFacts[key] = fact // clobber any existing entry + if dbg('f') { + objstr := types.ObjectString(obj, (*types.Package).Name) + fmt.Fprintf(os.Stderr, "%s: object %s has fact %s\n", + act.pkg.Fset.Position(obj.Pos()), objstr, fact) + } +} + +// allObjectFacts implements Pass.AllObjectFacts. +func (act *action) allObjectFacts() []analysis.ObjectFact { + facts := make([]analysis.ObjectFact, 0, len(act.objectFacts)) + for k := range act.objectFacts { + facts = append(facts, analysis.ObjectFact{k.obj, act.objectFacts[k]}) + } + return facts +} + +// importPackageFact implements Pass.ImportPackageFact. +// Given a non-nil pointer ptr of type *T, where *T satisfies Fact, +// fact copies the fact value to *ptr. +func (act *action) importPackageFact(pkg *types.Package, ptr analysis.Fact) bool { + if pkg == nil { + panic("nil package") + } + key := packageFactKey{pkg, factType(ptr)} + if v, ok := act.packageFacts[key]; ok { + reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem()) + return true + } + return false +} + +// exportPackageFact implements Pass.ExportPackageFact. +func (act *action) exportPackageFact(fact analysis.Fact) { + if act.pass.ExportPackageFact == nil { + log.Panicf("%s: Pass.ExportPackageFact(%T) called after Run", act, fact) + } + + key := packageFactKey{act.pass.Pkg, factType(fact)} + act.packageFacts[key] = fact // clobber any existing entry + if dbg('f') { + fmt.Fprintf(os.Stderr, "%s: package %s has fact %s\n", + act.pkg.Fset.Position(act.pass.Files[0].Pos()), act.pass.Pkg.Path(), fact) + } +} + +func factType(fact analysis.Fact) reflect.Type { + t := reflect.TypeOf(fact) + if t.Kind() != reflect.Ptr { + log.Fatalf("invalid Fact type: got %T, want pointer", t) + } + return t +} + +// allObjectFacts implements Pass.AllObjectFacts. +func (act *action) allPackageFacts() []analysis.PackageFact { + facts := make([]analysis.PackageFact, 0, len(act.packageFacts)) + for k := range act.packageFacts { + facts = append(facts, analysis.PackageFact{k.pkg, act.packageFacts[k]}) + } + return facts +} + +func dbg(b byte) bool { return strings.IndexByte(Debug, b) >= 0 } diff --git a/vendor/golang.org/x/tools/go/analysis/internal/facts/facts.go b/vendor/golang.org/x/tools/go/analysis/internal/facts/facts.go new file mode 100644 index 000000000000..468f148900f9 --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/internal/facts/facts.go @@ -0,0 +1,299 @@ +// 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 facts defines a serializable set of analysis.Fact. +// +// It provides a partial implementation of the Fact-related parts of the +// analysis.Pass interface for use in analysis drivers such as "go vet" +// and other build systems. +// +// The serial format is unspecified and may change, so the same version +// of this package must be used for reading and writing serialized facts. +// +// The handling of facts in the analysis system parallels the handling +// of type information in the compiler: during compilation of package P, +// the compiler emits an export data file that describes the type of +// every object (named thing) defined in package P, plus every object +// indirectly reachable from one of those objects. Thus the downstream +// compiler of package Q need only load one export data file per direct +// import of Q, and it will learn everything about the API of package P +// and everything it needs to know about the API of P's dependencies. +// +// Similarly, analysis of package P emits a fact set containing facts +// about all objects exported from P, plus additional facts about only +// those objects of P's dependencies that are reachable from the API of +// package P; the downstream analysis of Q need only load one fact set +// per direct import of Q. +// +// The notion of "exportedness" that matters here is that of the +// compiler. According to the language spec, a method pkg.T.f is +// unexported simply because its name starts with lowercase. But the +// compiler must nonethless export f so that downstream compilations can +// accurately ascertain whether pkg.T implements an interface pkg.I +// defined as interface{f()}. Exported thus means "described in export +// data". +// +package facts + +import ( + "bytes" + "encoding/gob" + "fmt" + "go/types" + "io/ioutil" + "log" + "reflect" + "sort" + "sync" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/types/objectpath" +) + +const debug = false + +// A Set is a set of analysis.Facts. +// +// Decode creates a Set of facts by reading from the imports of a given +// package, and Encode writes out the set. Between these operation, +// the Import and Export methods will query and update the set. +// +// All of Set's methods except String are safe to call concurrently. +type Set struct { + pkg *types.Package + mu sync.Mutex + m map[key]analysis.Fact +} + +type key struct { + pkg *types.Package + obj types.Object // (object facts only) + t reflect.Type +} + +// ImportObjectFact implements analysis.Pass.ImportObjectFact. +func (s *Set) ImportObjectFact(obj types.Object, ptr analysis.Fact) bool { + if obj == nil { + panic("nil object") + } + key := key{pkg: obj.Pkg(), obj: obj, t: reflect.TypeOf(ptr)} + s.mu.Lock() + defer s.mu.Unlock() + if v, ok := s.m[key]; ok { + reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem()) + return true + } + return false +} + +// ExportObjectFact implements analysis.Pass.ExportObjectFact. +func (s *Set) ExportObjectFact(obj types.Object, fact analysis.Fact) { + if obj.Pkg() != s.pkg { + log.Panicf("in package %s: ExportObjectFact(%s, %T): can't set fact on object belonging another package", + s.pkg, obj, fact) + } + key := key{pkg: obj.Pkg(), obj: obj, t: reflect.TypeOf(fact)} + s.mu.Lock() + s.m[key] = fact // clobber any existing entry + s.mu.Unlock() +} + +// ImportPackageFact implements analysis.Pass.ImportPackageFact. +func (s *Set) ImportPackageFact(pkg *types.Package, ptr analysis.Fact) bool { + if pkg == nil { + panic("nil package") + } + key := key{pkg: pkg, t: reflect.TypeOf(ptr)} + s.mu.Lock() + defer s.mu.Unlock() + if v, ok := s.m[key]; ok { + reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem()) + return true + } + return false +} + +// ExportPackageFact implements analysis.Pass.ExportPackageFact. +func (s *Set) ExportPackageFact(fact analysis.Fact) { + key := key{pkg: s.pkg, t: reflect.TypeOf(fact)} + s.mu.Lock() + s.m[key] = fact // clobber any existing entry + s.mu.Unlock() +} + +// gobFact is the Gob declaration of a serialized fact. +type gobFact struct { + PkgPath string // path of package + Object objectpath.Path // optional path of object relative to package itself + Fact analysis.Fact // type and value of user-defined Fact +} + +// Decode decodes all the facts relevant to the analysis of package pkg. +// The read function reads serialized fact data from an external source +// for one of of pkg's direct imports. The empty file is a valid +// encoding of an empty fact set. +// +// It is the caller's responsibility to call gob.Register on all +// necessary fact types. +func Decode(pkg *types.Package, read func(packagePath string) ([]byte, error)) (*Set, error) { + // Compute the import map for this package. + // See the package doc comment. + packages := importMap(pkg.Imports()) + + // Read facts from imported packages. + // Facts may describe indirectly imported packages, or their objects. + m := make(map[key]analysis.Fact) // one big bucket + for _, imp := range pkg.Imports() { + logf := func(format string, args ...interface{}) { + if debug { + prefix := fmt.Sprintf("in %s, importing %s: ", + pkg.Path(), imp.Path()) + log.Print(prefix, fmt.Sprintf(format, args...)) + } + } + + // Read the gob-encoded facts. + data, err := read(imp.Path()) + if err != nil { + return nil, fmt.Errorf("in %s, can't import facts for package %q: %v", + pkg.Path(), imp.Path(), err) + } + if len(data) == 0 { + continue // no facts + } + var gobFacts []gobFact + if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&gobFacts); err != nil { + return nil, fmt.Errorf("decoding facts for %q: %v", imp.Path(), err) + } + if debug { + logf("decoded %d facts: %v", len(gobFacts), gobFacts) + } + + // Parse each one into a key and a Fact. + for _, f := range gobFacts { + factPkg := packages[f.PkgPath] + if factPkg == nil { + // Fact relates to a dependency that was + // unused in this translation unit. Skip. + logf("no package %q; discarding %v", f.PkgPath, f.Fact) + continue + } + key := key{pkg: factPkg, t: reflect.TypeOf(f.Fact)} + if f.Object != "" { + // object fact + obj, err := objectpath.Object(factPkg, f.Object) + if err != nil { + // (most likely due to unexported object) + // TODO(adonovan): audit for other possibilities. + logf("no object for path: %v; discarding %s", err, f.Fact) + continue + } + key.obj = obj + logf("read %T fact %s for %v", f.Fact, f.Fact, key.obj) + } else { + // package fact + logf("read %T fact %s for %v", f.Fact, f.Fact, factPkg) + } + m[key] = f.Fact + } + } + + return &Set{pkg: pkg, m: m}, nil +} + +// Encode encodes a set of facts to a memory buffer. +// +// It may fail if one of the Facts could not be gob-encoded, but this is +// a sign of a bug in an Analyzer. +func (s *Set) Encode() []byte { + + // TODO(adonovan): opt: use a more efficient encoding + // that avoids repeating PkgPath for each fact. + + // Gather all facts, including those from imported packages. + var gobFacts []gobFact + + s.mu.Lock() + for k, fact := range s.m { + if debug { + log.Printf("%v => %s\n", k, fact) + } + var object objectpath.Path + if k.obj != nil { + path, err := objectpath.For(k.obj) + if err != nil { + if debug { + log.Printf("discarding fact %s about %s\n", fact, k.obj) + } + continue // object not accessible from package API; discard fact + } + object = path + } + gobFacts = append(gobFacts, gobFact{ + PkgPath: k.pkg.Path(), + Object: object, + Fact: fact, + }) + } + s.mu.Unlock() + + // Sort facts by (package, object, type) for determinism. + sort.Slice(gobFacts, func(i, j int) bool { + x, y := gobFacts[i], gobFacts[j] + if x.PkgPath != y.PkgPath { + return x.PkgPath < y.PkgPath + } + if x.Object != y.Object { + return x.Object < y.Object + } + tx := reflect.TypeOf(x.Fact) + ty := reflect.TypeOf(y.Fact) + if tx != ty { + return tx.String() < ty.String() + } + return false // equal + }) + + var buf bytes.Buffer + if len(gobFacts) > 0 { + if err := gob.NewEncoder(&buf).Encode(gobFacts); err != nil { + // Fact encoding should never fail. Identify the culprit. + for _, gf := range gobFacts { + if err := gob.NewEncoder(ioutil.Discard).Encode(gf); err != nil { + fact := gf.Fact + pkgpath := reflect.TypeOf(fact).Elem().PkgPath() + log.Panicf("internal error: gob encoding of analysis fact %s failed: %v; please report a bug against fact %T in package %q", + fact, err, fact, pkgpath) + } + } + } + } + + if debug { + log.Printf("package %q: encode %d facts, %d bytes\n", + s.pkg.Path(), len(gobFacts), buf.Len()) + } + + return buf.Bytes() +} + +// String is provided only for debugging, and must not be called +// concurrent with any Import/Export method. +func (s *Set) String() string { + var buf bytes.Buffer + buf.WriteString("{") + for k, f := range s.m { + if buf.Len() > 1 { + buf.WriteString(", ") + } + if k.obj != nil { + buf.WriteString(k.obj.String()) + } else { + buf.WriteString(k.pkg.Path()) + } + fmt.Fprintf(&buf, ": %v", f) + } + buf.WriteString("}") + return buf.String() +} diff --git a/vendor/golang.org/x/tools/go/analysis/internal/facts/imports.go b/vendor/golang.org/x/tools/go/analysis/internal/facts/imports.go new file mode 100644 index 000000000000..34740f48e04c --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/internal/facts/imports.go @@ -0,0 +1,88 @@ +// 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 facts + +import "go/types" + +// importMap computes the import map for a package by traversing the +// entire exported API each of its imports. +// +// This is a workaround for the fact that we cannot access the map used +// internally by the types.Importer returned by go/importer. The entries +// in this map are the packages and objects that may be relevant to the +// current analysis unit. +// +// Packages in the map that are only indirectly imported may be +// incomplete (!pkg.Complete()). +// +func importMap(imports []*types.Package) map[string]*types.Package { + objects := make(map[types.Object]bool) + packages := make(map[string]*types.Package) + + var addObj func(obj types.Object) bool + var addType func(T types.Type) + + addObj = func(obj types.Object) bool { + if !objects[obj] { + objects[obj] = true + addType(obj.Type()) + if pkg := obj.Pkg(); pkg != nil { + packages[pkg.Path()] = pkg + } + return true + } + return false + } + + addType = func(T types.Type) { + switch T := T.(type) { + case *types.Basic: + // nop + case *types.Named: + if addObj(T.Obj()) { + for i := 0; i < T.NumMethods(); i++ { + addObj(T.Method(i)) + } + } + case *types.Pointer: + addType(T.Elem()) + case *types.Slice: + addType(T.Elem()) + case *types.Array: + addType(T.Elem()) + case *types.Chan: + addType(T.Elem()) + case *types.Map: + addType(T.Key()) + addType(T.Elem()) + case *types.Signature: + addType(T.Params()) + addType(T.Results()) + case *types.Struct: + for i := 0; i < T.NumFields(); i++ { + addObj(T.Field(i)) + } + case *types.Tuple: + for i := 0; i < T.Len(); i++ { + addObj(T.At(i)) + } + case *types.Interface: + for i := 0; i < T.NumMethods(); i++ { + addObj(T.Method(i)) + } + } + } + + for _, imp := range imports { + packages[imp.Path()] = imp + + scope := imp.Scope() + for _, name := range scope.Names() { + addObj(scope.Lookup(name)) + } + } + + return packages +} diff --git a/vendor/golang.org/x/tools/go/analysis/multichecker/multichecker.go b/vendor/golang.org/x/tools/go/analysis/multichecker/multichecker.go new file mode 100644 index 000000000000..3c62be581896 --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/multichecker/multichecker.go @@ -0,0 +1,60 @@ +// 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 multichecker defines the main function for an analysis driver +// with several analyzers. This package makes it easy for anyone to build +// an analysis tool containing just the analyzers they need. +package multichecker + +import ( + "flag" + "fmt" + "log" + "os" + "path/filepath" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/internal/analysisflags" + "golang.org/x/tools/go/analysis/internal/checker" + "golang.org/x/tools/go/analysis/unitchecker" +) + +func Main(analyzers ...*analysis.Analyzer) { + progname := filepath.Base(os.Args[0]) + log.SetFlags(0) + log.SetPrefix(progname + ": ") // e.g. "vet: " + + if err := analysis.Validate(analyzers); err != nil { + log.Fatal(err) + } + + checker.RegisterFlags() + + analyzers = analysisflags.Parse(analyzers, true) + + args := flag.Args() + if len(args) == 0 { + fmt.Fprintf(os.Stderr, `%[1]s is a tool for static analysis of Go programs. + +Usage: %[1]s [-flag] [package] + +Run '%[1]s help' for more detail, + or '%[1]s help name' for details and flags of a specific analyzer. +`, progname) + os.Exit(1) + } + + if args[0] == "help" { + analysisflags.Help(progname, analyzers, args[1:]) + os.Exit(0) + } + + if len(args) == 1 && strings.HasSuffix(args[0], ".cfg") { + unitchecker.Run(args[0], analyzers) + panic("unreachable") + } + + os.Exit(checker.Run(args, analyzers)) +} diff --git a/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go b/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go index 6403d7783a2b..d41c4e97e326 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go @@ -130,7 +130,7 @@ var ( asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`) asmTEXT = re(`\bTEXT\b(.*)·([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+()]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`) asmDATA = re(`\b(DATA|GLOBL)\b`) - asmNamedFP = re(`([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`) + asmNamedFP = re(`\$?([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`) asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`) asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`) asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`) @@ -184,6 +184,7 @@ Files: fnName string localSize, argSize int wroteSP bool + noframe bool haveRetArg bool retLine []int ) @@ -231,6 +232,11 @@ Files: } } + // Ignore comments and commented-out code. + if i := strings.Index(line, "//"); i >= 0 { + line = line[:i] + } + if m := asmTEXT.FindStringSubmatch(line); m != nil { flushRet() if arch == "" { @@ -254,7 +260,7 @@ Files: // identifiers to represent the directory separator. pkgPath = strings.Replace(pkgPath, "∕", "/", -1) if pkgPath != pass.Pkg.Path() { - log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath) + // log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath) fn = nil fnName = "" continue @@ -275,7 +281,8 @@ Files: localSize += archDef.intSize } argSize, _ = strconv.Atoi(m[5]) - if fn == nil && !strings.Contains(fnName, "<>") { + noframe = strings.Contains(flag, "NOFRAME") + if fn == nil && !strings.Contains(fnName, "<>") && !noframe { badf("function %s missing Go declaration", fnName) } wroteSP = false @@ -305,13 +312,18 @@ Files: continue } - if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) { + if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) || strings.Contains(line, "NOP "+archDef.stack) || strings.Contains(line, "NOP\t"+archDef.stack) { wroteSP = true continue } + if arch == "wasm" && strings.Contains(line, "CallImport") { + // CallImport is a call out to magic that can write the result. + haveRetArg = true + } + for _, m := range asmSP.FindAllStringSubmatch(line, -1) { - if m[3] != archDef.stack || wroteSP { + if m[3] != archDef.stack || wroteSP || noframe { continue } off := 0 @@ -371,7 +383,7 @@ Files: } continue } - asmCheckVar(badf, fn, line, m[0], off, v) + asmCheckVar(badf, fn, line, m[0], off, v, archDef) } } flushRet() @@ -589,7 +601,7 @@ func asmParseDecl(pass *analysis.Pass, decl *ast.FuncDecl) map[string]*asmFunc { } // asmCheckVar checks a single variable reference. -func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar) { +func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar, archDef *asmArch) { m := asmOpcode.FindStringSubmatch(line) if m == nil { if !strings.HasPrefix(strings.TrimSpace(line), "//") { @@ -598,6 +610,8 @@ func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr stri return } + addr := strings.HasPrefix(expr, "$") + // Determine operand sizes from instruction. // Typically the suffix suffices, but there are exceptions. var src, dst, kind asmKind @@ -617,10 +631,13 @@ func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr stri // They just take the address of it. case "386.LEAL": dst = 4 + addr = true case "amd64.LEAQ": dst = 8 + addr = true case "amd64p32.LEAL": dst = 4 + addr = true default: switch fn.arch.name { case "386", "amd64": @@ -725,6 +742,11 @@ func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr stri vs = v.inner[0].size vt = v.inner[0].typ } + if addr { + vk = asmKind(archDef.ptrSize) + vs = archDef.ptrSize + vt = "address" + } if off != v.off { var inner bytes.Buffer diff --git a/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go b/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go index b5161836a57a..e88cf57d8f7a 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go @@ -45,6 +45,8 @@ var contextPackage = "context" // control-flow path from the call to a return statement and that path // does not "use" the cancel function. Any reference to the variable // counts as a use, even within a nested function literal. +// If the variable's scope is larger than the function +// containing the assignment, we assume that other uses exist. // // checkLostCancel analyzes a single named or literal function. func run(pass *analysis.Pass) (interface{}, error) { @@ -66,6 +68,15 @@ func run(pass *analysis.Pass) (interface{}, error) { } func runFunc(pass *analysis.Pass, node ast.Node) { + // Find scope of function node + var funcScope *types.Scope + switch v := node.(type) { + case *ast.FuncLit: + funcScope = pass.TypesInfo.Scopes[v.Type] + case *ast.FuncDecl: + funcScope = pass.TypesInfo.Scopes[v.Type] + } + // Maps each cancel variable to its defining ValueSpec/AssignStmt. cancelvars := make(map[*types.Var]ast.Node) @@ -114,7 +125,11 @@ func runFunc(pass *analysis.Pass, node ast.Node) { "the cancel function returned by context.%s should be called, not discarded, to avoid a context leak", n.(*ast.SelectorExpr).Sel.Name) } else if v, ok := pass.TypesInfo.Uses[id].(*types.Var); ok { - cancelvars[v] = stmt + // If the cancel variable is defined outside function scope, + // do not analyze it. + if funcScope.Contains(v.Pos()) { + cancelvars[v] = stmt + } } else if v, ok := pass.TypesInfo.Defs[id].(*types.Var); ok { cancelvars[v] = stmt } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/stdmethods/stdmethods.go b/vendor/golang.org/x/tools/go/analysis/passes/stdmethods/stdmethods.go index 72530a0eeb62..bc1db7e4c2e5 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/stdmethods/stdmethods.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/stdmethods/stdmethods.go @@ -116,6 +116,13 @@ func canonicalMethod(pass *analysis.Pass, id *ast.Ident) { args := sign.Params() results := sign.Results() + // Special case: WriteTo with more than one argument, + // not trying at all to implement io.WriterTo, + // comes up often enough to skip. + if id.Name == "WriteTo" && args.Len() > 1 { + return + } + // Do the =s (if any) all match? if !matchParams(pass, expect.args, args, "=") || !matchParams(pass, expect.results, results, "=") { return diff --git a/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go b/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go index 2b67c376bab8..bcdb04292005 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go @@ -56,6 +56,13 @@ var checkTagSpaces = map[string]bool{"json": true, "xml": true, "asn1": true} // checkCanonicalFieldTag checks a single struct field tag. func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, seen *map[[2]string]token.Pos) { + switch pass.Pkg.Path() { + case "encoding/json", "encoding/xml": + // These packages know how to use their own APIs. + // Sometimes they are testing what happens to incorrect programs. + return + } + for _, key := range checkTagDups { checkTagDuplicates(pass, tag, key, field, field, seen) } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go b/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go index 6cf4358ab9a3..d019ecef15a0 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go @@ -29,6 +29,13 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (interface{}, error) { + switch pass.Pkg.Path() { + case "encoding/gob", "encoding/json", "encoding/xml": + // These packages know how to use their own APIs. + // Sometimes they are testing what happens to incorrect programs. + return nil, nil + } + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) nodeFilter := []ast.Node{ diff --git a/vendor/golang.org/x/tools/go/analysis/unitchecker/main.go b/vendor/golang.org/x/tools/go/analysis/unitchecker/main.go new file mode 100644 index 000000000000..844e8f3dac2c --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/unitchecker/main.go @@ -0,0 +1,64 @@ +// +build ignore + +// This file provides an example command for static checkers +// conforming to the golang.org/x/tools/go/analysis API. +// It serves as a model for the behavior of the cmd/vet tool in $GOROOT. +// Being based on the unitchecker driver, it must be run by go vet: +// +// $ go build -o unitchecker main.go +// $ go vet -vettool=unitchecker my/project/... +// +// For a checker also capable of running standalone, use multichecker. +package main + +import ( + "golang.org/x/tools/go/analysis/unitchecker" + + "golang.org/x/tools/go/analysis/passes/asmdecl" + "golang.org/x/tools/go/analysis/passes/assign" + "golang.org/x/tools/go/analysis/passes/atomic" + "golang.org/x/tools/go/analysis/passes/bools" + "golang.org/x/tools/go/analysis/passes/buildtag" + "golang.org/x/tools/go/analysis/passes/cgocall" + "golang.org/x/tools/go/analysis/passes/composite" + "golang.org/x/tools/go/analysis/passes/copylock" + "golang.org/x/tools/go/analysis/passes/httpresponse" + "golang.org/x/tools/go/analysis/passes/loopclosure" + "golang.org/x/tools/go/analysis/passes/lostcancel" + "golang.org/x/tools/go/analysis/passes/nilfunc" + "golang.org/x/tools/go/analysis/passes/printf" + "golang.org/x/tools/go/analysis/passes/shift" + "golang.org/x/tools/go/analysis/passes/stdmethods" + "golang.org/x/tools/go/analysis/passes/structtag" + "golang.org/x/tools/go/analysis/passes/tests" + "golang.org/x/tools/go/analysis/passes/unmarshal" + "golang.org/x/tools/go/analysis/passes/unreachable" + "golang.org/x/tools/go/analysis/passes/unsafeptr" + "golang.org/x/tools/go/analysis/passes/unusedresult" +) + +func main() { + unitchecker.Main( + asmdecl.Analyzer, + assign.Analyzer, + atomic.Analyzer, + bools.Analyzer, + buildtag.Analyzer, + cgocall.Analyzer, + composite.Analyzer, + copylock.Analyzer, + httpresponse.Analyzer, + loopclosure.Analyzer, + lostcancel.Analyzer, + nilfunc.Analyzer, + printf.Analyzer, + shift.Analyzer, + stdmethods.Analyzer, + structtag.Analyzer, + tests.Analyzer, + unmarshal.Analyzer, + unreachable.Analyzer, + unsafeptr.Analyzer, + unusedresult.Analyzer, + ) +} diff --git a/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go b/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go new file mode 100644 index 000000000000..ba2e66fed2f6 --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go @@ -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. + +// The unitchecker package defines the main function for an analysis +// driver that analyzes a single compilation unit during a build. +// It is invoked by a build system such as "go vet": +// +// $ go vet -vettool=$(which vet) +// +// It supports the following command-line protocol: +// +// -V=full describe executable (to the build tool) +// -flags describe flags (to the build tool) +// foo.cfg description of compilation unit (from the build tool) +// +// This package does not depend on go/packages. +// If you need a standalone tool, use multichecker, +// which supports this mode but can also load packages +// from source using go/packages. +package unitchecker + +// TODO(adonovan): +// - with gccgo, go build does not build standard library, +// so we will not get to analyze it. Yet we must in order +// to create base facts for, say, the fmt package for the +// printf checker. + +import ( + "encoding/gob" + "encoding/json" + "flag" + "fmt" + "go/ast" + "go/build" + "go/importer" + "go/parser" + "go/token" + "go/types" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "time" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/internal/analysisflags" + "golang.org/x/tools/go/analysis/internal/facts" +) + +// A Config describes a compilation unit to be analyzed. +// It is provided to the tool in a JSON-encoded file +// whose name ends with ".cfg". +type Config struct { + ID string // e.g. "fmt [fmt.test]" + Compiler string + Dir string + ImportPath string + GoFiles []string + NonGoFiles []string + ImportMap map[string]string + PackageFile map[string]string + Standard map[string]bool + PackageVetx map[string]string + VetxOnly bool + VetxOutput string + SucceedOnTypecheckFailure bool +} + +// Main is the main function of a vet-like analysis tool that must be +// invoked by a build system to analyze a single package. +// +// The protocol required by 'go vet -vettool=...' is that the tool must support: +// +// -flags describe flags in JSON +// -V=full describe executable for build caching +// foo.cfg perform separate modular analyze on the single +// unit described by a JSON config file foo.cfg. +// +func Main(analyzers ...*analysis.Analyzer) { + progname := filepath.Base(os.Args[0]) + log.SetFlags(0) + log.SetPrefix(progname + ": ") + + if err := analysis.Validate(analyzers); err != nil { + log.Fatal(err) + } + + flag.Usage = func() { + fmt.Fprintf(os.Stderr, `%[1]s is a tool for static analysis of Go programs. + +Usage of %[1]s: + %.16[1]s unit.cfg # execute analysis specified by config file + %.16[1]s help # general help + %.16[1]s help name # help on specific analyzer and its flags +`, progname) + os.Exit(1) + } + + analyzers = analysisflags.Parse(analyzers, true) + + args := flag.Args() + if len(args) == 0 { + flag.Usage() + } + if args[0] == "help" { + analysisflags.Help(progname, analyzers, args[1:]) + os.Exit(0) + } + if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") { + log.Fatalf(`invoking "go tool vet" directly is unsupported; use "go vet"`) + } + Run(args[0], analyzers) +} + +// Run reads the *.cfg file, runs the analysis, +// and calls os.Exit with an appropriate error code. +// It assumes flags have already been set. +func Run(configFile string, analyzers []*analysis.Analyzer) { + cfg, err := readConfig(configFile) + if err != nil { + log.Fatal(err) + } + + fset := token.NewFileSet() + results, err := run(fset, cfg, analyzers) + if err != nil { + log.Fatal(err) + } + + // In VetxOnly mode, the analysis is run only for facts. + if !cfg.VetxOnly { + if analysisflags.JSON { + // JSON output + tree := make(analysisflags.JSONTree) + for _, res := range results { + tree.Add(fset, cfg.ID, res.a.Name, res.diagnostics, res.err) + } + tree.Print() + } else { + // plain text + exit := 0 + for _, res := range results { + if res.err != nil { + log.Println(res.err) + exit = 1 + } + } + for _, res := range results { + for _, diag := range res.diagnostics { + analysisflags.PrintPlain(fset, diag) + exit = 1 + } + } + os.Exit(exit) + } + } + + os.Exit(0) +} + +func readConfig(filename string) (*Config, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + cfg := new(Config) + if err := json.Unmarshal(data, cfg); err != nil { + return nil, fmt.Errorf("cannot decode JSON config file %s: %v", filename, err) + } + if len(cfg.GoFiles) == 0 { + // The go command disallows packages with no files. + // The only exception is unsafe, but the go command + // doesn't call vet on it. + return nil, fmt.Errorf("package has no files: %s", cfg.ImportPath) + } + return cfg, nil +} + +var importerForCompiler = func(_ *token.FileSet, compiler string, lookup importer.Lookup) types.Importer { + // broken legacy implementation (https://golang.org/issue/28995) + return importer.For(compiler, lookup) +} + +func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]result, error) { + // Load, parse, typecheck. + var files []*ast.File + for _, name := range cfg.GoFiles { + f, err := parser.ParseFile(fset, name, nil, parser.ParseComments) + if err != nil { + if cfg.SucceedOnTypecheckFailure { + // Silently succeed; let the compiler + // report parse errors. + err = nil + } + return nil, err + } + files = append(files, f) + } + compilerImporter := importerForCompiler(fset, cfg.Compiler, func(path string) (io.ReadCloser, error) { + // path is a resolved package path, not an import path. + file, ok := cfg.PackageFile[path] + if !ok { + if cfg.Compiler == "gccgo" && cfg.Standard[path] { + return nil, nil // fall back to default gccgo lookup + } + return nil, fmt.Errorf("no package file for %q", path) + } + return os.Open(file) + }) + importer := importerFunc(func(importPath string) (*types.Package, error) { + path, ok := cfg.ImportMap[importPath] // resolve vendoring, etc + if !ok { + return nil, fmt.Errorf("can't resolve import %q", path) + } + return compilerImporter.Import(path) + }) + tc := &types.Config{ + Importer: importer, + Sizes: types.SizesFor("gc", build.Default.GOARCH), // assume gccgo ≡ gc? + } + info := &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Implicits: make(map[ast.Node]types.Object), + Scopes: make(map[ast.Node]*types.Scope), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + } + pkg, err := tc.Check(cfg.ImportPath, fset, files, info) + if err != nil { + if cfg.SucceedOnTypecheckFailure { + // Silently succeed; let the compiler + // report type errors. + err = nil + } + return nil, err + } + + // Register fact types with gob. + // In VetxOnly mode, analyzers are only for their facts, + // so we can skip any analysis that neither produces facts + // nor depends on any analysis that produces facts. + // Also build a map to hold working state and result. + type action struct { + once sync.Once + result interface{} + err error + usesFacts bool // (transitively uses) + diagnostics []analysis.Diagnostic + } + actions := make(map[*analysis.Analyzer]*action) + var registerFacts func(a *analysis.Analyzer) bool + registerFacts = func(a *analysis.Analyzer) bool { + act, ok := actions[a] + if !ok { + act = new(action) + var usesFacts bool + for _, f := range a.FactTypes { + usesFacts = true + gob.Register(f) + } + for _, req := range a.Requires { + if registerFacts(req) { + usesFacts = true + } + } + act.usesFacts = usesFacts + actions[a] = act + } + return act.usesFacts + } + var filtered []*analysis.Analyzer + for _, a := range analyzers { + if registerFacts(a) || !cfg.VetxOnly { + filtered = append(filtered, a) + } + } + analyzers = filtered + + // Read facts from imported packages. + read := func(path string) ([]byte, error) { + if vetx, ok := cfg.PackageVetx[path]; ok { + return ioutil.ReadFile(vetx) + } + return nil, nil // no .vetx file, no facts + } + facts, err := facts.Decode(pkg, read) + if err != nil { + return nil, err + } + + // In parallel, execute the DAG of analyzers. + var exec func(a *analysis.Analyzer) *action + var execAll func(analyzers []*analysis.Analyzer) + exec = func(a *analysis.Analyzer) *action { + act := actions[a] + act.once.Do(func() { + execAll(a.Requires) // prefetch dependencies in parallel + + // The inputs to this analysis are the + // results of its prerequisites. + inputs := make(map[*analysis.Analyzer]interface{}) + var failed []string + for _, req := range a.Requires { + reqact := exec(req) + if reqact.err != nil { + failed = append(failed, req.String()) + continue + } + inputs[req] = reqact.result + } + + // Report an error if any dependency failed. + if failed != nil { + sort.Strings(failed) + act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", ")) + return + } + + pass := &analysis.Pass{ + Analyzer: a, + Fset: fset, + Files: files, + OtherFiles: cfg.NonGoFiles, + Pkg: pkg, + TypesInfo: info, + TypesSizes: tc.Sizes, + ResultOf: inputs, + Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) }, + ImportObjectFact: facts.ImportObjectFact, + ExportObjectFact: facts.ExportObjectFact, + ImportPackageFact: facts.ImportPackageFact, + ExportPackageFact: facts.ExportPackageFact, + } + + t0 := time.Now() + act.result, act.err = a.Run(pass) + if false { + log.Printf("analysis %s = %s", pass, time.Since(t0)) + } + }) + return act + } + execAll = func(analyzers []*analysis.Analyzer) { + var wg sync.WaitGroup + for _, a := range analyzers { + wg.Add(1) + go func(a *analysis.Analyzer) { + _ = exec(a) + wg.Done() + }(a) + } + wg.Wait() + } + + execAll(analyzers) + + // Return diagnostics and errors from root analyzers. + results := make([]result, len(analyzers)) + for i, a := range analyzers { + act := actions[a] + results[i].a = a + results[i].err = act.err + results[i].diagnostics = act.diagnostics + } + + data := facts.Encode() + if err := ioutil.WriteFile(cfg.VetxOutput, data, 0666); err != nil { + return nil, fmt.Errorf("failed to write analysis facts: %v", err) + } + + return results, nil +} + +type result struct { + a *analysis.Analyzer + diagnostics []analysis.Diagnostic + err error +} + +type importerFunc func(path string) (*types.Package, error) + +func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) } diff --git a/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker112.go b/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker112.go new file mode 100644 index 000000000000..683b7e91d25e --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker112.go @@ -0,0 +1,9 @@ +// +build go1.12 + +package unitchecker + +import "go/importer" + +func init() { + importerForCompiler = importer.ForCompiler +} diff --git a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go new file mode 100644 index 000000000000..0d85488efb61 --- /dev/null +++ b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go @@ -0,0 +1,523 @@ +// 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 objectpath defines a naming scheme for types.Objects +// (that is, named entities in Go programs) relative to their enclosing +// package. +// +// Type-checker objects are canonical, so they are usually identified by +// their address in memory (a pointer), but a pointer has meaning only +// within one address space. By contrast, objectpath names allow the +// identity of an object to be sent from one program to another, +// establishing a correspondence between types.Object variables that are +// distinct but logically equivalent. +// +// A single object may have multiple paths. In this example, +// type A struct{ X int } +// type B A +// the field X has two paths due to its membership of both A and B. +// The For(obj) function always returns one of these paths, arbitrarily +// but consistently. +package objectpath + +import ( + "fmt" + "strconv" + "strings" + + "go/types" +) + +// A Path is an opaque name that identifies a types.Object +// relative to its package. Conceptually, the name consists of a +// sequence of destructuring operations applied to the package scope +// to obtain the original object. +// The name does not include the package itself. +type Path string + +// Encoding +// +// An object path is a textual and (with training) human-readable encoding +// of a sequence of destructuring operators, starting from a types.Package. +// The sequences represent a path through the package/object/type graph. +// We classify these operators by their type: +// +// PO package->object Package.Scope.Lookup +// OT object->type Object.Type +// TT type->type Type.{Elem,Key,Params,Results,Underlying} [EKPRU] +// TO type->object Type.{At,Field,Method,Obj} [AFMO] +// +// All valid paths start with a package and end at an object +// and thus may be defined by the regular language: +// +// objectpath = PO (OT TT* TO)* +// +// The concrete encoding follows directly: +// - The only PO operator is Package.Scope.Lookup, which requires an identifier. +// - The only OT operator is Object.Type, +// which we encode as '.' because dot cannot appear in an identifier. +// - The TT operators are encoded as [EKPRU]. +// - The OT operators are encoded as [AFMO]; +// three of these (At,Field,Method) require an integer operand, +// which is encoded as a string of decimal digits. +// These indices are stable across different representations +// of the same package, even source and export data. +// +// In the example below, +// +// package p +// +// type T interface { +// f() (a string, b struct{ X int }) +// } +// +// field X has the path "T.UM0.RA1.F0", +// representing the following sequence of operations: +// +// p.Lookup("T") T +// .Type().Underlying().Method(0). f +// .Type().Results().At(1) b +// .Type().Field(0) X +// +// The encoding is not maximally compact---every R or P is +// followed by an A, for example---but this simplifies the +// encoder and decoder. +// +const ( + // object->type operators + opType = '.' // .Type() (Object) + + // type->type operators + opElem = 'E' // .Elem() (Pointer, Slice, Array, Chan, Map) + opKey = 'K' // .Key() (Map) + opParams = 'P' // .Params() (Signature) + opResults = 'R' // .Results() (Signature) + opUnderlying = 'U' // .Underlying() (Named) + + // type->object operators + opAt = 'A' // .At(i) (Tuple) + opField = 'F' // .Field(i) (Struct) + opMethod = 'M' // .Method(i) (Named or Interface; not Struct: "promoted" names are ignored) + opObj = 'O' // .Obj() (Named) +) + +// The For function returns the path to an object relative to its package, +// or an error if the object is not accessible from the package's Scope. +// +// The For function guarantees to return a path only for the following objects: +// - package-level types +// - exported package-level non-types +// - methods +// - parameter and result variables +// - struct fields +// These objects are sufficient to define the API of their package. +// The objects described by a package's export data are drawn from this set. +// +// For does not return a path for predeclared names, imported package +// names, local names, and unexported package-level names (except +// types). +// +// Example: given this definition, +// +// package p +// +// type T interface { +// f() (a string, b struct{ X int }) +// } +// +// For(X) would return a path that denotes the following sequence of operations: +// +// p.Scope().Lookup("T") (TypeName T) +// .Type().Underlying().Method(0). (method Func f) +// .Type().Results().At(1) (field Var b) +// .Type().Field(0) (field Var X) +// +// where p is the package (*types.Package) to which X belongs. +func For(obj types.Object) (Path, error) { + pkg := obj.Pkg() + + // This table lists the cases of interest. + // + // Object Action + // ------ ------ + // nil reject + // builtin reject + // pkgname reject + // label reject + // var + // package-level accept + // func param/result accept + // local reject + // struct field accept + // const + // package-level accept + // local reject + // func + // package-level accept + // init functions reject + // concrete method accept + // interface method accept + // type + // package-level accept + // local reject + // + // The only accessible package-level objects are members of pkg itself. + // + // The cases are handled in four steps: + // + // 1. reject nil and builtin + // 2. accept package-level objects + // 3. reject obviously invalid objects + // 4. search the API for the path to the param/result/field/method. + + // 1. reference to nil or builtin? + if pkg == nil { + return "", fmt.Errorf("predeclared %s has no path", obj) + } + scope := pkg.Scope() + + // 2. package-level object? + if scope.Lookup(obj.Name()) == obj { + // Only exported objects (and non-exported types) have a path. + // Non-exported types may be referenced by other objects. + if _, ok := obj.(*types.TypeName); !ok && !obj.Exported() { + return "", fmt.Errorf("no path for non-exported %v", obj) + } + return Path(obj.Name()), nil + } + + // 3. Not a package-level object. + // Reject obviously non-viable cases. + switch obj := obj.(type) { + case *types.Const, // Only package-level constants have a path. + *types.TypeName, // Only package-level types have a path. + *types.Label, // Labels are function-local. + *types.PkgName: // PkgNames are file-local. + return "", fmt.Errorf("no path for %v", obj) + + case *types.Var: + // Could be: + // - a field (obj.IsField()) + // - a func parameter or result + // - a local var. + // Sadly there is no way to distinguish + // a param/result from a local + // so we must proceed to the find. + + case *types.Func: + // A func, if not package-level, must be a method. + if recv := obj.Type().(*types.Signature).Recv(); recv == nil { + return "", fmt.Errorf("func is not a method: %v", obj) + } + // TODO(adonovan): opt: if the method is concrete, + // do a specialized version of the rest of this function so + // that it's O(1) not O(|scope|). Basically 'find' is needed + // only for struct fields and interface methods. + + default: + panic(obj) + } + + // 4. Search the API for the path to the var (field/param/result) or method. + + // First inspect package-level named types. + // In the presence of path aliases, these give + // the best paths because non-types may + // refer to types, but not the reverse. + empty := make([]byte, 0, 48) // initial space + for _, name := range scope.Names() { + o := scope.Lookup(name) + tname, ok := o.(*types.TypeName) + if !ok { + continue // handle non-types in second pass + } + + path := append(empty, name...) + path = append(path, opType) + + T := o.Type() + + if tname.IsAlias() { + // type alias + if r := find(obj, T, path); r != nil { + return Path(r), nil + } + } else { + // defined (named) type + if r := find(obj, T.Underlying(), append(path, opUnderlying)); r != nil { + return Path(r), nil + } + } + } + + // Then inspect everything else: + // non-types, and declared methods of defined types. + for _, name := range scope.Names() { + o := scope.Lookup(name) + path := append(empty, name...) + if _, ok := o.(*types.TypeName); !ok { + if o.Exported() { + // exported non-type (const, var, func) + if r := find(obj, o.Type(), append(path, opType)); r != nil { + return Path(r), nil + } + } + continue + } + + // Inspect declared methods of defined types. + if T, ok := o.Type().(*types.Named); ok { + path = append(path, opType) + for i := 0; i < T.NumMethods(); i++ { + m := T.Method(i) + path2 := appendOpArg(path, opMethod, i) + if m == obj { + return Path(path2), nil // found declared method + } + if r := find(obj, m.Type(), append(path2, opType)); r != nil { + return Path(r), nil + } + } + } + } + + return "", fmt.Errorf("can't find path for %v in %s", obj, pkg.Path()) +} + +func appendOpArg(path []byte, op byte, arg int) []byte { + path = append(path, op) + path = strconv.AppendInt(path, int64(arg), 10) + return path +} + +// find finds obj within type T, returning the path to it, or nil if not found. +func find(obj types.Object, T types.Type, path []byte) []byte { + switch T := T.(type) { + case *types.Basic, *types.Named: + // Named types belonging to pkg were handled already, + // so T must belong to another package. No path. + return nil + case *types.Pointer: + return find(obj, T.Elem(), append(path, opElem)) + case *types.Slice: + return find(obj, T.Elem(), append(path, opElem)) + case *types.Array: + return find(obj, T.Elem(), append(path, opElem)) + case *types.Chan: + return find(obj, T.Elem(), append(path, opElem)) + case *types.Map: + if r := find(obj, T.Key(), append(path, opKey)); r != nil { + return r + } + return find(obj, T.Elem(), append(path, opElem)) + case *types.Signature: + if r := find(obj, T.Params(), append(path, opParams)); r != nil { + return r + } + return find(obj, T.Results(), append(path, opResults)) + case *types.Struct: + for i := 0; i < T.NumFields(); i++ { + f := T.Field(i) + path2 := appendOpArg(path, opField, i) + if f == obj { + return path2 // found field var + } + if r := find(obj, f.Type(), append(path2, opType)); r != nil { + return r + } + } + return nil + case *types.Tuple: + for i := 0; i < T.Len(); i++ { + v := T.At(i) + path2 := appendOpArg(path, opAt, i) + if v == obj { + return path2 // found param/result var + } + if r := find(obj, v.Type(), append(path2, opType)); r != nil { + return r + } + } + return nil + case *types.Interface: + for i := 0; i < T.NumMethods(); i++ { + m := T.Method(i) + path2 := appendOpArg(path, opMethod, i) + if m == obj { + return path2 // found interface method + } + if r := find(obj, m.Type(), append(path2, opType)); r != nil { + return r + } + } + return nil + } + panic(T) +} + +// Object returns the object denoted by path p within the package pkg. +func Object(pkg *types.Package, p Path) (types.Object, error) { + if p == "" { + return nil, fmt.Errorf("empty path") + } + + pathstr := string(p) + var pkgobj, suffix string + if dot := strings.IndexByte(pathstr, opType); dot < 0 { + pkgobj = pathstr + } else { + pkgobj = pathstr[:dot] + suffix = pathstr[dot:] // suffix starts with "." + } + + obj := pkg.Scope().Lookup(pkgobj) + if obj == nil { + return nil, fmt.Errorf("package %s does not contain %q", pkg.Path(), pkgobj) + } + + // abtraction of *types.{Pointer,Slice,Array,Chan,Map} + type hasElem interface { + Elem() types.Type + } + // abstraction of *types.{Interface,Named} + type hasMethods interface { + Method(int) *types.Func + NumMethods() int + } + + // The loop state is the pair (t, obj), + // exactly one of which is non-nil, initially obj. + // All suffixes start with '.' (the only object->type operation), + // followed by optional type->type operations, + // then a type->object operation. + // The cycle then repeats. + var t types.Type + for suffix != "" { + code := suffix[0] + suffix = suffix[1:] + + // Codes [AFM] have an integer operand. + var index int + switch code { + case opAt, opField, opMethod: + rest := strings.TrimLeft(suffix, "0123456789") + numerals := suffix[:len(suffix)-len(rest)] + suffix = rest + i, err := strconv.Atoi(numerals) + if err != nil { + return nil, fmt.Errorf("invalid path: bad numeric operand %q for code %q", numerals, code) + } + index = int(i) + case opObj: + // no operand + default: + // The suffix must end with a type->object operation. + if suffix == "" { + return nil, fmt.Errorf("invalid path: ends with %q, want [AFMO]", code) + } + } + + if code == opType { + if t != nil { + return nil, fmt.Errorf("invalid path: unexpected %q in type context", opType) + } + t = obj.Type() + obj = nil + continue + } + + if t == nil { + return nil, fmt.Errorf("invalid path: code %q in object context", code) + } + + // Inv: t != nil, obj == nil + + switch code { + case opElem: + hasElem, ok := t.(hasElem) // Pointer, Slice, Array, Chan, Map + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want pointer, slice, array, chan or map)", code, t, t) + } + t = hasElem.Elem() + + case opKey: + mapType, ok := t.(*types.Map) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want map)", code, t, t) + } + t = mapType.Key() + + case opParams: + sig, ok := t.(*types.Signature) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t) + } + t = sig.Params() + + case opResults: + sig, ok := t.(*types.Signature) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t) + } + t = sig.Results() + + case opUnderlying: + named, ok := t.(*types.Named) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %s, want named)", code, t, t) + } + t = named.Underlying() + + case opAt: + tuple, ok := t.(*types.Tuple) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %s, want tuple)", code, t, t) + } + if n := tuple.Len(); index >= n { + return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n) + } + obj = tuple.At(index) + t = nil + + case opField: + structType, ok := t.(*types.Struct) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want struct)", code, t, t) + } + if n := structType.NumFields(); index >= n { + return nil, fmt.Errorf("field index %d out of range [0-%d)", index, n) + } + obj = structType.Field(index) + t = nil + + case opMethod: + hasMethods, ok := t.(hasMethods) // Interface or Named + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %s, want interface or named)", code, t, t) + } + if n := hasMethods.NumMethods(); index >= n { + return nil, fmt.Errorf("method index %d out of range [0-%d)", index, n) + } + obj = hasMethods.Method(index) + t = nil + + case opObj: + named, ok := t.(*types.Named) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %s, want named)", code, t, t) + } + obj = named.Obj() + t = nil + + default: + return nil, fmt.Errorf("invalid path: unknown code %q", code) + } + } + + if obj.Pkg() != pkg { + return nil, fmt.Errorf("path denotes %s, which belongs to a different package", obj) + } + + return obj, nil // success +} diff --git a/vendor/modules.txt b/vendor/modules.txt index a1da3efc57ac..66fa92fbbb29 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -180,6 +180,28 @@ github.com/aws/aws-sdk-go/private/protocol/query/queryutil github.com/aws/aws-sdk-go/internal/sdkuri # github.com/beevik/etree v1.1.0 github.com/beevik/etree +# github.com/bflad/tfproviderlint v0.2.0 +github.com/bflad/tfproviderlint/cmd/tfproviderlint +github.com/bflad/tfproviderlint/passes/AT001 +github.com/bflad/tfproviderlint/passes/AT002 +github.com/bflad/tfproviderlint/passes/AT003 +github.com/bflad/tfproviderlint/passes/AT004 +github.com/bflad/tfproviderlint/passes/R001 +github.com/bflad/tfproviderlint/passes/R002 +github.com/bflad/tfproviderlint/passes/R003 +github.com/bflad/tfproviderlint/passes/R004 +github.com/bflad/tfproviderlint/passes/S001 +github.com/bflad/tfproviderlint/passes/S002 +github.com/bflad/tfproviderlint/passes/S003 +github.com/bflad/tfproviderlint/passes/S004 +github.com/bflad/tfproviderlint/passes/S005 +github.com/bflad/tfproviderlint/passes/S006 +github.com/bflad/tfproviderlint/passes/acctestcase +github.com/bflad/tfproviderlint/passes/commentignore +github.com/bflad/tfproviderlint/passes/acctestfunc +github.com/bflad/tfproviderlint/passes/resourcedataset +github.com/bflad/tfproviderlint/passes/schemaresource +github.com/bflad/tfproviderlint/passes/schemaschema # github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d github.com/bgentry/go-netrc/netrc # github.com/bgentry/speakeasy v0.1.0 @@ -613,20 +635,28 @@ golang.org/x/text/transform golang.org/x/text/width golang.org/x/text/secure/bidirule golang.org/x/text/unicode/bidi -# golang.org/x/tools v0.0.0-20190425150028-36563e24a262 -golang.org/x/tools/go/loader +# golang.org/x/tools v0.0.0-20190510151030-63859f3815cb +golang.org/x/tools/go/analysis/multichecker +golang.org/x/tools/go/analysis +golang.org/x/tools/go/analysis/passes/inspect +golang.org/x/tools/go/ast/inspector +golang.org/x/tools/go/analysis/internal/analysisflags +golang.org/x/tools/go/analysis/internal/checker +golang.org/x/tools/go/analysis/unitchecker golang.org/x/tools/go/packages +golang.org/x/tools/go/analysis/internal/facts +golang.org/x/tools/go/loader golang.org/x/tools/go/ssa golang.org/x/tools/go/ssa/ssautil -golang.org/x/tools/go/ast/astutil -golang.org/x/tools/go/buildutil -golang.org/x/tools/go/internal/cgo golang.org/x/tools/go/gcexportdata golang.org/x/tools/go/internal/packagesdriver golang.org/x/tools/internal/gopathwalk golang.org/x/tools/internal/semver +golang.org/x/tools/go/types/objectpath +golang.org/x/tools/go/ast/astutil +golang.org/x/tools/go/buildutil +golang.org/x/tools/go/internal/cgo golang.org/x/tools/go/types/typeutil -golang.org/x/tools/go/analysis golang.org/x/tools/go/analysis/passes/asmdecl golang.org/x/tools/go/analysis/passes/assign golang.org/x/tools/go/analysis/passes/atomic @@ -655,8 +685,6 @@ golang.org/x/tools/go/internal/gcimporter golang.org/x/tools/internal/fastwalk golang.org/x/tools/go/analysis/passes/buildssa golang.org/x/tools/go/analysis/passes/internal/analysisutil -golang.org/x/tools/go/analysis/passes/inspect -golang.org/x/tools/go/ast/inspector golang.org/x/tools/go/analysis/passes/ctrlflow golang.org/x/tools/go/cfg golang.org/x/tools/internal/module