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.
- 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.
initfunctions keep their execution order.- The enabled settings add
decorderandfuncorderrules, 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.
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.
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.
- 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.
- Collect rules. The enabled settings add precedence edges between pairs of
functions, each labelled with the reason the diagnostic will quote:
initbefore 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 frominitto other functions, from exported to unexported declarations, from constructors to methods, or forward in name order, so they cannot form a cycle. Withconstructoron, each constructor also gets a floor: the position of its type, which the planner will not put it above. - 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.
- 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.
- 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.
funcorderreports the last two cases itself. - 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.
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 ./....
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.ZRun 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: trueRun ./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.