aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/alexkohler/prealloc
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2021-02-22 20:37:25 +0100
committerDmitry Vyukov <dvyukov@google.com>2021-02-22 21:02:12 +0100
commitfcc6d71be2c3ce7d9305c04fc2e87af554571bac (patch)
treeb01dbb3d1e2988e28ea158d2d543d603ec0b9569 /vendor/github.com/alexkohler/prealloc
parent8f23c528ad5a943b9ffec5dcaf332fd0f614006e (diff)
go.mod: update golangci-lint to v1.37
Diffstat (limited to 'vendor/github.com/alexkohler/prealloc')
-rw-r--r--vendor/github.com/alexkohler/prealloc/LICENSE21
-rw-r--r--vendor/github.com/alexkohler/prealloc/pkg/prealloc.go267
2 files changed, 288 insertions, 0 deletions
diff --git a/vendor/github.com/alexkohler/prealloc/LICENSE b/vendor/github.com/alexkohler/prealloc/LICENSE
new file mode 100644
index 000000000..9310fbcff
--- /dev/null
+++ b/vendor/github.com/alexkohler/prealloc/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Alex Kohler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/alexkohler/prealloc/pkg/prealloc.go b/vendor/github.com/alexkohler/prealloc/pkg/prealloc.go
new file mode 100644
index 000000000..72d8b95f7
--- /dev/null
+++ b/vendor/github.com/alexkohler/prealloc/pkg/prealloc.go
@@ -0,0 +1,267 @@
+package pkg
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+)
+
+type sliceDeclaration struct {
+ name string
+ // sType string
+ genD *ast.GenDecl
+}
+
+type returnsVisitor struct {
+ // flags
+ simple bool
+ includeRangeLoops bool
+ includeForLoops bool
+ // visitor fields
+ sliceDeclarations []*sliceDeclaration
+ preallocHints []Hint
+ returnsInsideOfLoop bool
+ arrayTypes []string
+}
+
+func Check(files []*ast.File, simple, includeRangeLoops, includeForLoops bool) []Hint {
+ hints := []Hint{}
+ for _, f := range files {
+ retVis := &returnsVisitor{
+ simple: simple,
+ includeRangeLoops: includeRangeLoops,
+ includeForLoops: includeForLoops,
+ }
+ ast.Walk(retVis, f)
+ // if simple is true, then we actually have to check if we had returns
+ // inside of our loop. Otherwise, we can just report all messages.
+ if !retVis.simple || !retVis.returnsInsideOfLoop {
+ hints = append(hints, retVis.preallocHints...)
+ }
+ }
+
+ return hints
+}
+
+func contains(slice []string, item string) bool {
+ for _, s := range slice {
+ if s == item {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (v *returnsVisitor) Visit(node ast.Node) ast.Visitor {
+
+ v.sliceDeclarations = nil
+ v.returnsInsideOfLoop = false
+
+ switch n := node.(type) {
+ case *ast.TypeSpec:
+ if _, ok := n.Type.(*ast.ArrayType); ok {
+ if n.Name != nil {
+ v.arrayTypes = append(v.arrayTypes, n.Name.Name)
+ }
+ }
+ case *ast.FuncDecl:
+ if n.Body != nil {
+ for _, stmt := range n.Body.List {
+ switch s := stmt.(type) {
+ // Find non pre-allocated slices
+ case *ast.DeclStmt:
+ genD, ok := s.Decl.(*ast.GenDecl)
+ if !ok {
+ continue
+ }
+ if genD.Tok == token.TYPE {
+ for _, spec := range genD.Specs {
+ tSpec, ok := spec.(*ast.TypeSpec)
+ if !ok {
+ continue
+ }
+
+ if _, ok := tSpec.Type.(*ast.ArrayType); ok {
+ if tSpec.Name != nil {
+ v.arrayTypes = append(v.arrayTypes, tSpec.Name.Name)
+ }
+ }
+ }
+ } else if genD.Tok == token.VAR {
+ for _, spec := range genD.Specs {
+ vSpec, ok := spec.(*ast.ValueSpec)
+ if !ok {
+ continue
+ }
+ var isArrType bool
+ switch val := vSpec.Type.(type) {
+ case *ast.ArrayType:
+ isArrType = true
+ case *ast.Ident:
+ isArrType = contains(v.arrayTypes, val.Name)
+ }
+ if isArrType {
+ if vSpec.Names != nil {
+ /*atID, ok := arrayType.Elt.(*ast.Ident)
+ if !ok {
+ continue
+ }*/
+
+ // We should handle multiple slices declared on same line e.g. var mySlice1, mySlice2 []uint32
+ for _, vName := range vSpec.Names {
+ v.sliceDeclarations = append(v.sliceDeclarations, &sliceDeclaration{name: vName.Name /*sType: atID.Name,*/, genD: genD})
+ }
+ }
+ }
+ }
+ }
+
+ case *ast.RangeStmt:
+ if v.includeRangeLoops {
+ if len(v.sliceDeclarations) == 0 {
+ continue
+ }
+ // Check the value being ranged over and ensure it's not a channel (we cannot offer any recommendations on channel ranges).
+ rangeIdent, ok := s.X.(*ast.Ident)
+ if ok && rangeIdent.Obj != nil {
+ valueSpec, ok := rangeIdent.Obj.Decl.(*ast.ValueSpec)
+ if ok {
+ if _, rangeTargetIsChannel := valueSpec.Type.(*ast.ChanType); rangeTargetIsChannel {
+ continue
+ }
+ }
+ }
+ if s.Body != nil {
+ v.handleLoops(s.Body)
+ }
+ }
+
+ case *ast.ForStmt:
+ if v.includeForLoops {
+ if len(v.sliceDeclarations) == 0 {
+ continue
+ }
+ if s.Body != nil {
+ v.handleLoops(s.Body)
+ }
+ }
+
+ default:
+ }
+ }
+ }
+ }
+ return v
+}
+
+// handleLoops is a helper function to share the logic required for both *ast.RangeLoops and *ast.ForLoops
+func (v *returnsVisitor) handleLoops(blockStmt *ast.BlockStmt) {
+
+ for _, stmt := range blockStmt.List {
+ switch bodyStmt := stmt.(type) {
+ case *ast.AssignStmt:
+ asgnStmt := bodyStmt
+ for index, expr := range asgnStmt.Rhs {
+ if index >= len(asgnStmt.Lhs) {
+ continue
+ }
+
+ lhsIdent, ok := asgnStmt.Lhs[index].(*ast.Ident)
+ if !ok {
+ continue
+ }
+
+ callExpr, ok := expr.(*ast.CallExpr)
+ if !ok {
+ continue
+ }
+
+ rhsFuncIdent, ok := callExpr.Fun.(*ast.Ident)
+ if !ok {
+ continue
+ }
+
+ if rhsFuncIdent.Name != "append" {
+ continue
+ }
+
+ // e.g., `x = append(x)`
+ // Pointless, but pre-allocation will not help.
+ if len(callExpr.Args) < 2 {
+ continue
+ }
+
+ rhsIdent, ok := callExpr.Args[0].(*ast.Ident)
+ if !ok {
+ continue
+ }
+
+ // e.g., `x = append(y, a)`
+ // This is weird (and maybe a logic error),
+ // but we cannot recommend pre-allocation.
+ if lhsIdent.Name != rhsIdent.Name {
+ continue
+ }
+
+ // e.g., `x = append(x, y...)`
+ // we should ignore this. Pre-allocating in this case
+ // is confusing, and is not possible in general.
+ if callExpr.Ellipsis.IsValid() {
+ continue
+ }
+
+ for _, sliceDecl := range v.sliceDeclarations {
+ if sliceDecl.name == lhsIdent.Name {
+ // This is a potential mark, we just need to make sure there are no returns/continues in the
+ // range loop.
+ // now we just need to grab whatever we're ranging over
+ /*sxIdent, ok := s.X.(*ast.Ident)
+ if !ok {
+ continue
+ }*/
+
+ v.preallocHints = append(v.preallocHints, Hint{
+ Pos: sliceDecl.genD.Pos(),
+ DeclaredSliceName: sliceDecl.name,
+ })
+ }
+ }
+ }
+ case *ast.IfStmt:
+ ifStmt := bodyStmt
+ if ifStmt.Body != nil {
+ for _, ifBodyStmt := range ifStmt.Body.List {
+ // TODO should probably handle embedded ifs here
+ switch /*ift :=*/ ifBodyStmt.(type) {
+ case *ast.BranchStmt, *ast.ReturnStmt:
+ v.returnsInsideOfLoop = true
+ default:
+ }
+ }
+ }
+
+ default:
+
+ }
+ }
+
+}
+
+// Hint stores the information about an occurrence of a slice that could be
+// preallocated.
+type Hint struct {
+ Pos token.Pos
+ DeclaredSliceName string
+}
+
+func (h Hint) String() string {
+ return fmt.Sprintf("%v: Consider preallocating %v", h.Pos, h.DeclaredSliceName)
+}
+
+func (h Hint) StringFromFS(f *token.FileSet) string {
+ file := f.File(h.Pos)
+ lineNumber := file.Position(h.Pos).Line
+
+ return fmt.Sprintf("%v:%v Consider preallocating %v", file.Name(), lineNumber, h.DeclaredSliceName)
+}