mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-11-03 21:24:29 +00:00 
			
		
		
		
	Remove TextEmitter (#20595)
	
		
			
	
		
	
	
		
	
		
			Some checks are pending
		
		
	
	
		
			
				
	
				CI / Determine changes (push) Waiting to run
				
			
		
			
				
	
				CI / cargo fmt (push) Waiting to run
				
			
		
			
				
	
				CI / cargo clippy (push) Blocked by required conditions
				
			
		
			
				
	
				CI / cargo test (linux) (push) Blocked by required conditions
				
			
		
			
				
	
				CI / cargo test (linux, release) (push) Blocked by required conditions
				
			
		
			
				
	
				CI / cargo test (windows) (push) Blocked by required conditions
				
			
		
			
				
	
				CI / cargo test (wasm) (push) Blocked by required conditions
				
			
		
			
				
	
				CI / cargo build (release) (push) Waiting to run
				
			
		
			
				
	
				CI / cargo build (msrv) (push) Blocked by required conditions
				
			
		
			
				
	
				CI / cargo fuzz build (push) Blocked by required conditions
				
			
		
			
				
	
				CI / fuzz parser (push) Blocked by required conditions
				
			
		
			
				
	
				CI / test scripts (push) Blocked by required conditions
				
			
		
			
				
	
				CI / ecosystem (push) Blocked by required conditions
				
			
		
			
				
	
				CI / Fuzz for new ty panics (push) Blocked by required conditions
				
			
		
			
				
	
				CI / cargo shear (push) Blocked by required conditions
				
			
		
			
				
	
				CI / python package (push) Waiting to run
				
			
		
			
				
	
				CI / pre-commit (push) Waiting to run
				
			
		
			
				
	
				CI / mkdocs (push) Waiting to run
				
			
		
			
				
	
				CI / formatter instabilities and black similarity (push) Blocked by required conditions
				
			
		
			
				
	
				CI / test ruff-lsp (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check playground (push) Blocked by required conditions
				
			
		
			
				
	
				CI / benchmarks instrumented (ruff) (push) Blocked by required conditions
				
			
		
			
				
	
				CI / benchmarks instrumented (ty) (push) Blocked by required conditions
				
			
		
			
				
	
				CI / benchmarks-walltime (push) Blocked by required conditions
				
			
		
			
				
	
				[ty Playground] Release / publish (push) Waiting to run
				
			
		
		
	
	
				
					
				
			
		
			Some checks are pending
		
		
	
	CI / Determine changes (push) Waiting to run
				
			CI / cargo fmt (push) Waiting to run
				
			CI / cargo clippy (push) Blocked by required conditions
				
			CI / cargo test (linux) (push) Blocked by required conditions
				
			CI / cargo test (linux, release) (push) Blocked by required conditions
				
			CI / cargo test (windows) (push) Blocked by required conditions
				
			CI / cargo test (wasm) (push) Blocked by required conditions
				
			CI / cargo build (release) (push) Waiting to run
				
			CI / cargo build (msrv) (push) Blocked by required conditions
				
			CI / cargo fuzz build (push) Blocked by required conditions
				
			CI / fuzz parser (push) Blocked by required conditions
				
			CI / test scripts (push) Blocked by required conditions
				
			CI / ecosystem (push) Blocked by required conditions
				
			CI / Fuzz for new ty panics (push) Blocked by required conditions
				
			CI / cargo shear (push) Blocked by required conditions
				
			CI / python package (push) Waiting to run
				
			CI / pre-commit (push) Waiting to run
				
			CI / mkdocs (push) Waiting to run
				
			CI / formatter instabilities and black similarity (push) Blocked by required conditions
				
			CI / test ruff-lsp (push) Blocked by required conditions
				
			CI / check playground (push) Blocked by required conditions
				
			CI / benchmarks instrumented (ruff) (push) Blocked by required conditions
				
			CI / benchmarks instrumented (ty) (push) Blocked by required conditions
				
			CI / benchmarks-walltime (push) Blocked by required conditions
				
			[ty Playground] Release / publish (push) Waiting to run
				
			## 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
					
				
					 18 changed files with 285 additions and 548 deletions
				
			
		| 
						 | 
					@ -227,7 +227,8 @@ mod test {
 | 
				
			||||||
    use rustc_hash::FxHashMap;
 | 
					    use rustc_hash::FxHashMap;
 | 
				
			||||||
    use tempfile::TempDir;
 | 
					    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::registry::Rule;
 | 
				
			||||||
    use ruff_linter::settings::types::UnsafeFixes;
 | 
					    use ruff_linter::settings::types::UnsafeFixes;
 | 
				
			||||||
    use ruff_linter::settings::{LinterSettings, flags};
 | 
					    use ruff_linter::settings::{LinterSettings, flags};
 | 
				
			||||||
| 
						 | 
					@ -280,19 +281,16 @@ mod test {
 | 
				
			||||||
            UnsafeFixes::Enabled,
 | 
					            UnsafeFixes::Enabled,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .unwrap();
 | 
					        .unwrap();
 | 
				
			||||||
        let mut output = Vec::new();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        TextEmitter::default()
 | 
					        let config = DisplayDiagnosticConfig::default()
 | 
				
			||||||
            .with_show_fix_status(true)
 | 
					            .format(DiagnosticFormat::Concise)
 | 
				
			||||||
            .with_color(false)
 | 
					            .hide_severity(true);
 | 
				
			||||||
            .emit(
 | 
					        let messages = DisplayDiagnostics::new(
 | 
				
			||||||
                &mut output,
 | 
					            &EmitterContext::new(&FxHashMap::default()),
 | 
				
			||||||
                &diagnostics.inner,
 | 
					            &config,
 | 
				
			||||||
                &EmitterContext::new(&FxHashMap::default()),
 | 
					            &diagnostics.inner,
 | 
				
			||||||
            )
 | 
					        )
 | 
				
			||||||
            .unwrap();
 | 
					        .to_string();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let messages = String::from_utf8(output).unwrap();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        insta::with_settings!({
 | 
					        insta::with_settings!({
 | 
				
			||||||
            omit_expression => true,
 | 
					            omit_expression => true,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,12 +10,11 @@ use ruff_linter::linter::FixTable;
 | 
				
			||||||
use serde::Serialize;
 | 
					use serde::Serialize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use ruff_db::diagnostic::{
 | 
					use ruff_db::diagnostic::{
 | 
				
			||||||
    Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics,
 | 
					    Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics, SecondaryCode,
 | 
				
			||||||
    DisplayGithubDiagnostics, GithubRenderer, SecondaryCode,
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use ruff_linter::fs::relativize_path;
 | 
					use ruff_linter::fs::relativize_path;
 | 
				
			||||||
use ruff_linter::logging::LogLevel;
 | 
					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::notify_user;
 | 
				
			||||||
use ruff_linter::settings::flags::{self};
 | 
					use ruff_linter::settings::flags::{self};
 | 
				
			||||||
use ruff_linter::settings::types::{OutputFormat, UnsafeFixes};
 | 
					use ruff_linter::settings::types::{OutputFormat, UnsafeFixes};
 | 
				
			||||||
| 
						 | 
					@ -225,86 +224,28 @@ impl Printer {
 | 
				
			||||||
        let context = EmitterContext::new(&diagnostics.notebook_indexes);
 | 
					        let context = EmitterContext::new(&diagnostics.notebook_indexes);
 | 
				
			||||||
        let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes);
 | 
					        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 {
 | 
					        render_diagnostics(writer, self.format, config, &context, &diagnostics.inner)?;
 | 
				
			||||||
            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)?;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
 | 
					        if matches!(
 | 
				
			||||||
                    if !diagnostics.fixed.is_empty() {
 | 
					            self.format,
 | 
				
			||||||
                        writeln!(writer)?;
 | 
					            OutputFormat::Full | OutputFormat::Concise | OutputFormat::Grouped
 | 
				
			||||||
                        print_fix_summary(writer, &diagnostics.fixed)?;
 | 
					        ) {
 | 
				
			||||||
                        writeln!(writer)?;
 | 
					            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()?;
 | 
					        writer.flush()?;
 | 
				
			||||||
| 
						 | 
					@ -448,11 +389,22 @@ impl Printer {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let context = EmitterContext::new(&diagnostics.notebook_indexes);
 | 
					            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_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
 | 
				
			||||||
                .with_show_source(preview)
 | 
					                .format(format)
 | 
				
			||||||
                .with_fix_applicability(self.unsafe_fixes.required_applicability())
 | 
					                .with_fix_applicability(self.unsafe_fixes.required_applicability());
 | 
				
			||||||
                .emit(writer, &diagnostics.inner, &context)?;
 | 
					            write!(
 | 
				
			||||||
 | 
					                writer,
 | 
				
			||||||
 | 
					                "{}",
 | 
				
			||||||
 | 
					                DisplayDiagnostics::new(&context, &config, &diagnostics.inner)
 | 
				
			||||||
 | 
					            )?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        writer.flush()?;
 | 
					        writer.flush()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6199,6 +6199,36 @@ match 42:  # invalid-syntax
 | 
				
			||||||
    Ok(())
 | 
					    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]
 | 
					#[test]
 | 
				
			||||||
fn up045_nested_optional_flatten_all() {
 | 
					fn up045_nested_optional_flatten_all() {
 | 
				
			||||||
    let contents = "\
 | 
					    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.
 | 
					    /// 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 {
 | 
					        DisplayDiagnosticConfig {
 | 
				
			||||||
            show_fix_status: yes,
 | 
					            show_fix_status: yes,
 | 
				
			||||||
            ..self
 | 
					            ..self
 | 
				
			||||||
| 
						 | 
					@ -1374,12 +1374,20 @@ impl DisplayDiagnosticConfig {
 | 
				
			||||||
    /// availability for unsafe or display-only fixes.
 | 
					    /// availability for unsafe or display-only fixes.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// Note that this option is currently ignored when `hide_severity` is false.
 | 
					    /// 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 {
 | 
					        DisplayDiagnosticConfig {
 | 
				
			||||||
            fix_applicability: applicability,
 | 
					            fix_applicability: applicability,
 | 
				
			||||||
            ..self
 | 
					            ..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 {
 | 
					impl Default for DisplayDiagnosticConfig {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2618,7 +2618,7 @@ watermelon
 | 
				
			||||||
        /// Show fix availability when rendering.
 | 
					        /// Show fix availability when rendering.
 | 
				
			||||||
        pub(super) fn show_fix_status(&mut self, yes: bool) {
 | 
					        pub(super) fn show_fix_status(&mut self, yes: bool) {
 | 
				
			||||||
            let mut config = std::mem::take(&mut self.config);
 | 
					            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;
 | 
					            self.config = config;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2632,7 +2632,7 @@ watermelon
 | 
				
			||||||
        /// The lowest fix applicability to show when rendering.
 | 
					        /// The lowest fix applicability to show when rendering.
 | 
				
			||||||
        pub(super) fn fix_applicability(&mut self, applicability: Applicability) {
 | 
					        pub(super) fn fix_applicability(&mut self, applicability: Applicability) {
 | 
				
			||||||
            let mut config = std::mem::take(&mut self.config);
 | 
					            let mut config = std::mem::take(&mut self.config);
 | 
				
			||||||
            config = config.fix_applicability(applicability);
 | 
					            config = config.with_fix_applicability(applicability);
 | 
				
			||||||
            self.config = config;
 | 
					            self.config = config;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,17 +6,25 @@ use std::num::NonZeroUsize;
 | 
				
			||||||
use colored::Colorize;
 | 
					use colored::Colorize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use ruff_db::diagnostic::Diagnostic;
 | 
					use ruff_db::diagnostic::Diagnostic;
 | 
				
			||||||
 | 
					use ruff_diagnostics::Applicability;
 | 
				
			||||||
use ruff_notebook::NotebookIndex;
 | 
					use ruff_notebook::NotebookIndex;
 | 
				
			||||||
use ruff_source_file::{LineColumn, OneIndexed};
 | 
					use ruff_source_file::{LineColumn, OneIndexed};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::fs::relativize_path;
 | 
					use crate::fs::relativize_path;
 | 
				
			||||||
use crate::message::{Emitter, EmitterContext};
 | 
					use crate::message::{Emitter, EmitterContext};
 | 
				
			||||||
use crate::settings::types::UnsafeFixes;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Default)]
 | 
					 | 
				
			||||||
pub struct GroupedEmitter {
 | 
					pub struct GroupedEmitter {
 | 
				
			||||||
    show_fix_status: bool,
 | 
					    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 {
 | 
					impl GroupedEmitter {
 | 
				
			||||||
| 
						 | 
					@ -27,8 +35,8 @@ impl GroupedEmitter {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn with_unsafe_fixes(mut self, unsafe_fixes: UnsafeFixes) -> Self {
 | 
					    pub fn with_applicability(mut self, applicability: Applicability) -> Self {
 | 
				
			||||||
        self.unsafe_fixes = unsafe_fixes;
 | 
					        self.applicability = applicability;
 | 
				
			||||||
        self
 | 
					        self
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -67,7 +75,7 @@ impl Emitter for GroupedEmitter {
 | 
				
			||||||
                        notebook_index: context.notebook_index(&message.expect_ruff_filename()),
 | 
					                        notebook_index: context.notebook_index(&message.expect_ruff_filename()),
 | 
				
			||||||
                        message,
 | 
					                        message,
 | 
				
			||||||
                        show_fix_status: self.show_fix_status,
 | 
					                        show_fix_status: self.show_fix_status,
 | 
				
			||||||
                        unsafe_fixes: self.unsafe_fixes,
 | 
					                        applicability: self.applicability,
 | 
				
			||||||
                        row_length,
 | 
					                        row_length,
 | 
				
			||||||
                        column_length,
 | 
					                        column_length,
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
| 
						 | 
					@ -114,7 +122,7 @@ fn group_diagnostics_by_filename(
 | 
				
			||||||
struct DisplayGroupedMessage<'a> {
 | 
					struct DisplayGroupedMessage<'a> {
 | 
				
			||||||
    message: MessageWithLocation<'a>,
 | 
					    message: MessageWithLocation<'a>,
 | 
				
			||||||
    show_fix_status: bool,
 | 
					    show_fix_status: bool,
 | 
				
			||||||
    unsafe_fixes: UnsafeFixes,
 | 
					    applicability: Applicability,
 | 
				
			||||||
    row_length: NonZeroUsize,
 | 
					    row_length: NonZeroUsize,
 | 
				
			||||||
    column_length: NonZeroUsize,
 | 
					    column_length: NonZeroUsize,
 | 
				
			||||||
    notebook_index: Option<&'a NotebookIndex>,
 | 
					    notebook_index: Option<&'a NotebookIndex>,
 | 
				
			||||||
| 
						 | 
					@ -162,7 +170,7 @@ impl Display for DisplayGroupedMessage<'_> {
 | 
				
			||||||
            code_and_body = RuleCodeAndBody {
 | 
					            code_and_body = RuleCodeAndBody {
 | 
				
			||||||
                message,
 | 
					                message,
 | 
				
			||||||
                show_fix_status: self.show_fix_status,
 | 
					                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(super) struct RuleCodeAndBody<'a> {
 | 
				
			||||||
    pub(crate) message: &'a Diagnostic,
 | 
					    pub(crate) message: &'a Diagnostic,
 | 
				
			||||||
    pub(crate) show_fix_status: bool,
 | 
					    pub(crate) show_fix_status: bool,
 | 
				
			||||||
    pub(crate) unsafe_fixes: UnsafeFixes,
 | 
					    pub(crate) applicability: Applicability,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Display for RuleCodeAndBody<'_> {
 | 
					impl Display for RuleCodeAndBody<'_> {
 | 
				
			||||||
| 
						 | 
					@ -181,7 +189,7 @@ impl Display for RuleCodeAndBody<'_> {
 | 
				
			||||||
        if self.show_fix_status {
 | 
					        if self.show_fix_status {
 | 
				
			||||||
            if let Some(fix) = self.message.fix() {
 | 
					            if let Some(fix) = self.message.fix() {
 | 
				
			||||||
                // Do not display an indicator for inapplicable fixes
 | 
					                // 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() {
 | 
					                    if let Some(code) = self.message.secondary_code() {
 | 
				
			||||||
                        write!(f, "{} ", code.red().bold())?;
 | 
					                        write!(f, "{} ", code.red().bold())?;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
| 
						 | 
					@ -217,11 +225,12 @@ impl Display for RuleCodeAndBody<'_> {
 | 
				
			||||||
mod tests {
 | 
					mod tests {
 | 
				
			||||||
    use insta::assert_snapshot;
 | 
					    use insta::assert_snapshot;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    use ruff_diagnostics::Applicability;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    use crate::message::GroupedEmitter;
 | 
					    use crate::message::GroupedEmitter;
 | 
				
			||||||
    use crate::message::tests::{
 | 
					    use crate::message::tests::{
 | 
				
			||||||
        capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
 | 
					        capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    use crate::settings::types::UnsafeFixes;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn default() {
 | 
					    fn default() {
 | 
				
			||||||
| 
						 | 
					@ -251,7 +260,7 @@ mod tests {
 | 
				
			||||||
    fn fix_status_unsafe() {
 | 
					    fn fix_status_unsafe() {
 | 
				
			||||||
        let mut emitter = GroupedEmitter::default()
 | 
					        let mut emitter = GroupedEmitter::default()
 | 
				
			||||||
            .with_show_fix_status(true)
 | 
					            .with_show_fix_status(true)
 | 
				
			||||||
            .with_unsafe_fixes(UnsafeFixes::Enabled);
 | 
					            .with_applicability(Applicability::Unsafe);
 | 
				
			||||||
        let content = capture_emitter_output(&mut emitter, &create_diagnostics());
 | 
					        let content = capture_emitter_output(&mut emitter, &create_diagnostics());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_snapshot!(content);
 | 
					        assert_snapshot!(content);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,8 +4,9 @@ use std::io::Write;
 | 
				
			||||||
use rustc_hash::FxHashMap;
 | 
					use rustc_hash::FxHashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use ruff_db::diagnostic::{
 | 
					use ruff_db::diagnostic::{
 | 
				
			||||||
    Annotation, Diagnostic, DiagnosticId, FileResolver, Input, LintName, SecondaryCode, Severity,
 | 
					    Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig,
 | 
				
			||||||
    Span, UnifiedFile,
 | 
					    DisplayDiagnostics, DisplayGithubDiagnostics, FileResolver, GithubRenderer, Input, LintName,
 | 
				
			||||||
 | 
					    SecondaryCode, Severity, Span, UnifiedFile,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use ruff_db::files::File;
 | 
					use ruff_db::files::File;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,14 +15,13 @@ use ruff_notebook::NotebookIndex;
 | 
				
			||||||
use ruff_source_file::SourceFile;
 | 
					use ruff_source_file::SourceFile;
 | 
				
			||||||
use ruff_text_size::{Ranged, TextRange, TextSize};
 | 
					use ruff_text_size::{Ranged, TextRange, TextSize};
 | 
				
			||||||
pub use sarif::SarifEmitter;
 | 
					pub use sarif::SarifEmitter;
 | 
				
			||||||
pub use text::TextEmitter;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::Fix;
 | 
					use crate::Fix;
 | 
				
			||||||
use crate::registry::Rule;
 | 
					use crate::registry::Rule;
 | 
				
			||||||
 | 
					use crate::settings::types::{OutputFormat, RuffOutputFormat};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mod grouped;
 | 
					mod grouped;
 | 
				
			||||||
mod sarif;
 | 
					mod sarif;
 | 
				
			||||||
mod text;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Creates a `Diagnostic` from a syntax error, with the format expected by Ruff.
 | 
					/// 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)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod tests {
 | 
					mod tests {
 | 
				
			||||||
    use rustc_hash::FxHashMap;
 | 
					    use rustc_hash::FxHashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    use ruff_db::diagnostic::Diagnostic;
 | 
					    use ruff_db::diagnostic::Diagnostic;
 | 
				
			||||||
    use ruff_notebook::NotebookIndex;
 | 
					 | 
				
			||||||
    use ruff_python_parser::{Mode, ParseOptions, parse_unchecked};
 | 
					    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 ruff_text_size::{TextRange, TextSize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    use crate::codes::Rule;
 | 
					    use crate::codes::Rule;
 | 
				
			||||||
| 
						 | 
					@ -257,104 +291,6 @@ def fibonacci(n):
 | 
				
			||||||
        vec![unused_import, unused_variable, undefined_name]
 | 
					        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(
 | 
					    pub(super) fn capture_emitter_output(
 | 
				
			||||||
        emitter: &mut dyn Emitter,
 | 
					        emitter: &mut dyn Emitter,
 | 
				
			||||||
        diagnostics: &[Diagnostic],
 | 
					        diagnostics: &[Diagnostic],
 | 
				
			||||||
| 
						 | 
					@ -366,16 +302,4 @@ def foo():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        String::from_utf8(output).expect("Output to be valid UTF-8")
 | 
					        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 globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
 | 
				
			||||||
use log::debug;
 | 
					use log::debug;
 | 
				
			||||||
use pep440_rs::{VersionSpecifier, VersionSpecifiers};
 | 
					use pep440_rs::{VersionSpecifier, VersionSpecifiers};
 | 
				
			||||||
 | 
					use ruff_db::diagnostic::DiagnosticFormat;
 | 
				
			||||||
use rustc_hash::FxHashMap;
 | 
					use rustc_hash::FxHashMap;
 | 
				
			||||||
use serde::{Deserialize, Deserializer, Serialize, de};
 | 
					use serde::{Deserialize, Deserializer, Serialize, de};
 | 
				
			||||||
use strum_macros::EnumIter;
 | 
					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)]
 | 
					#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
 | 
				
			||||||
#[serde(try_from = "String")]
 | 
					#[serde(try_from = "String")]
 | 
				
			||||||
pub struct RequiredVersion(VersionSpecifiers);
 | 
					pub struct RequiredVersion(VersionSpecifiers);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,9 @@ use anyhow::Result;
 | 
				
			||||||
use itertools::Itertools;
 | 
					use itertools::Itertools;
 | 
				
			||||||
use rustc_hash::FxHashMap;
 | 
					use rustc_hash::FxHashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use ruff_db::diagnostic::{Diagnostic, Span};
 | 
					use ruff_db::diagnostic::{
 | 
				
			||||||
 | 
					    Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics, Span,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
use ruff_notebook::Notebook;
 | 
					use ruff_notebook::Notebook;
 | 
				
			||||||
#[cfg(not(fuzzing))]
 | 
					#[cfg(not(fuzzing))]
 | 
				
			||||||
use ruff_notebook::NotebookError;
 | 
					use ruff_notebook::NotebookError;
 | 
				
			||||||
| 
						 | 
					@ -24,7 +26,7 @@ use ruff_source_file::SourceFileBuilder;
 | 
				
			||||||
use crate::codes::Rule;
 | 
					use crate::codes::Rule;
 | 
				
			||||||
use crate::fix::{FixResult, fix_file};
 | 
					use crate::fix::{FixResult, fix_file};
 | 
				
			||||||
use crate::linter::check_path;
 | 
					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::package::PackageRoot;
 | 
				
			||||||
use crate::packaging::detect_package_root;
 | 
					use crate::packaging::detect_package_root;
 | 
				
			||||||
use crate::settings::types::UnsafeFixes;
 | 
					use crate::settings::types::UnsafeFixes;
 | 
				
			||||||
| 
						 | 
					@ -444,42 +446,38 @@ pub(crate) fn print_jupyter_messages(
 | 
				
			||||||
    path: &Path,
 | 
					    path: &Path,
 | 
				
			||||||
    notebook: &Notebook,
 | 
					    notebook: &Notebook,
 | 
				
			||||||
) -> String {
 | 
					) -> String {
 | 
				
			||||||
    let mut output = Vec::new();
 | 
					    let config = DisplayDiagnosticConfig::default()
 | 
				
			||||||
 | 
					        .format(DiagnosticFormat::Full)
 | 
				
			||||||
    TextEmitter::default()
 | 
					        .hide_severity(true)
 | 
				
			||||||
        .with_show_fix_status(true)
 | 
					        .with_show_fix_status(true)
 | 
				
			||||||
        .with_show_fix_diff(true)
 | 
					        .show_fix_diff(true)
 | 
				
			||||||
        .with_show_source(true)
 | 
					        .with_fix_applicability(Applicability::DisplayOnly);
 | 
				
			||||||
        .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();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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 {
 | 
					pub(crate) fn print_messages(diagnostics: &[Diagnostic]) -> String {
 | 
				
			||||||
    let mut output = Vec::new();
 | 
					    let config = DisplayDiagnosticConfig::default()
 | 
				
			||||||
 | 
					        .format(DiagnosticFormat::Full)
 | 
				
			||||||
    TextEmitter::default()
 | 
					        .hide_severity(true)
 | 
				
			||||||
        .with_show_fix_status(true)
 | 
					        .with_show_fix_status(true)
 | 
				
			||||||
        .with_show_fix_diff(true)
 | 
					        .show_fix_diff(true)
 | 
				
			||||||
        .with_show_source(true)
 | 
					        .with_fix_applicability(Applicability::DisplayOnly);
 | 
				
			||||||
        .with_fix_applicability(Applicability::DisplayOnly)
 | 
					 | 
				
			||||||
        .emit(
 | 
					 | 
				
			||||||
            &mut output,
 | 
					 | 
				
			||||||
            diagnostics,
 | 
					 | 
				
			||||||
            &EmitterContext::new(&FxHashMap::default()),
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .unwrap();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    String::from_utf8(output).unwrap()
 | 
					    DisplayDiagnostics::new(
 | 
				
			||||||
 | 
					        &EmitterContext::new(&FxHashMap::default()),
 | 
				
			||||||
 | 
					        &config,
 | 
				
			||||||
 | 
					        diagnostics,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .to_string()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[macro_export]
 | 
					#[macro_export]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue