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

feat(analyzer): top level suppression #4306

Merged
merged 13 commits into from
Oct 21, 2024
Merged
34 changes: 34 additions & 0 deletions .changeset/new_top_level_suppression_for_the_analyzer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
cli: minor
---

# New top-level suppression for the analyzer

The Biome analyzer now supports a new top-level suppression. These suppression have to be placed at the top of the file, and they must be followed by two newlines (`\n\n\`).

The analyzer rules specified inside the block comment will be suppressed for the whole file.

In the example, we suppress the rules `lint/style/useConst` and `lint/suspicious/noDebugger` for the whole file:

```js
// main.js
/**
* biome-ignore lint/style/useConst: i like let
* biome-ignore lint/suspicious/noDebugger: needed now
*/

let path = "/path";
let _tmp = undefined;
debugger
```

In this other example, we suppress `lint/suspicious/noEmptyBlock` for a whole CSS file:

```css
/**
/* biome-ignore lint/suspicious/noEmptyBlock: it's fine to have empty blocks
*/

a {}
span {}
```
13 changes: 13 additions & 0 deletions .changeset/remove_support_for_legacy_suppressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
cli: major
---

# Remove support for legacy suppressions

Biome used to support "legacy suppressions" that looked like this:

```js
// biome-ignore lint(style/useWhile): reason
```

This format is no longer supported.
35 changes: 35 additions & 0 deletions .changeset/the_action_quickfixsuppressrule_is_removed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
cli: major
---

# Remove the code action `quickfix.suppressRule`

The code action `quickfix.suppressRule` was removed in favour of two new code actions:

- `quickfix.suppressRule.inline.biome`: a code action that adds a suppression comment for each violation.
- `quickfix.suppressRule.topLevel.biome`: a code action that adds a suppression comment at the top of the file which suppresses a rule for the whole file.


Given the following code
```js
let foo = "one";
debugger
```

The code action `quickfix.suppressRule.inline.biome` will result in the following code:
```js
// biome-ignore lint/style/useConst: <explanation>
let foo = "one";
// biome-ignore lint/suspicious/noDebugger: <explanation>
debugger
```

The code action `quickfix.suppressRule.topLevel.biome`, instead, will result in the following code:
```js
/** biome-ignore lint/suspicious/noDebugger: <explanation> */
/** biome-ignore lint/style/useConst: <explanation> */

let foo = "one";
debugger;
```

3 changes: 2 additions & 1 deletion Cargo.lock

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

34 changes: 30 additions & 4 deletions crates/biome_analyze/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ pub enum RuleCategory {
}

/// Actions that suppress rules should start with this string
pub const SUPPRESSION_ACTION_CATEGORY: &str = "quickfix.suppressRule";
pub const SUPPRESSION_INLINE_ACTION_CATEGORY: &str = "quickfix.suppressRule.inline";
pub const SUPPRESSION_TOP_LEVEL_ACTION_CATEGORY: &str = "quickfix.suppressRule.topLevel";

/// The category of a code action, this type maps directly to the
/// [CodeActionKind] type in the Language Server Protocol specification
Expand All @@ -48,7 +49,21 @@ pub enum ActionCategory {
Source(SourceActionKind),
/// This action is using a base kind not covered by any of the previous
/// variants
Other(Cow<'static, str>),
Other(OtherActionCategory),
}

#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)
)]
pub enum OtherActionCategory {
/// Base kind for inline suppressions actions: `quickfix.suppressRule.inline.biome`
InlineSuppression,
/// Base kind for inline suppressions actions: `quickfix.suppressRule.topLevel.biome`
ToplevelSuppression,
/// Generic action that can't be mapped
Generic(Cow<'static, str>),
}

impl ActionCategory {
Expand All @@ -57,7 +72,7 @@ impl ActionCategory {
/// ## Examples
///
/// ```
/// use biome_analyze::{ActionCategory, RefactorKind};
/// use biome_analyze::{ActionCategory, RefactorKind, OtherActionCategory};
///
/// assert!(ActionCategory::QuickFix.matches("quickfix"));
/// assert!(!ActionCategory::QuickFix.matches("refactor"));
Expand All @@ -67,6 +82,9 @@ impl ActionCategory {
///
/// assert!(ActionCategory::Refactor(RefactorKind::Extract).matches("refactor"));
/// assert!(ActionCategory::Refactor(RefactorKind::Extract).matches("refactor.extract"));
///
/// assert!(ActionCategory::Other(OtherActionCategory::InlineSuppression).matches("quickfix.suppressRule.inline.biome"));
/// assert!(ActionCategory::Other(OtherActionCategory::ToplevelSuppression).matches("quickfix.suppressRule.topLevel.biome"));
/// ```
pub fn matches(&self, filter: &str) -> bool {
self.to_str().starts_with(filter)
Expand Down Expand Up @@ -102,7 +120,15 @@ impl ActionCategory {
Cow::Owned(format!("source.{tag}.biome"))
}

ActionCategory::Other(tag) => Cow::Owned(format!("{tag}.biome")),
ActionCategory::Other(other_action) => match other_action {
OtherActionCategory::InlineSuppression => {
Cow::Borrowed("quickfix.suppressRule.inline.biome")
}
OtherActionCategory::ToplevelSuppression => {
Cow::Borrowed("quickfix.suppressRule.topLevel.biome")
}
OtherActionCategory::Generic(tag) => Cow::Owned(format!("{tag}.biome")),
},
}
}
}
Expand Down
33 changes: 27 additions & 6 deletions crates/biome_analyze/src/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use biome_console::MarkupBuf;
use biome_console::{markup, MarkupBuf};
use biome_diagnostics::{
advice::CodeSuggestionAdvice, category, Advices, Category, Diagnostic, DiagnosticExt,
DiagnosticTags, Error, Location, Severity, Visit,
DiagnosticTags, Error, Location, LogCategory, Severity, Visit,
};
use biome_rowan::TextRange;
use std::borrow::Cow;
Expand Down Expand Up @@ -141,7 +141,7 @@ impl AnalyzerDiagnostic {

#[derive(Debug, Diagnostic, Clone)]
#[diagnostic(severity = Warning)]
pub struct SuppressionDiagnostic {
pub struct AnalyzerSuppressionDiagnostic {
#[category]
category: &'static Category,
#[location(span)]
Expand All @@ -151,9 +151,12 @@ pub struct SuppressionDiagnostic {
message: String,
#[tags]
tags: DiagnosticTags,

#[advice]
advice: SuppressionAdvice,
}

impl SuppressionDiagnostic {
impl AnalyzerSuppressionDiagnostic {
pub(crate) fn new(
category: &'static Category,
range: TextRange,
Expand All @@ -164,15 +167,33 @@ impl SuppressionDiagnostic {
range,
message: message.to_string(),
tags: DiagnosticTags::empty(),
advice: SuppressionAdvice::default(),
}
}

pub(crate) fn with_tags(mut self, tags: DiagnosticTags) -> Self {
self.tags |= tags;
pub(crate) fn note(mut self, message: impl Into<String>, range: impl Into<TextRange>) -> Self {
self.advice.messages.push((message.into(), range.into()));
Copy link
Member Author

Choose a reason for hiding this comment

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

self
}
}

#[derive(Debug, Default, Clone)]
struct SuppressionAdvice {
messages: Vec<(String, TextRange)>,
}

impl Advices for SuppressionAdvice {
fn record(&self, visitor: &mut dyn Visit) -> std::io::Result<()> {
for (message, range) in &self.messages {
visitor.record_log(LogCategory::Info, &markup! {{message}})?;
let location = Location::builder().span(range);

visitor.record_frame(location.build())?
}
Ok(())
}
}

/// Series of errors encountered when running rules on a file
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand Down
Loading
Loading