Skip to main content

petitparser-test

Builds comprehensive, deterministic, and rigorous automated test suites for grammars, validating individual productions, end-to-end AST generation, edge-case token boundaries, and linter compliance.

Source facts

Repository
serverpod/wonky
Last source activity
October 20, 2026 at 10:16
Detected SKILL.md language
English
Stars
6
Forks
5

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
petitparser-test
description
Builds comprehensive, deterministic, and rigorous automated test suites for grammars, validating individual productions, end-to-end AST generation, edge-case token boundaries, and linter compliance.
user-invocable
false
# Grammar Testing & Verification ## Trigger Conditions - Writing unit and regression tests for new or modified grammar rules. - Isolating and testing individual grammar productions independently from the root parser. - Validating grammar behavior against syntax edge cases (empty strings, Unicode, whitespace, malformed inputs). - Running grammar linter checks to detect cycles, left recursion, and dead ends. ## Isolated Production Testing with `buildFrom` Do not test all grammar rules exclusively through the top-level `start()` entrypoint. Compile and test sub-rules in total isolation using `buildFrom`: ```dart import 'package:petitparser/petitparser.dart'; import 'package:test/test.dart'; void main() { final grammar = MyGrammarDefinition(); // Compile individual rules in isolation, anchored with .end(): final identifier = grammar.buildFrom(ref0(grammar.identifier)).end(); final statement = grammar.buildFrom(ref0(grammar.statement)).end(); group('identifier', () { test('valid identifiers', () { expect(identifier.parse('foo'), isA<Success>()); expect(identifier.parse('_bar123'), isA<Success>()); }); test('invalid identifiers', () { expect(identifier.parse('123foo'), isA<Failure>()); expect(identifier.parse(''), isA<Failure>()); }); }); } ``` ## Dual-Verification Strategy Every test suite must assert both valid input acceptance and invalid input rejection: - **Success Assertions**: Verify that the input parses successfully, consumes expected length, and produces the expected value. - **Failure Assertions**: Verify that malformed syntax produces a `Failure`, and optionally assert failure position and message. ```dart // Success: assert value and successful parsing expect(parser.parse('42').value, 42); expect(parser.accept('42'), isTrue); // Failure: assert Failure type or rejection expect(parser.parse('abc'), isA<Failure>()); expect(parser.accept('abc'), isFalse); // Detailed failure checks when needed: expect( parser.parse('abc'), isA<Failure>() .having((f) => f.position, 'position', 0) .having((f) => f.message, 'message', 'digit expected'), ); ``` ### Semantic AST Assertions Assert the complete structure and types of returned AST nodes, not just boolean success: ```dart test('parses binary expression AST', () { final result = exprParser.parse('1 + 2'); expect(result, isA<Success>()); final node = result.value; expect(node, isA<BinaryExpressionNode>() .having((n) => n.operator, 'operator', '+') .having((n) => n.left, 'left', isA<LiteralNode>().having((l) => l.value, 'value', 1)) .having((n) => n.right, 'right', isA<LiteralNode>().having((l) => l.value, 'value', 2))); }); ``` ### Automated Grammar Linter & Pragmatic Exclusions PetitParser includes a built-in static grammar analyzer via `linter(parser)`. Add a dedicated test verifying the grammar is free of structural defects: ```dart import 'package:petitparser/reflection.dart'; import 'package:test/test.dart'; test('grammar linter passes without issues', () { final parser = MyGrammarDefinition().build(); final issues = linter(parser); expect(issues, isEmpty, reason: issues.join('\n')); }); ``` The linter automatically checks for: - Unreachable / unreferenced parsers. - Left-recursive cycles that cause infinite call stacks. - Repetition over nullable / zero-width parsers. - Redundant and nested wrapper parsers. - Duplicate parsers. #### Pragmatic Linter Exclusions While grammars should strive to pass all linter checks cleanly, certain rules can be legitimately ignored depending on grammar design: - **`Duplicate parser`**: Often triggered when parameterized helper rules (e.g. `ref1(token, ',')`) or terminal tokens are independently generated across multiple productions. If consolidating them adds unnecessary coupling or complexity, exclude the rule pragmatically: ```dart expect( linter(parser, excludedRules: {'Duplicate parser'}), isEmpty, ); ``` - Always document the rationale when passing `excludedRules` to `linter()`. ### Edge-Case & Boundary Matrix Always include tests covering: - **Empty input**: `""` should fail cleanly if input is required, or produce the default empty AST. - **Whitespace variations**: Leading, trailing, internal newlines, tabs, and spaces. - **Truncated inputs**: Unterminated strings (`"hello`), unclosed brackets (`[1, 2`), trailing operators (`1 +`). - **Unicode characters**: Multi-byte code points, emojis, non-ASCII identifier characters. - **Input boundary**: Ensure input following a valid token is either parsed or rejected (verifying `.end()`). When debugging complex test failures, isolate the failure into a minimal test pair and use `trace()` as described in `petitparser-debug`. ## Critical Heuristics & Anti-Patterns - **Dual-Verification Mandatory**: Never only test `isSuccess`. Testing invalid inputs with `isFailure` is essential to prevent false-positive over-matching. - **Isolate Sub-Rules**: Test each production in isolation (`buildFrom`) to minimize search space when grammar rules break. - **Verify Clean Linter**: Run `linter(parser)` as part of continuous integration, documenting any intentional exclusions.
View on GitHub