├── .golangci.yml ├── Makefile ├── examples ├── example.go └── expected_results.txt ├── pkg └── analyzer │ ├── testdata │ ├── always │ │ └── append.go │ └── append │ │ └── append.go │ ├── analyzer_test.go │ └── analyzer.go ├── go.mod ├── .pre-commit-config.yaml ├── LICENSE ├── go.sum ├── .github └── workflows │ └── build.yml ├── main.go ├── README.md └── makezero ├── makezero_test.go └── makezero.go /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: {} 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL=bash 2 | 3 | test: 4 | diff <(sed 's|CURDIR|$(CURDIR)|' examples/expected_results.txt) <(go run . -always ./examples 2>&1) 5 | -------------------------------------------------------------------------------- /examples/example.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import "fmt" 4 | 5 | func Foo() { 6 | x := make([]int, 5) 7 | x = append(x, 1) 8 | fmt.Println(x) 9 | } 10 | -------------------------------------------------------------------------------- /examples/expected_results.txt: -------------------------------------------------------------------------------- 1 | slice `x` does not have non-zero initial length at CURDIR/examples/example.go:6:2 2 | append to slice `x` with non-zero initialized length at CURDIR/examples/example.go:7:6 3 | -------------------------------------------------------------------------------- /pkg/analyzer/testdata/always/append.go: -------------------------------------------------------------------------------- 1 | package always 2 | 3 | import "fmt" 4 | 5 | func Foo() { 6 | x := make([]int, 5) // want "slice `x` does not have non-zero initial length" 7 | fmt.Println(x) 8 | } 9 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ashanbrown/makezero/v2 2 | 3 | go 1.24.0 4 | 5 | require golang.org/x/tools v0.37.0 6 | 7 | require ( 8 | golang.org/x/mod v0.28.0 // indirect 9 | golang.org/x/sync v0.17.0 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /pkg/analyzer/testdata/append/append.go: -------------------------------------------------------------------------------- 1 | package append 2 | 3 | import "fmt" 4 | 5 | func Foo() { 6 | x := make([]int, 5) 7 | x = append(x, 1) // want "append to slice `x` with non-zero initialized length" 8 | fmt.Println(x) 9 | } 10 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/golangci/golangci-lint 3 | rev: v1.63.4 4 | hooks: 5 | - id: golangci-lint 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: v5.0.0 8 | hooks: 9 | - id: trailing-whitespace -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Andrew Shannon Brown 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 2 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 3 | golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= 4 | golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= 5 | golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= 6 | golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 7 | golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= 8 | golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= 9 | -------------------------------------------------------------------------------- /pkg/analyzer/analyzer_test.go: -------------------------------------------------------------------------------- 1 | package analyzer_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ashanbrown/makezero/v2/pkg/analyzer" 7 | "golang.org/x/tools/go/analysis/analysistest" 8 | ) 9 | 10 | func TestAppend(t *testing.T) { 11 | testdata := analysistest.TestData() 12 | a := analyzer.NewAnalyzer() 13 | analysistest.Run(t, testdata, a, "./append") 14 | } 15 | 16 | func TestAlways(t *testing.T) { 17 | testdata := analysistest.TestData() 18 | a := analyzer.NewAnalyzer() 19 | err := a.Flags.Set("always", "true") 20 | if err != nil { 21 | t.Fatalf("expected no error but got %q", err) 22 | } 23 | analysistest.Run(t, testdata, a, "./always") 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | go-version: [ '1.24.x', '1.25.x' ] 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Set up Python 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: '3.x' 18 | - uses: pre-commit/action@v3.0.1 19 | - name: Setup Go ${{ matrix.go-version }} 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version: ${{ matrix.go-version }} 23 | - name: Run Go tests 24 | run: go test ./... 25 | - name: Run make tests 26 | run: make test 27 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "go/ast" 6 | "log" 7 | "os" 8 | 9 | "golang.org/x/tools/go/packages" 10 | 11 | "github.com/ashanbrown/makezero/v2/makezero" 12 | ) 13 | 14 | func main() { 15 | log.SetFlags(0) // remove log timestamp 16 | 17 | setExitStatus := flag.Bool("set_exit_status", false, 18 | "Set exit status to 1 if any issues are found") 19 | always := flag.Bool("always", false, 20 | "require every make to have zero length regardless of whether append is used") 21 | flag.Parse() 22 | 23 | cfg := packages.Config{ 24 | Mode: packages.NeedSyntax | packages.NeedName | packages.NeedFiles | packages.NeedTypes | packages.NeedTypesInfo, 25 | } 26 | pkgs, err := packages.Load(&cfg, os.Args[1:]...) 27 | if err != nil { 28 | log.Fatalf("Could not load packages: %s", err) 29 | } 30 | linter := makezero.NewLinter(*always) 31 | 32 | var issues []makezero.Issue 33 | for _, p := range pkgs { 34 | nodes := make([]ast.Node, 0, len(p.Syntax)) 35 | for _, n := range p.Syntax { 36 | nodes = append(nodes, n) 37 | } 38 | newIssues, err := linter.Run(p.Fset, p.TypesInfo, nodes...) 39 | if err != nil { 40 | log.Fatalf("failed: %s", err) 41 | } 42 | issues = append(issues, newIssues...) 43 | } 44 | 45 | for _, issue := range issues { 46 | log.Println(issue) 47 | } 48 | 49 | if *setExitStatus && len(issues) > 0 { 50 | os.Exit(1) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pkg/analyzer/analyzer.go: -------------------------------------------------------------------------------- 1 | package analyzer 2 | 3 | import ( 4 | "flag" 5 | "go/ast" 6 | 7 | "github.com/ashanbrown/makezero/v2/makezero" 8 | "golang.org/x/tools/go/analysis" 9 | ) 10 | 11 | type analyzer struct { 12 | always bool 13 | } 14 | 15 | // NewAnalyzer returns a go/analysis-compatible analyzer 16 | // Set "-always" to report any non-empty slice initialization. 17 | func NewAnalyzer() *analysis.Analyzer { 18 | var flags flag.FlagSet 19 | a := analyzer{} 20 | flags.BoolVar(&a.always, "always", false, "report any non-empty slice initializations, regardless of intention") 21 | return &analysis.Analyzer{ 22 | Name: "makezero", 23 | Doc: "detect unintended non-empty slice initializations", 24 | Run: a.runAnalysis, 25 | Flags: flags, 26 | } 27 | } 28 | 29 | func (a *analyzer) runAnalysis(pass *analysis.Pass) (interface{}, error) { 30 | linter := makezero.NewLinter(a.always) 31 | nodes := make([]ast.Node, 0, len(pass.Files)) 32 | for _, f := range pass.Files { 33 | nodes = append(nodes, f) 34 | } 35 | issues, err := linter.Run(pass.Fset, pass.TypesInfo, nodes...) 36 | if err != nil { 37 | return nil, err 38 | } 39 | reportIssues(pass, issues) 40 | return nil, nil 41 | } 42 | 43 | func reportIssues(pass *analysis.Pass, issues []makezero.Issue) { 44 | for _, i := range issues { 45 | diag := analysis.Diagnostic{ 46 | Pos: i.Pos(), 47 | Message: i.Details(), 48 | Category: "restriction", 49 | } 50 | pass.Report(diag) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # makezero 2 | 3 | [![GitHub Actions](https://github.com/ashanbrown/makezero/actions/workflows/build.yml/badge.svg)](https://github.com/ashanbrown/makezero/actions/workflows/build.yml?query=branch%3Amaster) 4 | 5 | makezero is a Go static analysis tool to find slice declarations that are not initialized with zero length and are later 6 | used with append. 7 | 8 | ## Installation 9 | 10 | go get -u github.com/ashanbrown/makezero 11 | 12 | ## Usage 13 | 14 | Similar to other Go static analysis tools (such as golint, go vet), makezero can be invoked with one or more filenames, directories, or packages named by its import path. makezero also supports the `...` wildcard. 15 | 16 | makezero [-always] packages... 17 | 18 | ### Flags 19 | - **-set_exit_status** (default false) - Set exit status to 1 if any issues are found. 20 | - **-always** (default false) - Always require slices to be initialized with zero length, regardless of whether they are used with append. 21 | 22 | ## Purpose 23 | 24 | To prevent bugs caused by initializing a slice with non-constant length and later appending to it. The recommended 25 | [prealloc](https://github.com/alexkohler/prealloc) linter wisely encourages the developer to pre-allocate, but when we preallocate a slice with empty values in it and later append to it, we can easily introduce extra empty element in that slice. 26 | 27 | Consider the case below: 28 | 29 | ```Go 30 | func copyNumbers(nums []int) []int { 31 | values := make([]int, len(nums)) // satisfy prealloc 32 | for _, n := range nums { 33 | values = append(values, n) 34 | } 35 | return values 36 | } 37 | ``` 38 | 39 | In this case, you probably mean to preallocate with length 0 `values := make([]int, 0, len(nums))`. 40 | 41 | The `-always` directive enforces that slice created with `make` always have initial length of zero. This may sound 42 | draconian but it encourages the use of `append` when building up arrays rather than C-style code featuring the index 43 | variable `i` such as in: 44 | 45 | ```Go 46 | func copyNumbers(nums []int) []int { 47 | values := make([]int, len(nums)) 48 | for i, n := range nums { 49 | values[i] = n 50 | } 51 | return values 52 | } 53 | 54 | ``` 55 | 56 | ## Ignoring issues 57 | 58 | You can ignore a particular issue by including the directive `// nozero` on that line 59 | 60 | ## TODO 61 | 62 | Consider whether this should be part of prealloc itself. 63 | 64 | ## Contributing 65 | 66 | Pull requests welcome! 67 | -------------------------------------------------------------------------------- /makezero/makezero_test.go: -------------------------------------------------------------------------------- 1 | package makezero 2 | 3 | import ( 4 | "go/parser" 5 | "go/token" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestMakeZero(t *testing.T) { 11 | t.Run("when append is used", func(t *testing.T) { 12 | t.Run("finds appends to non-zero length initialized slices", func(t *testing.T) { 13 | linter := NewLinter(false) 14 | expectIssues(t, linter, ` 15 | package bar 16 | 17 | func foo() []int { 18 | x := make([]int, 5) 19 | append(x, 1) 20 | }`, "append to slice `x` with non-zero initialized length at testing.go:6:3") 21 | }) 22 | 23 | t.Run("works with custom types that are slices", func(t *testing.T) { 24 | linter := NewLinter(false) 25 | expectIssues(t, linter, ` 26 | package bar 27 | 28 | type intSlice []int 29 | func foo() { 30 | x := make(intSlice, 5) 31 | append(x, 1) 32 | }`, "append to slice `x` with non-zero initialized length at testing.go:7:3") 33 | }) 34 | 35 | t.Run("can report any initializes without length", func(t *testing.T) { 36 | linter := NewLinter(true) 37 | expectIssues(t, linter, ` 38 | package bar 39 | 40 | func foo() { 41 | x := make([]int, 5) 42 | }`, "slice `x` does not have non-zero initial length at testing.go:5:3") 43 | }) 44 | 45 | t.Run("doesn't confuse maps with slices", func(t *testing.T) { 46 | linter := NewLinter(true) 47 | expectIssues(t, linter, ` 48 | package bar 49 | 50 | func foo() { 51 | x := make(map[string]int, 5) 52 | }`) 53 | }) 54 | 55 | t.Run("can report any initializes without length", func(t *testing.T) { 56 | linter := NewLinter(true) 57 | expectIssues(t, linter, ` 58 | package bar 59 | 60 | func foo() { 61 | x := make([]int, 5) // nozero 62 | append(x, 1) //nozero 63 | append(x, 1) //nozeroxxx 64 | }`, "append to slice `x` with non-zero initialized length at testing.go:7:3") 65 | }) 66 | }) 67 | 68 | t.Run("ignores more complex constructs than basic variables", func(t *testing.T) { 69 | linter := NewLinter(false) 70 | expectIssues(t, linter, ` 71 | package bar 72 | 73 | func foo() { 74 | var x [][]int 75 | x[0] = make([]int, 5) 76 | }`) 77 | }) 78 | } 79 | 80 | func TestMultiDeclare(t *testing.T) { 81 | t.Run("handles multi declares in same line", func(t *testing.T) { 82 | t.Run("with just first obj is non-zero", func(t *testing.T) { 83 | linter := NewLinter(false) 84 | expectIssues(t, linter, ` 85 | package bar 86 | 87 | func foo() { 88 | a, b := make([]int, 10), make([]int, 0) 89 | a = append(a, 10) 90 | b = append(b, 10) 91 | }`, "append to slice `a` with non-zero initialized length at testing.go:6:9") 92 | }) 93 | 94 | t.Run("with just second obj is non-zero", func(t *testing.T) { 95 | linter := NewLinter(false) 96 | expectIssues(t, linter, ` 97 | package bar 98 | 99 | func foo() { 100 | a, b := make([]int, 0), make([]int, 10) 101 | a = append(a, 10) 102 | b = append(b, 10) 103 | }`, "append to slice `b` with non-zero initialized length at testing.go:7:9") 104 | }) 105 | 106 | t.Run("with all obj non-zero", func(t *testing.T) { 107 | linter := NewLinter(false) 108 | expectIssues(t, linter, ` 109 | package bar 110 | 111 | func foo() { 112 | a, b := make([]int, 10), make([]int, 10) 113 | a = append(a, 10) 114 | b = append(b, 10) 115 | }`, "append to slice `a` with non-zero initialized length at testing.go:6:9", "append to slice `b` with non-zero initialized length at testing.go:7:9") 116 | }) 117 | }) 118 | } 119 | 120 | func expectIssues(t *testing.T, linter *Linter, contents string, issues ...string) { 121 | actualIssues := parseFile(t, linter, contents) 122 | var actualIssueStrs []string 123 | for _, i := range actualIssues { 124 | actualIssueStrs = append(actualIssueStrs, i.String()) 125 | } 126 | 127 | if !reflect.DeepEqual(issues, actualIssueStrs) { 128 | t.Errorf("\nExpected:%v\nGot:%v\n", issues, actualIssueStrs) 129 | } 130 | } 131 | 132 | func parseFile(t *testing.T, linter *Linter, contents string) []Issue { 133 | fset := token.NewFileSet() 134 | expr, err := parser.ParseFile(fset, "testing.go", contents, parser.ParseComments) 135 | if err != nil { 136 | t.Fatalf("unable to parse file contents: %s", err) 137 | } 138 | issues, err := linter.Run(fset, nil, expr) 139 | if err != nil { 140 | t.Fatalf("unable to parse file: %s", err) 141 | } 142 | return issues 143 | } 144 | -------------------------------------------------------------------------------- /makezero/makezero.go: -------------------------------------------------------------------------------- 1 | // Package makezero provides a linter for appends to slices initialized with non-zero length. 2 | package makezero 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "go/ast" 8 | "go/printer" 9 | "go/token" 10 | "go/types" 11 | "log" 12 | "regexp" 13 | ) 14 | 15 | // a decl might include multiple var, 16 | // so var name with decl make final uniq obj. 17 | type uniqDecl struct { 18 | varName string 19 | decl interface{} 20 | } 21 | 22 | type Issue interface { 23 | Details() string 24 | Pos() token.Pos 25 | Position() token.Position 26 | String() string 27 | } 28 | 29 | type AppendIssue struct { 30 | name string 31 | pos token.Pos 32 | position token.Position 33 | } 34 | 35 | func (a AppendIssue) Details() string { 36 | return fmt.Sprintf("append to slice `%s` with non-zero initialized length", a.name) 37 | } 38 | 39 | func (a AppendIssue) Pos() token.Pos { 40 | return a.pos 41 | } 42 | 43 | func (a AppendIssue) Position() token.Position { 44 | return a.position 45 | } 46 | 47 | func (a AppendIssue) String() string { return toString(a) } 48 | 49 | type MustHaveNonZeroInitLenIssue struct { 50 | name string 51 | pos token.Pos 52 | position token.Position 53 | } 54 | 55 | func (i MustHaveNonZeroInitLenIssue) Details() string { 56 | return fmt.Sprintf("slice `%s` does not have non-zero initial length", i.name) 57 | } 58 | 59 | func (i MustHaveNonZeroInitLenIssue) Pos() token.Pos { 60 | return i.pos 61 | } 62 | 63 | func (i MustHaveNonZeroInitLenIssue) Position() token.Position { 64 | return i.position 65 | } 66 | 67 | func (i MustHaveNonZeroInitLenIssue) String() string { return toString(i) } 68 | 69 | func toString(i Issue) string { 70 | return fmt.Sprintf("%s at %s", i.Details(), i.Position()) 71 | } 72 | 73 | type visitor struct { 74 | initLenMustBeZero bool 75 | 76 | comments []*ast.CommentGroup // comments to apply during this visit 77 | info *types.Info 78 | 79 | nonZeroLengthSliceDecls map[uniqDecl]struct{} 80 | fset *token.FileSet 81 | issues []Issue 82 | } 83 | 84 | type Linter struct { 85 | initLenMustBeZero bool 86 | } 87 | 88 | func NewLinter(initialLengthMustBeZero bool) *Linter { 89 | return &Linter{ 90 | initLenMustBeZero: initialLengthMustBeZero, 91 | } 92 | } 93 | 94 | func (l Linter) Run(fset *token.FileSet, info *types.Info, nodes ...ast.Node) ([]Issue, error) { 95 | var issues []Issue 96 | for _, node := range nodes { 97 | var comments []*ast.CommentGroup 98 | if file, ok := node.(*ast.File); ok { 99 | comments = file.Comments 100 | } 101 | visitor := visitor{ 102 | nonZeroLengthSliceDecls: make(map[uniqDecl]struct{}), 103 | initLenMustBeZero: l.initLenMustBeZero, 104 | info: info, 105 | fset: fset, 106 | comments: comments, 107 | } 108 | ast.Walk(&visitor, node) 109 | issues = append(issues, visitor.issues...) 110 | } 111 | return issues, nil 112 | } 113 | 114 | func (v *visitor) Visit(node ast.Node) ast.Visitor { 115 | switch node := node.(type) { 116 | case *ast.CallExpr: 117 | fun, ok := node.Fun.(*ast.Ident) 118 | if !ok || fun.Name != "append" { 119 | break 120 | } 121 | if sliceIdent, ok := node.Args[0].(*ast.Ident); ok && 122 | v.hasNonZeroInitialLength(sliceIdent) && 123 | !v.hasNoLintOnSameLine(fun) { 124 | v.issues = append(v.issues, 125 | AppendIssue{ 126 | name: sliceIdent.Name, 127 | pos: fun.Pos(), 128 | position: v.fset.Position(fun.Pos()), 129 | }) 130 | } 131 | case *ast.AssignStmt: 132 | for i, right := range node.Rhs { 133 | if right, ok := right.(*ast.CallExpr); ok { 134 | fun, ok := right.Fun.(*ast.Ident) 135 | if !ok || fun.Name != "make" { 136 | continue 137 | } 138 | left := node.Lhs[i] 139 | if len(right.Args) == 2 { 140 | // ignore if not a slice or it has explicit zero length 141 | if !v.isSlice(right.Args[0]) { 142 | continue 143 | } else if lit, ok := right.Args[1].(*ast.BasicLit); ok && lit.Kind == token.INT && lit.Value == "0" { 144 | continue 145 | } 146 | if v.initLenMustBeZero && !v.hasNoLintOnSameLine(fun) { 147 | v.issues = append(v.issues, MustHaveNonZeroInitLenIssue{ 148 | name: v.textFor(left), 149 | pos: node.Pos(), 150 | position: v.fset.Position(node.Pos()), 151 | }) 152 | } 153 | v.recordNonZeroLengthSlices(left) 154 | } 155 | } 156 | } 157 | } 158 | return v 159 | } 160 | 161 | func (v *visitor) textFor(node ast.Node) string { 162 | typeBuf := new(bytes.Buffer) 163 | if err := printer.Fprint(typeBuf, v.fset, node); err != nil { 164 | log.Fatalf("ERROR: unable to print type: %s", err) 165 | } 166 | return typeBuf.String() 167 | } 168 | 169 | func (v *visitor) hasNonZeroInitialLength(ident *ast.Ident) bool { 170 | if ident.Obj == nil { 171 | log.Printf("WARNING: could not determine with %q at %s is a slice (missing object type)", 172 | ident.Name, v.fset.Position(ident.Pos()).String()) 173 | return false 174 | } 175 | _, exists := v.nonZeroLengthSliceDecls[uniqDecl{ 176 | varName: ident.Obj.Name, 177 | decl: ident.Obj.Decl, 178 | }] 179 | return exists 180 | } 181 | 182 | func (v *visitor) recordNonZeroLengthSlices(node ast.Node) { 183 | ident, ok := node.(*ast.Ident) 184 | if !ok { 185 | return 186 | } 187 | if ident.Obj == nil { 188 | return 189 | } 190 | v.nonZeroLengthSliceDecls[uniqDecl{ 191 | varName: ident.Obj.Name, 192 | decl: ident.Obj.Decl, 193 | }] = struct{}{} 194 | } 195 | 196 | func (v *visitor) isSlice(node ast.Node) bool { 197 | // determine type if this is a user-defined type 198 | if ident, ok := node.(*ast.Ident); ok { 199 | obj := ident.Obj 200 | if obj == nil { 201 | if v.info != nil { 202 | _, ok := v.info.ObjectOf(ident).Type().(*types.Slice) 203 | return ok 204 | } 205 | return false 206 | } 207 | spec, ok := obj.Decl.(*ast.TypeSpec) 208 | if !ok { 209 | return false 210 | } 211 | node = spec.Type 212 | } 213 | 214 | if node, ok := node.(*ast.ArrayType); ok { 215 | return node.Len == nil // only slices have zero length 216 | } 217 | return false 218 | } 219 | 220 | func (v *visitor) hasNoLintOnSameLine(node ast.Node) bool { 221 | nolint := regexp.MustCompile(`^\s*nozero\b`) 222 | nodePos := v.fset.Position(node.Pos()) 223 | for _, c := range v.comments { 224 | commentPos := v.fset.Position(c.Pos()) 225 | if commentPos.Line == nodePos.Line && nolint.MatchString(c.Text()) { 226 | return true 227 | } 228 | } 229 | return false 230 | } 231 | --------------------------------------------------------------------------------