Help us shape the future of Go! Take the 2026 Go Developer Survey

gocognit

package module
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 6, 2026 License: MIT Imports: 10 Imported by: 31

README

Go Reference go-recipes

Gocognit

Gocognit calculates cognitive complexities of functions (and methods) in Go source code. A measurement of how hard does the code is intuitively to understand.

Understanding the complexity

Given code using if statement,

func GetWords(number int) string {
    if number == 1 {            // +1
        return "one"
    } else if number == 2 {     // +1
        return "a couple"
    } else if number == 3 {     // +1
        return "a few"
    } else {                    // +1
        return "lots"
    }
} // Cognitive complexity = 4

Above code can be refactored using switch statement,

func GetWords(number int) string {
    switch number {             // +1
        case 1:
            return "one"
        case 2:
            return "a couple"
        case 3:
            return "a few"
        default:
            return "lots"
    }
} // Cognitive complexity = 1

As you see above codes are the same, but the second code are easier to understand, that is why the cognitive complexity score are lower compare to the first one.

Comparison with cyclomatic complexity

Example 1
Cyclomatic complexity
func GetWords(number int) string {      // +1
    switch number {
        case 1:                         // +1
            return "one"
        case 2:                         // +1
            return "a couple"
        case 3:                         // +1
             return "a few"
        default:
             return "lots"
    }
} // Cyclomatic complexity = 4
Cognitive complexity
func GetWords(number int) string {
    switch number {                     // +1
        case 1:
            return "one"
        case 2:
            return "a couple"
        case 3:
            return "a few"
        default:
            return "lots"
    }
} // Cognitive complexity = 1

Cognitive complexity give lower score compare to cyclomatic complexity.

Example 2
Cyclomatic complexity
func SumOfPrimes(max int) int {         // +1
    var total int

OUT:
    for i := 1; i < max; i++ {          // +1
        for j := 2; j < i; j++ {        // +1
            if i%j == 0 {               // +1
                continue OUT
            }
        }
        total += i
    }

    return total
} // Cyclomatic complexity = 4
Cognitive complexity
func SumOfPrimes(max int) int {
    var total int

OUT:
    for i := 1; i < max; i++ {          // +1
        for j := 2; j < i; j++ {        // +2 (nesting = 1)
            if i%j == 0 {               // +3 (nesting = 2)
                continue OUT            // +1
            }
        }
        total += i
    }

    return total
} // Cognitive complexity = 7

Cognitive complexity give higher score compare to cyclomatic complexity.

Rules

The cognitive complexity of a function is calculated according to the following rules:

Note: these rules are specific for Go, please see the original whitepaper for more complete reference.

Increments

There is an increment for each of the following:

  1. if, else if, else
  2. switch, select
  3. for
  4. goto LABEL, break LABEL, continue LABEL
  5. sequence of binary logical operators
  6. each method in a recursion cycle
Nesting level

The following structures increment the nesting level:

  1. if, else if, else
  2. switch, select
  3. for
  4. function literal or lambda
Nesting increments

The following structures receive a nesting increment commensurate with their nested depth inside nesting structures:

  1. if
  2. switch, select
  3. for

Installation

go install github.com/uudashr/gocognit/cmd/gocognit@latest

or

go get github.com/uudashr/gocognit/cmd/gocognit

Usage

$ gocognit
Calculate cognitive complexities of Go functions.

Usage:

  gocognit [<flag> ...] <Go file or directory> ...

Flags:

  -over N           show functions with complexity > N only
                    and return exit code 1 if the output is non-empty
  -top N            show the top N most complex functions only
  -avg              show the average complexity over all functions,
                    not depending on whether -over or -top are set
  -test             indicates whether test files should be included
  -json             encode the output as JSON
  -d                enable diagnostic output
  -f format         string the format to use
                    (default "{{.Complexity}} {{.PkgName}} {{.FuncName}} {{.Pos}}")
  -ignore expr      ignore files matching the given regexp
  -exact-recursion  work out what each call points to, the way the compiler
                    does, so recursion is scored exactly. Slower, and the code
                    must build; without it, mutually recursive methods may be
                    under-counted

The (default) output fields for each line are:

  <complexity> <package> <function> <file:row:column>

