-
Notifications
You must be signed in to change notification settings - Fork 913
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
Allow cargo-fmt to handle formatting individual files. #5071
base: master
Are you sure you want to change the base?
Conversation
1459d47
to
f6aefb0
Compare
Excellent work getting this pulled together so quickly! I want to first summarize the primary goal/use case of this new feature given that it's been primarily discussed offline so far.
The decoupling of cargo aspects from rustfmt is intentional and desirable, both from an overall design standpoint and so that rustfmt can be easily used for input strings, single *.rs files, etc. Users can pass their desired edition value to rustfmt via the rustfmt config file or the
This gives a lot of options to users that are running rustfmt, since they can either However, one scenario that is a bit tricky today is the various plugins and extensions for code editors and IDEs which support the increasingly common "format on save" behavior, where they will automatically invoke a formatter when users save changes to files in the IDE/editor (the formatter being rustfmt in the case of That is the specific use case we want to solve for here: we want to provide a built in way to run an Edition-aware rustfmt invocation against a single file with a target audience of editor/IDE plugins so that they no longer have to either reinvent that wheel or require all of their users add a Human users can and should still continue to use the existing invocations (i.e. |
Given the above, some things to consider around the implementation:
|
Got it. I'll update it to no longer accept an arbitrary number of files, and restrict it to a single file.
I personally feel like we should always resolve paths based on the on the current working directory. It does feel a little weird to change the path resolution based on the manifest path.
I think in a scenario like the one you just posed, where a user invokes As It might make sense for us to add the following check to make sure users can never pass a file path to if opts.rustfmt_options.iter().any(|s|s.ends_with(".rs")) {
print_usage_to_stderr("cannot pass rust files to rustfmt through cargo-fmt");
return FAILURE;
} Are there any other arguments we shouldn't let users pass along? I think it probably makes sense to write some tests to cover cases where users are passing values along to |
Also, when it comes to the edition, if a user does pass
If we pass I bring this up because it seems like there's currently issues with some arguments that I'd also expect to see the edition I passed in from the command line displayed in the verbose output, but the edition from the cargo.toml file is output (running the same command as above):
and what I expect to be output
Does it make sense to include this check to disallow passing the if opts.rustfmt_options.iter().any(|s| s.contains("--edition")) {
print_usage_to_stderr("cannot pass '--edition' to rustfmt through cargo-fmt");
return FAILURE;
} If we want to allow users to be able to provide an edition then this seems like a separate issue that I can open / work on a PR to fix. |
Fair enough, just thinking about scenarios where someone has to do something like
That's an interesting idea! IIRC that's technically permitted today, though I guess
Good observation, thanks for sharing. I vaguely remember thinking about this when I did some work on the emitters and IIRC I added a bit of logic into I want to think on this a bit more, but I am currently leaning towards trying to limit rustfmt args on |
Definitely take some time to think on it! I'm also leaning towards limiting the arguments that can be passed along to Once I know how you'd like to move forward I'll get back to working on the PR |
That's a good point, you've sold me 😄 I'd like to ensure that the two buckets of changes (new |
I think It's definitely doable to get them added at the same time but I'll let you know if I run into any issues. I've already mentioned a few inconsistent arguments, and I'll be sure to add checks for them. If you can think of any other arguments that we shouldn't allow |
f6aefb0
to
91080f3
Compare
Check-InJust want to give a follow up on the progress so far:
Things I'm still working throughCurrently the target resolution for source files (implemented in I was wondering if you thought there might be a better way to handle the target resolution or if you think it's appropriate to have a fallback for the Currently this works just fine: Since the main use case for this feature is to better help IDE/editors implement format on save I think it makes sense to have a default for |
Thinking through conflicting arguments:
I might be jumping the gun here, but I think that with the few checks I've added we pretty much handle all the conflicting arguments that could be passed to |
Have done a very quick pass through this, and some initial reactions:
|
All of the existing Given the current implementation, In a scenario where a user passes both the To ensure that we're using the right
I'm glad that you're on board with the current best effort solution that I put together. In the future we can definitely come back and revise it, but I think it's a good starter solution. |
The difference is that all of the other use cases are targeted at a human audience where invoking the metadata command from the current working directory is desirable/ideal. This use case is targeted at they very niche programmatic invocation from IDE editors/plugins, and I don't think we can (or at least should) assume those will set the cwd to the modified file for their invocation of If it helps, the big difference is that the existing discovery modes are "format my current context without me having to provide specifics" whereas this new feature is almost the inverse paradigm of "format this precise thing I've specified" |
I see. I wasn't thinking about it that way. In that case it definitely makes more sense to prevent users from passing the fn get_cargo_metadata_from_source_file(path: PathBuf) -> Option<cargo_metadata::Metadata> {
for p in path.ancestors() {
let manifest = p.join("Cargo.toml");
if manifest.exists() {
return get_cargo_metadata(Some(manifest)).ok()
}
}
None
} |
(EDIT: To be clear I doubt this is a reasonable option, but figured it made sense to mention in case it was useful somehow...) Hello, I've read through the discussion and I was curious if it would make sense for the filename(s) to be presented as positional arguments like this, where filenames would go before the
I think there are two upsides to having the files be positional arguments:
Meanwhile some downsides I can think of would be:
In any case, here are some examples of how this would look. These are invalid commands if you tried them in
To be clear I don't have a strong opinion either way but figured it was worth mentioning in case it was useful. The fact that there's already a |
For the To take advantage of this, fn get_cargo_metadata(manifest_path: Option<&Path>, cwd: Option<&Path>) -> Result<cargo_metadata::Metadata, io::Error> {
let mut cmd = cargo_metadata::MetadataCommand::new();
cmd.no_deps();
if let Some(manifest_path) = manifest_path {
cmd.manifest_path(manifest_path);
}
// NEW:
if let Some(cwd) = cwd {
cmd.current_dir(cwd);
}
cmd.other_options(vec![String::from("--offline")]);
match cmd.exec() {
Ok(metadata) => Ok(metadata),
Err(_) => {
cmd.other_options(vec![]);
match cmd.exec() {
Ok(metadata) => Ok(metadata),
Err(error) => Err(io::Error::new(io::ErrorKind::Other, error.to_string())),
}
}
}
} Then the existing calls to |
@nickbp Thanks so much for taking the time to looking into the To address your first comment, I don't think we want to allow users to be able to call
As @calebcartwright mentioned in his first comment on this post:
Since Since the primary goal of this feature is to aide editor/plugin authors in implementing "format on save" in a cargo-aware manner I also don't think it makes sense to allow you to pass more than one file. My initial implementation for the feature did allow you to pass multiple files, but that idea was already rejected, so just want us to avoid bringing that back up.
Again, really appreciate your input here, and just want to share some of my thoughts/opinions. Although the examples you pointed out might not work, there are still currently ways to pass files to
We had a little discussion about this above and I think this hack of passing individual files directly to rustfmt is inconsistent with All that being said, I'm personally leaning towards the |
To be clear I think the In any case I don't want to derail the feature and I think it's great to go ahead with |
Awesome! I think we're in agreement then! I also don't want to rule out us adding the ability to pass files as positional arguments in the future but it's not something I want to work to support with this PR. If you happen to take a look through the implementation and have any ideas for ways that things could be improved please let me know! |
Confess I've not read through all the subsequent dialog word for word, but gather from latest comments that everything is caught up. There's two outstanding items I'd like to see before moving ahead with releasing this feature, neither necessarily strictly related to this PR.
|
Got it! so I imagine we'd need to stabilize I'll also set aside some time to do some research into how editor plugins are invoking |
In spirit yes, though more precisely the opposite. We've wanted to invert the behavior to be non-recursive by default (with a recursive opt-in) for a while. It's an easy technical change, and actually already landed on a different branch, but it's more of a process blocker/question.
Sounds good, though to be clear that's not strictly a blocker for this PR. It's just that it's in the same vein of the editor experience as this one and worth looking at especially while this is blocked. Also I don't think we necessarily need to review all the various editor plugins either, a handful (like RLS) actually use the library API, but most of them just run |
Just opened #5266, which should hopefully address the fn run_rustfmt(
snap: &GlobalStateSnapshot,
text_document: TextDocumentIdentifier,
range: Option<lsp_types::Range>,
) -> Result<Option<Vec<lsp_types::TextEdit>>> {
let file_id = from_proto::file_id(snap, &text_document.uri)?;
let file = snap.analysis.file_text(file_id)?;
let crate_ids = snap.analysis.crate_for(file_id)?;
let line_index = snap.file_line_index(file_id)?;
let mut rustfmt = match snap.config.rustfmt() {
RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => {
/* A FEW LINES OF CODE WERE REMOVED FORE DEMONSTRATION */
if let Some(&crate_id) = crate_ids.first() {
// Assume all crates are in the same edition
let edition = snap.analysis.crate_edition(crate_id)?;
cmd.arg("--edition");
cmd.arg(edition.to_string());
}
... I want to highlight these lines because they show hat rust-analizer has everything it needs in context at this point to utilize the newly proposed option in #5266.
|
What do you folks think of using the |
@pankdm thanks for the input. It's hard for me to recall why I originally named the options |
Adds an additional command line option ``--src-file`` / ``-s`` It should be noted that this does not allow cargo-fmt to format an arbitrary rust file. The file must be contained with a target specified in a Cargo.toml file. Adjustments were also made to prevent cargo-fmt from passing along certain arguments to rustfmt. For example, users can no longer pass rust files to rustfmt through cargo-fmt. Additionally, users cannot pass --edition to cargo-fmt, since editions are read from the Cargo.toml. Tests were added in ``src/cargo-fmt/test/mod.rs`` and integration tests were added to ``tests/cargo-fmt/main.rs``
91080f3
to
7c14191
Compare
@epage - continuance from #6264 this (Cargo-aware single file formatting) is something that we've wanted to do for a while. It's not something we've looked at recently, but if i recall correctly the outstanding question in my mind was how reliable/accurate of an approach we could really have to determine the correct Cargo info based on any arbitrary directory/file on disk and I think this is where Cargo expertise will be highly valuable. i.e. i think comparing directory paths against package targets will be pretty accurate, but in what circumstances could that be inaccurate and how likely would those be (e.g. a multi-crate workspace with differing Cargo.toml editions and path-based module imports?) |
Going to step back and summarize the thread
The best we can do determining the manifest and target is a heuristic. A file can be used by multiple targets and even multiple packages. It might be good to consult r-a on this as I suspect they deal with this. Some ideas
|
Thank you for the summary and detail @epage! Not sure whether this would qualify as a challenge or question, but I wholeheartedly echo the need to consult with relevant ide/editor teams i.e. would they actually still prefer to use |
Sorry, I wasn't clear enough. I believe r-a has to deal with a subset of the challenge we face in mapping a source file back to a manifest and we can possibly gain insight into how they deal with that problem. I can't remember if they've yet solved the cargo-script side of this or not but I know its been discussed. Of course, it would be good to make sure we're looping in IDEs to make sure we are solving the right problem more generally. For example, would stdin work better for them than formatting a file path? If they load the manifest already, could they pass in the edition on the command-line? r-a is on one side of the spectrum for this and simpler plugins like for vim are likely on the other side of the spectrum. |
In case it makes a difference, rust-lang/rfcs#3772 was recently accepted which deprecates target-specific edition fields. In my interpretation (and intention of writing it), |
Summary
This PR adds a long requested feature to
cargo-fmt
. Users will now be able to specify individual files inside their package to format. The motivating use case for this change is to make it easier for maintainers and developers of editor/ide plugins to implement format file on save behavior. The command looks like this:Not addressed by this PR:
cargo fmt
cargo fmt
.Things To Do Before PR Is Officially Ready
-s
with-p
and--all
-s
option is incompatible with.