diff --git a/cli/rescript.js b/cli/rescript.js index 731b33143a..f1d3d1720d 100755 --- a/cli/rescript.js +++ b/cli/rescript.js @@ -7,41 +7,44 @@ import { bsc_exe, rescript_exe } from "./common/bins.js"; const args = process.argv.slice(2); -const firstPositionalArgIndex = args.findIndex(arg => !arg.startsWith("-")); - -try { - if (firstPositionalArgIndex !== -1) { - const subcommand = args[firstPositionalArgIndex]; - const subcommandWithArgs = args.slice(firstPositionalArgIndex); - - if ( - subcommand === "build" || - subcommand === "watch" || - subcommand === "clean" || - subcommand === "compiler-args" - ) { - child_process.execFileSync( - rescript_exe, - [...subcommandWithArgs, "--bsc-path", bsc_exe], - { - stdio: "inherit", - }, - ); - } else { - child_process.execFileSync(rescript_exe, [...args], { - stdio: "inherit", - }); - } - } else { - // no subcommand means build subcommand - child_process.execFileSync(rescript_exe, [...args, "--bsc-path", bsc_exe], { +/** + * @param {string[]} cmdArgs + */ +function execRescript(cmdArgs) { + console.log(cmdArgs) + try { + child_process.execFileSync(rescript_exe, cmdArgs, { stdio: "inherit", }); + } catch (err) { + if (err.status !== undefined) { + process.exit(err.status); // Pass through the exit code + } else { + process.exit(1); // Generic error + } } -} catch (err) { - if (err.status !== undefined) { - process.exit(err.status); // Pass through the exit code - } else { - process.exit(1); // Generic error +} + +const firstPositionalArgIndex = args.findIndex(arg => !arg.startsWith("-")); + +if (firstPositionalArgIndex !== -1) { + const subcommand = args[firstPositionalArgIndex]; + const subcommandWithArgs = args.slice(firstPositionalArgIndex); + + + switch (subcommand) { + case "build": + case "watch": + case "clean": + case "compiler-args": + case "format": + execRescript([...subcommandWithArgs, "--bsc-path", bsc_exe]); + break; + default: + execRescript(args); + break; } +} else { + // no subcommand means build subcommand + execRescript([...args, "--bsc-path", bsc_exe]); } diff --git a/rewatch/Cargo.lock b/rewatch/Cargo.lock index 9ce6fc0242..31a5a5ddc0 100644 --- a/rewatch/Cargo.lock +++ b/rewatch/Cargo.lock @@ -449,6 +449,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "humantime" version = "2.1.0" @@ -494,7 +500,7 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ - "hermit-abi", + "hermit-abi 0.4.0", "libc", "windows-sys 0.59.0", ] @@ -641,6 +647,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi 0.5.2", + "libc", +] + [[package]] name = "number_prefix" version = "0.4.0" @@ -766,6 +782,7 @@ dependencies = [ "log", "notify", "notify-debouncer-mini", + "num_cpus", "rayon", "regex", "serde", diff --git a/rewatch/Cargo.toml b/rewatch/Cargo.toml index 87d23df89c..c6b8cdbc9c 100644 --- a/rewatch/Cargo.toml +++ b/rewatch/Cargo.toml @@ -20,6 +20,7 @@ log = { version = "0.4.17" } notify = { version = "5.1.0", features = ["serde"] } notify-debouncer-mini = { version = "0.2.0" } rayon = "1.6.1" +num_cpus = "1.17.0" regex = "1.7.1" serde = { version = "1.0.152", features = ["derive"] } serde_derive = "1.0.152" diff --git a/rewatch/src/cli.rs b/rewatch/src/cli.rs index c15ca4ad79..f084a9b425 100644 --- a/rewatch/src/cli.rs +++ b/rewatch/src/cli.rs @@ -162,11 +162,23 @@ pub enum Command { #[command(flatten)] snapshot_output: SnapshotOutputArg, }, - /// Alias to `legacy format`. - #[command(disable_help_flag = true)] + /// Formats ReScript files. Format { - #[arg(allow_hyphen_values = true, num_args = 0..)] - format_args: Vec, + /// Read the code from stdin and print the formatted code to stdout. + #[arg(long)] + stdin: Option, + /// Format the whole project. + #[arg(short = 'a', long)] + all: bool, + /// Check formatting for file or the whole project. Use `--all` to check the whole project. + #[arg(short = 'c', long)] + check: bool, + /// Files to format. + files: Vec, + #[command(flatten)] + bsc_path: BscPathArg, + #[command(flatten)] + folder: FolderArg, }, /// Alias to `legacy dump`. #[command(disable_help_flag = true)] diff --git a/rewatch/src/format_cmd.rs b/rewatch/src/format_cmd.rs new file mode 100644 index 0000000000..061d01a544 --- /dev/null +++ b/rewatch/src/format_cmd.rs @@ -0,0 +1,147 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; +use anyhow::{bail, Result}; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::io::{self, Read, Write}; +use std::fs; +use rayon::prelude::*; +use crate::helpers; +use num_cpus; + +pub fn run( + stdin_path: Option, + all: bool, + check: bool, + files: Vec, + bsc_path: Option, + path: PathBuf, +) -> Result<()> { + let project_root = helpers::get_abs_path(&path); + let workspace_root = helpers::get_workspace_root(&project_root); + + let bsc_exe = match bsc_path { + Some(path) => helpers::get_abs_path(&path), + None => helpers::get_bsc(&project_root, &workspace_root), + }; + + if check && stdin_path.is_some() { + bail!("format --stdin cannot be used with --check flag"); + } + + if all { + if stdin_path.is_some() || !files.is_empty() { + bail!("format --all can not be in use with other flags"); + } + format_all(&bsc_exe, check)?; + } else if stdin_path.is_some() { + format_stdin(&bsc_exe, stdin_path.unwrap())?; + } else { + format_files(&bsc_exe, files, check)?; + } + + Ok(()) +} + + + +fn format_all(bsc_exe: &Path, check: bool) -> Result<()> { + let output = Command::new(std::env::current_exe()?) + .arg("info") + .arg("-list-files") + .output()?; + + if !output.status.success() { + io::stderr().write_all(&output.stderr)?; + bail!("Failed to list files"); + } + + let files_str = String::from_utf8_lossy(&output.stdout); + let files: Vec = files_str + .split('\n') + .filter(|s| !s.trim().is_empty()) + .map(|s| s.trim().to_string()) + .collect(); + + format_files(bsc_exe, files, check)?; + Ok(()) +} + +fn format_stdin(bsc_exe: &Path, stdin_path: String) -> Result<()> { + let mut input = String::new(); + io::stdin().read_to_string(&mut input)?; + + let mut cmd = Command::new(bsc_exe); + cmd.arg("-format").arg(&stdin_path); + cmd.stdin(std::process::Stdio::piped()); + cmd.stdout(std::process::Stdio::piped()); + cmd.stderr(std::process::Stdio::piped()); + + let mut child = cmd.spawn()?; + let mut stdin = child.stdin.take().unwrap(); + std::thread::spawn(move || { + stdin.write_all(input.as_bytes()).unwrap(); + }); + + let output = child.wait_with_output()?; + + if output.status.success() { + io::stdout().write_all(&output.stdout)?; + } + else { + io::stderr().write_all(&output.stderr)?; + bail!("bsc exited with an error"); + } + + Ok(()) +} + +fn format_files(bsc_exe: &Path, files: Vec, check: bool) -> Result<()> { + let batch_size = 4 * num_cpus::get(); + let incorrectly_formatted_files = AtomicUsize::new(0); + + files.par_chunks(batch_size).try_for_each(|batch| { + batch.iter().try_for_each(|file| { + let mut cmd = Command::new(bsc_exe); + if check { + cmd.arg("-format").arg(file); + } + else { + cmd.arg("-o").arg(file).arg("-format").arg(file); + } + + let output = cmd.output()?; + + if output.status.success() { + if check { + let original_content = fs::read_to_string(file)?; + let formatted_content = String::from_utf8_lossy(&output.stdout); + if original_content != formatted_content { + eprintln!("[format check] {}", file); + incorrectly_formatted_files.fetch_add(1, Ordering::SeqCst); + } + } + } + else { + io::stderr().write_all(&output.stderr)?; + bail!("bsc exited with an error for file {}", file); + } + Ok(()) as Result<()> + }) + })?; + + let count = incorrectly_formatted_files.load(Ordering::SeqCst); + if count > 0 { + if count == 1 { + eprintln!("The file listed above needs formatting"); + } + else { + eprintln!( + "The {} files listed above need formatting", + count + ); + } + bail!("Formatting check failed"); + } + + Ok(()) +} \ No newline at end of file diff --git a/rewatch/src/lib.rs b/rewatch/src/lib.rs index 2df92a48f3..6a25b6bfef 100644 --- a/rewatch/src/lib.rs +++ b/rewatch/src/lib.rs @@ -7,3 +7,4 @@ pub mod lock; pub mod queue; pub mod sourcedirs; pub mod watcher; +pub mod format_cmd; diff --git a/rewatch/src/main.rs b/rewatch/src/main.rs index 3cd1c5d084..01049b3818 100644 --- a/rewatch/src/main.rs +++ b/rewatch/src/main.rs @@ -7,7 +7,7 @@ use std::{ path::{Path, PathBuf}, }; -use rewatch::{build, cli, cmd, lock, watcher}; +use rewatch::{build, cli, cmd, lock, watcher, format_cmd}; fn main() -> Result<()> { let args = cli::Cli::parse(); @@ -20,7 +20,8 @@ fn main() -> Result<()> { .target(env_logger::fmt::Target::Stdout) .init(); - let command = args.command.unwrap_or(cli::Command::Build(args.build_args)); + + let command = args.command.unwrap_or_else(|| cli::Command::Build(args.build_args)); // The 'normal run' mode will show the 'pretty' formatted progress. But if we turn off the log // level, we should never show that. @@ -112,11 +113,14 @@ fn main() -> Result<()> { let code = build::pass_through_legacy(legacy_args); std::process::exit(code); } - cli::Command::Format { mut format_args } => { - format_args.insert(0, "format".into()); - let code = build::pass_through_legacy(format_args); - std::process::exit(code); - } + cli::Command::Format { + stdin, + all, + check, + files, + bsc_path, + folder: path, + } => format_cmd::run(stdin, all, check, files, bsc_path.as_ref().map(|s| PathBuf::from(s.clone())), PathBuf::from(path.folder)), cli::Command::Dump { mut dump_args } => { dump_args.insert(0, "dump".into()); let code = build::pass_through_legacy(dump_args); diff --git a/scripts/format.sh b/scripts/format.sh index d8d743a7e4..a1bbd91607 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -5,6 +5,6 @@ shopt -s extglob dune build @fmt --auto-promote files=$(find runtime tests -type f \( -name "*.res" -o -name "*.resi" \) ! -name "syntaxErrors*" ! -name "generated_mocha_test.res" ! -path "tests/syntax_tests*" ! -path "tests/analysis_tests/tests*" ! -path "*/node_modules/*") -./cli/rescript-legacy.js format $files +./cli/rescript.js format $files yarn format diff --git a/scripts/format_check.sh b/scripts/format_check.sh index 68ac076216..7d55912a29 100755 --- a/scripts/format_check.sh +++ b/scripts/format_check.sh @@ -18,7 +18,7 @@ case "$(uname -s)" in echo "Checking ReScript code formatting..." files=$(find runtime tests -type f \( -name "*.res" -o -name "*.resi" \) ! -name "syntaxErrors*" ! -name "generated_mocha_test.res" ! -path "tests/syntax_tests*" ! -path "tests/analysis_tests/tests*" ! -path "*/node_modules/*") - if ./cli/rescript-legacy.js format -check $files; then + if ./cli/rescript.js format --check $files; then printf "${successGreen}✅ ReScript code formatting ok.${reset}\n" else printf "${warningYellow}⚠️ ReScript code formatting issues found. Run 'make format' to fix.${reset}\n"