Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
feat(rome_cli): command rome lint (#4629)
Browse files Browse the repository at this point in the history
  • Loading branch information
ematipico committed Jun 30, 2023
1 parent e4747ee commit 08ad08f
Show file tree
Hide file tree
Showing 67 changed files with 5,644 additions and 30 deletions.
94 changes: 94 additions & 0 deletions crates/rome_cli/src/commands/lint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use crate::cli_options::CliOptions;
use crate::configuration::{load_configuration, LoadedConfiguration};
use crate::vcs::store_path_to_ignore_from_vcs;
use crate::{execute_mode, CliDiagnostic, CliSession, Execution, TraversalMode};
use rome_service::workspace::{FixFileMode, UpdateSettingsParams};
use rome_service::{Configuration, MergeWith};
use std::ffi::OsString;
use std::path::PathBuf;

pub(crate) struct LintCommandPayload {
pub(crate) apply: bool,
pub(crate) apply_unsafe: bool,
pub(crate) cli_options: CliOptions,
pub(crate) configuration: Option<Configuration>,
pub(crate) paths: Vec<OsString>,
pub(crate) stdin_file_path: Option<String>,
}

/// Handler for the "lint" command of the Rome CLI
pub(crate) fn lint(
mut session: CliSession,
payload: LintCommandPayload,
) -> Result<(), CliDiagnostic> {
let LintCommandPayload {
apply,
apply_unsafe,
cli_options,
configuration,
paths,
stdin_file_path,
} = payload;

let fix_file_mode = if apply && apply_unsafe {
return Err(CliDiagnostic::incompatible_arguments(
"--apply",
"--apply-unsafe",
));
} else if !apply && !apply_unsafe {
None
} else if apply && !apply_unsafe {
Some(FixFileMode::SafeFixes)
} else {
Some(FixFileMode::SafeAndUnsafeFixes)
};

let LoadedConfiguration {
configuration: mut fs_configuration,
directory_path: configuration_path,
..
} = load_configuration(&mut session, &cli_options)?
.or_diagnostic(session.app.console, cli_options.verbose)?;

fs_configuration.merge_with(configuration);

// check if support of git ignore files is enabled
let vcs_base_path = configuration_path.or(session.app.fs.working_directory());
store_path_to_ignore_from_vcs(
&mut session,
&mut fs_configuration,
vcs_base_path,
&cli_options,
)?;

let stdin = if let Some(stdin_file_path) = stdin_file_path {
let console = &mut session.app.console;
let input_code = console.read();
if let Some(input_code) = input_code {
let path = PathBuf::from(stdin_file_path);
Some((path, input_code))
} else {
// we provided the argument without a piped stdin, we bail
return Err(CliDiagnostic::missing_argument("stdin", "lint"));
}
} else {
None
};

session
.app
.workspace
.update_settings(UpdateSettingsParams {
configuration: fs_configuration,
})?;

execute_mode(
Execution::new(TraversalMode::Lint {
fix_file_mode,
stdin,
}),
session,
&cli_options,
paths,
)
}
24 changes: 24 additions & 0 deletions crates/rome_cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub(crate) mod ci;
pub(crate) mod daemon;
pub(crate) mod format;
pub(crate) mod init;
pub(crate) mod lint;
pub(crate) mod migrate;
pub(crate) mod rage;
pub(crate) mod version;
Expand Down Expand Up @@ -75,6 +76,26 @@ pub enum RomeCommand {
#[bpaf(positional("PATH"), many)]
paths: Vec<OsString>,
},
/// Run various checks on a set of files.
#[bpaf(command)]
Lint {
/// Apply safe fixes, formatting
#[bpaf(long("apply"), switch)]
apply: bool,
/// Apply safe fixes and unsafe fixes, formatting and import sorting
#[bpaf(long("apply-unsafe"), switch)]
apply_unsafe: bool,
#[bpaf(external, hide_usage, optional)]
configuration: Option<Configuration>,
#[bpaf(external, hide_usage)]
cli_options: CliOptions,
/// A file name with its extension to pass when reading from standard in, e.g. echo 'let a;' | rome lint --stdin-file-path=file.js"
#[bpaf(long("stdin-file-path"), argument("PATH"), hide_usage)]
stdin_file_path: Option<String>,
/// Single file, single path or list of paths
#[bpaf(positional("PATH"), many)]
paths: Vec<OsString>,
},
/// Run the formatter on a set of files.
#[bpaf(command)]
Format {
Expand Down Expand Up @@ -160,6 +181,7 @@ impl RomeCommand {
RomeCommand::Start => None,
RomeCommand::Stop => None,
RomeCommand::Check { cli_options, .. } => cli_options.colors.as_ref(),
RomeCommand::Lint { cli_options, .. } => cli_options.colors.as_ref(),
RomeCommand::Ci { cli_options, .. } => cli_options.colors.as_ref(),
RomeCommand::Format { cli_options, .. } => cli_options.colors.as_ref(),
RomeCommand::Init => None,
Expand All @@ -177,6 +199,7 @@ impl RomeCommand {
RomeCommand::Start => false,
RomeCommand::Stop => false,
RomeCommand::Check { cli_options, .. } => cli_options.use_server,
RomeCommand::Lint { cli_options, .. } => cli_options.use_server,
RomeCommand::Ci { cli_options, .. } => cli_options.use_server,
RomeCommand::Format { cli_options, .. } => cli_options.use_server,
RomeCommand::Init => false,
Expand All @@ -198,6 +221,7 @@ impl RomeCommand {
RomeCommand::Start => false,
RomeCommand::Stop => false,
RomeCommand::Check { cli_options, .. } => cli_options.verbose,
RomeCommand::Lint { cli_options, .. } => cli_options.verbose,
RomeCommand::Format { cli_options, .. } => cli_options.verbose,
RomeCommand::Ci { cli_options, .. } => cli_options.verbose,
RomeCommand::Init => false,
Expand Down
85 changes: 85 additions & 0 deletions crates/rome_cli/src/execute/lint_file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use crate::execute::diagnostics::{ResultExt, ResultIoExt};
use crate::execute::process_file::{FileResult, FileStatus, Message};
use crate::execute::traverse::TraversalOptions;
use crate::CliDiagnostic;
use rome_diagnostics::{category, Error};
use rome_fs::{OpenOptions, RomePath};
use rome_service::file_handlers::Language;
use rome_service::workspace::{FileGuard, OpenFileParams, RuleCategories};
use std::path::Path;
use std::sync::atomic::Ordering;

pub(crate) struct LintFile<'ctx, 'app> {
pub(crate) ctx: &'app TraversalOptions<'ctx, 'app>,
pub(crate) path: &'app Path,
}

/// Lints a single file and returns a [FileResult]
pub(crate) fn lint_file(payload: LintFile) -> FileResult {
let LintFile { ctx, path } = payload;
let rome_path = RomePath::new(path);
let mut errors = 0;
let open_options = OpenOptions::default()
.read(true)
.write(ctx.execution.requires_write_access());
let mut file = ctx
.fs
.open_with_options(path, open_options)
.with_file_path(path.display().to_string())?;

let mut input = String::new();
file.read_to_string(&mut input)
.with_file_path(path.display().to_string())?;

let file_guard = FileGuard::open(
ctx.workspace,
OpenFileParams {
path: rome_path,
version: 0,
content: input.clone(),
language_hint: Language::default(),
},
)
.with_file_path_and_code(path.display().to_string(), category!("internalError/fs"))?;
if let Some(fix_mode) = ctx.execution.as_fix_file_mode() {
let fixed = file_guard
.fix_file(*fix_mode, false)
.with_file_path_and_code(path.display().to_string(), category!("lint"))?;

ctx.push_message(Message::SkippedFixes {
skipped_suggested_fixes: fixed.skipped_suggested_fixes,
});

if fixed.code != input {
file.set_content(fixed.code.as_bytes())
.with_file_path(path.display().to_string())?;
file_guard.change_file(file.file_version(), fixed.code)?;
}
errors = fixed.errors;
}

let max_diagnostics = ctx.remaining_diagnostics.load(Ordering::Relaxed);
let result = file_guard
.pull_diagnostics(RuleCategories::LINT, max_diagnostics.into())
.with_file_path_and_code(path.display().to_string(), category!("lint"))?;

let no_diagnostics = result.diagnostics.is_empty() && result.skipped_diagnostics == 0;
let result = if no_diagnostics || ctx.execution.is_format() {
FileStatus::Success
} else {
FileStatus::Message(Message::Diagnostics {
name: path.display().to_string(),
content: input.clone(),
diagnostics: result.diagnostics.into_iter().map(Error::from).collect(),
skipped_diagnostics: result.skipped_diagnostics,
})
};
ctx.increment_processed();
if errors > 0 {
return Ok(FileStatus::Message(Message::ApplyError(
CliDiagnostic::file_apply_error(path.display().to_string()),
)));
} else {
Ok(result)
}
}
64 changes: 44 additions & 20 deletions crates/rome_cli/src/execute/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod diagnostics;
mod lint_file;
mod migrate;
mod process_file;
mod std_in;
Expand All @@ -11,6 +12,7 @@ use rome_diagnostics::MAXIMUM_DISPLAYABLE_DIAGNOSTICS;
use rome_fs::RomePath;
use rome_service::workspace::{FeatureName, FixFileMode};
use std::ffi::OsString;
use std::fmt::{Display, Formatter};
use std::path::PathBuf;

/// Useful information during the traversal of files and virtual content
Expand Down Expand Up @@ -48,6 +50,18 @@ pub(crate) enum TraversalMode {
/// 2. The content of the file
stdin: Option<(PathBuf, String)>,
},
/// This mode is enabled when running the command `rome lint`
Lint {
/// The type of fixes that should be applied when analyzing a file.
///
/// It's [None] if the `check` command is called without `--apply` or `--apply-suggested`
/// arguments.
fix_file_mode: Option<FixFileMode>,
/// An optional tuple.
/// 1. The virtual path to the file
/// 2. The content of the file
stdin: Option<(PathBuf, String)>,
},
/// This mode is enabled when running the command `rome ci`
CI,
/// This mode is enabled when running the command `rome format`
Expand All @@ -68,6 +82,18 @@ pub(crate) enum TraversalMode {
},
}

impl Display for TraversalMode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
TraversalMode::Check { .. } => write!(f, "check"),
TraversalMode::CI { .. } => write!(f, "ci"),
TraversalMode::Format { .. } => write!(f, "format"),
TraversalMode::Migrate { .. } => write!(f, "migrate"),
TraversalMode::Lint { .. } => write!(f, "lint"),
}
}
}

/// Tells to the execution of the traversal how the information should be reported
#[derive(Copy, Clone, Default)]
pub(crate) enum ReportMode {
Expand Down Expand Up @@ -111,10 +137,12 @@ impl Execution {

/// `true` only when running the traversal in [TraversalMode::Check] and `should_fix` is `true`
pub(crate) fn as_fix_file_mode(&self) -> Option<&FixFileMode> {
if let TraversalMode::Check { fix_file_mode, .. } = &self.traversal_mode {
fix_file_mode.as_ref()
} else {
None
match &self.traversal_mode {
TraversalMode::Check { fix_file_mode, .. }
| TraversalMode::Lint { fix_file_mode, .. } => fix_file_mode.as_ref(),
TraversalMode::Format { .. } | TraversalMode::CI | TraversalMode::Migrate { .. } => {
None
}
}
}

Expand All @@ -126,6 +154,10 @@ impl Execution {
matches!(self.traversal_mode, TraversalMode::Check { .. })
}

pub(crate) const fn is_lint(&self) -> bool {
matches!(self.traversal_mode, TraversalMode::Lint { .. })
}

pub(crate) const fn is_check_apply(&self) -> bool {
matches!(
self.traversal_mode,
Expand Down Expand Up @@ -153,7 +185,8 @@ impl Execution {
/// Whether the traversal mode requires write access to files
pub(crate) const fn requires_write_access(&self) -> bool {
match self.traversal_mode {
TraversalMode::Check { fix_file_mode, .. } => fix_file_mode.is_some(),
TraversalMode::Check { fix_file_mode, .. }
| TraversalMode::Lint { fix_file_mode, .. } => fix_file_mode.is_some(),
TraversalMode::CI => false,
TraversalMode::Format { write, .. } => write,
TraversalMode::Migrate { write: dry_run, .. } => dry_run,
Expand All @@ -162,19 +195,10 @@ impl Execution {

pub(crate) fn as_stdin_file(&self) -> Option<&(PathBuf, String)> {
match &self.traversal_mode {
TraversalMode::Format { stdin, .. } => stdin.as_ref(),
TraversalMode::Check { stdin, .. } => stdin.as_ref(),
_ => None,
}
}

/// Returns the subcommand of the [traversal mode](TraversalMode) execution
pub(crate) fn traversal_mode_subcommand(&self) -> &'static str {
match self.traversal_mode {
TraversalMode::Check { .. } => "check",
TraversalMode::CI { .. } => "ci",
TraversalMode::Format { .. } => "format",
TraversalMode::Migrate { .. } => "migrate",
TraversalMode::Format { stdin, .. }
| TraversalMode::Lint { stdin, .. }
| TraversalMode::Check { stdin, .. } => stdin.as_ref(),
TraversalMode::CI { .. } | TraversalMode::Migrate { .. } => None,
}
}
}
Expand All @@ -197,10 +221,10 @@ pub(crate) fn execute_mode(

max_diagnostics
} else {
// The command `rome check` gives a default value of 20.
// The commands `rome check` and `rome lint` give a default value of 20.
// In case of other commands that pass here, we limit to 50 to avoid to delay the terminal.
match &mode.traversal_mode {
TraversalMode::Check { .. } => 20,
TraversalMode::Check { .. } | TraversalMode::Lint { .. } => 20,
TraversalMode::CI | TraversalMode::Format { .. } | TraversalMode::Migrate { .. } => 50,
}
};
Expand Down
Loading

0 comments on commit 08ad08f

Please sign in to comment.