Skip to content

Commit

Permalink
feat: better assist (#4508)
Browse files Browse the repository at this point in the history
Co-authored-by: Arend van Beelen jr. <[email protected]>
Co-authored-by: Carson McManus <[email protected]>
  • Loading branch information
3 people committed Nov 27, 2024
1 parent 39edbf2 commit f281e8a
Show file tree
Hide file tree
Showing 80 changed files with 1,653 additions and 1,029 deletions.
55 changes: 55 additions & 0 deletions .changeset/biome_assist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
cli: minor
---

# Biome assist

Biome assist is a new feature of the Biome analyzer. The assist is meant to provide **actions**. Actions differ from linter rules in that they aren't meant to signal errors.

The assist will provide code actions that users can opt into via configuration or via IDEs/editors, using the Language Server Protocol.

The assist **is enabled by default**. However, you can turn if off via configuration:

```json
{
"assist": {
"enabled": false
}
}
```

You can turn on the actions that you want to use in your configuration. For example, you can enable the `useSortedKeys` action like this:

```json
{
"assist": {
"actions": {
"source": {
"useSortedKeys": "on"
}
}
}
}
```

Alternatively, IDE/editor users can decide which action to apply on save *directly from the editor settings*, as long as the assist is enabled.

For example, in VS Code you can apply the `useSortedKeys` action when saving a file by adding the following snippet in `settings.json`:

```json
{
"editor.codeActionsOnSave": {
"source.biome.useSortedKeys": "explicit"
}
}
```

In Zed, you can achieve the same by adding the following snippet in `~/.config/zed/settings.json`:

```json
{
"code_actions_on_format": {
"source.biome.useSortedKeys": true
}
}
```
37 changes: 37 additions & 0 deletions .changeset/code_actions_via_ideeditor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
cli: minor
---

# Code actions via IDE/editor

Biome users can now configure code actions from linter rules as well as assist actions directly in the settings of their IDE/editor.

For example, let's consider the lint rule [`noSwitchDeclarations`](https://biomejs.dev/linter/rules/no-switch-declarations/), which has an unsafe fix.
Previously, if you wanted to use this rule, you were "forced" to enable it via configuration, and if you wanted to apply its fix when you saved a file, you were forced to mark the fix as safe:

```json
{
"linter": {
"rules": {
"correctness": {
"noSwitchDeclarations": {
"level": "error",
"fix": "safe"
}
}
}
}
}
```

Now, you can benefit from the code action without making the fix safe for the entire project. IDEs and editors that are LSP compatible allow to list a series of "filters" or code actions that can be applied on save. In the case of VS Code, you will need to add the following snippet in the `settings.json`:

```json
{
"editor.codeActionsOnSave": {
"quickfix.biome.correctness.noSwitchDeclarations": "explicit"
}
}
```

Upon save, Biome will inform the editor the apply the code action of the rule `noSwitchDeclarations`.
18 changes: 9 additions & 9 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,26 @@
/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs linguist-generated=true text=auto eol=lf
/crates/biome_configuration/src/generated.rs linguist-generated=true text=auto eol=lf
/crates/biome_configuration/src/analyzer/linter/rules.rs linguist-generated=true text=auto eol=lf
/crates/biome_configuration/src/analyzer/assists/actions.rs linguist-generated=true text=auto eol=lf
/crates/biome_configuration/src/analyzer/assist/actions.rs linguist-generated=true text=auto eol=lf
/crates/biome_configuration/src/analyzer/parse/rules.rs linguist-generated=true text=auto eol=lf
# GraphQL
/crates/biome_graphql_analyze/src/{lint,assists,syntax}.rs linguist-generated=true text=auto eol=lf
/crates/biome_graphql_analyze/src/{lint,assists,syntax}/*.rs linguist-generated=true text=auto eol=lf
/crates/biome_graphql_analyze/src/{lint,assist,syntax}.rs linguist-generated=true text=auto eol=lf
/crates/biome_graphql_analyze/src/{lint,assist,syntax}/*.rs linguist-generated=true text=auto eol=lf
/crates/biome_graphql_analyze/src/options.rs linguist-generated=true text=auto eol=lf
/crates/biome_graphql_analyze/src/registry.rs linguist-generated=true text=auto eol=lf
# CSS
/crates/biome_css_analyze/src/{lint,assists,syntax}.rs linguist-generated=true text=auto eol=lf
/crates/biome_css_analyze/src/{lint,assists,syntax}/*.rs linguist-generated=true text=auto eol=lf
/crates/biome_css_analyze/src/{lint,assist,syntax}.rs linguist-generated=true text=auto eol=lf
/crates/biome_css_analyze/src/{lint,assist,syntax}/*.rs linguist-generated=true text=auto eol=lf
/crates/biome_css_analyze/src/options.rs linguist-generated=true text=auto eol=lf
/crates/biome_css_analyze/src/registry.rs linguist-generated=true text=auto eol=lf
# JSON
/crates/biome_json_analyze/src/{lint,assists,syntax}.rs linguist-generated=true text=auto eol=lf
/crates/biome_json_analyze/src/{lint,assists,syntax}/*.rs linguist-generated=true text=auto eol=lf
/crates/biome_json_analyze/src/{lint,assist,syntax}.rs linguist-generated=true text=auto eol=lf
/crates/biome_json_analyze/src/{lint,assist,syntax}/*.rs linguist-generated=true text=auto eol=lf
/crates/biome_js_analyze/src/options.rs linguist-generated=true text=auto eol=lf
/crates/biome_js_analyze/src/registry.rs linguist-generated=true text=auto eol=lf
# JS
/crates/biome_js_analyze/src/{lint,assists,syntax}.rs linguist-generated=true text=auto eol=lf
/crates/biome_js_analyze/src/{lint,assists,syntax}/*.rs linguist-generated=true text=auto eol=lf
/crates/biome_js_analyze/src/{lint,assist,syntax}.rs linguist-generated=true text=auto eol=lf
/crates/biome_js_analyze/src/{lint,assist,syntax}/*.rs linguist-generated=true text=auto eol=lf
/crates/biome_js_analyze/src/options.rs linguist-generated=true text=auto eol=lf
/crates/biome_js_analyze/src/registry.rs linguist-generated=true text=auto eol=lf
# Grit
Expand Down
12 changes: 6 additions & 6 deletions crates/biome_analyze/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ impl ActionCategory {
Cow::Borrowed("source.organizeImports.biome")
}
ActionCategory::Source(SourceActionKind::Other(tag)) => {
Cow::Owned(format!("source.{tag}.biome"))
Cow::Owned(format!("source.biome.{tag}"))
}

ActionCategory::Other(other_action) => match other_action {
Expand Down Expand Up @@ -214,7 +214,7 @@ pub enum SourceActionKind {
pub(crate) enum Categories {
Syntax = 1 << RuleCategory::Syntax as u8,
Lint = 1 << RuleCategory::Lint as u8,
Action = 1 << RuleCategory::Action as u8,
Assist = 1 << RuleCategory::Action as u8,
Transformation = 1 << RuleCategory::Transformation as u8,
}

Expand Down Expand Up @@ -260,7 +260,7 @@ impl From<RuleCategory> for RuleCategories {
match input {
RuleCategory::Syntax => RuleCategories(BitFlags::from_flag(Categories::Syntax)),
RuleCategory::Lint => RuleCategories(BitFlags::from_flag(Categories::Lint)),
RuleCategory::Action => RuleCategories(BitFlags::from_flag(Categories::Action)),
RuleCategory::Action => RuleCategories(BitFlags::from_flag(Categories::Assist)),
RuleCategory::Transformation => {
RuleCategories(BitFlags::from_flag(Categories::Transformation))
}
Expand All @@ -284,7 +284,7 @@ impl serde::Serialize for RuleCategories {
flags.push(RuleCategory::Lint);
}

if self.0.contains(Categories::Action) {
if self.0.contains(Categories::Assist) {
flags.push(RuleCategory::Action);
}

Expand Down Expand Up @@ -370,8 +370,8 @@ impl RuleCategoriesBuilder {
self
}

pub fn with_action(mut self) -> Self {
self.flags.insert(Categories::Action);
pub fn with_assist(mut self) -> Self {
self.flags.insert(Categories::Assist);
self
}

Expand Down
51 changes: 44 additions & 7 deletions crates/biome_analyze/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,34 +55,71 @@ impl AnalyzerRules {
#[derive(Debug, Default)]
pub struct AnalyzerConfiguration {
/// A list of rules and their options
pub rules: AnalyzerRules,
pub(crate) rules: AnalyzerRules,

/// A collections of bindings that the analyzers should consider as "external".
///
/// For example, lint rules should ignore them.
pub globals: Vec<Box<str>>,
globals: Vec<Box<str>>,

/// Allows to choose a different quote when applying fixes inside the lint rules
pub preferred_quote: PreferredQuote,
preferred_quote: PreferredQuote,

/// Indicates the type of runtime or transformation used for interpreting JSX.
pub jsx_runtime: Option<JsxRuntime>,
jsx_runtime: Option<JsxRuntime>,
}

impl AnalyzerConfiguration {
pub fn with_rules(mut self, rules: AnalyzerRules) -> Self {
self.rules = rules;
self
}

pub fn with_globals(mut self, globals: Vec<Box<str>>) -> Self {
self.globals = globals;
self
}

pub fn with_jsx_runtime(mut self, jsx_runtime: JsxRuntime) -> Self {
self.jsx_runtime = Some(jsx_runtime);
self
}

pub fn with_preferred_quote(mut self, preferred_quote: PreferredQuote) -> Self {
self.preferred_quote = preferred_quote;
self
}
}

/// A set of information useful to the analyzer infrastructure
#[derive(Debug, Default)]
pub struct AnalyzerOptions {
/// A data structured derived from the [`biome.json`] file
pub configuration: AnalyzerConfiguration,
configuration: AnalyzerConfiguration,

/// The file that is being analyzed
pub file_path: PathBuf,
pub(crate) file_path: PathBuf,

/// Suppression reason used when applying a suppression code action
pub suppression_reason: Option<String>,
pub(crate) suppression_reason: Option<String>,
}

impl AnalyzerOptions {
pub fn with_file_path(mut self, file_path: impl Into<PathBuf>) -> Self {
self.file_path = file_path.into();
self
}

pub fn with_configuration(mut self, analyzer_configuration: AnalyzerConfiguration) -> Self {
self.configuration = analyzer_configuration;
self
}

pub fn with_suppression_reason(mut self, reason: Option<String>) -> Self {
self.suppression_reason = reason;
self
}

pub fn globals(&self) -> Vec<&str> {
self.configuration
.globals
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_analyze/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ impl<L: Language + Default> RegistryRule<L> {
params.root,
params.services,
&globals,
&params.options.file_path,
params.options.file_path.as_path(),
&options,
preferred_quote,
jsx_runtime,
Expand Down
6 changes: 3 additions & 3 deletions crates/biome_analyze/src/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ macro_rules! declare_source_rule {
/// This macro returns the corresponding [ActionCategory] to use inside the [RuleAction]
#[allow(unused_macros)]
macro_rules! rule_action_category {
() => { ActionCategory::Source(SourceActionKind::Other(Cow::Borrowed(concat!($language, ".", $name) ))) };
() => { ActionCategory::Source(SourceActionKind::Other(Cow::Borrowed($name))) };
}
};
}
Expand Down Expand Up @@ -611,7 +611,7 @@ macro_rules! declare_lint_group {
/// This macro is used by the codegen script to declare an analyzer rule group,
/// and implement the [RuleGroup] trait for it
#[macro_export]
macro_rules! declare_assists_group {
macro_rules! declare_assist_group {
( $vis:vis $id:ident { name: $name:tt, rules: [ $( $( $rule:ident )::* , )* ] } ) => {
$vis enum $id {}

Expand All @@ -636,7 +636,7 @@ macro_rules! declare_assists_group {
// "lint" prefix, the name of this group, and the rule name argument
#[allow(unused_macros)]
macro_rules! group_category {
( $rule_name:tt ) => { $crate::category_concat!( "assists", $name, $rule_name ) };
( $rule_name:tt ) => { $crate::category_concat!( "assist", $name, $rule_name ) };
}

// Re-export the macro for child modules, so `declare_rule!` can access
Expand Down
6 changes: 3 additions & 3 deletions crates/biome_analyze/src/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ where
self.root,
self.services,
&globals,
&self.options.file_path,
self.options.file_path.as_path(),
&options,
preferred_quote,
self.options.jsx_runtime(),
Expand Down Expand Up @@ -393,7 +393,7 @@ where
self.root,
self.services,
&globals,
&self.options.file_path,
self.options.file_path.as_path(),
&options,
self.options.preferred_quote(),
self.options.jsx_runtime(),
Expand Down Expand Up @@ -455,7 +455,7 @@ where
self.root,
self.services,
&globals,
&self.options.file_path,
self.options.file_path.as_path(),
&options,
self.options.preferred_quote(),
self.options.jsx_runtime(),
Expand Down
14 changes: 7 additions & 7 deletions crates/biome_cli/src/commands/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::{determine_fix_file_mode, FixFileModeOptions, LoadEditorConfig};
use crate::cli_options::CliOptions;
use crate::commands::{get_files_to_process_with_cli_options, CommandRunner};
use crate::{CliDiagnostic, Execution, TraversalMode};
use biome_configuration::analyzer::assists::PartialAssistsConfiguration;
use biome_configuration::analyzer::assist::PartialAssistConfiguration;
use biome_configuration::{
organize_imports::PartialOrganizeImports, PartialConfiguration, PartialFormatterConfiguration,
PartialLinterConfiguration,
Expand All @@ -23,7 +23,7 @@ pub(crate) struct CheckCommandPayload {
pub(crate) formatter_enabled: Option<bool>,
pub(crate) linter_enabled: Option<bool>,
pub(crate) organize_imports_enabled: Option<bool>,
pub(crate) assists_enabled: Option<bool>,
pub(crate) assist_enabled: Option<bool>,
pub(crate) staged: bool,
pub(crate) changed: bool,
pub(crate) since: Option<String>,
Expand Down Expand Up @@ -81,12 +81,12 @@ impl CommandRunner for CheckCommandPayload {
organize_imports.enabled = self.organize_imports_enabled;
}

let assists = fs_configuration
.assists
.get_or_insert_with(PartialAssistsConfiguration::default);
let assist = fs_configuration
.assist
.get_or_insert_with(PartialAssistConfiguration::default);

if self.assists_enabled.is_some() {
assists.enabled = self.assists_enabled;
if self.assist_enabled.is_some() {
assist.enabled = self.assist_enabled;
}

if let Some(mut configuration) = self.configuration.clone() {
Expand Down
14 changes: 7 additions & 7 deletions crates/biome_cli/src/commands/ci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::changed::get_changed_files;
use crate::cli_options::CliOptions;
use crate::commands::{CommandRunner, LoadEditorConfig};
use crate::{CliDiagnostic, Execution};
use biome_configuration::analyzer::assists::PartialAssistsConfiguration;
use biome_configuration::analyzer::assist::PartialAssistConfiguration;
use biome_configuration::{organize_imports::PartialOrganizeImports, PartialConfiguration};
use biome_configuration::{PartialFormatterConfiguration, PartialLinterConfiguration};
use biome_console::Console;
Expand All @@ -16,7 +16,7 @@ pub(crate) struct CiCommandPayload {
pub(crate) formatter_enabled: Option<bool>,
pub(crate) linter_enabled: Option<bool>,
pub(crate) organize_imports_enabled: Option<bool>,
pub(crate) assists_enabled: Option<bool>,
pub(crate) assist_enabled: Option<bool>,
pub(crate) paths: Vec<OsString>,
pub(crate) configuration: Option<PartialConfiguration>,
pub(crate) changed: bool,
Expand Down Expand Up @@ -76,12 +76,12 @@ impl CommandRunner for CiCommandPayload {
organize_imports.enabled = self.organize_imports_enabled;
}

let assists = fs_configuration
.assists
.get_or_insert_with(PartialAssistsConfiguration::default);
let assist = fs_configuration
.assist
.get_or_insert_with(PartialAssistConfiguration::default);

if self.assists_enabled.is_some() {
assists.enabled = self.assists_enabled;
if self.assist_enabled.is_some() {
assist.enabled = self.assist_enabled;
}

if let Some(mut configuration) = self.configuration.clone() {
Expand Down
Loading

0 comments on commit f281e8a

Please sign in to comment.