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` |