2ed31869

Three rules: HsLambdaCase, HsAppOnlyMain, TestJsonSpecPerJsonType

Three rules, one commit each, from the easy end of `plan/rules.md`.

### `HsLambdaCase` (module level)

Both shapes the guide names: one equation that names its last argument and then
cases on it, and one equation per pattern with the function's name repeated in
each. hlint covers neither, so it stays a check of its own.

Reported at the function's name rather than its equations, because a
where-bound helper's equations reach to the end of its own where clause and so
contain the bindings written in it, and a finding drawn over another finding
reads as one thing to fix rather than as three.

Both shapes err towards silence: an equation that takes an earlier argument
apart, or names it differently from its neighbour, is doing something one
`\case` cannot say, and a last argument whose name is written anywhere else in
the equation is a name with a use.

**14 findings here**, all fixed in the same commit. 79 in nix-ci.

### `HsAppOnlyMain` (package level)

An executable's own source holds `main = theRealMain` and nothing else. Two
ways to break it: a declaration beside `main`, and a `main` that does the work
itself.

At the package level because the question is which component claims a module,
and a component is a package's answer rather than a module's. The declaration
list is what says what is there, so the only thing read out of the parse tree
is what `main`'s right-hand side does. A type signature is not counted: it
cannot be moved on its own and the binding it belongs to is reported already.

**0 findings here, 0 in nix-ci** — both write the one line already, which is
what puts it in M1 rather than M3.

### `TestJsonSpecPerJsonType` (project level)

A type with both a `ToJSON` and a `FromJSON` instance obliges `jsonSpec @T` in
the test suite of its package's gen package. Both classes rather than either,
because what `jsonSpec` asserts is a claim about the pair jointly.

That makes an obligation a *set* of classes, which makes this rule and
`TestGenValidSpecPerGenValid` the same engine, so the engine is now written
once in `Hopinion.Check.Obligation` and each rule is thirty lines of classes,
combinator and sentences. The rule id becomes a table column, so a row still
belongs to exactly one rule and no rule can read another's. That is the one
place a rule does not own its own table, and `plan/plan.md` now says so as the
family exception it is.

One behaviour change falls out of the class set: a finding is per type rather
than per instance, since one call meets the obligation of every instance of a
type.

**0 findings here** (the one suppression is persistent's generated `ToJSON`
/`FromJSON` for `Key`), 12 in nix-ci.

### Checks

`nix flake check` passes on each of the three commits.

Total

24s

Created Started Finished

Time to Start Worker time Duration Time to finish Idle
Config 37s 1s 1s 38s 37s
Eval 48s 13s 13s 1m01s 9s
Build 57s 0s 0s 57s 0s
Suite 37s 14s 24s 1m01s 46s

Timeline

Config Eval Build Compressed gap
0s50s1m