Are there any linters for Rescript?

Hi!
I’ve been searching for a linter for Rescript, but I didn’t have any luck.

By a linter, I mean something like ESLint, Rust’s Clippy, or Elixir’s Credo.

Is there anything like this for Rescript?
Thanks!.

1 Like

Nope, it’s my biggest pain right now

1 Like

Curious, what type of things would people be interested in linting?

At the top of my head I can think of:

  • Common pitfalls.
  • Consistent variable names.
  • Suggest idiomatic patterns.

Dependency arrays in hooks. For example, this code does not work as expected, but compiles just fine (no warnings):

module Counter = {
  @react.component
  let make = () => {
    let (count, setCount) = React.useState(() => 0)
    let doubleCount = React.useMemo(() =>  count * 2)

    <button onClick={_ => setCount(old => old + 1)}>
      {doubleCount->React.int}
    </button>
  }
}

When running the equivalent code in JS, ESLint (via the react-hooks/exhaustive-deps rule) produces the following warning:

React Hook useMemo does nothing when called with only one argument. Did you forget to pass an array of dependencies?

If you pass an empty dependency array, then you get:

React Hook React.useMemo has a missing dependency: 'count'. Either include it or remove the dependency array.

I use ESLint to lint my hooks (caused a really hard-to-find bug a while ago.) Sadly a lot of other linters and React rules don’t work on compiled code (Tailwind’s a big one I’d like to use but can’t.)

To prevent usage of Js and Belt in favor of custom Lib.res that extends RescriptJs. Thinking to write a script that does the job with regular expressions, though.

It’s the correct behavior. The only problem is that it’s impossible to disable a eslint rule for a specific line.

Most of this is possible with ESlint, but it becomes more difficult/impossible when you work with runtime-free bindings, labeled arguments, or switch statements.

If we were to build some sort of linter, what would be the most important lints for ReScript?

2 Likes

I think a tool to work with ReScript AST is more important than linter, linter can be done on top of it. What I use the most in ESlint is the no-restricted-syntax that allows to create custom rules for any possible situation. I’d like to have the flexibility in ReScript too. And it’s difficult to name some specific rule, because most of them are to ensure common code-style, prevent wrong usage of APIs, recommend one module over another etc.

2 Likes

While there are no official tools as of right now, it is possible to work with the ReScript AST today if you’re interested. Here’s an example project that extracts intln things using the parser and various AST tools: https://github.com/cca-io/rescript-react-intl-extractor

There are also plenty of examples related to the AST, and leveraging the compiled type information, in the VSCode extension repo.

5 Likes

It’s this part of the VSCode extension that has lots of examples of working with the AST and the type system: rescript-vscode/analysis at master · rescript-lang/rescript-vscode · GitHub

It might be interesting mid to long term to put together a slightly more polished toolkit for building various tooling related to the AST/type system. But again, I just want to reiterate that this is all possible today, even if it’s unlikely to become significantly more accessible in the short to mid term.

While we’re on the subject, what would be really useful for these type of things is to build AST Explorer support for ReScript. Support exists for ReasonML (built by @jchavarri I believe), so one could probably piggy back a bit on that, even if it’ll be a bit of work getting it set up. Here’s a few things related to the ReasonML support:

6 Likes

I built a AST-based linter for Rescript at work. We have a discussion recently on open sourcing it. It’s still rough around the edges but it is working well for our use case for now.

I made a sample recording here.

We only have limited rules at this point and no support for adding rules through plugins yet. The linter is mostly used to check usage of module that we want to disallow, or enforce our own React component over some specified component, etc.

11 Likes

Looks great!
How does it traverse the nodes in AST? Does the linter have a built-in parser or using cmt?

It’s using (an older version of) the ReScript parser.

1 Like