Skip to content

Detect single-line previews like #Preview("x") { foo() }#166

Merged
BarredEwe merged 3 commits into
BarredEwe:mainfrom
AllDmeat:fix/single-line-preview-detection
Jul 9, 2026
Merged

Detect single-line previews like #Preview("x") { foo() }#166
BarredEwe merged 3 commits into
BarredEwe:mainfrom
AllDmeat:fix/single-line-preview-detection

Conversation

@AllDmeat

Copy link
Copy Markdown
Contributor

Problem

Single-line previews whose closure opens and closes on the same line as the #Preview macro are silently skipped — no snapshot test is generated for them:

#Preview("MyView") { previewMyView() }

There is no warning or error; the preview simply never produces a test.

Root cause

Two spots, both needed for the case to work end to end:

  1. PreviewLoader.previewBodies starts tracking the brace balance only once a line has a non-zero brace change (braceChange != 0). On a single-line preview the opening and closing braces cancel out (braceChange == 0), so the balance is never initialised and the braceBalance == 0 close condition never fires — the body is never collected.

  2. RawPreviewModel.init derives the first line via dropLast(2) + removeFirst(). For a single-line body the collection is empty after dropLast(2), so removeFirst() traps.

Fix

  1. previewBodies now starts tracking the brace balance as soon as a line contains the opening brace, regardless of whether it nets to zero on that line.
  2. RawPreviewModel.init takes the first line directly. The lines slice after removeFirst() was never used — only the first line matters here (for displayName/traits); body and properties are already parsed from the full macro body via SwiftSyntax, so nothing else changes.

Multi-line previews are unaffected: their #Preview { line already has a non-zero brace change, so balance tracking starts exactly as before.

Tests

  • PreviewLoaderTests: single-line only, single-line + multi-line mixed, and single-line under defaultEnabled: false.
  • RawPreviewModelTests: single-line body parses displayName, body, and traits correctly.

Full PrefireExecutable suite: 45 tests, 0 failures.

Single-line previews whose closure opens and closes on the same line as
the #Preview macro were silently skipped, so no snapshot test was
generated for them.

Two issues, both fixed here:

- PreviewLoader.previewBodies started tracking the brace balance only
  when a line had a non-zero brace change. On a single-line preview the
  opening and closing braces cancel out (braceChange == 0), so the
  balance was never initialised and the body was never collected. It now
  starts tracking as soon as a line contains the opening brace.

- RawPreviewModel.init derived the first line via `dropLast(2)` +
  `removeFirst()`, which traps on a single-line body (the collection is
  empty after dropLast(2)). It now takes the first line directly; the
  body and properties are already parsed from the full macro body via
  SwiftSyntax, so nothing else is affected.

Adds unit tests for both code paths.
@BarredEwe

Copy link
Copy Markdown
Owner

@AllDmeat Hi! Thank you for improvements.

This fixes the single-line case, but it also makes extraction depend on any { appearing on the #Preview line, not on the actual preview closure opening.

For example this now gets truncated to only the first line:

#Preview("{braced}")
{
    Text("TestView")
}

Previously this worked because braceBalance did not start until the real { line. I think we should avoid extending the line-based brace scanner here and instead extract #Preview macros with SwiftSyntax, which is already a dependency in this target and already used by RawPreviewModel. That would correctly handle braces in strings/comments/raw strings/nested closures without adding more ad-hoc parsing.

The line-based brace scanner miscounted braces inside string literals,
comments and raw strings, truncating previews like `#Preview("{braced}")`.
Locate macros with SwiftSyntax so the real source range is always correct.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@AllDmeat

AllDmeat commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Good catch, thanks. Switched the extraction to SwiftSyntax as you suggested — PreviewLoader now walks the file for #Preview macro expansions (MacroExpansionDeclSyntax/MacroExpansionExprSyntax) and takes the real source range from the # token to the end of the closure, instead of the line-based brace scanner. So braces in strings/comments/raw strings/nested closures no longer confuse it.

Added a regression test for your counterexample:

#Preview("{braced}")
{
    Text("TestView")
}

All existing single-line/multi-line/Allman/@available/prefireIgnored cases still pass.

@BarredEwe

Copy link
Copy Markdown
Owner

Super, thanks for the enhancement!

@BarredEwe
BarredEwe merged commit 0b17f59 into BarredEwe:main Jul 9, 2026
3 checks passed
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.

2 participants