diff --git a/.changeset/add_new_option_javascriptjsxeverywhere.md b/.changeset/add_new_option_javascriptjsxeverywhere.md
new file mode 100644
index 000000000000..dd4844604b6f
--- /dev/null
+++ b/.changeset/add_new_option_javascriptjsxeverywhere.md
@@ -0,0 +1,9 @@
+---
+"@biomejs/biome": minor
+---
+
+Add new option `javascript.parser.jsxEverywhere`. This new option allows to control whether Biome should expect JSX syntax in `.js`/`.ts` files.
+
+When `jsxEverywhere` is set to `false`, having JSX syntax like `
` inside `.js`/`.ts` files will result in a **parsing error**.
+
+This option defaults to `true`.
diff --git a/.changeset/add_the_new_rule_nofloatingpromises.md b/.changeset/add_the_new_rule_nofloatingpromises.md
new file mode 100644
index 000000000000..3a80e6da2c43
--- /dev/null
+++ b/.changeset/add_the_new_rule_nofloatingpromises.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": minor
+---
+
+Add the new rule [`noFloatingPromises`](https://biomejs.dev/linter/rules/no-floating-promises).
diff --git a/.changeset/add_the_new_rule_noimportcycles.md b/.changeset/add_the_new_rule_noimportcycles.md
new file mode 100644
index 000000000000..b0296c588606
--- /dev/null
+++ b/.changeset/add_the_new_rule_noimportcycles.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": minor
+---
+
+Add the new rule [`noImportCycles`](https://biomejs.dev/linter/rules/no-import-cycles).
diff --git a/.changeset/add_the_new_rule_notsignorehttps.md b/.changeset/add_the_new_rule_notsignorehttps.md
new file mode 100644
index 000000000000..6999a8a84732
--- /dev/null
+++ b/.changeset/add_the_new_rule_notsignorehttps.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": minor
+---
+
+Add the new rule [`noTsIgnore`](https://biomejs.dev/linter/rules/no-ts-ignore).
diff --git a/.changeset/add_the_new_rule_nounwantedpolyfillio.md b/.changeset/add_the_new_rule_nounwantedpolyfillio.md
new file mode 100644
index 000000000000..e8fe8d8c1ce9
--- /dev/null
+++ b/.changeset/add_the_new_rule_nounwantedpolyfillio.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": minor
+---
+
+Add the new rule [`noUnwantedPolyfillio`](https://biomejs.dev/linter/rules/no-unwanted-polyfillio).
diff --git a/.changeset/add_whitespace_after_css_selecters_preceded_by_comment.md b/.changeset/add_whitespace_after_css_selecters_preceded_by_comment.md
new file mode 100644
index 000000000000..ff9ad381b107
--- /dev/null
+++ b/.changeset/add_whitespace_after_css_selecters_preceded_by_comment.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": patch
+---
+
+Fix [#5001](https://github.com/biomejs/biome/issues/5001), where the CSS formatter removes whitespace from selector preceded by a comment
diff --git a/.changeset/added_a_json_format_option_expandslists.md b/.changeset/added_a_json_format_option_expandslists.md
new file mode 100644
index 000000000000..67f1081bbaf5
--- /dev/null
+++ b/.changeset/added_a_json_format_option_expandslists.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": minor
+---
+
+Added a JSON format option `expand`. The option `json.formatter.expand` allows to enforce the formatting of arrays and objects on multiple lines, regardless of their length.
diff --git a/.changeset/better_control_over_linter_groups.md b/.changeset/better_control_over_linter_groups.md
new file mode 100644
index 000000000000..367420e91364
--- /dev/null
+++ b/.changeset/better_control_over_linter_groups.md
@@ -0,0 +1,30 @@
+---
+"@biomejs/biome": minor
+---
+
+Linter groups now accept new options to enable/disable all rules that belong to a group, and control the severity
+of the rules that belong to those groups.
+
+For example, you can downgrade the severity of rules that belong to `"style"` to emit `"info"` diagnostics:
+
+```json
+{
+ "linter": {
+ "rules": {
+ "style": "info"
+ }
+ }
+}
+```
+
+You can also enable all rules that belong to a group using the default severity of the rule using the `"on"` option:
+
+```json
+{
+ "linter": {
+ "rules": {
+ "complexity": "on"
+ }
+ }
+}
+```
diff --git a/.changeset/biome_assist.md b/.changeset/biome_assist.md
new file mode 100644
index 000000000000..97cd20cd5b71
--- /dev/null
+++ b/.changeset/biome_assist.md
@@ -0,0 +1,53 @@
+---
+"@biomejs/biome": minor
+---
+
+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
+ }
+}
+```
diff --git a/.changeset/biome_logs_a_warning_in_case_a_folder_contains_biomejson_and_biomejsonc_and_it_will_use_biomejson_by_default.md b/.changeset/biome_logs_a_warning_in_case_a_folder_contains_biomejson_and_biomejsonc_and_it_will_use_biomejson_by_default.md
new file mode 100644
index 000000000000..ac4d506100b2
--- /dev/null
+++ b/.changeset/biome_logs_a_warning_in_case_a_folder_contains_biomejson_and_biomejsonc_and_it_will_use_biomejson_by_default.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": patch
+---
+
+Biome logs a warning in case a folder contains `biome.json` and `biome.jsonc`, and it will use `biome.json` by default.
diff --git a/.changeset/biome_migrate_eslint_rule_overriding.md b/.changeset/biome_migrate_eslint_rule_overriding.md
new file mode 100644
index 000000000000..91fc475b789f
--- /dev/null
+++ b/.changeset/biome_migrate_eslint_rule_overriding.md
@@ -0,0 +1,38 @@
+---
+"@biomejs/biome": minor
+---
+
+Biome migrate eslint outputs a better overriding behavior.
+
+A Biome rule can have multiple ESLint equivalent rules.
+For example, [useLiteralKeys](https://biomejs.dev/linter/rules/use-literal-keys/) has two ESLint equivalent rules: [dot-notation](https://eslint.org/docs/latest/rules/dot-notation) and [@typescript-eslint/dot-notation](https://typescript-eslint.io/rules/dot-notation/).
+
+Previously, Biome wouldn't always enable a Biome rule even if one of its equivalent rules was enabled.
+Now Biome uses the higher severity level of all the equivalent ESLint rules to set the severity level of the Biome rule.
+
+The following ESLint configuration...
+
+```json
+{
+ "rules": {
+ "@typescript-eslint/dot-notation": "error",
+ "dot-notation": "off"
+ }
+}
+```
+
+...is now migrated to...
+
+```json
+{
+ "linter": {
+ "rules": {
+ "complexity": {
+ "useLiteralKeys": "error"
+ }
+ }
+ }
+}
+```
+
+...because `error` is higher than `off`.
diff --git a/.changeset/biome_now_resolves_globs_and_paths_from_the_configuration_before_paths_and_globs_were_resolved_from_the_working_directory.md b/.changeset/biome_now_resolves_globs_and_paths_from_the_configuration_before_paths_and_globs_were_resolved_from_the_working_directory.md
new file mode 100644
index 000000000000..1e653d38b4a8
--- /dev/null
+++ b/.changeset/biome_now_resolves_globs_and_paths_from_the_configuration_before_paths_and_globs_were_resolved_from_the_working_directory.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": major
+---
+
+Biome now resolves globs and paths from the configuration. Before, paths and globs were resolved from the working directory.
diff --git a/.changeset/code_actions_via_ideeditor.md b/.changeset/code_actions_via_ideeditor.md
new file mode 100644
index 000000000000..030e1720ae60
--- /dev/null
+++ b/.changeset/code_actions_via_ideeditor.md
@@ -0,0 +1,35 @@
+---
+"@biomejs/biome": minor
+---
+
+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`.
diff --git a/.changeset/config.json b/.changeset/config.json
new file mode 100644
index 000000000000..f3c8d5e988a3
--- /dev/null
+++ b/.changeset/config.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://unpkg.com/@changesets/config@3.0.5/schema.json",
+ "changelog": ["@changesets/changelog-github", { "repo": "withastro/astro" }],
+ "commit": false,
+ "fixed": [
+ [
+ "@biomejs/biome",
+ "@biomejs/cli-*",
+ "@biomejs/wasm-*"
+ ]
+ ],
+ "linked": [],
+ "access": "public",
+ "baseBranch": "main",
+ "updateInternalDependencies": "patch",
+ "ignore": [
+ "@biomejs/benchmark",
+ "@biomejs/aria-data",
+ "tailwindcss-config-analyzer"
+ ]
+}
diff --git a/.changeset/enable_rule_with_default_severity.md b/.changeset/enable_rule_with_default_severity.md
new file mode 100644
index 000000000000..18224faae141
--- /dev/null
+++ b/.changeset/enable_rule_with_default_severity.md
@@ -0,0 +1,33 @@
+---
+"@biomejs/biome": minor
+---
+
+You can now enable lint rules using the default severity suggested by Biome using the new variant `"on"`, when enabling a rule.
+
+For example, the default severity of the rule `style.noVar` is `error`, so you would use `"on"`, and then linting a code that uses `var`, will result in an error:
+
+```json
+{
+ "linter": {
+ "recommended": false,
+ "rules": {
+ "style": {
+ "noVar": "on"
+ }
+ }
+ }
+}
+```
+
+```js
+// main.js
+var name = "tobias"
+```
+
+The command `biome lint main.js` will result in an error due to the default severity assigned to `noVar`.
+
+Refer to the documentation page of each rule to know their suggested diagnostic severity, or use the command `biome explain `:
+
+```shell
+biome explain noVar
+```
diff --git a/.changeset/export_named_type_parser.md b/.changeset/export_named_type_parser.md
new file mode 100644
index 000000000000..01cb67b97d41
--- /dev/null
+++ b/.changeset/export_named_type_parser.md
@@ -0,0 +1,11 @@
+---
+"@biomejs/biome": patch
+---
+
+Export Named Type support `default` parser.
+
+The following code is now parsed successfully:
+
+```ts
+export { type A as default } from './b.ts';
+```
diff --git a/.changeset/fix_1597_useexhaustivedependencies_now_consider_react_hooks_stable_within_parentheses_or_type_assertions.md b/.changeset/fix_1597_useexhaustivedependencies_now_consider_react_hooks_stable_within_parentheses_or_type_assertions.md
new file mode 100644
index 000000000000..281f51da8692
--- /dev/null
+++ b/.changeset/fix_1597_useexhaustivedependencies_now_consider_react_hooks_stable_within_parentheses_or_type_assertions.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": patch
+---
+
+Fix #1597, useExhaustiveDependencies now consider React hooks stable within parentheses or type assertions.
diff --git a/.changeset/fix_a_bug_where_config_path_accepted_configuration_files_with_unsupported_extensions.md b/.changeset/fix_a_bug_where_config_path_accepted_configuration_files_with_unsupported_extensions.md
new file mode 100644
index 000000000000..8a8ab7bd34f1
--- /dev/null
+++ b/.changeset/fix_a_bug_where_config_path_accepted_configuration_files_with_unsupported_extensions.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": patch
+---
+
+Fix a bug where `--config-path` accepted configuration files with unsupported extensions. Now only `.json` and `.jsonc` are accepted, and an error is raised otherwise.
diff --git a/.changeset/fix_fragament_4751.md b/.changeset/fix_fragament_4751.md
new file mode 100644
index 000000000000..bf7e39366cf2
--- /dev/null
+++ b/.changeset/fix_fragament_4751.md
@@ -0,0 +1,24 @@
+---
+"@biomejs/biome": patch
+---
+
+Fix [#4751](https://github.com/biomejs/biome/issues/4751) by checking fragments inside `JSXElement` and conditional expressions. For example:
+
+The Case:
+
+```jsx
+
+ <>
+
+
+ >
+;
+```
+
+And:
+
+```jsx
+showFullName ? <>{fullName}> : <>{firstName}>;
+```
+
+It will report.
diff --git a/.changeset/fix_no_fallthrough_switch_case_panic.md b/.changeset/fix_no_fallthrough_switch_case_panic.md
new file mode 100644
index 000000000000..0cd7d3327678
--- /dev/null
+++ b/.changeset/fix_no_fallthrough_switch_case_panic.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": patch
+---
+
+The rule `noFallthroughSwitchCase` no longer panics on some incomplete code snippets.
diff --git a/.changeset/fix_nomissingvarfunction_false_positives_for_container_name.md b/.changeset/fix_nomissingvarfunction_false_positives_for_container_name.md
new file mode 100644
index 000000000000..0df2beed5748
--- /dev/null
+++ b/.changeset/fix_nomissingvarfunction_false_positives_for_container_name.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": patch
+---
+
+Fix [#5007](https://github.com/biomejs/biome/issues/5007) `noMissingVarFunction` false positives for `container-name`.
diff --git a/.changeset/fix_the_use_strict_directive_insertion_logic_for_shebang_and_top_leading_comments.md b/.changeset/fix_the_use_strict_directive_insertion_logic_for_shebang_and_top_leading_comments.md
new file mode 100644
index 000000000000..55e7dff6a1e9
--- /dev/null
+++ b/.changeset/fix_the_use_strict_directive_insertion_logic_for_shebang_and_top_leading_comments.md
@@ -0,0 +1,47 @@
+---
+"@biomejs/biome": patch
+---
+
+Fix [#4841](https://github.com/biomejs/biome/issues/4841), shebang and top leading comments in cjs files are now handled correctly
+
+- shebang only (keep it as is)
+
+```
+#!/usr/bin/env node
+```
+
+- comments only (keep it as is)
+
+```
+// comment
+```
+
+- with shebang
+
+```diff
+- #!/usr/bin/env node"use strict";
++ #!/usr/bin/env node
++ "use strict";
+let some_variable = "some value";
+```
+
+- with comment
+
+```diff
+- // comment
+- "use strict"; // comment
++ "use strict";
++ // comment
+let some_variable = "some value";
+```
+
+- with shebang and comment
+
+```diff
+- #!/usr/bin/env node"use strict";
+- // comment
++ #!/usr/bin/env node
++ "use strict";
++ // comment
+let some_variable = "some value";
+```
diff --git a/.changeset/fixanalyzer_suppression_comment_fails_with_inner_comments_in_functions.md b/.changeset/fixanalyzer_suppression_comment_fails_with_inner_comments_in_functions.md
new file mode 100644
index 000000000000..b96089a50b0c
--- /dev/null
+++ b/.changeset/fixanalyzer_suppression_comment_fails_with_inner_comments_in_functions.md
@@ -0,0 +1,17 @@
+---
+"@biomejs/biome": patch
+---
+
+Suppression comment should not fail with inner comments in functions.
+
+The following code:
+
+```ts
+// biome-ignore lint/complexity/useArrowFunction: not work
+const foo0 = function (bar: string) {
+ // biome-ignore lint/style/noParameterAssign: work
+ bar = "baz";
+};
+```
+
+The suppression comment `// biome-ignore lint/style/noParameterAssign: work` will not be invalid.
diff --git a/.changeset/introduce_includes.md b/.changeset/introduce_includes.md
new file mode 100644
index 000000000000..753136395f26
--- /dev/null
+++ b/.changeset/introduce_includes.md
@@ -0,0 +1,44 @@
+---
+"@biomejs/biome": minor
+---
+
+Introduce `includes`.
+
+Biome allows users to `include` and `ignore` files in its configuration using glob patterns.
+
+For example, in the following configuration, all files of the `src/` directory are checked except the ones ending with the extension `.test.js`.
+
+```json
+{
+ "files": {
+ "include": ["src/**"],
+ "ignore": ["**/*.test.js"]
+ }
+}
+```
+
+Some Biome users have requested the ability to ignore a set of files except some of the files.
+With the current system, this is not possible because `include` is always applied before `ignore`.
+
+Also, many Biome users [reported](https://github.com/biomejs/biome/issues/2421) [issues](https://github.com/biomejs/biome/issues/3345) with the behavior of the glob patterns.
+Notably:
+
+- `src/**` is interpreted as `**/src/**`
+- `*.js` is interpreted as `**/*.js`
+
+To solve all these issues, we introduce a new field `includes`, which replaces both `include` and `ignore`.
+`includes` accepts an array of glob patterns with a stricter and more intuitive behavior than the previous glob pattern format.
+A glob starting with a `!` is an exception.
+This replaces `ignore` patterns.
+
+The previous configuration must be updated as follows:
+
+```json
+{
+ "files": {
+ "includes": ["src/**", "!**/*.test.js"]
+ }
+}
+```
+
+You can run `biome migrate` to automatically convert from `include` and `ignore` to `includes`.
diff --git a/.changeset/introduce_the_domains_linter_feature.md b/.changeset/introduce_the_domains_linter_feature.md
new file mode 100644
index 000000000000..3f10fa1d8269
--- /dev/null
+++ b/.changeset/introduce_the_domains_linter_feature.md
@@ -0,0 +1,54 @@
+---
+"@biomejs/biome": minor
+---
+
+Introduce the `domains` linter feature. The Biome linter now has a new way to opt-in rules, with a concept called `domains`.
+
+Domains can be seen as concepts shared by different rules.
+
+You can enable and disable multiple rules that belong to a domain. When you assign `"all"`, Biome will enable all the rules, when you assign `"none"`, Biome will disable the rules, when you assign "recommended", Biome will enable all rules of the domain that are recommended.
+
+```json5
+// biome.jsonc
+{
+ "linter": {
+ "domains": {
+ "test": "all", // all rules that belong to this domain are enabled
+ "react": "recommended", // only the recommended rules from this domain are enabled
+ "solid": "none" // rules related to Solid are disabled
+ }
+ }
+}
+```
+
+New domains introduced:
+
+- `test`: it will enable rules:
+ - `noExportsInTest`
+ - `noExcessiveNestedTestSuites`
+ - `noDuplicateTestHooks`
+ - `noFocusedTests`
+ And it will inject the following globals:
+ - `after`
+ - `afterAll`
+ - `afterEach`
+ - `before`
+ - `beforeEach`
+ - `beforeAll`
+ - `describe`
+ - `it`
+ - `expect`
+ - `test`
+- `next`: it will enable rules for Next.js projects:
+ - `useExhaustiveDependencies`
+ - `useHookAtTopLevel`
+ - `noImgElement`
+ - `noHeadImportInDocument`
+ - `noHeadImportInDocument`
+- `react`: it will enable rules for React projects:
+ - `useExhaustiveDependencies`
+ - `useHookAtTopLevel`
+- `solid`: it will enable rules for Solid projects:
+ - `noReactSpecificProps`
+
+For more information regarding how Biome enables rules via domains, please refer to the documentation page of each rule.
diff --git a/.changeset/mark_useselfclosingelements_as_safe_and_improve_error_message.md b/.changeset/mark_useselfclosingelements_as_safe_and_improve_error_message.md
new file mode 100644
index 000000000000..0aecc868d2f2
--- /dev/null
+++ b/.changeset/mark_useselfclosingelements_as_safe_and_improve_error_message.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": patch
+---
+
+Mark `useSelfClosingElements` as safe and improve error message.
diff --git a/.changeset/new_top_level_suppression_for_the_analyzer.md b/.changeset/new_top_level_suppression_for_the_analyzer.md
new file mode 100644
index 000000000000..71417bb7d903
--- /dev/null
+++ b/.changeset/new_top_level_suppression_for_the_analyzer.md
@@ -0,0 +1,60 @@
+---
+"@biomejs/biome": minor
+---
+
+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-all lint/style/useConst: i like let
+ * biome-ignore-all 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-all lint/suspicious/noEmptyBlock: it's fine to have empty blocks
+*/
+
+a {}
+span {}
+```
+
+A new diagnostic is emitted if `biome-ignore-all` suppression isn't placed at the top of the file:
+
+
+```block
+file.js:3:1 suppressions/incorrect ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ ! Top level suppressions can only be used at the beginning of the file.
+
+ 2 │ let foo = 2;
+ > 3 │ /**
+ │ ^^^
+ > 4 │ * biome-ignore-all lint/style/useConst: reason
+ > 5 │ */
+ │ ^^
+ 6 │ let bar = 33;
+
+ i Rename this to biome-ignore
+
+ 2 │ let foo = 2;
+ 3 │ /**
+ > 4 │ * biome-ignore-all lint/style/useConst: reason
+ │ ^^^^^^^^^^^^^^^^
+ 5 │ */
+ 6 │ let bar = 33;
+
+
+```
diff --git a/.changeset/no_more_trailing_commas_in_json_files.md b/.changeset/no_more_trailing_commas_in_json_files.md
new file mode 100644
index 000000000000..0aa45692cb88
--- /dev/null
+++ b/.changeset/no_more_trailing_commas_in_json_files.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": major
+---
+
+The Biome formatter doesn't add a trailing command in `.json` files, even when `json.formatter.trailingCommas` is set to `true`.
diff --git a/.changeset/old-eels-help.md b/.changeset/old-eels-help.md
new file mode 100644
index 000000000000..a6580f596987
--- /dev/null
+++ b/.changeset/old-eels-help.md
@@ -0,0 +1,6 @@
+---
+"@biomejs/biome": patch
+---
+
+Fix [#4875](https://github.com/biomejs/biome/issues/4875), where the Jetbrains IDE terminal would output not clickable, relative file path link to the diagnostic file. This does not fix paths without line and column numbers.
+
diff --git a/.changeset/reduced_accepted_values.md b/.changeset/reduced_accepted_values.md
new file mode 100644
index 000000000000..f8ff953355f5
--- /dev/null
+++ b/.changeset/reduced_accepted_values.md
@@ -0,0 +1,11 @@
+---
+"@biomejs/biome": major
+---
+
+Reduced accepted values for formatter options:
+- The option `--quote-style` doesn't accept `Single` and `Double` anymore.
+- The option `--quote-properties` doesn't accept `AsNeeded` and `Preserve` anymore.
+- The option `--semicolons` doesn't accept `AsNeeded` and `Always` anymore.
+- The option `--arrow-parenthesis` doesn't accept `AsNeeded` and `Always` anymore.
+- The option `--trailing-commas` doesn't accept `ES5`, `All` and `None` anymore.
+- The option `--attribute-position` doesn't accept `Single` and `Multiline` anymore.
diff --git a/.changeset/remove_biome_log_dir.md b/.changeset/remove_biome_log_dir.md
new file mode 100644
index 000000000000..724e1f6d3e18
--- /dev/null
+++ b/.changeset/remove_biome_log_dir.md
@@ -0,0 +1,9 @@
+---
+"@biomejs/biome": major
+---
+
+Remove `BIOME_LOG_DIR`.
+
+The environment variable `BIOME_LOG_DIR` isn't supported anymore.
+
+Use `BIOME_LOG_PATH` instead.
diff --git a/.changeset/remove_deprecaterd_rules.md b/.changeset/remove_deprecaterd_rules.md
new file mode 100644
index 000000000000..66b9574e52a3
--- /dev/null
+++ b/.changeset/remove_deprecaterd_rules.md
@@ -0,0 +1,15 @@
+---
+"@biomejs/biome": major
+---
+
+Remove deprecated rules.
+
+The following _deprecated_ rules have been deleted:
+
+- `noInvalidNewBuiltin`
+- `noNewSymbol`
+- `useShorthandArrayType`
+- `useSingleCaseStatement`
+- `noConsoleLog`
+
+Run the command `biome migrate --write` to update the configuration.
diff --git a/.changeset/remove_indentsize_option.md b/.changeset/remove_indentsize_option.md
new file mode 100644
index 000000000000..2f880776a38f
--- /dev/null
+++ b/.changeset/remove_indentsize_option.md
@@ -0,0 +1,15 @@
+---
+"@biomejs/biome": major
+---
+
+Remove `indentSize` deprecated option.
+
+The deprecated option `indentSize`, and its relative CLI options, has been removed:
+- Configuration file: `formatter.indentSize`
+- Configuration file: `javascript.formatter.indentSize`
+- Configuration file: `json.formatter.indentSize`
+- CLI option `--indent-size`
+- CLI option `--javascript-formatter-indent-size`
+- CLI option `--json-formatter-indent-size`
+
+Use `indentWidth` and its relative CLI options instead.
diff --git a/.changeset/remove_rome_binary.md b/.changeset/remove_rome_binary.md
new file mode 100644
index 000000000000..1227e32d7790
--- /dev/null
+++ b/.changeset/remove_rome_binary.md
@@ -0,0 +1,7 @@
+---
+"@biomejs/biome": major
+---
+
+Remove `ROME_BINARY`. Use `BIOME_BINARY` instead.
+
+
diff --git a/.changeset/remove_support_for_legacy_suppressions.md b/.changeset/remove_support_for_legacy_suppressions.md
new file mode 100644
index 000000000000..4ba759773018
--- /dev/null
+++ b/.changeset/remove_support_for_legacy_suppressions.md
@@ -0,0 +1,13 @@
+---
+"@biomejs/biome": major
+---
+
+Remove support for legacy suppressions.
+
+Biome used to support "legacy suppressions" that looked like this:
+
+```js
+// biome-ignore lint(complexity/useWhile): reason
+```
+
+This format is no longer supported.
diff --git a/.changeset/remove_support_for_max_line_length_from_editorconfig_as_it_isnt_part_of_the_official_spec_anymore_.md b/.changeset/remove_support_for_max_line_length_from_editorconfig_as_it_isnt_part_of_the_official_spec_anymore_.md
new file mode 100644
index 000000000000..6b3965e195ff
--- /dev/null
+++ b/.changeset/remove_support_for_max_line_length_from_editorconfig_as_it_isnt_part_of_the_official_spec_anymore_.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": major
+---
+
+Remove support for `max_line_length` from `.editorconfig`, as it isn't part of the official spec anymore.
diff --git a/.changeset/remove_support_for_rome_ignore_suppression_comment.md b/.changeset/remove_support_for_rome_ignore_suppression_comment.md
new file mode 100644
index 000000000000..6d5fce844f8e
--- /dev/null
+++ b/.changeset/remove_support_for_rome_ignore_suppression_comment.md
@@ -0,0 +1,7 @@
+---
+"@biomejs/biome": major
+---
+
+Remove support for `rome-ignore` suppression comment.
+
+Use the `biome-ignore` suppression comment instead.
diff --git a/.changeset/remove_support_for_romejson.md b/.changeset/remove_support_for_romejson.md
new file mode 100644
index 000000000000..817626dcd737
--- /dev/null
+++ b/.changeset/remove_support_for_romejson.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": major
+---
+
+Remove support for `rome.json`.
diff --git a/.changeset/remove_the_option_all_from_the_linter.md b/.changeset/remove_the_option_all_from_the_linter.md
new file mode 100644
index 000000000000..2dd50bc4fe36
--- /dev/null
+++ b/.changeset/remove_the_option_all_from_the_linter.md
@@ -0,0 +1,16 @@
+---
+"@biomejs/biome": major
+---
+
+Remove the option `all` from the linter.
+
+The options `linter.rules.all` and `linter.rules..all` has been removed.
+
+The number of rules in Biome have increased in scope and use cases, and sometimes some of them can conflict with each other.
+
+The option was useful at the beginning, but now it's deemed harmful, because it can unexpected behaviours in users projects.
+
+To automatically remove it, run the following command:
+```shell
+biome migrate --write
+```
diff --git a/.changeset/remove_trailingcomma.md b/.changeset/remove_trailingcomma.md
new file mode 100644
index 000000000000..fecf99930666
--- /dev/null
+++ b/.changeset/remove_trailingcomma.md
@@ -0,0 +1,21 @@
+---
+"@biomejs/biome": major
+---
+
+Removed the option `trailingComma` from the configuration and the CLI. Use the option `trailingCommas` instead:
+
+```diff
+{
+ "javascript": {
+ "formatter": {
+- "trailingComma": "es5"
++ "trailingCommas": "es5"
+ }
+ }
+}
+```
+
+```diff
+-biome format --trailing-comma=es5
++biome format --trailing-commas=es5
+```
diff --git a/.changeset/removed_apply_and_apply_unsafe.md b/.changeset/removed_apply_and_apply_unsafe.md
new file mode 100644
index 000000000000..58d6558e5156
--- /dev/null
+++ b/.changeset/removed_apply_and_apply_unsafe.md
@@ -0,0 +1,17 @@
+---
+"@biomejs/biome": major
+---
+
+Removed `--apply` and `--apply-unsafe`.
+
+The CLI options `--apply` and `--apply-unasfe` aren't accepted anymore. Use `--write` and `--write --unafe` instead:
+
+```diff
+-biome check --apply-unsafe
++biome check --write --unsafe
+```
+
+```diff
+-biome check --apply
++biome check --write
+```
diff --git a/.changeset/removed_support_for_assert.md b/.changeset/removed_support_for_assert.md
new file mode 100644
index 000000000000..0763f0d03461
--- /dev/null
+++ b/.changeset/removed_support_for_assert.md
@@ -0,0 +1,15 @@
+---
+"@biomejs/biome": major
+---
+
+Removed support for `assert` syntax.
+
+Biome now longer supports the `assert` syntax, use the new `with` syntax instead
+
+```diff
+-import {test} from "foo.json" assert { for: "for" }
+-export * from "mod" assert { type: "json" }
++import {test} from "foo.json" with { for: "for" }
++export * from "mod" with { type: "json" }
+```
+
diff --git a/.changeset/renamed_useimportrestrictions_to_nopackageprivateimports.md b/.changeset/renamed_useimportrestrictions_to_nopackageprivateimports.md
new file mode 100644
index 000000000000..192fdd2cc5bd
--- /dev/null
+++ b/.changeset/renamed_useimportrestrictions_to_nopackageprivateimports.md
@@ -0,0 +1,8 @@
+---
+"@biomejs/biome": major
+---
+
+The rule `useImportRestrictions` has been renamed to `noPackagePrivateImports`.
+
+To avoid confusion with `noRestrictedImports`, `useImportRestrictions` has been
+renamed to `noPackagePrivateImports`.
diff --git a/.changeset/reworked_how_large_files_behave.md b/.changeset/reworked_how_large_files_behave.md
new file mode 100644
index 000000000000..308064a8a3a9
--- /dev/null
+++ b/.changeset/reworked_how_large_files_behave.md
@@ -0,0 +1,7 @@
+---
+"@biomejs/biome": major
+---
+
+Previously, files that should exceed the configured size limit would throw an error, and the CLI would exit with an error code.
+
+Now, the CLI ignores the file, emits a *information* diagnostic and doesn't exit with an error code.
diff --git a/.changeset/style_rules_arent_recommended_anymore_.md b/.changeset/style_rules_arent_recommended_anymore_.md
new file mode 100644
index 000000000000..3e42bc5e259e
--- /dev/null
+++ b/.changeset/style_rules_arent_recommended_anymore_.md
@@ -0,0 +1,34 @@
+---
+"@biomejs/biome": major
+---
+
+The `style` rules aren't recommended anymore.
+
+Linting rules that belong to the group `style` aren't recommended anymore. Here's the list of rules that aren't recommended anymore:
+
+- `useNumberNamespace`
+- `noNonnullAssertion`
+- `useAsConstAssertion`
+- `noParameterAssign`
+- `noInferrableTypes`
+- `useNodejsImportProtocol`
+- `useExportType`
+- `useDefaultParameterLast`
+- `noUnusedTemplateLiteral`
+- `useExponentiationOperator`
+- `useEnumInitializers`
+- `useShorthandFunctionType`
+- `useLiteralEnumMembers`
+- `noVar`
+- `noUselessElse`
+- `useNumericLiterals`
+- `noCommaOperator`
+- `useConst`
+- `noArguments`
+- `useSelfClosingElements`
+- `useImportType`
+- `useTemplate`
+- `useSingleVarDeclarator`
+- `useWhile`
+
+Use `biome migrate` to enable these rules, to avoid breaking changes.
diff --git a/.changeset/the_action_quickfixsuppressrule_is_removed.md b/.changeset/the_action_quickfixsuppressrule_is_removed.md
new file mode 100644
index 000000000000..939471000bd0
--- /dev/null
+++ b/.changeset/the_action_quickfixsuppressrule_is_removed.md
@@ -0,0 +1,35 @@
+---
+"@biomejs/biome": 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:
+let foo = "one";
+// biome-ignore lint/suspicious/noDebugger:
+debugger
+```
+
+The code action `quickfix.suppressRule.topLevel.biome`, instead, will result in the following code:
+```js
+/** biome-ignore lint/suspicious/noDebugger: */
+/** biome-ignore lint/style/useConst: */
+
+let foo = "one";
+debugger;
+```
+
diff --git a/.changeset/the_file_packagejson.md b/.changeset/the_file_packagejson.md
new file mode 100644
index 000000000000..ad79817a0abd
--- /dev/null
+++ b/.changeset/the_file_packagejson.md
@@ -0,0 +1,17 @@
+---
+"@biomejs/biome": major
+---
+
+Changed default formatting of `package.json`.
+
+When Biome encounters a file called `package.json`, by default it will format the file with all objects and arrays expanded.
+
+```diff
+- { "name": "project", "dependencies": { "foo": "latest" } }
++ {
++ "projectName": "project",
++ "dependencies": {
++ "foo": "^1.0.0"
++ }
++ }
+```
diff --git a/.changeset/the_organizeimports_is_now_part_of_biome_assist.md b/.changeset/the_organizeimports_is_now_part_of_biome_assist.md
new file mode 100644
index 000000000000..53ca8bc37772
--- /dev/null
+++ b/.changeset/the_organizeimports_is_now_part_of_biome_assist.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": major
+---
+
+The `organizeImports` is now part of Biome Assist
diff --git a/.changeset/the_rule_noconsolelog_has_been_removed.md b/.changeset/the_rule_noconsolelog_has_been_removed.md
new file mode 100644
index 000000000000..fbbef0f6277d
--- /dev/null
+++ b/.changeset/the_rule_noconsolelog_has_been_removed.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": major
+---
+
+The rule `noConsoleLog` has been removed
diff --git a/.changeset/the_rule_novar_now_belongs_to_the_suspicious_group.md b/.changeset/the_rule_novar_now_belongs_to_the_suspicious_group.md
new file mode 100644
index 000000000000..361307e82732
--- /dev/null
+++ b/.changeset/the_rule_novar_now_belongs_to_the_suspicious_group.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": major
+---
+
+The rule `noVar` now belongs to the `suspicious` group
diff --git a/.changeset/the_rule_useexhaustivedependencies_isnt_recommended_anymore.md b/.changeset/the_rule_useexhaustivedependencies_isnt_recommended_anymore.md
new file mode 100644
index 000000000000..67c95af30139
--- /dev/null
+++ b/.changeset/the_rule_useexhaustivedependencies_isnt_recommended_anymore.md
@@ -0,0 +1,19 @@
+---
+"@biomejs/biome": major
+---
+
+The rule `useExhaustiveDependencies` is not recommended anymore. If your codebase uses `react` and relies on that rule, you have to enable it:
+
+
+```jsonc
+// biome.json
+{
+ "linter": {
+ "rules": {
+ "correctness": {
+ "useExhaustiveDependencies": "error"
+ }
+ }
+ }
+}
+```
diff --git a/.changeset/the_rule_usewhile_now_belongs_to_the_complexity_group.md b/.changeset/the_rule_usewhile_now_belongs_to_the_complexity_group.md
new file mode 100644
index 000000000000..600a2e8578b2
--- /dev/null
+++ b/.changeset/the_rule_usewhile_now_belongs_to_the_complexity_group.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": major
+---
+
+The rule `useWhile` now belongs to the `complexity` group
diff --git a/.changeset/tsconfigjson_files_will_now_be_treated_the_same_as_tsconfigjson_files.md b/.changeset/tsconfigjson_files_will_now_be_treated_the_same_as_tsconfigjson_files.md
new file mode 100644
index 000000000000..3bc44ea008da
--- /dev/null
+++ b/.changeset/tsconfigjson_files_will_now_be_treated_the_same_as_tsconfigjson_files.md
@@ -0,0 +1,5 @@
+---
+"@biomejs/biome": patch
+---
+
+`tsconfig.*.json` files will now be treated the same as `tsconfig.json` files.
diff --git a/.changeset/use_new_workspace_apis.md b/.changeset/use_new_workspace_apis.md
new file mode 100644
index 000000000000..d71dfffcac13
--- /dev/null
+++ b/.changeset/use_new_workspace_apis.md
@@ -0,0 +1,6 @@
+---
+"@biomejs/js-api": minor
+"@biomejs/biome": minor
+---
+
+The package now requires `v2` of the WebAssembly packages. The internal APIs of Workspace are now `camelCase`.
diff --git a/.changeset/usefilenamingconvention_and_usenamingconvention_now_require_ascii_names_by_default.md b/.changeset/usefilenamingconvention_and_usenamingconvention_now_require_ascii_names_by_default.md
new file mode 100644
index 000000000000..47e01c88929b
--- /dev/null
+++ b/.changeset/usefilenamingconvention_and_usenamingconvention_now_require_ascii_names_by_default.md
@@ -0,0 +1,37 @@
+---
+"@biomejs/biome": major
+---
+
+Prior to Biome 2.0, non-ASCII names were accepted by default.
+They are now rejected.
+
+For example, the following code is now reported as invalid by the `useNamingConvention` rule.
+
+```js
+let johnCafé;
+```
+
+If you want to allow non ASCII filenames and non-ASCII identifiers, you need to set the `requireAscii` options in your Biome configuration file to `false`:
+
+```json
+{
+ "linter": {
+ "rules": {
+ "style": {
+ "useFilenamingConvention": {
+ "level": "on",
+ "options": {
+ "requireAscii": false
+ }
+ }
+ "useFilenamingConvention": {
+ "level": "on",
+ "options": {
+ "requireAscii": false
+ }
+ }
+ }
+ }
+ }
+}
+```
diff --git a/.changeset/usenamingconvention_preserves_capitalization.md b/.changeset/usenamingconvention_preserves_capitalization.md
new file mode 100644
index 000000000000..8b827a3fa621
--- /dev/null
+++ b/.changeset/usenamingconvention_preserves_capitalization.md
@@ -0,0 +1,29 @@
+---
+"@biomejs/biome": patch
+---
+
+The `useNamingConvention` rule now suggests a rename that preserves uppercase if possible.
+
+For instance, Biome suggested renaming `HTMLWrapper` as `htmlWrapper`:
+
+```diff
+- import HTMLWrapper from "HTMLWrapper.tsx";
++ import htmlWrapper from "HTMLWrapper.tsx";
+
+ function component() {
+- return ;
++ return ;
+ }
+```
+
+Since both `PascalCase` and `CamelCase` are accepted, Biome now suggests renaming `HTMLWrapper` as `HtmlWrapper`:
+
+```diff
+- import HTMLWrapper from "HTMLWrapper.tsx";
++ import HtmlWrapper from "HTMLWrapper.tsx";
+
+ function component() {
+- return ;
++ return ;
+ }
+```
diff --git a/.gitattributes b/.gitattributes
index bbd8e76b00ad..e9bcd97d8d38 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -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
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0b30c11872c5..ef45e7f37f97 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -189,6 +189,8 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b
- Add [noGlobalDirnameFilename](https://biomejs.dev/linter/rules/no-global-dirname-filename/). Contributed by @unvalley
+- Add [noUnwantedPolyfillio](https://biomejs.dev/linter/rules/no-unwanted-polyfillio/). Contributed by @unvalley
+
- [noForEach](https://biomejs.dev/linter/rules/no-for-each/) now provides a new option `validIdentifiers` ([#3351](https://github.com/biomejs/biome/issues/3351)) to specify which variable names are allowed to call `forEach`.
Identifiers containing dots (e.g., "lib._") or empty strings are not allowed. Invalid configurations will produce a diagnostic warning.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a54e90022076..c88ded571ec3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,39 +3,39 @@
We can use help in a bunch of areas and any help is greatly appreciated!
## Table of Contents
-
- [🚀 Contributing](#-contributing)
- - [Table of Contents](#table-of-contents)
- - [Asking questions, making proposals](#asking-questions-making-proposals)
- - [Reporting bugs](#reporting-bugs)
- - [Getting Started](#getting-started)
- - [Install the required tools](#install-the-required-tools)
- - [Testing](#testing)
- - [Debugging](#debugging)
- - [Debug binaries](#debug-binaries)
- - [Production binaries](#production-binaries)
- - [Checks](#checks)
- - [Crates development](#crates-development)
- - [Create new crates](#create-new-crates)
- - [Analyzers and lint rules](#analyzers-and-lint-rules)
- - [Parser](#parser)
- - [Formatter](#formatter)
- - [Crate dependencies](#crate-dependencies)
- - [Node.js development](#nodejs-development)
- - [Translations](#translations)
- - [Commit messages](#commit-messages)
- - [Creating pull requests](#creating-pull-requests)
- - [Changelog](#changelog)
- - [Writing a changelog line](#writing-a-changelog-line)
- - [Documentation](#documentation)
- - [Versioning](#versioning)
- - [Releasing](#releasing)
- - [Resources](#resources)
- - [Current Members](#current-members)
- - [Lead team](#lead-team)
- - [Core Contributors team](#core-contributors-team)
- - [Maintainers team](#maintainers-team)
- - [Past Maintainers](#past-maintainers)
+ * [Asking questions, making proposals](#asking-questions-making-proposals)
+ * [Reporting bugs](#reporting-bugs)
+ * [Getting Started](#getting-started)
+ * [Install the required tools](#install-the-required-tools)
+ * [Testing](#testing)
+ + [Debugging](#debugging)
+ * [Debug binaries](#debug-binaries)
+ * [Production binaries](#production-binaries)
+ * [Checks](#checks)
+ * [Crates development](#crates-development)
+ + [Create new crates](#create-new-crates)
+ + [Analyzers and lint rules](#analyzers-and-lint-rules)
+ + [Parser](#parser)
+ + [Formatter](#formatter)
+ * [Crate dependencies](#crate-dependencies)
+ * [Node.js development](#nodejs-development)
+ + [Translations](#translations)
+ * [Commit messages](#commit-messages)
+ * [Creating pull requests](#creating-pull-requests)
+ + [Changelog](#changelog)
+ - [Choose the correct packages](#choose-the-correct-packages)
+ - [Choose the correct type of change](#choose-the-correct-type-of-change)
+ - [Writing a changeset](#writing-a-changeset)
+ + [Documentation](#documentation)
+ + [Versioning](#versioning)
+ * [Releasing](#releasing)
+ * [Resources](#resources)
+ * [Current Members](#current-members)
+ + [Lead team](#lead-team)
+ + [Core Contributors team](#core-contributors-team)
+ + [Maintainers team](#maintainers-team)
+ + [Past Maintainers](#past-maintainers)
## Asking questions, making proposals
@@ -249,7 +249,7 @@ things you would need to run and check:
- `just f` (alias for `just format`), formats Rust and TOML files.
- `just l` (alias for `just lint`), run the linter for the whole project.
- Code generation. The code generation of the repository is spread in the different parts of the code base. Sometimes is needed and sometime it isn't:
- - run `just gen-lint` when you're working on the **linter**;
+ - run `just gen-analyzer` when you're working on the **linter**;
- run `just gen-bindings` in case you worked around the **workspace**.
> [!NOTE]
@@ -348,62 +348,50 @@ Please use the template provided.
### Changelog
-If the PR you're about to open is a bugfix/feature visible to Biome users, you CAN add a new bullet point to [CHANGELOG.md](./CHANGELOG.md). Although **not required**, we appreciate the effort.
-
-At the top of the file you will see a `Unreleased` section.
-The headings divide the sections by "scope"; you should be able to identify the scope that belongs to your change. If the change belongs to multiple scopes, you can copy the same sentence under those scopes.
-
-Here's a sample of the headings:
-
-```markdown
-## Unreleased
+This repository uses [changesets](https://github.com/changesets/changesets) to automate the releases of Biome's binaries, the JavaScript libraries and the creation of the `CHANGELOG.md` for each library.
-### Analyzer
+If the PR you're about to open is a bugfix/feature visible to users of the Biome toolchain or of the published Biome crates, you are encouraged to provide a **changeset** . To *create* a changeset, use the following command (*don't create it manually*):
-### CLI
+```shell
+just new-changeset
+```
+The command will present a prompt where you need to choose the libraries involved by the PR, the type of change (`major`, `minor` or `patch`) for each library, and a description of the change. The description will be used as name of the file.
-### Configuration
+The command will create the changeset(s) in the `.changeset` folder. You're free to open the file, and add more information in it.
-### Editors
+#### Choose the correct packages
-### Formatter
+In the vast majority of cases, you want to choose the `@biomejs/biome` package, which represents the main package.
-### JavaScript APIs
+The frontmatter of the changset will look like this:
-### Linter
+```markdown
+---
+"@biomejs/biome": patch
+---
-### Parser
+Description here...
```
-When you edit a blank section:
-
-- If your PR adds a **breaking change**, create a new heading called `#### BREAKING CHANGES` and add
- bullet point that explains the breaking changes; provide a migration path if possible.
- Read [how we version Biome](https://biomejs.dev/internals/versioning/) to determine if your change is breaking. A breaking change results in a major release.
-- If your PR adds a new feature, enhances an existing feature, or fixes a bug, create a new heading called `#### New features`, `#### Enhancements`, or `#### Bug fixes`. Ultimately, add a bullet point that explains the change.
-
-Make sure that the created subsections are ordered in the following order:
+#### Choose the correct type of change
-```md
-#### BREAKING CHANGES
+We are very strict about `major` changes in the `@biomejs/biome` package. To better understand type of your change *for this package*, please refer to our [versioning page](https://biomejs.dev/internals/versioning/). Generally:
+- `patch`: any sort of change that fixes a bug.
+- `minor`: new features available to the users.
+- `major`: a change that breaks a user API.
-#### New features
-
-#### Enhancements
-
-#### Bug fixes
-```
+#### Writing a changeset
-#### Writing a changelog line
+The description of the changeset should follow the these guidelines:
- Use the present tense, e.g. "Add new feature", "Fix edge case".
- If you fix a bug, please add the link to the issue, e.g. "Fix edge case [#4444]()".
-- You can add a mention `@user` for every contributor of the change.
- Whenever applicable, add a code block to show your new changes. For example, for a new
rule you might want to show an invalid case, for the formatter you might want to show
how the new formatting changes, and so on.
+- End each sentence with fullstops.
-If in doubt, take a look to existing changelog lines.
+If in doubt, take a look at existing or past changesets.
### Documentation
diff --git a/Cargo.lock b/Cargo.lock
index 07f8028a060e..7ffb24af1d6c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -53,6 +53,12 @@ version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
+[[package]]
+name = "append-only-vec"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7992085ec035cfe96992dd31bfd495a2ebd31969bb95f624471cb6c0b349e571"
+
[[package]]
name = "ascii_table"
version = "4.0.5"
@@ -117,8 +123,12 @@ dependencies = [
"biome_deserialize",
"biome_deserialize_macros",
"biome_diagnostics",
+ "biome_parser",
"biome_rowan",
+ "biome_suppression",
+ "camino",
"enumflags2",
+ "indexmap",
"rustc-hash 2.1.0",
"schemars",
"serde",
@@ -162,6 +172,8 @@ dependencies = [
"biome_flags",
"biome_formatter",
"biome_fs",
+ "biome_glob",
+ "biome_grit_patterns",
"biome_js_analyze",
"biome_js_formatter",
"biome_json_formatter",
@@ -173,10 +185,9 @@ dependencies = [
"biome_service",
"biome_text_edit",
"bpaf",
+ "camino",
"crossbeam",
"dashmap 6.1.0",
- "hdrhistogram",
- "indexmap 2.7.1",
"insta",
"libc",
"mimalloc",
@@ -188,6 +199,7 @@ dependencies = [
"serde",
"serde_json",
"smallvec",
+ "terminal_size",
"tikv-jemallocator",
"tokio",
"tracing",
@@ -209,26 +221,27 @@ dependencies = [
"biome_diagnostics",
"biome_flags",
"biome_formatter",
+ "biome_glob",
"biome_graphql_analyze",
"biome_graphql_syntax",
+ "biome_html_formatter",
"biome_html_syntax",
"biome_js_analyze",
"biome_js_formatter",
- "biome_js_syntax",
"biome_json_analyze",
"biome_json_formatter",
"biome_json_parser",
"biome_json_syntax",
"biome_rowan",
"bpaf",
- "indexmap 2.7.1",
+ "camino",
"insta",
"oxc_resolver",
"rustc-hash 2.1.0",
"schemars",
"serde",
"serde_ini",
- "serde_json",
+ "tests_macros",
]
[[package]]
@@ -265,10 +278,13 @@ dependencies = [
"biome_deserialize",
"biome_deserialize_macros",
"biome_diagnostics",
+ "biome_fs",
+ "biome_plugin_loader",
"biome_rowan",
"biome_string_case",
"biome_suppression",
"biome_test_utils",
+ "camino",
"insta",
"regex",
"rustc-hash 2.1.0",
@@ -301,6 +317,7 @@ dependencies = [
"biome_service",
"biome_string_case",
"biome_suppression",
+ "camino",
"countme",
"serde",
"serde_json",
@@ -323,6 +340,7 @@ dependencies = [
"biome_service",
"biome_test_utils",
"biome_unicode_table",
+ "camino",
"insta",
"quickcheck",
"quickcheck_macros",
@@ -346,10 +364,34 @@ version = "0.5.7"
dependencies = [
"biome_rowan",
"biome_string_case",
+ "camino",
"schemars",
"serde",
]
+[[package]]
+name = "biome_dependency_graph"
+version = "0.0.1"
+dependencies = [
+ "biome_deserialize",
+ "biome_fs",
+ "biome_js_parser",
+ "biome_js_syntax",
+ "biome_json_parser",
+ "biome_json_value",
+ "biome_package",
+ "biome_project_layout",
+ "biome_rowan",
+ "camino",
+ "cfg-if",
+ "once_cell",
+ "oxc_resolver",
+ "papaya",
+ "rustc-hash 2.1.0",
+ "seize",
+ "serde_json",
+]
+
[[package]]
name = "biome_deserialize"
version = "0.6.0"
@@ -360,9 +402,9 @@ dependencies = [
"biome_json_parser",
"biome_json_syntax",
"biome_rowan",
+ "camino",
"enumflags2",
- "indexmap 2.7.1",
- "schemars",
+ "indexmap",
"serde",
"serde_json",
"smallvec",
@@ -391,6 +433,7 @@ dependencies = [
"biome_text_edit",
"biome_text_size",
"bpaf",
+ "camino",
"enumflags2",
"insta",
"oxc_resolver",
@@ -399,6 +442,7 @@ dependencies = [
"serde_ini",
"serde_json",
"termcolor",
+ "terminal_size",
"trybuild",
"unicode-width 0.1.12",
]
@@ -441,10 +485,11 @@ dependencies = [
"biome_js_syntax",
"biome_rowan",
"biome_string_case",
+ "camino",
"cfg-if",
"countme",
"drop_bomb",
- "indexmap 2.7.1",
+ "indexmap",
"insta",
"rustc-hash 2.1.0",
"schemars",
@@ -466,6 +511,7 @@ dependencies = [
"biome_parser",
"biome_rowan",
"biome_service",
+ "camino",
"insta",
"serde",
"serde_json",
@@ -478,11 +524,12 @@ name = "biome_fs"
version = "0.5.7"
dependencies = [
"biome_diagnostics",
+ "camino",
"crossbeam",
"directories",
"enumflags2",
- "indexmap 2.7.1",
"oxc_resolver",
+ "papaya",
"parking_lot",
"rayon",
"rustc-hash 2.1.0",
@@ -520,6 +567,7 @@ dependencies = [
"biome_string_case",
"biome_suppression",
"biome_test_utils",
+ "camino",
"insta",
"schemars",
"serde",
@@ -549,6 +597,7 @@ dependencies = [
"biome_rowan",
"biome_service",
"biome_suppression",
+ "camino",
"countme",
"serde",
"serde_json",
@@ -590,6 +639,7 @@ version = "0.1.0"
dependencies = [
"biome_rowan",
"biome_string_case",
+ "camino",
"schemars",
"serde",
]
@@ -616,6 +666,7 @@ dependencies = [
"biome_parser",
"biome_rowan",
"biome_service",
+ "camino",
"countme",
"serde",
"serde_json",
@@ -648,7 +699,10 @@ dependencies = [
name = "biome_grit_patterns"
version = "0.0.1"
dependencies = [
+ "biome_analyze",
"biome_console",
+ "biome_css_parser",
+ "biome_css_syntax",
"biome_diagnostics",
"biome_grit_parser",
"biome_grit_syntax",
@@ -658,6 +712,7 @@ dependencies = [
"biome_rowan",
"biome_string_case",
"biome_test_utils",
+ "camino",
"grit-pattern-matcher",
"grit-util",
"insta",
@@ -665,7 +720,9 @@ dependencies = [
"rand 0.8.5",
"regex",
"rustc-hash 2.1.0",
+ "schemars",
"serde",
+ "serde_json",
"tests_macros",
]
@@ -675,6 +732,7 @@ version = "0.5.7"
dependencies = [
"biome_rowan",
"biome_string_case",
+ "camino",
"schemars",
"serde",
]
@@ -691,6 +749,8 @@ dependencies = [
name = "biome_html_formatter"
version = "0.0.0"
dependencies = [
+ "biome_deserialize",
+ "biome_deserialize_macros",
"biome_diagnostics_categories",
"biome_formatter",
"biome_formatter_test",
@@ -701,7 +761,10 @@ dependencies = [
"biome_rowan",
"biome_service",
"biome_suppression",
+ "camino",
"countme",
+ "schemars",
+ "serde",
"tests_macros",
]
@@ -730,6 +793,7 @@ version = "0.5.7"
dependencies = [
"biome_rowan",
"biome_string_case",
+ "camino",
"schemars",
"serde",
]
@@ -743,21 +807,26 @@ dependencies = [
"biome_aria_metadata",
"biome_console",
"biome_control_flow",
+ "biome_dependency_graph",
"biome_deserialize",
"biome_deserialize_macros",
"biome_diagnostics",
+ "biome_fs",
"biome_glob",
"biome_js_factory",
"biome_js_parser",
"biome_js_semantic",
"biome_js_syntax",
- "biome_project",
+ "biome_package",
+ "biome_plugin_loader",
+ "biome_project_layout",
"biome_rowan",
"biome_string_case",
"biome_suppression",
"biome_test_utils",
"biome_unicode_table",
"bitvec",
+ "camino",
"enumflags2",
"globset",
"insta",
@@ -799,6 +868,7 @@ dependencies = [
"biome_suppression",
"biome_text_size",
"biome_unicode_table",
+ "camino",
"countme",
"quickcheck",
"schemars",
@@ -825,9 +895,10 @@ dependencies = [
"biome_service",
"biome_test_utils",
"biome_unicode_table",
+ "camino",
"drop_bomb",
"enumflags2",
- "indexmap 2.7.1",
+ "indexmap",
"insta",
"quickcheck",
"quickcheck_macros",
@@ -864,6 +935,7 @@ dependencies = [
"biome_js_parser",
"biome_rowan",
"biome_string_case",
+ "camino",
"enumflags2",
"schemars",
"serde",
@@ -881,6 +953,7 @@ dependencies = [
"biome_js_syntax",
"biome_rowan",
"biome_test_utils",
+ "camino",
"insta",
"tests_macros",
]
@@ -896,7 +969,9 @@ dependencies = [
"biome_json_parser",
"biome_json_syntax",
"biome_rowan",
+ "biome_suppression",
"biome_test_utils",
+ "camino",
"insta",
"natord",
"rustc-hash 2.1.0",
@@ -927,6 +1002,8 @@ dependencies = [
"biome_rowan",
"biome_service",
"biome_suppression",
+ "biome_test_utils",
+ "camino",
"countme",
"schemars",
"serde",
@@ -959,10 +1036,26 @@ version = "0.5.7"
dependencies = [
"biome_rowan",
"biome_string_case",
+ "camino",
"schemars",
"serde",
]
+[[package]]
+name = "biome_json_value"
+version = "0.1.0"
+dependencies = [
+ "biome_deserialize",
+ "biome_deserialize_macros",
+ "biome_json_parser",
+ "biome_json_syntax",
+ "biome_rowan",
+ "indexmap",
+ "oxc_resolver",
+ "rustc-hash 2.1.0",
+ "static_assertions",
+]
+
[[package]]
name = "biome_lsp"
version = "0.0.0"
@@ -978,7 +1071,9 @@ dependencies = [
"biome_rowan",
"biome_service",
"biome_text_edit",
+ "camino",
"futures",
+ "papaya",
"rustc-hash 2.1.0",
"serde",
"serde_json",
@@ -1052,14 +1147,42 @@ dependencies = [
"biome_analyze",
"biome_console",
"biome_diagnostics",
- "biome_json_analyze",
+ "biome_glob",
"biome_json_factory",
+ "biome_json_formatter",
"biome_json_parser",
"biome_json_syntax",
+ "biome_package",
"biome_rowan",
"biome_test_utils",
+ "camino",
+ "insta",
+ "rustc-hash 2.1.0",
+ "tests_macros",
+]
+
+[[package]]
+name = "biome_package"
+version = "0.5.7"
+dependencies = [
+ "biome_console",
+ "biome_deserialize",
+ "biome_deserialize_macros",
+ "biome_diagnostics",
+ "biome_json_parser",
+ "biome_json_syntax",
+ "biome_json_value",
+ "biome_parser",
+ "biome_rowan",
+ "biome_text_size",
+ "camino",
+ "indexmap",
"insta",
+ "node-semver",
+ "oxc_resolver",
"rustc-hash 2.1.0",
+ "serde",
+ "static_assertions",
"tests_macros",
]
@@ -1077,23 +1200,35 @@ dependencies = [
]
[[package]]
-name = "biome_project"
-version = "0.5.7"
+name = "biome_plugin_loader"
+version = "0.0.1"
dependencies = [
+ "biome_analyze",
"biome_console",
"biome_deserialize",
"biome_deserialize_macros",
"biome_diagnostics",
+ "biome_fs",
+ "biome_grit_patterns",
"biome_json_parser",
- "biome_json_syntax",
"biome_parser",
"biome_rowan",
- "biome_text_size",
+ "camino",
+ "grit-pattern-matcher",
+ "grit-util",
"insta",
- "node-semver",
- "rustc-hash 2.1.0",
"serde",
- "tests_macros",
+]
+
+[[package]]
+name = "biome_project_layout"
+version = "0.0.1"
+dependencies = [
+ "biome_package",
+ "biome_parser",
+ "camino",
+ "papaya",
+ "rustc-hash 2.1.0",
]
[[package]]
@@ -1108,15 +1243,16 @@ dependencies = [
"quickcheck",
"quickcheck_macros",
"rustc-hash 2.1.0",
+ "schemars",
"serde",
"serde_json",
- "tracing",
]
[[package]]
name = "biome_service"
version = "0.0.0"
dependencies = [
+ "append-only-vec",
"biome_analyze",
"biome_configuration",
"biome_console",
@@ -1124,12 +1260,12 @@ dependencies = [
"biome_css_formatter",
"biome_css_parser",
"biome_css_syntax",
+ "biome_dependency_graph",
"biome_deserialize",
- "biome_deserialize_macros",
"biome_diagnostics",
- "biome_flags",
"biome_formatter",
"biome_fs",
+ "biome_glob",
"biome_graphql_analyze",
"biome_graphql_formatter",
"biome_graphql_parser",
@@ -1151,27 +1287,26 @@ dependencies = [
"biome_json_formatter",
"biome_json_parser",
"biome_json_syntax",
+ "biome_package",
"biome_parser",
- "biome_project",
+ "biome_project_layout",
"biome_rowan",
"biome_string_case",
"biome_text_edit",
- "bpaf",
- "dashmap 6.1.0",
+ "camino",
+ "crossbeam",
"enumflags2",
"getrandom 0.2.15",
"ignore",
- "indexmap 2.7.1",
"insta",
- "oxc_resolver",
+ "papaya",
+ "rayon",
"regex",
"rustc-hash 2.1.0",
"schemars",
"serde",
"serde_json",
- "slotmap",
"smallvec",
- "tests_macros",
"tracing",
]
@@ -1186,6 +1321,7 @@ dependencies = [
"biome_console",
"biome_diagnostics",
"biome_rowan",
+ "log",
]
[[package]]
@@ -1206,12 +1342,18 @@ dependencies = [
"biome_analyze",
"biome_configuration",
"biome_console",
+ "biome_dependency_graph",
"biome_deserialize",
"biome_diagnostics",
+ "biome_formatter",
+ "biome_fs",
+ "biome_js_parser",
"biome_json_parser",
- "biome_project",
+ "biome_package",
+ "biome_project_layout",
"biome_rowan",
"biome_service",
+ "camino",
"countme",
"json_comments",
"serde_json",
@@ -1248,10 +1390,11 @@ version = "0.5.7"
[[package]]
name = "biome_wasm"
-version = "1.7.3"
+version = "1.9.4"
dependencies = [
"biome_console",
"biome_diagnostics",
+ "biome_fs",
"biome_js_factory",
"biome_js_formatter",
"biome_rowan",
@@ -1292,6 +1435,8 @@ name = "biome_yaml_syntax"
version = "0.0.1"
dependencies = [
"biome_rowan",
+ "biome_string_case",
+ "camino",
"schemars",
"serde",
]
@@ -1383,6 +1528,15 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+[[package]]
+name = "camino"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "case"
version = "1.0.0"
@@ -1557,7 +1711,7 @@ dependencies = [
"cookie",
"document-features",
"idna",
- "indexmap 2.7.1",
+ "indexmap",
"log",
"serde",
"serde_derive",
@@ -2160,9 +2314,9 @@ dependencies = [
[[package]]
name = "grit-pattern-matcher"
-version = "0.4.0"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8430b130e086a1764789402b34685336f2e3f6ec37cd188535bede892f88e65b"
+checksum = "4694b698b2b87b9ad1c2dfef1103de207b4e12821d338b153264c8058ea58fa4"
dependencies = [
"elsa",
"grit-util",
@@ -2173,9 +2327,9 @@ dependencies = [
[[package]]
name = "grit-util"
-version = "0.4.0"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99b005c4c15ce0c47022554c41a01fe2441d3622e586a82614a6fe681833d5d4"
+checksum = "0cde701c8427e7260b65e979bc90b34be2681b5af20908ff6c6dfff683ff6f02"
dependencies = [
"derive_builder",
"once_cell",
@@ -2190,12 +2344,6 @@ version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
-[[package]]
-name = "hashbrown"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
-
[[package]]
name = "hashbrown"
version = "0.14.5"
@@ -2208,16 +2356,6 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
-[[package]]
-name = "hdrhistogram"
-version = "7.5.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d"
-dependencies = [
- "byteorder",
- "num-traits",
-]
-
[[package]]
name = "hermit-abi"
version = "0.3.9"
@@ -2408,17 +2546,6 @@ dependencies = [
"winapi-util",
]
-[[package]]
-name = "indexmap"
-version = "1.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
-dependencies = [
- "autocfg",
- "hashbrown 0.12.3",
- "serde",
-]
-
[[package]]
name = "indexmap"
version = "2.7.1"
@@ -2477,7 +2604,7 @@ checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
dependencies = [
"hermit-abi",
"io-lifetimes",
- "rustix",
+ "rustix 0.37.7",
"windows-sys 0.48.0",
]
@@ -2601,6 +2728,12 @@ version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+
[[package]]
name = "litemap"
version = "0.7.3"
@@ -2841,16 +2974,17 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "oxc_resolver"
-version = "3.0.3"
+version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bed381b6ab4bbfebfc7a011ad43b110ace8d201d02a39c0e09855f16b8f3f741"
+checksum = "d0f82c2be3d07b2ac002fb4a414d6fab602b352a8d99ed9b59f6868a968c73ba"
dependencies = [
"cfg-if",
- "dashmap 6.1.0",
- "indexmap 2.7.1",
+ "indexmap",
"json-strip-comments",
"once_cell",
+ "papaya",
"rustc-hash 2.1.0",
+ "seize",
"serde",
"serde_json",
"simdutf8",
@@ -2858,6 +2992,16 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "papaya"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc7c76487f7eaa00a0fc1d7f88dc6b295aec478d11b0fc79f857b62c2874124c"
+dependencies = [
+ "equivalent",
+ "seize",
+]
+
[[package]]
name = "parking_lot"
version = "0.12.3"
@@ -3074,7 +3218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed1a693391a16317257103ad06a88c6529ac640846021da7c435a06fffdacd7"
dependencies = [
"chrono",
- "indexmap 2.7.1",
+ "indexmap",
"newtype-uuid",
"quick-xml",
"strip-ansi-escapes",
@@ -3349,6 +3493,7 @@ dependencies = [
"biome_json_syntax",
"biome_rowan",
"biome_service",
+ "camino",
"pulldown-cmark",
]
@@ -3389,10 +3534,23 @@ dependencies = [
"errno",
"io-lifetimes",
"libc",
- "linux-raw-sys",
+ "linux-raw-sys 0.3.8",
"windows-sys 0.45.0",
]
+[[package]]
+name = "rustix"
+version = "0.38.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
+dependencies = [
+ "bitflags 2.6.0",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.4.15",
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "rustls"
version = "0.23.19"
@@ -3462,8 +3620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92"
dependencies = [
"dyn-clone",
- "indexmap 1.9.3",
- "indexmap 2.7.1",
+ "indexmap",
"schemars_derive",
"serde",
"serde_json",
@@ -3488,6 +3645,16 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+[[package]]
+name = "seize"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d84b0c858bdd30cb56f5597f8b3bf702ec23829e652cc636a1e5a7b9de46ae93"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
[[package]]
name = "serde"
version = "1.0.217"
@@ -3547,7 +3714,7 @@ version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
dependencies = [
- "indexmap 2.7.1",
+ "indexmap",
"itoa",
"memchr",
"ryu",
@@ -3589,7 +3756,7 @@ version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
- "indexmap 2.7.1",
+ "indexmap",
"itoa",
"ryu",
"serde",
@@ -3655,16 +3822,6 @@ dependencies = [
"autocfg",
]
-[[package]]
-name = "slotmap"
-version = "1.0.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
-dependencies = [
- "serde",
- "version_check",
-]
-
[[package]]
name = "smallvec"
version = "1.13.2"
@@ -3790,6 +3947,16 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "terminal_size"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9"
+dependencies = [
+ "rustix 0.38.25",
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "tests_macros"
version = "0.0.0"
@@ -3993,7 +4160,7 @@ version = "0.22.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
dependencies = [
- "indexmap 2.7.1",
+ "indexmap",
"serde",
"serde_spanned",
"toml_datetime",
@@ -4795,6 +4962,7 @@ dependencies = [
"biome_json_syntax",
"biome_parser",
"biome_rowan",
+ "camino",
"codspeed-criterion-compat",
"criterion",
"mimalloc",
@@ -4856,6 +5024,7 @@ dependencies = [
"biome_parser",
"biome_rowan",
"biome_string_case",
+ "camino",
"colored 3.0.0",
"indicatif",
"pico-args",
diff --git a/Cargo.toml b/Cargo.toml
index 453ddadfff9c..eb6c5aa70118 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -109,6 +109,7 @@ biome_css_formatter = { version = "0.5.7", path = "./crates/biome_css_f
biome_css_parser = { version = "0.5.7", path = "./crates/biome_css_parser" }
biome_css_semantic = { version = "0.0.0", path = "./crates/biome_css_semantic" }
biome_css_syntax = { version = "0.5.7", path = "./crates/biome_css_syntax" }
+biome_dependency_graph = { version = "0.0.1", path = "./crates/biome_dependency_graph" }
biome_deserialize = { version = "0.6.0", path = "./crates/biome_deserialize" }
biome_deserialize_macros = { version = "0.6.0", path = "./crates/biome_deserialize_macros" }
biome_diagnostics = { version = "0.5.7", path = "./crates/biome_diagnostics" }
@@ -143,18 +144,21 @@ biome_json_factory = { version = "0.5.7", path = "./crates/biome_json_
biome_json_formatter = { version = "0.5.7", path = "./crates/biome_json_formatter" }
biome_json_parser = { version = "0.5.7", path = "./crates/biome_json_parser" }
biome_json_syntax = { version = "0.5.7", path = "./crates/biome_json_syntax" }
+biome_json_value = { version = "0.1.0", path = "./crates/biome_json_value" }
biome_lsp_converters = { version = "0.1.0", path = "./crates/biome_lsp_converters" }
biome_markdown_factory = { version = "0.0.1", path = "./crates/biome_markdown_factory" }
biome_markdown_parser = { version = "0.0.1", path = "./crates/biome_markdown_parser" }
biome_markdown_syntax = { version = "0.0.1", path = "./crates/biome_markdown_syntax" }
+biome_plugin_loader = { version = "0.0.1", path = "./crates/biome_plugin_loader" }
+biome_project_layout = { version = "0.0.1", path = "./crates/biome_project_layout" }
biome_ungrammar = { version = "0.3.1", path = "./crates/biome_ungrammar" }
biome_yaml_factory = { version = "0.0.1", path = "./crates/biome_yaml_factory" }
biome_yaml_parser = { version = "0.0.1", path = "./crates/biome_yaml_parser" }
biome_yaml_syntax = { version = "0.0.1", path = "./crates/biome_yaml_syntax" }
biome_markup = { version = "0.5.7", path = "./crates/biome_markup" }
+biome_package = { version = "0.5.7", path = "./crates/biome_package" }
biome_parser = { version = "0.5.7", path = "./crates/biome_parser" }
-biome_project = { version = "0.5.7", path = "./crates/biome_project" }
biome_rowan = { version = "0.5.7", path = "./crates/biome_rowan" }
biome_string_case = { version = "0.5.7", path = "./crates/biome_string_case" }
biome_suppression = { version = "0.5.7", path = "./crates/biome_suppression" }
@@ -174,40 +178,45 @@ biome_test_utils = { path = "./crates/biome_test_utils" }
tests_macros = { path = "./crates/tests_macros" }
# Crates needed in the workspace
-anyhow = "1.0.95"
-bpaf = { version = "0.9.16", features = ["derive"] }
-countme = "3.0.1"
-crossbeam = "0.8.4"
-dashmap = "6.1.0"
-enumflags2 = "0.7.11"
-getrandom = "0.2.15"
-globset = "0.4.15"
-ignore = "0.4.23"
-indexmap = { version = "2.7.1", features = ["serde"] }
-insta = "1.42.1"
-natord = "1.0.9"
-oxc_resolver = "3.0.3"
-proc-macro2 = "1.0.86"
-quickcheck = "1.0.3"
-quickcheck_macros = "1.0.0"
-quote = "1.0.38"
-rayon = "1.10.0"
-regex = "1.11.1"
-rustc-hash = "2.1.0"
-schemars = { version = "0.8.21", features = ["indexmap2", "smallvec"] }
-serde = { version = "1.0.217", features = ["derive"] }
-serde_ini = "0.2.0"
-serde_json = "1.0.137"
-similar = "2.7.0"
-slotmap = "1.0.7"
-smallvec = { version = "1.13.2", features = ["union", "const_new", "serde"] }
-syn = "1.0.109"
-termcolor = "1.4.1"
-tokio = "1.43.0"
-tracing = { version = "0.1.41", default-features = false, features = ["std"] }
-tracing-subscriber = "0.3.19"
-unicode-bom = "2.0.3"
-unicode-width = "0.1.12"
+anyhow = "1.0.95"
+bpaf = { version = "0.9.16", features = ["derive"] }
+camino = "1.1.9"
+cfg-if = "1"
+countme = "3.0.1"
+crossbeam = "0.8.4"
+dashmap = "6.1.0"
+enumflags2 = "0.7.11"
+getrandom = "0.2.15"
+globset = "0.4.15"
+grit-pattern-matcher = "0.5"
+grit-util = "0.5"
+ignore = "0.4.23"
+indexmap = { version = "2.7.1" }
+insta = "1.42.1"
+natord = "1.0.9"
+oxc_resolver = "4.0"
+papaya = "0.1.8"
+proc-macro2 = "1.0.86"
+quickcheck = "1.0.3"
+quickcheck_macros = "1.0.0"
+quote = "1.0.38"
+rayon = "1.10.0"
+regex = "1.11.1"
+rustc-hash = "2.1.0"
+schemars = { version = "0.8.21", features = ["indexmap2", "smallvec"] }
+serde = { version = "1.0.217", features = ["derive"] }
+serde_ini = "0.2.0"
+serde_json = "1.0.137"
+similar = "2.7.0"
+smallvec = { version = "1.13.2", features = ["union", "const_new", "serde"] }
+syn = "1.0.109"
+termcolor = "1.4.1"
+terminal_size = "0.4.1"
+tokio = "1.43.0"
+tracing = { version = "0.1.41", default-features = false, features = ["std", "attributes"] }
+tracing-subscriber = "0.3.19"
+unicode-bom = "2.0.3"
+unicode-width = "0.1.12"
[profile.dev.package.biome_wasm]
debug = true
opt-level = "s"
diff --git a/benchmark/biome.json b/benchmark/biome.json
index 84d8e7851659..77e7210b2b6c 100644
--- a/benchmark/biome.json
+++ b/benchmark/biome.json
@@ -66,7 +66,6 @@
"noArguments": "error",
"noCommaOperator": "error",
"noParameterAssign": "error",
- "noVar": "error",
"useConst": "error",
"useCollapsedElseIf": "error",
"useDefaultParameterLast": "error",
@@ -105,7 +104,8 @@
"useAwait": "error",
"useDefaultSwitchClauseLast": "error",
"useGetterReturn": "error",
- "useValidTypeof": "error"
+ "useValidTypeof": "error",
+ "noVar": "error"
}
}
}
diff --git a/benchmark/package.json b/benchmark/package.json
index 1b650334c87c..62778faa1b7f 100644
--- a/benchmark/package.json
+++ b/benchmark/package.json
@@ -12,6 +12,7 @@
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "8.20.0",
+ "@typescript-eslint/parser": "8.3.0",
"dprint": "0.48.0",
"eslint": "9.17.0",
"prettier": "3.4.2"
diff --git a/biome.json b/biome.json
index f2311cb3aca5..98fb7eae05d2 100644
--- a/biome.json
+++ b/biome.json
@@ -1,6 +1,6 @@
{
"$schema": "./packages/@biomejs/biome/configuration_schema.json",
- "assists": {
+ "assist": {
"enabled": true,
"ignore": [
"./packages/@biomejs/biome/configuration_schema.json"
@@ -17,29 +17,28 @@
}
},
"files": {
- "ignore": [
- "crates/**",
- "dist/**",
- ".astro/**",
- "assets/**",
- "packages/@biomejs/backend-jsonrpc/src/workspace.ts",
- "public/**",
- "**/__snapshots__",
- "**/undefined/**",
- "_fonts/**",
- "packages/@biomejs/wasm-*",
- "benchmark/target/**"
- ],
- "include": [
- "packages/aria-data/*.js",
- "packages/@biomejs/**",
- "packages/tailwindcss-config-analyzer/**",
- "benchmark/**"
+ "includes": [
+ "**/packages/aria-data/*.js",
+ "**/packages/@biomejs/**",
+ "**/packages/tailwindcss-config-analyzer/**",
+ "**/benchmark/**",
+ "!**/crates/**",
+ "!**/dist/**",
+ "!**/.astro/**",
+ "!**/assets/**",
+ "!**/packages/@biomejs/backend-jsonrpc/src/workspace.ts",
+ "!**/public/**",
+ "!**/__snapshots__",
+ "!**/undefined/**",
+ "!**/_fonts/**",
+ "!**/packages/@biomejs/wasm-*",
+ "!**/benchmark/target/**"
]
},
"formatter": {
- "ignore": [
- "configuration_schema.json"
+ "includes": [
+ "**",
+ "!**/configuration_schema.json"
]
},
"json": {
@@ -51,15 +50,38 @@
"linter": {
"enabled": true,
"rules": {
- "recommended": true,
"style": {
- "noNonNullAssertion": "off"
+ "noNonNullAssertion": "off",
+ "useNodejsImportProtocol": "error",
+ "useLiteralEnumMembers": "error",
+ "noArguments": "error",
+ "noParameterAssign": "error",
+ "useShorthandFunctionType": "error",
+ "useExportType": "error",
+ "useDefaultParameterLast": "error",
+ "noCommaOperator": "error",
+ "useSingleVarDeclarator": "error",
+ "useConst": "error",
+ "noInferrableTypes": "error",
+ "useExponentiationOperator": "error",
+ "noUselessElse": "error",
+ "useSelfClosingElements": "error",
+ "useImportType": "error",
+ "useNumberNamespace": "error",
+ "useAsConstAssertion": "error",
+ "noUnusedTemplateLiteral": "error",
+ "useNumericLiterals": "error",
+ "useTemplate": "error",
+ "useEnumInitializers": "error"
+ },
+ "correctness": {
+ "noUndeclaredDependencies": "error"
+ },
+ "suspicious": {
+ "noVar": "on"
}
}
},
- "organizeImports": {
- "enabled": true
- },
"vcs": {
"clientKind": "git",
"enabled": true,
diff --git a/crates/biome_analyze/CONTRIBUTING.md b/crates/biome_analyze/CONTRIBUTING.md
index db3d73195e50..10b44a2c2312 100644
--- a/crates/biome_analyze/CONTRIBUTING.md
+++ b/crates/biome_analyze/CONTRIBUTING.md
@@ -276,7 +276,6 @@ Don't forget to format your code with `just f` and lint with `just l`.
That's it! Now, let's [test the rule](#testing-the-rule).
-
### Coding Tips for Rules
Below, there are many tips and guidelines on how to create a lint rule using Biome infrastructure.
@@ -375,6 +374,59 @@ impl Rule for ExampleRule {
}
```
+#### Rule severity
+
+The macro accepts a `severity` field, of type `biome_diagnostics::Severity`. By default, rules without `severity` will start with `Severity::Information`.
+
+If you want to change the default severity, you need to assign it:
+
+```diff
++ use biome_diagnostics::Severity;
+
+declare_lint_rule! {
+ /// Documentation
+ pub(crate) ExampleRule {
+ version: "next",
+ name: "myRuleName",
+ language: "js",
+ recommended: false,
++ severity: Severity::Warning,
+ }
+}
+```
+
+#### Rule domains
+
+Domains are very specific ways to collect rules that belong to the same "concept". Domains are a way for users to opt-in/opt-out rules that belong to the same domain.
+
+Some examples of domains: testing, specific framework, specific runtime, specific library. A rule can belong to multiple domains.
+
+```diff
++ use biome_analyze::RuleDomain;
+
+
+declare_lint_rule! {
+ /// Documentation
+ pub(crate) ExampleRule {
+ version: "next",
+ name: "myRuleName",
+ language: "js",
+ recommended: true,
++ domains: &[RuleDomain::Test],
+ }
+}
+```
+
+Rule domains can unlock various perks in the Biome analyzer:
+- A domain can define a number of `package.json` dependencies. When a user has one or more of these dependencies, Biome will automatically enable the recommended rules that belong to the domain. To add/update/remove dependencies to a domain, check the function `RuleDomain::manifest_dependencies`.
+- A domain can define a number of "globals". These globals will be used by other rules, and improve the UX of them. To add/update/remove globals to a domain, check the function `RuleDomain::globals`.
+
+When a rule is **recommended** and _has domains_, the rule is enabled only when the user enables the relative domains via `"recommneded"` or `"all"`.
+Instead, if the rule is **recommended** but _doesn't have domains_, the rule is always enabled by default.
+
+> [!NOTE]
+> Before adding a new domain, please consult with the maintainers of the project.
+
#### Rule Options
Some rules may allow customization [using per-rule options in `biome.json`](https://biomejs.dev/linter/#rule-options).
@@ -1139,7 +1191,7 @@ declare_lint_rule! {
For simplicity, use `just` to run all the commands with:
```shell
-just gen-lint
+just gen-analyzer
```
### Commiting your work
diff --git a/crates/biome_analyze/Cargo.toml b/crates/biome_analyze/Cargo.toml
index 94b961b898e6..df83a2e465ec 100644
--- a/crates/biome_analyze/Cargo.toml
+++ b/crates/biome_analyze/Cargo.toml
@@ -17,16 +17,20 @@ biome_console = { workspace = true }
biome_deserialize = { workspace = true, optional = true }
biome_deserialize_macros = { workspace = true, optional = true }
biome_diagnostics = { workspace = true }
+biome_parser = { workspace = true }
biome_rowan = { workspace = true }
+biome_suppression = { workspace = true }
+camino = { workspace = true }
enumflags2 = { workspace = true }
+indexmap = { workspace = true }
rustc-hash = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"], optional = true }
tracing = { workspace = true }
-
[features]
-serde = ["dep:serde", "dep:schemars", "dep:biome_deserialize", "dep:biome_deserialize_macros"]
+schema = ["dep:schemars", "biome_console/schema", "serde"]
+serde = ["dep:serde", "dep:biome_deserialize", "dep:biome_deserialize_macros"]
[lints]
workspace = true
diff --git a/crates/biome_analyze/src/analyzer_plugin.rs b/crates/biome_analyze/src/analyzer_plugin.rs
new file mode 100644
index 000000000000..e7b59220ea27
--- /dev/null
+++ b/crates/biome_analyze/src/analyzer_plugin.rs
@@ -0,0 +1,13 @@
+use crate::RuleDiagnostic;
+use biome_parser::AnyParse;
+use camino::Utf8PathBuf;
+use std::fmt::Debug;
+
+/// Definition of an analyzer plugin.
+pub trait AnalyzerPlugin: Debug {
+ fn evaluate(&self, root: AnyParse, path: Utf8PathBuf) -> Vec;
+
+ fn supports_css(&self) -> bool;
+
+ fn supports_js(&self) -> bool;
+}
diff --git a/crates/biome_analyze/src/categories.rs b/crates/biome_analyze/src/categories.rs
index 25d6a060543e..1b0835a82e9e 100644
--- a/crates/biome_analyze/src/categories.rs
+++ b/crates/biome_analyze/src/categories.rs
@@ -1,11 +1,14 @@
use enumflags2::{bitflags, BitFlags};
use std::borrow::Cow;
+use std::fmt::{Display, Formatter};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
feature = "serde",
- derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)
+ derive(serde::Serialize, serde::Deserialize),
+ serde(rename_all = "camelCase")
)]
+#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum RuleCategory {
/// This rule checks the syntax according to the language specification
/// and emits error diagnostics accordingly
@@ -21,8 +24,20 @@ pub enum RuleCategory {
Transformation,
}
+impl Display for RuleCategory {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ RuleCategory::Syntax => write!(f, "Syntax"),
+ RuleCategory::Lint => write!(f, "Lint"),
+ RuleCategory::Action => write!(f, "Action"),
+ RuleCategory::Transformation => write!(f, "Transformation"),
+ }
+ }
+}
+
/// 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
@@ -31,8 +46,10 @@ pub const SUPPRESSION_ACTION_CATEGORY: &str = "quickfix.suppressRule";
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
- derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)
+ derive(serde::Serialize, serde::Deserialize),
+ serde(rename_all = "camelCase")
)]
+#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum ActionCategory {
/// Base kind for quickfix actions: 'quickfix'.
///
@@ -48,7 +65,23 @@ 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),
+ serde(rename_all = "camelCase")
+)]
+#[cfg_attr(feature = "schema", derive(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 {
@@ -58,7 +91,7 @@ impl ActionCategory {
///
/// ```
/// use std::borrow::Cow;
- /// use biome_analyze::{ActionCategory, RefactorKind};
+ /// use biome_analyze::{ActionCategory, RefactorKind, OtherActionCategory};
///
/// assert!(ActionCategory::QuickFix(Cow::from("quickfix")).matches("quickfix"));
///
@@ -67,6 +100,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)
@@ -105,10 +141,18 @@ 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(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")),
+ },
}
}
}
@@ -119,8 +163,10 @@ impl ActionCategory {
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
- derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)
+ derive(serde::Serialize, serde::Deserialize),
+ serde(rename_all = "camelCase")
)]
+#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum RefactorKind {
/// This action describes a refactor with no particular sub-category
None,
@@ -159,8 +205,10 @@ pub enum RefactorKind {
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
- derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)
+ derive(serde::Serialize, serde::Deserialize),
+ serde(rename_all = "camelCase")
)]
+#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum SourceActionKind {
/// This action describes a source action with no particular sub-category
None,
@@ -183,7 +231,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,
}
@@ -195,6 +243,26 @@ pub(crate) enum Categories {
/// Use [RuleCategoriesBuilder] to generate the categories you want to query.
pub struct RuleCategories(BitFlags);
+impl Display for RuleCategories {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ if self.0.is_empty() {
+ write!(f, "No categories")
+ } else {
+ let mut list = f.debug_list();
+ if self.0.contains(Categories::Syntax) {
+ list.entry(&RuleCategory::Syntax);
+ }
+ if self.0.contains(Categories::Lint) {
+ list.entry(&RuleCategory::Lint);
+ }
+ if self.0.contains(Categories::Assist) {
+ list.entry(&RuleCategory::Action);
+ }
+ list.finish()
+ }
+ }
+}
+
impl RuleCategories {
pub fn empty() -> Self {
let empty: BitFlags = BitFlags::empty();
@@ -229,7 +297,7 @@ impl From 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))
}
@@ -253,7 +321,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);
}
@@ -301,7 +369,7 @@ impl<'de> serde::Deserialize<'de> for RuleCategories {
}
}
-#[cfg(feature = "serde")]
+#[cfg(feature = "schema")]
impl schemars::JsonSchema for RuleCategories {
fn schema_name() -> String {
String::from("RuleCategories")
@@ -339,8 +407,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
}
diff --git a/crates/biome_analyze/src/context.rs b/crates/biome_analyze/src/context.rs
index dc5027c562e4..650f3d52e5dd 100644
--- a/crates/biome_analyze/src/context.rs
+++ b/crates/biome_analyze/src/context.rs
@@ -2,8 +2,8 @@ use crate::options::{JsxRuntime, PreferredQuote};
use crate::{registry::RuleRoot, FromServices, Queryable, Rule, RuleKey, ServiceBag};
use crate::{GroupCategory, RuleCategory, RuleGroup, RuleMetadata};
use biome_diagnostics::{Error, Result};
+use camino::Utf8Path;
use std::ops::Deref;
-use std::path::Path;
type RuleQueryResult = <::Query as Queryable>::Output;
type RuleServiceBag = <::Query as Queryable>::Services;
@@ -14,7 +14,7 @@ pub struct RuleContext<'a, R: Rule> {
bag: &'a ServiceBag,
services: RuleServiceBag,
globals: &'a [&'a str],
- file_path: &'a Path,
+ file_path: &'a Utf8Path,
options: &'a R::Options,
preferred_quote: &'a PreferredQuote,
preferred_jsx_quote: &'a PreferredQuote,
@@ -31,7 +31,7 @@ where
root: &'a RuleRoot,
services: &'a ServiceBag,
globals: &'a [&'a str],
- file_path: &'a Path,
+ file_path: &'a Utf8Path,
options: &'a R::Options,
preferred_quote: &'a PreferredQuote,
preferred_jsx_quote: &'a PreferredQuote,
@@ -162,7 +162,7 @@ where
}
/// The file path of the current file
- pub fn file_path(&self) -> &Path {
+ pub fn file_path(&self) -> &Utf8Path {
self.file_path
}
@@ -186,7 +186,7 @@ where
}
}
-impl<'a, R> Deref for RuleContext<'a, R>
+impl Deref for RuleContext<'_, R>
where
R: Rule,
{
diff --git a/crates/biome_analyze/src/diagnostics.rs b/crates/biome_analyze/src/diagnostics.rs
index 44cf2631c876..436653d4c29e 100644
--- a/crates/biome_analyze/src/diagnostics.rs
+++ b/crates/biome_analyze/src/diagnostics.rs
@@ -1,11 +1,11 @@
-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, MessageAndDescription, Severity, Visit,
};
use biome_rowan::TextRange;
use std::borrow::Cow;
-use std::fmt::{Debug, Display, Formatter};
+use std::fmt::{Debug, Formatter};
use crate::rule::RuleDiagnostic;
@@ -65,7 +65,7 @@ impl Diagnostic for AnalyzerDiagnostic {
fn severity(&self) -> Severity {
match &self.kind {
- DiagnosticKind::Rule { .. } => Severity::Error,
+ DiagnosticKind::Rule(diagnostic) => diagnostic.severity(),
DiagnosticKind::Raw(error) => error.severity(),
}
}
@@ -141,38 +141,64 @@ impl AnalyzerDiagnostic {
#[derive(Debug, Diagnostic, Clone)]
#[diagnostic(severity = Warning)]
-pub struct SuppressionDiagnostic {
+pub struct AnalyzerSuppressionDiagnostic {
#[category]
category: &'static Category,
#[location(span)]
range: TextRange,
#[message]
#[description]
- message: String,
+ message: MessageAndDescription,
#[tags]
tags: DiagnosticTags,
+
+ #[advice]
+ advice: SuppressionAdvice,
}
-impl SuppressionDiagnostic {
+impl AnalyzerSuppressionDiagnostic {
pub(crate) fn new(
category: &'static Category,
range: TextRange,
- message: impl Display,
+ message: impl biome_console::fmt::Display,
) -> Self {
Self {
category,
range,
- message: message.to_string(),
+ message: MessageAndDescription::from(markup! { {message} }.to_owned()),
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: MarkupBuf, range: impl Into) -> Self {
+ self.advice.messages.push((message, Some(range.into())));
+ self
+ }
+
+ pub(crate) fn hint(mut self, message: MarkupBuf) -> Self {
+ self.advice.messages.push((message, None));
self
}
}
+#[derive(Debug, Default, Clone)]
+struct SuppressionAdvice {
+ messages: Vec<(MarkupBuf, Option)>,
+}
+
+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))]
diff --git a/crates/biome_analyze/src/lib.rs b/crates/biome_analyze/src/lib.rs
index 561adeb90e4f..21d7831aa0cf 100644
--- a/crates/biome_analyze/src/lib.rs
+++ b/crates/biome_analyze/src/lib.rs
@@ -1,12 +1,12 @@
#![deny(rustdoc::broken_intra_doc_links)]
-use std::borrow::Cow;
-use std::cmp::Ordering;
+use biome_console::markup;
+use biome_parser::AnyParse;
use std::collections::{BTreeMap, BinaryHeap};
use std::fmt::{Debug, Display, Formatter};
use std::ops;
-use tracing::trace;
+mod analyzer_plugin;
mod categories;
pub mod context;
mod diagnostics;
@@ -18,17 +18,20 @@ mod rule;
mod services;
mod signals;
mod suppression_action;
+mod suppressions;
mod syntax;
mod visitor;
// Re-exported for use in the `declare_group` macro
pub use biome_diagnostics::category_concat;
+pub use crate::analyzer_plugin::AnalyzerPlugin;
pub use crate::categories::{
- ActionCategory, RefactorKind, RuleCategories, RuleCategoriesBuilder, RuleCategory,
- SourceActionKind, SUPPRESSION_ACTION_CATEGORY,
+ ActionCategory, OtherActionCategory, RefactorKind, RuleCategories, RuleCategoriesBuilder,
+ RuleCategory, SourceActionKind, SUPPRESSION_INLINE_ACTION_CATEGORY,
+ SUPPRESSION_TOP_LEVEL_ACTION_CATEGORY,
};
-pub use crate::diagnostics::{AnalyzerDiagnostic, RuleError, SuppressionDiagnostic};
+pub use crate::diagnostics::{AnalyzerDiagnostic, AnalyzerSuppressionDiagnostic, RuleError};
pub use crate::matcher::{InspectMatcher, MatchQueryParams, QueryMatcher, RuleKey, SignalEntry};
pub use crate::options::{AnalyzerConfiguration, AnalyzerOptions, AnalyzerRules};
pub use crate::query::{AddVisitor, QueryKey, QueryMatch, Queryable};
@@ -38,34 +41,38 @@ pub use crate::registry::{
};
pub use crate::rule::{
CategoryLanguage, FixKind, GroupCategory, GroupLanguage, Rule, RuleAction, RuleDiagnostic,
- RuleGroup, RuleMeta, RuleMetadata, RuleSource, RuleSourceKind, SuppressAction,
+ RuleDomain, RuleGroup, RuleMeta, RuleMetadata, RuleSource, RuleSourceKind, SuppressAction,
};
pub use crate::services::{FromServices, MissingServicesDiagnostic, ServiceBag};
pub use crate::signals::{
AnalyzerAction, AnalyzerSignal, AnalyzerTransformation, DiagnosticSignal,
};
+use crate::suppressions::Suppressions;
pub use crate::syntax::{Ast, SyntaxVisitor};
pub use crate::visitor::{NodeVisitor, Visitor, VisitorContext, VisitorFinishContext};
-pub use suppression_action::{ApplySuppression, SuppressionAction};
-
-use biome_console::markup;
-use biome_diagnostics::{
- category, Applicability, Diagnostic, DiagnosticExt, DiagnosticTags, Severity,
-};
+use biome_diagnostics::{category, Diagnostic, DiagnosticExt};
use biome_rowan::{
- AstNode, BatchMutation, Direction, Language, SyntaxElement, SyntaxToken, TextLen, TextRange,
- TextSize, TokenAtOffset, TriviaPiece, TriviaPieceKind, WalkEvent,
+ AstNode, BatchMutation, Direction, Language, SyntaxElement, SyntaxToken, TextRange, TextSize,
+ TokenAtOffset, TriviaPieceKind, WalkEvent,
};
+use biome_suppression::{Suppression, SuppressionKind};
+pub use suppression_action::{ApplySuppression, SuppressionAction};
/// The analyzer is the main entry point into the `biome_analyze` infrastructure.
/// Its role is to run a collection of [Visitor]s over a syntax tree, with each
/// visitor implementing various analysis over this syntax tree to generate
/// auxiliary data structures as well as emit "query match" events to be
/// processed by lint rules and in turn emit "analyzer signals" in the form of
-/// diagnostics, code actions or both
+/// diagnostics, code actions or both.
+/// The analyzer also has support for plugins, although do not (as of yet)
+/// support the same visitor pattern. This makes them slower to execute, but
+/// otherwise they act the same for consumers of the analyzer. They respect the
+/// same suppression comments, and report signals in the same format.
pub struct Analyzer<'analyzer, L: Language, Matcher, Break, Diag> {
/// List of visitors being run by this instance of the analyzer for each phase
phases: BTreeMap + 'analyzer>>>,
+ /// Plugins to be run after the phases for built-in rules.
+ plugins: Vec>,
/// Holds the metadata for all the rules statically known to the analyzer
metadata: &'analyzer MetadataRegistry,
/// Executor for the query matches emitted by the visitors
@@ -87,7 +94,7 @@ pub struct AnalyzerContext<'a, L: Language> {
impl<'analyzer, L, Matcher, Break, Diag> Analyzer<'analyzer, L, Matcher, Break, Diag>
where
- L: Language,
+ L: Language + 'static,
Matcher: QueryMatcher,
Diag: Diagnostic + Clone + Send + Sync + 'static,
{
@@ -102,6 +109,7 @@ where
) -> Self {
Self {
phases: BTreeMap::new(),
+ plugins: Vec::new(),
metadata,
query_matcher,
parse_suppression_comment,
@@ -119,35 +127,40 @@ where
self.phases.entry(phase).or_default().push(visitor);
}
+ /// Registers an [AnalyzerPlugin] to be executed after the regular phases.
+ pub fn add_plugin(&mut self, plugin: Box) {
+ self.plugins.push(plugin);
+ }
+
pub fn run(self, mut ctx: AnalyzerContext) -> Option {
let Self {
phases,
- metadata,
+ plugins,
mut query_matcher,
parse_suppression_comment,
mut emit_signal,
suppression_action,
+ metadata: _,
} = self;
let mut line_index = 0;
- let mut line_suppressions = Vec::new();
+ let mut suppressions = Suppressions::new(self.metadata);
for (index, (phase, mut visitors)) in phases.into_iter().enumerate() {
let runner = PhaseRunner {
phase,
visitors: &mut visitors,
- metadata,
query_matcher: &mut query_matcher,
signal_queue: BinaryHeap::new(),
parse_suppression_comment,
line_index: &mut line_index,
- line_suppressions: &mut line_suppressions,
emit_signal: &mut emit_signal,
root: &ctx.root,
services: &ctx.services,
range: ctx.range,
suppression_action: suppression_action.as_ref(),
options: ctx.options,
+ suppressions: &mut suppressions,
};
// The first phase being run will inspect the tokens and parse the
@@ -174,17 +187,60 @@ where
}
}
- for suppression in line_suppressions {
+ for plugin in plugins {
+ let root: AnyParse = ctx.root.syntax().as_send().expect("not a root node").into();
+ for diagnostic in plugin.evaluate(root, ctx.options.file_path.clone()) {
+ let signal = DiagnosticSignal::new(|| diagnostic.clone());
+
+ if let ControlFlow::Break(br) = (emit_signal)(&signal) {
+ return Some(br);
+ }
+ }
+ }
+
+ for range_suppression in suppressions.range_suppressions.suppressions {
+ if range_suppression.did_suppress_signal {
+ continue;
+ }
+ if let Some(range) = range_suppression.already_suppressed {
+ let signal = DiagnosticSignal::new(|| {
+ AnalyzerSuppressionDiagnostic::new(
+ category!("suppressions/unused"),
+ range_suppression.start_comment_range,
+ "Suppression comment has no effect because another suppression comment suppresses the same rule.",
+ ).note(
+ markup!{"This is the suppression comment that was used."}.to_owned(),
+ range
+ )
+ });
+ if let ControlFlow::Break(br) = (emit_signal)(&signal) {
+ return Some(br);
+ }
+ }
+ }
+
+ for suppression in suppressions.line_suppressions {
if suppression.did_suppress_signal {
continue;
}
let signal = DiagnosticSignal::new(|| {
- SuppressionDiagnostic::new(
+ if let Some(range) = suppression.already_suppressed {
+ AnalyzerSuppressionDiagnostic::new(
+ category!("suppressions/unused"),
+ suppression.comment_span,
+ "Suppression comment has no effect because another suppression comment suppresses the same rule.",
+ ).note(
+ markup!{"This is the suppression comment that was used."}.to_owned(),
+ range
+ )
+ } else {
+ AnalyzerSuppressionDiagnostic::new(
category!("suppressions/unused"),
suppression.comment_span,
"Suppression comment has no effect. Remove the suppression or make sure you are suppressing the correct rule.",
- )
+ )
+ }
});
if let ControlFlow::Break(br) = (emit_signal)(&signal) {
@@ -202,8 +258,6 @@ struct PhaseRunner<'analyzer, 'phase, L: Language, Matcher, Break, Diag> {
phase: Phases,
/// List of visitors being run by this instance of the analyzer for each phase
visitors: &'phase mut [Box + 'analyzer>],
- /// Holds the metadata for all the rules statically known to the analyzer
- metadata: &'analyzer MetadataRegistry,
/// Executor for the query matches emitted by the visitors
query_matcher: &'phase mut Matcher,
/// Queue for pending analyzer signals
@@ -214,8 +268,6 @@ struct PhaseRunner<'analyzer, 'phase, L: Language, Matcher, Break, Diag> {
suppression_action: &'phase dyn SuppressionAction,
/// Line index at the current position of the traversal
line_index: &'phase mut usize,
- /// Track active suppression comments per-line, ordered by line index
- line_suppressions: &'phase mut Vec,
/// Handles analyzer signals emitted by individual rules
emit_signal: &'phase mut SignalHandler<'analyzer, L, Break>,
/// Root node of the file being analyzed
@@ -226,31 +278,11 @@ struct PhaseRunner<'analyzer, 'phase, L: Language, Matcher, Break, Diag> {
range: Option,
/// Analyzer options
options: &'phase AnalyzerOptions,
+ /// Tracks all suppressions during the analyzer phase
+ suppressions: &'phase mut Suppressions<'analyzer>,
}
-/// Single entry for a suppression comment in the `line_suppressions` buffer
-#[derive(Debug)]
-struct LineSuppression {
- /// Line index this comment is suppressing lint rules for
- line_index: usize,
- /// Range of source text covered by the suppression comment
- comment_span: TextRange,
- /// Range of source text this comment is suppressing lint rules for
- text_range: TextRange,
- /// Set to true if this comment has set the `suppress_all` flag to true
- /// (must be restored to false on expiration)
- suppress_all: bool,
- /// List of all the rules this comment has started suppressing (must be
- /// removed from the suppressed set on expiration)
- suppressed_rules: Vec>,
- /// List of all the rule instances this comment has started suppressing.
- suppressed_instances: Vec<(RuleFilter<'static>, String)>,
- /// Set to `true` when a signal matching this suppression was emitted and
- /// suppressed
- did_suppress_signal: bool,
-}
-
-impl<'a, 'phase, L, Matcher, Break, Diag> PhaseRunner<'a, 'phase, L, Matcher, Break, Diag>
+impl PhaseRunner<'_, '_, L, Matcher, Break, Diag>
where
L: Language,
Matcher: QueryMatcher,
@@ -259,7 +291,6 @@ where
/// Runs phase 0 over nodes and tokens to process line breaks and
/// suppression comments
fn run_first_phase(mut self) -> ControlFlow {
- trace!("Running first analyzer phase");
let iter = self.root.syntax().preorder_with_tokens(Direction::Next);
for event in iter {
let node_event = match event {
@@ -326,7 +357,7 @@ where
/// Process the text for a single token, parsing suppression comments and
/// handling line breaks, then flush all pending query signals in the queue
- /// whose position is less then the end of the token within the file
+ /// whose position is less than the end of the token within the file
fn handle_token(&mut self, token: SyntaxToken) -> ControlFlow {
// Process the content of the token for comments and newline
for (index, piece) in token.leading_trivia().pieces().enumerate() {
@@ -377,30 +408,54 @@ where
}
}
- // Search for an active suppression comment covering the range of
+ if self
+ .suppressions
+ .top_level_suppression
+ .suppressed_rule(&entry.rule)
+ || self.suppressions.top_level_suppression.suppress_all
+ {
+ self.signal_queue.pop();
+ break;
+ }
+
+ if self
+ .suppressions
+ .range_suppressions
+ .suppressed_rule(&entry.rule, &entry.text_range)
+ {
+ self.signal_queue.pop();
+ break;
+ }
+
+ // Search for an active line suppression comment covering the range of
// this signal: first try to load the last line suppression and see
// if it matches the current line index, otherwise perform a binary
// search over all the previously seen suppressions to find one
// with a matching range
- let suppression = self.line_suppressions.last_mut().filter(|suppression| {
- suppression.line_index == *self.line_index
- && suppression.text_range.start() <= start
- });
+ let suppression =
+ self.suppressions
+ .line_suppressions
+ .last_mut()
+ .filter(|suppression| {
+ suppression.line_index == *self.line_index
+ && suppression.text_range.start() <= start
+ });
let suppression = match suppression {
Some(suppression) => Some(suppression),
None => {
- let index = self.line_suppressions.binary_search_by(|suppression| {
- if suppression.text_range.end() < entry.text_range.start() {
- Ordering::Less
- } else if entry.text_range.end() < suppression.text_range.start() {
- Ordering::Greater
- } else {
- Ordering::Equal
- }
- });
-
- index.ok().map(|index| &mut self.line_suppressions[index])
+ let index =
+ self.suppressions
+ .line_suppressions
+ .partition_point(|suppression| {
+ suppression.text_range.end() < entry.text_range.start()
+ });
+
+ if index >= self.suppressions.line_suppressions.len() {
+ None
+ } else {
+ Some(&mut self.suppressions.line_suppressions[index])
+ }
}
};
@@ -408,28 +463,22 @@ where
if suppression.suppress_all {
return true;
}
-
- if suppression
- .suppressed_rules
- .iter()
- .any(|filter| *filter == entry.rule)
- {
- return true;
- }
-
- if entry.instances.is_empty() {
- return false;
- }
-
- entry.instances.iter().all(|value| {
+ if suppression.suppressed_instances.is_empty() {
suppression
- .suppressed_instances
+ .suppressed_rules
.iter()
- .any(|(filter, v)| *filter == entry.rule && v == value.as_ref())
- })
+ .any(|filter| *filter == entry.rule)
+ } else {
+ entry.instances.iter().all(|value| {
+ suppression
+ .suppressed_instances
+ .iter()
+ .any(|(v, filter)| *filter == entry.rule && v == value.as_ref())
+ })
+ }
});
- // If the signal is being suppressed mark the line suppression as
+ // If the signal is being suppressed, mark the line suppression as
// hit, otherwise emit the signal
if let Some(suppression) = suppression {
suppression.did_suppress_signal = true;
@@ -449,18 +498,15 @@ where
fn handle_comment(
&mut self,
token: &SyntaxToken,
- is_leading: bool,
- index: usize,
+ _is_leading: bool,
+ _index: usize,
text: &str,
range: TextRange,
) -> ControlFlow {
- let mut suppress_all = false;
- let mut suppressed_rules = Vec::new();
- let mut suppressed_instances = Vec::new();
- let mut has_legacy = false;
+ let mut has_suppressions = false;
- for result in (self.parse_suppression_comment)(text) {
- let kind = match result {
+ for result in (self.parse_suppression_comment)(text, range) {
+ let suppression = match result {
Ok(kind) => kind,
Err(diag) => {
// Emit the suppression parser diagnostic
@@ -475,136 +521,26 @@ where
}
};
- if matches!(kind, SuppressionKind::Deprecated) {
- let signal = DiagnosticSignal::new(move || {
- SuppressionDiagnostic::new(
- category!("suppressions/deprecatedSuppressionComment"),
- range,
- "// rome-ignore is deprecated, use // biome-ignore instead",
- )
- .with_tags(DiagnosticTags::DEPRECATED_CODE)
- .with_severity(Severity::Information)
- })
- .with_action(move || create_suppression_comment_action(token));
-
+ if let Err(diagnostic) =
+ self.suppressions
+ .push_suppression(&suppression, range, token.text_range())
+ {
+ let signal = DiagnosticSignal::new(|| diagnostic.clone());
(self.emit_signal)(&signal)?;
+ continue;
}
- let (rule, instance) = match kind {
- SuppressionKind::Everything => (None, None),
- SuppressionKind::Rule(rule) => (Some(rule), None),
- SuppressionKind::RuleInstance(rule, instance) => (Some(rule), Some(instance)),
- SuppressionKind::MaybeLegacy(rule) => (Some(rule), None),
- SuppressionKind::Deprecated => (None, None),
- };
-
- if let Some(rule) = rule {
- let group_rule = rule.split_once('/');
-
- let key = match group_rule {
- None => self.metadata.find_group(rule).map(RuleFilter::from),
- Some((group, rule)) => {
- self.metadata.find_rule(group, rule).map(RuleFilter::from)
- }
- };
-
- match (key, instance) {
- (Some(key), Some(value)) => suppressed_instances.push((key, value.to_owned())),
- (Some(key), None) => {
- suppressed_rules.push(key);
- has_legacy |= matches!(kind, SuppressionKind::MaybeLegacy(_));
- }
- _ if range_match(self.range, range) => {
- // Emit a warning for the unknown rule
- let signal = DiagnosticSignal::new(move || match group_rule {
- Some((group, rule)) => SuppressionDiagnostic::new(
- category!("suppressions/unknownRule"),
- range,
- format_args!(
- "Unknown lint rule {group}/{rule} in suppression comment"
- ),
- ),
-
- None => SuppressionDiagnostic::new(
- category!("suppressions/unknownGroup"),
- range,
- format_args!(
- "Unknown lint rule group {rule} in suppression comment"
- ),
- ),
- });
-
- (self.emit_signal)(&signal)?;
- }
- _ => {}
- }
- } else {
- suppressed_rules.clear();
- suppress_all = true;
- // If this if a "suppress all lints" comment, no need to
- // parse anything else
- break;
- }
- }
-
- // Emit a warning for legacy suppression syntax
- if has_legacy && range_match(self.range, range) {
- let signal = DiagnosticSignal::new(move || {
- SuppressionDiagnostic::new(
- category!("suppressions/deprecatedSuppressionComment"),
- range,
- "Suppression is using a deprecated syntax",
- )
- .with_tags(DiagnosticTags::DEPRECATED_CODE)
- });
-
- let signal = signal
- .with_action(|| update_suppression(self.root, token, is_leading, index, text));
-
- (self.emit_signal)(&signal)?;
- }
-
- if !suppress_all && suppressed_rules.is_empty() && suppressed_instances.is_empty() {
- return ControlFlow::Continue(());
+ has_suppressions = true;
}
// Suppression comments apply to the next line
- let line_index = *self.line_index + 1;
+ if has_suppressions {
+ let line_index = *self.line_index + 1;
- // If the last suppression was on the same or previous line, extend its
- // range and set of suppressed rules with the content for the new suppression
- if let Some(last_suppression) = self.line_suppressions.last_mut() {
- if last_suppression.line_index == line_index
- || last_suppression.line_index + 1 == line_index
- {
- last_suppression.line_index = line_index;
- last_suppression.text_range = last_suppression.text_range.cover(range);
- last_suppression.suppress_all |= suppress_all;
- if !last_suppression.suppress_all {
- last_suppression.suppressed_rules.extend(suppressed_rules);
- last_suppression
- .suppressed_instances
- .extend(suppressed_instances);
- } else {
- last_suppression.suppressed_rules.clear();
- last_suppression.suppressed_instances.clear();
- }
- return ControlFlow::Continue(());
- }
+ self.suppressions
+ .overlap_last_suppression(line_index, range);
}
- let entry = LineSuppression {
- line_index,
- comment_span: range,
- text_range: range,
- suppress_all,
- suppressed_rules,
- suppressed_instances,
- did_suppress_signal: false,
- };
-
- self.line_suppressions.push(entry);
-
ControlFlow::Continue(())
}
@@ -613,168 +549,173 @@ where
/// current suppression as required
fn bump_line_index(&mut self, text: &str, range: TextRange) {
let mut did_match = false;
- for (index, _) in text.match_indices('\n') {
- if let Some(last_suppression) = self.line_suppressions.last_mut() {
- if last_suppression.line_index == *self.line_index {
- let index = TextSize::try_from(index).expect(
- "integer overflow while converting a suppression line to `TextSize`",
- );
- let range = TextRange::at(range.start(), index);
- last_suppression.text_range = last_suppression.text_range.cover(range);
- did_match = true;
- }
- }
+ for (index, _) in text.match_indices(['\n']) {
+ let index = TextSize::try_from(index)
+ .expect("integer overflow while converting a suppression line to `TextSize`");
+ let range = TextRange::at(range.start(), index);
+ did_match = self.suppressions.expand_range(range, *self.line_index);
*self.line_index += 1;
+ self.suppressions.bump_line_index(*self.line_index);
}
if !did_match {
- if let Some(last_suppression) = self.line_suppressions.last_mut() {
- if last_suppression.line_index == *self.line_index {
- last_suppression.text_range = last_suppression.text_range.cover(range);
- }
- }
+ self.suppressions.expand_range(range, *self.line_index);
}
}
}
-fn create_suppression_comment_action(
- token: &SyntaxToken,
-) -> Option> {
- let first_node = token.parent()?;
- let mut new_leading_trivia = vec![];
- let mut token_text = String::new();
- let mut new_trailing_trivia = vec![];
- let mut mutation = BatchMutation::new(first_node);
-
- for piece in token.leading_trivia().pieces() {
- if !piece.is_comments() {
- new_leading_trivia.push(TriviaPiece::new(piece.kind(), piece.text_len()));
- token_text.push_str(piece.text());
- }
-
- if piece.text().contains("rome-ignore") {
- let new_text = piece.text().replace("rome-ignore", "biome-ignore");
- new_leading_trivia.push(TriviaPiece::new(piece.kind(), new_text.text_len()));
- token_text.push_str(&new_text);
- }
- }
-
- token_text.push_str(token.text_trimmed());
-
- for piece in token.trailing_trivia().pieces() {
- new_trailing_trivia.push(TriviaPiece::new(piece.kind(), piece.text_len()));
- token_text.push_str(piece.text());
- }
-
- let new_token = SyntaxToken::new_detached(
- token.kind(),
- &token_text,
- new_leading_trivia,
- new_trailing_trivia,
- );
-
- mutation.replace_token_discard_trivia(token.clone(), new_token);
- Some(AnalyzerAction {
- mutation,
- applicability: Applicability::MaybeIncorrect,
- category: ActionCategory::QuickFix(Cow::Borrowed("")),
- message: markup! {
- "Use // biome-ignore instead"
- }
- .to_owned(),
- rule_name: None,
- })
-}
-
fn range_match(filter: Option, range: TextRange) -> bool {
filter.map_or(true, |filter| filter.intersect(range).is_some())
}
/// Signature for a suppression comment parser function
///
-/// This function receives the text content of a comment and returns a list of
-/// lint suppressions as an optional lint rule (if the lint rule is `None` the
+/// This function receives two parameters:
+/// 1. The text content of a comment.
+/// 2. The range of the token the comment belongs too. The range is calculated from [SyntaxToken::text_range], so the range
+/// includes all trivia.
+///
+/// It returns the lint suppressions as an optional lint rule (if the lint rule is `None` the
/// comment is interpreted as suppressing all lints)
///
/// # Examples
///
/// - `// biome-ignore format` -> `vec![]`
/// - `// biome-ignore lint` -> `vec![Everything]`
-/// - `// biome-ignore lint/style/useWhile` -> `vec![Rule("style/useWhile")]`
-/// - `// biome-ignore lint/style/useWhile(foo)` -> `vec![RuleWithValue("style/useWhile", "foo")]`
-/// - `// biome-ignore lint/style/useWhile lint/nursery/noUnreachable` -> `vec![Rule("style/useWhile"), Rule("nursery/noUnreachable")]`
-/// - `// biome-ignore lint(style/useWhile)` -> `vec![MaybeLegacy("style/useWhile")]`
-/// - `// biome-ignore lint(style/useWhile) lint(nursery/noUnreachable)` -> `vec![MaybeLegacy("style/useWhile"), MaybeLegacy("nursery/noUnreachable")]`
-type SuppressionParser = fn(&str) -> Vec>;
-
+/// - `// biome-ignore lint/complexity/useWhile` -> `vec![Rule("complexity/useWhile")]`
+/// - `// biome-ignore lint/complexity/useWhile(foo)` -> `vec![RuleWithValue("complexity/useWhile", "foo")]`
+/// - `// biome-ignore lint/complexity/useWhile lint/nursery/noUnreachable` -> `vec![Rule("complexity/useWhile"), Rule("nursery/noUnreachable")]`
+/// - `/** biome-ignore lint/complexity/useWhile */` if the comment is top-level -> `vec![TopLevel("complexity/useWhile")]`
+type SuppressionParser =
+ for<'a> fn(&'a str, TextRange) -> Vec, D>>;
+
+#[derive(Debug, Clone)]
/// This enum is used to categorize what is disabled by a suppression comment and with what syntax
-pub enum SuppressionKind<'a> {
+pub struct AnalyzerSuppression<'a> {
+ /// The kind of suppression
+ pub(crate) kind: AnalyzerSuppressionKind<'a>,
+
+ /// The range where the `biome-ignore` comment is placed inside the whole text
+ pub(crate) ignore_range: Option,
+
+ /// The kind of `biome-ignore` comment used for this suppression
+ pub(crate) variant: AnalyzerSuppressionVariant,
+}
+
+#[derive(Debug, Clone)]
+pub enum AnalyzerSuppressionVariant {
+ /// biome-ignore
+ Line,
+ /// biome-ignore-all
+ TopLevel,
+ /// biome-ignore-start
+ RangeStart,
+ /// biome-ignore-end
+ RangeEnd,
+}
+
+impl From<&SuppressionKind> for AnalyzerSuppressionVariant {
+ fn from(value: &SuppressionKind) -> Self {
+ match value {
+ SuppressionKind::Classic => AnalyzerSuppressionVariant::Line,
+ SuppressionKind::All => AnalyzerSuppressionVariant::TopLevel,
+ SuppressionKind::RangeStart => AnalyzerSuppressionVariant::RangeStart,
+ SuppressionKind::RangeEnd => AnalyzerSuppressionVariant::RangeEnd,
+ }
+ }
+}
+
+impl<'a> AnalyzerSuppression<'a> {
+ pub fn everything() -> Self {
+ Self {
+ kind: AnalyzerSuppressionKind::Everything,
+ ignore_range: None,
+ variant: AnalyzerSuppressionVariant::Line,
+ }
+ }
+
+ pub fn rule_instance(rule: &'a str, instance: &'a str) -> Self {
+ Self {
+ kind: AnalyzerSuppressionKind::RuleInstance(rule, instance),
+ ignore_range: None,
+ variant: AnalyzerSuppressionVariant::Line,
+ }
+ }
+ pub fn rule(rule: &'a str) -> Self {
+ Self {
+ kind: AnalyzerSuppressionKind::Rule(rule),
+ ignore_range: None,
+ variant: AnalyzerSuppressionVariant::Line,
+ }
+ }
+
+ #[must_use]
+ pub fn with_ignore_range(mut self, ignore_range: TextRange) -> Self {
+ self.ignore_range = Some(ignore_range);
+ self
+ }
+
+ #[must_use]
+ pub fn with_variant(mut self, variant: impl Into) -> Self {
+ self.variant = variant.into();
+ self
+ }
+}
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum AnalyzerSuppressionKind<'a> {
/// A suppression disabling all lints eg. `// biome-ignore lint`
Everything,
- /// A suppression disabling a specific rule eg. `// biome-ignore lint/style/useWhile`
+ /// A suppression disabling a specific rule eg. `// biome-ignore lint/complexity/useWhile`
Rule(&'a str),
/// A suppression to be evaluated by a specific rule eg. `// biome-ignore lint/correctness/useExhaustiveDependencies(foo)`
RuleInstance(&'a str, &'a str),
- /// A suppression using the legacy syntax to disable a specific rule eg. `// biome-ignore lint(style/useWhile)`
- MaybeLegacy(&'a str),
- /// `rome-ignore` is legacy
- Deprecated,
}
-fn update_suppression(
- root: &L::Root,
- token: &SyntaxToken,
- is_leading: bool,
- index: usize,
- text: &str,
-) -> Option> {
- let old_token = token.clone();
- let new_token = token.clone().detach();
-
- let old_trivia = if is_leading {
- old_token.leading_trivia()
- } else {
- old_token.trailing_trivia()
- };
-
- let old_trivia: Vec<_> = old_trivia.pieces().collect();
-
- let mut text = text.to_string();
-
- while let Some(range_start) = text.find("lint(") {
- let range_end = range_start + text[range_start..].find(')')?;
- text.replace_range(range_end..range_end + 1, "");
- text.replace_range(range_start + 4..range_start + 5, "/");
- }
-
- let new_trivia = old_trivia.iter().enumerate().map(|(piece_index, piece)| {
- if piece_index == index {
- (piece.kind(), text.as_str())
+/// Takes a [Suppression] and returns a [AnalyzerSuppression]
+pub fn to_analyzer_suppressions(
+ suppression: Suppression,
+ piece_range: TextRange,
+) -> Vec {
+ let mut result = Vec::with_capacity(suppression.categories.len());
+ let ignore_range = TextRange::new(
+ piece_range.add_start(suppression.range().start()).start(),
+ piece_range.add_start(suppression.range().end()).start(),
+ );
+ for (key, value) in suppression.categories {
+ if key == category!("lint") {
+ result.push(AnalyzerSuppression::everything().with_variant(&suppression.kind));
} else {
- (piece.kind(), piece.text())
- }
- });
-
- let new_token = if is_leading {
- new_token.with_leading_trivia(new_trivia)
- } else {
- new_token.with_trailing_trivia(new_trivia)
- };
-
- let mut mutation = BatchMutation::new(root.syntax().clone());
- mutation.replace_token_discard_trivia(old_token, new_token);
-
- Some(AnalyzerAction {
- rule_name: None,
- category: ActionCategory::QuickFix(Cow::Borrowed("")),
- applicability: Applicability::Always,
- message: markup! {
- "Rewrite suppression to use the newer syntax"
+ let category = key.name();
+ if let Some(rule) = category.strip_prefix("lint/") {
+ let suppression = if let Some(instance) = value {
+ AnalyzerSuppression::rule_instance(rule, instance)
+ .with_ignore_range(ignore_range)
+ } else {
+ AnalyzerSuppression::rule(rule).with_ignore_range(ignore_range)
+ }
+ .with_variant(&suppression.kind);
+ result.push(suppression);
+ }
}
- .to_owned(),
- mutation,
- })
+ }
+
+ result
+}
+
+impl AnalyzerSuppression<'_> {
+ pub const fn is_top_level(&self) -> bool {
+ matches!(self.variant, AnalyzerSuppressionVariant::TopLevel)
+ }
+ pub const fn is_range_start(&self) -> bool {
+ matches!(self.variant, AnalyzerSuppressionVariant::RangeStart)
+ }
+ pub const fn is_range_end(&self) -> bool {
+ matches!(self.variant, AnalyzerSuppressionVariant::RangeEnd)
+ }
+ pub const fn is_line(&self) -> bool {
+ matches!(self.variant, AnalyzerSuppressionVariant::Line)
+ }
}
/// Payload received by the function responsible to mark a suppression comment
@@ -783,7 +724,7 @@ pub struct SuppressionCommentEmitterPayload<'a, L: Language> {
pub token_offset: TokenAtOffset>,
/// A [BatchMutation] where the consumer can apply the suppression comment
pub mutation: &'a mut BatchMutation,
- /// A string equals to "rome-ignore: lint(/)"
+ /// A string equals to "biome-ignore: lint(/)"
pub suppression_text: &'a str,
/// The original range of the diagnostic where the rule was triggered
pub diagnostic_text_range: &'a TextRange,
@@ -830,13 +771,13 @@ impl<'a> RuleFilter<'a> {
}
}
-impl<'a> Debug for RuleFilter<'a> {
+impl Debug for RuleFilter<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
-impl<'a> Display for RuleFilter<'a> {
+impl Display for RuleFilter<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
RuleFilter::Group(group) => {
@@ -849,7 +790,7 @@ impl<'a> Display for RuleFilter<'a> {
}
}
-impl<'a> biome_console::fmt::Display for RuleFilter<'a> {
+impl biome_console::fmt::Display for RuleFilter<'_> {
fn fmt(&self, fmt: &mut biome_console::fmt::Formatter) -> std::io::Result<()> {
match self {
RuleFilter::Group(group) => {
diff --git a/crates/biome_analyze/src/matcher.rs b/crates/biome_analyze/src/matcher.rs
index ac0f3e5694f7..f70acc483f78 100644
--- a/crates/biome_analyze/src/matcher.rs
+++ b/crates/biome_analyze/src/matcher.rs
@@ -144,21 +144,21 @@ pub struct SignalEntry<'phase, L: Language> {
}
// SignalEntry is ordered based on the starting point of its `text_range`
-impl<'phase, L: Language> Ord for SignalEntry<'phase, L> {
+impl Ord for SignalEntry<'_, L> {
fn cmp(&self, other: &Self) -> Ordering {
other.text_range.start().cmp(&self.text_range.start())
}
}
-impl<'phase, L: Language> PartialOrd for SignalEntry<'phase, L> {
+impl PartialOrd for SignalEntry<'_, L> {
fn partial_cmp(&self, other: &Self) -> Option {
Some(self.cmp(other))
}
}
-impl<'phase, L: Language> Eq for SignalEntry<'phase, L> {}
+impl Eq for SignalEntry<'_, L> {}
-impl<'phase, L: Language> PartialEq for SignalEntry<'phase, L> {
+impl PartialEq for SignalEntry<'_, L> {
fn eq(&self, other: &Self) -> bool {
self.text_range.start() == other.text_range.start()
}
@@ -204,7 +204,7 @@ mod tests {
ControlFlow, MetadataRegistry, Never, Phases, QueryMatcher, RuleKey, ServiceBag,
SignalEntry, SuppressionAction, SyntaxVisitor,
};
- use crate::{AnalyzerOptions, SuppressionKind};
+ use crate::{AnalyzerOptions, AnalyzerSuppression};
use biome_diagnostics::{category, DiagnosticExt};
use biome_diagnostics::{Diagnostic, Severity};
use biome_rowan::{
@@ -350,12 +350,13 @@ mod tests {
};
fn parse_suppression_comment(
- comment: &'_ str,
- ) -> Vec, Infallible>> {
+ comment: &str,
+ _piece_range: TextRange,
+ ) -> Vec> {
comment
.trim_start_matches("//")
.split(' ')
- .map(SuppressionKind::Rule)
+ .map(AnalyzerSuppression::rule)
.map(Ok)
.collect()
}
@@ -368,14 +369,14 @@ mod tests {
impl SuppressionAction for TestAction {
type Language = RawLanguage;
- fn find_token_to_apply_suppression(
+ fn find_token_for_inline_suppression(
&self,
_: SyntaxToken,
) -> Option> {
None
}
- fn apply_suppression(
+ fn apply_inline_suppression(
&self,
_: &mut BatchMutation,
_: ApplySuppression,
@@ -384,6 +385,15 @@ mod tests {
) {
unreachable!("")
}
+
+ fn apply_top_level_suppression(
+ &self,
+ _: &mut BatchMutation,
+ _: SyntaxToken,
+ _: &str,
+ ) {
+ unreachable!("")
+ }
}
let mut analyzer = Analyzer::new(
diff --git a/crates/biome_analyze/src/options.rs b/crates/biome_analyze/src/options.rs
index 3394012ff1b6..357f33c1534f 100644
--- a/crates/biome_analyze/src/options.rs
+++ b/crates/biome_analyze/src/options.rs
@@ -1,9 +1,9 @@
+use camino::Utf8PathBuf;
use rustc_hash::FxHashMap;
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)]
@@ -55,42 +55,88 @@ 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,
+ globals: Vec>,
/// Allows to choose a different quote when applying fixes inside the lint rules
- pub preferred_quote: PreferredQuote,
+ preferred_quote: PreferredQuote,
/// Allows to choose a different JSX quote when applying fixes inside the lint rules
pub preferred_jsx_quote: PreferredQuote,
/// Indicates the type of runtime or transformation used for interpreting JSX.
- pub jsx_runtime: Option,
+ jsx_runtime: Option,
+}
+
+impl AnalyzerConfiguration {
+ pub fn with_rules(mut self, rules: AnalyzerRules) -> Self {
+ self.rules = rules;
+ self
+ }
+
+ pub fn with_globals(mut self, globals: Vec>) -> 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
+ }
+
+ pub fn with_preferred_jsx_quote(mut self, preferred_jsx_quote: PreferredQuote) -> Self {
+ self.preferred_jsx_quote = preferred_jsx_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,
+ pub(crate) configuration: AnalyzerConfiguration,
/// The file that is being analyzed
- pub file_path: PathBuf,
+ pub file_path: Utf8PathBuf,
/// Suppression reason used when applying a suppression code action
- pub suppression_reason: Option,
+ pub(crate) suppression_reason: Option,
}
impl AnalyzerOptions {
+ pub fn with_file_path(mut self, file_path: impl Into) -> 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<&str>) -> Self {
+ self.suppression_reason = reason.map(String::from);
+ self
+ }
+
+ pub fn push_globals(&mut self, globals: Vec>) {
+ self.configuration.globals.extend(globals);
+ }
+
pub fn globals(&self) -> Vec<&str> {
self.configuration
.globals
.iter()
- .map(|global| global.as_str())
+ .map(AsRef::as_ref)
.collect()
}
diff --git a/crates/biome_analyze/src/registry.rs b/crates/biome_analyze/src/registry.rs
index 0111d3c24b0f..f6ebffd7c398 100644
--- a/crates/biome_analyze/src/registry.rs
+++ b/crates/biome_analyze/src/registry.rs
@@ -402,20 +402,17 @@ impl RegistryRule {
let preferred_jsx_quote = params.options.preferred_jsx_quote();
let jsx_runtime = params.options.jsx_runtime();
let options = params.options.rule_options::().unwrap_or_default();
- let ctx = match RuleContext::new(
+ let ctx = RuleContext::new(
&query_result,
params.root,
params.services,
&globals,
- ¶ms.options.file_path,
+ params.options.file_path.as_path(),
&options,
preferred_quote,
preferred_jsx_quote,
jsx_runtime,
- ) {
- Ok(ctx) => ctx,
- Err(error) => return Err(error),
- };
+ )?;
for result in R::run(&ctx) {
let text_range =
diff --git a/crates/biome_analyze/src/rule.rs b/crates/biome_analyze/src/rule.rs
index b72c5e871ee5..acc1a8f0f282 100644
--- a/crates/biome_analyze/src/rule.rs
+++ b/crates/biome_analyze/src/rule.rs
@@ -4,15 +4,15 @@ use crate::registry::{RegistryVisitor, RuleLanguage, RuleSuppressions};
use crate::{
Phase, Phases, Queryable, SourceActionKind, SuppressionAction, SuppressionCommentEmitterPayload,
};
-use biome_console::fmt::Display;
-use biome_console::{markup, MarkupBuf};
+use biome_console::fmt::{Display, Formatter};
+use biome_console::{markup, MarkupBuf, Padding};
use biome_diagnostics::advice::CodeSuggestionAdvice;
use biome_diagnostics::location::AsSpan;
-use biome_diagnostics::Applicability;
use biome_diagnostics::{
Advices, Category, Diagnostic, DiagnosticTags, Location, LogCategory, MessageAndDescription,
Visit,
};
+use biome_diagnostics::{Applicability, Severity};
use biome_rowan::{AstNode, BatchMutation, BatchMutationExt, Language, TextRange};
use std::borrow::Cow;
use std::cmp::Ordering;
@@ -41,6 +41,159 @@ pub struct RuleMetadata {
pub sources: &'static [RuleSource],
/// The source kind of the rule
pub source_kind: Option,
+ /// The default severity of the rule
+ pub severity: Severity,
+ /// Domains applied by this rule
+ pub domains: &'static [RuleDomain],
+}
+
+impl biome_console::fmt::Display for RuleMetadata {
+ fn fmt(&self, fmt: &mut Formatter) -> std::io::Result<()> {
+ fmt.write_markup(markup! {
+ "Summary"
+ })?;
+ fmt.write_str("\n")?;
+ fmt.write_str("\n")?;
+
+ fmt.write_markup(markup! {
+ "- Name: "{self.name}
+ })?;
+ fmt.write_str("\n")?;
+ match self.fix_kind {
+ FixKind::None => {
+ fmt.write_markup(markup! {
+ "- No fix available."
+ })?;
+ }
+ kind => {
+ fmt.write_markup(markup! {
+ "- Fix: "{kind}
+ })?;
+ }
+ }
+ fmt.write_str("\n")?;
+
+ fmt.write_markup(markup! {
+ "- Default severity: "{self.severity}
+ })?;
+ fmt.write_str("\n")?;
+
+ fmt.write_markup(markup! {
+ "- Available from version: "{self.version}
+ })?;
+ fmt.write_str("\n")?;
+
+ if self.domains.is_empty() && self.recommended {
+ fmt.write_markup(markup! {
+ "- This rule is not recommended"
+ })?;
+ }
+
+ let domains = DisplayDomains(self.domains, self.recommended);
+
+ fmt.write_str("\n")?;
+
+ fmt.write_markup(markup!({ domains }))?;
+
+ fmt.write_str("\n")?;
+
+ fmt.write_markup(markup! {
+ "Description"
+ })?;
+ fmt.write_str("\n")?;
+ fmt.write_str("\n")?;
+
+ for line in self.docs.lines() {
+ if let Some((_, remainder)) = line.split_once("## ") {
+ fmt.write_markup(markup! {
+ {remainder.trim_start()}
+ })?;
+ } else if let Some((_, remainder)) = line.split_once("### ") {
+ fmt.write_markup(markup! {
+ {remainder.trim_start()}
+ })?;
+ } else {
+ fmt.write_str(line)?;
+ }
+
+ fmt.write_str("\n")?;
+ }
+
+ Ok(())
+ }
+}
+
+struct DisplayDomains(&'static [RuleDomain], bool);
+
+impl Display for DisplayDomains {
+ fn fmt(&self, fmt: &mut Formatter) -> std::io::Result<()> {
+ let domains = self.0;
+ let recommended = self.1;
+
+ if domains.is_empty() {
+ return Ok(());
+ }
+
+ fmt.write_markup(markup!(
+ "Domains"
+ ))?;
+ fmt.write_str("\n")?;
+ fmt.write_str("\n")?;
+
+ for domain in domains {
+ let dependencies = domain.manifest_dependencies();
+
+ fmt.write_markup(markup! {
+ "- Name: "{domain}
+ })?;
+ fmt.write_str("\n")?;
+
+ if recommended {
+ fmt.write_markup(markup! {
+ "- The rule is recommended for this domain"
+ })?;
+ fmt.write_str("\n")?;
+ }
+
+ if !dependencies.is_empty() {
+ fmt.write_markup(markup! {
+ "- The rule is enabled when one of these dependencies are detected:"
+ })?;
+ fmt.write_str("\n")?;
+ let padding = Padding::new(2);
+ for (index, (dep, range)) in dependencies.iter().enumerate() {
+ fmt.write_markup(
+ markup! { {padding}"- "{dep}"@"{range} },
+ )?;
+ if index + 1 < dependencies.len() {
+ fmt.write_str("\n")?;
+ }
+ }
+ fmt.write_str("\n")?;
+ }
+
+ let globals = domain.globals();
+
+ if !globals.is_empty() {
+ fmt.write_markup(markup! {
+ "- The rule adds the following globals: "
+ })?;
+ fmt.write_str("\n")?;
+
+ let padding = Padding::new(2);
+ for (index, global) in globals.iter().enumerate() {
+ fmt.write_markup(markup! { {padding}"- "{global} })?;
+ if index + 1 < globals.len() {
+ fmt.write_str("\n")?;
+ }
+ }
+ fmt.write_str("\n")?;
+ }
+ fmt.write_str("\n")?;
+ }
+
+ Ok(())
+ }
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
@@ -48,12 +201,12 @@ pub struct RuleMetadata {
feature = "serde",
derive(
biome_deserialize_macros::Deserializable,
- schemars::JsonSchema,
serde::Deserialize,
serde::Serialize
)
)]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
+#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
/// Used to identify the kind of code action emitted by a rule
pub enum FixKind {
/// The rule doesn't emit code actions.
@@ -69,9 +222,9 @@ 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"),
+ FixKind::None => fmt.write_markup(markup!("none")),
+ FixKind::Safe => fmt.write_markup(markup!("safe")),
+ FixKind::Unsafe => fmt.write_markup(markup!("unsafe")),
}
}
}
@@ -88,7 +241,8 @@ impl TryFrom for Applicability {
}
#[derive(Debug, Clone, Eq)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, schemars::JsonSchema))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize))]
+#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[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)
@@ -307,8 +461,9 @@ impl RuleSource {
}
#[derive(Debug, Default, Clone, Copy)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, schemars::JsonSchema))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
+#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum RuleSourceKind {
/// The rule implements the same logic of the source
#[default]
@@ -323,6 +478,81 @@ impl RuleSourceKind {
}
}
+/// Rule domains
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
+#[cfg_attr(
+ feature = "serde",
+ derive(
+ serde::Deserialize,
+ serde::Serialize,
+ biome_deserialize_macros::Deserializable
+ )
+)]
+#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
+#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
+pub enum RuleDomain {
+ /// React library rules
+ React,
+ /// Testing rules
+ Test,
+ /// Solid.js framework rules
+ Solid,
+ /// Next.js framework rules
+ Next,
+}
+
+impl Display for RuleDomain {
+ fn fmt(&self, fmt: &mut Formatter) -> std::io::Result<()> {
+ // use lower case naming, it needs to match the name of the configuration
+ match self {
+ RuleDomain::React => fmt.write_str("react"),
+ RuleDomain::Test => fmt.write_str("test"),
+ RuleDomain::Solid => fmt.write_str("solid"),
+ RuleDomain::Next => fmt.write_str("next"),
+ }
+ }
+}
+
+impl RuleDomain {
+ /// If the project has one of these dependencies, the domain will be automatically enabled, unless it's explicitly disabled by the configuration.
+ ///
+ /// If the array is empty, it means that the rules that belong to a certain domain won't enable themselves automatically.
+ pub const fn manifest_dependencies(self) -> &'static [&'static (&'static str, &'static str)] {
+ match self {
+ RuleDomain::React => &[&("react", ">=16.0.0")],
+ RuleDomain::Test => &[
+ &("jest", ">=26.0.0"),
+ &("mocha", ">=8.0.0"),
+ &("ava", ">=2.0.0"),
+ &("vitest", ">=1.0.0"),
+ ],
+ RuleDomain::Solid => &[&("solid", ">=1.0.0")],
+ RuleDomain::Next => &[&("next", ">=14.0.0")],
+ }
+ }
+
+ /// Global identifiers that should be added to the `globals` of the [crate::AnalyzerConfiguration] type
+ pub const fn globals(self) -> &'static [&'static str] {
+ match self {
+ RuleDomain::React => &[],
+ RuleDomain::Test => &[
+ "after",
+ "afterAll",
+ "afterEach",
+ "before",
+ "beforeEach",
+ "beforeAll",
+ "describe",
+ "it",
+ "expect",
+ "test",
+ ],
+ RuleDomain::Solid => &[],
+ RuleDomain::Next => &[],
+ }
+ }
+}
+
impl RuleMetadata {
pub const fn new(
version: &'static str,
@@ -340,6 +570,8 @@ impl RuleMetadata {
fix_kind: FixKind::None,
sources: &[],
source_kind: None,
+ severity: Severity::Information,
+ domains: &[],
}
}
@@ -376,6 +608,16 @@ impl RuleMetadata {
self
}
+ pub const fn severity(mut self, severity: Severity) -> Self {
+ self.severity = severity;
+ self
+ }
+
+ pub const fn domains(mut self, domains: &'static [RuleDomain]) -> Self {
+ self.domains = domains;
+ self
+ }
+
pub fn applicability(&self) -> Applicability {
self.fix_kind
.try_into()
@@ -488,6 +730,7 @@ macro_rules! declare_syntax_rule {
version: $version,
name: $name,
language: $language,
+ severity: biome_diagnostics::Severity::Error,
$( $key: $value, )*
}
);
@@ -566,7 +809,7 @@ macro_rules! declare_source_rule {
/// This macro returns the corresponding [ActionCategory] to use inside the [RuleAction]
#[expect(unused_macros)]
macro_rules! rule_action_category {
- () => { ActionCategory::Source(SourceActionKind::Other(Cow::Borrowed(concat!($language, ".", $name) ))) };
+ () => { biome_analyze::ActionCategory::Source(biome_analyze::SourceActionKind::Other(Cow::Borrowed($name))) };
}
};
}
@@ -623,7 +866,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 {}
@@ -648,7 +891,7 @@ macro_rules! declare_assists_group {
// "lint" prefix, the name of this group, and the rule name argument
#[expect(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
@@ -902,9 +1145,42 @@ pub trait Rule: RuleMeta + Sized {
None
}
+ fn top_level_suppression(
+ ctx: &RuleContext,
+ suppression_action: &dyn SuppressionAction>,
+ ) -> Option>>
+ where
+ Self: 'static,
+ {
+ if ::Category::CATEGORY == RuleCategory::Lint {
+ let rule_category = format!(
+ "lint/{}/{}",
+ ::NAME,
+ Self::METADATA.name
+ );
+ let suppression_text = format!("biome-ignore-all {rule_category}");
+ let root = ctx.root();
+
+ if let Some(first_token) = root.syntax().first_token() {
+ let mut mutation = root.begin();
+ suppression_action.apply_top_level_suppression(
+ &mut mutation,
+ first_token,
+ suppression_text.as_str(),
+ );
+ return Some(SuppressAction {
+ mutation,
+ message: markup! { "Suppress rule " {rule_category} " for the whole file."}
+ .to_owned(),
+ });
+ }
+ }
+ None
+ }
+
/// Create a code action that allows to suppress the rule. The function
/// returns the node to which the suppression comment is applied.
- fn suppress(
+ fn inline_suppression(
ctx: &RuleContext,
text_range: &TextRange,
suppression_action: &dyn SuppressionAction>,
@@ -924,7 +1200,7 @@ pub trait Rule: RuleMeta + Sized {
let root = ctx.root();
let token = root.syntax().token_at_offset(text_range.start());
let mut mutation = root.begin();
- suppression_action.apply_suppression_comment(SuppressionCommentEmitterPayload {
+ suppression_action.inline_suppression(SuppressionCommentEmitterPayload {
suppression_text: suppression_text.as_str(),
mutation: &mut mutation,
token_offset: token,
@@ -951,7 +1227,7 @@ pub trait Rule: RuleMeta + Sized {
}
/// Diagnostic object returned by a single analysis rule
-#[derive(Debug, Diagnostic)]
+#[derive(Clone, Debug, Diagnostic)]
pub struct RuleDiagnostic {
#[category]
pub(crate) category: &'static Category,
@@ -964,9 +1240,11 @@ pub struct RuleDiagnostic {
pub(crate) tags: DiagnosticTags,
#[advice]
pub(crate) rule_advice: RuleAdvice,
+ #[severity]
+ pub(crate) severity: Severity,
}
-#[derive(Debug, Default)]
+#[derive(Clone, Debug, Default)]
/// It contains possible advices to show when printing a diagnostic that belong to the rule
pub struct RuleAdvice {
pub(crate) details: Vec,
@@ -975,7 +1253,7 @@ pub struct RuleAdvice {
pub(crate) code_suggestion_list: Vec>,
}
-#[derive(Debug, Default)]
+#[derive(Clone, Debug, Default)]
pub struct SuggestionList {
pub(crate) message: MarkupBuf,
pub(crate) list: Vec,
@@ -1017,7 +1295,7 @@ impl Advices for RuleAdvice {
}
}
-#[derive(Debug)]
+#[derive(Clone, Debug)]
pub struct Detail {
pub log_category: LogCategory,
pub message: MarkupBuf,
@@ -1035,6 +1313,7 @@ impl RuleDiagnostic {
message: MessageAndDescription::from(message),
tags: DiagnosticTags::empty(),
rule_advice: RuleAdvice::default(),
+ severity: Severity::default(),
}
}
@@ -1062,6 +1341,14 @@ impl RuleDiagnostic {
self
}
+ /// Marks this diagnostic as verbose.
+ ///
+ /// The diagnostic will only be shown when using the `--verbose` argument.
+ pub fn verbose(mut self) -> Self {
+ self.tags |= DiagnosticTags::VERBOSE;
+ self
+ }
+
/// Attaches a label to this [`RuleDiagnostic`].
///
/// The given span has to be in the file that was provided while creating this [`RuleDiagnostic`].
diff --git a/crates/biome_analyze/src/signals.rs b/crates/biome_analyze/src/signals.rs
index d95c60f6bcd8..543ec957f167 100644
--- a/crates/biome_analyze/src/signals.rs
+++ b/crates/biome_analyze/src/signals.rs
@@ -1,15 +1,17 @@
-use crate::categories::SUPPRESSION_ACTION_CATEGORY;
+use crate::categories::{
+ SUPPRESSION_INLINE_ACTION_CATEGORY, SUPPRESSION_TOP_LEVEL_ACTION_CATEGORY,
+};
use crate::{
categories::ActionCategory,
context::RuleContext,
registry::{RuleLanguage, RuleRoot},
rule::Rule,
- AnalyzerDiagnostic, AnalyzerOptions, Queryable, RuleGroup, ServiceBag, SuppressionAction,
+ AnalyzerDiagnostic, AnalyzerOptions, OtherActionCategory, Queryable, RuleGroup, ServiceBag,
+ SuppressionAction,
};
use biome_console::MarkupBuf;
use biome_diagnostics::{advice::CodeSuggestionAdvice, Applicability, CodeSuggestion, Error};
use biome_rowan::{BatchMutation, Language};
-use std::borrow::Cow;
use std::iter::FusedIterator;
use std::marker::PhantomData;
use std::vec::IntoIter;
@@ -115,7 +117,15 @@ pub struct AnalyzerAction {
impl AnalyzerAction {
pub fn is_suppression(&self) -> bool {
- self.category.matches(SUPPRESSION_ACTION_CATEGORY)
+ self.is_inline_suppression() || self.is_top_level_suppression()
+ }
+
+ pub fn is_inline_suppression(&self) -> bool {
+ self.category.matches(SUPPRESSION_INLINE_ACTION_CATEGORY)
+ }
+
+ pub fn is_top_level_suppression(&self) -> bool {
+ self.category.matches(SUPPRESSION_TOP_LEVEL_ACTION_CATEGORY)
}
}
@@ -339,7 +349,7 @@ where
}
}
-impl<'bag, R> AnalyzerSignal> for RuleSignal<'bag, R>
+impl AnalyzerSignal> for RuleSignal<'_, R>
where
R: Rule + 'static,
{
@@ -353,7 +363,7 @@ where
self.root,
self.services,
&globals,
- &self.options.file_path,
+ self.options.file_path.as_path(),
&options,
preferred_quote,
preferred_jsx_quote,
@@ -361,7 +371,10 @@ where
)
.ok()?;
- R::diagnostic(&ctx, &self.state).map(AnalyzerDiagnostic::from)
+ R::diagnostic(&ctx, &self.state).map(|mut diagnostic| {
+ diagnostic.severity = ctx.metadata().severity;
+ AnalyzerDiagnostic::from(diagnostic)
+ })
}
fn actions(&self) -> AnalyzerActionIter> {
@@ -385,15 +398,15 @@ where
self.root,
self.services,
&globals,
- &self.options.file_path,
+ self.options.file_path.as_path(),
&options,
self.options.preferred_quote(),
self.options.preferred_jsx_quote(),
self.options.jsx_runtime(),
)
.ok();
+ let mut actions = Vec::new();
if let Some(ctx) = ctx {
- let mut actions = Vec::new();
if let Some(action) = R::action(&ctx, &self.state) {
actions.push(AnalyzerAction {
rule_name: Some((::NAME, R::METADATA.name)),
@@ -404,7 +417,7 @@ where
});
};
if let Some(text_range) = R::text_range(&ctx, &self.state) {
- if let Some(suppression_action) = R::suppress(
+ if let Some(suppression_action) = R::inline_suppression(
&ctx,
&text_range,
self.suppression_action,
@@ -412,7 +425,7 @@ where
) {
let action = AnalyzerAction {
rule_name: Some((::NAME, R::METADATA.name)),
- category: ActionCategory::Other(Cow::Borrowed(SUPPRESSION_ACTION_CATEGORY)),
+ category: ActionCategory::Other(OtherActionCategory::InlineSuppression),
applicability: Applicability::Always,
mutation: suppression_action.mutation,
message: suppression_action.message,
@@ -421,6 +434,19 @@ where
}
}
+ if let Some(suppression_action) =
+ R::top_level_suppression(&ctx, self.suppression_action)
+ {
+ let action = AnalyzerAction {
+ rule_name: Some((::NAME, R::METADATA.name)),
+ category: ActionCategory::Other(OtherActionCategory::ToplevelSuppression),
+ applicability: Applicability::Always,
+ mutation: suppression_action.mutation,
+ message: suppression_action.message,
+ };
+ actions.push(action);
+ }
+
AnalyzerActionIter::new(actions)
} else {
AnalyzerActionIter::new(vec![])
@@ -435,7 +461,7 @@ where
self.root,
self.services,
&globals,
- &self.options.file_path,
+ self.options.file_path.as_path(),
&options,
self.options.preferred_quote(),
self.options.preferred_jsx_quote(),
diff --git a/crates/biome_analyze/src/suppression_action.rs b/crates/biome_analyze/src/suppression_action.rs
index 77359a95f085..446eac999658 100644
--- a/crates/biome_analyze/src/suppression_action.rs
+++ b/crates/biome_analyze/src/suppression_action.rs
@@ -4,7 +4,7 @@ use biome_rowan::{BatchMutation, Language, SyntaxToken, TextRange, TokenAtOffset
pub trait SuppressionAction {
type Language: Language;
- fn apply_suppression_comment(&self, payload: SuppressionCommentEmitterPayload) {
+ fn inline_suppression(&self, payload: SuppressionCommentEmitterPayload) {
let SuppressionCommentEmitterPayload {
token_offset,
mutation,
@@ -19,11 +19,11 @@ pub trait SuppressionAction {
// considering that our suppression system works via lines, we need to look for the first newline,
// so we can place the comment there
let apply_suppression = original_token.as_ref().and_then(|original_token| {
- self.find_token_to_apply_suppression(original_token.clone())
+ self.find_token_for_inline_suppression(original_token.clone())
});
if let Some(apply_suppression) = apply_suppression {
- self.apply_suppression(
+ self.apply_inline_suppression(
mutation,
apply_suppression,
suppression_text,
@@ -68,23 +68,30 @@ pub trait SuppressionAction {
}
}
- fn find_token_to_apply_suppression(
+ fn find_token_for_inline_suppression(
&self,
original_token: SyntaxToken,
) -> Option>;
- fn apply_suppression(
+ fn apply_inline_suppression(
&self,
mutation: &mut BatchMutation,
apply_suppression: ApplySuppression,
suppression_text: &str,
suppression_reason: &str,
);
+
+ fn apply_top_level_suppression(
+ &self,
+ mutation: &mut BatchMutation,
+ token: SyntaxToken,
+ suppression_text: &str,
+ );
}
/// Convenient type to store useful information
pub struct ApplySuppression {
- /// If the token is following by trailing comments
+ /// If the token is followed by trailing comments
pub token_has_trailing_comments: bool,
/// The token to attach the suppression
pub token_to_apply_suppression: SyntaxToken,
diff --git a/crates/biome_analyze/src/suppressions.rs b/crates/biome_analyze/src/suppressions.rs
new file mode 100644
index 000000000000..c8f14fb08f06
--- /dev/null
+++ b/crates/biome_analyze/src/suppressions.rs
@@ -0,0 +1,456 @@
+use crate::{
+ AnalyzerSuppression, AnalyzerSuppressionDiagnostic, AnalyzerSuppressionKind,
+ AnalyzerSuppressionVariant, MetadataRegistry, RuleFilter, RuleKey,
+};
+use biome_console::markup;
+use biome_diagnostics::category;
+use biome_rowan::{TextRange, TextSize};
+use rustc_hash::{FxHashMap, FxHashSet};
+
+#[derive(Debug, Default)]
+pub struct TopLevelSuppression {
+ /// Whether this suppression suppresses all filters
+ pub(crate) suppress_all: bool,
+ /// Filters for the current suppression
+ pub(crate) filters: FxHashSet>,
+ /// The range of the comment
+ pub(crate) comment_range: TextRange,
+
+ /// The range covered by the current suppression.
+ /// Eventually, it should hit the entire document
+ pub(crate) range: TextRange,
+}
+
+impl TopLevelSuppression {
+ fn push_suppression(
+ &mut self,
+ suppression: &AnalyzerSuppression,
+ filter: Option>,
+ token_range: TextRange,
+ comment_range: TextRange,
+ ) -> Result<(), AnalyzerSuppressionDiagnostic> {
+ if suppression.is_top_level() && token_range.start() > TextSize::from(0) {
+ let mut diagnostic = AnalyzerSuppressionDiagnostic::new(
+ category!("suppressions/incorrect"),
+ comment_range,
+ "Top level suppressions can only be used at the beginning of the file.",
+ );
+ if let Some(ignore_range) = suppression.ignore_range {
+ diagnostic = diagnostic.note(
+ markup! {"Rename this to ""biome-ignore"" or move it to the top of the file"}
+ .to_owned(),
+ ignore_range,
+ );
+ }
+
+ return Err(diagnostic);
+ }
+ // The absence of a filter means that it's a suppression all
+ match filter {
+ None => self.suppress_all = true,
+ Some(filter) => self.insert(filter),
+ }
+ self.comment_range = comment_range;
+
+ Ok(())
+ }
+
+ pub(crate) fn insert(&mut self, filter: RuleFilter<'static>) {
+ self.filters.insert(filter);
+ }
+
+ pub(crate) fn suppressed_rule(&self, filter: &RuleKey) -> bool {
+ self.filters.iter().any(|f| f == filter)
+ }
+
+ pub(crate) fn expand_range(&mut self, range: TextRange) {
+ self.range.cover(range);
+ }
+
+ pub(crate) fn has_filter(&self, filter: &RuleFilter) -> bool {
+ self.filters.contains(filter)
+ }
+}
+
+/// Single entry for a suppression comment in the `line_suppressions` buffer
+#[derive(Default, Debug)]
+pub(crate) struct LineSuppression {
+ /// Line index this comment is suppressing lint rules for
+ pub(crate) line_index: usize,
+ /// Range of source text covered by the suppression comment
+ pub(crate) comment_span: TextRange,
+ /// Range of source text this comment is suppressing lint rules for
+ pub(crate) text_range: TextRange,
+ /// Set to true if this comment has set the `suppress_all` flag to true
+ /// (must be restored to false on expiration)
+ pub(crate) suppress_all: bool,
+ /// List of all the rules this comment has started suppressing (must be
+ /// removed from the suppressed set on expiration)
+ pub(crate) suppressed_rules: FxHashSet>,
+ /// List of all the rule instances this comment has started suppressing.
+ pub(crate) suppressed_instances: FxHashMap>,
+ /// Set to `true` when a signal matching this suppression was emitted and
+ /// suppressed
+ pub(crate) did_suppress_signal: bool,
+ /// Set to `true` when this line suppresses a signal that was already suppressed by another entity e.g. top-level suppression
+ pub(crate) already_suppressed: Option,
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct RangeSuppressions {
+ pub(crate) suppressions: Vec,
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct RangeSuppression {
+ /// Whether the current suppression should suppress all signals
+ pub(crate) suppress_all: bool,
+
+ /// The range of the `biome-ignore-start` suppressions
+ pub(crate) start_comment_range: TextRange,
+
+ /// A range that indicates how long this suppression has effect
+ pub(crate) suppression_range: TextRange,
+
+ /// Set to `true` when this line suppresses a signal that was already suppressed by another entity e.g. top-level suppression
+ pub(crate) already_suppressed: Option,
+
+ /// Whether this suppression has suppressed a signal
+ pub(crate) did_suppress_signal: bool,
+
+ /// The rules to suppress
+ pub(crate) filters: FxHashSet>,
+}
+
+impl RangeSuppressions {
+ /// Expands the range of all range suppressions
+ pub(crate) fn expand_range(&mut self, text_range: TextRange) {
+ for range_suppression in self.suppressions.iter_mut() {
+ if !range_suppression.filters.is_empty() {
+ range_suppression.suppression_range =
+ range_suppression.suppression_range.cover(text_range);
+ }
+ }
+ }
+ pub(crate) fn push_suppression(
+ &mut self,
+ suppression: &AnalyzerSuppression,
+ filter: Option>,
+ text_range: TextRange,
+ already_suppressed: Option,
+ ) -> Result<(), AnalyzerSuppressionDiagnostic> {
+ if suppression.is_range_start() {
+ if let Some(range_suppression) = self.suppressions.last_mut() {
+ match filter {
+ None => {
+ range_suppression.suppress_all = true;
+ range_suppression.already_suppressed = already_suppressed;
+ }
+ Some(filter) => {
+ range_suppression.filters.insert(filter);
+ range_suppression.already_suppressed = already_suppressed;
+ }
+ }
+ } else {
+ let mut range_suppression = RangeSuppression::default();
+ match filter {
+ None => range_suppression.suppress_all = true,
+ Some(filter) => {
+ range_suppression.filters.insert(filter);
+ }
+ }
+ range_suppression.suppression_range = text_range;
+ range_suppression.already_suppressed = already_suppressed;
+ range_suppression.start_comment_range = text_range;
+ self.suppressions.push(range_suppression);
+ }
+ } else if suppression.is_range_end() {
+ if self.suppressions.is_empty() {
+ // This an error. We found a range end suppression without having a range start
+ return Err(AnalyzerSuppressionDiagnostic::new(
+ category!("suppressions/incorrect"),
+ text_range,
+ markup!{"Found a ""biome-range-end"" suppression without a ""biome-range-start"" suppression. This is invalid"}
+ ).hint(markup!{
+ "Remove this suppression."
+ }.to_owned()));
+ }
+
+ match filter {
+ None => {
+ self.suppressions.pop();
+ }
+ Some(filter) => {
+ // SAFETY: we checked if the vector isn't empty at the beginning
+ let range_suppression = self.suppressions.last_mut().unwrap();
+ let present = range_suppression.filters.remove(&filter);
+ // the user tried to remove a filter that wasn't added, let's fire a diagnostic
+ if !present {
+ // This an error. We found a range end suppression without having a range start
+ return Err(AnalyzerSuppressionDiagnostic::new(
+ category!("suppressions/incorrect"),
+ text_range,
+ markup!{"Found a ""biome-range-end"" suppression without a ""biome-range-start"" suppression. This is invalid"}
+ ).hint(markup!{
+ "Remove this suppression."
+ }.to_owned()));
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+
+ /// Checks if there's suppression that suppresses the current rule in the range provided
+ pub(crate) fn suppressed_rule(&mut self, filter: &RuleKey, position: &TextRange) -> bool {
+ let range_suppression = self
+ .suppressions
+ .iter_mut()
+ .rev()
+ .find(|range_suppression| {
+ range_suppression
+ .suppression_range
+ .contains_range(*position)
+ });
+ let range_suppression = range_suppression
+ .filter(|range_suppression| range_suppression.filters.iter().any(|f| f == filter));
+ if let Some(range_suppression) = range_suppression {
+ range_suppression.did_suppress_signal = true;
+ true
+ } else {
+ false
+ }
+ }
+
+ /// Whether if the provided `filter` matches ones, given a range.
+ pub(crate) fn matches_filter_in_range(
+ &self,
+ filter: &RuleFilter,
+ position: &TextRange,
+ ) -> Option {
+ for range_suppression in self.suppressions.iter().rev() {
+ if range_suppression
+ .suppression_range
+ .contains_range(*position)
+ && range_suppression.filters.contains(filter)
+ {
+ return Some(range_suppression.suppression_range);
+ }
+ }
+
+ None
+ }
+}
+
+#[derive(Debug)]
+pub struct Suppressions<'analyzer> {
+ /// Current line index
+ pub(crate) line_index: usize,
+ /// Registry metadata, used to find match the rules
+ metadata: &'analyzer MetadataRegistry,
+ /// Used to track the last suppression pushed.
+ last_suppression: Option,
+ pub(crate) line_suppressions: Vec,
+ pub(crate) top_level_suppression: TopLevelSuppression,
+ pub(crate) range_suppressions: RangeSuppressions,
+}
+
+impl<'analyzer> Suppressions<'analyzer> {
+ pub(crate) fn new(metadata: &'analyzer MetadataRegistry) -> Self {
+ Self {
+ line_index: 0,
+ metadata,
+ line_suppressions: vec![],
+ top_level_suppression: TopLevelSuppression::default(),
+ range_suppressions: RangeSuppressions::default(),
+ last_suppression: None,
+ }
+ }
+
+ fn push_line_suppression(
+ &mut self,
+ filter: Option>,
+ instance: Option,
+ current_range: TextRange,
+ already_suppressed: Option,
+ ) -> Result<(), AnalyzerSuppressionDiagnostic> {
+ if let Some(suppression) = self.line_suppressions.last_mut() {
+ if (suppression.line_index) == (self.line_index) {
+ suppression.already_suppressed = already_suppressed;
+
+ match filter {
+ None => {
+ suppression.suppress_all = true;
+ suppression.suppressed_rules.clear();
+ suppression.suppressed_instances.clear();
+ }
+ Some(filter) => {
+ suppression.suppressed_rules.insert(filter);
+ if let Some(instance) = instance {
+ suppression.suppressed_instances.insert(instance, filter);
+ }
+ suppression.suppress_all = false;
+ }
+ }
+ return Ok(());
+ }
+ }
+
+ let mut suppression = LineSuppression {
+ comment_span: current_range,
+ text_range: current_range,
+ line_index: self.line_index,
+ already_suppressed,
+ ..Default::default()
+ };
+ match filter {
+ None => {
+ suppression.suppress_all = true;
+ }
+ Some(filter) => {
+ suppression.suppressed_rules.insert(filter);
+ if let Some(instance) = instance {
+ suppression.suppressed_instances.insert(instance, filter);
+ }
+ }
+ }
+ self.line_suppressions.push(suppression);
+
+ Ok(())
+ }
+
+ /// Maps a [suppression](AnalyzerSuppressionKind) to a [RuleFilter]
+ fn map_to_rule_filter(
+ &self,
+ suppression_kind: &AnalyzerSuppressionKind,
+ text_range: TextRange,
+ ) -> Result