diff --git a/Cargo.lock b/Cargo.lock index ce777d5efb..d132303688 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1114,11 +1114,23 @@ dependencies = [ "lazy_static", "linked-hash-map", "regex", + "serde", "similar", "walkdir", "yaml-rust", ] +[[package]] +name = "insta-cmd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809d3023d1d6e8d5c2206f199251f75cb26180e41f18cb0f22dd119161cb5127" +dependencies = [ + "insta", + "serde", + "serde_json", +] + [[package]] name = "instant" version = "0.1.12" @@ -2169,6 +2181,7 @@ dependencies = [ "glob", "ignore", "insta", + "insta-cmd", "is-macro", "itertools", "itoa", diff --git a/crates/ruff_cli/Cargo.toml b/crates/ruff_cli/Cargo.toml index 59603a9d21..4f913e0dd4 100644 --- a/crates/ruff_cli/Cargo.toml +++ b/crates/ruff_cli/Cargo.toml @@ -70,7 +70,10 @@ wild = { version = "2" } [dev-dependencies] assert_cmd = { version = "2.0.8" } +# Avoid writing colored snapshots when running tests from the terminal +colored = { workspace = true, features = ["no-color"]} insta = { workspace = true, features = ["filters"] } +insta-cmd = { version = "0.4.0" } tempfile = "3.6.0" ureq = { version = "2.6.2", features = [] } diff --git a/crates/ruff_cli/tests/black_compatibility_test.rs b/crates/ruff_cli/tests/black_compatibility_test.rs index a09cb6185a..fb66e2e6dd 100644 --- a/crates/ruff_cli/tests/black_compatibility_test.rs +++ b/crates/ruff_cli/tests/black_compatibility_test.rs @@ -1,15 +1,15 @@ #![cfg(not(target_family = "wasm"))] -use std::io::{ErrorKind, Read}; +use std::io::{ErrorKind, Read, Write}; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener, TcpStream}; use std::path::{Path, PathBuf}; -use std::process::Stdio; +use std::process::{Command, Stdio}; use std::thread::sleep; use std::time::Duration; use std::{fs, process, str}; -use anyhow::{anyhow, Context, Result}; -use assert_cmd::Command; +use anyhow::{anyhow, bail, Context, Result}; +use insta_cmd::get_cargo_bin; use log::info; use walkdir::WalkDir; @@ -29,13 +29,14 @@ impl Blackd { // Get free TCP port to run on let address = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0))?.local_addr()?; - let server = process::Command::new("blackd") - .args([ - "--bind-host", - &address.ip().to_string(), - "--bind-port", - &address.port().to_string(), - ]) + let args = [ + "--bind-host", + &address.ip().to_string(), + "--bind-port", + &address.port().to_string(), + ]; + let server = Command::new("blackd") + .args(args) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn() @@ -51,16 +52,16 @@ impl Blackd { Err(e) => return Err(e.into()), Ok(_) => { info!("`blackd` ready"); - break; + return Ok(Self { + address, + server, + client: ureq::agent(), + }); } } } - Ok(Self { - address, - server, - client: ureq::agent(), - }) + bail!("blackd {:?} failed to start", args) } /// Format given code with blackd. @@ -104,34 +105,42 @@ fn run_test(path: &Path, blackd: &Blackd, ruff_args: &[&str]) -> Result<()> { let input = fs::read(path)?; // Step 1: Run `ruff` on the input. - let step_1 = &Command::cargo_bin(BIN_NAME)? + let mut step_1 = Command::new(get_cargo_bin(BIN_NAME)) .args(ruff_args) - .write_stdin(input) - .assert() - .append_context("step", "running input through ruff"); - if !step_1.get_output().status.success() { + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn()?; + if let Some(mut stdin) = step_1.stdin.take() { + stdin.write_all(input.as_ref())?; + } + let step_1_output = step_1.wait_with_output()?; + if !step_1_output.status.success() { return Err(anyhow!( "Running input through ruff failed:\n{}", - str::from_utf8(&step_1.get_output().stderr)? + str::from_utf8(&step_1_output.stderr)? )); } - let step_1_output = step_1.get_output().stdout.clone(); // Step 2: Run `blackd` on the input. - let step_2_output = blackd.check(&step_1_output)?; + let step_2_output = blackd.check(&step_1_output.stdout.clone())?; // Step 3: Re-run `ruff` on the input. - let step_3 = &Command::cargo_bin(BIN_NAME)? + let mut step_3 = Command::new(get_cargo_bin(BIN_NAME)) .args(ruff_args) - .write_stdin(step_2_output.clone()) - .assert(); - if !step_3.get_output().status.success() { + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn()?; + if let Some(mut stdin) = step_3.stdin.take() { + stdin.write_all(step_2_output.as_ref())?; + } + let step_3_output = step_3.wait_with_output()?; + if !step_3_output.status.success() { return Err(anyhow!( "Running input through ruff after black failed:\n{}", - str::from_utf8(&step_3.get_output().stderr)? + str::from_utf8(&step_3_output.stderr)? )); } - let step_3_output = step_3.get_output().stdout.clone(); + let step_3_output = step_3_output.stdout.clone(); assert_eq!( str::from_utf8(&step_2_output), @@ -177,11 +186,13 @@ fn test_ruff_black_compatibility() -> Result<()> { "--fix", "--line-length", "88", - "--select ALL", + "--select", + "ALL", // Exclude ruff codes, specifically RUF100, because it causes differences that are not a // problem. Ruff would add a `# noqa: W292` after the first run, black introduces a // newline, and ruff removes the `# noqa: W292` again. - "--ignore RUF", + "--ignore", + "RUF", ]; for entry in paths { diff --git a/crates/ruff_cli/tests/integration_test.rs b/crates/ruff_cli/tests/integration_test.rs index 1c411fd52e..acdf802b9f 100644 --- a/crates/ruff_cli/tests/integration_test.rs +++ b/crates/ruff_cli/tests/integration_test.rs @@ -8,14 +8,15 @@ use std::fs::Permissions; use std::os::unix::fs::{OpenOptionsExt, PermissionsExt}; #[cfg(unix)] use std::path::Path; +use std::process::Command; use std::str; #[cfg(unix)] use anyhow::Context; use anyhow::Result; -use assert_cmd::Command; #[cfg(unix)] use clap::Parser; +use insta_cmd::{assert_cmd_snapshot, get_cargo_bin}; #[cfg(unix)] use path_absolutize::path_dedot; #[cfg(unix)] @@ -27,315 +28,280 @@ use ruff_cli::args::Args; use ruff_cli::run; const BIN_NAME: &str = "ruff"; +const STDIN_BASE_OPTIONS: &[&str] = &["--isolated", "--no-cache", "-", "--format", "text"]; #[test] -fn stdin_success() -> Result<()> { - let mut cmd = Command::cargo_bin(BIN_NAME)?; - cmd.args(["-", "--format", "text", "--isolated"]) - .write_stdin("") - .assert() - .success(); - Ok(()) +fn stdin_success() { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .pass_stdin(""), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + "###); } #[test] -fn stdin_error() -> Result<()> { - let mut cmd = Command::cargo_bin(BIN_NAME)?; - let output = cmd - .args(["-", "--format", "text", "--isolated"]) - .write_stdin("import os\n") - .assert() - .failure(); - assert_eq!( - str::from_utf8(&output.get_output().stdout)?, - r#"-:1:8: F401 [*] `os` imported but unused -Found 1 error. -[*] 1 potentially fixable with the --fix option. -"# - ); - Ok(()) +fn stdin_error() { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .pass_stdin("import os\n"), @r#" + success: false + exit_code: 1 + ----- stdout ----- + -:1:8: F401 [*] `os` imported but unused + Found 1 error. + [*] 1 potentially fixable with the --fix option. + + ----- stderr ----- + "#); } #[test] -fn stdin_filename() -> Result<()> { - let mut cmd = Command::cargo_bin(BIN_NAME)?; - let output = cmd - .args([ - "-", - "--format", - "text", - "--stdin-filename", - "F401.py", - "--isolated", - ]) - .write_stdin("import os\n") - .assert() - .failure(); - assert_eq!( - str::from_utf8(&output.get_output().stdout)?, - r#"F401.py:1:8: F401 [*] `os` imported but unused -Found 1 error. -[*] 1 potentially fixable with the --fix option. -"# - ); - Ok(()) +fn stdin_filename() { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .args(["--stdin-filename", "F401.py"]) + .pass_stdin("import os\n"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + F401.py:1:8: F401 [*] `os` imported but unused + Found 1 error. + [*] 1 potentially fixable with the --fix option. + + ----- stderr ----- + "###); } #[test] -fn stdin_source_type() -> Result<()> { - // Raise `TCH` errors in `.py` files. - let mut cmd = Command::cargo_bin(BIN_NAME)?; - let output = cmd - .args([ - "-", - "--format", - "text", - "--stdin-filename", - "TCH.py", - "--isolated", - ]) - .write_stdin("import os\n") - .assert() - .failure(); - assert_eq!( - str::from_utf8(&output.get_output().stdout)?, - r#"TCH.py:1:8: F401 [*] `os` imported but unused -Found 1 error. -[*] 1 potentially fixable with the --fix option. -"# - ); +/// Raise `TCH` errors in `.py` files ... +fn stdin_source_type_py() { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .args(["--stdin-filename", "TCH.py"]) + .pass_stdin("import os\n"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + TCH.py:1:8: F401 [*] `os` imported but unused + Found 1 error. + [*] 1 potentially fixable with the --fix option. - // But not in `.pyi` files. - let mut cmd = Command::cargo_bin(BIN_NAME)?; - cmd.args([ - "-", - "--format", - "text", - "--stdin-filename", - "TCH.pyi", - "--isolated", - "--select", - "TCH", - ]) - .write_stdin("import os\n") - .assert() - .success(); - Ok(()) + ----- stderr ----- + "###); +} + +/// ... but not in `.pyi` files. +#[test] +fn stdin_source_type_pyi() { + let args = ["--stdin-filename", "TCH.pyi", "--select", "TCH"]; + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .args(args) + .pass_stdin("import os\n"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + "###); } #[cfg(unix)] #[test] -fn stdin_json() -> Result<()> { - let mut cmd = Command::cargo_bin(BIN_NAME)?; - let output = cmd - .args([ - "-", - "--format", - "json", - "--stdin-filename", - "F401.py", - "--isolated", - ]) - .write_stdin("import os\n") - .assert() - .failure(); +fn stdin_json() { + let args = [ + "-", + "--isolated", + "--no-cache", + "--format", + "json", + "--stdin-filename", + "F401.py", + ]; let directory = path_dedot::CWD.to_str().unwrap(); let binding = Path::new(directory).join("F401.py"); let file_path = binding.display(); - assert_eq!( - str::from_utf8(&output.get_output().stdout)?, - format!( - r#"[ - {{ - "code": "F401", - "end_location": {{ - "column": 10, - "row": 1 - }}, - "filename": "{file_path}", - "fix": {{ - "applicability": "Automatic", - "edits": [ - {{ - "content": "", - "end_location": {{ - "column": 1, - "row": 2 - }}, - "location": {{ - "column": 1, - "row": 1 - }} - }} - ], - "message": "Remove unused import: `os`" - }}, - "location": {{ - "column": 8, - "row": 1 - }}, - "message": "`os` imported but unused", - "noqa_row": 1, - "url": "https://beta.ruff.rs/docs/rules/unused-import" - }} -]"# - ) - ); - Ok(()) + insta::with_settings!({filters => vec![ + (file_path.to_string().as_str(), "/path/to/F401.py"), + ]}, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(args) + .pass_stdin("import os\n")); + }); } #[test] -fn stdin_autofix() -> Result<()> { - let mut cmd = Command::cargo_bin(BIN_NAME)?; - let output = cmd - .args(["-", "--format", "text", "--fix", "--isolated"]) - .write_stdin("import os\nimport sys\n\nprint(sys.version)\n") - .assert() - .success(); - assert_eq!( - str::from_utf8(&output.get_output().stdout)?, - "import sys\n\nprint(sys.version)\n" - ); - Ok(()) +fn stdin_autofix() { + let args = ["--fix"]; + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .args(args) + .pass_stdin("import os\nimport sys\n\nprint(sys.version)\n"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + import sys + + print(sys.version) + + ----- stderr ----- + "###); } #[test] -fn stdin_autofix_when_not_fixable_should_still_print_contents() -> Result<()> { - let mut cmd = Command::cargo_bin(BIN_NAME)?; - let output = cmd - .args(["-", "--format", "text", "--fix", "--isolated"]) - .write_stdin("import os\nimport sys\n\nif (1, 2):\n print(sys.version)\n") - .assert() - .failure(); - assert_eq!( - str::from_utf8(&output.get_output().stdout)?, - "import sys\n\nif (1, 2):\n print(sys.version)\n" - ); - Ok(()) +fn stdin_autofix_when_not_fixable_should_still_print_contents() { + let args = ["--fix"]; + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .args(args) + .pass_stdin("import os\nimport sys\n\nif (1, 2):\n print(sys.version)\n"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + import sys + + if (1, 2): + print(sys.version) + + ----- stderr ----- + "###); } #[test] -fn stdin_autofix_when_no_issues_should_still_print_contents() -> Result<()> { - let mut cmd = Command::cargo_bin(BIN_NAME)?; - let output = cmd - .args(["-", "--format", "text", "--fix", "--isolated"]) - .write_stdin("import sys\n\nprint(sys.version)\n") - .assert() - .success(); - assert_eq!( - str::from_utf8(&output.get_output().stdout)?, - "import sys\n\nprint(sys.version)\n" - ); - Ok(()) +fn stdin_autofix_when_no_issues_should_still_print_contents() { + let args = ["--fix"]; + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .args(args) + .pass_stdin("import sys\n\nprint(sys.version)\n"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + import sys + + print(sys.version) + + ----- stderr ----- + "###); } #[test] -fn show_source() -> Result<()> { - let mut cmd = Command::cargo_bin(BIN_NAME)?; - let output = cmd - .args(["-", "--format", "text", "--show-source", "--isolated"]) - .write_stdin("l = 1") - .assert() - .failure(); - assert!(str::from_utf8(&output.get_output().stdout)?.contains("l = 1")); - Ok(()) +fn show_source() { + let args = ["--show-source"]; + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .args(args) + .pass_stdin("l = 1"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + -:1:1: E741 Ambiguous variable name: `l` + | + 1 | l = 1 + | ^ E741 + | + + Found 1 error. + + ----- stderr ----- + "###); } #[test] -fn explain_status_codes() -> Result<()> { - let mut cmd = Command::cargo_bin(BIN_NAME)?; - cmd.args(["--explain", "F401"]).assert().success(); - let mut cmd = Command::cargo_bin(BIN_NAME)?; - cmd.args(["--explain", "RUF404"]).assert().failure(); - Ok(()) +fn explain_status_codes_f401() { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)).args(["--explain", "F401"])); +} +#[test] +fn explain_status_codes_ruf404() { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)).args(["--explain", "RUF404"]), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: invalid value 'RUF404' for '[RULE]': unknown rule code + + For more information, try '--help'. + "###); } #[test] -fn show_statistics() -> Result<()> { - let mut cmd = Command::cargo_bin(BIN_NAME)?; - let output = cmd - .args([ - "-", - "--format", - "text", - "--select", - "F401", - "--statistics", - "--isolated", - ]) - .write_stdin("import sys\nimport os\n\nprint(os.getuid())\n") - .assert() - .failure(); - assert_eq!( - str::from_utf8(&output.get_output().stdout)? - .lines() - .last() - .unwrap(), - "1\tF401\t[*] `sys` imported but unused" - ); - Ok(()) +fn show_statistics() { + let args = ["--select", "F401", "--statistics"]; + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .args(args) + .pass_stdin("import sys\nimport os\n\nprint(os.getuid())\n"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + 1 F401 [*] `sys` imported but unused + + ----- stderr ----- + "###); } #[test] -fn nursery_prefix() -> Result<()> { - let mut cmd = Command::cargo_bin(BIN_NAME)?; - +fn nursery_prefix() { // `--select E` should detect E741, but not E225, which is in the nursery. - let output = cmd - .args(["-", "--format", "text", "--isolated", "--select", "E"]) - .write_stdin("I=42\n") - .assert() - .failure(); - assert_eq!( - str::from_utf8(&output.get_output().stdout)?, - r#"-:1:1: E741 Ambiguous variable name: `I` -Found 1 error. -"# - ); + let args = ["--select", "E"]; + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .args(args) + .pass_stdin("I=42\n"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + -:1:1: E741 Ambiguous variable name: `I` + Found 1 error. - Ok(()) + ----- stderr ----- + "###); } #[test] -fn nursery_all() -> Result<()> { - let mut cmd = Command::cargo_bin(BIN_NAME)?; - +fn nursery_all() { // `--select ALL` should detect E741, but not E225, which is in the nursery. - let output = cmd - .args(["-", "--format", "text", "--isolated", "--select", "E"]) - .write_stdin("I=42\n") - .assert() - .failure(); - assert_eq!( - str::from_utf8(&output.get_output().stdout)?, - r#"-:1:1: E741 Ambiguous variable name: `I` -Found 1 error. -"# - ); + let args = ["--select", "ALL"]; + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .args(args) + .pass_stdin("I=42\n"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + -:1:1: E741 Ambiguous variable name: `I` + -:1:1: D100 Missing docstring in public module + Found 2 errors. - Ok(()) + ----- stderr ----- + warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`. + warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`. + "###); } #[test] -fn nursery_direct() -> Result<()> { - let mut cmd = Command::cargo_bin(BIN_NAME)?; - +fn nursery_direct() { // `--select E225` should detect E225. - let output = cmd - .args(["-", "--format", "text", "--isolated", "--select", "E225"]) - .write_stdin("I=42\n") - .assert() - .failure(); - assert_eq!( - str::from_utf8(&output.get_output().stdout)?, - r#"-:1:2: E225 Missing whitespace around operator -Found 1 error. -"# - ); + let args = ["--select", "E225"]; + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .args(args) + .pass_stdin("I=42\n"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + -:1:2: E225 Missing whitespace around operator + Found 1 error. - Ok(()) + ----- stderr ----- + "###); } /// An unreadable pyproject.toml in non-isolated mode causes ruff to hard-error trying to build up @@ -376,17 +342,17 @@ fn unreadable_dir() -> Result<()> { // We (currently?) have to use a subcommand to check exit status (currently wrong) and logging // output - let mut cmd = Command::cargo_bin(BIN_NAME)?; - let output = cmd + // TODO(konstin): This should be a failure, but we currently can't track that + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) .args(["--no-cache", "--isolated"]) - .arg(&unreadable_dir) - .assert() - // TODO(konstin): This should be a failure, but we currently can't track that - .success(); - assert_eq!( - str::from_utf8(&output.get_output().stderr)?, - "warning: Encountered error: Permission denied (os error 13)\n" - ); + .arg(&unreadable_dir), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: Encountered error: Permission denied (os error 13) + "###); Ok(()) } @@ -416,17 +382,22 @@ fn check_input_from_argfile() -> Result<()> { format!("@{}", &input_file_path.display()), ]; - let mut cmd = Command::cargo_bin(BIN_NAME)?; - let output = cmd.args(args).write_stdin("").assert().failure(); - assert_eq!( - str::from_utf8(&output.get_output().stdout)?, - format!( - "{}:1:8: F401 [*] `os` imported but unused -Found 1 error. -[*] 1 potentially fixable with the --fix option. -", - file_a_path.display() - ) - ); + insta::with_settings!({filters => vec![ + (file_a_path.display().to_string().as_str(), "/path/to/a.py"), + ]}, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(args) + .pass_stdin(""), @r###" + success: false + exit_code: 1 + ----- stdout ----- + /path/to/a.py:1:8: F401 [*] `os` imported but unused + Found 1 error. + [*] 1 potentially fixable with the --fix option. + + ----- stderr ----- + "###); + }); + Ok(()) } diff --git a/crates/ruff_cli/tests/snapshots/integration_test__explain_status_codes_f401.snap b/crates/ruff_cli/tests/snapshots/integration_test__explain_status_codes_f401.snap new file mode 100644 index 0000000000..17615bf570 --- /dev/null +++ b/crates/ruff_cli/tests/snapshots/integration_test__explain_status_codes_f401.snap @@ -0,0 +1,61 @@ +--- +source: crates/ruff_cli/tests/integration_test.rs +info: + program: ruff + args: + - "--explain" + - F401 +--- +success: true +exit_code: 0 +----- stdout ----- +# unused-import (F401) + +Derived from the **Pyflakes** linter. + +Autofix is sometimes available. + +## What it does +Checks for unused imports. + +## Why is this bad? +Unused imports add a performance overhead at runtime, and risk creating +import cycles. They also increase the cognitive load of reading the code. + +If an import statement is used to check for the availability or existence +of a module, consider using `importlib.util.find_spec` instead. + +## Example +```python +import numpy as np # unused import + + +def area(radius): + return 3.14 * radius**2 +``` + +Use instead: +```python +def area(radius): + return 3.14 * radius**2 +``` + +To check the availability of a module, use `importlib.util.find_spec`: +```python +from importlib.util import find_spec + +if find_spec("numpy") is not None: + print("numpy is installed") +else: + print("numpy is not installed") +``` + +## Options +- `pyflakes.extend-generics` + +## References +- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement) +- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec) + +----- stderr ----- + diff --git a/crates/ruff_cli/tests/snapshots/integration_test__stdin_json.snap b/crates/ruff_cli/tests/snapshots/integration_test__stdin_json.snap new file mode 100644 index 0000000000..f127d39ad7 --- /dev/null +++ b/crates/ruff_cli/tests/snapshots/integration_test__stdin_json.snap @@ -0,0 +1,53 @@ +--- +source: crates/ruff_cli/tests/integration_test.rs +info: + program: ruff + args: + - "-" + - "--isolated" + - "--no-cache" + - "--format" + - json + - "--stdin-filename" + - F401.py + stdin: "import os\n" +--- +success: false +exit_code: 1 +----- stdout ----- +[ + { + "code": "F401", + "end_location": { + "column": 10, + "row": 1 + }, + "filename": "/path/to/F401.py", + "fix": { + "applicability": "Automatic", + "edits": [ + { + "content": "", + "end_location": { + "column": 1, + "row": 2 + }, + "location": { + "column": 1, + "row": 1 + } + } + ], + "message": "Remove unused import: `os`" + }, + "location": { + "column": 8, + "row": 1 + }, + "message": "`os` imported but unused", + "noqa_row": 1, + "url": "https://beta.ruff.rs/docs/rules/unused-import" + } +] +----- stderr ----- +