Skip to content

C#: Fix AutoFormat not inserting newlines in synthesized nodes#7189

Merged
knutwannheden merged 3 commits intomainfrom
autoformat-should-handle-synthesized-nodes-with-space.empty
Mar 28, 2026
Merged

C#: Fix AutoFormat not inserting newlines in synthesized nodes#7189
knutwannheden merged 3 commits intomainfrom
autoformat-should-handle-synthesized-nodes-with-space.empty

Conversation

@knutwannheden
Copy link
Copy Markdown
Contributor

Motivation

When a recipe constructs AST nodes with Space.Empty (e.g., building an if/else chain from scratch, or using CSharpTemplate with compressed formatting), AutoFormat produces single-line output like if (a) { s = "A"; } else { s = "D"; } instead of properly formatted multi-line code.

The root cause is Roslyn's Formatter.Format has a SuppressWrappingIfOnSingleLine heuristic that preserves single-line formatting when all tokens are on one line. Since synthesized nodes with Space.Empty print without newlines, Roslyn sees "everything is on one line" and adds spaces but never inserts structural newlines.

Summary

  • Set CSharpFormattingOptions.WrappingPreserveSingleLine and WrappingKeepStatementsOnSingleLine to false in FormatWithRoslyn, disabling the single-line preservation heuristic so Roslyn inserts structural newlines (after {, before }, before else, etc.)
  • Extract BuildOptions helper in RoslynFormatter to centralize Roslyn option configuration
  • Add two new RewriteRun integration tests:
    • Manual AST construction matching the AvoidNestingTernary pattern (with surrounding formatting quirks preserved)
    • Multi-statement template producing a SyntheticBlock that gets flattened and individually formatted

Test plan

  • All 1785 C# dotnet tests pass
  • AvoidNestingTernary tests in recipes-csharp (branch quiet-badger) pass — 5/5
  • Formatting quirks outside spliced subtrees are preserved (verified by test)

Roslyn's Formatter.Format has a SuppressWrappingIfOnSingleLine heuristic
that preserves single-line formatting when all tokens are on one line.
This meant synthesized nodes with Space.Empty (produced by templates or
manual AST construction) stayed compressed on one line after formatting.

Fix: set WrappingPreserveSingleLine and WrappingKeepStatementsOnSingleLine
to false in the Roslyn options, so the formatter inserts structural newlines
(after {, before }, before else, etc.) even when the input has none.
…ement templates

- AutoFormatManualIfElseWithMaybeAutoFormatAtBlockLevel: reproduces the
  AvoidNestingTernary pattern (manual AST construction with Space.Empty,
  conditions extracted from ternary, MaybeAutoFormat at Block level)
- AutoFormatSplicedMultiStatementTemplate: multi-statement template that
  produces a SyntheticBlock, flattened and individually formatted
- AutoFormatSplicedSubtreePreservesSurroundingCode: formatting quirks in
  surrounding code are preserved when only the spliced subtree is formatted
- Rename RoslynFormatterNormalizesWhitespace to reflect new behavior
@github-project-automation github-project-automation bot moved this to In Progress in OpenRewrite Mar 28, 2026
@knutwannheden knutwannheden merged commit f71618f into main Mar 28, 2026
1 check passed
@knutwannheden knutwannheden deleted the autoformat-should-handle-synthesized-nodes-with-space.empty branch March 28, 2026 10:23
@github-project-automation github-project-automation bot moved this from In Progress to Done in OpenRewrite Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant