//! Tests the interaction of the `lint` configuration section #![cfg(not(target_family = "wasm"))] use std::fs; use std::process::Command; use std::str; use anyhow::Result; use insta_cmd::{assert_cmd_snapshot, get_cargo_bin}; use tempfile::TempDir; const BIN_NAME: &str = "ruff"; const STDIN_BASE_OPTIONS: &[&str] = &["--no-cache", "--output-format", "text"]; #[test] fn top_level_options() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" extend-select = ["B", "Q"] [flake8-quotes] inline-quotes = "single" "#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(STDIN_BASE_OPTIONS) .arg("--config") .arg(&ruff_toml) .args(["--stdin-filename", "test.py"]) .arg("-") .pass_stdin(r#"a = "abcba".strip("aba")"#), @r###" success: false exit_code: 1 ----- stdout ----- test.py:1:5: Q000 [*] Double quotes found but single quotes preferred test.py:1:5: B005 Using `.strip()` with multi-character strings is misleading test.py:1:19: Q000 [*] Double quotes found but single quotes preferred Found 3 errors. [*] 2 fixable with the `--fix` option. ----- stderr ----- "###); Ok(()) } #[test] fn lint_options() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" [lint] extend-select = ["B", "Q"] [lint.flake8-quotes] inline-quotes = "single" "#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(STDIN_BASE_OPTIONS) .arg("--config") .arg(&ruff_toml) .arg("-") .pass_stdin(r#"a = "abcba".strip("aba")"#), @r###" success: false exit_code: 1 ----- stdout ----- -:1:5: Q000 [*] Double quotes found but single quotes preferred -:1:5: B005 Using `.strip()` with multi-character strings is misleading -:1:19: Q000 [*] Double quotes found but single quotes preferred Found 3 errors. [*] 2 fixable with the `--fix` option. ----- stderr ----- "###); Ok(()) } /// Tests that configurations from the top-level and `lint` section are merged together. #[test] fn mixed_levels() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" extend-select = ["B", "Q"] [lint.flake8-quotes] inline-quotes = "single" "#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(STDIN_BASE_OPTIONS) .arg("--config") .arg(&ruff_toml) .arg("-") .pass_stdin(r#"a = "abcba".strip("aba")"#), @r###" success: false exit_code: 1 ----- stdout ----- -:1:5: Q000 [*] Double quotes found but single quotes preferred -:1:5: B005 Using `.strip()` with multi-character strings is misleading -:1:19: Q000 [*] Double quotes found but single quotes preferred Found 3 errors. [*] 2 fixable with the `--fix` option. ----- stderr ----- "###); Ok(()) } /// Tests that options in the `lint` section have higher precedence than top-level options (because they are more specific). #[test] fn precedence() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" [lint] extend-select = ["B", "Q"] [flake8-quotes] inline-quotes = "double" [lint.flake8-quotes] inline-quotes = "single" "#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(STDIN_BASE_OPTIONS) .arg("--config") .arg(&ruff_toml) .arg("-") .pass_stdin(r#"a = "abcba".strip("aba")"#), @r###" success: false exit_code: 1 ----- stdout ----- -:1:5: Q000 [*] Double quotes found but single quotes preferred -:1:5: B005 Using `.strip()` with multi-character strings is misleading -:1:19: Q000 [*] Double quotes found but single quotes preferred Found 3 errors. [*] 2 fixable with the `--fix` option. ----- stderr ----- "###); Ok(()) } #[test] fn exclude() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" extend-select = ["B", "Q"] extend-exclude = ["out"] [lint] exclude = ["test.py", "generated.py"] [lint.flake8-quotes] inline-quotes = "single" "#, )?; fs::write( tempdir.path().join("main.py"), r#" from test import say_hy if __name__ == "__main__": say_hy("dear Ruff contributor") "#, )?; // Excluded file but passed to the CLI directly, should be linted let test_path = tempdir.path().join("test.py"); fs::write( &test_path, r#" def say_hy(name: str): print(f"Hy {name}")"#, )?; fs::write( tempdir.path().join("generated.py"), r#"NUMBERS = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ] OTHER = "OTHER" "#, )?; let out_dir = tempdir.path().join("out"); fs::create_dir(&out_dir)?; fs::write(out_dir.join("a.py"), r#"a = "a""#)?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .current_dir(tempdir.path()) .arg("check") .args(STDIN_BASE_OPTIONS) .args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()]) // Explicitly pass test.py, should be linted regardless of it being excluded by lint.exclude .arg(test_path.file_name().unwrap()) // Lint all other files in the directory, should respect the `exclude` and `lint.exclude` options .arg("."), @r###" success: false exit_code: 1 ----- stdout ----- main.py:4:16: Q000 [*] Double quotes found but single quotes preferred main.py:5:12: Q000 [*] Double quotes found but single quotes preferred test.py:3:15: Q000 [*] Double quotes found but single quotes preferred Found 3 errors. [*] 3 fixable with the `--fix` option. ----- stderr ----- "###); Ok(()) } #[test] fn exclude_stdin() -> Result<()> { let tempdir = TempDir::new()?; let ruff_toml = tempdir.path().join("ruff.toml"); fs::write( &ruff_toml, r#" extend-select = ["B", "Q"] [lint] exclude = ["generated.py"] [lint.flake8-quotes] inline-quotes = "single" "#, )?; assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .current_dir(tempdir.path()) .arg("check") .args(STDIN_BASE_OPTIONS) .args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()]) .args(["--stdin-filename", "generated.py"]) .arg("-") .pass_stdin(r#" from test import say_hy if __name__ == "__main__": say_hy("dear Ruff contributor") "#), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- "###); Ok(()) }