aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/ashanbrown/makezero
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/ashanbrown/makezero')
-rw-r--r--vendor/github.com/ashanbrown/makezero/LICENSE13
-rw-r--r--vendor/github.com/ashanbrown/makezero/makezero/makezero.go197
2 files changed, 210 insertions, 0 deletions
diff --git a/vendor/github.com/ashanbrown/makezero/LICENSE b/vendor/github.com/ashanbrown/makezero/LICENSE
new file mode 100644
index 000000000..dc1d47ad5
--- /dev/null
+++ b/vendor/github.com/ashanbrown/makezero/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2019 Andrew Shannon Brown
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/vendor/github.com/ashanbrown/makezero/makezero/makezero.go b/vendor/github.com/ashanbrown/makezero/makezero/makezero.go
new file mode 100644
index 000000000..6bf230b12
--- /dev/null
+++ b/vendor/github.com/ashanbrown/makezero/makezero/makezero.go
@@ -0,0 +1,197 @@
+// makezero provides a linter for appends to slices initialized with non-zero length.
+package makezero
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/printer"
+ "go/token"
+ "go/types"
+ "log"
+ "regexp"
+)
+
+type Issue interface {
+ Details() string
+ Position() token.Position
+ String() string
+}
+
+type AppendIssue struct {
+ name string
+ position token.Position
+}
+
+func (a AppendIssue) Details() string {
+ return fmt.Sprintf("append to slice `%s` with non-zero initialized length", a.name)
+}
+
+func (a AppendIssue) Position() token.Position {
+ return a.position
+}
+
+func (a AppendIssue) String() string { return toString(a) }
+
+type MustHaveNonZeroInitLenIssue struct {
+ name string
+ position token.Position
+}
+
+func (i MustHaveNonZeroInitLenIssue) Details() string {
+ return fmt.Sprintf("slice `%s` does not have non-zero initial length", i.name)
+}
+
+func (i MustHaveNonZeroInitLenIssue) Position() token.Position {
+ return i.position
+}
+
+func (i MustHaveNonZeroInitLenIssue) String() string { return toString(i) }
+
+func toString(i Issue) string {
+ return fmt.Sprintf("%s at %s", i.Details(), i.Position())
+}
+
+type visitor struct {
+ initLenMustBeZero bool
+
+ comments []*ast.CommentGroup // comments to apply during this visit
+ info *types.Info
+
+ nonZeroLengthSliceDecls map[interface{}]struct{}
+ fset *token.FileSet
+ issues []Issue
+}
+
+type Linter struct {
+ initLenMustBeZero bool
+}
+
+func NewLinter(initialLengthMustBeZero bool) *Linter {
+ return &Linter{
+ initLenMustBeZero: initialLengthMustBeZero,
+ }
+}
+
+func (l Linter) Run(fset *token.FileSet, info *types.Info, nodes ...ast.Node) ([]Issue, error) {
+ var issues []Issue // nolint:prealloc // don't know how many there will be
+ for _, node := range nodes {
+ var comments []*ast.CommentGroup
+ if file, ok := node.(*ast.File); ok {
+ comments = file.Comments
+ }
+ visitor := visitor{
+ nonZeroLengthSliceDecls: make(map[interface{}]struct{}),
+ initLenMustBeZero: l.initLenMustBeZero,
+ info: info,
+ fset: fset,
+ comments: comments,
+ }
+ ast.Walk(&visitor, node)
+ issues = append(issues, visitor.issues...)
+ }
+ return issues, nil
+}
+
+func (v *visitor) Visit(node ast.Node) ast.Visitor {
+ switch node := node.(type) {
+ case *ast.CallExpr:
+ fun, ok := node.Fun.(*ast.Ident)
+ if !ok || fun.Name != "append" {
+ break
+ }
+ if sliceIdent, ok := node.Args[0].(*ast.Ident); ok &&
+ v.hasNonZeroInitialLength(sliceIdent) &&
+ !v.hasNoLintOnSameLine(fun) {
+ v.issues = append(v.issues, AppendIssue{name: sliceIdent.Name, position: v.fset.Position(fun.Pos())})
+ }
+ case *ast.AssignStmt:
+ for i, right := range node.Rhs {
+ if right, ok := right.(*ast.CallExpr); ok {
+ fun, ok := right.Fun.(*ast.Ident)
+ if !ok || fun.Name != "make" {
+ continue
+ }
+ left := node.Lhs[i]
+ if len(right.Args) == 2 {
+ // ignore if not a slice or it has explicit zero length
+ if !v.isSlice(right.Args[0]) {
+ break
+ } else if lit, ok := right.Args[1].(*ast.BasicLit); ok && lit.Kind == token.INT && lit.Value == "0" {
+ break
+ }
+ if v.initLenMustBeZero && !v.hasNoLintOnSameLine(fun) {
+ v.issues = append(v.issues, MustHaveNonZeroInitLenIssue{
+ name: v.textFor(left),
+ position: v.fset.Position(node.Pos()),
+ })
+ }
+ v.recordNonZeroLengthSlices(left)
+ }
+ }
+ }
+ }
+ return v
+}
+
+func (v *visitor) textFor(node ast.Node) string {
+ typeBuf := new(bytes.Buffer)
+ if err := printer.Fprint(typeBuf, v.fset, node); err != nil {
+ log.Fatalf("ERROR: unable to print type: %s", err)
+ }
+ return typeBuf.String()
+}
+
+func (v *visitor) hasNonZeroInitialLength(ident *ast.Ident) bool {
+ if ident.Obj == nil {
+ log.Printf("WARNING: could not determine with %q at %s is a slice (missing object type)",
+ ident.Name, v.fset.Position(ident.Pos()).String())
+ return false
+ }
+ _, exists := v.nonZeroLengthSliceDecls[ident.Obj.Decl]
+ return exists
+}
+
+func (v *visitor) recordNonZeroLengthSlices(node ast.Node) {
+ ident, ok := node.(*ast.Ident)
+ if !ok {
+ return
+ }
+ v.nonZeroLengthSliceDecls[ident.Obj.Decl] = struct{}{}
+}
+
+func (v *visitor) isSlice(node ast.Node) bool {
+ // determine type if this is a user-defined type
+ if ident, ok := node.(*ast.Ident); ok {
+ obj := ident.Obj
+ if obj == nil {
+ if v.info != nil {
+ _, ok := v.info.ObjectOf(ident).Type().(*types.Slice)
+ return ok
+ }
+ return false
+ }
+ spec, ok := obj.Decl.(*ast.TypeSpec)
+ if !ok {
+ return false
+ }
+ node = spec.Type
+ }
+
+ if node, ok := node.(*ast.ArrayType); ok {
+ return node.Len == nil // only slices have zero length
+ }
+ return false
+}
+
+func (v *visitor) hasNoLintOnSameLine(node ast.Node) bool {
+ var nolint = regexp.MustCompile(`^\s*nozero\b`)
+ nodePos := v.fset.Position(node.Pos())
+ for _, c := range v.comments {
+ commentPos := v.fset.Position(c.Pos())
+ if commentPos.Line == nodePos.Line && nolint.MatchString(c.Text()) {
+ return true
+ }
+ }
+ return false
+}