Skip to content

ast-grep

The sg engine matches against the structure of a single source file. Because it reads the syntax tree rather than the text, it catches a pattern regardless of formatting, variable names, or whitespace.

It makes a codebase’s structure checkable. Vale makes its contents checkable.

Anything one file can decide: a call, an import, a JSX attribute, a type annotation, a function shape.

Correlating two things inside one file is still sg. A useEffect whose dependency array omits a value used in its body compares two places, but both are in the same file, so a single-file engine can decide it. Reach for runtime only when the evidence genuinely leaves the file.

.taskless/rules/sg/no-eval/
no-eval.yml the rule
.tests/no-eval-YYYYMMDD-test.yml its test cases

The id is kebab-case and names both the directory and the file. An sg rule carries its own files and ignores, so unlike a Vale rule there is no second config file. The rule file is the whole rule.

The dot on .tests/ is required. See Engines for what breaks without it.

id: no-console-log
language: TypeScript
rule:
pattern: console.log($$$ARGS)
severity: warning
message: Avoid console.log in production code. Use a structured logger instead.
fix: logger.info($$$ARGS)
Field Purpose
id kebab-case, matching the directory and file name
language the target language, in ast-grep’s own spelling
rule the ast-grep rule object
severity error, warning, info, or hint
message a concise single-line explanation
note optional multi-line guidance, supports Markdown
fix optional auto-fix pattern
ignores optional file patterns to skip

For the pattern syntax itself, including meta-variables and the any/all/has/inside/not operators, see ast-grep and the upstream rule reference.

To suppress one finding in the scanned code, put ast-grep-ignore at the start of a comment on the line above it. As of ast-grep v0.45.3 the directive takes effect only as the comment’s first alphabetic text; a comment that merely mentions it as prose no longer suppresses anything, so a finding that was hidden that way reappears.

language is ast-grep’s vocabulary, not yours

Section titled “language is ast-grep’s vocabulary, not yours”

Supported Languages lists every spelling the pinned ast-grep binary accepts, generated from the binary’s own run -h output.

Copy a spelling from that list rather than typing one that looks right. Three traps in particular:

  • Do not copy the language from detect --json. It reports your repository’s languages in a different vocabulary. It says C++ where this list says Cpp.
  • Tsx and TypeScript are two parsers, not aliases. A rule over .tsx files that declares TypeScript does not merely miss JSX syntax, it does not read those files at all.
  • Markdown sees blocks only. ast-grep exposes the block grammar, so atx_heading, fenced_code_block, list_item, paragraph and section are real kinds, while everything inside a line is one opaque inline node. There is no link or emphasis kind. A rule about the words inside a line belongs to Vale, not here.

Test cases live in .tests/ and use ast-grep’s own valid and invalid keys, because that file is handed to sg test verbatim.

Where a rule has an arity boundary, put a case on each side of it. A pattern that starts at two arguments when it was meant to start at one passes a suite whose fixtures all have two.

Terminal window
npx @taskless/cli agent create-sg-rule

Then validate, both taking the rule’s directory:

Terminal window
npx @taskless/cli verify .taskless/rules/sg/no-console-log/
npx @taskless/cli test .taskless/rules/sg/no-console-log/

verify asks whether the rule is well formed: the YAML matches the ast-grep schema and every required field is present. It needs no test file, so run it the moment the rule exists. test then runs the cases.

The sg engine is available in the Taskless Open Source CLI and does not require a Taskless Cloud account.

sg rules are inert data. Running one executes nothing and makes no network call, so it works offline.