Conversation
There was a problem hiding this comment.
This was extracted from format.rs but nothing really changed, so you can skip review of this file
| // Blocks until EOF is received! | ||
| io::stdin() | ||
| .lock() | ||
| .read_to_string(&mut old) | ||
| .map_err(FormatStdinError::Read)?; |
There was a problem hiding this comment.
Air will "hang" if you just do this at the command line without providing it any stdin
air format --stdin-file-path foo.RThe process is waiting for you to write to stdin and will hang until you get an EOF.
I think this is all correct, it just caught me by surprise the first time
There was a problem hiding this comment.
yep that's standard behaviour
| /// TODO!: This test should FAIL once we change Air's defaults regarding directly supplied | ||
| /// files like `air format standalone-*.R` or `air format --stdin-file-path | ||
| /// standalone-*.R` and how they interact with `exclude`, `default-exclude`, and | ||
| /// `default-include`. | ||
| /// | ||
| /// To better support pre-commit and RStudio, which will blindly provide whatever the user | ||
| /// has touched / saved to air as a file to format, we SHOULD respect the `exclude` rules | ||
| /// here by default and refuse to format this at the command line. If a user really wants | ||
| /// to bypass the exclude rules then they can do something like | ||
| /// `--ignore-exclude-for-directly-supplied-file` (or maybe we wouldn't allow this at | ||
| /// all?). This is honestly more in line with the LSP. If you do `Format Document` in | ||
| /// an excluded file, then we still refuse to format it! | ||
| /// | ||
| /// Also, this would help with Quarto/Rmarkdown where people are trying to do `air format | ||
| /// test.Rmd` and they either get an obscure parse error or it actually fake works due to | ||
| /// chance. At the very least we'd now silently refuse to format this file because it | ||
| /// isn't in our `default-includes`, even though they provided it directly at the command | ||
| /// line. Or we could report a warning about this rather than being silent, and still | ||
| /// refuse to format. | ||
| #[test] | ||
| fn test_stdin_refuses_to_format_default_excludes() -> anyhow::Result<()> { | ||
| let directory = TempDir::new()?; | ||
| let directory = directory.path(); | ||
|
|
||
| let cpp11_path = "cpp11.R"; | ||
| let cpp11_contents = "1+1"; | ||
| std::fs::write(directory.join(cpp11_path), cpp11_contents)?; | ||
|
|
||
| // `cpp11.R` is a `default-exclude` so it SHOULD refuse to format this | ||
| // and just reemit the existing `1+1` asis | ||
| insta::assert_snapshot!( | ||
| Command::new(binary_path()) | ||
| .current_dir(directory) | ||
| .stdin(Stdio::piped()) | ||
| .stdout(Stdio::piped()) | ||
| .stderr(Stdio::piped()) | ||
| .arg("format") | ||
| .arg("--stdin-file-path") | ||
| .arg(cpp11_path) | ||
| .run_with_stdin(cpp11_contents.to_string()) | ||
| ); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| // TODO!: This should also refuse to format. See above. | ||
| #[test] | ||
| fn test_stdin_refuses_to_format_user_excludes() -> anyhow::Result<()> { |
There was a problem hiding this comment.
Two tests which I think should probably fail once we change the default exclude behavior, this would better support pre-commit and rstudio
|
|
||
| /// Representation of a formatted file | ||
| #[derive(Debug)] | ||
| pub enum FormattedFile { |
There was a problem hiding this comment.
The little format_file() helper from here moved into format/paths.rs directly, and that allowed me to flatten a lot of unneeded abstraction and remove most of these intermediate enums
| // Blocks until EOF is received! | ||
| io::stdin() | ||
| .lock() | ||
| .read_to_string(&mut old) | ||
| .map_err(FormatStdinError::Read)?; |
There was a problem hiding this comment.
yep that's standard behaviour
| } | ||
| fn check_argument_consistency(command: &FormatCommand) -> Option<ExitStatus> { | ||
| if command.stdin_file_path.is_some() && !command.paths.is_empty() { | ||
| tracing::error!( |
There was a problem hiding this comment.
It's still feels weird to me that we communicate user-facing messages on stderr via the log infrastructure, even if it also makes sense 🙃
It seems nice to follow the same general API as other formatters, to facilitate integrations with external tools. |
01c6e33 to
634a933
Compare
Closes #202
New
--stdin-file-path <path>option to enable stdin support in the Air CLI.This is extremely similar to what most of the CLI formatting ecosystem does
This option is a bit weird, and different formatters handle it slightly differently.
Three reasons to have
--stdin-file-pathIt is the trigger to recognize that you're in "stdin mode". If it is supplied, we read from
stdinand also error if any files or directories are provided the "normal" way.It determines where to start the search for
air.tomlfiles. This is extremely important!For preexisting files, if your IDE formats via stdin then we need to know where the stdin comes from. i.e. if stdin came from
project/file.Rand you haveproject/air.toml, we need--stdin-file-path project/file.Rso we can find your config fileFor new files, where some tool has created R code but you haven't written it to disk yet, this is equally as important. If you want to write to
project/doesnt-exist-yet.Rand you haveproject.air.toml, we need--stdin-file-path project/doesnt-exist-yet.Rto find your config file. Note that this means that the file provided doesn't even have to exist yet. And that's considered a feature!It determines the file extension to associate with stdin. For us, that is currently always
.Rand we don't really do anything with this. But for prettier, biome, and ruff, which can all format multiple file types, the--stdin-file-pathtells them which formatter to use under the hood. Again, even if the file doesn't exist, the extension provides valuable info. I like that this leaves the door open for.Rmdor.qmdsupport if we want to add it in the future.Other miscellaneous details
If a relative path is provided to
--stdin-file-path, it is resolved from the current working directory. So basicallyair format file.Randair format --stdin-file-path file.Rresolve the same way. I think this is right, but I would hope that any IDE that uses this feature would supply an absolute path as--stdin-file-pathto avoid any funny business that could result from them launching air in a weird directory or something. Providing an absolute path removes any chance to miss anair.toml.I think our default exclude behavior for
air format cpp11.R, wherecpp11.Ris adefault-exclude, is not quite right. I think that both here and inair format --stdin-file-path cpp11.R, we should refuse to format these by default, and maybe provide some way to opt back in when you really want it. This would better support both pre-commit and RStudio, where they both blindly provide air whatever file the user saved or edited in, and they have no way of knowing what the excludes are. I think it is more important for that to refuse to formatcpp11.Rthan it is for a user to explicitly runair format cpp11.Rat the command line and get that file formatted. I'd argue we probably should not even provide an opt in flag to allow this at all.air format <file>.Randair format --stdin-file-path <file>.Rshould respectexcludeanddefault-exclude#472Similarly, I think our default include behavior for
air format file.qmd, wherefile.qmdis not indefault-include, is not quite right. We've had a lot of people get confused thatair format file.qmdfake works. If you don't get a parse error by chance, then air will just completely break your qmd. I'd again argue that we should refuse to format this, maybe with a warning. Alternatively, if we feel like this goes a step too far, then I'd be ok with black listing.qmdand.Rmd, while still allowing everything else, likeair format NAMESPACE, since that's technically an R file and isn't "excluded".air format <file>.qmdandair format --stdin-file-path <file>.qmdshould maybe respectdefault-include#473