Skip to content

fix(cli): sanitize newlines in min-text output format#3425

Open
Karnav018 wants to merge 2 commits into
facebook:mainfrom
Karnav018:fix-min-text-newlines
Open

fix(cli): sanitize newlines in min-text output format#3425
Karnav018 wants to merge 2 commits into
facebook:mainfrom
Karnav018:fix-min-text-newlines

Conversation

@Karnav018
Copy link
Copy Markdown

Summary

This PR addresses an issue where the --output-format min-text format breaks its single-line execution contract when handling multi-line error descriptions (such as evaluation errors across union types).

Because min-text is specifically designed for predictable line-by-line parsing by downstream automated scripts, internal newlines (\n) present within error messages break external tools.

Changes Made

Modified pyrefly/lib/error/error.rs across two methods handling output serialization:

  • Error::write_line(): Sanitizes the msg_header using .replace('\n', " ") when verbose execution is disabled (representing min-text mode).
  • Error::print_colors(): Mirrors the sanitization mapping to maintain terminal output consistency across all stdout/stderr channels.

Verbose mode, JSON mode, and GitHub actions format remain entirely untouched, preserving their native structured data or human-oriented pretty-printing layouts.

Fixes the issue where multi-line errors broke the one-error-per-line parsing agreement in min-text.

Test Plan

Automated Coverage

Added a new dedicated integration unit test inside pyrefly/lib/error/error.rs: test_mintext_format_sanitizes_newlines(). This test:

  1. Constructs an explicit error message incorporating deliberate internal newlines (simulating union type attribute errors).
  2. Sets verbose=false to emulate the min-text execution logic.
  3. Asserts that write_line() squashes the payload into a singular string line while retaining text integrity.

Execution Results

Verified locally across the local environment suite:

# Target the specific test package
cargo test error::tests::test_mintext_format_sanitizes_newlines

# Verify structural health of the error module
cargo test -p pyrefly error::tests

# Execute the parent harness check
python3 test.py

Copilot AI review requested due to automatic review settings May 16, 2026 17:17
@meta-cla
Copy link
Copy Markdown

meta-cla Bot commented May 16, 2026

Hi @Karnav018!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

Copy link
Copy Markdown

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

This PR addresses the --output-format min-text contract of one error per line by replacing embedded newlines in msg_header with spaces before formatting in both the plain (write_line) and colorized (print_colors) min-text code paths. Verbose, JSON, and GitHub Actions outputs are left unchanged. A unit test is added that constructs an error with an embedded newline and asserts the resulting min-text output collapses to a single line.

Changes:

  • Sanitize \n → space in Error::write_line non-verbose branch.
  • Mirror the same sanitization in Error::print_colors non-verbose branch.
  • Add test_mintext_format_sanitizes_newlines unit test.
Comments suppressed due to low confidence (1)

pyrefly/lib/error/error.rs:133

  • The identical sanitization (self.msg_header.replace('\n', " ")) is duplicated here and in write_line at line 101. Consider extracting it into a small helper (e.g., a sanitized_header() method on Error, or sanitizing once at construction in Error::new) so the two code paths can't drift apart, and so any future addition (such as also stripping \r) only has to be made in one place.
            let sanitized_msg = self.msg_header.replace('\n', " ");

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

Comment thread pyrefly/lib/error/error.rs Outdated
"second part of message should be present"
);
// Verify the format is still min-text: "SEVERITY path:range: message [kind]"
assert!(error_line.starts_with("ERROR test.py:1:1-11:"), "output should start with 'ERROR test.py:1:1-11:'");
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for catching this. We removed test_mintext_format_sanitizes_newlines entirely when moving to the header invariant approach, so this assertion is no longer present. Marking this resolved.

Comment thread pyrefly/lib/error/error.rs Outdated
Comment thread pyrefly/lib/error/error.rs Outdated
Comment on lines +99 to +101
// In min-text format, sanitize the message to ensure one error per line
// by replacing newlines with spaces.
let sanitized_msg = self.msg_header.replace('\n', " ");
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

We removed the min-text sanitization entirely and now enforce the invariant at construction: headers must not contain \n or \r. Error::new asserts !header.contains(['\n', '\r']), and test_error_header_rejects_newlines covers CRLF explicitly. This prevents CR from ever reaching output (including the spot you called out around line 133) and keeps min-text single-line without post-processing.

@meta-cla
Copy link
Copy Markdown

meta-cla Bot commented May 16, 2026

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

@meta-cla meta-cla Bot added the cla signed label May 16, 2026
Copy link
Copy Markdown
Contributor

@rchen152 rchen152 left a comment

Choose a reason for hiding this comment

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

Thanks for the PR! I'd actually prefer to fix this issue in a different way, by fixing specific problematic error messages rather than sanitizing them after the fact. The reason is that the sanitization approach will cause us to end up with really long messages that are technically single-line but in practice are long and hard-to-read.

Pyrefly error messages include a "header" line and zero or more "detail" lines. As I understand it, the problem is that we sometimes put the entirety of a multi-line error message in the header. So the approach I'd recommend is (1) adding an assertion when building a new error that the header does not contain any newlines, and (2) for any existing error messages that violate this assertion (running the tests should flush them out), changing the code that logs the error to properly split the message between header and details.

Why: min-text output requires one error per line, but some errors built multiline headers.

What: assert headers contain no newline/CR and split missing-attribute union failures into header plus details.

Why it works: invalid headers are rejected at construction and details carry the extra lines.
@github-actions github-actions Bot added size/xs and removed size/s labels May 18, 2026
@Karnav018 Karnav018 requested a review from rchen152 May 18, 2026 13:51
Copy link
Copy Markdown
Contributor

@rchen152 rchen152 left a comment

Choose a reason for hiding this comment

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

Thanks! Could you (1) fix the test failures caused by the attribute error message changing and (2) add the error kind to the assert! message? Once you do that, I can import the PR and patch the mypy_primer failures internally, since the fixes should be pretty mechanical at that point.

let display_range = module.display_range(range);
assert!(
!header.contains(['\n', '\r']),
"error header must not contain newlines"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should include the error kind in this message, so that assertion failures are easier to debug.

@rchen152 rchen152 self-assigned this May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants