Skip to content
thoPublic

About

Go linter: order functions by call graph, with autofix

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

6 Commits

Folders and files

Repository files navigation

callorder

callorder checks the order of functions and methods within each non-generated Go file. It reports one misplaced declaration per file and offers a suggested fix that reorders the file's functions with their doc comments, directives, and same-line trailing comments. Free-standing comments and type, constant, and variable declarations stay in place.

Ordering rules

  • A direct call to an unexported function or method in the same file puts its caller before the callee. Calls in closures count for the enclosing function. Dynamic calls and calls across files do not impose an order.
  • Helpers follow their callers depth-first, in first-call order. A helper used by several callers follows the last caller; unrelated entry points keep their source order.
  • Mutually recursive functions stay together, in their existing relative order unless a rule below requires otherwise. A call to an exported function that a rule places earlier groups the two the same way: a method that calls its type's constructor moves up to follow it.
  • init functions keep their execution order.
  • The enabled settings add decorder and funcorder rules, which take priority when call dependencies conflict: a helper declared above a type stays there even when a constructor below the type calls it.

A diagnostic names the rule a misplaced function breaks, for example expected A before b: exported methods come before unexported methods (funcorder struct-method), or reports a break in depth-first call order. The diagnostic sits on the function occupying the first slot the fix changes, which need not be the function that moves furthest; applying the fix settles the whole file.

Settings

The settings mirror decorder's init-first check and funcorder's checks, with the same defaults as golangci-lint. Give them the same values as those linters so that fixes satisfy all three.

Setting Default Rule
init-first false init functions come before other functions. Enabling it matches decorder's disable-init-func-first-check: false.
constructor true Constructors (exported New* or Must* returning a type declared in the file) stay after that type and before its methods.
struct-method true Exported methods come before unexported methods of the same type.
alphabetical false A type's constructors, exported methods, and unexported methods are each sorted by name. As in funcorder, constructors are sorted only with constructor, and methods only with struct-method.
function false Exported functions, including constructors, come before unexported functions. Methods and init are exempt.

Fixes only move functions, so decorder's declaration order and count checks are unaffected. Constructors already above their type may stay there, since funcorder reports them, while other functions are still ordered. The same goes for a rule that only moving a type could satisfy, such as a method above its type with the constructor below it: the method stays, funcorder reports it, and callorder still orders the rest of the file.

The tests in testdata/src check each fix against funcorder and decorder at the versions golangci-lint v2.13.2 pins; update them together.

How it works

callorder plans an order for each file's functions, compares it with the source order, and reports the first difference. The plan comes from these steps.

  1. Collect calls. Every function declaration in the file is a node. A static call to a function declared in the same file, including calls inside closures, adds an edge from the caller to the callee, once per callee in first-call order. Calls through interfaces, function values, or into other files add nothing.
  2. Collect rules. The enabled settings add precedence edges between pairs of functions, each labelled with the reason the diagnostic will quote: init before the rest, constructors before their type's methods, exported before unexported, and alphabetical order within a type's constructors and methods. Rules only ever point from init to other functions, from exported to unexported declarations, from constructors to methods, or forward in name order, so they cannot form a cycle. With constructor on, each constructor also gets a floor: the position of its type, which the planner will not put it above.
  3. Group cycles. Calls and rules together form a graph whose strongly connected components are the groups of mutually recursive functions. A group keeps its members in their current relative order and moves as a unit. Exported functions take part here, which is why a method that calls its type's constructor follows it when a rule places the constructor earlier.
  4. Walk depth-first. Between groups, only calls to unexported functions and rule edges count. The walk starts at the first group that nothing holds back, emits it, and then visits its callees in first-call order, each as soon as every caller of it has been emitted. Rules only hold back the start of a walk, never a helper's place under its caller. After every walk, the search for the next start returns to the top of the file, so that unrelated entry points keep their source order. The result is the preferred order.
  5. Fill the slots. The functions' positions in the file are the slots, and they are filled from the top. Each slot takes the first function in the preferred order whose callers and rule predecessors are already placed and whose floor allows the slot. When nothing qualifies, the planner relaxes in turn: it ignores pending calls, so a helper above a type stays there when a constructor below the type calls it; it lets a constructor that is already above its type stay above it; and finally it takes the earliest unplaced function in the current order, for rules that only moving a type could satisfy. funcorder reports the last two cases itself.
  6. Settle. The source order guides steps 3 to 5, so the planner runs again with the planned order as its guide until the plan stops changing. This is what makes the fix idempotent.

The diagnostic goes on the first slot whose planned occupant differs. If a chain of rules runs from the planned occupant to the current one, the diagnostic quotes a rule in that chain that runs backwards in the file; a constructor pushed below its type names the constructor check; any other displacement is a break in depth-first call order.

The fix replaces the text of each changed slot with the text of its planned occupant. A function's text runs from its doc comment to its closing brace, and on to a comment on the same line unless that line is shared with another declaration or the function would land on such a line, in which case the comment stays where it is.

Standalone

From this repository, run go run ./cmd/callorder ./... to audit or go run ./cmd/callorder -fix ./... to rewrite files. For JSON diagnostics, use go run ./cmd/callorder -json ./.... You can also build or install the binary with go build -o callorder ./cmd/callorder or go install ./cmd/callorder. Settings are flags, for example go run ./cmd/callorder -init-first -constructor=false ./....

golangci-lint module plugin

Use golangci-lint v2. Build a custom binary with a .custom-gcl.yml file in the project that will run the linter:

version: v2.13.2
plugins:
  - module: github.com/tho/callorder
    import: github.com/tho/callorder/plugin
    version: vX.Y.Z

Run golangci-lint custom to create custom-gcl. Configure that binary in the project's .golangci.yml:

version: "2"
linters:
  enable:
    - callorder
  settings:
    custom:
      callorder:
        type: module
        description: Require depth-first call order within Go files.
        settings:
          init-first: true

Run ./custom-gcl run ./... to audit or ./custom-gcl run --fix ./... to apply fixes. Omitted settings keep their defaults; unknown settings fail. A published private module needs normal Go private-module access (GOPRIVATE and Git credentials) while building the custom binary.

About

Go linter: order functions by call graph, with autofix

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages