Skip to content

feat: use semver to match required version #6066

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

Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
3469dd6
test: add test for `required_version`
wesleymatosdev Feb 8, 2024
2ce0666
chore: install `semver`
wesleymatosdev Feb 8, 2024
01a1ebf
refactor: use `semver` to compare versions
wesleymatosdev Feb 8, 2024
73e75ad
fix: use exact version for comparison when no comparator is specified
wesleymatosdev Feb 8, 2024
75b65d6
refactor: avoid overhead by considering default behavior from `semver`
wesleymatosdev Feb 9, 2024
33eb55c
test: add complex comparisons for required_version
wesleymatosdev Feb 9, 2024
b9934e6
refactor: handle `required_version` parsing manually
wesleymatosdev Feb 9, 2024
21ca2e2
chore: add `#[allow(dead_code)]`
wesleymatosdev Feb 9, 2024
435d1b9
docs: specify atual behavior on required_version docs
wesleymatosdev Feb 9, 2024
cbb6f62
refactor: avoid unwrap and nesting
wesleymatosdev Feb 9, 2024
93a3400
refactor: extract semver version checking
wesleymatosdev Feb 9, 2024
1fc86bc
test: extensively test `chek_semver_version`
wesleymatosdev Feb 9, 2024
89d2c0b
Merge branch 'master' into feat/#6063/use-semver-to-check-required-ve…
wesleymatosdev Feb 9, 2024
005ecb9
docs: add examples on configurations
wesleymatosdev Feb 9, 2024
d599275
Merge branch 'feat/#6063/use-semver-to-check-required-version' of git…
wesleymatosdev Feb 9, 2024
012f71b
docs: explain multiple values
wesleymatosdev Feb 9, 2024
da749b6
docs: mention `semver::Version` spec
wesleymatosdev Feb 9, 2024
18e692d
docs: delete `semver::Version` from spec
wesleymatosdev Feb 9, 2024
dd1d8ce
docs: add examples of different settings
wesleymatosdev Feb 9, 2024
cf53621
Merge branch 'feat/#6063/use-semver-to-check-required-version' of git…
wesleymatosdev Feb 9, 2024
7a92ca5
feat: enforce exact comparison
wesleymatosdev Feb 9, 2024
a7a04c7
docs: add more examples of operators
wesleymatosdev Feb 9, 2024
aceb3fd
docs: improve formatting
wesleymatosdev Feb 9, 2024
7fe12d1
refactor: early return `None` to avoid cognitive load
wesleymatosdev Feb 9, 2024
9142de4
docs: add `*` to examples
wesleymatosdev Feb 9, 2024
899d843
docs: clarify `*` restrictions on range
wesleymatosdev Feb 9, 2024
5b6592d
Update Configurations.md
wesleymatosdev Feb 17, 2024
60b7e2f
style(docs): add `:` to keep standard
wesleymatosdev Feb 17, 2024
ab0ba60
refactor: improve variable naming
wesleymatosdev Oct 8, 2024
f096df6
docs: add comment explaining why we need to check `^` and override th…
wesleymatosdev Oct 8, 2024
c3537ea
Merge branch 'master' into feat/#6063/use-semver-to-check-required-ve…
wesleymatosdev Oct 8, 2024
c29d067
refactor: use `version_req` instead of `required_version` and `label`…
wesleymatosdev Oct 8, 2024
aa2df65
test: add `"./rustfmt.toml"` to `Path`
wesleymatosdev Oct 8, 2024
2b58939
test: add wildcard unmatch
wesleymatosdev Oct 8, 2024
9380e25
docs: improve multiple versions to match session
wesleymatosdev Oct 8, 2024
9c4682a
test: add test for invalid usage of `||`
wesleymatosdev Oct 8, 2024
9567198
Merge branch 'master' of github.com:rust-lang/rustfmt into feat/#6063…
wesleymatosdev Oct 10, 2024
d649463
refactor: add versions on error logs
wesleymatosdev Oct 10, 2024
82dbefd
test: include more tests for mismatching versions
wesleymatosdev Oct 10, 2024
fbaddcc
docs: remove confusing section
wesleymatosdev Oct 10, 2024
0bae336
Merge branch 'feat/#6063/use-semver-to-check-required-version' of git…
wesleymatosdev Oct 10, 2024
23cf487
docs: re-include `style_edition` deleted by merge
wesleymatosdev Oct 10, 2024
f5684a7
docs: improve contradicting requirements explanation
wesleymatosdev Oct 10, 2024
fede8d0
test: include more mismatch tests
wesleymatosdev Oct 10, 2024
4e15dbf
docs: revert unnecessary change
wesleymatosdev Oct 10, 2024
e201040
test: include test matching to wildcard matching any version
wesleymatosdev Feb 18, 2025
afdef69
docs: improve multiple versions to match explanation
wesleymatosdev Feb 18, 2025
1eacf2c
Merge branch 'master' into feat/#6063/use-semver-to-check-required-ve…
wesleymatosdev Feb 18, 2025
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
25 changes: 13 additions & 12 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ unicode-width = "0.1"
unicode-properties = { version = "0.1", default-features = false, features = ["general-category"] }

