mirror of https://github.com/astral-sh/ruff
Include fixes in JSON API output (#988)
This commit is contained in:
parent
af40e64d6c
commit
46f5053c73
46
src/main.rs
46
src/main.rs
|
|
@ -37,6 +37,7 @@ use log::{debug, error};
|
||||||
use notify::{raw_watcher, RecursiveMode, Watcher};
|
use notify::{raw_watcher, RecursiveMode, Watcher};
|
||||||
#[cfg(not(target_family = "wasm"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
use ruff::autofix::fixer;
|
||||||
use rustpython_ast::Location;
|
use rustpython_ast::Location;
|
||||||
use walkdir::DirEntry;
|
use walkdir::DirEntry;
|
||||||
|
|
||||||
|
|
@ -60,14 +61,23 @@ fn read_from_stdin() -> Result<String> {
|
||||||
Ok(buffer)
|
Ok(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_once_stdin(settings: &Settings, filename: &Path, autofix: bool) -> Result<Diagnostics> {
|
fn run_once_stdin(
|
||||||
|
settings: &Settings,
|
||||||
|
filename: &Path,
|
||||||
|
autofix: &fixer::Mode,
|
||||||
|
) -> Result<Diagnostics> {
|
||||||
let stdin = read_from_stdin()?;
|
let stdin = read_from_stdin()?;
|
||||||
let mut diagnostics = lint_stdin(filename, &stdin, settings, &autofix.into())?;
|
let mut diagnostics = lint_stdin(filename, &stdin, settings, autofix)?;
|
||||||
diagnostics.messages.sort_unstable();
|
diagnostics.messages.sort_unstable();
|
||||||
Ok(diagnostics)
|
Ok(diagnostics)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_once(files: &[PathBuf], settings: &Settings, cache: bool, autofix: bool) -> Diagnostics {
|
fn run_once(
|
||||||
|
files: &[PathBuf],
|
||||||
|
settings: &Settings,
|
||||||
|
cache: bool,
|
||||||
|
autofix: &fixer::Mode,
|
||||||
|
) -> Diagnostics {
|
||||||
// Collect all the files to check.
|
// Collect all the files to check.
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let paths: Vec<Result<DirEntry, walkdir::Error>> = files
|
let paths: Vec<Result<DirEntry, walkdir::Error>> = files
|
||||||
|
|
@ -83,7 +93,7 @@ fn run_once(files: &[PathBuf], settings: &Settings, cache: bool, autofix: bool)
|
||||||
match entry {
|
match entry {
|
||||||
Ok(entry) => {
|
Ok(entry) => {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
lint_path(path, settings, &cache.into(), &autofix.into())
|
lint_path(path, settings, &cache.into(), autofix)
|
||||||
.map_err(|e| (Some(path.to_owned()), e.to_string()))
|
.map_err(|e| (Some(path.to_owned()), e.to_string()))
|
||||||
}
|
}
|
||||||
Err(e) => Err((
|
Err(e) => Err((
|
||||||
|
|
@ -99,6 +109,7 @@ fn run_once(files: &[PathBuf], settings: &Settings, cache: bool, autofix: bool)
|
||||||
kind: CheckKind::IOError(message),
|
kind: CheckKind::IOError(message),
|
||||||
location: Location::default(),
|
location: Location::default(),
|
||||||
end_location: Location::default(),
|
end_location: Location::default(),
|
||||||
|
fix: None,
|
||||||
filename: path.to_string_lossy().to_string(),
|
filename: path.to_string_lossy().to_string(),
|
||||||
source: None,
|
source: None,
|
||||||
}])
|
}])
|
||||||
|
|
@ -266,7 +277,13 @@ fn inner_main() -> Result<ExitCode> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract settings for internal use.
|
// Extract settings for internal use.
|
||||||
let fix_enabled: bool = configuration.fix;
|
let autofix = if configuration.fix {
|
||||||
|
fixer::Mode::Apply
|
||||||
|
} else if matches!(configuration.format, SerializationFormat::Json) {
|
||||||
|
fixer::Mode::Generate
|
||||||
|
} else {
|
||||||
|
fixer::Mode::None
|
||||||
|
};
|
||||||
let settings = Settings::from_configuration(configuration, project_root.as_ref())?;
|
let settings = Settings::from_configuration(configuration, project_root.as_ref())?;
|
||||||
|
|
||||||
// Now that we've inferred the appropriate log level, add some debug
|
// Now that we've inferred the appropriate log level, add some debug
|
||||||
|
|
@ -299,10 +316,7 @@ fn inner_main() -> Result<ExitCode> {
|
||||||
|
|
||||||
let printer = Printer::new(&settings.format, &log_level);
|
let printer = Printer::new(&settings.format, &log_level);
|
||||||
if cli.watch {
|
if cli.watch {
|
||||||
if settings.format != SerializationFormat::Text {
|
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||||
eprintln!("Warning: --format 'text' is used in watch mode.");
|
|
||||||
}
|
|
||||||
if fix_enabled {
|
|
||||||
eprintln!("Warning: --fix is not enabled in watch mode.");
|
eprintln!("Warning: --fix is not enabled in watch mode.");
|
||||||
}
|
}
|
||||||
if cli.add_noqa {
|
if cli.add_noqa {
|
||||||
|
|
@ -311,12 +325,15 @@ fn inner_main() -> Result<ExitCode> {
|
||||||
if cli.autoformat {
|
if cli.autoformat {
|
||||||
eprintln!("Warning: --autoformat is not enabled in watch mode.");
|
eprintln!("Warning: --autoformat is not enabled in watch mode.");
|
||||||
}
|
}
|
||||||
|
if settings.format != SerializationFormat::Text {
|
||||||
|
eprintln!("Warning: --format 'text' is used in watch mode.");
|
||||||
|
}
|
||||||
|
|
||||||
// Perform an initial run instantly.
|
// Perform an initial run instantly.
|
||||||
printer.clear_screen()?;
|
printer.clear_screen()?;
|
||||||
printer.write_to_user("Starting linter in watch mode...\n");
|
printer.write_to_user("Starting linter in watch mode...\n");
|
||||||
|
|
||||||
let messages = run_once(&cli.files, &settings, cache_enabled, false);
|
let messages = run_once(&cli.files, &settings, cache_enabled, &fixer::Mode::None);
|
||||||
printer.write_continuously(&messages)?;
|
printer.write_continuously(&messages)?;
|
||||||
|
|
||||||
// Configure the file watcher.
|
// Configure the file watcher.
|
||||||
|
|
@ -334,7 +351,8 @@ fn inner_main() -> Result<ExitCode> {
|
||||||
printer.clear_screen()?;
|
printer.clear_screen()?;
|
||||||
printer.write_to_user("File change detected...\n");
|
printer.write_to_user("File change detected...\n");
|
||||||
|
|
||||||
let messages = run_once(&cli.files, &settings, cache_enabled, false);
|
let messages =
|
||||||
|
run_once(&cli.files, &settings, cache_enabled, &fixer::Mode::None);
|
||||||
printer.write_continuously(&messages)?;
|
printer.write_continuously(&messages)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -359,15 +377,15 @@ fn inner_main() -> Result<ExitCode> {
|
||||||
let diagnostics = if is_stdin {
|
let diagnostics = if is_stdin {
|
||||||
let filename = cli.stdin_filename.unwrap_or_else(|| "-".to_string());
|
let filename = cli.stdin_filename.unwrap_or_else(|| "-".to_string());
|
||||||
let path = Path::new(&filename);
|
let path = Path::new(&filename);
|
||||||
run_once_stdin(&settings, path, fix_enabled)?
|
run_once_stdin(&settings, path, &autofix)?
|
||||||
} else {
|
} else {
|
||||||
run_once(&cli.files, &settings, cache_enabled, fix_enabled)
|
run_once(&cli.files, &settings, cache_enabled, &autofix)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Always try to print violations (the printer itself may suppress output),
|
// Always try to print violations (the printer itself may suppress output),
|
||||||
// unless we're writing fixes via stdin (in which case, the transformed
|
// unless we're writing fixes via stdin (in which case, the transformed
|
||||||
// source code goes to stdout).
|
// source code goes to stdout).
|
||||||
if !(is_stdin && fix_enabled) {
|
if !(is_stdin && matches!(autofix, fixer::Mode::Apply)) {
|
||||||
printer.write_once(&diagnostics)?;
|
printer.write_once(&diagnostics)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use rustpython_parser::ast::Location;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::ast::types::Range;
|
use crate::ast::types::Range;
|
||||||
|
use crate::autofix::Fix;
|
||||||
use crate::checks::{Check, CheckKind};
|
use crate::checks::{Check, CheckKind};
|
||||||
use crate::source_code_locator::SourceCodeLocator;
|
use crate::source_code_locator::SourceCodeLocator;
|
||||||
|
|
||||||
|
|
@ -12,6 +13,7 @@ pub struct Message {
|
||||||
pub kind: CheckKind,
|
pub kind: CheckKind,
|
||||||
pub location: Location,
|
pub location: Location,
|
||||||
pub end_location: Location,
|
pub end_location: Location,
|
||||||
|
pub fix: Option<Fix>,
|
||||||
pub filename: String,
|
pub filename: String,
|
||||||
pub source: Option<Source>,
|
pub source: Option<Source>,
|
||||||
}
|
}
|
||||||
|
|
@ -22,6 +24,7 @@ impl Message {
|
||||||
kind: check.kind,
|
kind: check.kind,
|
||||||
location: Location::new(check.location.row(), check.location.column() + 1),
|
location: Location::new(check.location.row(), check.location.column() + 1),
|
||||||
end_location: Location::new(check.end_location.row(), check.end_location.column() + 1),
|
end_location: Location::new(check.end_location.row(), check.end_location.column() + 1),
|
||||||
|
fix: check.fix,
|
||||||
filename,
|
filename,
|
||||||
source,
|
source,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ use itertools::iterate;
|
||||||
use rustpython_parser::ast::Location;
|
use rustpython_parser::ast::Location;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::checks::{CheckCode, CheckKind};
|
use crate::autofix::Fix;
|
||||||
|
use crate::checks::CheckCode;
|
||||||
use crate::fs::relativize_path;
|
use crate::fs::relativize_path;
|
||||||
use crate::linter::Diagnostics;
|
use crate::linter::Diagnostics;
|
||||||
use crate::logging::LogLevel;
|
use crate::logging::LogLevel;
|
||||||
|
|
@ -19,9 +20,9 @@ use crate::tell_user;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct ExpandedMessage<'a> {
|
struct ExpandedMessage<'a> {
|
||||||
kind: &'a CheckKind,
|
|
||||||
code: &'a CheckCode,
|
code: &'a CheckCode,
|
||||||
message: String,
|
message: String,
|
||||||
|
fix: Option<&'a Fix>,
|
||||||
location: Location,
|
location: Location,
|
||||||
end_location: Location,
|
end_location: Location,
|
||||||
filename: &'a str,
|
filename: &'a str,
|
||||||
|
|
@ -85,9 +86,9 @@ impl<'a> Printer<'a> {
|
||||||
.messages
|
.messages
|
||||||
.iter()
|
.iter()
|
||||||
.map(|message| ExpandedMessage {
|
.map(|message| ExpandedMessage {
|
||||||
kind: &message.kind,
|
|
||||||
code: message.kind.code(),
|
code: message.kind.code(),
|
||||||
message: message.kind.body(),
|
message: message.kind.body(),
|
||||||
|
fix: message.fix.as_ref(),
|
||||||
location: message.location,
|
location: message.location,
|
||||||
end_location: message.end_location,
|
end_location: message.end_location,
|
||||||
filename: &message.filename,
|
filename: &message.filename,
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,11 @@ fn test_stdin_error() -> Result<()> {
|
||||||
.write_stdin("import os\n")
|
.write_stdin("import os\n")
|
||||||
.assert()
|
.assert()
|
||||||
.failure();
|
.failure();
|
||||||
assert!(str::from_utf8(&output.get_output().stdout)?.contains("-:1:8: F401"));
|
assert_eq!(
|
||||||
|
str::from_utf8(&output.get_output().stdout)?,
|
||||||
|
"Found 1 error(s).\n-:1:8: F401 `os` imported but unused\n1 potentially fixable with the \
|
||||||
|
--fix option.\n"
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,7 +37,31 @@ fn test_stdin_filename() -> Result<()> {
|
||||||
.write_stdin("import os\n")
|
.write_stdin("import os\n")
|
||||||
.assert()
|
.assert()
|
||||||
.failure();
|
.failure();
|
||||||
assert!(str::from_utf8(&output.get_output().stdout)?.contains("F401.py:1:8: F401"));
|
assert_eq!(
|
||||||
|
str::from_utf8(&output.get_output().stdout)?,
|
||||||
|
"Found 1 error(s).\nF401.py:1:8: F401 `os` imported but unused\n1 potentially fixable \
|
||||||
|
with the --fix option.\n"
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stdin_json() -> Result<()> {
|
||||||
|
let mut cmd = Command::cargo_bin(crate_name!())?;
|
||||||
|
let output = cmd
|
||||||
|
.args(["-", "--format", "json", "--stdin-filename", "F401.py"])
|
||||||
|
.write_stdin("import os\n")
|
||||||
|
.assert()
|
||||||
|
.failure();
|
||||||
|
assert_eq!(
|
||||||
|
str::from_utf8(&output.get_output().stdout)?,
|
||||||
|
"[\n {\n \"code\": \"F401\",\n \"message\": \"`os` imported but unused\",\n \
|
||||||
|
\"fix\": {\n \"content\": \"\",\n \"location\": {\n \"row\": 1,\n \
|
||||||
|
\"column\": 0\n },\n \"end_location\": {\n \"row\": 2,\n \
|
||||||
|
\"column\": 0\n }\n },\n \"location\": {\n \"row\": 1,\n \
|
||||||
|
\"column\": 8\n },\n \"end_location\": {\n \"row\": 1,\n \"column\": \
|
||||||
|
10\n },\n \"filename\": \"F401.py\"\n }\n]\n"
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue