From 679cd8c0465bbafe9b167f216ab295f6d4fa1aa0 Mon Sep 17 00:00:00 2001 From: Christopher Chang <51393127+chriscerie@users.noreply.github.com> Date: Sat, 14 Oct 2023 12:31:56 -0700 Subject: [PATCH] Add fix option --- docs/src/cli/usage.md | 1 + selene-lib/src/lints/test_util.rs | 4 +- selene/src/main.rs | 73 ++++++++++++++++++++++++++++--- selene/src/opts.rs | 3 ++ 4 files changed, 72 insertions(+), 9 deletions(-) diff --git a/docs/src/cli/usage.md b/docs/src/cli/usage.md index 25eb5855..fc7f0a79 100644 --- a/docs/src/cli/usage.md +++ b/docs/src/cli/usage.md @@ -9,6 +9,7 @@ USAGE: FLAGS: --allow-warnings Pass when only warnings occur --no-exclude Ignore excludes defined in config + --fix Automatically applies some lint suggestions -h, --help Prints help information -n, --no-summary Suppress summary information -q, --quiet Display only the necessary information. Equivalent to --display-style="quiet" diff --git a/selene-lib/src/lints/test_util.rs b/selene-lib/src/lints/test_util.rs index e921b93a..8ac4199b 100644 --- a/selene-lib/src/lints/test_util.rs +++ b/selene-lib/src/lints/test_util.rs @@ -44,7 +44,7 @@ fn replace_code_range(code: &str, start: usize, end: usize, replacement: &str) - } // Assumes diagnostics is sorted by starting ranges and that there are no overlapping ranges -fn apply_diagnostics_fixes(code: &str, diagnostics: &Vec) -> String { +fn apply_diagnostics_fixes(code: &str, diagnostics: &Vec<&Diagnostic>) -> String { let mut bytes_offset = 0; let new_code = diagnostics @@ -131,7 +131,7 @@ pub fn test_lint_config_with_output< let mut output = termcolor::NoColor::new(Vec::new()); - let fixed_lua_code = apply_diagnostics_fixes(&lua_source, &diagnostics); + let fixed_lua_code = apply_diagnostics_fixes(&lua_source, &diagnostics.iter().collect()); let fixed_diff = generate_diff(&lua_source, &fixed_lua_code); let diff_output_path = path_base.with_extension("fixed.diff"); diff --git a/selene/src/main.rs b/selene/src/main.rs index 610aeb8a..d3d4b26c 100644 --- a/selene/src/main.rs +++ b/selene/src/main.rs @@ -11,11 +11,15 @@ use std::{ use codespan_reporting::{ diagnostic::{ - Diagnostic as CodespanDiagnostic, Label as CodespanLabel, Severity as CodespanSeverity, + self, Diagnostic as CodespanDiagnostic, Label as CodespanLabel, + Severity as CodespanSeverity, }, term::DisplayStyle as CodespanDisplayStyle, }; -use selene_lib::{lints::Severity, *}; +use selene_lib::{ + lints::{Diagnostic, Severity}, + *, +}; use structopt::{clap, StructOpt}; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; use threadpool::ThreadPool; @@ -178,7 +182,50 @@ fn emit_codespan_locked( emit_codespan(&mut stdout, files, diagnostic); } -fn read(checker: &Checker, filename: &Path, mut reader: R) { +fn replace_code_range(code: &str, start: usize, end: usize, replacement: &str) -> String { + if start > end || end > code.len() { + return code.to_string(); + } + + return format!("{}{}{}", &code[..start], replacement, &code[end..]); +} + +// Assumes diagnostics is sorted by starting ranges and that there are no overlapping ranges +// This is just copied from `test_util`. Can we get a better abstraction? +// FIXME: handle the overlapping ranges +fn apply_diagnostics_fixes(code: &str, diagnostics: &Vec<&Diagnostic>) -> String { + let mut bytes_offset = 0; + + let new_code = diagnostics + .iter() + .fold(code.to_string(), |code, diagnostic| { + if let Some(fixed) = &diagnostic.fixed_code { + let (start, end) = diagnostic.primary_label.range; + let new_code = replace_code_range( + code.as_str(), + (start as isize + bytes_offset as isize) as usize, + (end as isize + bytes_offset as isize) as usize, + &fixed.as_str(), + ); + + bytes_offset += fixed.len() as isize - (end - start) as isize; + new_code + } else { + code + } + }); + + full_moon::parse(&new_code).expect("Failed to parse fixed code"); + + new_code +} + +fn read( + checker: &Checker, + filename: &Path, + mut reader: R, + is_fix: bool, +) { let mut buffer = Vec::new(); if let Err(error) = reader.read_to_end(&mut buffer) { error!( @@ -288,6 +335,17 @@ fn read(checker: &Checker, filename: &Path, mut rea let stdout = termcolor::StandardStream::stdout(get_color()); let mut stdout = stdout.lock(); + if is_fix { + let fixed_code = apply_diagnostics_fixes( + contents.as_ref(), + &diagnostics + .iter() + .map(|diagnostic| &diagnostic.diagnostic) + .collect(), + ); + let _ = fs::write(filename, fixed_code); + } + for diagnostic in diagnostics { if opts.luacheck { // Existing Luacheck consumers presumably use --formatter plain @@ -376,7 +434,7 @@ fn read(checker: &Checker, filename: &Path, mut rea } } -fn read_file(checker: &Checker, filename: &Path) { +fn read_file(checker: &Checker, filename: &Path, is_fix: bool) { read( checker, filename, @@ -388,6 +446,7 @@ fn read_file(checker: &Checker, filename: &Path) { return; } }, + is_fix, ); } @@ -608,7 +667,7 @@ fn start(mut options: opts::Options) { for filename in &options.files { if filename == "-" { let checker = Arc::clone(&checker); - pool.execute(move || read(&checker, Path::new("-"), io::stdin().lock())); + pool.execute(move || read(&checker, Path::new("-"), io::stdin().lock(), options.fix)); continue; } @@ -622,7 +681,7 @@ fn start(mut options: opts::Options) { continue; } - pool.execute(move || read_file(&checker, Path::new(&filename))); + pool.execute(move || read_file(&checker, Path::new(&filename), options.fix)); } else if metadata.is_dir() { for pattern in &options.pattern { let glob = match glob::glob(&format!( @@ -646,7 +705,7 @@ fn start(mut options: opts::Options) { let checker = Arc::clone(&checker); - pool.execute(move || read_file(&checker, &path)); + pool.execute(move || read_file(&checker, &path, options.fix)); } Err(error) => { diff --git a/selene/src/opts.rs b/selene/src/opts.rs index 1ce1bbb7..8f83a3b1 100644 --- a/selene/src/opts.rs +++ b/selene/src/opts.rs @@ -69,6 +69,9 @@ pub struct Options { #[structopt(long)] pub no_exclude: bool, + + #[structopt(long)] + pub fix: bool, } impl Options {