4c64d9fc

By: Tom Sydney Kerckhove <syd@cs-syd.eu>

Check a repository against one rule

hopinion reads every module of every cabal component and reports what the rules
have to say about it. One rule ships, HsNoCustomShowRead from
haskell-style.md, so that the machinery is exercised by something real rather
than by a demonstration.

The shape it is built on:

A rule says there is something to fix or there is not. There is no third
answer, because a checker whose silence can mean "I could not tell" is a
checker whose silence means nothing. 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.

A rule is a value in a registry rather than 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 a list, plus a directory of
resources that becomes a golden test per file with no test code to write.

Errors are types with renderers, not prose in a Left. RuleSetError says which
of the three ways a list of rules fails to be a set, ReportError tells bytes
that are not JSON from JSON that is not a report, DiscoveryError says what
stopped a repository from being read, and Failure is what a report carries when
the tool could not tell. Rendering happens once per error, at the boundary, so
the wording lives in one place and a caller matches on what happened rather
than on a sentence.

Paths are path and path-io throughout. Nothing in the library takes a FilePath:
a cabal file's main-is becomes a Path Rel File where it is read, the repository
root arrives from the command line as a SomeBase Dir because which of the two
it is is all a parser can know, and the working directory turns it absolute at
the one place that does IO. ParseInput lost a field to this: it carried the
module's path twice, once as a name for GHC's messages and once as a RelPath,
and only the second was ever a path.

There is no set of the shipped rules here. Building one can fail, and for a
hand-written list it can only fail on something a test asserts rather than
something a run could hit, so a value carrying that failure would hand every
caller a branch it cannot act on. RuleSpec asserts the invariant instead, and
asserts that the set the rest of the suite runs with is the rules that ship, so
a broken list cannot show up as a suite passing over no rules.

Nothing here is built for a rule that does not exist yet. One level, no fact
store, no cross-process split, and nothing reads what the compiler wrote down,
because one module-level rule needs none of them. The build is what holds that
line: -Wunused-packages, -Wunused-top-binds and weeder run as errors, and they
are what removed the last of the machinery this rule turned out not to need.

It cannot be adopted yet, and the rule it ships is the reason: haskell-style.md
allows a written Show for data that is secret, and there is no way to say so.
Suppressions come next, with the rule that makes them unavoidable.
hlint-checkpre-commitreleaseshellweeder-checkconfiguredevShelldefaultx86_64-linux