Skip to content

Commit 53968fe

Browse files
authored
H-5817: HashQL: Add diagnostic writing and testing skills (#8175)
1 parent ee22fac commit 53968fe

File tree

14 files changed

+3347
-209
lines changed

14 files changed

+3347
-209
lines changed

.claude/skills/handling-rust-errors/SKILL.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,28 @@ Automatically activates when:
4141

4242
---
4343

44+
## HashQL Compiler Exception
45+
46+
**HashQL compiler code uses a different error handling approach.**
47+
48+
Code in `libs/@local/hashql/*` uses the `hashql-diagnostics` crate instead of `error-stack`. This is because compiler errors require rich formatting capabilities:
49+
50+
- Source spans pointing to exact code locations
51+
- Multiple labeled regions within the same diagnostic
52+
- Fix suggestions with replacement text
53+
- Severity levels (error, warning, hint)
54+
55+
**Which approach to use:**
56+
57+
| Location | Error Handling |
58+
|-----------------------------------------|-----------------------------------------------------------------------------------------------------------|
59+
| `libs/@local/hashql/*` (compiler code) | Use `hashql-diagnostics` → See [writing-hashql-diagnostics](../writing-hashql-diagnostics/SKILL.md) skill |
60+
| Everywhere else | Use `error-stack` patterns from this skill |
61+
62+
Traditional `error-stack` patterns still apply for HashQL infrastructure code (CLI, file I/O, configuration) that doesn't involve compiler diagnostics.
63+
64+
---
65+
4466
## Quick Start Guide
4567

4668
Choose the resource that matches your current task:

.claude/skills/skill-rules.json

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,84 @@
8484
"\\.rs\\b.*?\\b(documentation|docs|doc comment)\\b"
8585
]
8686
}
87+
},
88+
"writing-hashql-diagnostics": {
89+
"type": "domain",
90+
"enforcement": "suggest",
91+
"priority": "high",
92+
"description": "HashQL diagnostic writing patterns using hashql-diagnostics crate",
93+
"promptTriggers": {
94+
"keywords": [
95+
"diagnostic",
96+
"diagnostics",
97+
"hashql-diagnostics",
98+
"Label",
99+
"Message",
100+
"Severity",
101+
"Patch",
102+
"Suggestions",
103+
"error message",
104+
"warning message"
105+
],
106+
"intentPatterns": [
107+
"\\b(create|write|add|improve|fix)\\b.*?\\bdiagnostic(s)?\\b",
108+
"\\bdiagnostic(s)?\\b.*?\\b(message|label|severity|suggestion|patch)\\b",
109+
"\\b(error|warning)\\b.*?\\bmessage\\b.*?\\b(hashql|compiler)\\b",
110+
"\\bhashql\\b.*?\\b(error|warning|diagnostic)\\b"
111+
]
112+
}
113+
},
114+
"writing-hashql-jexpr": {
115+
"type": "domain",
116+
"enforcement": "suggest",
117+
"priority": "high",
118+
"description": "HashQL J-Expr syntax patterns for writing JSON Expression Language queries",
119+
"promptTriggers": {
120+
"keywords": [
121+
"J-Expr",
122+
"jexpr",
123+
"hashql syntax",
124+
"hashql query",
125+
"#literal",
126+
"function call",
127+
"S-Expr",
128+
"JSON Expression"
129+
],
130+
"intentPatterns": [
131+
"\\b(write|read|understand|create|parse)\\b.*?\\b(j-?expr|hashql)\\b.*?\\b(query|syntax)\\b",
132+
"\\bj-?expr\\b.*?\\b(syntax|function|symbol|literal|call)\\b",
133+
"\\bhashql\\b.*?\\b(query|syntax|expression)\\b",
134+
"\\b(json|s-?expr)\\b.*?\\bexpression\\b.*?\\b(language|syntax)\\b"
135+
]
136+
}
137+
},
138+
"testing-hashql": {
139+
"type": "domain",
140+
"enforcement": "suggest",
141+
"priority": "high",
142+
"description": "HashQL testing strategies including compiletest, unit tests, and snapshot tests",
143+
"promptTriggers": {
144+
"keywords": [
145+
"compiletest",
146+
"hashql test",
147+
"ui test",
148+
".spec.toml",
149+
"//~",
150+
"--bless",
151+
"snapshot test",
152+
"insta",
153+
"hashql-compiletest",
154+
".stdout",
155+
".stderr"
156+
],
157+
"intentPatterns": [
158+
"\\b(write|create|add|run|debug|fix)\\b.*?\\b(hashql|compiletest)\\b.*?\\btest\\b",
159+
"\\btest(s|ing)?\\b.*?\\b(hashql|compiletest|ui)\\b",
160+
"\\b(compiletest|ui test)\\b.*?\\b(run|filter|bless|fail|pass)\\b",
161+
"\\bdiagnostic\\b.*?\\bannotation\\b",
162+
"\\b(snapshot|insta)\\b.*?\\btest\\b"
163+
]
164+
}
87165
}
88166
},
89167
"notes": {
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
---
2+
name: testing-hashql
3+
description: HashQL testing strategies including compiletest (UI tests), unit tests, and snapshot tests. Use when writing tests for HashQL code, using //~ annotations, running --bless, debugging test failures, or choosing the right testing approach.
4+
---
5+
6+
# HashQL Testing Strategies
7+
8+
HashQL uses three testing approaches. **compiletest is the default** for testing compiler behavior.
9+
10+
## Quick Reference
11+
12+
| Scenario | Test Type | Location |
13+
| -------- | --------- | -------- |
14+
| Diagnostics/error messages | compiletest | `tests/ui/` |
15+
| Compiler pipeline phases | compiletest | `tests/ui/` |
16+
| MIR/HIR/AST pass integration | compiletest | `tests/ui/` |
17+
| MIR/HIR/AST pass edge cases | insta | `tests/ui/<category>/` |
18+
| Core crate (where needed) | insta | `src/**/snapshots/` |
19+
| Parser fragments (syntax-jexpr) | insta | `src/*/snapshots/` |
20+
| Internal functions/logic | Unit tests | `src/*.rs` |
21+
22+
## 1. compiletest (UI Tests)
23+
24+
Tests parsing, type checking, and error reporting using J-Expr files with diagnostic annotations.
25+
26+
**Structure:**
27+
28+
```text
29+
package/tests/ui/
30+
category/
31+
.spec.toml # Suite specification (required)
32+
test.jsonc # Test input
33+
test.stdout # Expected output (run: pass)
34+
test.stderr # Expected errors (run: fail)
35+
test.aux.svg # Auxiliary output (some suites)
36+
```
37+
38+
**Commands:**
39+
40+
```bash
41+
cargo run -p hashql-compiletest run # Run all
42+
cargo run -p hashql-compiletest run --filter "test(name)" # Filter
43+
cargo run -p hashql-compiletest run --bless # Update expected
44+
```
45+
46+
**Test file example:**
47+
48+
```jsonc
49+
//@ run: fail
50+
//@ description: Tests duplicate field detection
51+
["type", "Bad", {"#struct": {"x": "Int", "x": "String"}}, "_"]
52+
//~^ ERROR Field `x` first defined here
53+
```
54+
55+
**Directives** (`//@` at file start):
56+
57+
- `run: pass` / `run: fail` (default) / `run: skip`
58+
- `description: ...` (encouraged)
59+
- `name: custom_name`
60+
61+
**Annotations** (`//~` for expected diagnostics):
62+
63+
- `//~ ERROR msg` - current line
64+
- `//~^ ERROR msg` - previous line
65+
- `//~v ERROR msg` - next line
66+
- `//~| ERROR msg` - same as previous annotation
67+
68+
📖 **Full Guide:** [resources/compiletest-guide.md](resources/compiletest-guide.md)
69+
70+
---
71+
72+
## 2. Unit Tests
73+
74+
Standard Rust `#[test]` functions for testing internal logic.
75+
76+
**Location:** `#[cfg(test)]` modules in source files
77+
78+
**Example from** `hashql-syntax-jexpr/src/parser/state.rs`:
79+
80+
```rust
81+
#[test]
82+
fn peek_returns_token_without_consuming() {
83+
bind_context!(let context = "42");
84+
bind_state!(let mut state from context);
85+
86+
let token = state.peek().expect("should not fail").expect("should have token");
87+
assert_eq!(token.kind, number("42"));
88+
}
89+
```
90+
91+
**Commands:**
92+
93+
```bash
94+
cargo nextest run --package hashql-<package>
95+
cargo test --package hashql-<package> --doc # Doc tests
96+
```
97+
98+
---
99+
100+
## 3. insta Snapshot Tests
101+
102+
Uses `insta` crate for snapshot-based output when compiletest (the preferred method) is infeasible. Three categories exist:
103+
104+
| Category | Crates | Snapshot Location | Rationale |
105+
| -------- | ------ | ----------------- | --------- |
106+
| **Pipeline Crates** | mir, hir, ast | `tests/ui/<category>/*.snap` | Colocate with compiletest tests |
107+
| **Core** | hashql-core | Default insta (`src/**/snapshots/`) | Separate from pipeline; prefer unit tests |
108+
| **Syntax** | syntax-jexpr | `src/*/snapshots/` | Macro-based for parser fragments |
109+
110+
### Pipeline Crates (mir, hir, ast)
111+
112+
Snapshots colocate with compiletest UI tests. Test code lives in `src/**/tests.rs`, snapshots go in the appropriate `tests/ui/<category>/` directory.
113+
114+
```rust
115+
// Example: hashql-mir/src/pass/transform/ssa_repair/tests.rs
116+
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
117+
let mut settings = Settings::clone_current();
118+
settings.set_snapshot_path(dir.join("tests/ui/pass/ssa_repair")); // matches test category
119+
settings.set_prepend_module_to_snapshot(false);
120+
121+
let _drop = settings.bind_to_scope();
122+
assert_snapshot!(name, value);
123+
```
124+
125+
Categories vary: `reify/`, `lower/`, `pass/ssa_repair/`, etc.
126+
127+
### Core
128+
129+
`hashql-core` is separate from the compilation pipeline, so it uses default insta directories. Prefer unit tests; only use snapshots where necessary.
130+
131+
### Syntax (syntax-jexpr)
132+
133+
Syntax crates predate compiletest and use macro-based test harnesses for testing parser fragments directly.
134+
135+
```rust
136+
// hashql-syntax-jexpr/src/parser/string/test.rs
137+
pub(crate) macro test_cases($parser:ident; $($name:ident($source:expr) => $description:expr,)*) {
138+
$(
139+
#[test]
140+
fn $name() {
141+
assert_parse!($parser, $source, $description);
142+
}
143+
)*
144+
}
145+
```
146+
147+
Snapshots: `hashql-syntax-jexpr/src/parser/*/snapshots/*.snap`
148+
149+
### Commands
150+
151+
```bash
152+
cargo insta test --package hashql-<package>
153+
cargo insta review # Interactive review
154+
cargo insta accept # Accept all pending
155+
```
156+
157+
---
158+
159+
## Resources
160+
161+
- [compiletest Guide](resources/compiletest-guide.md) - Detailed UI test documentation
162+
- [Testing Strategies](resources/testing-strategies.md) - Choosing the right approach

0 commit comments

Comments
 (0)