I'm looking into using ast-grep fo linting ReScript. What rules should I investigate?

I’ve used a tool called ast-grep to write some custom lint rules and code transformations for TypeScript.

It has experimental support for custom languages via tree-sitter: Custom Language Support | ast-grep

There is an official tree-sitter-rescript repo: GitHub - rescript-lang/tree-sitter-rescript: ReScript parser for Tree-Sitter

I’m poking at this over the week.

What types of rules would ReScript need?

I’m planning on starting with a simple no-console rule.

2 Likes

rules of hooks would be nice

1 Like

I came across this: GitHub - plow-technologies/rescript-linter: Lint your ReScript code!

I might try using that as a base instead of ast-grep

1 Like

@cristianoc i’m not super familiar with setting up tree-sitter and it looks like you worked on the repo for Rescript.

How do I get things set up so I can run tree-sitter parse src/foo.res?

It’s more the work of @nkrkv @aspeddro and @Emilios1995 (not sure if he has a forum account).

On my Linux machine, I got the rescript parser running through the CLI by:

  • putting the json file below (with my actual username in the paths) at ~/.config/tree-sitter/config.json
  • cloning the tree-sitter-rescript repo so that the project root was at ~/tree-sitter/tree-sitter-rescript
  • running tree-sitter build at the root of said repo
{
  "parser-directories": [
    "/home/<user>/github",
    "/home/<user>/src",
    "/home/<user>/source",
    "/home/<user>/projects",
    "/home/<user>/dev",
    "/home/<user>/git",
    "/home/<user>/tree-sitter"
  ],
  "theme": {
    "module": 136,
    "string.special": 30,
    "embedded": null,
    "variable": 252,
    "variable.builtin": {
      "color": 252,
      "bold": true
    },
    "variable.parameter": {
      "color": 252,
      "underline": true
    },
    "property": 124,
    "constant": 94,
    "number": {
      "bold": true,
      "color": 94
    },
    "constant.builtin": {
      "color": 94,
      "bold": true
    },
    "function.builtin": {
      "color": 26,
      "bold": true
    },
    "property.builtin": {
      "bold": true,
      "color": 124
    },
    "type.builtin": {
      "bold": true,
      "color": 23
    },
    "punctuation": 239,
    "string": 28,
    "constructor": 136,
    "type": 23,
    "punctuation.delimiter": 239,
    "tag": 18,
    "attribute": {
      "color": 124,
      "italic": true
    },
    "comment": {
      "color": 245,
      "italic": true
    },
    "operator": {
      "bold": true,
      "color": 239
    },
    "punctuation.bracket": 239,
    "keyword": 56,
    "punctuation.special": 239,
    "function": 26
  }
}

(The other directories were in the default config; they might work out of the box.)

I’m getting No language found after following those steps and trying to run tree-sitter parse src/foo.res.

Ah, missed a step. Had to run tree-sitter build in the tree-sitter-rescript repo.

I’m still getting No language found.

Can try running it on my work computer tomorrow to see through the fog of “it works on my machine” if we’re still stumped by then.

Thanks for the help!

Aha! For whatever reason, on my linux machine, it could auto-migrate to the latest version of tree-sitter. On my mac, I was left without that, but managed to add it manually to a fork of the current tree-sitter repo.

Full install instructions for tree-sitter are now:

  • Install tree-sitter (in my case via brew)
> brew install tree-sitter
  • Initialize the tree-sitter config, which, in my case, spit out a file at ~/.config/tree-sitter/config.json
> tree-sitter init-config
  • Add the directory where you’d like to place the repo to the parser-directories in that config file.
  • cd into said path
  • Clone and cd into the tree-sitter-rescript repo (currently my fork has the needed files).
> git clone https://github.com/cwstra/tree-sitter-rescript
> cd tree-sitter-rescript
  • Compile the parser file.
> tree-sitter build
  • Profit!
> tree-sitter dump-languages
(Should output promising information)
> tree-sitter parse test/highlight/functions.res
(Should output an ast)
1 Like

Another aha! Turns out that linux could successfully build the rescript repository because it was a version behind on tree-sitter (v24 instead of v25), which is also the latest version that ast-grep currently supports. Using npm to download the downgraded version of tree-sitter will let us generate a file usable by ast-grep.

npm install -g tree-sitter-cli@v0.24.7
cd desired/parent/directory/for/repo
git clone https://github.com/rescript-lang/tree-sitter-rescript
cd tree-sitter-rescript
tree-sitter build

Thanks for helping dig into this!

I realized I had made a mistake in my top level config and didn’t have the path set correctly for where I cloned the repo, and I also needed to downgrade to 0.24.

I think I might be able to start making progress now!

(source_file [0, 0] - [0, 32]
  (let_declaration [0, 0] - [0, 32]
    (let_binding [0, 4] - [0, 32]
      pattern: (value_identifier [0, 4] - [0, 5])
      body: (function [0, 8] - [0, 32]
        parameters: (formal_parameters [0, 8] - [0, 10])
        body: (call_expression [0, 14] - [0, 32]
          function: (value_identifier_path [0, 14] - [0, 25]
            (module_identifier [0, 14] - [0, 21])
            (value_identifier [0, 22] - [0, 25]))
          arguments: (arguments [0, 25] - [0, 32]
            (string [0, 26] - [0, 31]
              (string_fragment [0, 27] - [0, 30]))))))))

Not a bad start!

no-console.yaml - ast-grep-rescript WSL_ Ubuntu-22.04 - Visual Studio Code 2_27_2025 1_09_19 PM

image

image

ast-grep has a VSCode plugin, so there are already editor highlights in place.

id: no-console
language: rescript
severity: warning
message: Unexpected console statement.
note: |
  Remove the console statement.
rule:
  kind: module_identifier
  regex: Console

Part of me wants to just really dig into complex rules like the rules of hooks, but the other part of me want’s to see how far I can go with making this portable and configurable.

1 Like

I might have been nerd-sniped by this the last couple of nights, and cooked up an initial draft of rules of hooks. Combining that with the findings from today, I’ve made this repo that should demonstrate a project setup reasonably well. In theory, should only need to install ast-grep globally, install the repo’s dependencies, then run the update-tree-sitter-binary script to fill in the custom language’s binary.

1 Like

Might also be able to make a package to distribute the rules jsons, with a script to register itself in the sgconfig.yml.

Oh wow, very nice!

I think it should be possible to publish the compiled tree sitter Grammer in a package with a setup script to configure ast-grep.

Agreed. Might look into it this evening, unless you get to it first. :wink:

I have a portable version working-ish in this repo: GitHub - jderochervlk/ast-grep-rescript

The ./linter folder is setup as a local package, which once I get the bugs sorted out I can publish to npm.

The rescript-lint.mjs file allows you to toggle rules on and off and set warning or error for each one. The way ast-grep works is that it reads the configs for each rules from a folder, so I just copy the rules you have set into that folder. I’ll need a way to clean these up, and it would be great to have the top level config be a rescript-lint.res file with some types.

The .vscode/settings.json file sets the ast grep config path to be in the linter folder, so vscode works as expected. The part that I am trying to sort out is how to get ast-grep to run from the bin commands in linter.

The setup script can probably also make sure you have the astGrep path set correctly in the vscode settings.

The workflow could be install this package, configure some rules, and run the init command, have the ast-grep plugin installed, and tada!

I wonder if it would be possible to create a fork of the ast-grep vscode plugin that has the config path set already?

2 Likes