diff --git a/crates/rome_cli/src/commands/check.rs b/crates/rome_cli/src/commands/check.rs index 664809a98256..5875ad9f65d3 100644 --- a/crates/rome_cli/src/commands/check.rs +++ b/crates/rome_cli/src/commands/check.rs @@ -1,8 +1,5 @@ use crate::commands::format::apply_format_settings_from_cli; -use crate::{ - traversal::{traverse, TraversalMode}, - CliSession, Termination, -}; +use crate::{execute_mode, CliSession, ExecutionMode, Termination}; use rome_diagnostics::MAXIMUM_DISPLAYABLE_DIAGNOSTICS; use rome_service::load_config; use rome_service::settings::WorkspaceSettings; @@ -63,8 +60,8 @@ pub(crate) fn check(mut session: CliSession) -> Result<(), Termination> { Some(FixFileMode::SafeAndSuggestedFixes) }; - traverse( - TraversalMode::Check { + execute_mode( + ExecutionMode::Check { max_diagnostics, fix_file_mode, }, diff --git a/crates/rome_cli/src/commands/ci.rs b/crates/rome_cli/src/commands/ci.rs index 8918e163d999..7f3881877715 100644 --- a/crates/rome_cli/src/commands/ci.rs +++ b/crates/rome_cli/src/commands/ci.rs @@ -1,7 +1,4 @@ -use crate::{ - traversal::{traverse, TraversalMode}, - CliSession, Termination, -}; +use crate::{execute_mode, CliSession, ExecutionMode, Termination}; use rome_service::load_config; use rome_service::settings::WorkspaceSettings; use rome_service::workspace::UpdateSettingsParams; @@ -26,5 +23,5 @@ pub(crate) fn ci(mut session: CliSession) -> Result<(), Termination> { settings: workspace_settings, })?; - traverse(TraversalMode::CI, session) + execute_mode(ExecutionMode::CI, session) } diff --git a/crates/rome_cli/src/commands/format.rs b/crates/rome_cli/src/commands/format.rs index 65509c25a6c2..3ed6f80a3361 100644 --- a/crates/rome_cli/src/commands/format.rs +++ b/crates/rome_cli/src/commands/format.rs @@ -1,21 +1,14 @@ use rome_formatter::IndentStyle; use rome_service::{load_config, settings::WorkspaceSettings, workspace::UpdateSettingsParams}; +use std::path::PathBuf; -use crate::{ - traversal::{traverse, TraversalMode}, - CliSession, Termination, -}; +use crate::{execute_mode, CliSession, ExecutionMode, Termination}; /// Handler for the "format" command of the Rome CLI pub(crate) fn format(mut session: CliSession) -> Result<(), Termination> { let configuration = load_config(&session.app.fs)?; let mut workspace_settings = WorkspaceSettings::default(); - if let Some(configuration) = &configuration { - if configuration.is_formatter_disabled() { - return Ok(()); - } - } if let Some(configuration) = configuration { workspace_settings.merge_with_configuration(configuration); } @@ -24,6 +17,27 @@ pub(crate) fn format(mut session: CliSession) -> Result<(), Termination> { let is_write = session.args.contains("--write"); let ignore_errors = session.args.contains("--skip-errors"); + let stdin_file_path: Option = session + .args + .opt_value_from_str("--stdin-file-path") + .map_err(|source| Termination::ParseError { + argument: "--stdin-file-path", + source, + })?; + + 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(Termination::MissingArgument { argument: "stdin" }); + } + } else { + None + }; session .app @@ -32,10 +46,11 @@ pub(crate) fn format(mut session: CliSession) -> Result<(), Termination> { settings: workspace_settings, })?; - traverse( - TraversalMode::Format { + execute_mode( + ExecutionMode::Format { ignore_errors, write: is_write, + stdin, }, session, ) diff --git a/crates/rome_cli/src/commands/help.rs b/crates/rome_cli/src/commands/help.rs index 390c4f43bd61..c8ad6bc17ffd 100644 --- a/crates/rome_cli/src/commands/help.rs +++ b/crates/rome_cli/src/commands/help.rs @@ -43,6 +43,7 @@ const FORMAT_OPTIONS: Markup = markup! { ""--indent-size "" If the indentation style is set to spaces, determine how many spaces should be used for indentation (default: 2) ""--line-width "" Determine how many characters the formatter is allowed to print in a single line (default: 80) ""--quote-style "" Determine whether the formatter should use single or double quotes for strings (default: double) + ""--stdin-file-path "" Mandatory argument to use when piping content via standard input, e.g. echo 'let a;' | rome format --stdin-filepath file.js " }; diff --git a/crates/rome_cli/src/execute.rs b/crates/rome_cli/src/execute.rs new file mode 100644 index 000000000000..83b06795c174 --- /dev/null +++ b/crates/rome_cli/src/execute.rs @@ -0,0 +1,119 @@ +use crate::traversal::traverse; +use crate::{CliSession, Termination}; +use rome_console::{markup, ConsoleExt}; +use rome_fs::RomePath; +use rome_service::workspace::{ + FeatureName, FixFileMode, FormatFileParams, OpenFileParams, SupportsFeatureParams, +}; +use std::path::PathBuf; + +#[derive(Clone)] +pub(crate) enum ExecutionMode { + /// This mode is enabled when running the command `rome check` + Check { + /// The maximum number of diagnostics that can be printed in console + max_diagnostics: u16, + + /// 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, + }, + /// This mode is enabled when running the command `rome ci` + CI, + /// This mode is enabled when running the command `rome format` + Format { + /// It ignores parse errors + ignore_errors: bool, + /// It writes the new content on file + write: bool, + /// An optional tuple. + /// 1. The virtual path to the file + /// 2. The content of the file + stdin: Option<(PathBuf, String)>, + }, +} + +impl ExecutionMode { + pub(crate) fn get_max_diagnostics(&self) -> Option { + match self { + ExecutionMode::Check { + max_diagnostics, .. + } => Some(*max_diagnostics), + _ => None, + } + } + + /// `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 ExecutionMode::Check { fix_file_mode, .. } = self { + fix_file_mode.as_ref() + } else { + None + } + } + + pub(crate) fn is_ci(&self) -> bool { + matches!(self, ExecutionMode::CI { .. }) + } + + pub(crate) fn is_check(&self) -> bool { + matches!(self, ExecutionMode::Check { .. }) + } + + pub(crate) fn is_format(&self) -> bool { + matches!(self, ExecutionMode::Format { .. }) + } + + pub(crate) fn as_stdin_file(&self) -> Option<&(PathBuf, String)> { + match self { + ExecutionMode::Format { stdin, .. } => stdin.as_ref(), + _ => None, + } + } +} + +/// Based on the [mode](ExecutionMode), the function might launch a traversal of the file system +/// or handles the stdin file. +pub(crate) fn execute_mode( + mode: ExecutionMode, + mut session: CliSession, +) -> Result<(), Termination> { + // don't do any traversal if there's some content coming from stdin + if let Some((path, content)) = mode.as_stdin_file() { + let workspace = &*session.app.workspace; + let console = &mut *session.app.console; + let rome_path = RomePath::new(path, 0); + + if mode.is_format() { + let can_format = workspace.supports_feature(SupportsFeatureParams { + path: rome_path.clone(), + feature: FeatureName::Format, + }); + if can_format { + workspace.open_file(OpenFileParams { + path: rome_path.clone(), + version: 0, + content: content.into(), + })?; + let printed = workspace.format_file(FormatFileParams { path: rome_path })?; + + console.log(markup! { + {printed.as_code()} + }); + } else { + console.log(markup! { + {content} + }); + console.error(markup!{ + "The content was not formatted because the formatter is currently disabled." + }) + } + } + + Ok(()) + } else { + traverse(mode, session) + } +} diff --git a/crates/rome_cli/src/lib.rs b/crates/rome_cli/src/lib.rs index e7fc364da26c..77e132ece5a6 100644 --- a/crates/rome_cli/src/lib.rs +++ b/crates/rome_cli/src/lib.rs @@ -7,11 +7,13 @@ use rome_flags::FeatureFlags; use rome_service::App; mod commands; +mod execute; mod metrics; mod panic; mod termination; mod traversal; +pub(crate) use execute::{execute_mode, ExecutionMode}; pub use panic::setup_panic_handler; pub use termination::Termination; diff --git a/crates/rome_cli/src/traversal.rs b/crates/rome_cli/src/traversal.rs index 2104bcb83836..841b96b926e1 100644 --- a/crates/rome_cli/src/traversal.rs +++ b/crates/rome_cli/src/traversal.rs @@ -1,14 +1,3 @@ -use std::{ - collections::HashMap, - ffi::OsString, - fmt::Display, - io, - panic::catch_unwind, - path::{Path, PathBuf}, - sync::atomic::{AtomicUsize, Ordering}, - time::{Duration, Instant}, -}; - use crossbeam::channel::{unbounded, Receiver, Sender}; use rayon::join; use rome_console::{ @@ -22,15 +11,24 @@ use rome_diagnostics::{ }; use rome_fs::{AtomicInterner, FileSystem, OpenOptions, PathInterner, RomePath}; use rome_fs::{TraversalContext, TraversalScope}; -use rome_service::workspace::FixFileMode; use rome_service::{ workspace::{FeatureName, FileGuard, OpenFileParams, RuleCategories, SupportsFeatureParams}, Workspace, }; +use std::{ + collections::HashMap, + ffi::OsString, + fmt::Display, + io, + panic::catch_unwind, + path::{Path, PathBuf}, + sync::atomic::{AtomicUsize, Ordering}, + time::{Duration, Instant}, +}; -use crate::{CliSession, Termination}; +use crate::{CliSession, ExecutionMode, Termination}; -pub(crate) fn traverse(mode: TraversalMode, mut session: CliSession) -> Result<(), Termination> { +pub(crate) fn traverse(mode: ExecutionMode, mut session: CliSession) -> Result<(), Termination> { // Check that at least one input file / directory was specified in the command line let mut inputs = vec![]; @@ -49,7 +47,7 @@ pub(crate) fn traverse(mode: TraversalMode, mut session: CliSession) -> Result<( inputs.push(input); } - if inputs.is_empty() { + if inputs.is_empty() && mode.as_stdin_file().is_none() { return Err(Termination::MissingArgument { argument: "", }); @@ -66,7 +64,7 @@ pub(crate) fn traverse(mode: TraversalMode, mut session: CliSession) -> Result<( let console = &mut *session.app.console; let (has_errors, duration) = join( - || print_messages_to_console(mode, console, recv_files, recv_msgs), + || print_messages_to_console(mode.clone(), console, recv_files, recv_msgs), || { // The traversal context is scoped to ensure all the channels it // contains are properly closed once the traversal finishes @@ -76,7 +74,7 @@ pub(crate) fn traverse(mode: TraversalMode, mut session: CliSession) -> Result<( &TraversalOptions { fs, workspace, - mode, + mode: mode.clone(), interner, processed: &processed, skipped: &skipped, @@ -90,7 +88,7 @@ pub(crate) fn traverse(mode: TraversalMode, mut session: CliSession) -> Result<( let skipped = skipped.load(Ordering::Relaxed); match mode { - TraversalMode::Check { .. } => { + ExecutionMode::Check { .. } => { if mode.as_fix_file_mode().is_some() { console.log(rome_console::markup! { "Fixed "{count}" files in "{duration} @@ -101,17 +99,17 @@ pub(crate) fn traverse(mode: TraversalMode, mut session: CliSession) -> Result<( }); } } - TraversalMode::CI { .. } => { + ExecutionMode::CI { .. } => { console.log(rome_console::markup! { "Checked "{count}" files in "{duration} }); } - TraversalMode::Format { write: false, .. } => { + ExecutionMode::Format { write: false, .. } => { console.log(rome_console::markup! { "Compared "{count}" files in "{duration} }); } - TraversalMode::Format { write: true, .. } => { + ExecutionMode::Format { write: true, .. } => { console.log(rome_console::markup! { "Formatted "{count}" files in "{duration} }); @@ -149,7 +147,7 @@ fn traverse_inputs(fs: &dyn FileSystem, inputs: Vec, ctx: &TraversalOp /// This thread receives [Message]s from the workers through the `recv_msgs` /// and `recv_files` channels and prints them to the console fn print_messages_to_console( - mode: TraversalMode, + mode: ExecutionMode, console: &mut dyn Console, recv_files: Receiver<(usize, PathBuf)>, recv_msgs: Receiver, @@ -239,7 +237,7 @@ fn print_messages_to_console( old, new, } => { - let header = if matches!(mode, TraversalMode::CI { .. }) { + let header = if matches!(mode, ExecutionMode::CI { .. }) { // A diff is an error in CI mode has_errors = true; DiagnosticHeader { @@ -298,53 +296,6 @@ fn print_messages_to_console( has_errors } -#[derive(Clone, Copy)] -pub(crate) enum TraversalMode { - /// This mode is enabled when running the command `rome check` - Check { - /// The maximum number of diagnostics that can be printed in console - max_diagnostics: u16, - - /// 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, - }, - /// This mode is enabled when running the command `rome ci` - CI, - /// This mode is enabled when running the command `rome format` - Format { ignore_errors: bool, write: bool }, -} - -impl TraversalMode { - fn get_max_diagnostics(&self) -> Option { - match self { - TraversalMode::Check { - max_diagnostics, .. - } => Some(*max_diagnostics), - _ => None, - } - } - - /// `true` only when running the traversal in [TraversalMode::Check] and `should_fix` is `true` - fn as_fix_file_mode(&self) -> Option<&FixFileMode> { - if let TraversalMode::Check { fix_file_mode, .. } = self { - fix_file_mode.as_ref() - } else { - None - } - } - - fn is_ci(&self) -> bool { - matches!(self, TraversalMode::CI { .. }) - } - - fn is_check(&self) -> bool { - matches!(self, TraversalMode::Check { .. }) - } -} - /// Context object shared between directory traversal tasks struct TraversalOptions<'ctx, 'app> { /// Shared instance of [FileSystem] @@ -352,7 +303,7 @@ struct TraversalOptions<'ctx, 'app> { /// Instance of [Workspace] used by this instance of the CLI workspace: &'ctx dyn Workspace, /// Determines how the files should be processed - mode: TraversalMode, + mode: ExecutionMode, /// File paths interner used by the filesystem traversal interner: AtomicInterner, /// Shared atomic counter storing the number of processed files @@ -400,9 +351,9 @@ impl<'ctx, 'app> TraversalContext for TraversalOptions<'ctx, 'app> { fn can_handle(&self, rome_path: &RomePath) -> bool { match self.mode { - TraversalMode::Check { .. } => self.can_lint(rome_path), - TraversalMode::CI { .. } => self.can_lint(rome_path) || self.can_format(rome_path), - TraversalMode::Format { .. } => self.can_format(rome_path), + ExecutionMode::Check { .. } => self.can_lint(rome_path), + ExecutionMode::CI { .. } => self.can_lint(rome_path) || self.can_format(rome_path), + ExecutionMode::Format { .. } => self.can_format(rome_path), } } @@ -474,9 +425,9 @@ fn process_file(ctx: &TraversalOptions, path: &Path, file_id: FileId) -> FileRes let rome_path = RomePath::new(path, file_id); let can_format = ctx.can_format(&rome_path); let can_handle = match ctx.mode { - TraversalMode::Check { .. } => ctx.can_lint(&rome_path), - TraversalMode::CI { .. } => ctx.can_lint(&rome_path) || can_format, - TraversalMode::Format { .. } => can_format, + ExecutionMode::Check { .. } => ctx.can_lint(&rome_path), + ExecutionMode::CI { .. } => ctx.can_lint(&rome_path) || can_format, + ExecutionMode::Format { .. } => can_format, }; if !can_handle { @@ -526,7 +477,7 @@ fn process_file(ctx: &TraversalOptions, path: &Path, file_id: FileId) -> FileRes return Ok(FileStatus::Ignored); } - let is_format = matches!(ctx.mode, TraversalMode::Format { .. }); + let is_format = matches!(ctx.mode, ExecutionMode::Format { .. }); let categories = if is_format { RuleCategories::SYNTAX } else { @@ -544,7 +495,7 @@ fn process_file(ctx: &TraversalOptions, path: &Path, file_id: FileId) -> FileRes // In formatting mode, abort immediately if the file has errors match ctx.mode { - TraversalMode::Format { ignore_errors, .. } if has_errors => { + ExecutionMode::Format { ignore_errors, .. } if has_errors => { return Err(if ignore_errors { Message::from(TraversalError { severity: Severity::Warning, @@ -591,15 +542,15 @@ fn process_file(ctx: &TraversalOptions, path: &Path, file_id: FileId) -> FileRes let write = match ctx.mode { // In check mode do not run the formatter and return the result immediately, // but only if the argument `--apply` is not passed. - TraversalMode::Check { .. } => { + ExecutionMode::Check { .. } => { if ctx.mode.as_fix_file_mode().is_some() { true } else { return Ok(result); } } - TraversalMode::CI { .. } => false, - TraversalMode::Format { write, .. } => write, + ExecutionMode::CI { .. } => false, + ExecutionMode::Format { write, .. } => write, }; let printed = file_guard diff --git a/crates/rome_cli/tests/main.rs b/crates/rome_cli/tests/main.rs index b66d85edea02..92879060e28f 100644 --- a/crates/rome_cli/tests/main.rs +++ b/crates/rome_cli/tests/main.rs @@ -84,7 +84,8 @@ const NO_DEAD_CODE_ERROR: &str = r#"function f() { mod check { use super::*; use crate::configs::{ - CONFIG_LINTER_DISABLED, CONFIG_LINTER_SUPPRESSED_GROUP, CONFIG_LINTER_SUPPRESSED_RULE, + CONFIG_LINTER_DISABLED, CONFIG_LINTER_DOWNGRADE_DIAGNOSTIC, CONFIG_LINTER_SUPPRESSED_GROUP, + CONFIG_LINTER_SUPPRESSED_RULE, CONFIG_LINTER_UPGRADE_DIAGNOSTIC, }; use rome_console::LogLevel; use rome_fs::FileSystemExt; @@ -164,11 +165,11 @@ mod check { args: Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), }); - eprintln!("{:?}", console.buffer); + eprintln!("{:?}", console.out_buffer); assert!(result.is_err()); - let messages = &console.buffer; + let messages = &console.out_buffer; assert_eq!( messages @@ -461,6 +462,86 @@ mod check { assert_cli_snapshot("should_disable_a_rule_group", fs, console); } + + #[test] + fn downgrade_severity() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + let file_path = Path::new("rome.json"); + fs.insert( + file_path.into(), + CONFIG_LINTER_DOWNGRADE_DIAGNOSTIC.as_bytes(), + ); + + let file_path = Path::new("file.js"); + fs.insert(file_path.into(), NO_DEBUGGER.as_bytes()); + + let result = run_cli(CliSession { + app: App::with_filesystem_and_console( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + ), + args: Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), + }); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + let messages = &console.out_buffer; + + assert_eq!( + messages + .iter() + .filter(|m| m.level == LogLevel::Error) + .filter(|m| { + let content = format!("{:#?}", m.content); + content.contains("js/noDebugger") + }) + .count(), + 1 + ); + + assert_cli_snapshot("downgrade_severity", fs, console); + } + + #[test] + fn upgrade_severity() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + let file_path = Path::new("rome.json"); + fs.insert( + file_path.into(), + CONFIG_LINTER_UPGRADE_DIAGNOSTIC.as_bytes(), + ); + + let file_path = Path::new("file.js"); + fs.insert(file_path.into(), NO_DEAD_CODE_ERROR.as_bytes()); + + let result = run_cli(CliSession { + app: App::with_filesystem_and_console( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + ), + args: Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), + }); + + assert!(result.is_err(), "run_cli returned {result:?}"); + + let messages = &console.out_buffer; + + assert_eq!( + messages + .iter() + .filter(|m| m.level == LogLevel::Error) + .filter(|m| { + let content = format!("{:?}", m.content); + content.contains("js/noDeadCode") + }) + .count(), + 1 + ); + + assert_cli_snapshot("upgrade_severity", fs, console); + } } mod ci { @@ -497,8 +578,8 @@ mod ci { assert_eq!(content, FORMATTED); - if console.buffer.len() != 1 { - panic!("unexpected console content: {:#?}", console.buffer); + if console.out_buffer.len() != 1 { + panic!("unexpected console content: {:#?}", console.out_buffer); } } @@ -560,7 +641,7 @@ mod ci { args: Arguments::from_vec(vec![OsString::from("ci"), file_path.as_os_str().into()]), }); - eprintln!("{:?}", console.buffer); + eprintln!("{:?}", console.out_buffer); match result { Err(Termination::CheckError) => {} @@ -573,11 +654,9 @@ mod ci { mod format { use super::*; - use crate::configs::{ - CONFIG_DISABLED_FORMATTER, CONFIG_FORMAT, CONFIG_LINTER_DOWNGRADE_DIAGNOSTIC, - CONFIG_LINTER_UPGRADE_DIAGNOSTIC, - }; - use rome_console::LogLevel; + use crate::configs::{CONFIG_DISABLED_FORMATTER, CONFIG_FORMAT}; + use crate::snap_test::markup_to_string; + use rome_console::markup; use rome_fs::FileSystemExt; #[test] @@ -630,7 +709,7 @@ mod format { ]), }); - eprintln!("{:?}", console.buffer); + eprintln!("{:?}", console.out_buffer); assert!(result.is_ok(), "run_cli returned {result:?}"); @@ -644,7 +723,7 @@ mod format { assert_eq!(content, FORMATTED); - assert_eq!(console.buffer.len(), 1); + assert_eq!(console.out_buffer.len(), 1); } // Ensures lint warnings are not printed in format mode @@ -679,7 +758,12 @@ mod format { // The console buffer is expected to contain the following message: // 0: "Formatter would have printed the following content" // 1: "Compared 1 files" - assert_eq!(console.buffer.len(), 2, "console {:#?}", console.buffer); + assert_eq!( + console.out_buffer.len(), + 2, + "console {:#?}", + console.out_buffer + ); } #[test] @@ -866,84 +950,109 @@ mod format { } #[test] - fn downgrade_severity() { + fn format_stdin_successfully() { let mut fs = MemoryFileSystem::default(); let mut console = BufferConsole::default(); - let file_path = Path::new("rome.json"); - fs.insert( - file_path.into(), - CONFIG_LINTER_DOWNGRADE_DIAGNOSTIC.as_bytes(), - ); - let file_path = Path::new("file.js"); - fs.insert(file_path.into(), NO_DEBUGGER.as_bytes()); + console + .in_buffer + .push("function f() {return{}}".to_string()); let result = run_cli(CliSession { app: App::with_filesystem_and_console( DynRef::Borrowed(&mut fs), DynRef::Borrowed(&mut console), ), - args: Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), + args: Arguments::from_vec(vec![ + OsString::from("format"), + OsString::from("--stdin-file-path"), + OsString::from("mock.js"), + ]), }); assert!(result.is_ok(), "run_cli returned {result:?}"); - let messages = &console.buffer; + let message = console + .out_buffer + .get(0) + .expect("Console should have written a message"); - assert_eq!( - messages - .iter() - .filter(|m| m.level == LogLevel::Error) - .filter(|m| { - let content = format!("{:#?}", m.content); - content.contains("js/noDebugger") - }) - .count(), - 1 - ); + let content = markup_to_string(markup! { + {message.content} + }); - assert_cli_snapshot("downgrade_severity", fs, console); + assert_eq!(content, "function f() {\n\treturn {};\n}\n"); + + assert_cli_snapshot("format_stdin_successfully", fs, console); } #[test] - #[ignore] - fn upgrade_severity() { + fn format_stdin_with_errors() { let mut fs = MemoryFileSystem::default(); let mut console = BufferConsole::default(); + + let result = run_cli(CliSession { + app: App::with_filesystem_and_console( + DynRef::Borrowed(&mut fs), + DynRef::Borrowed(&mut console), + ), + args: Arguments::from_vec(vec![ + OsString::from("format"), + OsString::from("--stdin-file-path"), + OsString::from("mock.js"), + ]), + }); + + assert!(result.is_err(), "run_cli returned {result:?}"); + + match result { + Err(Termination::MissingArgument { argument }) => assert_eq!(argument, "stdin"), + _ => { + panic!("run_cli returned {result:?} for an unknown command help, expected an error") + } + } + + assert_cli_snapshot("format_stdin_with_errors", fs, console); + } + + #[test] + fn does_not_format_if_disabled() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + let file_path = Path::new("rome.json"); - fs.insert( - file_path.into(), - CONFIG_LINTER_UPGRADE_DIAGNOSTIC.as_bytes(), - ); + fs.insert(file_path.into(), CONFIG_DISABLED_FORMATTER.as_bytes()); - let file_path = Path::new("file.js"); - fs.insert(file_path.into(), NO_DEAD_CODE_ERROR.as_bytes()); + console + .in_buffer + .push("function f() {return{}}".to_string()); let result = run_cli(CliSession { app: App::with_filesystem_and_console( DynRef::Borrowed(&mut fs), DynRef::Borrowed(&mut console), ), - args: Arguments::from_vec(vec![OsString::from("check"), file_path.as_os_str().into()]), + args: Arguments::from_vec(vec![ + OsString::from("format"), + OsString::from("--stdin-file-path"), + OsString::from("mock.js"), + ]), }); - assert!(result.is_err(), "run_cli returned {result:?}"); + assert!(result.is_ok(), "run_cli returned {result:?}"); - let messages = &console.buffer; + let message = console + .out_buffer + .get(0) + .expect("Console should have written a message"); - assert_eq!( - messages - .iter() - .filter(|m| m.level == LogLevel::Error) - .filter(|m| { - let content = format!("{:?}", m.content); - content.contains("js/noDeadCode") - }) - .count(), - 1 - ); + let content = markup_to_string(markup! { + {message.content} + }); - assert_cli_snapshot("upgrade_severity", fs, console); + assert_eq!(content, "function f() {return{}}".to_string()); + + assert_cli_snapshot("does_not_format_if_disabled", fs, console); } } diff --git a/crates/rome_cli/tests/snap_test.rs b/crates/rome_cli/tests/snap_test.rs index 3245c56ca8d7..73ec795621c2 100644 --- a/crates/rome_cli/tests/snap_test.rs +++ b/crates/rome_cli/tests/snap_test.rs @@ -6,8 +6,15 @@ use std::collections::HashMap; use std::fmt::Write as _; use std::path::PathBuf; +#[derive(Default)] +struct InMessages { + stdin: Option, +} + #[derive(Default)] struct CliSnapshot { + /// input messages, coming from different sources + pub in_messages: InMessages, /// the configuration, if set pub configuration: Option, /// file name -> content @@ -44,8 +51,18 @@ impl CliSnapshot { } } + if let Some(stdin) = &self.in_messages.stdin { + content.push_str("# Input messages\n\n"); + content.push_str("```block"); + content.push('\n'); + content.push_str(stdin); + content.push('\n'); + content.push_str("```"); + content.push_str("\n\n") + } + if !self.messages.is_empty() { - content.push_str("## Messages\n\n"); + content.push_str("# Emitted Messages\n\n"); for message in &self.messages { let message_content = &*message; @@ -68,7 +85,7 @@ impl CliSnapshot { } } -fn markup_to_string(markup: Markup) -> String { +pub fn markup_to_string(markup: Markup) -> String { let mut buffer = Vec::new(); let mut write = Termcolor(NoColor::new(&mut buffer)); let mut fmt = Formatter::new(&mut write); @@ -98,7 +115,14 @@ pub fn assert_cli_snapshot(test_name: &str, fs: MemoryFileSystem, console: Buffe .insert(file.to_str().unwrap().to_string(), String::from(content)); } - for message in console.buffer { + let in_buffer = &console.in_buffer; + for (index, message) in in_buffer.iter().enumerate() { + if index == 0 { + cli_snapshot.in_messages.stdin = Some(message.to_string()); + } + } + + for message in &console.out_buffer { let content = markup_to_string(markup! { {message.content} }); diff --git a/crates/rome_cli/tests/snapshots/apply_noop.snap b/crates/rome_cli/tests/snapshots/apply_noop.snap index 98df55e0c5f5..0ea051567c47 100644 --- a/crates/rome_cli/tests/snapshots/apply_noop.snap +++ b/crates/rome_cli/tests/snapshots/apply_noop.snap @@ -1,6 +1,6 @@ --- source: crates/rome_cli/tests/snap_test.rs -assertion_line: 113 +assertion_line: 137 expression: content --- ## `fix.js` @@ -11,7 +11,7 @@ if(a != 0) {} ``` -## Messages +# Emitted Messages ```block Skipped 1 suggested fixes. diff --git a/crates/rome_cli/tests/snapshots/apply_ok.snap b/crates/rome_cli/tests/snapshots/apply_ok.snap index 98df55e0c5f5..0ea051567c47 100644 --- a/crates/rome_cli/tests/snapshots/apply_ok.snap +++ b/crates/rome_cli/tests/snapshots/apply_ok.snap @@ -1,6 +1,6 @@ --- source: crates/rome_cli/tests/snap_test.rs -assertion_line: 113 +assertion_line: 137 expression: content --- ## `fix.js` @@ -11,7 +11,7 @@ if(a != 0) {} ``` -## Messages +# Emitted Messages ```block Skipped 1 suggested fixes. diff --git a/crates/rome_cli/tests/snapshots/apply_suggested.snap b/crates/rome_cli/tests/snapshots/apply_suggested.snap index 0cfe2c19736c..0cf3081537c0 100644 --- a/crates/rome_cli/tests/snapshots/apply_suggested.snap +++ b/crates/rome_cli/tests/snapshots/apply_suggested.snap @@ -1,6 +1,6 @@ --- source: crates/rome_cli/tests/snap_test.rs -assertion_line: 107 +assertion_line: 137 expression: content --- ## `fix.js` @@ -10,6 +10,6 @@ let a = 4; ``` -## Messages +# Emitted Messages diff --git a/crates/rome_cli/tests/snapshots/does_not_format_if_disabled.snap b/crates/rome_cli/tests/snapshots/does_not_format_if_disabled.snap new file mode 100644 index 000000000000..fdb6692e6bdd --- /dev/null +++ b/crates/rome_cli/tests/snapshots/does_not_format_if_disabled.snap @@ -0,0 +1,33 @@ +--- +source: crates/rome_cli/tests/snap_test.rs +assertion_line: 137 +expression: content +--- +## `rome.json` + +```json +{ + "formatter": { + "enabled": false + } +} + +``` + +# Input messages + +```block +function f() {return{}} +``` + +# Emitted Messages + +```block +function f() {return{}} +``` + +```block +The content was not formatted because the formatter is currently disabled. +``` + + diff --git a/crates/rome_cli/tests/snapshots/downgrade_severity.snap b/crates/rome_cli/tests/snapshots/downgrade_severity.snap index 8e01f3c267c7..091537d3f955 100644 --- a/crates/rome_cli/tests/snapshots/downgrade_severity.snap +++ b/crates/rome_cli/tests/snapshots/downgrade_severity.snap @@ -1,6 +1,6 @@ --- source: crates/rome_cli/tests/snap_test.rs -assertion_line: 113 +assertion_line: 137 expression: content --- ## `rome.json` @@ -25,7 +25,7 @@ expression: content debugger; ``` -## Messages +# Emitted Messages ```block warning[js/noDebugger]: This is an unexpected use of the debugger statement. diff --git a/crates/rome_cli/tests/snapshots/format_stdin_successfully.snap b/crates/rome_cli/tests/snapshots/format_stdin_successfully.snap new file mode 100644 index 000000000000..5786b56ecb04 --- /dev/null +++ b/crates/rome_cli/tests/snapshots/format_stdin_successfully.snap @@ -0,0 +1,21 @@ +--- +source: crates/rome_cli/tests/snap_test.rs +assertion_line: 137 +expression: content +--- +# Input messages + +```block +function f() {return{}} +``` + +# Emitted Messages + +```block +function f() { + return {}; +} + +``` + + diff --git a/crates/rome_cli/tests/snapshots/format_stdin_with_errors.snap b/crates/rome_cli/tests/snapshots/format_stdin_with_errors.snap new file mode 100644 index 000000000000..7e382cef3ac5 --- /dev/null +++ b/crates/rome_cli/tests/snapshots/format_stdin_with_errors.snap @@ -0,0 +1,6 @@ +--- +source: crates/rome_cli/tests/snap_test.rs +assertion_line: 137 +expression: content +--- + diff --git a/crates/rome_cli/tests/snapshots/lint_error.snap b/crates/rome_cli/tests/snapshots/lint_error.snap index 40c3955fb8b6..7a184fc9ed56 100644 --- a/crates/rome_cli/tests/snapshots/lint_error.snap +++ b/crates/rome_cli/tests/snapshots/lint_error.snap @@ -1,6 +1,6 @@ --- source: crates/rome_cli/tests/snap_test.rs -assertion_line: 113 +assertion_line: 137 expression: content --- ## `ci.js` @@ -10,7 +10,7 @@ for(;true;); ``` -## Messages +# Emitted Messages ```block error[js/useBlockStatements]: Block statements are preferred in this position. diff --git a/crates/rome_cli/tests/snapshots/maximum_diagnostics.snap b/crates/rome_cli/tests/snapshots/maximum_diagnostics.snap index f2065ef1e596..d0c582eec9f1 100644 --- a/crates/rome_cli/tests/snapshots/maximum_diagnostics.snap +++ b/crates/rome_cli/tests/snapshots/maximum_diagnostics.snap @@ -1,6 +1,6 @@ --- source: crates/rome_cli/tests/snap_test.rs -assertion_line: 113 +assertion_line: 137 expression: content --- ## `check.js` @@ -18,7 +18,7 @@ for(;true;);for(;true;);for(;true;);for(;true;);for(;true;);for(;true;); ``` -## Messages +# Emitted Messages ```block error[js/useBlockStatements]: Block statements are preferred in this position. diff --git a/crates/rome_cli/tests/snapshots/no_lint_if_linter_is_disabled.snap b/crates/rome_cli/tests/snapshots/no_lint_if_linter_is_disabled.snap index fdde190f6a86..35412c4fb35d 100644 --- a/crates/rome_cli/tests/snapshots/no_lint_if_linter_is_disabled.snap +++ b/crates/rome_cli/tests/snapshots/no_lint_if_linter_is_disabled.snap @@ -1,6 +1,6 @@ --- source: crates/rome_cli/tests/snap_test.rs -assertion_line: 113 +assertion_line: 137 expression: content --- ## `rome.json` @@ -21,7 +21,7 @@ if(a != -0) {} ``` -## Messages +# Emitted Messages ```block fix.js: error[IO]: unhandled file type diff --git a/crates/rome_cli/tests/snapshots/no_lint_if_linter_is_disabled_when_run_apply.snap b/crates/rome_cli/tests/snapshots/no_lint_if_linter_is_disabled_when_run_apply.snap index fdde190f6a86..35412c4fb35d 100644 --- a/crates/rome_cli/tests/snapshots/no_lint_if_linter_is_disabled_when_run_apply.snap +++ b/crates/rome_cli/tests/snapshots/no_lint_if_linter_is_disabled_when_run_apply.snap @@ -1,6 +1,6 @@ --- source: crates/rome_cli/tests/snap_test.rs -assertion_line: 113 +assertion_line: 137 expression: content --- ## `rome.json` @@ -21,7 +21,7 @@ if(a != -0) {} ``` -## Messages +# Emitted Messages ```block fix.js: error[IO]: unhandled file type diff --git a/crates/rome_cli/tests/snapshots/should_disable_a_rule.snap b/crates/rome_cli/tests/snapshots/should_disable_a_rule.snap index 59cef5ebe289..5d075fd79a4d 100644 --- a/crates/rome_cli/tests/snapshots/should_disable_a_rule.snap +++ b/crates/rome_cli/tests/snapshots/should_disable_a_rule.snap @@ -1,6 +1,6 @@ --- source: crates/rome_cli/tests/snap_test.rs -assertion_line: 113 +assertion_line: 137 expression: content --- ## `rome.json` @@ -24,6 +24,6 @@ expression: content debugger; ``` -## Messages +# Emitted Messages diff --git a/crates/rome_cli/tests/snapshots/should_disable_a_rule_group.snap b/crates/rome_cli/tests/snapshots/should_disable_a_rule_group.snap index ce93e9a91cbb..0c81e3a67949 100644 --- a/crates/rome_cli/tests/snapshots/should_disable_a_rule_group.snap +++ b/crates/rome_cli/tests/snapshots/should_disable_a_rule_group.snap @@ -1,6 +1,6 @@ --- source: crates/rome_cli/tests/snap_test.rs -assertion_line: 107 +assertion_line: 137 expression: content --- ## `rome.json` @@ -29,6 +29,6 @@ try { ``` -## Messages +# Emitted Messages diff --git a/crates/rome_cli/tests/snapshots/upgrade_severity.snap b/crates/rome_cli/tests/snapshots/upgrade_severity.snap index c78b143badf4..48bbe57cf602 100644 --- a/crates/rome_cli/tests/snapshots/upgrade_severity.snap +++ b/crates/rome_cli/tests/snapshots/upgrade_severity.snap @@ -1,6 +1,6 @@ --- source: crates/rome_cli/tests/snap_test.rs -assertion_line: 97 +assertion_line: 137 expression: content --- ## `rome.json` @@ -11,9 +11,7 @@ expression: content "rules": { "recommended": true, "js": { - "rules": { - "noDeadCode": "error" - } + "noDeadCode": "error" } } } @@ -22,7 +20,7 @@ expression: content ## `file.js` -```json +```js function f() { for (;;) { continue; @@ -32,8 +30,9 @@ function f() { ``` -## Messages +# Emitted Messages +```block error[js/noDeadCode]: This code is unreachable ┌─ file.js:3:9 │ @@ -42,5 +41,6 @@ error[js/noDeadCode]: This code is unreachable │ └──────────────^ +``` diff --git a/crates/rome_console/src/codespan/mod.rs b/crates/rome_console/src/codespan/mod.rs index de26b6896d83..23a223fb25e0 100644 --- a/crates/rome_console/src/codespan/mod.rs +++ b/crates/rome_console/src/codespan/mod.rs @@ -618,7 +618,7 @@ labore et dolore magna aliqua"; {codespan} }); - let mut iter = console.buffer.into_iter(); + let mut iter = console.out_buffer.into_iter(); let message = iter .next() diff --git a/crates/rome_console/src/diff.rs b/crates/rome_console/src/diff.rs index ee4ca764b112..4f6707e33f3b 100644 --- a/crates/rome_console/src/diff.rs +++ b/crates/rome_console/src/diff.rs @@ -423,7 +423,7 @@ function name(args) { {diff} }); - let mut messages = console.buffer.into_iter(); + let mut messages = console.out_buffer.into_iter(); let message = match messages.next() { Some(msg) => msg, other => panic!("unexpected message {other:?}"), @@ -447,7 +447,7 @@ function name(args) { {diff} }); - let mut messages = console.buffer.into_iter(); + let mut messages = console.out_buffer.into_iter(); let message = match messages.next() { Some(msg) => msg, other => panic!("unexpected message {other:?}"), diff --git a/crates/rome_console/src/lib.rs b/crates/rome_console/src/lib.rs index e41806c0baa1..881ab086bb15 100644 --- a/crates/rome_console/src/lib.rs +++ b/crates/rome_console/src/lib.rs @@ -1,6 +1,7 @@ -use std::io::Write; +use atty::Stream; +use std::io; +use std::io::{Read, Stdin, Write}; use std::panic::RefUnwindSafe; - use termcolor::{ColorChoice, StandardStream}; use write::Termcolor; @@ -29,6 +30,9 @@ pub enum LogLevel { pub trait Console: Send + Sync + RefUnwindSafe { /// Prints a message (formatted using [markup!]) to the console fn print(&mut self, level: LogLevel, args: Markup); + + /// It reads from a source, and if this source contains something, it's converted into a [String] + fn read(&mut self) -> Option; } /// Extension trait for [Console] providing convenience printing methods @@ -52,8 +56,12 @@ impl ConsoleExt for T { /// Implementation of [Console] printing messages to the standard output and standard error pub struct EnvConsole { + /// Channel to print messages out: StandardStream, + /// Channel to print errors err: StandardStream, + /// Channel to read arbitrary input + r#in: Stdin, } impl EnvConsole { @@ -72,6 +80,7 @@ impl EnvConsole { Self { out: StandardStream::stdout(out_mode), err: StandardStream::stderr(err_mode), + r#in: io::stdin(), } } } @@ -89,12 +98,32 @@ impl Console for EnvConsole { writeln!(out).unwrap(); } + + fn read(&mut self) -> Option { + // Here we check if stdin is redirected. If not, we bail. + // + // Doing this check allows us to pipe stdin to rome, without expecting + // user content when we call `read_to_string` + if atty::is(Stream::Stdin) { + return None; + } + let mut handle = self.r#in.lock(); + let mut buffer = String::new(); + let result = handle.read_to_string(&mut buffer); + // Skipping the error for now + if result.is_ok() { + Some(buffer) + } else { + None + } + } } /// Implementation of [Console] storing all printed messages to a memory buffer #[derive(Default, Debug)] pub struct BufferConsole { - pub buffer: Vec, + pub out_buffer: Vec, + pub in_buffer: Vec, } /// Individual message entry printed to a [BufferConsole] @@ -106,9 +135,18 @@ pub struct Message { impl Console for BufferConsole { fn print(&mut self, level: LogLevel, args: Markup) { - self.buffer.push(Message { + self.out_buffer.push(Message { level, content: args.to_owned(), }); } + fn read(&mut self) -> Option { + if self.in_buffer.is_empty() { + None + } else { + // for the time being we simple return the first message, as we don't + // particular use case for multiple prompts + Some(self.in_buffer[0].clone()) + } + } } diff --git a/crates/rome_diagnostics/src/emit.rs b/crates/rome_diagnostics/src/emit.rs index 13d499873b68..5ad2b6ca4fa2 100644 --- a/crates/rome_diagnostics/src/emit.rs +++ b/crates/rome_diagnostics/src/emit.rs @@ -348,7 +348,7 @@ labore et dolore magna aliqua"; {diag.display(&files)} }); - let mut iter = console.buffer.into_iter(); + let mut iter = console.out_buffer.into_iter(); let message = match iter.next() { Some(msg) => msg, diff --git a/crates/rome_service/src/lib.rs b/crates/rome_service/src/lib.rs index 848c075d8dd1..b837544f8add 100644 --- a/crates/rome_service/src/lib.rs +++ b/crates/rome_service/src/lib.rs @@ -65,12 +65,16 @@ impl Display for RomeError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { RomeError::SourceFileNotSupported(path) => { - let ext = path - .extension() - .and_then(|ext| ext.to_str()) - .unwrap_or(""); + let ext = path.extension().and_then(|ext| ext.to_str()); - write!(f, "Rome doesn't support the file extension {ext:?} yet") + if let Some(ext) = ext { + write!(f, "Rome doesn't support the file extension {ext:?} yet") + } else { + write!( + f, + "Rome can't process the file because it doesn't have a clear extension" + ) + } } RomeError::NotFound => { write!(f, "the file does not exist in the workspace")