The (default) output fields for each line are:

  {{.Complexity}} {{.PkgName}} {{.FuncName}} {{.Pos}}

or equal to <complexity> <package> <function> <file:row:column>

The struct being passed to the template is:

  type Stat struct {
    PkgName     string
    FuncName    string
    Complexity  int
    Pos         token.Position
    Diagnostics []Diagnostics
  }

  type Diagnostic struct {
    Inc     string
    Nesting int
    Text    string
    Pos     DiagnosticPosition
  }

  type DiagnosticPosition struct {
    Offset int
    Line   int
    Column int
  }

Examples:

$ gocognit .
$ gocognit main.go
$ gocognit -top 10 src/
$ gocognit -over 25 docker
$ gocognit -avg .
$ gocognit -ignore "_test|testdata" .

The output fields for each line are:

<complexity> <package> <function> <file:row:column>

Recursion and -exact-recursion. To score recursion, gocognit has to know which function each call actually goes to. By default it works that out from the source alone: fast, no build required, and fine for direct recursion and for cycles between top-level functions. It is not enough when methods call each other indirectly — for example Walk calls n.Step() and Step calls n.next.Walk() — because the type of n is not visible in the syntax, so that cycle can be under-counted. When that might be happening, the CLI prints a one-time note on stderr. Pass -exact-recursion to have gocognit work out call targets the way the compiler does, giving exact recursion scores; it is slower and needs the code to build, and it falls back to the default mode with a warning if it cannot load.

Ignore individual functions

Ignore individual functions by specifying gocognit:ignore directive.

//gocognit:ignore
func IgnoreMe() {
    // ...
}

Diagnostic

To understand how the complexity are calculated, we can enable the diagnostic by using -d flag.

Example:

$ gocognit -json -d .

It will show the diagnostic output in JSON format

JSON Output
[
    {
        "PkgName": "prime",
        "FuncName": "SumOfPrimes",
        "Complexity": 7,
        "Pos": {
            "Filename": "prime.go",
            "Offset": 15,
            "Line": 3,
            "Column": 1
        },
        "Diagnostics": [
            {
                "Inc": 1,
                "Text": "for",
                "Pos": {
                    "Offset": 69,
                    "Line": 7,
                    "Column": 2
                }
            },
            {
                "Inc": 2,
                "Nesting": 1,
                "Text": "for",
                "Pos": {
                    "Offset": 104,
                    "Line": 8,
                    "Column": 3
                }
            },
            {
                "Inc": 3,
                "Nesting": 2,
                "Text": "if",
                "Pos": {
                    "Offset": 152,
                    "Line": 9,
                    "Column": 4
                }
            },
            {
                "Inc": 1,
                "Text": "continue",
                "Pos": {
                    "Offset": 190,
                    "Line": 10,
                    "Column": 5
                }
            }
        ]
    }
]

Documentation

Overview

Package gocognit defines Analyzer other utilities to checks and calculate the complexity of function based on "cognitive complexity" methods.

Index

Constants

View Source
const Doc = `` /* 169-byte string literal not displayed */

Variables

View Source
var Analyzer = &analysis.Analyzer{
	Name:     "gocognit",
	Doc:      Doc,
	Requires: []*analysis.Analyzer{inspect.Analyzer},
	Run:      run,
}

Analyzer reports a diagnostic for every function or method which is too complex specified by its -over flag.

Functions

func Complexity

func Complexity(fn *ast.FuncDecl) int

Complexity calculates the cognitive complexity of a function.

func CountDown added in v1.2.1

func CountDown(start int) iter.Seq[int]

func DoCount added in v1.2.1

func DoCount()

func DoFilter added in v1.2.1

func DoFilter()

func FilteredMap added in v1.2.1

func FilteredMap(m map[string]int, threshold int) iter.Seq2[string, int]

func ForRangeIter added in v1.2.1

func ForRangeIter(all iter.Seq[int])

func PossibleMethodRecursion added in v1.3.0

func PossibleMethodRecursion(files []*ast.File) bool

PossibleMethodRecursion reports whether the type-less recursion analysis may have missed a cycle. Without type information a call such as x.M(...) cannot be attributed, so two declarations that each call a same-named method can look unrelated. When those unresolved calls would close a cycle that the resolved analysis did not find, this returns true.

It is a heuristic intended for user guidance (for example, suggesting a type-aware run); it never affects complexity scores.

func RecursiveFuncs added in v1.3.0

func RecursiveFuncs(files []*ast.File, info *types.Info) map[*ast.FuncDecl]bool

RecursiveFuncs reports which top-level functions and methods in files take part in a recursion cycle, whether direct or indirect.

A function is reported when it lies on a cycle of mutually recursive calls (a strongly connected component with more than one member) or when it calls itself directly. All declarations of the given files are expected to belong to the same package.

When info is non-nil it is used to resolve calls with type information. That covers methods and references across files of the same package. When info is nil a syntactic approximation is used: package-level functions and direct calls through a method's own receiver are recognized, but mutual method recursion may go undetected. Calls into other packages are never resolved.

See also ScanComplexityWithRecursion, which scores a declaration with the result, and ComplexityStatsForFiles, which builds the statistics for a whole package.

Types

type Diagnostic added in v1.2.0

type Diagnostic struct {
	Inc     int
	Nesting int `json:",omitempty"`
	Text    string
	Pos     DiagnosticPosition
}

Diagnostic contains information how the complexity increase.

func (Diagnostic) String added in v1.2.0

func (d Diagnostic) String() string

type DiagnosticPosition added in v1.2.0

type DiagnosticPosition struct {
	Offset int // offset, starting at 0
	Line   int // line number, starting at 1
	Column int // column number, starting at 1 (byte count)
}

DiagnosticPosition is the position of the diagnostic.

func (DiagnosticPosition) String added in v1.2.0

func (pos DiagnosticPosition) String() string

type ScanResult added in v1.2.0

type ScanResult struct {
	Diagnostics []diagnostic
	Complexity  int
}

func ScanComplexity added in v1.2.0

func ScanComplexity(fn *ast.FuncDecl, includeDiagnostics bool) ScanResult

ScanComplexity scans the function declaration. Recursion is detected for direct self-calls only: a lone declaration carries no information about the surrounding package, so indirect cycles cannot be seen. Prefer a package-aware entry point such as ComplexityStatsForFiles, or combine RecursiveFuncs with ScanComplexityWithRecursion.

func ScanComplexityWithRecursion added in v1.3.0

func ScanComplexityWithRecursion(fn *ast.FuncDecl, recursive bool, includeDiagnostics bool) ScanResult

ScanComplexityWithRecursion scans the function declaration with recursion information from a package-level analysis (see RecursiveFuncs). When recursive is true, exactly one fundamental increment is added for the recursion cycle, regardless of how many recursive calls the function makes.

type Stat

type Stat struct {
	PkgName     string
	FuncName    string
	Complexity  int
	Pos         token.Position
	Diagnostics []Diagnostic `json:",omitempty"`
}

Stat is statistic of the complexity.

func ComplexityStats

func ComplexityStats(f *ast.File, fset *token.FileSet, stats []Stat) []Stat

ComplexityStats builds the complexity statistics.

func ComplexityStatsForFiles added in v1.3.0

func ComplexityStatsForFiles(files []*ast.File, fset *token.FileSet, info *types.Info, stats []Stat) []Stat

ComplexityStatsForFiles builds the complexity statistics for a package made up of the given files. Recursion is detected across the whole set, so indirect recursion is counted. When info is non-nil it is used to resolve calls precisely; otherwise a syntactic approximation is used.

func ComplexityStatsForFilesWithDiagnostic added in v1.3.0

func ComplexityStatsForFilesWithDiagnostic(files []*ast.File, fset *token.FileSet, info *types.Info, stats []Stat, enableDiagnostics bool) []Stat

ComplexityStatsForFilesWithDiagnostic builds the complexity statistics for a package made up of the given files, optionally with diagnostic output.

func ComplexityStatsWithDiagnostic added in v1.2.0

func ComplexityStatsWithDiagnostic(f *ast.File, fset *token.FileSet, stats []Stat, enableDiagnostics bool) []Stat

ComplexityStatsWithDiagnostic builds the complexity statistics with diagnostic.

func (Stat) String

func (s Stat) String() string

Directories

Path Synopsis
cmd
gocognit command
GoCognitive calculates the cognitive complexities of functions and methods in Go source code.
GoCognitive calculates the cognitive complexities of functions and methods in Go source code.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL