Skip to content

Commit 1667f76

Browse files
committed
test(cli): assert the specific cause for each refusal shape ast-grep reports
Only `kind: readonly` was asserted, so a reformat of the regex or pattern diagnostics could have degraded the message to the generic exit-code fallback without failing anything. The regex case is the only one that reaches the continuation-line fold; removing the fold fails it. Found in review of #493.
1 parent 9f730ee commit 1667f76

1 file changed

Lines changed: 40 additions & 2 deletions

File tree

‎packages/cli/test/sg-engine-parse.test.ts‎

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ afterEach(async () => {
4747
});
4848

4949
/** An sg rule with both fixture buckets, `body` written verbatim. */
50-
async function sgRule(id: string, body: string) {
50+
async function sgRule(id: string, body: string, language = "TypeScript") {
5151
const directory = join(cwd, ".taskless", "rules", "sg", id);
5252
await mkdir(join(directory, ".tests"), { recursive: true });
5353
await writeFile(
5454
join(directory, `${id}.yml`),
55-
`id: ${id}\nlanguage: TypeScript\nseverity: error\nmessage: m\n${body}`
55+
`id: ${id}\nlanguage: ${language}\nseverity: error\nmessage: m\n${body}`
5656
);
5757
await writeFile(
5858
join(directory, ".tests", `${id}-test.yml`),
@@ -78,6 +78,44 @@ describe("validate parses each sg rule with ast-grep, alone", () => {
7878
expect(rules[0]?.errors.join("\n")).toContain("Kind `readonly` is invalid");
7979
});
8080

81+
// Each shape reaches a different part of the cause-chain parsing, and each
82+
// was measured on 0.45.3. The specific cause has to survive: a reformat that
83+
// dropped it would still fail the rule, but with the generic exit-code
84+
// message, and only these assertions would notice.
85+
it.each([
86+
{
87+
shape: "a Markdown inline kind",
88+
body: "rule:\n kind: link\n",
89+
language: "Markdown",
90+
expected: "Kind `link` is invalid",
91+
},
92+
{
93+
// A one-link chain, with no context to put in parentheses.
94+
shape: "a pattern with no AST kind",
95+
body: 'rule:\n pattern: "foo("\n',
96+
language: "TypeScript",
97+
expected: "Rule must specify a set of AST kinds to match",
98+
},
99+
{
100+
// The specific part arrives on a later line, after the pattern and a
101+
// caret, as `error: ...`; it is folded into the last link.
102+
shape: "a regex that does not compile",
103+
body: 'rule:\n kind: identifier\n regex: "(["\n',
104+
language: "TypeScript",
105+
expected: "regex parse error: unclosed character class",
106+
},
107+
])(
108+
"names the specific cause for $shape",
109+
async ({ body, language, expected }) => {
110+
await sgRule("refused", body, language);
111+
const { rules } = await report("validate", ".taskless/rules/sg/refused");
112+
const errors = rules[0]?.errors.join("\n") ?? "";
113+
expect(rules[0]?.ok).toBe(false);
114+
expect(errors).toContain(`ast-grep refuses this rule: ${expected}`);
115+
expect(errors).not.toContain("(exit ");
116+
}
117+
);
118+
81119
it("attaches no constraint id to the engine's refusal", async () => {
82120
await sgRule("kw", KEYWORD_KIND);
83121
const { rules } = await report("validate", ".taskless/rules/sg/kw");

0 commit comments

Comments
 (0)