Buffer diagnostic writes to `stdout` (#1900)

This commit is contained in:
Charlie Marsh 2023-01-15 19:34:15 -05:00 committed by GitHub
parent dfc2a34878
commit d71a615b18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 64 additions and 36 deletions

View File

@ -208,7 +208,7 @@ pub fn main() -> Result<ExitCode> {
cache.into(), cache.into(),
fix::FixMode::None, fix::FixMode::None,
)?; )?;
printer.write_continuously(&messages); printer.write_continuously(&messages)?;
// Configure the file watcher. // Configure the file watcher.
let (tx, rx) = channel(); let (tx, rx) = channel();
@ -238,7 +238,7 @@ pub fn main() -> Result<ExitCode> {
cache.into(), cache.into(),
fix::FixMode::None, fix::FixMode::None,
)?; )?;
printer.write_continuously(&messages); printer.write_continuously(&messages)?;
} }
} }
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),

View File

@ -1,4 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::io;
use std::io::{BufWriter, Write};
use std::path::Path; use std::path::Path;
use annotate_snippets::display_list::{DisplayList, FormatOptions}; use annotate_snippets::display_list::{DisplayList, FormatOptions};
@ -69,7 +71,7 @@ impl<'a> Printer<'a> {
} }
} }
fn post_text(&self, diagnostics: &Diagnostics) { fn post_text<T: Write>(&self, stdout: &mut T, diagnostics: &Diagnostics) -> Result<()> {
if self.log_level >= &LogLevel::Default { if self.log_level >= &LogLevel::Default {
match self.violations { match self.violations {
Violations::Show => { Violations::Show => {
@ -77,9 +79,12 @@ impl<'a> Printer<'a> {
let remaining = diagnostics.messages.len(); let remaining = diagnostics.messages.len();
let total = fixed + remaining; let total = fixed + remaining;
if fixed > 0 { if fixed > 0 {
println!("Found {total} error(s) ({fixed} fixed, {remaining} remaining)."); writeln!(
stdout,
"Found {total} error(s) ({fixed} fixed, {remaining} remaining)."
)?;
} else if remaining > 0 { } else if remaining > 0 {
println!("Found {remaining} error(s)."); writeln!(stdout, "Found {remaining} error(s).")?;
} }
if !matches!(self.autofix, fix::FixMode::Apply) { if !matches!(self.autofix, fix::FixMode::Apply) {
@ -89,7 +94,10 @@ impl<'a> Printer<'a> {
.filter(|message| message.kind.fixable()) .filter(|message| message.kind.fixable())
.count(); .count();
if num_fixable > 0 { if num_fixable > 0 {
println!("{num_fixable} potentially fixable with the --fix option."); writeln!(
stdout,
"{num_fixable} potentially fixable with the --fix option."
)?;
} }
} }
} }
@ -97,14 +105,15 @@ impl<'a> Printer<'a> {
let fixed = diagnostics.fixed; let fixed = diagnostics.fixed;
if fixed > 0 { if fixed > 0 {
if matches!(self.autofix, fix::FixMode::Apply) { if matches!(self.autofix, fix::FixMode::Apply) {
println!("Fixed {fixed} error(s)."); writeln!(stdout, "Fixed {fixed} error(s).")?;
} else if matches!(self.autofix, fix::FixMode::Diff) { } else if matches!(self.autofix, fix::FixMode::Diff) {
println!("Would fix {fixed} error(s)."); writeln!(stdout, "Would fix {fixed} error(s).")?;
} }
} }
} }
} }
} }
Ok(())
} }
pub fn write_once(&self, diagnostics: &Diagnostics) -> Result<()> { pub fn write_once(&self, diagnostics: &Diagnostics) -> Result<()> {
@ -113,18 +122,21 @@ impl<'a> Printer<'a> {
} }
if matches!(self.violations, Violations::Hide) { if matches!(self.violations, Violations::Hide) {
let mut stdout = BufWriter::new(io::stdout());
if matches!( if matches!(
self.format, self.format,
SerializationFormat::Text | SerializationFormat::Grouped SerializationFormat::Text | SerializationFormat::Grouped
) { ) {
self.post_text(diagnostics); self.post_text(&mut stdout, diagnostics)?;
} }
return Ok(()); return Ok(());
} }
let mut stdout = BufWriter::new(io::stdout());
match self.format { match self.format {
SerializationFormat::Json => { SerializationFormat::Json => {
println!( writeln!(
stdout,
"{}", "{}",
serde_json::to_string_pretty( serde_json::to_string_pretty(
&diagnostics &diagnostics
@ -145,7 +157,7 @@ impl<'a> Printer<'a> {
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
)? )?
); )?;
} }
SerializationFormat::Junit => { SerializationFormat::Junit => {
use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite}; use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite};
@ -180,14 +192,14 @@ impl<'a> Printer<'a> {
} }
report.add_test_suite(test_suite); report.add_test_suite(test_suite);
} }
println!("{}", report.to_string().unwrap()); writeln!(stdout, "{}", report.to_string().unwrap())?;
} }
SerializationFormat::Text => { SerializationFormat::Text => {
for message in &diagnostics.messages { for message in &diagnostics.messages {
print_message(message); print_message(&mut stdout, message)?;
} }
self.post_text(diagnostics); self.post_text(&mut stdout, diagnostics)?;
} }
SerializationFormat::Grouped => { SerializationFormat::Grouped => {
for (filename, messages) in group_messages_by_filename(&diagnostics.messages) { for (filename, messages) in group_messages_by_filename(&diagnostics.messages) {
@ -209,21 +221,25 @@ impl<'a> Printer<'a> {
); );
// Print the filename. // Print the filename.
println!("{}:", relativize_path(Path::new(&filename)).underline()); writeln!(
stdout,
"{}:",
relativize_path(Path::new(&filename)).underline()
)?;
// Print each message. // Print each message.
for message in messages { for message in messages {
print_grouped_message(message, row_length, column_length); print_grouped_message(&mut stdout, message, row_length, column_length)?;
} }
println!(); writeln!(stdout)?;
} }
self.post_text(diagnostics); self.post_text(&mut stdout, diagnostics)?;
} }
SerializationFormat::Github => { SerializationFormat::Github => {
// Generate error workflow command in GitHub Actions format. // Generate error workflow command in GitHub Actions format.
// See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message // See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
diagnostics.messages.iter().for_each(|message| { for message in &diagnostics.messages {
let label = format!( let label = format!(
"{}{}{}{}{}{} {} {}", "{}{}{}{}{}{} {} {}",
relativize_path(Path::new(&message.filename)), relativize_path(Path::new(&message.filename)),
@ -235,7 +251,8 @@ impl<'a> Printer<'a> {
message.kind.code().as_ref(), message.kind.code().as_ref(),
message.kind.body(), message.kind.body(),
); );
println!( writeln!(
stdout,
"::error title=Ruff \ "::error title=Ruff \
({}),file={},line={},col={},endLine={},endColumn={}::{}", ({}),file={},line={},col={},endLine={},endColumn={}::{}",
message.kind.code(), message.kind.code(),
@ -245,13 +262,13 @@ impl<'a> Printer<'a> {
message.end_location.row(), message.end_location.row(),
message.end_location.column(), message.end_location.column(),
label, label,
); )?;
}); }
} }
SerializationFormat::Gitlab => { SerializationFormat::Gitlab => {
// Generate JSON with errors in GitLab CI format // Generate JSON with errors in GitLab CI format
// https://docs.gitlab.com/ee/ci/testing/code_quality.html#implementing-a-custom-tool // https://docs.gitlab.com/ee/ci/testing/code_quality.html#implementing-a-custom-tool
println!( writeln!(stdout,
"{}", "{}",
serde_json::to_string_pretty( serde_json::to_string_pretty(
&diagnostics &diagnostics
@ -274,16 +291,18 @@ impl<'a> Printer<'a> {
) )
.collect::<Vec<_>>() .collect::<Vec<_>>()
)? )?
); )?;
} }
} }
stdout.flush()?;
Ok(()) Ok(())
} }
pub fn write_continuously(&self, diagnostics: &Diagnostics) { pub fn write_continuously(&self, diagnostics: &Diagnostics) -> Result<()> {
if matches!(self.log_level, LogLevel::Silent) { if matches!(self.log_level, LogLevel::Silent) {
return; return Ok(());
} }
if self.log_level >= &LogLevel::Default { if self.log_level >= &LogLevel::Default {
@ -293,14 +312,18 @@ impl<'a> Printer<'a> {
); );
} }
let mut stdout = BufWriter::new(io::stdout());
if !diagnostics.messages.is_empty() { if !diagnostics.messages.is_empty() {
if self.log_level >= &LogLevel::Default { if self.log_level >= &LogLevel::Default {
println!(); writeln!(stdout)?;
} }
for message in &diagnostics.messages { for message in &diagnostics.messages {
print_message(message); print_message(&mut stdout, message)?;
} }
} }
stdout.flush()?;
Ok(())
} }
pub fn clear_screen() -> Result<()> { pub fn clear_screen() -> Result<()> {
@ -329,7 +352,7 @@ fn num_digits(n: usize) -> usize {
} }
/// Print a single `Message` with full details. /// Print a single `Message` with full details.
fn print_message(message: &Message) { fn print_message<T: Write>(stdout: &mut T, message: &Message) -> Result<()> {
let label = format!( let label = format!(
"{}{}{}{}{}{} {} {}", "{}{}{}{}{}{} {} {}",
relativize_path(Path::new(&message.filename)).bold(), relativize_path(Path::new(&message.filename)).bold(),
@ -341,7 +364,7 @@ fn print_message(message: &Message) {
message.kind.code().as_ref().red().bold(), message.kind.code().as_ref().red().bold(),
message.kind.body(), message.kind.body(),
); );
println!("{label}"); writeln!(stdout, "{label}")?;
if let Some(source) = &message.source { if let Some(source) = &message.source {
let commit = message.kind.commit(); let commit = message.kind.commit();
let footer = if commit.is_some() { let footer = if commit.is_some() {
@ -353,7 +376,6 @@ fn print_message(message: &Message) {
} else { } else {
vec![] vec![]
}; };
let snippet = Snippet { let snippet = Snippet {
title: Some(Annotation { title: Some(Annotation {
label: None, label: None,
@ -383,13 +405,19 @@ fn print_message(message: &Message) {
// Skip the first line, since we format the `label` ourselves. // Skip the first line, since we format the `label` ourselves.
let message = DisplayList::from(snippet).to_string(); let message = DisplayList::from(snippet).to_string();
let (_, message) = message.split_once('\n').unwrap(); let (_, message) = message.split_once('\n').unwrap();
println!("{message}\n"); writeln!(stdout, "{message}\n")?;
} }
Ok(())
} }
/// Print a grouped `Message`, assumed to be printed in a group with others from /// Print a grouped `Message`, assumed to be printed in a group with others from
/// the same file. /// the same file.
fn print_grouped_message(message: &Message, row_length: usize, column_length: usize) { fn print_grouped_message<T: Write>(
stdout: &mut T,
message: &Message,
row_length: usize,
column_length: usize,
) -> Result<()> {
let label = format!( let label = format!(
" {}{}{}{}{} {} {}", " {}{}{}{}{} {} {}",
" ".repeat(row_length - num_digits(message.location.row())), " ".repeat(row_length - num_digits(message.location.row())),
@ -400,7 +428,7 @@ fn print_grouped_message(message: &Message, row_length: usize, column_length: us
message.kind.code().as_ref().red().bold(), message.kind.code().as_ref().red().bold(),
message.kind.body(), message.kind.body(),
); );
println!("{label}"); writeln!(stdout, "{label}")?;
if let Some(source) = &message.source { if let Some(source) = &message.source {
let commit = message.kind.commit(); let commit = message.kind.commit();
let footer = if commit.is_some() { let footer = if commit.is_some() {
@ -412,7 +440,6 @@ fn print_grouped_message(message: &Message, row_length: usize, column_length: us
} else { } else {
vec![] vec![]
}; };
let snippet = Snippet { let snippet = Snippet {
title: Some(Annotation { title: Some(Annotation {
label: None, label: None,
@ -443,6 +470,7 @@ fn print_grouped_message(message: &Message, row_length: usize, column_length: us
let message = DisplayList::from(snippet).to_string(); let message = DisplayList::from(snippet).to_string();
let (_, message) = message.split_once('\n').unwrap(); let (_, message) = message.split_once('\n').unwrap();
let message = textwrap::indent(message, " "); let message = textwrap::indent(message, " ");
println!("{message}"); writeln!(stdout, "{message}")?;
} }
Ok(())
} }