Skip to content

docs(cli): collect create-sg-rule's authoring traps into one Quirks section - #488

Merged
theCodeDrift merged 1 commit into
mainfrom
docs/sg-rule-quirks
Oct 12, 2026
Merged

theCodeDrift merged 1 commit into
mainfrom
docs/sg-rule-quirks

Conversation

@theCodeDrift

Copy link
Copy Markdown
Member

create-sg-rule had its authoring traps in four places (step 4's language: and Markdown notes, step 5's separator trap, and "Two ways a relational rule matches nothing"). This pulls them into one Quirks section and adds the two tree-sitter traps from #485.

Structure

  • Quirks opens with the two failure kinds: a config error (exit 8, aborts every rule's report) or silent (exit 0, reads like a clean codebase). A table follows, listing every quirk with its symptom.
  • Each entry gives the symptom, a repro, and the workaround.
  • Step 4 keeps the language: list and points to Quirks for the spelling traps. Step 5 becomes "check the rule against Quirks", so the check keeps its place in the step order. Step 6 asks for an awaited generic call among the invalid: cases when the rule keys on a callee.
  • Topic v5 → v6.

New entries (measured on ast-grep 0.45.3)

  • A keyword is not a valid kind. kind: readonly exits 8 with Kind `readonly` is invalid. --debug-query=ast prints named nodes only, so it shows which names are usable kinds; the CST dump does not distinguish them. Workaround: match the named parent (kind: required_parameter + regex: "^readonly\\b").
  • An awaited generic call hides its callee. await rpc<Foo>("x") parses as call_expression(function: await_expression). Also true for !, void and typeof (via unary_expression), and in Tsx; yield is unaffected. Workaround: an any branch on await_expression. Measured: that match spans the await, so a fix: rewrites it too, and the recipe says so.

Tests

  • ast-grep-vendor-contract.test.ts pins both quirks and both workarounds against the binary, so a grammar bump that fixes either one fails the suite.
  • agent-extensions.test.ts pins that the recipe names them.

Patch changeset: .changeset/sg-rule-quirks.md.

Fixes #485

…ection

The recipe had its traps in four places: the `language:` traps and the
Markdown note in step 4, the separator trap as step 5, and "Two ways a
relational rule matches nothing" after the steps. An agent whose rule matches
less than it should had no one place to look.

They now live under one Quirks section. It opens with the two ways a quirk
fails (a config error that aborts the scan, or a silent zero) and a table of
every entry with its symptom. Each entry gives the symptom, a repro, and the
workaround. Step 5 becomes "check the rule against Quirks", so the check
keeps its place in the step order.

Two new entries, both measured against ast-grep 0.45.3:

- A keyword is not a valid `kind`. `--debug-query=cst` prints `readonly` as
  a node, but `kind: readonly` exits 8 with "Kind `readonly` is invalid":
  `kind:` takes named nodes only. `--debug-query=ast` prints named nodes
  only, which is the reliable way to tell them apart.
- An awaited generic call hides its callee. `await rpc<Foo>("x")` parses as
  a call of the await expression, so a callee-keyed rule silently skips it.
  `!`, `void` and `typeof` do the same through `unary_expression`; `yield`
  does not; Tsx matches TypeScript. The workaround's match spans the
  `await`, so a `fix:` rewrites it too, measured.

The vendor contract pins both against the binary so a grammar bump that
fixes either goes red, and agent-extensions pins the recipe text.

Fixes #485
@theCodeDrift
theCodeDrift merged commit 7f66001 into main Oct 12, 2026
8 checks passed
@theCodeDrift
theCodeDrift deleted the docs/sg-rule-quirks branch October 12, 2026 18:31
theCodeDrift added a commit that referenced this pull request Oct 12, 2026
A post-merge review of #488 found the recipe's own workarounds reintroduced
the silent failures the section warns about. All measured on ast-grep 0.45.3.

- The `readonly` workaround (`^readonly\b` on `required_parameter`) matched
  1 of 4 real shapes. It missed `private readonly x` and a decorated
  parameter, whose text starts before the keyword, and `readonly x?`, which
  is an `optional_parameter`. The new regex covers all four and keeps out a
  `readonly` type and a parameter named `readonly`.
- The awaited-call workaround and the repro both used a `regex` with no
  sibling `kind`, which `verify` refuses (`sg-regex-needs-kind`). Both now
  carry `kind: identifier`. The recipe also shows the method form: under
  `await`, `client.rpc<Foo>()` wraps a `member_expression`, and the
  identifier branch found 0 of 2.
- Step 5 claimed every quirk "verifies clean", but `verify` does report the
  `language:` spelling and a TypeScript rule whose `files:` names `.tsx`.
  The table gains a column saying which quirks `verify` reports.

The vendor contract now pins the claims it previously took on trust: Tsx
parses awaited generics the same way, `!`/`void`/`typeof` wrap the callee in
`unary_expression` and `yield` does not, and both workarounds match exactly
what the recipe says. The header reads "measured at" rather than "added at",
since a bump did not introduce these. `typescriptMatches` and `arityMatches`
were the third copy of the same parse chain; both now go through
`grammarMatches`. The topic moves to v7, because v6 already shipped in the
nightly, and the pending changeset says so.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

create-sg-rule: collect authoring quirks into one section, and add two tree-sitter traps

1 participant