Logo

dev-resources.site

for different kinds of informations.

Using ast-grep with a vue project

Published at
1/1/2025
Categories
Author
insidewhy
Categories
1 categories in total
open
Using ast-grep with a vue project

Motivation

The amazing ast-grep tool does not support vue and scss by default but can be configured to do so.

ast-grep is like a much more powerful version of ripgrep or grep when it comes to searching for patterns within code as it understands the structure of the code (via the library tree-sitter).

For example the following command:

ast-grep -p '<h2>$A</h2>'

Will show all <h2> tags in your html (and also vue files after applying the config from this post). Then the following could change all <h2> tags to <h3> tags interactively (each change will be shown and then y can be used to accept it or n to reject it):

ast-grep -p '<h2>$A</h2>' -r '<h3>$A</h3>' -i

-U can be used instead of -i to apply the changes without asking or neither can be used to view all the changes as a patch.

ast-grep also has some overlap with eslint, rules can be stored within the project along with fixes and these can be tested/fixed using ast-grep scan and ast-grep scan -U.

Configuration

All of the following commands should be run from the root directory of your vue project.

First create a "rules" directory and add it to git. This directory must exist for the config to be accepted so for now just create an empty directory. Rules may also use utilities so that common config can be shared across rules, so we're going to create both directories with this config:

mkdir ast/{rules,utils}
touch ast/{rules,utils}/.keep-dir
git add ast/{rules,utils}/.keep-dir

Then create a file sgconfig.yml:

ruleDirs:
  - ast/rules
utilDirs:
  - ast/utils

customLanguages:
  vue:
    libraryPath: .tree-sitter/vue.so
    extensions: [vue]
    expandoChar: $
  scss:
    libraryPath: .tree-sitter/scss.so
    extensions: [scss]
    expandoChar: $

languageInjections:
  - hostLanguage: vue
    rule:
      pattern: <template>$CONTENT</template>
    injected: html
  - hostLanguage: vue
    rule:
      pattern: <script $$$ lang="ts" $$$>$CONTENT</script>
    injected: typescript
  - hostLanguage: vue
    rule:
      pattern: <style $$$ lang="scss" $$$>$CONTENT</style>
    injected: scss

Now a script is needed to create the libraries that this ast-grep configuration needs to understand vue and scss files. This could be created at scripts/init-ast-grep-config.sh:

#!/usr/bin/env bash

outdir=$PWD/.tree-sitter/
mkdir $outdir

cd /tmp
git clone https://github.com/tree-sitter-grammars/tree-sitter-vue
cd tree-sitter-vue
pnpm dlx tree-sitter-cli build
cp vue.so $outdir/
cd ..
rm -rf tree-sitter-vue

git clone https://github.com/serenadeai/tree-sitter-scss
cd tree-sitter-scss
pnpm dlx tree-sitter-cli build
cp scss.so $outdir/
cd ..
rm -rf tree-sitter-scss

Then ensure this script is executable:

chmod a+x scripts/init-ast-grep-config.sh

This script stores files in the directory .tree-sitter within the project, these libraries are binaries so they are unique to the platform on which they were compiled/installed so it's a good idea to add the .tree-sitter directory to .gitignore.

Next add a reference to this script to the scripts section of package.json:

{
  "scripts": {
    "init-ast-grep-config": "./scripts/init-ast-grep-config.sh"
  }
}

Now document this in your readme.md, commit everything to git, and developers on the project can begin using ast-grep on your vue project after running pnpm run init-ast-grep-config.

Add some rules

Now that ast-grep is configured I'll show how easy it is to create a rule to enforce code practices. vue allows defineEmits to be written in a type safe way:

defineEmits<{
  'update:value': [string]
}>

Or an unsafe way:

defineEmits(['update:value'])

If using typescript then there's not much reason to use the type unsafe version and this can be enforced with a five line rule in the project (to do the same with eslint would involve a lot more work). Create the following yaml file at ast/rules/type-unsafe-define-emits.yaml:

id: type-unsafe-define-emits
language: typescript
rule:
  pattern: defineEmits([$$$])
message: Use type safe version of defineEmits

Then run ast-grep scan and it will raise an error is the type unsafe version of defineEmits has been used.

Featured ones: