Skip to content
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

Merge configs from parent directories #4179

Merged
merged 6 commits into from
May 24, 2020
Merged

Conversation

ayazhafiz
Copy link
Contributor

Currently, rustfmt stops building its configuration after one
configuration file is found. However, users may expect that rustfmt
includes configuration options inhereted by configuration files in
parent directories of the directory rustfmt is run in.

The motivation for this commit is for a use case involving files that
should be ignored by rustfmt. Consider the directory structure

a
|-- rustfmt.toml
|-- b
    |-- rustfmt.toml
    |-- main.rs

Suppose a/rustfmt.toml has the option ignore = ["b/main.rs"]. A user
may expect that running rusfmt in either directory a/ or b/ will
ignore main.rs. Today, though, if rustfmt is run in b/, main.rs
will be formatted because only b/rustfmt.toml will be used for
configuration.

Although motivated by the use case for ignored files, this change sets
up rustfmt to incrementally populate all configuration options from
parent config files. The priority of population is closest config file
to most transient config file.

To avoid churn of the existing implementation for ignoring files,
populating the ignored files config option with multiple config files is
done by computing a join. Given config files "outer" and "inner", where
"inner" is of higher priority (nested in the directory of) "outer", we
merge their ignore configurations by finding the ignore files
specified in "outer" that are present in the "inner" directory, and
appending this to the files ignored by "inner".

This means that printing an ignore configuration may not be entirely
accurate, as it could be missing files that are not in the current
directory specified by transient configurations. These files are out of
the scope the rustfmt instance's operation, though, so for practical
purposes I don't think this matters.

Closes #3881

@ayazhafiz
Copy link
Contributor Author

ayazhafiz commented May 16, 2020

Blocked by #4177

@topecongiro topecongiro added the blocked Blocked on rustc, an RFC, etc. label May 17, 2020
@calebcartwright calebcartwright removed the blocked Blocked on rustc, an RFC, etc. label May 21, 2020
@calebcartwright
Copy link
Member

Still working through this (may have to circle back to it over the weekend) but could you add some tests for this too? It's one of these scenarios that'll likely need a bit of additional plumbing but this is awesome behavior that we want to have tests for.

I think one approach would be setting up a subdirectory to mimic nested config files, and ignoring everything except the entry point file to properly validate the config detection and merging

@ayazhafiz
Copy link
Contributor Author

@calebcartwright Ive added a test doing this on lines 648-685 of rustfmt-lib/src/config.rs. Do you think we should pull it out into the tests directory?

@calebcartwright
Copy link
Member

Apologies should've been more clear. It'd be great to get some integration/system tests in addition to the unit tests (not instead of)

case ${INTEGRATION} in
cargo)
git clone --depth=1 https://github.com/rust-lang/${INTEGRATION}.git
cd ${INTEGRATION}
git clone --depth=1 https://github.com/rust-lang/${INTEGRATION}.git ${tempdir}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a temp dir so we don’t pick up rustfmt’s rustfmt.toml. This shouldn’t matter for this PR bc of the feature flag, but will long term if the PR lands.

Copy link
Contributor

@topecongiro topecongiro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR! Looks good to me overall.

rustfmt-core/rustfmt-lib/src/config/options.rs Outdated Show resolved Hide resolved
@@ -50,6 +50,22 @@ impl ConfigType for IgnoreList {
}
}

macro_rules! update_config {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this macro?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, see the usage in fill_from_parsed_config on line 164.

rustfmt-core/rustfmt-lib/src/config.rs Outdated Show resolved Hide resolved
rustfmt-core/rustfmt-lib/src/config.rs Outdated Show resolved Hide resolved
Currently, `rustfmt` stops building its configuration after one
configuration file is found. However, users may expect that `rustfmt`
includes configuration options inhereted by configuration files in
parent directories of the directory `rustfmt` is run in.

The motivation for this commit is for a use case involving files that
should be ignored by `rustfmt`. Consider the directory structure

```
a
|-- rustfmt.toml
|-- b
    |-- rustfmt.toml
    |-- main.rs
```

Suppose `a/rustfmt.toml` has the option `ignore = ["b/main.rs"]`. A user
may expect that running `rusfmt` in either directory `a/` or `b/` will
ignore `main.rs`. Today, though, if `rustfmt` is run in `b/`, `main.rs`
will be formatted because only `b/rustfmt.toml` will be used for
configuration.

Although motivated by the use case for ignored files, this change sets
up `rustfmt` to incrementally populate all configuration options from
parent config files. The priority of population is closest config file
to most transient config file.

To avoid churn of the existing implementation for ignoring files,
populating the ignored files config option with multiple config files is
done by computing a join. Given config files "outer" and "inner", where
"inner" is of higher priority (nested in the directory of) "outer", we
merge their `ignore` configurations by finding the ignore files
specified in "outer" that are present in the "inner" directory, and
appending this to the files ignored by "inner".

This means that printing an `ignore` configuration may not be entirely
accurate, as it could be missing files that are not in the current
directory specified by transient configurations. These files are out of
the scope the `rustfmt` instance's operation, though, so for practical
purposes I don't think this matters.

Closes rust-lang#3881
Copy link
Contributor

@topecongiro topecongiro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you for making updates!

@topecongiro topecongiro merged commit 796cc58 into rust-lang:master May 24, 2020
@ayazhafiz ayazhafiz deleted the i/3881 branch May 24, 2020 03:31
bradleypmartin pushed a commit to bradleypmartin/rustfmt that referenced this pull request May 25, 2020
@calebcartwright calebcartwright added 1x-backport:pending Fixed/resolved in source but not yet backported to a 1x branch and release and removed backport-triage labels Jul 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
1x-backport:pending Fixed/resolved in source but not yet backported to a 1x branch and release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ignore is overwritten in nested rustfmt.toml layouts
4 participants