Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to debug custom lint rules #1612

Open
ulidtko opened this issue Aug 28, 2024 · 1 comment
Open

How to debug custom lint rules #1612

ulidtko opened this issue Aug 28, 2024 · 1 comment

Comments

@ulidtko
Copy link
Contributor

ulidtko commented Aug 28, 2024

This isn't a bug report, more like a documentation attempt. I've been repeatedly hitting speed-bumps when trying to introduce custom project-specific HLint rules; at least to me, rule writing feels harder than it should be. Perhaps some hints could help future me, or others.

I'm assuming a flow like the following.

  1. Spot a "popular" but flawed code pattern in a codebase.
  2. Write an improved replacement.
  3. Write a hlint rule, for the double-duty:
    • statically find the pattern in existing code for mass-change;
    • prevent popping in again in the future.
  4. Apply, build, test, commit... continue as usual.

Alas, something goes wrong at step 3. The new rule doesn't trigger, No hints. What do?

Conventional first steps

  • Skim the output of --help, discover and try --verbose & --show.

  • Consult the README, several times if impatient.

  • Minimize a self-contained small source snippet where your rule should trigger. There's no need for it to typecheck, it only has to parse as well-formed Haskell syntax. Test your rule on it. Experiment.

(That's generically-applicable troubleshooting advice; as such, I'd assume it's well-known anyway. However, I'd been wrong.)

HLint-specific gotchas

  • hlint considers only single-letter identifiers as rules' variables. Anything longer will match literally.

  • hlint has special interpretations for (), $, (.) and possibly other haskell syntax.

  • know thy yaml syntax, and its symbols. Try Dhall if unsure.

Renamed as imports

The AST that HLint works on, comes after GHC renamer (which is the chunk of compiler logic that resolves names). This means that your rules might need to refer to identifiers by fully-qualified original names.

Example
-- sample source file

import Control.Exception (handle)
import qualified AcmeProject.Logger as Log

main = handle onException $ do
  Log.debug "example"
  Log.info "OK"
  where
    onException (err :: SomeException)
      = Log.error "not ok" >> Log.exception err
# .hlint.yaml rules file

#-- won't trigger:
warn:
  lhs: Log.error a >> Log.exception b
  rhs: Log.exceptionCtx a b

#-- won't trigger either:
warn:
  lhs: error a >> exception b
  rhs: exceptionCtx a b

#-- this one does work!
warn:
  lhs: AcmeProject.Logger.error a >> AcmeProject.Logger.exception b
  rhs: AcmeProject.Logger.exceptionCtx a b
@zliu41
Copy link
Collaborator

zliu41 commented Dec 14, 2024

I agree better documentation is needed especially for scoping. Regarding your example, I think the second hint (with unqualified names) should trigger - it doesn't make much sense that it doesn't.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants