From 0108387c24d461028f92f925c2456a868b7c5ab9 Mon Sep 17 00:00:00 2001 From: nakabonne Date: Sun, 2 Feb 2020 14:58:38 +0900 Subject: [PATCH 1/3] Add nestif linter --- .golangci.example.yml | 3 + README.md | 6 + go.mod | 1 + go.sum | 2 + pkg/config/config.go | 8 + pkg/golinters/nestif.go | 65 ++++++++ pkg/lint/lintersdb/manager.go | 3 + test/testdata/nestif.go | 47 ++++++ vendor/github.com/nakabonne/nestif/.gitignore | 16 ++ vendor/github.com/nakabonne/nestif/LICENSE | 25 +++ vendor/github.com/nakabonne/nestif/README.md | 122 +++++++++++++++ vendor/github.com/nakabonne/nestif/go.mod | 8 + vendor/github.com/nakabonne/nestif/go.sum | 12 ++ vendor/github.com/nakabonne/nestif/nestif.go | 148 ++++++++++++++++++ 14 files changed, 466 insertions(+) create mode 100644 pkg/golinters/nestif.go create mode 100644 test/testdata/nestif.go create mode 100644 vendor/github.com/nakabonne/nestif/.gitignore create mode 100644 vendor/github.com/nakabonne/nestif/LICENSE create mode 100644 vendor/github.com/nakabonne/nestif/README.md create mode 100644 vendor/github.com/nakabonne/nestif/go.mod create mode 100644 vendor/github.com/nakabonne/nestif/go.sum create mode 100644 vendor/github.com/nakabonne/nestif/nestif.go diff --git a/.golangci.example.yml b/.golangci.example.yml index 50e3d6acd68c..bfae504c4e2c 100644 --- a/.golangci.example.yml +++ b/.golangci.example.yml @@ -96,6 +96,9 @@ linters-settings: gocognit: # minimal code complexity to report, 30 by default (but we recommend 10-20) min-complexity: 10 + nestif: + # minimal complexity of if statements to report, 5 by default + min-complexity: 4 goconst: # minimal length of string constant, 3 by default min-len: 3 diff --git a/README.md b/README.md index 1ca41e10c5ed..14e6fc6b84ae 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,7 @@ lll: Reports long lines [fast: true, auto-fix: false] maligned: Tool to detect Go structs that would take less memory if their fields were sorted [fast: true, auto-fix: false] misspell: Finds commonly misspelled English words in comments [fast: true, auto-fix: true] nakedret: Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false] +nestif: Reports deeply nested if statements [fast: true, auto-fix: false] prealloc: Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false] rowserrcheck: checks whether Err of rows is checked successfully [fast: true, auto-fix: false] scopelint: Scopelint checks for unpinned variables in go programs [fast: true, auto-fix: false] @@ -490,6 +491,7 @@ golangci-lint help linters - [goprintffuncname](https://github.com/jirfag/go-printf-func-name) - Checks that printf-like functions are named with `f` at the end - [gomnd](https://github.com/tommy-muehle/go-mnd) - An analyzer to detect magic numbers. - [godot](https://github.com/tetafro/godot) - Check if comments end in a period +- [nestif](https://github.com/nakabonne/nestif) - Reports deeply nested if statements ## Configuration @@ -708,6 +710,9 @@ linters-settings: gocognit: # minimal code complexity to report, 30 by default (but we recommend 10-20) min-complexity: 10 + nestif: + # minimal complexity of if statements to report, 5 by default + min-complexity: 4 goconst: # minimal length of string constant, 3 by default min-len: 3 @@ -1267,6 +1272,7 @@ Thanks to developers and authors of used linters: - [jirfag](https://github.com/jirfag) - [tommy-muehle](https://github.com/tommy-muehle) - [tetafro](https://github.com/tetafro) +- [nakabonne](https://github.com/nakabonne) ## Changelog diff --git a/go.mod b/go.mod index c58df07239d3..9b16070cf144 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/mattn/go-colorable v0.1.4 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b + github.com/nakabonne/nestif v0.3.0 github.com/pkg/errors v0.8.1 github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83 github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada // v2.19.8 diff --git a/go.sum b/go.sum index fa3ecc58fe77..224c3e8edfa4 100644 --- a/go.sum +++ b/go.sum @@ -183,6 +183,8 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nakabonne/nestif v0.3.0 h1:+yOViDGhg8ygGrmII72nV9B/zGxY188TYpfolntsaPw= +github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= diff --git a/pkg/config/config.go b/pkg/config/config.go index 0c440ae9a0e7..0c97317fa411 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -199,6 +199,7 @@ type LintersSettings struct { Dogsled DogsledSettings Gocognit GocognitSettings Godot GodotSettings + Nestif NestifSettings Custom map[string]CustomLinterSettings } @@ -280,6 +281,10 @@ type GodotSettings struct { CheckAll bool `mapstructure:"check-all"` } +type NestifSettings struct { + MinComplexity int `mapstructure:"min-complexity"` +} + //nolint:gomnd var defaultLintersSettings = LintersSettings{ Lll: LllSettings{ @@ -319,6 +324,9 @@ var defaultLintersSettings = LintersSettings{ ForceCuddleErrCheckAndAssign: false, ForceCaseTrailingWhitespaceLimit: 0, }, + Nestif: NestifSettings{ + MinComplexity: 5, + }, } type CustomLinterSettings struct { diff --git a/pkg/golinters/nestif.go b/pkg/golinters/nestif.go new file mode 100644 index 000000000000..03dc46be18ad --- /dev/null +++ b/pkg/golinters/nestif.go @@ -0,0 +1,65 @@ +package golinters + +import ( + "sort" + "sync" + + "github.com/nakabonne/nestif" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/result" +) + +const nestifName = "nestif" + +func NewNestif() *goanalysis.Linter { + var mu sync.Mutex + var resIssues []goanalysis.Issue + + analyzer := &analysis.Analyzer{ + Name: goanalysis.TheOnlyAnalyzerName, + Doc: goanalysis.TheOnlyanalyzerDoc, + } + return goanalysis.NewLinter( + nestifName, + "Reports deeply nested if statements", + []*analysis.Analyzer{analyzer}, + nil, + ).WithContextSetter(func(lintCtx *linter.Context) { + analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { + checker := &nestif.Checker{ + MinComplexity: lintCtx.Settings().Nestif.MinComplexity, + } + var issues []nestif.Issue + for _, f := range pass.Files { + issues = append(issues, checker.Check(f, pass.Fset)...) + } + if len(issues) == 0 { + return nil, nil + } + + sort.Slice(issues, func(i, j int) bool { + return issues[i].Complexity > issues[j].Complexity + }) + + res := make([]goanalysis.Issue, 0, len(issues)) + for _, i := range issues { + res = append(res, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint + Pos: i.Pos, + Text: i.Message, + FromLinter: nestifName, + }, pass)) + } + + mu.Lock() + resIssues = append(resIssues, res...) + mu.Unlock() + + return nil, nil + } + }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return resIssues + }).WithLoadMode(goanalysis.LoadModeSyntax) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 85f2cec79035..4322b2c5b0a5 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -250,6 +250,9 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { linter.NewConfig(golinters.NewGodot()). WithPresets(linter.PresetStyle). WithURL("https://github.com/tetafro/godot"), + linter.NewConfig(golinters.NewNestif()). + WithPresets(linter.PresetComplexity). + WithURL("https://github.com/nakabonne/nestif"), } isLocalRun := os.Getenv("GOLANGCI_COM_RUN") == "" diff --git a/test/testdata/nestif.go b/test/testdata/nestif.go new file mode 100644 index 000000000000..350e0a7257f1 --- /dev/null +++ b/test/testdata/nestif.go @@ -0,0 +1,47 @@ +//args: -Enestif +//config: linters-settings.nestif.min-complexity=1 +package testdata + +func _() { + var b1, b2, b3, b4 bool + + if b1 { // ERROR "`if b1` is deeply nested \(complexity: 1\)" + if b2 { // +1 + } + } + + if b1 { // ERROR "`if b1` is deeply nested \(complexity: 3\)" + if b2 { // +1 + if b3 { // +2 + } + } + } + + if b1 { // ERROR "`if b1` is deeply nested \(complexity: 5\)" + if b2 { // +1 + } else if b3 { // +1 + if b4 { // +2 + } + } else { // +1 + } + } + + if b1 { // ERROR "`if b1` is deeply nested \(complexity: 9\)" + if b2 { // +1 + if b3 { // +2 + } + } + + if b2 { // +1 + if b3 { // +2 + if b4 { // +3 + } + } + } + } + + if b1 == b2 == b3 { // ERROR "`if b1 == b2 == b3` is deeply nested \(complexity: 1\)" + if b4 { // +1 + } + } +} diff --git a/vendor/github.com/nakabonne/nestif/.gitignore b/vendor/github.com/nakabonne/nestif/.gitignore new file mode 100644 index 000000000000..df71a2ac7fb6 --- /dev/null +++ b/vendor/github.com/nakabonne/nestif/.gitignore @@ -0,0 +1,16 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +/nestif + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/vendor/github.com/nakabonne/nestif/LICENSE b/vendor/github.com/nakabonne/nestif/LICENSE new file mode 100644 index 000000000000..ddf4d71ede83 --- /dev/null +++ b/vendor/github.com/nakabonne/nestif/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2020, Ryo Nakao +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/nakabonne/nestif/README.md b/vendor/github.com/nakabonne/nestif/README.md new file mode 100644 index 000000000000..ede411f7311b --- /dev/null +++ b/vendor/github.com/nakabonne/nestif/README.md @@ -0,0 +1,122 @@ +# nestif + +[![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/nakabonne/nestif) + +Reports deeply nested if statements in Go code, by calculating its complexities based on the rules defined by the [Cognitive Complexity white paper by G. Ann Campbell](https://www.sonarsource.com/docs/CognitiveComplexity.pdf). + +It helps you find if statements that make your code hard to read, and clarifies which parts to refactor. + +## Installation + +``` +go get github.com/nakabonne/nestif/cmd/nestif +``` + +## Usage + +### Quick Start + +```bash +nestif +``` + +The `...` glob operator is supported, and the above is an equivalent of: + +```bash +nestif ./... +``` + +One or more files and directories can be specified in a single command: + +```bash +nestif dir/foo.go dir2 dir3/... +``` + +Packages can be specified as well: + +```bash +nestif github.com/foo/bar example.com/bar/baz +``` + +### Options + +``` +usage: nestif [ ...] ... + -e, --exclude-dirs strings regexps of directories to be excluded for checking; comma-separated list + --json emit json format + --min int minimum complexity to show (default 1) + --top int show only the top N most complex if statements (default 10) + -v, --verbose verbose output +``` + +### Example + +Let's say you write: + +```go +package main + +func _() { + if foo { + if bar { + } + } + + if baz == "baz" { + if qux { + if quux { + } + } + } +} +``` + +And give it to nestif: + +```console +$ nestif foo.go +foo.go:9:2: `if baz == "baz"` is nested (complexity: 3) +foo.go:4:2: `if foo` is nested (complexity: 1) +``` + +Note that the results are sorted in descending order of complexity. In addition, it shows only the top 10 most complex if statements by default, and you can specify how many to show with `-top` flag. + +### Rules + +It calculates the complexities of if statements according to the nesting rules of Cognitive Complexity. +Since the more deeply-nested your code gets, the harder it can be to reason about, it assesses a nesting increment for it: + +```go +if condition1 { + if condition2 { // +1 + if condition3 { // +2 + if condition4 { // +3 + } + } + } +} +``` + +`else` and `else if` increase complexity by one wherever they are because the mental cost has already been paid when reading the if: + +```go +if condition1 { + if condition2 { // +1 + if condition3 { // +2 + } else if condition4 { // +1 + } else { // +1 + if condition5 { // +3 + } + } + } +} +``` + +## Inspired by + +- [uudashr/gocognit](https://github.com/uudashr/gocognit) +- [fzipp/gocyclo](https://github.com/fzipp/gocyclo) + +## Further reading + +Please see the [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf) white paper by G. Ann Campbell for more detail on Cognitive Complexity. diff --git a/vendor/github.com/nakabonne/nestif/go.mod b/vendor/github.com/nakabonne/nestif/go.mod new file mode 100644 index 000000000000..325901d59051 --- /dev/null +++ b/vendor/github.com/nakabonne/nestif/go.mod @@ -0,0 +1,8 @@ +module github.com/nakabonne/nestif + +go 1.13 + +require ( + github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.4.0 +) diff --git a/vendor/github.com/nakabonne/nestif/go.sum b/vendor/github.com/nakabonne/nestif/go.sum new file mode 100644 index 000000000000..6d790ef35bdd --- /dev/null +++ b/vendor/github.com/nakabonne/nestif/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/nakabonne/nestif/nestif.go b/vendor/github.com/nakabonne/nestif/nestif.go new file mode 100644 index 000000000000..d458022fb690 --- /dev/null +++ b/vendor/github.com/nakabonne/nestif/nestif.go @@ -0,0 +1,148 @@ +// Copyright 2020 Ryo Nakao . +// +// All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package nestif provides an API to detect deeply nested if statements. +package nestif + +import ( + "bytes" + "fmt" + "go/ast" + "go/printer" + "go/token" + "io" +) + +// Issue represents an issue of root if statement that has nested ifs. +type Issue struct { + Pos token.Position + Complexity int + Message string +} + +// Checker represents a checker that finds nested if statements. +type Checker struct { + // Minimum complexity to report. + MinComplexity int + + // For debug mode. + debugWriter io.Writer + issues []Issue +} + +// Check inspects a single file and returns found issues. +func (c *Checker) Check(f *ast.File, fset *token.FileSet) []Issue { + c.issues = []Issue{} // refresh + ast.Inspect(f, func(n ast.Node) bool { + fn, ok := n.(*ast.FuncDecl) + if !ok || fn.Body == nil { + return true + } + for _, stmt := range fn.Body.List { + c.checkFunc(&stmt, fset) + } + return true + }) + + return c.issues +} + +// checkFunc inspects a function and sets a list of issues if there are. +func (c *Checker) checkFunc(stmt *ast.Stmt, fset *token.FileSet) { + ast.Inspect(*stmt, func(n ast.Node) bool { + ifStmt, ok := n.(*ast.IfStmt) + if !ok { + return true + } + + c.checkIf(ifStmt, fset) + return false + }) +} + +// checkIf inspects a if statement and sets an issue if there is. +func (c *Checker) checkIf(stmt *ast.IfStmt, fset *token.FileSet) { + v := newVisitor() + ast.Walk(v, stmt) + if v.complexity < c.MinComplexity { + return + } + pos := fset.Position(stmt.Pos()) + c.issues = append(c.issues, Issue{ + Pos: pos, + Complexity: v.complexity, + Message: c.makeMessage(v.complexity, stmt.Cond, fset), + }) +} + +type visitor struct { + complexity int + nesting int + // To avoid adding complexity including nesting level to `else if`. + elseifs map[*ast.IfStmt]bool +} + +func newVisitor() *visitor { + return &visitor{ + elseifs: make(map[*ast.IfStmt]bool), + } +} + +// Visit traverses an AST in depth-first order by calling itself +// recursively, and calculates the complexities of if statements. +func (v *visitor) Visit(n ast.Node) ast.Visitor { + ifStmt, ok := n.(*ast.IfStmt) + if !ok { + return v + } + + v.incComplexity(ifStmt) + v.nesting++ + ast.Walk(v, ifStmt.Body) + v.nesting-- + + switch t := ifStmt.Else.(type) { + case *ast.BlockStmt: + v.complexity++ + v.nesting++ + ast.Walk(v, t) + v.nesting-- + case *ast.IfStmt: + v.elseifs[t] = true + ast.Walk(v, t) + } + + return nil +} + +func (v *visitor) incComplexity(n *ast.IfStmt) { + // In case of `else if`, increase by 1. + if v.elseifs[n] { + v.complexity++ + } else { + v.complexity += v.nesting + } +} + +func (c *Checker) makeMessage(complexity int, cond ast.Expr, fset *token.FileSet) string { + p := &printer.Config{} + b := new(bytes.Buffer) + if err := p.Fprint(b, fset, cond); err != nil { + c.debug("failed to convert condition into string: %v", err) + } + return fmt.Sprintf("`if %s` is deeply nested (complexity: %d)", b.String(), complexity) +} + +// DebugMode makes it possible to emit debug logs. +func (c *Checker) DebugMode(w io.Writer) { + c.debugWriter = w +} + +func (c *Checker) debug(format string, a ...interface{}) { + if c.debugWriter != nil { + fmt.Fprintf(c.debugWriter, format, a...) + } +} From 9442a4132c0e6e0ad2c38762ba120da96a1a8473 Mon Sep 17 00:00:00 2001 From: nakabonne Date: Wed, 22 Apr 2020 08:53:50 +0900 Subject: [PATCH 2/3] Use sort.SliceStable instead of sort.Slice --- pkg/golinters/nestif.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/golinters/nestif.go b/pkg/golinters/nestif.go index 03dc46be18ad..841a22d4b284 100644 --- a/pkg/golinters/nestif.go +++ b/pkg/golinters/nestif.go @@ -40,7 +40,7 @@ func NewNestif() *goanalysis.Linter { return nil, nil } - sort.Slice(issues, func(i, j int) bool { + sort.SliceStable(issues, func(i, j int) bool { return issues[i].Complexity > issues[j].Complexity }) From 37f0e44dfaa26357d35621e2dd5960180dfc8603 Mon Sep 17 00:00:00 2001 From: nakabonne Date: Wed, 22 Apr 2020 08:58:24 +0900 Subject: [PATCH 3/3] Remove vendoring --- vendor/github.com/nakabonne/nestif/.gitignore | 16 -- vendor/github.com/nakabonne/nestif/LICENSE | 25 --- vendor/github.com/nakabonne/nestif/README.md | 122 --------------- vendor/github.com/nakabonne/nestif/go.mod | 8 - vendor/github.com/nakabonne/nestif/go.sum | 12 -- vendor/github.com/nakabonne/nestif/nestif.go | 148 ------------------ 6 files changed, 331 deletions(-) delete mode 100644 vendor/github.com/nakabonne/nestif/.gitignore delete mode 100644 vendor/github.com/nakabonne/nestif/LICENSE delete mode 100644 vendor/github.com/nakabonne/nestif/README.md delete mode 100644 vendor/github.com/nakabonne/nestif/go.mod delete mode 100644 vendor/github.com/nakabonne/nestif/go.sum delete mode 100644 vendor/github.com/nakabonne/nestif/nestif.go diff --git a/vendor/github.com/nakabonne/nestif/.gitignore b/vendor/github.com/nakabonne/nestif/.gitignore deleted file mode 100644 index df71a2ac7fb6..000000000000 --- a/vendor/github.com/nakabonne/nestif/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib -/nestif - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ diff --git a/vendor/github.com/nakabonne/nestif/LICENSE b/vendor/github.com/nakabonne/nestif/LICENSE deleted file mode 100644 index ddf4d71ede83..000000000000 --- a/vendor/github.com/nakabonne/nestif/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -BSD 2-Clause License - -Copyright (c) 2020, Ryo Nakao -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/nakabonne/nestif/README.md b/vendor/github.com/nakabonne/nestif/README.md deleted file mode 100644 index ede411f7311b..000000000000 --- a/vendor/github.com/nakabonne/nestif/README.md +++ /dev/null @@ -1,122 +0,0 @@ -# nestif - -[![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/nakabonne/nestif) - -Reports deeply nested if statements in Go code, by calculating its complexities based on the rules defined by the [Cognitive Complexity white paper by G. Ann Campbell](https://www.sonarsource.com/docs/CognitiveComplexity.pdf). - -It helps you find if statements that make your code hard to read, and clarifies which parts to refactor. - -## Installation - -``` -go get github.com/nakabonne/nestif/cmd/nestif -``` - -## Usage - -### Quick Start - -```bash -nestif -``` - -The `...` glob operator is supported, and the above is an equivalent of: - -```bash -nestif ./... -``` - -One or more files and directories can be specified in a single command: - -```bash -nestif dir/foo.go dir2 dir3/... -``` - -Packages can be specified as well: - -```bash -nestif github.com/foo/bar example.com/bar/baz -``` - -### Options - -``` -usage: nestif [ ...] ... - -e, --exclude-dirs strings regexps of directories to be excluded for checking; comma-separated list - --json emit json format - --min int minimum complexity to show (default 1) - --top int show only the top N most complex if statements (default 10) - -v, --verbose verbose output -``` - -### Example - -Let's say you write: - -```go -package main - -func _() { - if foo { - if bar { - } - } - - if baz == "baz" { - if qux { - if quux { - } - } - } -} -``` - -And give it to nestif: - -```console -$ nestif foo.go -foo.go:9:2: `if baz == "baz"` is nested (complexity: 3) -foo.go:4:2: `if foo` is nested (complexity: 1) -``` - -Note that the results are sorted in descending order of complexity. In addition, it shows only the top 10 most complex if statements by default, and you can specify how many to show with `-top` flag. - -### Rules - -It calculates the complexities of if statements according to the nesting rules of Cognitive Complexity. -Since the more deeply-nested your code gets, the harder it can be to reason about, it assesses a nesting increment for it: - -```go -if condition1 { - if condition2 { // +1 - if condition3 { // +2 - if condition4 { // +3 - } - } - } -} -``` - -`else` and `else if` increase complexity by one wherever they are because the mental cost has already been paid when reading the if: - -```go -if condition1 { - if condition2 { // +1 - if condition3 { // +2 - } else if condition4 { // +1 - } else { // +1 - if condition5 { // +3 - } - } - } -} -``` - -## Inspired by - -- [uudashr/gocognit](https://github.com/uudashr/gocognit) -- [fzipp/gocyclo](https://github.com/fzipp/gocyclo) - -## Further reading - -Please see the [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf) white paper by G. Ann Campbell for more detail on Cognitive Complexity. diff --git a/vendor/github.com/nakabonne/nestif/go.mod b/vendor/github.com/nakabonne/nestif/go.mod deleted file mode 100644 index 325901d59051..000000000000 --- a/vendor/github.com/nakabonne/nestif/go.mod +++ /dev/null @@ -1,8 +0,0 @@ -module github.com/nakabonne/nestif - -go 1.13 - -require ( - github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.4.0 -) diff --git a/vendor/github.com/nakabonne/nestif/go.sum b/vendor/github.com/nakabonne/nestif/go.sum deleted file mode 100644 index 6d790ef35bdd..000000000000 --- a/vendor/github.com/nakabonne/nestif/go.sum +++ /dev/null @@ -1,12 +0,0 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/nakabonne/nestif/nestif.go b/vendor/github.com/nakabonne/nestif/nestif.go deleted file mode 100644 index d458022fb690..000000000000 --- a/vendor/github.com/nakabonne/nestif/nestif.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2020 Ryo Nakao . -// -// All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package nestif provides an API to detect deeply nested if statements. -package nestif - -import ( - "bytes" - "fmt" - "go/ast" - "go/printer" - "go/token" - "io" -) - -// Issue represents an issue of root if statement that has nested ifs. -type Issue struct { - Pos token.Position - Complexity int - Message string -} - -// Checker represents a checker that finds nested if statements. -type Checker struct { - // Minimum complexity to report. - MinComplexity int - - // For debug mode. - debugWriter io.Writer - issues []Issue -} - -// Check inspects a single file and returns found issues. -func (c *Checker) Check(f *ast.File, fset *token.FileSet) []Issue { - c.issues = []Issue{} // refresh - ast.Inspect(f, func(n ast.Node) bool { - fn, ok := n.(*ast.FuncDecl) - if !ok || fn.Body == nil { - return true - } - for _, stmt := range fn.Body.List { - c.checkFunc(&stmt, fset) - } - return true - }) - - return c.issues -} - -// checkFunc inspects a function and sets a list of issues if there are. -func (c *Checker) checkFunc(stmt *ast.Stmt, fset *token.FileSet) { - ast.Inspect(*stmt, func(n ast.Node) bool { - ifStmt, ok := n.(*ast.IfStmt) - if !ok { - return true - } - - c.checkIf(ifStmt, fset) - return false - }) -} - -// checkIf inspects a if statement and sets an issue if there is. -func (c *Checker) checkIf(stmt *ast.IfStmt, fset *token.FileSet) { - v := newVisitor() - ast.Walk(v, stmt) - if v.complexity < c.MinComplexity { - return - } - pos := fset.Position(stmt.Pos()) - c.issues = append(c.issues, Issue{ - Pos: pos, - Complexity: v.complexity, - Message: c.makeMessage(v.complexity, stmt.Cond, fset), - }) -} - -type visitor struct { - complexity int - nesting int - // To avoid adding complexity including nesting level to `else if`. - elseifs map[*ast.IfStmt]bool -} - -func newVisitor() *visitor { - return &visitor{ - elseifs: make(map[*ast.IfStmt]bool), - } -} - -// Visit traverses an AST in depth-first order by calling itself -// recursively, and calculates the complexities of if statements. -func (v *visitor) Visit(n ast.Node) ast.Visitor { - ifStmt, ok := n.(*ast.IfStmt) - if !ok { - return v - } - - v.incComplexity(ifStmt) - v.nesting++ - ast.Walk(v, ifStmt.Body) - v.nesting-- - - switch t := ifStmt.Else.(type) { - case *ast.BlockStmt: - v.complexity++ - v.nesting++ - ast.Walk(v, t) - v.nesting-- - case *ast.IfStmt: - v.elseifs[t] = true - ast.Walk(v, t) - } - - return nil -} - -func (v *visitor) incComplexity(n *ast.IfStmt) { - // In case of `else if`, increase by 1. - if v.elseifs[n] { - v.complexity++ - } else { - v.complexity += v.nesting - } -} - -func (c *Checker) makeMessage(complexity int, cond ast.Expr, fset *token.FileSet) string { - p := &printer.Config{} - b := new(bytes.Buffer) - if err := p.Fprint(b, fset, cond); err != nil { - c.debug("failed to convert condition into string: %v", err) - } - return fmt.Sprintf("`if %s` is deeply nested (complexity: %d)", b.String(), complexity) -} - -// DebugMode makes it possible to emit debug logs. -func (c *Checker) DebugMode(w io.Writer) { - c.debugWriter = w -} - -func (c *Checker) debug(format string, a ...interface{}) { - if c.debugWriter != nil { - fmt.Fprintf(c.debugWriter, format, a...) - } -}