Skip to content
catena
v1.5.0
Theme

Linting

Most of what goes wrong in a pipeline is not a bug. The code is correct; it just sorts a million elements to keep ten, or builds a pipeline and never runs it. catenalint finds those.

Terminal window
go install github.com/NerdMeNot/catena/lint/cmd/catenalint@latest
go vet -vettool=$(which catenalint) ./...

It is a set of ordinary go/analysis analyzers, so it also runs standalone (catenalint ./..., with -fix to apply the fixes) and can be loaded by anything that hosts analyzers.

The linter is a separate module, github.com/NerdMeNot/catena/lint. It needs golang.org/x/tools, and the library itself depends on nothing: keeping them apart means adding catena to a project adds exactly one module.

Check Reports Suggests
catenatopn Sorted(s).Take(n), s.SortedBy(sel).Take(n) and their Desc forms BottomN, BottomNBy, TopN, TopNBy
catenaminmax Sorted(s).First(), s.SortedBy(sel).First(), s.SortedWith(cmp).First(), and Sorted(s).Last() over integers and strings Min, Max, MinBy, MaxBy, MinWith
catenadedupe Distinct(Sorted(s)) Dedupe
catenatally for _, bucket := range s.GroupBy(sel) where the loop only uses len(bucket) TallyBy
catenaunusedseq a Seq, Seq2 or Try built as a statement and discarded —

The first four are about memory class. Sorting buffers the whole input; TopN holds n elements, Min holds one. Distinct remembers every value it has seen; on sorted input Dedupe remembers the last. GroupBy keeps every element; TallyBy keeps a count per key.

The last is about laziness. A pipeline is a description of work, and nothing in it runs until a terminal consumes it:

orders.OnEach(audit) // reported: nothing ever calls audit
orders.OnEach(audit).Drain() // runs

Both spellings of a package function are recognised — catena.Sorted(s) and s.Then(catena.Sorted).

A check offers a fix only where the replacement returns the same values of the same type, including which of several equal elements you get. Where that does not hold it reports without fixing, or stays silent:

  • Sorted(s).Take(n) is a sequence and TopN(s, n) is a slice. The fix is offered when the sequence is collected on the spot — Sorted(s).Take(n).Collect() — and only the report otherwise.
  • s.SortedBy(sel).First() is fixed to s.MinBy(sel): a stable sort puts the earliest of several equal keys first, and that is the one MinBy returns. s.SortedBy(sel).Last() is not reported at all — it is the latest of the equal keys, and MaxBy returns the earliest.
  • Sorted(s).Last() is fixed to Max(s) for integers and strings, where two equal elements are the same value. For floats they need not be — -0 == +0 — so there it is not reported.
  • catenatally recognises one shape and no others. A GroupBy stored in a variable, passed along, or whose buckets are read is left alone.

These equivalences are not taken on trust: a property test in the library compares each rewritten form with its original, bit for bit, over inputs chosen to contain ties, signed zeros and NaNs.

So a clean run is not a proof that nothing is wasteful. It means none of the shapes the linter can be sure about are present.

Assign the result: _ = s.Map(f) is not reported by catenaunusedseq. For the others, a //nolint-style directive is whatever your runner supports; go vet has none, and each check can be switched off by name — -catenatopn=false.

The linter carries the library’s version: lint/v1.5.0 was cut from the same commit as v1.5.0, and each library release tags the linter with it. So the linter that knows your catena’s operators is the one with your catena’s version number.

What a minor version may change is the set of reports: a new check, or a report gaining or losing a fix. If go vet gates your CI, pin the linter — catenalint@v1.5.0 rather than @latest — so that a new check arrives when you choose to take it. A patch version fixes a wrong report and adds none.

It matches catena by package path rather than by importing it, so a newer linter still works on an older catena; a check for an operator your version does not have simply never fires.

The library’s own examples are linted in CI and must come back clean.