Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions .changeset/shaggy-grapes-act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@biomejs/biome": minor
---

Added `ignore` option to the [noUnusedVariables](https://biomejs.dev/linter/rules/no-unused-variables/) rule. The option allows excluding identifiers by providing a list of ignored names. It also allows excluding kinds of identifiers from this rule entirely, which may be useful when loading classes dynamically.

For example, unused classes as well as all unused variables, functions, etc. called "unused" may be ignored entirely with the following configuration:

```json
{
"ignore": {
"*": ["unused"],
"class": ["*"]
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,9 @@ use biome_diagnostics::Severity;
use biome_js_semantic::{ReferencesExtensions, SemanticModel};
use biome_js_syntax::binding_ext::{AnyJsBindingDeclaration, AnyJsIdentifierBinding};
use biome_js_syntax::declaration_ext::is_in_ambient_context;
use biome_js_syntax::{
AnyJsExpression, EmbeddingKind, JsClassExpression, JsFileSource, JsForStatement,
JsFunctionExpression, JsIdentifierExpression, JsModuleItemList, JsSequenceExpression,
JsSyntaxKind, JsSyntaxNode, TsConditionalType, TsDeclarationModule, TsInferType,
TsInterfaceDeclaration, TsTypeAliasDeclaration,
};
use biome_js_syntax::{AnyJsExpression, EmbeddingKind, JsClassExpression, JsFileSource, JsForStatement, JsFunctionExpression, JsIdentifierExpression, JsModuleItemList, JsSequenceExpression, JsSyntaxKind, JsSyntaxNode, TsConditionalType, TsDeclarationModule, TsInferType, TsInterfaceDeclaration, TsTypeAliasDeclaration};
use biome_rowan::{AstNode, BatchMutationExt, Direction, SyntaxResult};
use biome_rule_options::no_unused_variables::NoUnusedVariablesOptions;
use biome_rule_options::no_unused_variables::{NoUnusedVariablesOptions, NoUnusedVariablesOptionsIgnore};

declare_lint_rule! {
/// Disallow unused variables.
Expand Down Expand Up @@ -117,7 +112,7 @@ declare_lint_rule! {
///
/// Default: `true`
///
/// #### Example
/// If this option is set to `false`, unused rest siblings either have to be renamed or removed.
///
/// ```json,options
/// {
Expand All @@ -138,6 +133,57 @@ declare_lint_rule! {
/// const { brand: _, ...other } = car;
/// console.log(other);
/// ```
///
/// ### `ignore`
///
/// An object that allows excluding matching identifiers from this rule.
///
/// Each key may specify an array of identifiers to ignore which are case-sensitive matches.
///
/// The special string `"*"` can serve two purposes:
/// - As a **key** it refers to every kind of identifier.
/// - As a **value** it may be used to match all identifiers in the respective group, effectively disabling this rule for that group.
///
/// Allowed keys:
///
/// - `"*"`: Applies to all identifiers
/// - `"class"`: Applies to class names
/// - `"function"`: Applies to function names
/// - `"interface"`: Applies to interface names
/// - `"typeAlias"`: Applies to type aliases
/// - `"typeParameter"`: Applies to type parameters
/// - `"variable"`: Applies to variable names
///
/// Default: `{}` (no variables are excluded)
///
/// For example, you can exclude all unused identifiers named `ignored` regardless of their kind,
/// all unused classes named `IgnoredClass`, and all unused functions with the following
/// configuration.
///
/// A variable named `unusedVariable` is still flagged as unused, and so is a class named
/// `UnusedClass` since they don't fall under the exceptions.
///
Comment thread
ematipico marked this conversation as resolved.
/// ```json,options
/// {
/// "options": {
/// "ignore": {
/// "*": ["ignored"],
/// "class": ["IgnoredClass"],
/// "function": ["*"]
/// }
/// }
/// }
/// ```
///
/// ```js,expect_diagnostic,ignore,use_options
/// const ignored = 0;
/// class IgnoredClass {}
/// function ignoredFunction() {}
///
/// const unusedVariable = 0;
/// class UnusedClass {}
/// ```
///
pub NoUnusedVariables {
version: "1.0.0",
name: "noUnusedVariables",
Expand Down Expand Up @@ -326,14 +372,18 @@ impl Rule for NoUnusedVariables {
.find_map(JsModuleItemList::cast)
{
// A declaration file without top-level exports and imports is a global declaration file.
// All top-level types and variables are available in every files of the project.
// All top-level types and variables are available in every file of the project.
// Thus, it is ok if top-level types are not used locally.
let is_top_level = items.parent::<TsDeclarationModule>().is_some();
if is_top_level && items.into_iter().all(|x| x.as_any_js_statement().is_some()) {
return None;
}
}

if is_ignored(binding, ctx.options()).unwrap_or_default() {
return None;
}

let binding_name = binding.name_token().ok()?;
let binding_name = binding_name.text_trimmed();

Expand Down Expand Up @@ -471,6 +521,32 @@ impl Rule for NoUnusedVariables {
}
}

/// Returns `true` if `binding` is considered as ignored by the user.
pub fn is_ignored(binding: &AnyJsIdentifierBinding, options: &NoUnusedVariablesOptions) -> Option<bool> {
let binding_name = binding.name_token().ok()?;
let binding_name = binding_name.text_trimmed();

let ignore_options = options.ignore();
let is_all_ignored = ignore_options.all.unwrap_or_default().iter().any(|ignore| &**ignore == NoUnusedVariablesOptionsIgnore::IGNORE_ALL || &**ignore == binding_name);

if is_all_ignored {
return Some(true);
}

let specific_ignores = match binding.syntax().parent()?.kind() {
JsSyntaxKind::JS_FUNCTION_DECLARATION => ignore_options.function,
JsSyntaxKind::JS_CLASS_DECLARATION => ignore_options.class,
JsSyntaxKind::TS_INTERFACE_DECLARATION => ignore_options.interface,
JsSyntaxKind::TS_TYPE_ALIAS_DECLARATION => ignore_options.type_alias,
JsSyntaxKind::TS_TYPE_PARAMETER => ignore_options.type_parameter,
_ => ignore_options.variable,
};
Comment thread
ematipico marked this conversation as resolved.

let is_specific_ignored = specific_ignores.unwrap_or_default().iter().any(|ignore| &**ignore == NoUnusedVariablesOptionsIgnore::IGNORE_ALL || &**ignore == binding_name);

Some(is_specific_ignored)
}

/// Returns `true` if `binding` is considered as unused.
pub fn is_unused(model: &SemanticModel, binding: &AnyJsIdentifierBinding) -> bool {
if matches!(binding, AnyJsIdentifierBinding::TsLiteralEnumMemberName(_)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// With ignore: { "*": ["Ignored"] }, variables should be reported that don't match the pattern
const unusedVariable = 0;
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
assertion_line: 155
expression: invalidIgnoreAllPattern.js
---
# Input
```js
// With ignore: { "*": ["Ignored"] }, variables should be reported that don't match the pattern
const unusedVariable = 0;

```

# Diagnostics
```
invalidIgnoreAllPattern.js:2:7 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━

! This variable unusedVariable is unused.

1 │ // With ignore: { "*": ["Ignored"] }, variables should be reported that don't match the pattern
> 2 │ const unusedVariable = 0;
│ ^^^^^^^^^^^^^^
3 │

i Unused variables are often the result of typos, incomplete refactors, or other sources of bugs.

i Unsafe fix: If this is intentional, prepend unusedVariable with an underscore.

1 1 │ // With ignore: { "*": ["Ignored"] }, variables should be reported that don't match the pattern
2 │ - const·unusedVariable·=·0;
2 │ + const·_unusedVariable·=·0;
3 3 │


```
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json",
"linter": {
"rules": {
"correctness": {
"noUnusedVariables": {
"level": "error",
"options": {"ignore": {"*": ["Ignored"]}}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// With ignore: { "class": ["ignored"] }, variables should still be reported even if they match the pattern
const ignored = 0;
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
assertion_line: 155
expression: invalidIgnoreClassPattern.js
---
# Input
```js
// With ignore: { "class": ["ignored"] }, variables should still be reported even if they match the pattern
const ignored = 0;

```

# Diagnostics
```
invalidIgnoreClassPattern.js:2:7 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━

! This variable ignored is unused.

1 │ // With ignore: { "class": ["ignored"] }, variables should still be reported even if they match the pattern
> 2 │ const ignored = 0;
│ ^^^^^^^
3 │

i Unused variables are often the result of typos, incomplete refactors, or other sources of bugs.

i Unsafe fix: If this is intentional, prepend ignored with an underscore.

1 1 │ // With ignore: { "class": ["ignored"] }, variables should still be reported even if they match the pattern
2 │ - const·ignored·=·0;
2 │ + const·_ignored·=·0;
3 3 │


```
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json",
"linter": {
"rules": {
"correctness": {
"noUnusedVariables": {
"level": "error",
"options": {"ignore": {"class": ["ignored"]}}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// With ignore: { "class": ["*"] }, all other language constructs should still be reported if they are unused
const unusedVariable = 0;
function unusedFunction(unusedParameter) {}
class UnusedClass {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
assertion_line: 155
expression: invalidIgnoreWildcardPattern.js
---
# Input
```js
// With ignore: { "class": ["*"] }, all other language constructs should still be reported if they are unused
const unusedVariable = 0;
function unusedFunction(unusedParameter) {}
class UnusedClass {}

```

# Diagnostics
```
invalidIgnoreWildcardPattern.js:2:7 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━

! This variable unusedVariable is unused.

1 │ // With ignore: { "class": ["*"] }, all other language constructs should still be reported if they are unused
> 2 │ const unusedVariable = 0;
│ ^^^^^^^^^^^^^^
3 │ function unusedFunction(unusedParameter) {}
4 │ class UnusedClass {}

i Unused variables are often the result of typos, incomplete refactors, or other sources of bugs.

i Unsafe fix: If this is intentional, prepend unusedVariable with an underscore.

1 1 │ // With ignore: { "class": ["*"] }, all other language constructs should still be reported if they are unused
2 │ - const·unusedVariable·=·0;
2 │ + const·_unusedVariable·=·0;
3 3 │ function unusedFunction(unusedParameter) {}
4 4 │ class UnusedClass {}


```

```
invalidIgnoreWildcardPattern.js:3:10 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━

! This function unusedFunction is unused.

1 │ // With ignore: { "class": ["*"] }, all other language constructs should still be reported if they are unused
2 │ const unusedVariable = 0;
> 3 │ function unusedFunction(unusedParameter) {}
│ ^^^^^^^^^^^^^^
4 │ class UnusedClass {}
5 │

i Unused variables are often the result of typos, incomplete refactors, or other sources of bugs.

i Unsafe fix: If this is intentional, prepend unusedFunction with an underscore.

1 1 │ // With ignore: { "class": ["*"] }, all other language constructs should still be reported if they are unused
2 2 │ const unusedVariable = 0;
3 │ - function·unusedFunction(unusedParameter)·{}
3 │ + function·_unusedFunction(unusedParameter)·{}
4 4 │ class UnusedClass {}
5 5 │


```
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json",
"linter": {
"rules": {
"correctness": {
"noUnusedVariables": {
"level": "error",
"options": {"ignore": {"class": ["*"]}}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* should not generate diagnostics */

function a(a: number) {}
a(1);

function b({a, b}: {a: number, b: string}) {}
b({a: 1, b: "2"});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
assertion_line: 155
expression: validFunctionParameter.ts
---
# Input
```ts
/* should not generate diagnostics */

function a(a: number) {}
a(1);

function b({a, b}: {a: number, b: string}) {}
b({a: 1, b: "2"});

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* should not generate diagnostics */

// With ignore: { "*": ["ignored"] }, unused variables named ignored should be ignored
const ignored = 1;
const secondVar = 2;
console.log(secondVar);

// With ignore: { "class": ["IgnoredClass"] }, unused classes named IgnoredClass should be ignored
class IgnoredClass {}

// With ignore: { "function": ["*"] }, all unused functions should be ignored
function unusedFunction() {}
function anotherUnusedFunction() {}
Loading