feat(config): make opptions optional for rule with a fix

This commit is contained in:
Victorien Elvinger 2025-11-01 17:14:01 +01:00
parent 27a58380b6
commit 80974fb70d
No known key found for this signature in database
GPG key ID: 2DBFB646E2071F2A
8 changed files with 962 additions and 354 deletions

View file

@ -0,0 +1,24 @@
---
"@biomejs/biome": minor
---
Rule's `options` is now optional in the Biome configuration files for rules with a `fix` kind.
Previously, configuring a rule's `fix` required `options` to be set.
Now, `options` is optional.
The following configuration is now valid:
```json
{
"linter": {
"rules": {
"correctness": {
"noUnusedImports": {
"level": "on",
"fix": "safe"
}
}
}
}
}
```

View file

@ -487,7 +487,7 @@ fn migrate_eslint_rule(
biome_config::RuleWithFixOptions {
level: severity.into(),
fix: None,
options: *Box::new((*rule_options).into()),
options: Some(*Box::new((*rule_options).into())),
},
));
}
@ -534,7 +534,7 @@ fn migrate_eslint_rule(
biome_config::RuleWithFixOptions {
level: severity.into(),
fix: None,
options: *Box::new((*rule_options).into()),
options: Some(*Box::new((*rule_options).into())),
},
));
}
@ -551,7 +551,7 @@ fn migrate_eslint_rule(
biome_config::RuleWithFixOptions {
level: severity.into(),
fix: None,
options: rule_options.into(),
options: Some(rule_options.into()),
},
));
}
@ -567,7 +567,7 @@ fn migrate_eslint_rule(
biome_config::RuleWithFixOptions {
level: severity.into(),
fix: None,
options: rule_options.into(),
options: Some(rule_options.into()),
},
));
}
@ -602,7 +602,7 @@ fn migrate_eslint_rule(
biome_config::RuleWithFixOptions {
level: severity.into(),
fix: None,
options: options.into(),
options: Some(options.into()),
},
));
}

View file

@ -550,3 +550,68 @@ fn extends_config_merge_overrides() {
result,
));
}
#[test]
fn extends_config_rule_options_merge() {
let fs = MemoryFileSystem::default();
let mut console = BufferConsole::default();
let shared = Utf8Path::new("shared.json");
fs.insert(
shared.into(),
r#"{
"linter": {
"enabled": true,
"rules": {
"correctness": {
"noUnusedVariables": {
"level": "on",
"options": {
"ignoreRestSiblings": false
}
}
}
}
}
}"#,
);
let biome_json = Utf8Path::new("biome.json");
fs.insert(
biome_json.into(),
r#"{
"extends": ["shared.json"],
"linter": {
"enabled": true,
"rules": {
"correctness": {
"noUnusedVariables": {
"level": "on",
"fix": "safe"
}
}
}
}
}"#,
);
let test_file = Utf8Path::new("test.js");
fs.insert(
test_file.into(),
"const { a, ...rest } = { a: 1, b: 2}; export { rest }",
);
let (fs, result) = run_cli(
fs,
&mut console,
Args::from(["lint", test_file.as_str()].as_slice()),
);
assert_cli_snapshot(SnapshotPayload::new(
module_path!(),
"extends_config_rule_options_merge",
fs,
console,
result,
));
}

View file

@ -0,0 +1,70 @@
---
source: crates/biome_cli/tests/snap_test.rs
expression: redactor(content)
---
## `biome.json`
```json
{
"extends": ["shared.json"],
"linter": {
"enabled": true,
"rules": {
"correctness": {
"noUnusedVariables": {
"level": "on",
"fix": "safe"
}
}
}
}
}
```
## `shared.json`
```json
{
"linter": {
"enabled": true,
"rules": {
"correctness": {
"noUnusedVariables": {
"level": "on",
"options": {
"ignoreRestSiblings": false
}
}
}
}
}
}
```
## `test.js`
```js
const { a, ...rest } = { a: 1, b: 2}; export { rest }
```
# Emitted Messages
```block
test.js:1:9 lint/correctness/noUnusedVariables ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
! This variable a is unused.
> 1 │ const { a, ...rest } = { a: 1, b: 2}; export { rest }
│ ^
i Unused variables are often the result of an incomplete refactoring, typos, or other sources of bugs.
i You can use the ignoreRestSiblings option to ignore unused variables in an object destructuring with a spread.
```
```block
Checked 1 file in <TIME>. No fixes applied.
Found 1 warning.
```

View file

@ -165,9 +165,10 @@ impl<T: Clone + Default + 'static> RuleFixConfiguration<T> {
pub fn get_options(&self) -> Option<RuleOptions> {
match self {
Self::Plain(_) => None,
Self::WithOptions(options) => {
Some(RuleOptions::new(options.options.clone(), options.fix))
}
Self::WithOptions(options) => Some(RuleOptions::new(
options.options.clone().unwrap_or_default(),
options.fix,
)),
}
}
}
@ -403,14 +404,16 @@ pub struct RuleWithFixOptions<T: Default> {
#[serde(skip_serializing_if = "Option::is_none")]
pub fix: Option<FixKind>,
/// Rule's options
pub options: T,
pub options: Option<T>,
}
impl<T: Default> Merge for RuleWithFixOptions<T> {
fn merge_with(&mut self, other: Self) {
self.level = other.level;
self.fix = other.fix.or(self.fix);
self.options = other.options;
if other.options.is_some() {
self.options = other.options;
}
}
}

View file

@ -0,0 +1,12 @@
{
"linter": {
"rules": {
"correctness": {
"noUnusedImports": {
"level": "on",
"fix": "safe"
}
}
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff