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.
go install github.com/NerdMeNot/catena/lint/cmd/catenalint@latestgo 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.
The checks
Section titled “The checks”| 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 auditorders.OnEach(audit).Drain() // runsBoth spellings of a package function are recognised — catena.Sorted(s)
and s.Then(catena.Sorted).
What a fix promises
Section titled “What a fix promises”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 andTopN(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 tos.MinBy(sel): a stable sort puts the earliest of several equal keys first, and that is the oneMinByreturns.s.SortedBy(sel).Last()is not reported at all — it is the latest of the equal keys, andMaxByreturns the earliest.Sorted(s).Last()is fixed toMax(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.catenatallyrecognises one shape and no others. AGroupBystored 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.
Silencing a report
Section titled “Silencing a report”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.
Versions
Section titled “Versions”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
— [email protected] 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.