Plugins · CPO
Code rules
Rules over syntax trees and configuration keys: tree-sitter queries, ast-grep patterns and the config check.
These checks come with the ccpp CLI and work on file.write. The grammar is chosen from the path.
tree-sitter
---
description: No any in TypeScript
on: [file.write]
when:
tree-sitter:
query: '((predefined_type) @any (#eq? @any "any"))'
language: [typescript, tsx] # optional
onlyNew: true # optional: only what the write introduces
---
No `any` in {{ file.relative }} at {{ facts.line }}:{{ facts.column }} ({{ facts.locations }}).A query in tree-sitter's S-expression syntax, predicates included (#eq?, #match?, #any-of?…). A finding is
reported at the @match capture when the query names one, else at the match's first capture. The check holds
when a finding remains.
ast-grep
when: { ast-grep: { language: TypeScript, rule: { pattern: "ipcRenderer.$M($$$)" } } }{ language, rule }, the rule object of ast-grep YAML (pattern, kind, has, inside…), with the same
onlyNew / ranges options.
Builtins
ts-no-any (onlyNew defaults to true) and comments, whose facts.comments lists the comments for where.
Before and after
onlyNew: truecompares findings by fingerprint: pattern, node type, captured texts and enclosing named definitions (class_declaration:A/method_definition:m). Line shifts and edits elsewhere keep a finding; a secondanyin the same function is new.ranges: changedkeeps findings that touch the edited text: the file is re-parsed incrementally and compared with tree-sitter's changed ranges.
Languages
language lists the grammars a rule is written for. Files of other languages are not concerned. Without
language, a file with no grammar skips, and so does content unknown before the write (a shell redirection,
checked again once the command ran).
An HTML page, and a .vue, .svelte or .astro component, is also checked on the code it embeds when
language lists that code's grammar: <script>, <style>, style="", on* handlers and javascript: URLs.
embedded: false opts out.
ccpp cpo lint compiles every query. Syntax errors, unknown node types or fields and unknown predicates (which
tree-sitter would silently ignore, matching more) make the policy invalid, with the position in the query.
Facts
language, matches and introduced (findings with line, column, endLine, endColumn, type, text,
captures, scope, one-based), count, the line / column / text of the first introduced finding, and
locations ("2:10, 3:15").
Configuration files: config
on: [file.write]
when:
config: { key: "services.*.privileged", equals: true } # equals, in, matches, type, missing, onlyNewOne key model for json (comments allowed), json5, yaml, toml, xml, ini, properties, dotenv and
hcl. Every key, containers included, is an entry with its value, type and position.
- Key patterns:
*one segment,**any number,[n]/[*]list indexes,*inside a segment (dev*). - YAML anchors, aliases and merges are resolved; TOML dates stay text; XML attributes are
@nameand text is#text; HCL blocks are keyed by type then labels. missing: trueholds when no entry satisfies the check; withonlyNew, only when the write removes the last one.
CEL functions
codeLanguage(path), parse(text, language), query(tree, source), parseConfig(text, language),
configGet(text, language, key), configFind(text, language, key) and readConfig(path).