99c29f74

Two rules about what a test file exports and defines

Two module-level rules, one per commit, both about `*Spec.hs` files.

**`HsTestOneSpecPerFile`** — a test file exports exactly `(spec)`. Three shapes
are reported and each gets its own sentence: no export list at all, an empty
one, and one holding anything besides `spec` (the message names what it
exports). The finding is file-scoped, so the suppression the report offers is
the `[allow:file:...]` form the parser will accept.

**`TestNoTestHelpers`** — one finding per top-level value binding beside `spec`,
scoped to that declaration. A type signature is not one, since it cannot move on
its own and its binding is reported already. Neither is a type, a class or an
instance. Let-bindings are untouched.

## What they cost

`ModuleContext` carries two more facts. The declaration list was already read
for comment attachment and was simply not exposed. The export list is new:
`Hopinion.Facts.Export`, twenty-five lines of fact and thirty of reading, and
documentation in an export list is not an entry, since a section heading exports
nothing. Neither rule walks the module itself, which is the shape M5 wants.

What a test file is gets asked in one place, `moduleContextIsSpecFile`, rather
than once per rule.

## Measurement

Zero findings for the export rule against this repository. Sixty-one for the
helper rule, all of them in this repository's own test suite, and all of them
fixed rather than suppressed:

- helpers that hide what a test asserts became let-bindings in the spec that
  uses them, across twelve spec files;
- helpers that build a `Spec` became functions in a new `Hopinion.TestUtils`:
  `ruleSpec`, `moduleScenario`, `projectScenario`, `projectScenarios`,
  `ownedSpec`, and the IO helpers only they use.

That second kind is most of `RuleSpec`, which is now four meta-properties and
one `mapM_`, and it is the fix the rule's own text names, so a repository
adopting the rule is told where such a helper goes rather than only that it may
not stay.

## Checking

`nix flake check` passes on each commit separately, not only on the branch tip.
e2e-added-rule-is-rune2e-artifacts-are-reade2e-choices-are-found-beside-the-repositorye2e-cleane2e-dirty-package-is-judged-failinge2e-dirty-project-is-judged-failinge2e-dirty-report-is-readablee2e-disabled-rule-is-not-rune2e-incomplete-artifacts-are-a-failuree2e-judging-nothing-failse2e-named-packages-are-every-packagee2e-no-packages-is-judged-failinge2e-no-store-paths-in-findingse2e-paths-are-repository-relativee2e-withheld-facts-faile2e-wrong-source-mapping-is-a-complainthlint-checkhopinionpre-commitreleaseshellweeder-checkconfiguredevShelldefaultx86_64-linux