rustfmt-config_proc_macro = { version = "0.3", path = "config_proc_macro" }
semver = "1.0.21"

# Rustc dependencies are loaded from the sysroot, Cargo doesn't know about them.

Expand Down
2 changes: 1 addition & 1 deletion Configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -2353,7 +2353,7 @@ Require a specific version of rustfmt. If you want to make sure that the
specific version of rustfmt is used in your CI, use this option.

- **Default value**: `CARGO_PKG_VERSION`
- **Possible values**: any published version (e.g. `"0.3.8"`)
- **Possible values**: `semver` compliant values, such as defined on [semver.org](https://semver.org/). Multiple values can be used, they should be separeted by a comma, e.g.: `1.0.0, 1.1.0`. By default, values without comparison operators are treated as `^` (caret) version requirements. If you want to specify an exact version, use the operator `=`, e.g.: `=1.0.0`.
- **Stable**: No (tracking issue: [#3386](https://github.com/rust-lang/rustfmt/issues/3386))

## `short_array_element_width_threshold`
Expand Down
238 changes: 230 additions & 8 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,23 @@ impl PartialConfig {
impl Config {
pub(crate) fn version_meets_requirement(&self) -> bool {
if self.was_set().required_version() {
let version = env!("CARGO_PKG_VERSION");
let required_version = self.required_version();
if version != required_version {
println!(
"Error: rustfmt version ({version}) doesn't match the required version \
({required_version})"
);
return false;
let version = semver::Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
let required_version = semver::VersionReq::parse(self.required_version().as_str());

match required_version {
Ok(required_version) => {
if !required_version.matches(&version) {
eprintln!(
"Error: rustfmt version ({}) doesn't match the required version ({})",
version, required_version
);
return false;
}
}
Err(e) => {
eprintln!("Error: failed to parse required version: {}", e);
return false;
}
}
}

Expand Down Expand Up @@ -1065,4 +1074,217 @@ make_backup = false
])
);
}

#[cfg(test)]
mod required_version {
use super::*;

#[allow(dead_code)] // Only used in tests
fn get_current_version() -> semver::Version {
semver::Version::parse(env!("CARGO_PKG_VERSION")).unwrap()
}

#[nightly_only_test]
#[test]
fn test_required_version_default() {
let config = Config::default();
assert!(config.version_meets_requirement());
}

#[nightly_only_test]
#[test]
fn test_current_required_version() {
let toml = format!("required_version=\"{}\"", env!("CARGO_PKG_VERSION"));
let config = Config::from_toml(&toml, Path::new("")).unwrap();

assert!(config.version_meets_requirement());
}

#[nightly_only_test]
#[test]
fn test_required_version_above() {
let toml = "required_version=\"1000.0.0\"";
let config = Config::from_toml(toml, Path::new("")).unwrap();

assert!(!config.version_meets_requirement());
}

#[nightly_only_test]
#[test]
fn test_required_version_below() {
let versions = vec!["0.0.0", "0.0.1", "0.1.0"];

for version in versions {
let toml = format!("required_version=\"{}\"", version.to_string());
let config = Config::from_toml(&toml, Path::new("")).unwrap();

assert!(!config.version_meets_requirement());
}
}

#[nightly_only_test]
#[test]
fn test_required_version_tilde() {
let toml = format!("required_version=\"~{}\"", env!("CARGO_PKG_VERSION"));
let config = Config::from_toml(&toml, Path::new("")).unwrap();

assert!(config.version_meets_requirement());
}

#[nightly_only_test]
#[test]
fn test_required_version_caret() {
let current_version = get_current_version();

for minor in current_version.minor..0 {
let toml = format!(
"required_version=\"^{}.{}.0\"",
current_version.major.to_string(),
minor.to_string()
);
let config = Config::from_toml(&toml, Path::new("")).unwrap();

assert!(!config.version_meets_requirement());
}
}

#[nightly_only_test]
#[test]
fn test_required_version_greater_than() {
let toml = "required_version=\">1.0.0\"";
let config = Config::from_toml(toml, Path::new("")).unwrap();

assert!(config.version_meets_requirement());
}

#[nightly_only_test]
#[test]
fn test_required_version_less_than() {
let toml = "required_version=\"<1.0.0\"";
let config = Config::from_toml(toml, Path::new("")).unwrap();

assert!(!config.version_meets_requirement());
}

#[nightly_only_test]
#[test]
fn test_required_version_range() {
let current_version = get_current_version();

let toml = format!(
"required_version=\">={}.0.0, <{}.0.0\"",
current_version.major,
current_version.major + 1
);
let config = Config::from_toml(&toml, Path::new("")).unwrap();

assert!(config.version_meets_requirement());
}

#[nightly_only_test]
#[test]
fn test_required_version_exact_boundary() {
let toml = format!("required_version=\"{}\"", get_current_version().to_string());
let config = Config::from_toml(&toml, Path::new("")).unwrap();

assert!(config.version_meets_requirement());
}

#[nightly_only_test]
#[test]
fn test_required_version_pre_release() {
let toml = format!(
"required_version=\"{}-alpha\"",
get_current_version().to_string()
);
let config = Config::from_toml(&toml, Path::new("")).unwrap();

assert!(config.version_meets_requirement());
}

#[nightly_only_test]
#[test]
fn test_required_version_with_build_metadata() {
let toml = format!(
"required_version=\"{}+build.1\"",
get_current_version().to_string()
);

let config = Config::from_toml(&toml, Path::new("")).unwrap();

assert!(config.version_meets_requirement());
}

#[nightly_only_test]
#[test]
fn test_required_version_invalid_specification() {
let toml = "required_version=\"not.a.version\"";
let config = Config::from_toml(toml, Path::new("")).unwrap();

assert!(!config.version_meets_requirement())
}

#[nightly_only_test]
#[test]
fn test_required_version_complex_range() {
let current_version = get_current_version();

let toml = format!(
"required_version=\">={}.0.0, <{}.0.0, ~{}.{}.0\"",
current_version.major,
current_version.major + 1,
current_version.major,
current_version.minor
);
let config = Config::from_toml(&toml, Path::new("")).unwrap();

assert!(config.version_meets_requirement());
}

#[nightly_only_test]
#[test]
fn test_required_version_wildcard_major() {
let toml = "required_version=\"1.x\"";
let config = Config::from_toml(toml, Path::new("")).unwrap();

assert!(config.version_meets_requirement());
}

#[nightly_only_test]
#[test]
fn test_required_version_wildcard_any() {
let toml = "required_version=\"*\"";
let config = Config::from_toml(toml, Path::new("")).unwrap();

assert!(config.version_meets_requirement());
}

#[nightly_only_test]
#[test]
fn test_required_version_major_version_zero() {
let toml = "required_version=\"0.1.0\"";
let config = Config::from_toml(toml, Path::new("")).unwrap();

assert!(!config.version_meets_requirement());
}

#[nightly_only_test]
#[test]
fn test_required_version_future_major_version() {
let toml = "required_version=\"3.0.0\"";
let config = Config::from_toml(toml, Path::new("")).unwrap();

assert!(!config.version_meets_requirement());
}

#[nightly_only_test]
#[test]
fn test_required_version_fail_different_operator() {
// != is not supported
let toml = "required_version=\"!=1.0.0\"";
let config = Config::from_toml(toml, Path::new("")).unwrap();

assert!(!config.version_meets_requirement());
}
}
}