An opinionated set of ast-grep rules for Rust, TypeScript, Svelte, HTML, Python, Go, Pi extensions, and OpenCode plugins.
The rules catch API design problems, weak error handling, deprecated Svelte patterns, and code clutter. Pre-commit users can choose rules by language or run the whole set.
Install prek (recommended for faster installs and runs) or pre-commit. Both use the same .pre-commit-config.yaml.
Choose all to run every rule:
repos:
- repo: https://github.com/joshuadavidthomas/ast-grep-rules
rev: main
hooks:
- id: allSet rev to a release tag when you want a fixed version. To select rule groups instead, list only the hooks the project needs:
repos:
- repo: https://github.com/joshuadavidthomas/ast-grep-rules
rev: main
hooks:
- id: pi
- id: opencode| Hook | Files checked | Rules selected |
|---|---|---|
all |
All text files | Every rule |
rust |
Rust | IDs starting with rust- |
typescript |
TypeScript and Svelte script blocks | IDs starting with typescript- |
pi |
TypeScript Pi extensions | IDs starting with pi- |
opencode |
TypeScript and TSX OpenCode plugins | IDs starting with opencode- |
svelte |
Svelte | IDs starting with svelte- |
html |
HTML | IDs starting with html- |
python |
Python | IDs starting with python- |
go |
Go | IDs starting with go- |
Language hooks include the shared checks for that language because shared rule IDs carry the same language prefix. For example, rust includes rust-no-code-barricade. Pi and OpenCode use product prefixes so each plugin ecosystem can run only its own rules.
With prek, install the Git hook and prepare its environment up front:
prek install --prepare-hooks
prek run all --all-filesThe equivalent pre-commit commands are:
pre-commit install --install-hooks
pre-commit run all --all-filesPreparing the hook downloads an isolated Node.js environment, ast-grep, and the Svelte parser. Later runs reuse that environment. The project does not need Node.js or an sgconfig.yml.
Both tools scan tracked files. Add a new file to version control before expecting --all-files to include it. Rules with warning severity print findings without failing the hook; error rules fail it.
Pass ast-grep's --off option once for each rule:
repos:
- repo: https://github.com/joshuadavidthomas/ast-grep-rules
rev: main
hooks:
- id: rust
args:
- --off=rust-no-single-field-struct
- --off=rust-no-visible-bool-argumentInstall the tagged Git package in a project that uses Node.js 22, 24, or 26:
npm install --save-dev "github:joshuadavidthomas/ast-grep-rules#v0.6.0"
npx ast-grep-rules .The command uses its packaged rules and configuration. Pass any ast-grep scan options after the command.
Install a maintained Node.js release (22, 24, or 26) and just, then install the pinned ast-grep CLI and Svelte parser:
npm installScan a project:
just scan /path/to/projectjust scan also reports blanket suppressions and stale suppression comments. To run the rule tests:
just testInstall uv only if you need to audit the GitHub Actions workflows with Zizmor:
just lintAll rules link to their definitions. Most definitions include a note with exceptions and replacement advice.
| Rule | Language | Severity | Reports |
|---|---|---|---|
rust-no-code-barricade |
Rust | warning | Decorative divider comments made from repeated punctuation. Use a plain section comment or split the file. |
typescript-no-code-barricade |
TypeScript | warning | Decorative divider comments made from repeated punctuation. Use a plain section comment or split the file. |
html-no-code-barricade |
HTML | warning | Decorative divider comments made from repeated punctuation. Use a plain section comment or split the file. |
svelte-no-code-barricade |
Svelte | warning | Decorative divider comments in Svelte templates. Use a plain section comment or split the file. |
python-no-code-barricade |
Python | warning | Decorative divider comments made from repeated punctuation. Use a plain section comment or split the file. |
go-no-code-barricade |
Go | warning | Decorative divider comments made from repeated punctuation. Use a plain section comment or split the file. |
| Rule | Severity | Reports |
|---|---|---|
rust-no-anyhow-in-public-api |
warning | Visible functions, trait methods, and type aliases that expose anyhow. Return an error type callers can inspect. |
rust-no-deny-warnings |
warning | #![deny(warnings)], which can break a build when a compiler or dependency adds a warning. Set explicit lint levels instead. |
rust-no-deref-polymorphism |
warning | Deref implementations used for API forwarding. Reserve Deref for smart pointers and expose methods on other types. |
rust-no-dropped-error-source |
warning | map_err(|_| Error::Variant) and .ok().ok_or(...) that discard the original error. Carry the source in the new error instead. |
rust-no-empty-braced-struct |
warning | Empty braced structs. Use a unit struct unless braces carry a schema, FFI, or code-generation contract. |
rust-no-indexed-array-rebuild |
warning | [bytes[0], bytes[1], ...] array literals that index the same slice element by element. Take the chunk with split_first_chunk or try_into instead. |
rust-no-option-bool-field |
warning | Option<bool> struct fields. Name the three states with an enum. |
rust-no-panicking-fallback |
error | unwrap_or_else and map_or_else fallbacks that end in a panic or another diverging macro. Handle the failed or absent value instead. Skips test files and #[cfg(test)] items. |
rust-no-panicking-let-else |
error | let-else branches that end in a panic or another diverging macro. Return, break, continue, or model the failed pattern. Skips test files and #[cfg(test)] items. |
rust-no-panicking-match-arm |
error | Match arms that end in a panic or another diverging macro. Return, propagate, or give the arm a real value. Skips test files and #[cfg(test)] items. |
rust-no-public-struct-fields |
warning | Visible fields on visible structs. Keep representation private unless the struct is passive data. |
rust-no-public-tuple-newtype-field |
warning | Visible tuple-newtype fields. Keep the field private and expose chosen constructors or accessors. |
rust-no-single-field-struct |
warning | Named-field structs with one field. Use a tuple newtype unless an external schema or FFI needs the field name. |
rust-no-single-member-enum |
warning | Enums with one variant, except #[non_exhaustive] enums. Use a struct or newtype, or add the missing variants. |
rust-no-string-error-variant |
warning | Bare String payloads in variants of enums named Error or ending in Error. Keep structured fields or source errors. |
rust-no-trivial-whitespace-helper |
error | Local helpers that only wrap standard whitespace checks. Inline the check or name the domain policy it implements. |
rust-no-visible-bool-argument |
warning | bool parameters in visible functions and trait methods. Use a named enum or split the operation. |
rust-require-reasoned-clippy-suppression |
warning | Clippy allow and expect attributes without a reason. Record why the local exception should remain. |
rust-require-thiserror-error-enum |
warning | Error enums without a thiserror::Error derive. Derive it instead of hand-writing error behavior. |
| Rule | Severity | Reports |
|---|---|---|
typescript-no-chained-type-assertions |
error | Nested as or angle-bracket assertions, except chains made only of const. Keep the original type or parse unknown input at its boundary. |
typescript-no-conditional-empty-object-spread |
error | Object spreads that use {} on one side of a ternary to omit a field. Write the property or build the object in later statements. |
typescript-no-generic-error-message-helper |
error | Helpers that reduce an unknown error to error.message or String(error). Preserve the original error and write separate user-facing text. |
typescript-no-hand-rolled-object-type-guard |
error | Object type predicates over unknown input that rely on typeof value === "object". Parse the expected shape with a schema validator. |
typescript-no-known-value-widening |
warning | A written literal, array, object, or function given an explicit unknown, object, Record, or anonymous object type. Keep inference, use satisfies, or name the owner type. |
typescript-no-object-parameters |
error | Function parameters typed as object. Use the owner type or parse the input at its boundary. |
typescript-no-record-string-unknown |
error | Dictionary types whose value is unknown, any, object, or {}, including index signatures and Readonly/NonNullable wrappers. Declare the actual value type, or keep the input unknown and parse it at the boundary. |
typescript-no-runtime-typeof |
error | Runtime typeof checks. Parse the value into a domain type at the I/O boundary. |
typescript-no-shape-in-symbol-names |
warning | The word shape in a TypeScript identifier. Name the owner type or the parsed domain value. |
typescript-no-shape-in-symbol-names-tsx |
warning | The same shape name check for TSX identifiers. |
typescript-no-unknown-parameters |
error | Parameters typed as unknown, except a parameter named cause. Give the input a named contract or parse it at the I/O boundary. |
typescript-no-unknown-type-aliases |
error | Type aliases that only rename unknown. Keep unknown visible or replace it with the parsed owner type. |
typescript-no-widen-then-assert |
warning | A local const widened to unknown, any, object, or Record<..., unknown> and later asserted to a narrower type. Keep the precise type, or parse unknown input once at the boundary. |
| Rule | Severity | Reports |
|---|---|---|
pi-extension-no-console-log |
error | console.log in a typed Pi extension entry point. Use ctx.ui, tool result rendering, or a message or entry renderer. |
| Rule | Severity | Reports |
|---|---|---|
opencode-plugin-no-console-log |
error | console.log in TypeScript modules that import OpenCode's plugin API. Use TUI feedback, structured server logging, or a tool result. |
opencode-plugin-no-console-log-tsx |
error | The same OpenCode plugin check for TSX modules. |
| Rule | Severity | Reports |
|---|---|---|
svelte-no-create-event-dispatcher |
warning | createEventDispatcher imports and direct calls. Svelte 5 components should declare and call callback props. |
The wrapper registers tree-sitter-svelte as an ast-grep custom language, so Svelte rules can match component and template nodes such as {#if} and {#each} blocks. It injects every <script> body as TypeScript so script rules also work with plain and lang="ts" blocks.
The parser installs without a compiler on glibc Linux x64, macOS x64 and arm64, and Windows x64. Linux arm64 and Windows arm64 or 32-bit x86 build the parser from source and need Python plus a native compiler. The pinned ast-grep CLI does not support other targets such as musl Linux.
- Add the rule under
rules/<prefix>-<rule-id>.yml. - Add valid and invalid examples under
tests/<prefix>-<rule-id>-test.yml. - Run
just test. - If the expected findings changed, run
just test --update-alland review the snapshots intests/__snapshots__/. - Add or update the rule in the reference above.
Use common- for files that contain rules for several languages and a language prefix such as rust- for files tied to one language. Start every rule ID with its language, such as rust-no-deny-warnings. Keep rule IDs stable, descriptive, and kebab-cased.
rules/contains the rule definitions.tests/contains valid and invalid examples, generated snapshots, and a real.sveltescan test.sgconfig.ymldefines the rules, tests, custom Svelte language, and script injection.bin/ast-grep-rules.mjsresolves the native parser, renders the config paths, and runs the pinned ast-grep CLI..agents/skills/ast-grep/contains the local agent guide for writing and debugging rules.
ast-grep-rules is licensed under the MIT license. See LICENSE for details.