eaaad3ed

Say what a path is, and add the rule that holds us to it

Stacked on #14, which typed the paths this holds the repository to. Review that first; this diff is against it.

`HsNoFilePath`. Everything it reports is a module's own signature naming the type where `path`'s `Path` would say whether it holds a file or a directory and whether it is absolute or relative.

11 files, 154 lines. 181 tests, 3 of them new.

## Read off the tokens

Not the parse tree. The token stream is where a name the code wrote is already told apart from the same word elsewhere: a comment saying `-- a FilePath is a String`, the string literal of the same sentence, and the tail of `import System.FilePath` are each their own token. `good.hs` holds all three and its golden is empty.

That is also why this is not a grep.

## Every finding is suppressible where it is

Each one is scoped to the declaration it is inside, so the report offers a suppression for that declaration and nothing wider:

```
[error HsNoFilePath]: The type FilePath. Say what the path is with Path from path.
     ╭──▶ hopinion-gen/test/Hopinion/AnnotationSpec.hs@28:16-28:24
  28 │ resourceDir :: FilePath
     •                ┬───────
     │ Hint: To suppress, write this directly above line 28 of ... : -- [allow:HsNoFilePath] <reason>
```

Converting at the edge is not reported at all. A library that wants a `String` gets `readFile (toFilePath file)`, and that is correct.

## The fact it reads is new

Nothing recorded which capitalised names a module writes, so `NameFact` arrives with it: the `conid` pass in `Parse`, the field on `ModuleContext`, and the declaration lookup in `Extract` that says where an annotation about one would have to go. Three files, 74 lines. That is the whole of the machinery, and a name-occurrence fact is reusable by other rules in the catalogue.

## What it does not touch

The repository already passes, because #14 converted it. Nothing here changes existing code beyond the one line that registers the rule.

## What is deliberately not here

The same rule over `import System.FilePath` and `import System.Directory`. An import is not a declaration, so the comment attachment pass has nowhere to attach a suppression above one, and the only thing left is a file-scoped suppression, which would exempt every other finding in that file. A rule whose findings cannot be suppressed where they are breaks the rule #10 is built on: a suppression the report offers must be one the parser accepts. That wants an `AttachedToImport` first, and it is its own change.

`filepath` therefore survives in two places, both genuine gaps in `path`: cabal hands module names back as `[String]`, so `joinPath` is needed at that boundary, and turning a directory's name into a file's name has no typed equivalent. Those two are exactly where a dependency-based rule would want a per-site suppression.

One thing to settle: this cites `haskell-style.md`, "Preferred libraries", which does not currently say it. The guide wants the line, or the rule is citing something that is not there.

## What it costs elsewhere

57 findings in nix-ci, 47 in sydtest, 10 in centjes. None of them is the exception the rule stays quiet about, so all three are real conversions rather than suppressions to write.

nix-ci turns the rule off in a new `hopinion.yaml` until those 57 are converted, which is NorfairKing/nix-ci#527. That is a decision, not a workaround: 57 suppressions would reach a passing check dishonestly, because a suppression says this one is deliberate and none of these is.

## Review loop

```
nix flake check
nix develop --command cabal test hopinion-test --test-options="--ai-executor"
```
hlint-checkpre-commitreleaseshellweeder-checkconfiguredevShelldefaultx86_64-linux