Skip to content

Latest commit

 

History

History
547 lines (374 loc) · 29.6 KB

File metadata and controls

547 lines (374 loc) · 29.6 KB

Fixture Creation Workflow

For agents: Follow this script when creating fixtures for new features.


Golden Rules

NEVER modify a correct fixture to make tests pass. Fixtures define correct behavior (prettier's output). If a test fails, fix the implementation — not the fixture. A failing test is doing its job: revealing a bug.

No code changes without a failing fixture first. Even "obvious" fixes need a fixture proving the divergence exists. The fixture is the proof and the regression guard. Exception: pure comment/doc updates that don't change behavior.

When a fixture is the wrong tool

A fixture defines correct formatting, with prettier as the source of truth. That is the right instrument for a formatting conformance bug — our output differing from prettier's, which is the bulk of the work: SAFETY violations, unknowns, partials, sanctioned divergences. Where the bug is not about formatting output, a fixture adds ceremony without a claim, and a unit test at the failure site is the instrument:

  • Parser crashes — a panic, a stack overflow, an unbounded scan. These are internal bugs, not formatting disagreements; the fix is a guard, and the test belongs on the guarded function. (A ValueCursor state update that didn't guard parens inside quotes overflowed the stack; two lines and cursor-level unit tests closed it — no fixture.)
  • Infrastructure — FFI, CLI argument parsing, serialization.
  • Lexer/parser correctness where the AST is wrong but formatting is incidentally right — test at the parser level, where the wrong node is observable.

The two are not exclusive: a parser fix that also changes output still wants the fixture. The question is what the bug is, not which test is cheaper to write.

TDD Steps

These steps match CLAUDE.md's numbered list. Each is expanded in a section below.

0. LOAD CONTEXT — read this file + fixture_naming.md, study 2-3 existing fixtures
1. CREATE FIXTURE — fixture_init (formats through prettier + generates expected.json)
2. REVIEW — verify generic names, edge cases, AST structure, handle divergence
3. SEE IT FAIL — deno task fixtures:validate <pattern>
4. ★ APPROVAL GATE — show input + failing diff, wait for user approval
5. IMPLEMENT — fix parser/formatter to make the fixture pass
6. VALIDATE — deno task fixtures:validate <pattern>, add unformatted_* variants

Approval Gate (step 4) is a hard stop — what to present, and how the gate resets on rework, is stated once under Steps 3-4.

Failing tests are the normal starting point. When you create a fixture for a feature that doesn't exist yet, tests will fail. That's the workflow — the fixture defines the target, implementation catches up.


Step 0: Load Context

Pick ONE item from the todo list. State: "Working on: [item name]"

Read these docs first (don't skip):

  • ./fixture_naming.md — REQUIRED before every fixture. Actually open and read it — generic naming rules, long fixture conventions, variant naming, boundary testing
  • ./conformance_prettier.md + the language's catalog (its §Catalogs table indexes them: _css / _svelte / _ts / _ts_comments / _ignore) — REQUIRED only before a _prettier_divergence fixture (skip for ordinary fixtures). Read §Comment Position Philosophy in the frame + the §Comment relocation catalog in conformance_prettier_ts_comments.md; the divergence must be sanctioned and cataloged there (see Step 2.3)
  • This file's Golden Rules — fixture-first discipline

Always load fixture_naming.md — skipping it leads to domain-specific names, missing boundary tests, and fixture rework; conventions are easy to forget. Add conformance_prettier.md + the language's catalog whenever the fixture will be a divergence.

Then find 2-3 similar existing fixtures and READ them. Note: how many examples (usually 3-6), naming conventions (expr, cond, a, b — see ./fixture_naming.md), edge cases, and unformatted_* variants.

Use --prettier-only during fixture design to validate structure without running our parser/formatter:

deno task fixtures:validate --prettier-only <pattern>

Step 1: Create Fixture

1.1 Create Directory and Draft

mkdir -p tests/fixtures/typescript/[category]/[name]

Directory location:

  • Feature fixtures in feature-specific directories (e.g., calls/chained/, types/conditional/)
  • Comment fixtures with the feature they test, using *_comment suffix (e.g., calls/chained/trailing_arg_comment). Only syntax/comments/ for universal comment rules (basic syntax, blank lines, cross-cutting edge cases)

Choose input file type:

  • input.svelte (preferred) - Tests code embedded in Svelte <script>/<style> context
  • input.ts (rare) - Only when a feature can't be tested in .svelte: byte-0 file-level features (hashbang, BOM) or context-dependent formatting (e.g. the <T> vs <T,> arrow type-parameter trailing comma). TS-only syntax (import =, export =, types, decorators, declare) is not a reason — it formats identically in <script lang="ts">. See fixture_overview.md §Input File Types
  • input.css (rare) - Only for file-level CSS features at byte position 0 (e.g., BOM)
  • input.svelte.ts (runes) - Svelte rune modules ($state, $derived, etc.)

⚠️ Prefer .svelte - it's the only path with an external canonical source for CSS. See fixture_overview.md for details.

⚠️ Prefer plain block comments over JSDoc — When testing comment-related formatting (e.g., comment placement, blank line detection), use plain /* comment */ instead of /** @type {T} */ unless the fixture specifically tests JSDoc cast behavior. A JSDoc cast triggers paren preservation — a dedicated code path whose prettier-oracle behavior differs by parser backend (oxc-ts strips, babel keeps) — which can obscure the real formatting issue being tested. Plain block comments exercise the same comment-placement paths without the cast's preservation semantics.

Write input content with:

  • Multiple related patterns in ONE fixture (consolidate)
  • Generic names: a, b, cond, expr, obj, arr, i, n
  • Content does NOT need to be prettier-formatted — fixture_init handles that

1.2 Create with fixture_init

Formats through prettier + generates expected.json automatically. Input is guaranteed correctly formatted by construction.

cargo run -p tsv_debug fixture_init tests/fixtures/.../name --content '<script>code</script>'
cargo run -p tsv_debug fixture_init tests/fixtures/.../name --stdin << 'EOF'
<script>multiline code (any formatting is fine)</script>
EOF
cargo run -p tsv_debug fixture_init tests/fixtures/.../name   # reformat existing input

Options: --parser typescript|css|svelte-ts (default: svelte; ts and svelte.ts are accepted aliases), --force (overwrite existing), --goal script|module.

--goal script builds a standalone-script fixture: it writes the goal marker and generates expected.json from acorn at sourceType: 'script', so await is an ordinary identifier and import/export/import.meta are syntax errors — the two sides of the fixture then agree on the goal it is graded at. It applies to .ts / .svelte.ts inputs only (Svelte <script> is always a module, CSS has no goal); --goal module removes any marker. Either move lands only on a successful regeneration of expected.json — on failure the marker is left as it was, so it never describes a file generated at the other goal. With no --goal the directory's existing marker is kept, and no marker means module — so a bare reinit of a script fixture regenerates expected.json at script goal. See fixture_naming.md and fixture_overview.md.

cargo run -p tsv_debug fixture_init tests/fixtures/typescript/script_goal/name \
  --parser typescript --goal script --content 'const await = 1;'

After running, read the generated input.svelte to verify structure. For long fixtures, check the line widths in the output — do not estimate widths manually.

⚠️ long fixtures MUST include BOTH boundary cases: a line at exactly 100 chars (stays inline) AND a line at exactly 101 chars (breaks/wraps). This catches off-by-one errors. Iterate with --force until both widths are exact — adjust variable name length to hit the boundary precisely.

1.3 Manual Alternative

For divergence fixtures needing fine-grained control (writing output_prettier.*, prettier_variant_*.*, prettier_intermediate_*.*, prettier_intermediate_to_variant_*.*, etc.), use format_prettier directly:

cargo run -p tsv_debug format_prettier .../input.svelte --no-line-widths 2>/dev/null > /tmp/p.svelte && cp /tmp/p.svelte .../input.svelte
deno task fixtures:update:parsed <pattern>  # generate expected.json separately

⚠️ Never run fixture_init on an existing _prettier_divergence fixture whose input is not a prettier fixed point — not even --force to pick up a case you hand-added. It formats the input through prettier, so it overwrites tsv's claimed form with prettier's, which is the exact thing the fixture exists to say tsv does NOT do. Nothing catches it: fixtures:validate then regenerates a self-consistent set around the new input and reports green while the fixture asserts the opposite of its README. To add a case to one of these, edit input.svelte by hand, then run fixtures:update:parsed and fixtures:update:formatted — which regenerate expected.json, output_prettier.* and any audit_signature.txt from the input rather than replacing it. Read the resulting git diff of input.svelte: it must contain only the lines you added.

1.4 Quick Parse Check

cargo run -p tsv_debug canonical_parse tests/fixtures/.../input.svelte 2>/dev/null | head -30

Verify no parse errors (detailed AST verification in step 2.2). If prettier passes but canonical_parse fails, you have invalid JS that prettier silently accepts:

Error → cause — fix:

  • 'return' outside of functionreturn in script block — Wrap in function or use break
  • 'await' outside async functionawait in script block — Use {#await} block or wrap in async function
  • 'break'/'continue' outside loop → Control flow outside loop — Add enclosing loop context
  • Unexpected token → Invalid JS syntax — Check syntax in browser console

Step 2: Review

2.1 Edge Case Checklist

Review your fixture against these checklists. If gaps found: add to input.svelte, rerun fixture_init to reformat (step 1.2), and repeat.

Comments describe formatting, not bugs. Fixture comments should explain what the correct output IS (e.g., "array expands to multi-line"), never how our formatter differs. Fixtures define correct behavior — they are not bug reports.

For major features: build a feature-specific edge-case checklist first — extend the relevant language checklist below.

For comprehensive feature matrices: See ./checklist_css.md, ./checklist_svelte.md, ./checklist_typescript.md.

For Statements (if, for, while, switch, try):

  • Empty body — if (cond) {}, for (;;) {}, while (cond) {}
  • No braces — if (cond) expr;, for (...) expr;, while (cond) expr;
  • Nested — if (a) { if (b) {} }, for (...) { for (...) {} }
  • Chained — if (a) {} else if (b) {} else {}
  • With expressions — if (a && b), for (let i = 0, j = 0; ...)

For Imports/Exports:

  • Basic — import x from 'y', export const x = 1
  • Named — import {a, b} from 'y', export {a, b}
  • Renamed — import {a as b}, export {a as b}
  • Combined — import x, {a, b} from 'y', import x, * as ns from 'y'
  • Re-exports — export * from 'y', export * as ns from 'y', export {a} from 'y'
  • Default — export default x, export default function fn() {}
  • Empty braces — import {} from 'y', export {}
  • Trailing comma — import {a, b,} from 'y', export {a, b,}
  • Type imports — import type {A}, import type * as T, import {type A, b}
  • Attributes — import x from 'y' with {type: 'json'}

Notes:

  • Use unique variable names across all imports (JS doesn't allow duplicate identifiers)
  • Trailing commas are normalized by prettier (removed), so they belong in unformatted_*.svelte, not input.svelte

For Expressions:

  • Precedence — a + b * c, a || b && c
  • Parenthesized — (a + b) * c
  • Nested — fn(fn(fn(x))), a.b.c.d
  • With types — x as T, fn(): T => ...

For CSS:

  • Empty — div {}, @media screen {}
  • Nested — div { span {} }
  • Multiple values — margin: 1px 2px 3px 4px;
  • Functions — calc(), var(), url()
  • Comments — /* before */ selector, property: /* mid */ value
  • Long lines — See long naming below

Width-based wrapping (long naming):

When testing line-width wrapping behavior, use long in the directory name:

  • Subdirectory: feature/long/ (e.g., calls/long/) - when feature is the parent
  • Suffix: feature_long/ (e.g., gradient_long/) - when describing what's long
  • Content must exceed 100 chars to trigger wrapping
  • Use generic data: rgba(0, 0, 0, 0.8), 'f0000000' (not realistic values)
  • Add comments explaining what wraps vs what doesn't

⚠️ Always test the exact 100/101 boundary. Include both a case that fits at exactly 100 chars and one that exceeds at 101. This catches off-by-one errors and documents the precise breakpoint. Test at multiple indent levels if the feature appears nested (each tab adds 2 visual chars).

Do not estimate line widths manually — they are often wrong (tabs count as 2 visual chars, emoji/unicode vary, and off-by-one errors are common). fixture_init shows line widths automatically: lines at 90+ chars are listed with markers for exactly/over 100, and _long directories warn if nothing is near the boundary. Use --force to iterate until widths are correct:

# Iterate: adjust content, rerun, check widths in output
cargo run -p tsv_debug fixture_init tests/fixtures/.../name --force
# For specific line detail:
cargo run -p tsv_debug line_width input.svelte --line 5

Simplify content. Strip the reproduction to the minimum that triggers the divergence — use simple string literals ('aaa...') and generic names instead of complex expressions or domain data. The fixture should isolate the formatting behavior, not the content.

See fixture_naming.md for full conventions.

2.2 Parser Verification

Verify expected node types, no Error/Unknown nodes, correct structure:

cargo run -p tsv_cli parse tests/fixtures/.../input.svelte --pretty | head -80       # our parser
cargo run -p tsv_debug canonical_parse tests/fixtures/.../input.svelte | head -80  # canonical

2.3 Divergence Handling

The spec wins; adopting prettier's output is the default tie-breaker. When the spec defines canonical behavior, follow the spec — even if prettier's output is itself valid CSS. Otherwise adopt prettier's output. Diverge only for a spec-defined canonical form prettier doesn't emit, documented prettier bugs, spec violations, or comment repositioning — never for preference. When prettier moves a comment to a different syntactic position, preserve the user's placement (see conformance_prettier.md Comment Position Philosophy). See fixture_overview.md Decision Framework.

Creating a divergence fixture (rare):

  1. Load conformance_prettier.md + the language's catalog FIRST — REQUIRED before any divergence. Read §Comment Position Philosophy in the frame and the §Comment relocation catalog in conformance_prettier_ts_comments.md to (a) confirm the divergence is sanctioned (not a bug you're papering over) and (b) check whether it's already cataloged. If a sibling rule exists, match its shape; if new, add a catalog entry + one-line note there as part of this change. The README and conformance entry must agree.
  2. Create directory with _prettier_divergence suffix
  3. Add README.md explaining why tsv differs — match the concise style of sibling READMEs (prettier form ↔ tsv form ↔ reason + conformance link)
  4. Document with: output_prettier.*, prettier_variant_*.*, variant_*.*, divergent_variant_*.*, unformatted_ours_*.*, prettier_intermediate_*.*, prettier_intermediate_to_variant_*.*, or prettier_intermediate_to_divergent_variant_*.* — see fixture_naming.md for details
  5. Use deno task fixtures:audit <pattern> to investigate novel prettier outputs

deno task fixtures:update:formatted may also auto-generate an audit_signature.txt next to output_prettier.* when prettier requires multiple passes on it. Treat it as a sibling of output_prettier.* — never edit by hand; regenerate with the same command. See ./fixture_overview.md (rule F4).

The same command may generate an audit_signature_<suffix>.txt next to an unformatted_ours_<suffix>.* — the same file format anchored at that source instead, and the marker of last resort: it appears only where prettier's output from that source fits no single-form marker (a chain with two or more distinct intermediates, or a stable form tsv cannot format). Also never hand-written; the command decides whether one is warranted, deletes it when it stops being (orphaned intermediates and signatures whose source is gone are swept the same way), and refuses to delete over a chain prettier can no longer complete (an error mid-walk — investigate instead). See ./fixture_overview.md (rule N12).

If prettier never converges on the input (each pass keeps changing the output — no fixed point, so no output_prettier.* is possible), add a prettier_nonconvergent.txt marker + README instead of the claim files above; the validator live-verifies the non-convergence. Rare — a handful of in-tree cases. See ./fixture_overview.md (rules F5/S18).

If prettier throws on the input (a parse rejection or a printer crash — also no output_prettier.* possible), add a prettier_rejects.txt marker + README instead. The marker's trimmed content is the position-stripped expected-error substring; the validator live-verifies that prettier still errors with that message (rules F6/S19). The input must be valid by tsv's parse oracle (Svelte / acorn-typescript) and idempotent under tsv. Hand-author it — fixture_init runs prettier, which throws — then deno task fixtures:update:parsed for expected.json. See ./fixture_overview.md (rules F6/S19) and the catalog of in-tree cases in ./conformance_prettier_ts.md §"Prettier rejects valid input".

If tsv rejects an input the canonical parser accepts (a deliberate, spec-stricter tsv over-rejection), it can't be an input_invalid_* fixture (which needs both parsers to reject) nor a plain fixture (which needs tsv to parse+format). Add a tsv_rejects.txt marker to a _svelte_divergence dir instead: input.* + tsv_rejects.txt (the position-stripped expected tsv-error substring) + expected_svelte.json (the canonical AST) + README — no expected.json / expected_ours.json, no format-claim files, no prettier no-oracle markers. The validator live-verifies that tsv still rejects (with the substring) and the canonical parser still accepts and matches expected_svelte.json — a dead divergence (canonical rejects too) fails loudly (rules F7/S20). Prettier is never consulted (skip fixture_init / output_prettier): hand-author input.*, then deno task fixtures:update:parsed generates expected_svelte.json from the canonical parser (failing if it rejects). Catalog the divergence in ./conformance_svelte.md §TypeScript Corrections and back-link it from the README. See ./fixture_overview.md (rules F7/S20).


Steps 3-4: See It Fail + Approval Gate

Run validation to confirm the fixture fails as expected:

deno task fixtures:validate <pattern>

⚠️ APPROVAL GATE — STOP HERE. Show the user:

  • The input file content
  • The failing diff
  • Your proposed fix approach

Wait for explicit approval ("lgtm" or feedback) before writing ANY implementation code. This is a hard stop — not a suggestion.

The gate resets on rework. If the user gives feedback that requires changing the fixture (naming, structure, cases, etc.), redo steps 1-3 and return here for approval again. Every version of the fixture must pass through this gate before implementation begins. Variants (step 6) don't need separate approval unless complex (divergence fixtures).


Step 5: Implement

Fix parser/formatter errors so the fixture passes validation:

deno task fixtures:validate [pattern]

If errors exist:

  1. Fix the implementation (parser, printer, converter)
  2. Never modify fixtures to work around bugs — fix the code
  3. If a feature genuinely can't be implemented yet, leave a // TODO: at the site (and note it in the language checklist) and defer the fixture

Step 6: Validate + Variants

6.1 Validate

deno task fixtures:validate [pattern]

All checks must pass.

6.2 Add Variants

Variants stress-test normalization — unusual formatting that should collapse to the canonical input. Extension must match input file (input.svelteunformatted_*.svelte, etc.).

  • unformatted_compact → Minimal whitespace — if(cond){expr;}
  • unformatted_spaces → Excessive whitespace — if ( cond ) { expr ; }

⚠️ The two are directional opposites, and deno task variants:audit gates it: a _compact variant never widens a gap, a _spaces variant never empties one. Write both where the input supports both. A variant that must move whitespace both ways (the weld itself is the subject) takes a qualified name instead — see fixture_naming.md §Standard Variant Names.

Preserve blank lines between statements — prettier preserves them, so compact variants must too. Without matching blank lines, the variant won't normalize to input.

<!-- input.svelte -->
<script>
	if (cond) {
		expr;
	}

	if (a) {
		expr;
	} else {
		expr;
	}
</script>

<!-- unformatted_compact.svelte — note the blank line is preserved -->
<script>
if(cond){expr;}

if(a){expr;}else{expr;}
</script>

In _prettier_divergence directories: use unformatted_ours_*.* instead (normalizes with our formatter only).

6.3 Verify Variants Normalize

# Both must produce output identical to input.svelte
cargo run -p tsv_debug format_prettier .../unformatted_compact.svelte 2>/dev/null > /tmp/c.svelte
cargo run -p tsv_debug format_prettier .../unformatted_spaces.svelte 2>/dev/null > /tmp/s.svelte

diff .../input.svelte /tmp/c.svelte && echo "✓ compact normalizes"
diff .../input.svelte /tmp/s.svelte && echo "✓ spaces normalizes"

If a variant doesn't normalize: It may be a prettier_variant_*.* (prettier stable, ours normalizes to input), a variant_*.* (both formatters keep stable), or a divergent_variant_*.* (prettier stable, ours rewrites to a distinct third stable form). Use deno task fixtures:audit <pattern> to investigate. See fixture_overview.md.


6.4 Mark Complete

Update the relevant language checklist if applicable. State: "Completed: [item] - N fixtures in [paths]"


Pre-Implementation Fixtures

When the feature doesn't exist yet, fixture_init still works — prettier doesn't need our parser. Follow the normal steps but skip "our parser" checks in step 2 (they'll fail) and skip variants in step 6:

deno task fixtures:validate --prettier-only [pattern]  # skips our formatter, validates prettier + canonical parser

Formatter errors like InvalidSyntax are expected until implementation. The approval gate (step 4) still applies. To regenerate expected.json after upstream parser changes: deno task fixtures:update:parsed [pattern]


Worked Example: Creating statements/if/basic

# Step 0: LOAD CONTEXT
# "Working on: if/else statements"
# Read fixture_workflow.md + fixture_naming.md (done)
cat tests/fixtures/typescript/declarations/function/*/input.svelte  # study existing

# Step 1: CREATE FIXTURE (content does NOT need to be formatted)
cargo run -p tsv_debug fixture_init tests/fixtures/typescript/statements/if/basic --stdin << 'EOF'
<script>
if(cond){expr;}

if(a){expr;}else{expr;}

if(a){expr;}else if(b){expr;}else{expr;}
</script>
EOF
# ✓ input.svelte (prettier-formatted)
# ✓ expected.json

# Step 2: REVIEW
cat tests/fixtures/typescript/statements/if/basic/input.svelte  # verify generic names, structure
# Checklist: empty body? no braces? nested? ← Found gap: no "if without braces"
cargo run -p tsv_cli parse tests/fixtures/typescript/statements/if/basic/input.svelte --pretty | head -30

# Step 3: SEE IT FAIL
deno task fixtures:validate statements/if

# Step 4: ★ APPROVAL GATE - present input + failing diff to user, wait for "lgtm"

# Step 5: IMPLEMENT - fix any parser/formatter errors
deno task fixtures:validate statements/if  # should pass after implementation

# Step 6: VALIDATE + VARIANTS
# Note: blank lines between statements must be preserved!
cat > tests/fixtures/typescript/statements/if/basic/unformatted_compact.svelte << 'EOF'
<script>
if(cond){expr;}

if(a){expr;}else{expr;}

if(a){expr;}else if(b){expr;}else{expr;}
</script>
EOF

cargo run -p tsv_debug format_prettier tests/fixtures/typescript/statements/if/basic/unformatted_compact.svelte 2>/dev/null > /tmp/c.svelte
diff tests/fixtures/typescript/statements/if/basic/input.svelte /tmp/c.svelte && echo "✓ normalizes"

deno task fixtures:validate statements/if  # all checks pass
# "Completed: if/else statements - 3 fixtures in statements/if/"

Quick Reference

# Create a new fixture (formats through prettier automatically)
cargo run -p tsv_debug fixture_init tests/fixtures/.../name --content '<script>code</script>'
cargo run -p tsv_debug fixture_init tests/fixtures/.../name --stdin << 'EOF'
<script>multiline code</script>
EOF
cargo run -p tsv_debug fixture_init tests/fixtures/.../name  # reformat existing input file

# Compare our output vs prettier (most useful for quick testing)
cargo run -p tsv_debug compare FILE
cargo run -p tsv_debug compare --content "<script>CODE</script>" --parser svelte
cargo run -p tsv_debug compare --content "const x = 1" --parser typescript

# Verify fixture matches prettier (manual check)
cargo run -p tsv_debug format_prettier FILE 2>/dev/null > /tmp/p.svelte && diff FILE /tmp/p.svelte && echo "✓ MATCH"

# Check our parser output
cargo run -p tsv_cli parse FILE --pretty | head -50

# Check canonical parser (Svelte/acorn for .svelte, acorn+typescript for .ts)
cargo run -p tsv_debug canonical_parse FILE 2>/dev/null | head -50

# Measure line width (for long fixtures)
cargo run -p tsv_debug line_width FILE --line N

# Validate fixtures
deno task fixtures:validate [pattern]

# Count fixtures (all input types)
find tests/fixtures \( -name "input.svelte" -o -name "input.svelte.ts" -o -name "input.ts" -o -name "input.css" \) | wc -l

Using Fixtures to Track Bugs

Add the failing case to input.svelte (prettier's canonical form), run validation (it will fail), and document with a TODO. The failing fixture is a regression test that passes once the bug is fixed.

cargo run -p tsv_debug compare --content "<script>import {} from 'x';</script>" --parser svelte

Keep failing tests. Never remove test cases to make tests pass — fix the code instead.

  • Formatter bug (output differs) — Keep — fix the formatter
  • Parser bug (parse error on valid code) — Keep — fix the parser
  • Feature reveals OTHER unrelated bugs — Keep — fix those bugs too
  • Parser genuinely can't parse (not impl) — Note it in the language checklist, defer fixture until parser works

Only defer when the parser can't parse the syntax yet (use --prettier-only until ready).


Invalid Syntax Fixtures

input_invalid_<description>.<ext> files test that parsers correctly reject invalid syntax. Both our parser and the canonical parser must reject the file.

cat > tests/fixtures/.../input_invalid_await_const.svelte << 'EOF'
<script lang="ts">
	const await = 'a';
</script>
EOF
cargo run -p tsv_debug canonical_parse tests/fixtures/.../input_invalid_await_const.svelte  # must fail
deno task fixtures:validate [pattern]

Rules: one syntax error per file, minimal content, no expected.json, descriptive suffixes (_const, _param, _destructure, _label). If our parser accepts but canonical rejects, our parser is too permissive (bug).


Common Mistakes

Mistake → symptom — fix:

  • Domain-specific names → describe, Logger, UserProfile in fixture — Use generic names per ./fixture_naming.md
  • Code change before fixture → Implementation attempted without proof of divergence — Stop, create fixture, get approval, then implement
  • Skipping naming doc → Fixture requires redo after review — Always read ./fixture_naming.md in step 0
  • Too many test cases → >6 unrelated patterns in one fixture — Focus on 3-6 related patterns per fixture
  • Missing blank lines in variants → unformatted_compact doesn't normalize to input — Preserve blank lines between logical groups

See Also

  • ./fixture_overview.md - Validation rules, divergence patterns, troubleshooting
  • ./fixture_naming.md - Naming conventions, long fixture details