Created Started Finished
Give every rule a directory, and every fact its own module
Two commits. The first gives every fact its own module; the second gives every
rule a directory, which is what the module layout was actually missing.
## A rule is a directory
`Rule.hs` is the check, and beside it goes whatever only that rule needs:
`Fact.hs` for a fact nothing else reads, `Extract.hs` for the reading of the
parse tree that produces it.
```
Hopinion/Check/Hs/NoSemigroupOnText/
Fact.hs ConcatChain, ConcatOperand 44 lines
Extract.hs concatChainsOf, runs, operands 104 lines
Rule.hs the check 30 lines
Hopinion/Check/Comment/BareTodo/Rule.hs
Hopinion/Check/Hs/NoCustomShowRead/Rule.hs
Hopinion/Check/Hs/NoFilePath/Rule.hs
Hopinion/Check/Package/GenValidInGenPackage/Rule.hs
Hopinion/Check/Project/GenValidSpecPerGenValid/Rule.hs
```
Five of the six rules need neither a fact nor an extraction and are the one
module, in a directory of their own so the shape is the same for all of them.
A rule's cost is now readable off the rule. `HsNoSemigroupOnText` is 178 lines,
and the 104 of parse-tree reading was invisible while it sat in
`Hopinion.Extract`. That number is what M5 has to stop a later expression rule
from paying again.
## What no rule owns
`Hopinion.Extract.Ghc`: a GHC span as a fact's span, the declaration a span
falls inside, and taking an expression apart far enough to see what it is
(`peelExpr`, `infixOperands`, `spineOf`). A rule's own extraction asks that for
the parts it shares, rather than importing `Hopinion.Extract`, which would have
made the two import each other. It is also where the walking M5 plans to
centralise belongs.
## Facts, one per module, no re-exports
`Hopinion.Facts` was one module holding fourteen types and re-exporting four
more, so nothing said which of them it needed. Each type now lives in the module
that gives it meaning, and no module re-exports another. This costs import
lines rather than saving them, and that is the trade: what each line says is now
true.
Three dependencies nobody could see, all now said outright:
- **`Hopinion.Compiled`** uses `CompiledModule` and `DeclaredInstance`, which
reached it through `Facts` re-exporting `Hopinion.Hie`. It imports `Hie`
directly now.
- **`GenValidSpecPerGenValid`** uses `declaredInstanceType` and
`declaredInstanceClass`, field accessors it only ever saw through two layers
of re-export. Same fix.
- **`validity-aeson`** was a dependency of the library only because `Facts`
imported `Data.Validity.Aeson ()` for orphan instances no `Validity` instance
here needs. The import alone satisfied `-Wunused-packages`; removing it took
the dependency with it.
`NameFact` is in `Facts.Occurrence`, not `Facts.Name` where a name-shaped thing
would seem to belong: it carries a `Span` and a `ScopeKey`, and `Facts.Place`
already imports `Facts.Name`, so that would have closed a cycle.
## The one edge left
`ModuleContext` still carries a field per module-level fact, so it imports the
fact of any rule that brings one. That is the only edge pointing from the middle
of the tool at a rule, and it is why the rule directory holds the fact rather
than the fact holding the rule.
No behaviour change: no type, field, constructor or function was renamed, added
or removed. `nix flake check` passes.