Skip to content

Commit

Permalink
Support linting input from stdin (#387)
Browse files Browse the repository at this point in the history
  • Loading branch information
harupy authored Oct 11, 2022
1 parent 209dce2 commit 8ba872e
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 3 deletions.
69 changes: 69 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ strum_macros = "0.24.3"
num-bigint = "0.4.3"

[dev-dependencies]
assert_cmd = "2.0.4"
insta = { version = "1.19.1", features = ["yaml"] }

[features]
Expand Down
3 changes: 3 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ pub struct Cli {
// TODO(charlie): This should be a sub-command.
#[arg(long, hide = true)]
pub autoformat: bool,
/// The name of the file when passing it through stdin.
#[arg(long)]
pub stdin_filename: Option<String>,
}

pub enum Warnable {
Expand Down
30 changes: 30 additions & 0 deletions src/linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,36 @@ pub(crate) fn check_path(
Ok(checks)
}

pub fn lint_stdin(path: &Path, stdin: &str, settings: &Settings) -> Result<Vec<Message>> {
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(stdin);

// Determine the noqa line for every line in the source.
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);

// Generate checks.
let checks = check_path(
path,
stdin,
tokens,
&noqa_line_for,
settings,
&fixer::Mode::None,
)?;

// Convert to messages.
Ok(checks
.into_iter()
.map(|check| Message {
kind: check.kind,
fixed: check.fix.map(|fix| fix.applied).unwrap_or_default(),
location: check.location,
end_location: check.end_location,
filename: path.to_string_lossy().to_string(),
})
.collect())
}

pub fn lint_path(
path: &Path,
settings: &Settings,
Expand Down
29 changes: 26 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::io;
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use std::sync::mpsc::channel;
Expand All @@ -19,7 +19,7 @@ use ruff::cli::{warn_on, Cli, Warnable};
use ruff::fs::iter_python_files;
use ruff::linter::add_noqa_to_path;
use ruff::linter::autoformat_path;
use ruff::linter::lint_path;
use ruff::linter::{lint_path, lint_stdin};
use ruff::logging::set_up_logging;
use ruff::message::Message;
use ruff::printer::{Printer, SerializationFormat};
Expand Down Expand Up @@ -75,6 +75,19 @@ fn show_files(files: &[PathBuf], settings: &Settings) {
}
}

fn read_from_stdin() -> Result<String> {
let mut buffer = String::new();
io::stdin().lock().read_to_string(&mut buffer)?;
Ok(buffer)
}

fn run_once_stdin(settings: &Settings, filename: &Path) -> Result<Vec<Message>> {
let stdin = read_from_stdin()?;
let mut messages = lint_stdin(filename, &stdin, settings)?;
messages.sort_unstable();
Ok(messages)
}

fn run_once(
files: &[PathBuf],
settings: &Settings,
Expand Down Expand Up @@ -352,7 +365,17 @@ fn inner_main() -> Result<ExitCode> {
println!("Formatted {modifications} files.");
}
} else {
let messages = run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?;
let messages = if cli.files == vec![PathBuf::from("-")] {
if cli.fix {
eprintln!("Warning: --fix is not enabled when reading from stdin.");
}

let filename = cli.stdin_filename.unwrap_or_else(|| "-".to_string());
let path = Path::new(&filename);
run_once_stdin(&settings, path)?
} else {
run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?
};
if !cli.quiet {
printer.write_once(&messages)?;
}
Expand Down
47 changes: 47 additions & 0 deletions tests/integration_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use std::str;

use anyhow::Result;
use assert_cmd::{crate_name, Command};

#[test]
fn test_stdin_success() -> Result<()> {
let mut cmd = Command::cargo_bin(crate_name!())?;
cmd.args(&["-"]).write_stdin("").assert().success();
Ok(())
}

#[test]
fn test_stdin_error() -> Result<()> {
let mut cmd = Command::cargo_bin(crate_name!())?;
let output = cmd
.args(&["-"])
.write_stdin("import os\n")
.assert()
.failure();
assert!(str::from_utf8(&output.get_output().stdout)?.contains("-:1:1: F401"));
Ok(())
}

#[test]
fn test_stdin_filename() -> Result<()> {
let mut cmd = Command::cargo_bin(crate_name!())?;
let output = cmd
.args(&["-", "--stdin-filename", "F401.py"])
.write_stdin("import os\n")
.assert()
.failure();
assert!(str::from_utf8(&output.get_output().stdout)?.contains("F401.py:1:1: F401"));
Ok(())
}

#[test]
fn test_stdin_autofix() -> Result<()> {
let mut cmd = Command::cargo_bin(crate_name!())?;
let output = cmd
.args(&["-", "--fix"])
.write_stdin("import os\n")
.assert()
.failure();
assert!(str::from_utf8(&output.get_output().stdout)?.contains("-:1:1: F401"));
Ok(())
}

0 comments on commit 8ba872e

Please sign in to comment.