ce70d9d7

Check a repository against one rule

Replaces #5, which GitHub will not let me reopen: I merged it by mistake and
then took it back off master, so the commits are no longer there.

Stacked on the merged #4.

`hopinion check .` reads every module of every cabal component and reports what
the rules have to say about it. One rule ships.

54 files, 3,961 lines. 103 tests.

## The invariants, each of which fails the run when broken

**A rule says there is something to fix, or there is not.** No third answer. A
checker whose silence can mean "I could not tell" is a checker whose silence
means nothing. So a module that did not parse is a failure of the run rather
than a module with nothing wrong in it, and a module whose source is a
preprocessor's input is counted as unread rather than passing quietly.

**A rule is a value, not an instance of a class.** No generic code is
polymorphic in a check type, so a record of functions is the right encoding.
Adding a rule is a module and a line in `Hopinion.Rule.Registry`, plus a
directory under `hopinion-gen/test_resources/Rule` named after it, which
`Hopinion.RuleSpec` turns into a golden test per file with no test code to
write.

**Facts come from the token stream and the parse tree, never from exact-print
annotations.** That placement answers "where must this be reprinted", not "what
is this about", and the two differ exactly where it would matter.

**Nothing is built for a rule that does not exist yet.** One level, no fact
store, no cross-process split, nothing reads what the compiler wrote down. The
build holds that line rather than my judgement: `-Wunused-packages`,
`-Wunused-top-binds` and weeder run as errors and removed the last of the
machinery this rule turned out not to need.

## The rule

`HsNoCustomShowRead`, from `haskell-style.md`: `Show` and `Read` are derived,
never written out. An `InstanceFact` whose class is one of those and whose
origin is a written instance declaration.

## This is deliberately not adoptable yet

It finds 22 real instances on nix-ci. All 22 are the secret-hiding `Show`
instances that `haskell-style.md` explicitly permits, and there is no way to
tell hopinion so, because there is no suppression mechanism yet.

That is the argument for the next PR rather than a gap I am hiding: the rule
the plan already annotates with "escape hatch needed for secret data" is the
one that makes suppressions unavoidable.

## Where to start reading

`Hopinion.Rule` for what a rule is, then
`Hopinion.Check.Hs.NoCustomShowRead` for what one looks like, then
`Hopinion.Run` for how they are run. `Hopinion.Facts` and its neighbours are
the vocabulary everything else is a function between.

## Review loop

```
nix flake check
nix develop --command cabal test hopinion-test --test-options="--ai-executor"
```

## What comes after

| PR | adds | brought in by |
|---|---|---|
| 3 | comment attachment, scopes, suppressions, `hopinion.yaml` | `CommentBareTodo` |
| 4 | the SQLite fact store, the package level | `HsGenValidInGenPackage` |
| 5 | project level, `.hie`/`.hi`, the cross-process split, `makeHopinionCheck` | `TestGenValidSpecPerGenValid` |

Suite timing

Time to Start Worker time Duration Time to finish Idle
Config 32s 1s 1s 34s 32s
Eval 39s 9s 9s 49s 5s
Build 48s 0s 2m29s 3m17s 2m28s
Suite 32s 10s 2m45s 3m17s 3m07s

Timeline

0s40s