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

feat(rome_cli): command rome lint #4629

Merged
merged 2 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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