Skip to content

fix(psl): preserve schema line endings during reformat#5814

Open
barobaonguyen wants to merge 2 commits into
prisma:mainfrom
barobaonguyen:bounty/8548-prisma-format-line-endings
Open

fix(psl): preserve schema line endings during reformat#5814
barobaonguyen wants to merge 2 commits into
prisma:mainfrom
barobaonguyen:bounty/8548-prisma-format-line-endings

Conversation

@barobaonguyen
Copy link
Copy Markdown

Summary

Fixes prisma/prisma#8548prisma format emitted LF for every line of its output but added a CRLF as the trailing newline when the input used CRLF anywhere, leaving Windows users with mixed line endings (CRLF only on the last line).

Per maintainer guidance from @SevInf (prisma/prisma#8548 comment), this fix lives in the Rust formatter (psl/schema-ast/src/reformat.rs) rather than the TS CLI, and the line-ending decision is part of the renderer state — no post-process string replace.

Changes

  • New helper NewlineType::detect(&str) -> NewlineType in psl/schema-ast/src/ast/newline_type.rs — first newline in the input wins; mixed-ending inputs fall back to LF per maintainer note.
  • Renderer now carries a line_ending: NewlineType field; end_line() writes the configured separator natively (not LF + later replace).
  • reformat() auto-detects the input line ending and delegates to a new sibling reformat_with_line_ending() that exposes explicit control. The trailing-newline guard appends the configured ending too.

How tested

Added psl/psl/tests/reformat/reformat.rs::line_endings covering:

  • LF input → LF-only output (every line + trailing newline).
  • CRLF input → CRLF on every line including the trailing newline.
  • Mixed-ending input → LF fallback.
  • Explicit reformat_with_line_ending LF / CRLF.
  • The exact issue-8548 regression: LF body must not have a CRLF trailing newline.

⚠ Local Rust toolchain was not available, so cargo test -p psl line_endings could not be run on my machine before opening the PR. Marking this draft so CI verifies. Happy to iterate based on CI feedback and review notes.

Differs from prior PRs

  • Lives in this repo, not prisma/prisma TS CLI (#29245, #29299, #29373, #29460, #29475, #29561, #29569, #29570 — closed for wrong target).
  • Threads the line-ending decision through Renderer state, so each emitted line writes the configured separator rather than the formatter emitting LF and then string-replacing post-hoc (the rejection reason for fix(psl): preserve schema line endings during reformat #5812).

Closes prisma/prisma#8548

/claim prisma/prisma#8548

`prisma format` previously emitted LF for every line of its output but
ended the file with a CRLF when the input used CRLF anywhere. That left
Windows users with a stray `\r\n` on the last line of an otherwise-LF
file (or vice versa) and broke editors that disallow mixed line endings.

Fix it natively in the renderer instead of post-processing the output
(the maintainer rejected post-processing in prisma#5812). The reformatter now:

* Detects the input's line-ending style (first newline wins; mixed
  inputs fall back to LF) via a new `NewlineType::detect` helper.
* Threads the chosen ending through `Renderer` so each `end_line` writes
  the configured separator. The trailing-newline guard in `reformat` is
  updated to check for and append the configured ending too.
* Exposes a `reformat_with_line_ending` entry point alongside the
  existing `reformat` for callers that want to pin a specific style.

Closes prisma/prisma#8548
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9cbfd816-141e-4162-b5c6-4061c408a103

📥 Commits

Reviewing files that changed from the base of the PR and between d1a01a9 and b699af9.

📒 Files selected for processing (2)
  • psl/psl/tests/reformat/reformat.rs
  • psl/schema-ast/src/ast/newline_type.rs

Summary by CodeRabbit

  • New Features

    • The schema reformatter now preserves the input file's line-ending style (LF or CRLF) and ensures formatted output uses the detected line endings.
  • Tests

    • Added tests verifying preservation for LF-only and CRLF inputs, normalization behavior for mixed inputs, and a regression case ensuring pure-LF files remain LF at end-of-file.

Walkthrough

The PR detects input newline style (LF vs CRLF), threads it into the schema Renderer, exposes reformat_with_line_ending, makes reformat() preserve detected endings, and adds tests validating LF/CRLF behavior and regressions.

Changes

Line-ending preservation in schema reformatting

Layer / File(s) Summary
Line-ending detection contract
psl/schema-ast/src/ast/newline_type.rs
NewlineType::detect() scans input bytes and returns Unix (LF) if any bare \n is found, otherwise returns Windows (CRLF) if at least one \r\n was observed, defaulting to Unix when no newline is present.
Renderer configurable line-ending support
psl/schema-ast/src/renderer.rs
Renderer now stores a line_ending: NewlineType, its constructor accepts the line ending, and end_line() appends the configured line ending instead of a hard-coded \n.
Reformat API with line-ending preservation
psl/schema-ast/src/reformat.rs, psl/schema-ast/src/lib.rs
Adds reformat_with_line_ending(input, indent_width, line_ending) that constructs Renderer with the explicit line ending; reformat() detects the input's line ending via NewlineType::detect() and delegates. The new helper is re-exported.
Line-ending preservation tests
psl/psl/tests/reformat/reformat.rs
Adds mod line_endings with tests asserting LF-only input remains LF and ends with \n, CRLF input remains CRLF and contains no bare \n, mixed inputs normalize to LF, and a regression test ensures pure-LF bodies don't end with CRLF.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: preserving schema line endings during reformat. It directly addresses the core objective without ambiguity.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the bug fix, implementation approach, test coverage, and deferring to CI for verification.
Linked Issues check ✅ Passed The PR fully addresses issue #8548's requirements: detects input line endings, preserves them throughout formatting, ensures consistent output, and implements the fix in the Rust formatter as specified by maintainers.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the line-ending preservation issue. No unrelated modifications to other parts of the codebase were detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@barobaonguyen barobaonguyen marked this pull request as ready for review May 23, 2026 01:01
Copilot AI review requested due to automatic review settings May 23, 2026 01:01
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR updates the PSL reformatter to preserve (or explicitly control) output line endings (LF vs CRLF), addressing a regression where output could end with a different newline style than the body.

Changes:

  • Add NewlineType::detect() and thread NewlineType through Renderer so all emitted newlines are consistent.
  • Introduce reformat_with_line_ending() and have reformat() auto-detect the input’s line-ending style.
  • Add regression tests ensuring LF/CRLF consistency and correct trailing newline behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
psl/schema-ast/src/renderer.rs Renderer now emits newlines using a configured NewlineType instead of always \n.
psl/schema-ast/src/reformat.rs Adds auto-detection and a public API to force line endings; ensures trailing newline matches chosen ending.
psl/schema-ast/src/lib.rs Re-exports reformat_with_line_ending as part of the public API surface.
psl/schema-ast/src/ast/newline_type.rs Adds NewlineType::detect() to infer newline style from an input string.
psl/psl/tests/reformat/reformat.rs Adds regression tests for line-ending preservation and consistency.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +36 to +47
pub fn detect(input: &str) -> NewlineType {
let bytes = input.as_bytes();
for (i, &b) in bytes.iter().enumerate() {
if b == b'\n' {
if i > 0 && bytes[i - 1] == b'\r' {
return NewlineType::Windows;
}
return NewlineType::Unix;
}
}
NewlineType::Unix
}
Comment on lines +13 to +14
/// The output preserves the line-ending style detected from `input` (LF or
/// CRLF). Mixed-ending inputs fall back to LF. See `NewlineType::detect`.
Comment on lines +1284 to +1294
#[test]
fn mixed_input_defaults_to_lf() {
// First line uses LF, later lines use CRLF: the first newline wins
// and the reformatter falls back to LF for the whole output.
let input = format!(
"{}\n{}\r\n{}\r\n{}\r\n",
SCHEMA_LINES[0], SCHEMA_LINES[1], SCHEMA_LINES[2], SCHEMA_LINES[3]
);
let out = reformat(&input);
assert!(!out.contains('\r'), "mixed input should normalize to LF: {out:?}");
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@psl/psl/tests/reformat/reformat.rs`:
- Around line 1285-1294: Add a second test variant that mirrors
mixed_input_defaults_to_lf but with CRLF on the first line(s) and LF later to
ensure normalization behavior is symmetric; duplicate the test logic in a new
function (e.g., mixed_input_with_crlf_first) using the same SCHEMA_LINES
sequence but build input so the first newline is "\r\n" and later lines use "\n"
(or construct format with CRLF-first then LF), call reformat(&input) and assert
no '\r' in out exactly like the original test to lock the mixed-ending contract
from both directions.

In `@psl/schema-ast/src/ast/newline_type.rs`:
- Around line 36-47: The detect function currently returns on the first newline
and misclassifies mixed CRLF+LF input as Windows; update NewlineType::detect to
scan the entire byte slice and track two booleans (e.g., seen_crlf and seen_lf)
while iterating bytes, setting seen_crlf when a newline has a preceding '\r' and
seen_lf when a newline does not; after the loop return NewlineType::Unix if both
flags are set (mixed -> prefer LF), otherwise return Windows if only seen_crlf,
Unix if seen_lf, or Unix by default—adjust the logic inside detect accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3c5ba2f3-013b-4cd4-86aa-32c56f7f3c0a

📥 Commits

Reviewing files that changed from the base of the PR and between 3c6e192 and d1a01a9.

📒 Files selected for processing (5)
  • psl/psl/tests/reformat/reformat.rs
  • psl/schema-ast/src/ast/newline_type.rs
  • psl/schema-ast/src/lib.rs
  • psl/schema-ast/src/reformat.rs
  • psl/schema-ast/src/renderer.rs

Comment thread psl/psl/tests/reformat/reformat.rs
Comment thread psl/schema-ast/src/ast/newline_type.rs
Per Copilot + coderabbitai review on PR prisma#5814: detect() previously
returned on the first newline seen, so CRLF-first-then-LF inputs were
classified as Windows even though the doc contract says mixed-ending
inputs should fall back to LF. Now any bare LF anywhere in the input
forces NewlineType::Unix; an all-CRLF input still returns Windows.

Adds a regression test covering the CRLF-first-then-LF direction
(mirroring the existing LF-first-then-CRLF test) so both mixing
orders are locked down.
@barobaonguyen
Copy link
Copy Markdown
Author

Addressed in b699af9.

NewlineType::detect() previously short-circuited on the first newline seen, so a CRLF-first input with a later bare LF was classified as Windows — contradicting the doc contract that mixed-ending inputs fall back to LF. As @Copilot and @coderabbitai flagged on psl/schema-ast/src/ast/newline_type.rs:47, the implementation needed to scan the full input.

What changed

  • detect() now scans every byte: any bare LF anywhere in the input returns Unix; only an all-CRLF input returns Windows. Empty / newline-free input still defaults to Unix.
  • Doc comment on detect() rewritten to describe the actual behavior (no more "first newline wins" wording).
  • New regression test mixed_input_crlf_first_then_lf_defaults_to_lf mirrors the existing LF-first test, locking the mixed-ending contract from both directions per @coderabbitai's suggestion on reformat.rs:1294.

Local cargo test -p psl line_endings still cannot run on my machine (no Rust toolchain), so I'm relying on CI to confirm — same caveat as the original PR description.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

CodeRabbit chat interactions are restricted to organization members for this repository. Ask an organization member to interact with CodeRabbit, or set chat.allow_non_org_members: true in your configuration.

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.

prisma format ends the file with a single CRLF on windows

3 participants