Skip to content

Commit e276090

Browse files
committed
Rust implementation of "rescript format"
1 parent dbcaa93 commit e276090

File tree

12 files changed

+280
-14
lines changed

12 files changed

+280
-14
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
1313
# 12.0.0-beta.2 (Unreleased)
1414

15+
#### :boom: Breaking Change
16+
17+
- Rust implementation of the `rescript format` command. Command line options changed from `-all`, `-check` and `-stdin` to `--all`, `--check` and `--stdin` compared to the legacy implementation. https://github.com/rescript-lang/rescript/pull/7603
18+
1519
#### :nail_care: Polish
1620

1721
- Add missing backtick and spaces to `Belt.Map.map` doc comment. https://github.com/rescript-lang/rescript/pull/7632

rewatch/Cargo.lock

Lines changed: 60 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rewatch/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ log = { version = "0.4.17" }
2020
notify = { version = "5.1.0", features = ["serde"] }
2121
notify-debouncer-mini = { version = "0.2.0" }
2222
rayon = "1.6.1"
23+
num_cpus = "1.17.0"
2324
regex = "1.7.1"
2425
serde = { version = "1.0.152", features = ["derive"] }
2526
serde_derive = "1.0.152"
2627
serde_json = { version = "1.0.93" }
2728
sysinfo = "0.29.10"
29+
tempfile = "3.10.1"
2830

2931

3032
[profile.release]

rewatch/src/build/packages.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ This inconsistency will cause issues with package resolution.\n",
457457
}
458458
}
459459

460-
fn read_packages(
460+
pub fn read_packages(
461461
project_root: &Path,
462462
workspace_root: &Option<PathBuf>,
463463
show_progress: bool,

rewatch/src/cli.rs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ fn parse_regex(s: &str) -> Result<Regex, regex::Error> {
88
Regex::new(s)
99
}
1010

11+
use clap::ValueEnum;
12+
13+
#[derive(Debug, Clone, ValueEnum)]
14+
pub enum FileExtension {
15+
#[value(name = ".res")]
16+
Res,
17+
#[value(name = ".resi")]
18+
Resi,
19+
}
20+
1121
/// ReScript - Fast, Simple, Fully Typed JavaScript from the Future
1222
#[derive(Parser, Debug)]
1323
#[command(version)]
@@ -169,11 +179,29 @@ pub enum Command {
169179
#[command(flatten)]
170180
dev: DevArg,
171181
},
172-
/// Alias to `legacy format`.
173-
#[command(disable_help_flag = true)]
182+
/// Formats ReScript files.
174183
Format {
175-
#[arg(allow_hyphen_values = true, num_args = 0..)]
176-
format_args: Vec<OsString>,
184+
/// Format the whole project.
185+
#[arg(short, long, group = "format_input_mode")]
186+
all: bool,
187+
188+
/// Check formatting status without applying changes.
189+
#[arg(short, long)]
190+
check: bool,
191+
192+
/// Read the code from stdin and print the formatted code to stdout.
193+
#[arg(
194+
short,
195+
long,
196+
group = "format_input_mode",
197+
value_enum,
198+
conflicts_with = "check"
199+
)]
200+
stdin: Option<FileExtension>,
201+
202+
/// Files to format.
203+
#[arg(group = "format_input_mode")]
204+
files: Vec<String>,
177205
},
178206
/// Alias to `legacy dump`.
179207
#[command(disable_help_flag = true)]

rewatch/src/format.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
use crate::helpers;
2+
use anyhow::{Result, bail};
3+
use num_cpus;
4+
use rayon::prelude::*;
5+
use std::fs;
6+
use std::io::{self, Write};
7+
use std::path::Path;
8+
use std::process::Command;
9+
use std::sync::atomic::{AtomicUsize, Ordering};
10+
11+
use crate::build::packages;
12+
use crate::cli::FileExtension;
13+
use clap::ValueEnum;
14+
15+
pub fn format(
16+
stdin_extension: Option<FileExtension>,
17+
all: bool,
18+
check: bool,
19+
files: Vec<String>,
20+
) -> Result<()> {
21+
let bsc_path = helpers::get_bsc();
22+
23+
match stdin_extension {
24+
Some(extension) => {
25+
format_stdin(&bsc_path, extension)?;
26+
}
27+
None => {
28+
let files = if all { get_all_files()? } else { files };
29+
format_files(&bsc_path, files, check)?;
30+
}
31+
}
32+
33+
Ok(())
34+
}
35+
36+
fn get_all_files() -> Result<Vec<String>> {
37+
let current_dir = std::env::current_dir()?;
38+
let project_root = helpers::get_abs_path(&current_dir);
39+
let workspace_root_option = helpers::get_workspace_root(&project_root);
40+
41+
let build_state = packages::make(&None, &project_root, &workspace_root_option, false, false)?;
42+
let mut files: Vec<String> = Vec::new();
43+
44+
for (_package_name, package) in build_state {
45+
if let Some(source_files) = package.source_files {
46+
for (path, _metadata) in source_files {
47+
if let Some(extension) = path.extension() {
48+
if extension == "res" || extension == "resi" {
49+
files.push(package.path.join(path).to_string_lossy().into_owned());
50+
}
51+
}
52+
}
53+
}
54+
}
55+
Ok(files)
56+
}
57+
58+
fn format_stdin(bsc_exe: &Path, extension: FileExtension) -> Result<()> {
59+
let extension_value = extension
60+
.to_possible_value()
61+
.ok_or(anyhow::anyhow!("Could not get extension arg value"))?;
62+
63+
let mut temp_file = tempfile::Builder::new()
64+
.suffix(extension_value.get_name())
65+
.tempfile()?;
66+
io::copy(&mut io::stdin(), &mut temp_file)?;
67+
let temp_path = temp_file.path();
68+
69+
let mut cmd = Command::new(bsc_exe);
70+
cmd.arg("-format").arg(temp_path);
71+
72+
let output = cmd.output()?;
73+
74+
if output.status.success() {
75+
io::stdout().write_all(&output.stdout)?;
76+
} else {
77+
let stderr_str = String::from_utf8_lossy(&output.stderr);
78+
bail!("Error formatting stdin: {}", stderr_str);
79+
}
80+
81+
Ok(())
82+
}
83+
84+
fn format_files(bsc_exe: &Path, files: Vec<String>, check: bool) -> Result<()> {
85+
let batch_size = 4 * num_cpus::get();
86+
let incorrectly_formatted_files = AtomicUsize::new(0);
87+
88+
files.par_chunks(batch_size).try_for_each(|batch| {
89+
batch.iter().try_for_each(|file| {
90+
let mut cmd = Command::new(bsc_exe);
91+
if check {
92+
cmd.arg("-format").arg(file);
93+
} else {
94+
cmd.arg("-o").arg(file).arg("-format").arg(file);
95+
}
96+
97+
let output = cmd.output()?;
98+
99+
if output.status.success() {
100+
if check {
101+
let original_content = fs::read_to_string(file)?;
102+
let formatted_content = String::from_utf8_lossy(&output.stdout);
103+
if original_content != formatted_content {
104+
eprintln!("[format check] {}", file);
105+
incorrectly_formatted_files.fetch_add(1, Ordering::SeqCst);
106+
}
107+
}
108+
} else {
109+
let stderr_str = String::from_utf8_lossy(&output.stderr);
110+
bail!("Error formatting {}: {}", file, stderr_str);
111+
}
112+
Ok(())
113+
})
114+
})?;
115+
116+
let count = incorrectly_formatted_files.load(Ordering::SeqCst);
117+
if count > 0 {
118+
if count == 1 {
119+
eprintln!("The file listed above needs formatting");
120+
} else {
121+
eprintln!("The {} files listed above need formatting", count);
122+
}
123+
bail!("Formatting check failed");
124+
}
125+
126+
Ok(())
127+
}

rewatch/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod build;
22
pub mod cli;
33
pub mod cmd;
44
pub mod config;
5+
pub mod format;
56
pub mod helpers;
67
pub mod lock;
78
pub mod queue;

rewatch/src/main.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use clap::Parser;
33
use log::LevelFilter;
44
use std::{io::Write, path::Path};
55

6-
use rewatch::{build, cli, cmd, lock, watcher};
6+
use rewatch::{build, cli, cmd, format, lock, watcher};
77

88
fn main() -> Result<()> {
99
let args = cli::Cli::parse();
@@ -91,11 +91,12 @@ fn main() -> Result<()> {
9191
let code = build::pass_through_legacy(legacy_args);
9292
std::process::exit(code);
9393
}
94-
cli::Command::Format { mut format_args } => {
95-
format_args.insert(0, "format".into());
96-
let code = build::pass_through_legacy(format_args);
97-
std::process::exit(code);
98-
}
94+
cli::Command::Format {
95+
stdin,
96+
all,
97+
check,
98+
files,
99+
} => format::format(stdin, all, check, files),
99100
cli::Command::Dump { mut dump_args } => {
100101
dump_args.insert(0, "dump".into());
101102
let code = build::pass_through_legacy(dump_args);

rewatch/tests/format.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
source "./utils.sh"
2+
cd ../testrepo
3+
4+
bold "Test: It should format all files"
5+
6+
git diff --name-only ./
7+
error_output=$("$REWATCH_EXECUTABLE" format --all)
8+
git_diff_file_count=$(git diff --name-only ./ | wc -l | xargs)
9+
if [ $? -eq 0 ] && [ $git_diff_file_count -eq 4 ];
10+
then
11+
success "Test package formatted. Got $git_diff_file_count changed files."
12+
git restore .
13+
else
14+
error "Error formatting test package"
15+
echo $error_output
16+
exit 1
17+
fi
18+
19+
bold "Test: It should format a single file"
20+
21+
error_output=$("$REWATCH_EXECUTABLE" format packages/dep01/src/Dep01.res)
22+
git_diff_file_count=$(git diff --name-only ./ | wc -l | xargs)
23+
if [ $? -eq 0 ] && [ $git_diff_file_count -eq 1 ];
24+
then
25+
success "Single file formatted successfully"
26+
git restore .
27+
else
28+
error "Error formatting single file"
29+
echo $error_output
30+
exit 1
31+
fi
32+
33+
bold "Test: It should format from stdin"
34+
35+
error_output=$(echo "let x = 1" | "$REWATCH_EXECUTABLE" format --stdin .res)
36+
if [ $? -eq 0 ];
37+
then
38+
success "Stdin formatted successfully"
39+
else
40+
error "Error formatting from stdin"
41+
echo $error_output
42+
exit 1
43+
fi

rewatch/tests/suite-ci.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ else
4040
exit 1
4141
fi
4242

43-
./compile.sh && ./watch.sh && ./lock.sh && ./suffix.sh && ./legacy.sh
43+
./compile.sh && ./watch.sh && ./lock.sh && ./suffix.sh && ./legacy.sh && ./format.sh

0 commit comments

Comments
 (0)