From 1cf8a714e2b1d5c4fd161ea0911380e9e075a01e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ho=C3=A0ng=20=C4=90=E1=BB=A9c=20Hi=E1=BA=BFu?= Date: Mon, 1 Nov 2021 11:27:59 +0700 Subject: [PATCH] fmt: check formatting with `--check` (#1001) --- Cargo.lock | 7 ++++++ Cargo.toml | 1 + completions/just.bash | 2 +- completions/just.elvish | 1 + completions/just.fish | 1 + completions/just.powershell | 1 + completions/just.zsh | 1 + src/config.rs | 12 ++++++++++ src/error.rs | 4 ++++ src/subcommand.rs | 19 ++++++++++++---- tests/fmt.rs | 45 +++++++++++++++++++++++++++++++++++++ 11 files changed, 89 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bed130eb..5204c551 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -231,6 +231,7 @@ dependencies = [ "log", "pretty_assertions", "regex", + "similar", "snafu", "strum", "strum_macros", @@ -463,6 +464,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" +[[package]] +name = "similar" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" + [[package]] name = "snafu" version = "0.6.10" diff --git a/Cargo.toml b/Cargo.toml index 26cee9f2..9697940e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ lexiclean = "0.0.1" libc = "0.2.0" log = "0.4.4" regex = "1.5.4" +similar = "2.1.0" snafu = "0.6.0" strum_macros = "0.22.0" target = "2.0.0" diff --git a/completions/just.bash b/completions/just.bash index 7a1a2d27..cae30cc9 100644 --- a/completions/just.bash +++ b/completions/just.bash @@ -20,7 +20,7 @@ _just() { case "${cmd}" in just) - opts=" -q -u -v -e -l -h -V -f -d -c -s --dry-run --highlight --no-dotenv --no-highlight --quiet --shell-command --clear-shell-args --unsorted --unstable --verbose --changelog --choose --dump --edit --evaluate --fmt --init --list --summary --variables --help --version --chooser --color --list-heading --list-prefix --justfile --set --shell --shell-arg --working-directory --command --completions --show --dotenv-filename --dotenv-path ... " + opts=" -q -u -v -e -l -h -V -f -d -c -s --check --dry-run --highlight --no-dotenv --no-highlight --quiet --shell-command --clear-shell-args --unsorted --unstable --verbose --changelog --choose --dump --edit --evaluate --fmt --init --list --summary --variables --help --version --chooser --color --list-heading --list-prefix --justfile --set --shell --shell-arg --working-directory --command --completions --show --dotenv-filename --dotenv-path ... " if [[ ${cur} == -* ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/completions/just.elvish b/completions/just.elvish index 4972a56e..531dcb40 100644 --- a/completions/just.elvish +++ b/completions/just.elvish @@ -32,6 +32,7 @@ edit:completion:arg-completer[just] = [@words]{ cand --show 'Show information about ' cand --dotenv-filename 'Search for environment file named instead of `.env`' cand --dotenv-path 'Load environment file at instead of searching for one' + cand --check 'Run `--fmt` in ''check'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.' cand --dry-run 'Print what just would do without doing it' cand --highlight 'Highlight echoed recipe lines in bold' cand --no-dotenv 'Don''t load `.env` file' diff --git a/completions/just.fish b/completions/just.fish index 5379aeaf..7a17f6c9 100644 --- a/completions/just.fish +++ b/completions/just.fish @@ -23,6 +23,7 @@ complete -c just -n "__fish_use_subcommand" -l completions -d 'Print shell compl complete -c just -n "__fish_use_subcommand" -s s -l show -d 'Show information about ' complete -c just -n "__fish_use_subcommand" -l dotenv-filename -d 'Search for environment file named instead of `.env`' complete -c just -n "__fish_use_subcommand" -l dotenv-path -d 'Load environment file at instead of searching for one' +complete -c just -n "__fish_use_subcommand" -l check -d 'Run `--fmt` in \'check\' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.' complete -c just -n "__fish_use_subcommand" -l dry-run -d 'Print what just would do without doing it' complete -c just -n "__fish_use_subcommand" -l highlight -d 'Highlight echoed recipe lines in bold' complete -c just -n "__fish_use_subcommand" -l no-dotenv -d 'Don\'t load `.env` file' diff --git a/completions/just.powershell b/completions/just.powershell index f2be3127..f2f68d2d 100644 --- a/completions/just.powershell +++ b/completions/just.powershell @@ -37,6 +37,7 @@ Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock { [CompletionResult]::new('--show', 'show', [CompletionResultType]::ParameterName, 'Show information about ') [CompletionResult]::new('--dotenv-filename', 'dotenv-filename', [CompletionResultType]::ParameterName, 'Search for environment file named instead of `.env`') [CompletionResult]::new('--dotenv-path', 'dotenv-path', [CompletionResultType]::ParameterName, 'Load environment file at instead of searching for one') + [CompletionResult]::new('--check', 'check', [CompletionResultType]::ParameterName, 'Run `--fmt` in ''check'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.') [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'Print what just would do without doing it') [CompletionResult]::new('--highlight', 'highlight', [CompletionResultType]::ParameterName, 'Highlight echoed recipe lines in bold') [CompletionResult]::new('--no-dotenv', 'no-dotenv', [CompletionResultType]::ParameterName, 'Don''t load `.env` file') diff --git a/completions/just.zsh b/completions/just.zsh index b504b709..1efbadce 100644 --- a/completions/just.zsh +++ b/completions/just.zsh @@ -33,6 +33,7 @@ _just() { '--show=[Show information about ]: :_just_commands' \ '(--dotenv-path)--dotenv-filename=[Search for environment file named instead of `.env`]' \ '--dotenv-path=[Load environment file at instead of searching for one]' \ +'--check[Run `--fmt` in '\''check'\'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.]' \ '(-q --quiet)--dry-run[Print what just would do without doing it]' \ '--highlight[Highlight echoed recipe lines in bold]' \ '--no-dotenv[Don'\''t load `.env` file]' \ diff --git a/src/config.rs b/src/config.rs index e8a1f1c1..bed6e1e2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,6 +15,7 @@ pub(crate) const DEFAULT_SHELL_ARG: &str = "-cu"; #[derive(Debug, PartialEq)] pub(crate) struct Config { pub(crate) color: Color, + pub(crate) check: bool, pub(crate) dry_run: bool, pub(crate) highlight: bool, pub(crate) invocation_directory: PathBuf, @@ -81,6 +82,7 @@ mod cmd { mod arg { pub(crate) const ARGUMENTS: &str = "ARGUMENTS"; + pub(crate) const CHECK: &str = "CHECK"; pub(crate) const CHOOSER: &str = "CHOOSER"; pub(crate) const CLEAR_SHELL_ARGS: &str = "CLEAR-SHELL-ARGS"; pub(crate) const COLOR: &str = "COLOR"; @@ -116,6 +118,12 @@ impl Config { .version_message("Print version information") .setting(AppSettings::ColoredHelp) .setting(AppSettings::TrailingVarArg) + .arg( + Arg::with_name(arg::CHECK) + .long("check") + .requires(cmd::FORMAT) + .help("Run `--fmt` in 'check' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required."), + ) .arg( Arg::with_name(arg::CHOOSER) .long("chooser") @@ -532,6 +540,7 @@ impl Config { || matches.occurrences_of(arg::SHELL_ARG) > 0; Ok(Self { + check: matches.is_present(arg::CHECK), dry_run: matches.is_present(arg::DRY_RUN), highlight: !matches.is_present(arg::NO_HIGHLIGHT), shell: matches.value_of(arg::SHELL).unwrap().to_owned(), @@ -594,6 +603,9 @@ USAGE: FLAGS: --changelog Print changelog + --check Run `--fmt` in 'check' mode. Exits with 0 if + justfile is formatted correctly. Exits with 1 and + prints a diff if formatting is required. --choose Select one or more recipes to run using a binary. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to diff --git a/src/error.rs b/src/error.rs index be044f00..b479570d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -75,6 +75,7 @@ pub(crate) enum Error<'src> { variable: String, suggestion: Option>, }, + FormatCheckFoundDiff, FunctionCall { function: Name<'src>, message: String, @@ -458,6 +459,9 @@ impl<'src> ColorDisplay for Error<'src> { write!(f, "\n{}", suggestion)?; } } + FormatCheckFoundDiff => { + write!(f, "Formatted justfile differs from original.")?; + } FunctionCall { function, message } => { write!( f, diff --git a/src/subcommand.rs b/src/subcommand.rs index b6e81e3f..3dc0d6d4 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -77,7 +77,7 @@ impl Subcommand { justfile.run(config, &search, overrides, &[])? } Dump => Self::dump(ast), - Format => Self::format(config, ast, &search)?, + Format => Self::format(config, ast, &src, &search)?, List => Self::list(config, justfile), Run { arguments, @@ -255,11 +255,22 @@ impl Subcommand { Ok(()) } - fn format(config: &Config, ast: Ast, search: &Search) -> Result<(), Error<'static>> { + fn format(config: &Config, ast: Ast, src: &str, search: &Search) -> Result<(), Error<'static>> { config.require_unstable("The `--fmt` command is currently unstable.")?; - if let Err(io_error) = - File::create(&search.justfile).and_then(|mut file| write!(file, "{}", ast)) + let src_formatted = ast.to_string(); + if config.check { + if src == src_formatted { + Ok(()) + } else { + print!( + "{}", + similar::udiff::unified_diff(similar::Algorithm::Patience, &src, &src_formatted, 2, None) + ); + Err(Error::FormatCheckFoundDiff) + } + } else if let Err(io_error) = + File::create(&search.justfile).and_then(|mut file| file.write_all(&src_formatted.as_bytes())) { Err(Error::WriteJustfile { justfile: search.justfile.clone(), diff --git a/tests/fmt.rs b/tests/fmt.rs index 7e7e4b16..16530967 100644 --- a/tests/fmt.rs +++ b/tests/fmt.rs @@ -11,6 +11,51 @@ test! { status: EXIT_FAILURE, } +test! { + name: check_without_fmt, + justfile: "", + args: ("--check"), + stderr_regex: "error: The following required arguments were not provided: + --fmt +(.|\\n)+", + status: EXIT_FAILURE, +} + +test! { + name: dry_run_ok, + justfile: r#" +# comment with spaces + +export x := `backtick +with +lines` + +recipe: deps + echo "$x" + +deps: + echo {{ x }} + echo '$x' +"#, + args: ("--unstable", "--fmt", "--check"), + status: EXIT_SUCCESS, +} + +test! { + name: dry_run_found_diff, + justfile: "x:=``\n", + args: ("--unstable", "--fmt", "--check"), + stdout: " + @@ -1 +1 @@ + -x:=`` + +x := `` + ", + stderr: " + error: Formatted justfile differs from original. + ", + status: EXIT_FAILURE, +} + #[test] fn unstable_passed() { let tmp = tempdir();