Skip to content

Commit

Permalink
feat: add fix option
Browse files Browse the repository at this point in the history
  • Loading branch information
Conaclos committed May 16, 2024
1 parent 671e138 commit 2e8ad62
Show file tree
Hide file tree
Showing 21 changed files with 563 additions and 95 deletions.
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,47 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b

### Configuration

#### New features

- Add an rule option `fix` to override the code fix kind of a rule ([#2882](https://github.com/biomejs/biome/issues/2882)).

A rule can provide a safe or an **unsafe** code **action**.
You can now tune the kind of code actions thanks to the `fix` option.
This rule option takes a value among:

- `none`: the rule no longer emits code actions.
- `safe`: the rule emits safe code action.
- `unsafe`: the rule emits unsafe code action.

The following configuration disables the code actions of `noUnusedVariables`, makes the emitted code actions of `style/useConst` and `style/useTemplate` unsafe and safe respectively.

```json
{
"linter": {
"rules": {
"correctness": {
"noUnusedVariables": {
"level": "error",
"fix": "none"
},
"style": {
"useConst": {
"level": "warn",
"fix": "unsafe"
},
"useTemplate": {
"level": "warn",
"fix": "safe"
}
}
}
}
}
}
```

Contributed by @Conaclos

#### Enhancements

- The `javascript.formatter.trailingComma` option is deprecated and renamed to `javascript.formatter.trailingCommas`. The corresponding CLI option `--trailing-comma` is also deprecated and renamed to `--trailing-commas`. Details can be checked in [#2492](https://github.com/biomejs/biome/pull/2492). Contributed by @Sec-ant
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

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

20 changes: 11 additions & 9 deletions crates/biome_analyze/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@ version = "0.5.7"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
biome_console = { workspace = true }
biome_diagnostics = { workspace = true }
biome_rowan = { workspace = true }
bitflags = { workspace = true }
rustc-hash = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
tracing = { workspace = true }
biome_console = { workspace = true }
biome_deserialize = { workspace = true, optional = true }
biome_deserialize_macros = { workspace = true, optional = true }
biome_diagnostics = { workspace = true }
biome_rowan = { workspace = true }
bitflags = { workspace = true }
rustc-hash = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"], optional = true }
tracing = { workspace = true }


[features]
serde = ["schemars"]
serde = ["dep:serde", "dep:schemars", "dep:biome_deserialize", "dep:biome_deserialize_macros"]

[lints]
workspace = true
30 changes: 24 additions & 6 deletions crates/biome_analyze/src/options.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
use rustc_hash::FxHashMap;

use crate::{Rule, RuleKey};
use crate::{FixKind, Rule, RuleKey};
use std::any::{Any, TypeId};
use std::fmt::Debug;
use std::path::PathBuf;

/// A convenient new type data structure to store the options that belong to a rule
#[derive(Debug)]
pub struct RuleOptions((TypeId, Box<dyn Any>));
pub struct RuleOptions(TypeId, Box<dyn Any>, Option<FixKind>);

impl RuleOptions {
/// Creates a new [RuleOptions]
pub fn new<O: 'static>(options: O, fix_kind: Option<FixKind>) -> Self {
Self(TypeId::of::<O>(), Box::new(options), fix_kind)
}

/// It returns the deserialized rule option
pub fn value<O: 'static>(&self) -> &O {
let (type_id, value) = &self.0;
let RuleOptions(type_id, value, _) = &self;
let current_id = TypeId::of::<O>();
debug_assert_eq!(type_id, &current_id);
// SAFETY: the code should fail when asserting the types.
Expand All @@ -21,9 +26,8 @@ impl RuleOptions {
value.downcast_ref::<O>().unwrap()
}

/// Creates a new [RuleOptions]
pub fn new<O: 'static>(options: O) -> Self {
Self((TypeId::of::<O>(), Box::new(options)))
pub fn fix_kind(&self) -> Option<FixKind> {
self.2
}
}

Expand All @@ -41,6 +45,10 @@ impl AnalyzerRules {
pub fn get_rule_options<O: 'static>(&self, rule_key: &RuleKey) -> Option<&O> {
self.0.get(rule_key).map(|o| o.value::<O>())
}

pub fn get_rule_fix_kind(&self, rule_key: &RuleKey) -> Option<FixKind> {
self.0.get(rule_key).and_then(|options| options.fix_kind())
}
}

/// A data structured derived from the `biome.json` file
Expand Down Expand Up @@ -95,6 +103,16 @@ impl AnalyzerOptions {
.cloned()
}

pub fn rule_fix_kind<R>(&self) -> Option<FixKind>
where
R: Rule + 'static,
R::Options: Clone,
{
self.configuration
.rules
.get_rule_fix_kind(&RuleKey::rule::<R>())
}

pub fn preferred_quote(&self) -> &PreferredQuote {
&self.configuration.preferred_quote
}
Expand Down
39 changes: 27 additions & 12 deletions crates/biome_analyze/src/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ use biome_diagnostics::{
Visit,
};
use biome_rowan::{AstNode, BatchMutation, BatchMutationExt, Language, TextRange};
use serde::Serialize;
use std::cmp::Ordering;
use std::fmt::Debug;

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
/// Static metadata containing information about a rule
pub struct RuleMetadata {
/// It marks if a rule is deprecated, and if so a reason has to be provided.
Expand All @@ -35,17 +35,29 @@ pub struct RuleMetadata {
/// Whether a rule is recommended or not
pub recommended: bool,
/// The kind of fix
pub fix_kind: Option<FixKind>,
pub fix_kind: FixKind,
/// The source URL of the rule
pub sources: &'static [RuleSource],
/// The source kind of the rule
pub source_kind: Option<RuleSourceKind>,
}

#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(
biome_deserialize_macros::Deserializable,
schemars::JsonSchema,
serde::Deserialize,
serde::Serialize
)
)]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
/// Used to identify the kind of code action emitted by a rule
pub enum FixKind {
/// The rule doesn't emit code actions.
#[default]
None,
/// The rule emits a code action that is safe to apply. Usually these fixes don't change the semantic of the program.
Safe,
/// The rule emits a code action that is _unsafe_ to apply. Usually these fixes remove comments, or change
Expand All @@ -56,14 +68,16 @@ pub enum FixKind {
impl Display for FixKind {
fn fmt(&self, fmt: &mut biome_console::fmt::Formatter) -> std::io::Result<()> {
match self {
FixKind::None => fmt.write_str("None"),
FixKind::Safe => fmt.write_str("Safe"),
FixKind::Unsafe => fmt.write_str("Unsafe"),
}
}
}

#[derive(Debug, Clone, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
#[derive(Debug, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub enum RuleSource {
/// Rules from [Rust Clippy](https://rust-lang.github.io/rust-clippy/master/index.html)
Clippy(&'static str),
Expand Down Expand Up @@ -232,8 +246,9 @@ impl RuleSource {
}
}

#[derive(Debug, Default, Clone, Copy, Serialize)]
#[serde(rename_all = "camelCase")]
#[derive(Debug, Default, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub enum RuleSourceKind {
/// The rule implements the same logic of the source
#[default]
Expand Down Expand Up @@ -262,7 +277,7 @@ impl RuleMetadata {
docs,
language,
recommended: false,
fix_kind: None,
fix_kind: FixKind::None,
sources: &[],
source_kind: None,
}
Expand All @@ -279,7 +294,7 @@ impl RuleMetadata {
}

pub const fn fix_kind(mut self, kind: FixKind) -> Self {
self.fix_kind = Some(kind);
self.fix_kind = kind;
self
}

Expand Down
14 changes: 13 additions & 1 deletion crates/biome_analyze/src/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,18 @@ where
fn actions(&self) -> AnalyzerActionIter<RuleLanguage<R>> {
let globals = self.options.globals();

let configured_applicability = if let Some(fix_kind) = self.options.rule_fix_kind::<R>() {
match fix_kind {
crate::FixKind::None => {
// The action is disabled
return AnalyzerActionIter::new(vec![]);
}
crate::FixKind::Safe => Some(Applicability::Always),
crate::FixKind::Unsafe => Some(Applicability::MaybeIncorrect),
}
} else {
None
};
let options = self.options.rule_options::<R>().unwrap_or_default();
let ctx = RuleContext::new(
&self.query_result,
Expand All @@ -384,8 +396,8 @@ where
if let Some(action) = R::action(&ctx, &self.state) {
actions.push(AnalyzerAction {
rule_name: Some((<R::Group as RuleGroup>::NAME, R::METADATA.name)),
applicability: configured_applicability.unwrap_or(action.applicability),
category: action.category,
applicability: action.applicability,
mutation: action.mutation,
message: action.message,
});
Expand Down
21 changes: 12 additions & 9 deletions crates/biome_cli/src/commands/explain.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use biome_analyze::RuleMetadata;
use biome_analyze::{FixKind, RuleMetadata};
use biome_console::{markup, ConsoleExt};
use biome_service::documentation::Doc;

Expand All @@ -10,14 +10,17 @@ fn print_rule(session: CliSession, metadata: &RuleMetadata) {
"# "{metadata.name}"\n"
});

if let Some(kind) = &metadata.fix_kind {
session.app.console.log(markup! {
"Fix is "{kind}".\n"
});
} else {
session.app.console.log(markup! {
"No fix available.\n"
});
match metadata.fix_kind {
FixKind::None => {
session.app.console.log(markup! {
"No fix available.\n"
});
}
kind => {
session.app.console.log(markup! {
"Fix is "{kind}".\n"
});
}
}

let docs = metadata
Expand Down
5 changes: 5 additions & 0 deletions crates/biome_cli/src/execute/migrate/eslint_to_biome.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ fn migrate_eslint_rule(
group.no_restricted_globals = Some(biome_config::RuleConfiguration::WithOptions(
biome_config::RuleWithOptions {
level: severity.into(),
fix: None,
options: Box::new(no_restricted_globals::RestrictedGlobalsOptions {
denied_globals: globals.collect(),
}),
Expand All @@ -225,6 +226,7 @@ fn migrate_eslint_rule(
group.use_valid_aria_role = Some(biome_config::RuleConfiguration::WithOptions(
biome_config::RuleWithOptions {
level: severity.into(),
fix: None,
options: Box::new((*rule_options).into()),
},
));
Expand All @@ -239,6 +241,7 @@ fn migrate_eslint_rule(
Some(biome_config::RuleConfiguration::WithOptions(
biome_config::RuleWithOptions {
level: severity.into(),
fix: None,
options: rule_options.into(),
},
));
Expand All @@ -255,6 +258,7 @@ fn migrate_eslint_rule(
group.use_naming_convention = Some(biome_config::RuleConfiguration::WithOptions(
biome_config::RuleWithOptions {
level: severity.into(),
fix: None,
options: options.into(),
},
));
Expand All @@ -266,6 +270,7 @@ fn migrate_eslint_rule(
group.use_filenaming_convention = Some(
biome_config::RuleConfiguration::WithOptions(biome_config::RuleWithOptions {
level: conf.severity().into(),
fix: None,
options: Box::new(conf.option_or_default().into()),
}),
);
Expand Down
Loading

0 comments on commit 2e8ad62

Please sign in to comment.