Skip to content

Rust implementation of "rescript format" #7603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
# 12.0.0-beta.2 (Unreleased)

#### :boom: Breaking Change

- 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

#### :nail_care: Polish

- Add missing backtick and spaces to `Belt.Map.map` doc comment. https://github.com/rescript-lang/rescript/pull/7632
Expand Down
60 changes: 60 additions & 0 deletions rewatch/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions rewatch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ 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"
serde_json = { version = "1.0.93" }
sysinfo = "0.29.10"
tempfile = "3.10.1"


[profile.release]
Expand Down
36 changes: 32 additions & 4 deletions rewatch/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ fn parse_regex(s: &str) -> Result<Regex, regex::Error> {
Regex::new(s)
}

use clap::ValueEnum;

#[derive(Debug, Clone, ValueEnum)]
pub enum FileExtension {
#[value(name = ".res")]
Res,
#[value(name = ".resi")]
Resi,
}

/// ReScript - Fast, Simple, Fully Typed JavaScript from the Future
#[derive(Parser, Debug)]
#[command(version)]
Expand Down Expand Up @@ -169,11 +179,29 @@ pub enum Command {
#[command(flatten)]
dev: DevArg,
},
/// Alias to `legacy format`.
#[command(disable_help_flag = true)]
/// Formats ReScript files.
Format {
#[arg(allow_hyphen_values = true, num_args = 0..)]
format_args: Vec<OsString>,
/// Format the whole project.
#[arg(short, long, group = "format_input_mode")]
all: bool,

/// Check formatting status without applying changes.
#[arg(short, long)]
check: bool,

/// Read the code from stdin and print the formatted code to stdout.
#[arg(
short,
long,
group = "format_input_mode",
value_enum,
conflicts_with = "check"
)]
stdin: Option<FileExtension>,

/// Files to format.
#[arg(group = "format_input_mode")]
files: Vec<String>,
},
/// Alias to `legacy dump`.
#[command(disable_help_flag = true)]
Expand Down
127 changes: 127 additions & 0 deletions rewatch/src/format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use crate::helpers;
use anyhow::{Result, bail};
use num_cpus;
use rayon::prelude::*;
use std::fs;
use std::io::{self, Write};
use std::path::Path;
use std::process::Command;
use std::sync::atomic::{AtomicUsize, Ordering};

use crate::build::packages;
use crate::cli::FileExtension;
use clap::ValueEnum;

pub fn format(
stdin_extension: Option<FileExtension>,
all: bool,
check: bool,
files: Vec<String>,
) -> Result<()> {
let bsc_path = helpers::get_bsc();

match stdin_extension {
Some(extension) => {
format_stdin(&bsc_path, extension)?;
}
None => {
let files = if all { get_all_files()? } else { files };
format_files(&bsc_path, files, check)?;
}
}

Ok(())
}

fn get_all_files() -> Result<Vec<String>> {
let current_dir = std::env::current_dir()?;
let project_root = helpers::get_abs_path(&current_dir);
let workspace_root_option = helpers::get_workspace_root(&project_root);

let build_state = packages::make(&None, &project_root, &workspace_root_option, false, false)?;
let mut files: Vec<String> = Vec::new();

for (_package_name, package) in build_state {
if let Some(source_files) = package.source_files {
for (path, _metadata) in source_files {
if let Some(extension) = path.extension() {
if extension == "res" || extension == "resi" {
files.push(package.path.join(path).to_string_lossy().into_owned());
}
}
}
}
}
Ok(files)
}

fn format_stdin(bsc_exe: &Path, extension: FileExtension) -> Result<()> {
let extension_value = extension
.to_possible_value()
.ok_or(anyhow::anyhow!("Could not get extension arg value"))?;

let mut temp_file = tempfile::Builder::new()
.suffix(extension_value.get_name())
.tempfile()?;
io::copy(&mut io::stdin(), &mut temp_file)?;
let temp_path = temp_file.path();

let mut cmd = Command::new(bsc_exe);
cmd.arg("-format").arg(temp_path);

let output = cmd.output()?;

if output.status.success() {
io::stdout().write_all(&output.stdout)?;
} else {
let stderr_str = String::from_utf8_lossy(&output.stderr);
bail!("Error formatting stdin: {}", stderr_str);
}

Ok(())
}

fn format_files(bsc_exe: &Path, files: Vec<String>, 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 {
let stderr_str = String::from_utf8_lossy(&output.stderr);
bail!("Error formatting {}: {}", file, stderr_str);
}
Ok(())
})
})?;

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(())
}
1 change: 1 addition & 0 deletions rewatch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod build;
pub mod cli;
pub mod cmd;
pub mod config;
pub mod format;
pub mod helpers;
pub mod lock;
pub mod queue;
Expand Down
13 changes: 7 additions & 6 deletions rewatch/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use clap::Parser;
use log::LevelFilter;
use std::{io::Write, path::Path};

use rewatch::{build, cli, cmd, lock, watcher};
use rewatch::{build, cli, cmd, format, lock, watcher};

fn main() -> Result<()> {
let args = cli::Cli::parse();
Expand Down Expand Up @@ -91,11 +91,12 @@ 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,
} => format::format(stdin, all, check, files),
cli::Command::Dump { mut dump_args } => {
dump_args.insert(0, "dump".into());
let code = build::pass_through_legacy(dump_args);
Expand Down
43 changes: 43 additions & 0 deletions rewatch/tests/format.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
source "./utils.sh"
cd ../testrepo

bold "Test: It should format all files"

git diff --name-only ./
error_output=$("$REWATCH_EXECUTABLE" format --all)
git_diff_file_count=$(git diff --name-only ./ | wc -l | xargs)
if [ $? -eq 0 ] && [ $git_diff_file_count -eq 4 ];
then
success "Test package formatted. Got $git_diff_file_count changed files."
git restore .
else
error "Error formatting test package"
echo $error_output
exit 1
fi

bold "Test: It should format a single file"

error_output=$("$REWATCH_EXECUTABLE" format packages/dep01/src/Dep01.res)
git_diff_file_count=$(git diff --name-only ./ | wc -l | xargs)
if [ $? -eq 0 ] && [ $git_diff_file_count -eq 1 ];
then
success "Single file formatted successfully"
git restore .
else
error "Error formatting single file"
echo $error_output
exit 1
fi

bold "Test: It should format from stdin"

error_output=$(echo "let x = 1" | "$REWATCH_EXECUTABLE" format --stdin .res)
if [ $? -eq 0 ];
then
success "Stdin formatted successfully"
else
error "Error formatting from stdin"
echo $error_output
exit 1
fi
2 changes: 1 addition & 1 deletion rewatch/tests/suite-ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ else
exit 1
fi

./compile.sh && ./watch.sh && ./lock.sh && ./suffix.sh && ./legacy.sh
./compile.sh && ./watch.sh && ./lock.sh && ./suffix.sh && ./legacy.sh && ./format.sh
2 changes: 1 addition & 1 deletion scripts/format.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ dune build @fmt --auto-promote

echo Formatting ReScript code...
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

echo Formatting JS code...
yarn format
Expand Down
Loading