mirror of https://github.com/astral-sh/ruff
Remove `TextEmitter` (#20595)
## Summary Addresses https://github.com/astral-sh/ruff/pull/20443#discussion_r2381237640 by factoring out the `match` on the ruff output format in a way that should be reusable by the formatter. I didn't think this was going to work at first, but the fact that the config holds options that apply only to certain output formats works in our favor here. We can set up a single config for all of the output formats and then use `try_from` to convert the `OutputFormat` to a `DiagnosticFormat` later. ## Test Plan Existing tests, plus a few new ones to make sure relocating the `SHOW_FIX_SUMMARY` rendering worked, that was untested before. I deleted a bunch of test code along with the `text` module, but I believe all of it is now well-covered by the `full` and `concise` tests in `ruff_db`. I also merged this branch into https://github.com/astral-sh/ruff/pull/20443 locally and made sure that the API actually helps. `render_diagnostics` dropped in perfectly and passed the tests there too.
This commit is contained in:
parent
1cf19732b9
commit
00c8851ef8
|
|
@ -227,7 +227,8 @@ mod test {
|
|||
use rustc_hash::FxHashMap;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use ruff_linter::message::{Emitter, EmitterContext, TextEmitter};
|
||||
use ruff_db::diagnostic::{DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics};
|
||||
use ruff_linter::message::EmitterContext;
|
||||
use ruff_linter::registry::Rule;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use ruff_linter::settings::{LinterSettings, flags};
|
||||
|
|
@ -280,19 +281,16 @@ mod test {
|
|||
UnsafeFixes::Enabled,
|
||||
)
|
||||
.unwrap();
|
||||
let mut output = Vec::new();
|
||||
|
||||
TextEmitter::default()
|
||||
.with_show_fix_status(true)
|
||||
.with_color(false)
|
||||
.emit(
|
||||
&mut output,
|
||||
&diagnostics.inner,
|
||||
&EmitterContext::new(&FxHashMap::default()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let messages = String::from_utf8(output).unwrap();
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.format(DiagnosticFormat::Concise)
|
||||
.hide_severity(true);
|
||||
let messages = DisplayDiagnostics::new(
|
||||
&EmitterContext::new(&FxHashMap::default()),
|
||||
&config,
|
||||
&diagnostics.inner,
|
||||
)
|
||||
.to_string();
|
||||
|
||||
insta::with_settings!({
|
||||
omit_expression => true,
|
||||
|
|
|
|||
|
|
@ -10,12 +10,11 @@ use ruff_linter::linter::FixTable;
|
|||
use serde::Serialize;
|
||||
|
||||
use ruff_db::diagnostic::{
|
||||
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics,
|
||||
DisplayGithubDiagnostics, GithubRenderer, SecondaryCode,
|
||||
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics, SecondaryCode,
|
||||
};
|
||||
use ruff_linter::fs::relativize_path;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::message::{Emitter, EmitterContext, GroupedEmitter, SarifEmitter, TextEmitter};
|
||||
use ruff_linter::message::{EmitterContext, render_diagnostics};
|
||||
use ruff_linter::notify_user;
|
||||
use ruff_linter::settings::flags::{self};
|
||||
use ruff_linter::settings::types::{OutputFormat, UnsafeFixes};
|
||||
|
|
@ -225,86 +224,28 @@ impl Printer {
|
|||
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
||||
let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes);
|
||||
|
||||
let config = DisplayDiagnosticConfig::default().preview(preview);
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.preview(preview)
|
||||
.hide_severity(true)
|
||||
.color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize())
|
||||
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
||||
.with_fix_applicability(self.unsafe_fixes.required_applicability())
|
||||
.show_fix_diff(preview);
|
||||
|
||||
match self.format {
|
||||
OutputFormat::Json => {
|
||||
let config = config.format(DiagnosticFormat::Json);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Rdjson => {
|
||||
let config = config.format(DiagnosticFormat::Rdjson);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::JsonLines => {
|
||||
let config = config.format(DiagnosticFormat::JsonLines);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Junit => {
|
||||
let config = config.format(DiagnosticFormat::Junit);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Concise | OutputFormat::Full => {
|
||||
TextEmitter::default()
|
||||
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
||||
.with_show_fix_diff(self.format == OutputFormat::Full && preview)
|
||||
.with_show_source(self.format == OutputFormat::Full)
|
||||
.with_fix_applicability(self.unsafe_fixes.required_applicability())
|
||||
.with_preview(preview)
|
||||
.emit(writer, &diagnostics.inner, &context)?;
|
||||
render_diagnostics(writer, self.format, config, &context, &diagnostics.inner)?;
|
||||
|
||||
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
||||
if !diagnostics.fixed.is_empty() {
|
||||
writeln!(writer)?;
|
||||
print_fix_summary(writer, &diagnostics.fixed)?;
|
||||
writeln!(writer)?;
|
||||
}
|
||||
if matches!(
|
||||
self.format,
|
||||
OutputFormat::Full | OutputFormat::Concise | OutputFormat::Grouped
|
||||
) {
|
||||
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
||||
if !diagnostics.fixed.is_empty() {
|
||||
writeln!(writer)?;
|
||||
print_fix_summary(writer, &diagnostics.fixed)?;
|
||||
writeln!(writer)?;
|
||||
}
|
||||
|
||||
self.write_summary_text(writer, diagnostics)?;
|
||||
}
|
||||
OutputFormat::Grouped => {
|
||||
GroupedEmitter::default()
|
||||
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
||||
.with_unsafe_fixes(self.unsafe_fixes)
|
||||
.emit(writer, &diagnostics.inner, &context)?;
|
||||
|
||||
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
||||
if !diagnostics.fixed.is_empty() {
|
||||
writeln!(writer)?;
|
||||
print_fix_summary(writer, &diagnostics.fixed)?;
|
||||
writeln!(writer)?;
|
||||
}
|
||||
}
|
||||
self.write_summary_text(writer, diagnostics)?;
|
||||
}
|
||||
OutputFormat::Github => {
|
||||
let renderer = GithubRenderer::new(&context, "Ruff");
|
||||
let value = DisplayGithubDiagnostics::new(&renderer, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Gitlab => {
|
||||
let config = config.format(DiagnosticFormat::Gitlab);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Pylint => {
|
||||
let config = config.format(DiagnosticFormat::Pylint);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Azure => {
|
||||
let config = config.format(DiagnosticFormat::Azure);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Sarif => {
|
||||
SarifEmitter.emit(writer, &diagnostics.inner, &context)?;
|
||||
}
|
||||
self.write_summary_text(writer, diagnostics)?;
|
||||
}
|
||||
|
||||
writer.flush()?;
|
||||
|
|
@ -448,11 +389,22 @@ impl Printer {
|
|||
}
|
||||
|
||||
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
||||
TextEmitter::default()
|
||||
let format = if preview {
|
||||
DiagnosticFormat::Full
|
||||
} else {
|
||||
DiagnosticFormat::Concise
|
||||
};
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.hide_severity(true)
|
||||
.color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize())
|
||||
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
||||
.with_show_source(preview)
|
||||
.with_fix_applicability(self.unsafe_fixes.required_applicability())
|
||||
.emit(writer, &diagnostics.inner, &context)?;
|
||||
.format(format)
|
||||
.with_fix_applicability(self.unsafe_fixes.required_applicability());
|
||||
write!(
|
||||
writer,
|
||||
"{}",
|
||||
DisplayDiagnostics::new(&context, &config, &diagnostics.inner)
|
||||
)?;
|
||||
}
|
||||
writer.flush()?;
|
||||
|
||||
|
|
|
|||
|
|
@ -6199,6 +6199,36 @@ match 42: # invalid-syntax
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case::test_case("concise"; "concise_show_fixes")]
|
||||
#[test_case::test_case("full"; "full_show_fixes")]
|
||||
#[test_case::test_case("grouped"; "grouped_show_fixes")]
|
||||
fn output_format_show_fixes(output_format: &str) -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let input = tempdir.path().join("input.py");
|
||||
fs::write(&input, "import os # F401")?;
|
||||
|
||||
let snapshot = format!("output_format_show_fixes_{output_format}");
|
||||
|
||||
assert_cmd_snapshot!(
|
||||
snapshot,
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args([
|
||||
"check",
|
||||
"--no-cache",
|
||||
"--output-format",
|
||||
output_format,
|
||||
"--select",
|
||||
"F401",
|
||||
"--fix",
|
||||
"--show-fixes",
|
||||
"input.py",
|
||||
])
|
||||
.current_dir(&tempdir),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn up045_nested_optional_flatten_all() {
|
||||
let contents = "\
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: crates/ruff/tests/lint.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
- check
|
||||
- "--no-cache"
|
||||
- "--output-format"
|
||||
- concise
|
||||
- "--select"
|
||||
- F401
|
||||
- "--fix"
|
||||
- "--show-fixes"
|
||||
- input.py
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
Fixed 1 error:
|
||||
- input.py:
|
||||
1 × F401 (unused-import)
|
||||
|
||||
Found 1 error (1 fixed, 0 remaining).
|
||||
|
||||
----- stderr -----
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: crates/ruff/tests/lint.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
- check
|
||||
- "--no-cache"
|
||||
- "--output-format"
|
||||
- full
|
||||
- "--select"
|
||||
- F401
|
||||
- "--fix"
|
||||
- "--show-fixes"
|
||||
- input.py
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
Fixed 1 error:
|
||||
- input.py:
|
||||
1 × F401 (unused-import)
|
||||
|
||||
Found 1 error (1 fixed, 0 remaining).
|
||||
|
||||
----- stderr -----
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
source: crates/ruff/tests/lint.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
- check
|
||||
- "--no-cache"
|
||||
- "--output-format"
|
||||
- grouped
|
||||
- "--select"
|
||||
- F401
|
||||
- "--fix"
|
||||
- "--show-fixes"
|
||||
- input.py
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
Fixed 1 error:
|
||||
- input.py:
|
||||
1 × F401 (unused-import)
|
||||
|
||||
Found 1 error (1 fixed, 0 remaining).
|
||||
|
||||
----- stderr -----
|
||||
|
|
@ -1353,7 +1353,7 @@ impl DisplayDiagnosticConfig {
|
|||
}
|
||||
|
||||
/// Whether to show a fix's availability or not.
|
||||
pub fn show_fix_status(self, yes: bool) -> DisplayDiagnosticConfig {
|
||||
pub fn with_show_fix_status(self, yes: bool) -> DisplayDiagnosticConfig {
|
||||
DisplayDiagnosticConfig {
|
||||
show_fix_status: yes,
|
||||
..self
|
||||
|
|
@ -1374,12 +1374,20 @@ impl DisplayDiagnosticConfig {
|
|||
/// availability for unsafe or display-only fixes.
|
||||
///
|
||||
/// Note that this option is currently ignored when `hide_severity` is false.
|
||||
pub fn fix_applicability(self, applicability: Applicability) -> DisplayDiagnosticConfig {
|
||||
pub fn with_fix_applicability(self, applicability: Applicability) -> DisplayDiagnosticConfig {
|
||||
DisplayDiagnosticConfig {
|
||||
fix_applicability: applicability,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show_fix_status(&self) -> bool {
|
||||
self.show_fix_status
|
||||
}
|
||||
|
||||
pub fn fix_applicability(&self) -> Applicability {
|
||||
self.fix_applicability
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DisplayDiagnosticConfig {
|
||||
|
|
|
|||
|
|
@ -2618,7 +2618,7 @@ watermelon
|
|||
/// Show fix availability when rendering.
|
||||
pub(super) fn show_fix_status(&mut self, yes: bool) {
|
||||
let mut config = std::mem::take(&mut self.config);
|
||||
config = config.show_fix_status(yes);
|
||||
config = config.with_show_fix_status(yes);
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
|
|
@ -2632,7 +2632,7 @@ watermelon
|
|||
/// The lowest fix applicability to show when rendering.
|
||||
pub(super) fn fix_applicability(&mut self, applicability: Applicability) {
|
||||
let mut config = std::mem::take(&mut self.config);
|
||||
config = config.fix_applicability(applicability);
|
||||
config = config.with_fix_applicability(applicability);
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,17 +6,25 @@ use std::num::NonZeroUsize;
|
|||
use colored::Colorize;
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_diagnostics::Applicability;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{LineColumn, OneIndexed};
|
||||
|
||||
use crate::fs::relativize_path;
|
||||
use crate::message::{Emitter, EmitterContext};
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GroupedEmitter {
|
||||
show_fix_status: bool,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
applicability: Applicability,
|
||||
}
|
||||
|
||||
impl Default for GroupedEmitter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
show_fix_status: false,
|
||||
applicability: Applicability::Safe,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupedEmitter {
|
||||
|
|
@ -27,8 +35,8 @@ impl GroupedEmitter {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_unsafe_fixes(mut self, unsafe_fixes: UnsafeFixes) -> Self {
|
||||
self.unsafe_fixes = unsafe_fixes;
|
||||
pub fn with_applicability(mut self, applicability: Applicability) -> Self {
|
||||
self.applicability = applicability;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -67,7 +75,7 @@ impl Emitter for GroupedEmitter {
|
|||
notebook_index: context.notebook_index(&message.expect_ruff_filename()),
|
||||
message,
|
||||
show_fix_status: self.show_fix_status,
|
||||
unsafe_fixes: self.unsafe_fixes,
|
||||
applicability: self.applicability,
|
||||
row_length,
|
||||
column_length,
|
||||
}
|
||||
|
|
@ -114,7 +122,7 @@ fn group_diagnostics_by_filename(
|
|||
struct DisplayGroupedMessage<'a> {
|
||||
message: MessageWithLocation<'a>,
|
||||
show_fix_status: bool,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
applicability: Applicability,
|
||||
row_length: NonZeroUsize,
|
||||
column_length: NonZeroUsize,
|
||||
notebook_index: Option<&'a NotebookIndex>,
|
||||
|
|
@ -162,7 +170,7 @@ impl Display for DisplayGroupedMessage<'_> {
|
|||
code_and_body = RuleCodeAndBody {
|
||||
message,
|
||||
show_fix_status: self.show_fix_status,
|
||||
unsafe_fixes: self.unsafe_fixes
|
||||
applicability: self.applicability
|
||||
},
|
||||
)?;
|
||||
|
||||
|
|
@ -173,7 +181,7 @@ impl Display for DisplayGroupedMessage<'_> {
|
|||
pub(super) struct RuleCodeAndBody<'a> {
|
||||
pub(crate) message: &'a Diagnostic,
|
||||
pub(crate) show_fix_status: bool,
|
||||
pub(crate) unsafe_fixes: UnsafeFixes,
|
||||
pub(crate) applicability: Applicability,
|
||||
}
|
||||
|
||||
impl Display for RuleCodeAndBody<'_> {
|
||||
|
|
@ -181,7 +189,7 @@ impl Display for RuleCodeAndBody<'_> {
|
|||
if self.show_fix_status {
|
||||
if let Some(fix) = self.message.fix() {
|
||||
// Do not display an indicator for inapplicable fixes
|
||||
if fix.applies(self.unsafe_fixes.required_applicability()) {
|
||||
if fix.applies(self.applicability) {
|
||||
if let Some(code) = self.message.secondary_code() {
|
||||
write!(f, "{} ", code.red().bold())?;
|
||||
}
|
||||
|
|
@ -217,11 +225,12 @@ impl Display for RuleCodeAndBody<'_> {
|
|||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use ruff_diagnostics::Applicability;
|
||||
|
||||
use crate::message::GroupedEmitter;
|
||||
use crate::message::tests::{
|
||||
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
|
||||
};
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
|
|
@ -251,7 +260,7 @@ mod tests {
|
|||
fn fix_status_unsafe() {
|
||||
let mut emitter = GroupedEmitter::default()
|
||||
.with_show_fix_status(true)
|
||||
.with_unsafe_fixes(UnsafeFixes::Enabled);
|
||||
.with_applicability(Applicability::Unsafe);
|
||||
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
|
||||
|
||||
assert_snapshot!(content);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ use std::io::Write;
|
|||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_db::diagnostic::{
|
||||
Annotation, Diagnostic, DiagnosticId, FileResolver, Input, LintName, SecondaryCode, Severity,
|
||||
Span, UnifiedFile,
|
||||
Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig,
|
||||
DisplayDiagnostics, DisplayGithubDiagnostics, FileResolver, GithubRenderer, Input, LintName,
|
||||
SecondaryCode, Severity, Span, UnifiedFile,
|
||||
};
|
||||
use ruff_db::files::File;
|
||||
|
||||
|
|
@ -14,14 +15,13 @@ use ruff_notebook::NotebookIndex;
|
|||
use ruff_source_file::SourceFile;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
pub use sarif::SarifEmitter;
|
||||
pub use text::TextEmitter;
|
||||
|
||||
use crate::Fix;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::types::{OutputFormat, RuffOutputFormat};
|
||||
|
||||
mod grouped;
|
||||
mod sarif;
|
||||
mod text;
|
||||
|
||||
/// Creates a `Diagnostic` from a syntax error, with the format expected by Ruff.
|
||||
///
|
||||
|
|
@ -160,14 +160,48 @@ impl<'a> EmitterContext<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn render_diagnostics(
|
||||
writer: &mut dyn Write,
|
||||
format: OutputFormat,
|
||||
config: DisplayDiagnosticConfig,
|
||||
context: &EmitterContext<'_>,
|
||||
diagnostics: &[Diagnostic],
|
||||
) -> std::io::Result<()> {
|
||||
match DiagnosticFormat::try_from(format) {
|
||||
Ok(format) => {
|
||||
let config = config.format(format);
|
||||
let value = DisplayDiagnostics::new(context, &config, diagnostics);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
Err(RuffOutputFormat::Github) => {
|
||||
let renderer = GithubRenderer::new(context, "Ruff");
|
||||
let value = DisplayGithubDiagnostics::new(&renderer, diagnostics);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
Err(RuffOutputFormat::Grouped) => {
|
||||
GroupedEmitter::default()
|
||||
.with_show_fix_status(config.show_fix_status())
|
||||
.with_applicability(config.fix_applicability())
|
||||
.emit(writer, diagnostics, context)
|
||||
.map_err(std::io::Error::other)?;
|
||||
}
|
||||
Err(RuffOutputFormat::Sarif) => {
|
||||
SarifEmitter
|
||||
.emit(writer, diagnostics, context)
|
||||
.map_err(std::io::Error::other)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_python_parser::{Mode, ParseOptions, parse_unchecked};
|
||||
use ruff_source_file::{OneIndexed, SourceFileBuilder};
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use crate::codes::Rule;
|
||||
|
|
@ -257,104 +291,6 @@ def fibonacci(n):
|
|||
vec![unused_import, unused_variable, undefined_name]
|
||||
}
|
||||
|
||||
pub(super) fn create_notebook_diagnostics()
|
||||
-> (Vec<Diagnostic>, FxHashMap<String, NotebookIndex>) {
|
||||
let notebook = r"# cell 1
|
||||
import os
|
||||
# cell 2
|
||||
import math
|
||||
|
||||
print('hello world')
|
||||
# cell 3
|
||||
def foo():
|
||||
print()
|
||||
x = 1
|
||||
";
|
||||
|
||||
let notebook_source = SourceFileBuilder::new("notebook.ipynb", notebook).finish();
|
||||
|
||||
let unused_import_os_start = TextSize::from(16);
|
||||
let unused_import_os = create_lint_diagnostic(
|
||||
"`os` imported but unused",
|
||||
Some("Remove unused import: `os`"),
|
||||
TextRange::new(unused_import_os_start, TextSize::from(18)),
|
||||
Some(Fix::safe_edit(Edit::range_deletion(TextRange::new(
|
||||
TextSize::from(9),
|
||||
TextSize::from(19),
|
||||
)))),
|
||||
None,
|
||||
notebook_source.clone(),
|
||||
Some(unused_import_os_start),
|
||||
Rule::UnusedImport,
|
||||
);
|
||||
|
||||
let unused_import_math_start = TextSize::from(35);
|
||||
let unused_import_math = create_lint_diagnostic(
|
||||
"`math` imported but unused",
|
||||
Some("Remove unused import: `math`"),
|
||||
TextRange::new(unused_import_math_start, TextSize::from(39)),
|
||||
Some(Fix::safe_edit(Edit::range_deletion(TextRange::new(
|
||||
TextSize::from(28),
|
||||
TextSize::from(40),
|
||||
)))),
|
||||
None,
|
||||
notebook_source.clone(),
|
||||
Some(unused_import_math_start),
|
||||
Rule::UnusedImport,
|
||||
);
|
||||
|
||||
let unused_variable_start = TextSize::from(98);
|
||||
let unused_variable = create_lint_diagnostic(
|
||||
"Local variable `x` is assigned to but never used",
|
||||
Some("Remove assignment to unused variable `x`"),
|
||||
TextRange::new(unused_variable_start, TextSize::from(99)),
|
||||
Some(Fix::unsafe_edit(Edit::deletion(
|
||||
TextSize::from(94),
|
||||
TextSize::from(104),
|
||||
))),
|
||||
None,
|
||||
notebook_source,
|
||||
Some(unused_variable_start),
|
||||
Rule::UnusedVariable,
|
||||
);
|
||||
|
||||
let mut notebook_indexes = FxHashMap::default();
|
||||
notebook_indexes.insert(
|
||||
"notebook.ipynb".to_string(),
|
||||
NotebookIndex::new(
|
||||
vec![
|
||||
OneIndexed::from_zero_indexed(0),
|
||||
OneIndexed::from_zero_indexed(0),
|
||||
OneIndexed::from_zero_indexed(1),
|
||||
OneIndexed::from_zero_indexed(1),
|
||||
OneIndexed::from_zero_indexed(1),
|
||||
OneIndexed::from_zero_indexed(1),
|
||||
OneIndexed::from_zero_indexed(2),
|
||||
OneIndexed::from_zero_indexed(2),
|
||||
OneIndexed::from_zero_indexed(2),
|
||||
OneIndexed::from_zero_indexed(2),
|
||||
],
|
||||
vec![
|
||||
OneIndexed::from_zero_indexed(0),
|
||||
OneIndexed::from_zero_indexed(1),
|
||||
OneIndexed::from_zero_indexed(0),
|
||||
OneIndexed::from_zero_indexed(1),
|
||||
OneIndexed::from_zero_indexed(2),
|
||||
OneIndexed::from_zero_indexed(3),
|
||||
OneIndexed::from_zero_indexed(0),
|
||||
OneIndexed::from_zero_indexed(1),
|
||||
OneIndexed::from_zero_indexed(2),
|
||||
OneIndexed::from_zero_indexed(3),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
(
|
||||
vec![unused_import_os, unused_import_math, unused_variable],
|
||||
notebook_indexes,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn capture_emitter_output(
|
||||
emitter: &mut dyn Emitter,
|
||||
diagnostics: &[Diagnostic],
|
||||
|
|
@ -366,16 +302,4 @@ def foo():
|
|||
|
||||
String::from_utf8(output).expect("Output to be valid UTF-8")
|
||||
}
|
||||
|
||||
pub(super) fn capture_emitter_notebook_output(
|
||||
emitter: &mut dyn Emitter,
|
||||
diagnostics: &[Diagnostic],
|
||||
notebook_indexes: &FxHashMap<String, NotebookIndex>,
|
||||
) -> String {
|
||||
let context = EmitterContext::new(notebook_indexes);
|
||||
let mut output: Vec<u8> = Vec::new();
|
||||
emitter.emit(&mut output, diagnostics, &context).unwrap();
|
||||
|
||||
String::from_utf8(output).expect("Output to be valid UTF-8")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/message/text.rs
|
||||
expression: content
|
||||
---
|
||||
F401 `os` imported but unused
|
||||
--> fib.py:1:8
|
||||
|
|
||||
1 | import os
|
||||
| ^^
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
|
||||
F841 Local variable `x` is assigned to but never used
|
||||
--> fib.py:6:5
|
||||
|
|
||||
4 | def fibonacci(n):
|
||||
5 | """Compute the nth number in the Fibonacci sequence."""
|
||||
6 | x = 1
|
||||
| ^
|
||||
7 | if n == 0:
|
||||
8 | return 0
|
||||
|
|
||||
help: Remove assignment to unused variable `x`
|
||||
|
||||
F821 Undefined name `a`
|
||||
--> undef.py:1:4
|
||||
|
|
||||
1 | if a == 1: pass
|
||||
| ^
|
||||
|
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/message/text.rs
|
||||
expression: content
|
||||
---
|
||||
F401 `os` imported but unused
|
||||
--> fib.py:1:8
|
||||
|
|
||||
1 | import os
|
||||
| ^^
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
|
||||
F841 Local variable `x` is assigned to but never used
|
||||
--> fib.py:6:5
|
||||
|
|
||||
4 | def fibonacci(n):
|
||||
5 | """Compute the nth number in the Fibonacci sequence."""
|
||||
6 | x = 1
|
||||
| ^
|
||||
7 | if n == 0:
|
||||
8 | return 0
|
||||
|
|
||||
help: Remove assignment to unused variable `x`
|
||||
|
||||
F821 Undefined name `a`
|
||||
--> undef.py:1:4
|
||||
|
|
||||
1 | if a == 1: pass
|
||||
| ^
|
||||
|
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/message/text.rs
|
||||
expression: content
|
||||
---
|
||||
F401 [*] `os` imported but unused
|
||||
--> fib.py:1:8
|
||||
|
|
||||
1 | import os
|
||||
| ^^
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
|
||||
F841 [*] Local variable `x` is assigned to but never used
|
||||
--> fib.py:6:5
|
||||
|
|
||||
4 | def fibonacci(n):
|
||||
5 | """Compute the nth number in the Fibonacci sequence."""
|
||||
6 | x = 1
|
||||
| ^
|
||||
7 | if n == 0:
|
||||
8 | return 0
|
||||
|
|
||||
help: Remove assignment to unused variable `x`
|
||||
|
||||
F821 Undefined name `a`
|
||||
--> undef.py:1:4
|
||||
|
|
||||
1 | if a == 1: pass
|
||||
| ^
|
||||
|
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/message/text.rs
|
||||
expression: content
|
||||
---
|
||||
F401 [*] `os` imported but unused
|
||||
--> notebook.ipynb:cell 1:2:8
|
||||
|
|
||||
1 | # cell 1
|
||||
2 | import os
|
||||
| ^^
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
|
||||
F401 [*] `math` imported but unused
|
||||
--> notebook.ipynb:cell 2:2:8
|
||||
|
|
||||
1 | # cell 2
|
||||
2 | import math
|
||||
| ^^^^
|
||||
3 |
|
||||
4 | print('hello world')
|
||||
|
|
||||
help: Remove unused import: `math`
|
||||
|
||||
F841 [*] Local variable `x` is assigned to but never used
|
||||
--> notebook.ipynb:cell 3:4:5
|
||||
|
|
||||
2 | def foo():
|
||||
3 | print()
|
||||
4 | x = 1
|
||||
| ^
|
||||
|
|
||||
help: Remove assignment to unused variable `x`
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/message/text.rs
|
||||
expression: content
|
||||
---
|
||||
invalid-syntax: Expected one or more symbol names after import
|
||||
--> syntax_errors.py:1:15
|
||||
|
|
||||
1 | from os import
|
||||
| ^
|
||||
2 |
|
||||
3 | if call(foo
|
||||
|
|
||||
|
||||
invalid-syntax: Expected ')', found newline
|
||||
--> syntax_errors.py:3:12
|
||||
|
|
||||
1 | from os import
|
||||
2 |
|
||||
3 | if call(foo
|
||||
| ^
|
||||
4 | def bar():
|
||||
5 | pass
|
||||
|
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
use std::io::Write;
|
||||
|
||||
use ruff_db::diagnostic::{
|
||||
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics,
|
||||
};
|
||||
use ruff_diagnostics::Applicability;
|
||||
|
||||
use crate::message::{Emitter, EmitterContext};
|
||||
|
||||
pub struct TextEmitter {
|
||||
config: DisplayDiagnosticConfig,
|
||||
}
|
||||
|
||||
impl Default for TextEmitter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
config: DisplayDiagnosticConfig::default()
|
||||
.format(DiagnosticFormat::Concise)
|
||||
.hide_severity(true)
|
||||
.color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextEmitter {
|
||||
#[must_use]
|
||||
pub fn with_show_fix_status(mut self, show_fix_status: bool) -> Self {
|
||||
self.config = self.config.show_fix_status(show_fix_status);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_show_fix_diff(mut self, show_fix_diff: bool) -> Self {
|
||||
self.config = self.config.show_fix_diff(show_fix_diff);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_show_source(mut self, show_source: bool) -> Self {
|
||||
self.config = self.config.format(if show_source {
|
||||
DiagnosticFormat::Full
|
||||
} else {
|
||||
DiagnosticFormat::Concise
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_fix_applicability(mut self, applicability: Applicability) -> Self {
|
||||
self.config = self.config.fix_applicability(applicability);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_preview(mut self, preview: bool) -> Self {
|
||||
self.config = self.config.preview(preview);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_color(mut self, color: bool) -> Self {
|
||||
self.config = self.config.color(color);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Emitter for TextEmitter {
|
||||
fn emit(
|
||||
&mut self,
|
||||
writer: &mut dyn Write,
|
||||
diagnostics: &[Diagnostic],
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
write!(
|
||||
writer,
|
||||
"{}",
|
||||
DisplayDiagnostics::new(context, &self.config, diagnostics)
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
use ruff_diagnostics::Applicability;
|
||||
|
||||
use crate::message::TextEmitter;
|
||||
use crate::message::tests::{
|
||||
capture_emitter_notebook_output, capture_emitter_output, create_diagnostics,
|
||||
create_notebook_diagnostics, create_syntax_error_diagnostics,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
let mut emitter = TextEmitter::default().with_show_source(true);
|
||||
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_status() {
|
||||
let mut emitter = TextEmitter::default()
|
||||
.with_show_fix_status(true)
|
||||
.with_show_source(true);
|
||||
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_status_unsafe() {
|
||||
let mut emitter = TextEmitter::default()
|
||||
.with_show_fix_status(true)
|
||||
.with_show_source(true)
|
||||
.with_fix_applicability(Applicability::Unsafe);
|
||||
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notebook_output() {
|
||||
let mut emitter = TextEmitter::default()
|
||||
.with_show_fix_status(true)
|
||||
.with_show_source(true)
|
||||
.with_fix_applicability(Applicability::Unsafe);
|
||||
let (messages, notebook_indexes) = create_notebook_diagnostics();
|
||||
let content = capture_emitter_notebook_output(&mut emitter, &messages, ¬ebook_indexes);
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let mut emitter = TextEmitter::default().with_show_source(true);
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ use anyhow::{Context, Result, bail};
|
|||
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
|
||||
use log::debug;
|
||||
use pep440_rs::{VersionSpecifier, VersionSpecifiers};
|
||||
use ruff_db::diagnostic::DiagnosticFormat;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Deserializer, Serialize, de};
|
||||
use strum_macros::EnumIter;
|
||||
|
|
@ -553,6 +554,34 @@ impl Display for OutputFormat {
|
|||
}
|
||||
}
|
||||
|
||||
/// The subset of output formats only implemented in Ruff, not in `ruff_db` via `DisplayDiagnostics`.
|
||||
pub enum RuffOutputFormat {
|
||||
Github,
|
||||
Grouped,
|
||||
Sarif,
|
||||
}
|
||||
|
||||
impl TryFrom<OutputFormat> for DiagnosticFormat {
|
||||
type Error = RuffOutputFormat;
|
||||
|
||||
fn try_from(format: OutputFormat) -> std::result::Result<Self, Self::Error> {
|
||||
match format {
|
||||
OutputFormat::Concise => Ok(DiagnosticFormat::Concise),
|
||||
OutputFormat::Full => Ok(DiagnosticFormat::Full),
|
||||
OutputFormat::Json => Ok(DiagnosticFormat::Json),
|
||||
OutputFormat::JsonLines => Ok(DiagnosticFormat::JsonLines),
|
||||
OutputFormat::Junit => Ok(DiagnosticFormat::Junit),
|
||||
OutputFormat::Gitlab => Ok(DiagnosticFormat::Gitlab),
|
||||
OutputFormat::Pylint => Ok(DiagnosticFormat::Pylint),
|
||||
OutputFormat::Rdjson => Ok(DiagnosticFormat::Rdjson),
|
||||
OutputFormat::Azure => Ok(DiagnosticFormat::Azure),
|
||||
OutputFormat::Github => Err(RuffOutputFormat::Github),
|
||||
OutputFormat::Grouped => Err(RuffOutputFormat::Grouped),
|
||||
OutputFormat::Sarif => Err(RuffOutputFormat::Sarif),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
#[serde(try_from = "String")]
|
||||
pub struct RequiredVersion(VersionSpecifiers);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ use anyhow::Result;
|
|||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_db::diagnostic::{Diagnostic, Span};
|
||||
use ruff_db::diagnostic::{
|
||||
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics, Span,
|
||||
};
|
||||
use ruff_notebook::Notebook;
|
||||
#[cfg(not(fuzzing))]
|
||||
use ruff_notebook::NotebookError;
|
||||
|
|
@ -24,7 +26,7 @@ use ruff_source_file::SourceFileBuilder;
|
|||
use crate::codes::Rule;
|
||||
use crate::fix::{FixResult, fix_file};
|
||||
use crate::linter::check_path;
|
||||
use crate::message::{Emitter, EmitterContext, TextEmitter, create_syntax_error_diagnostic};
|
||||
use crate::message::{EmitterContext, create_syntax_error_diagnostic};
|
||||
use crate::package::PackageRoot;
|
||||
use crate::packaging::detect_package_root;
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
|
|
@ -444,42 +446,38 @@ pub(crate) fn print_jupyter_messages(
|
|||
path: &Path,
|
||||
notebook: &Notebook,
|
||||
) -> String {
|
||||
let mut output = Vec::new();
|
||||
|
||||
TextEmitter::default()
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.format(DiagnosticFormat::Full)
|
||||
.hide_severity(true)
|
||||
.with_show_fix_status(true)
|
||||
.with_show_fix_diff(true)
|
||||
.with_show_source(true)
|
||||
.with_fix_applicability(Applicability::DisplayOnly)
|
||||
.emit(
|
||||
&mut output,
|
||||
diagnostics,
|
||||
&EmitterContext::new(&FxHashMap::from_iter([(
|
||||
path.file_name().unwrap().to_string_lossy().to_string(),
|
||||
notebook.index().clone(),
|
||||
)])),
|
||||
)
|
||||
.unwrap();
|
||||
.show_fix_diff(true)
|
||||
.with_fix_applicability(Applicability::DisplayOnly);
|
||||
|
||||
String::from_utf8(output).unwrap()
|
||||
DisplayDiagnostics::new(
|
||||
&EmitterContext::new(&FxHashMap::from_iter([(
|
||||
path.file_name().unwrap().to_string_lossy().to_string(),
|
||||
notebook.index().clone(),
|
||||
)])),
|
||||
&config,
|
||||
diagnostics,
|
||||
)
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub(crate) fn print_messages(diagnostics: &[Diagnostic]) -> String {
|
||||
let mut output = Vec::new();
|
||||
|
||||
TextEmitter::default()
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.format(DiagnosticFormat::Full)
|
||||
.hide_severity(true)
|
||||
.with_show_fix_status(true)
|
||||
.with_show_fix_diff(true)
|
||||
.with_show_source(true)
|
||||
.with_fix_applicability(Applicability::DisplayOnly)
|
||||
.emit(
|
||||
&mut output,
|
||||
diagnostics,
|
||||
&EmitterContext::new(&FxHashMap::default()),
|
||||
)
|
||||
.unwrap();
|
||||
.show_fix_diff(true)
|
||||
.with_fix_applicability(Applicability::DisplayOnly);
|
||||
|
||||
String::from_utf8(output).unwrap()
|
||||
DisplayDiagnostics::new(
|
||||
&EmitterContext::new(&FxHashMap::default()),
|
||||
&config,
|
||||
diagnostics,
|
||||
)
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
|
|
|||
Loading…
Reference in New Issue