9dfe4915

Refuse a second anonymous scalar in a positional constructor

A new rule, `HsNoAnonymousScalar`: a non-record constructor holds at most one
anonymous primitive.

## The gap

`HsNewtypeNotSynonym` refuses `type RuleName = Text`, so a domain scalar cannot
be a synonym. Nothing refuses the commoner shape, which is a domain scalar
written as a bare `Text`, `String` or `Int` in a constructor with no name of its
own at all. The guide asks for both halves:

> Wrap domain scalars (ids, names, hashes) in `newtype`s, not `type` synonyms,
> so they cannot be confused positionally.

The clause that matters is "confused positionally", and that is what the
existing rule does not reach.

## The principle

"Is this a domain scalar" is judgement and cannot be checked. What can be
checked is whether the scalar has a name, because Haskell gives a scalar
occurrence exactly three places to get one: the type names it, a record field
names it, or nothing names it. The third is a positional constructor field,
where a reader has nothing to go on but position. That is decidable from one
module's syntax.

## Why the budget is one and not zero

A relayed `String` from another library, a count that is a count and a literal
payload in a parsed AST are all genuinely primitive and travel anonymously on
purpose, and when a constructor carries one payload the constructor name is its
name: `AnswerUnusable ![Text]` and `UnknownRepoType !Text` need nothing. With
one anonymous primitive the constructor name names it. With two the constructor
name can name at most one of them, so the second has no name anywhere and
swapping the pair typechecks.

The same measurement says so from the other side. At a budget of zero this
repository has 26 anonymous primitives, 17 of them textual, and of those 17 two
are worth fixing and fifteen are error constructors carrying relayed prose.
Twelve percent precision at zero, against nothing to fix at one.

## Measured population

A throwaway extractor over `src/` and `app/` of nix-ci, smos, sydtest, hopinion,
centjes and intray, 2330 modules. It approximates the parse with regexes, so
these are the order of magnitude rather than the number the check prints.

Non-record constructors over the budget:

| | nix-ci | smos | sydtest | centjes | hopinion | intray |
|---|---|---|---|---|---|---|
| over budget | 14 | 5 | 12 | 8 | **0** | 0 |

Thirty-nine sites in 2330 modules, and none in this repository, so the rule
ships here with no cleanup at all. Hopinion scores zero for the reason the
budget exists: every error constructor here carries exactly one prose payload,
and the constructor name is its name.

The true positives are all one shape, a name beside a reason or a key beside a
value: `RenderErrorUTCTimeParseError !Text !Text !String`, `ReqKvPart !Text
!Text`, `CodebergDecodeError !String !String`, `PostErrorTooLong !Text !Int
!Int`. The false positives are one class, a pair that is genuinely a pair of the
same thing, and they are all numeric: `RGB !Int !Int !Int`, `SegNormal !Int64
!Int64`.

## Scope

- **Primitive**: `Text`, `String`, `ByteString`, `Char`, `Int`, `Integer`,
  `Word`, the sized variants, `Double`, `Float`, `Rational`, plus `Maybe`, `[]`
  and `NonEmpty` of any of them, which are as anonymous as what they wrap.
  `[Char]` falls out of that as the list of a primitive. Not `Path Abs File`,
  not a project type, not a tuple.
- **`Bool` does not count.** Two `Bool` fields in one constructor is
  `HsNoDomainBool`'s finding, and reporting the same line twice in two
  vocabularies is worse than reporting it once.
- **Record constructors are out of scope**, because their fields are named,
  which is half the point already.
- **Function signatures are out of scope for now.** The shape is identical and
  the argument is the same, but the population is three to six times larger and
  the precision is visibly worse, so it wants its own rule and its own row.

## The fixes, and why this is an `error`

Two, both always available: give at least all but one of the anonymous
primitives a `newtype`, which is what the guide asks for, or switch the
constructor to record syntax, which names them without adding a type. The second
is why the class is `error` rather than `warn`: a site that wants neither
carries an `[allow:HsNoAnonymousScalar]` saying why, and `RGB` saying "three
channels, and the type is the order" is a comment worth having.

## What is here

A check under `hopinion/src/Hopinion/Check/Hs/NoAnonymousScalar/`, split into
`Fact`, `Extract` and `Rule` like its two neighbours, an entry in
`Hopinion.Rule.Registry`, and a good and a bad resource with their goldens. The
rule leaves `plan/rules.md` by joining the paragraph of shipped rules, as a rule
that ships does.

All four feedback loops pass, including `nix flake check`, which runs hopinion
on hopinion: the new rule finds nothing in this repository.
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