diff --git a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs index ef30e0c3590b..a8b970f2b07b 100644 --- a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs +++ b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs @@ -315,6 +315,16 @@ pub(crate) fn migrate_eslint_any_rule( let rule = group.use_block_statements.get_or_insert(Default::default()); rule.set_level(rule_severity.into()); } + "default-case" => { + if !options.include_nursery { + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .use_default_switch_clause + .get_or_insert(Default::default()); + rule.set_level(rule_severity.into()); + } "default-case-last" => { let group = rules.suspicious.get_or_insert_with(Default::default); let rule = group @@ -513,6 +523,11 @@ pub(crate) fn migrate_eslint_any_rule( let rule = group.use_iframe_title.get_or_insert(Default::default()); rule.set_level(rule_severity.into()); } + "jsx-a11y/img-redundant-alt" => { + let group = rules.a11y.get_or_insert_with(Default::default); + let rule = group.no_redundant_alt.get_or_insert(Default::default()); + rule.set_level(rule_severity.into()); + } "jsx-a11y/lang" => { let group = rules.a11y.get_or_insert_with(Default::default); let rule = group.use_valid_lang.get_or_insert(Default::default()); diff --git a/crates/biome_configuration/src/linter/rules.rs b/crates/biome_configuration/src/linter/rules.rs index fe41a19cea64..450b0ba852e5 100644 --- a/crates/biome_configuration/src/linter/rules.rs +++ b/crates/biome_configuration/src/linter/rules.rs @@ -2712,6 +2712,9 @@ pub struct Nursery { #[doc = "Enforce the use of new for all builtins, except String, Number, Boolean, Symbol and BigInt."] #[serde(skip_serializing_if = "Option::is_none")] pub use_consistent_new_builtin: Option>, + #[doc = "Require the default clause in switch statements."] + #[serde(skip_serializing_if = "Option::is_none")] + pub use_default_switch_clause: Option>, #[doc = "Disallow a missing generic family keyword within font families."] #[serde(skip_serializing_if = "Option::is_none")] pub use_generic_font_names: Option>, @@ -2760,6 +2763,7 @@ impl Nursery { "noUselessUndefinedInitialization", "useArrayLiterals", "useConsistentNewBuiltin", + "useDefaultSwitchClause", "useGenericFontNames", "useImportRestrictions", "useSortedClasses", @@ -2788,7 +2792,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -2815,6 +2819,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended_true(&self) -> bool { @@ -2936,21 +2941,26 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.use_generic_font_names.as_ref() { + if let Some(rule) = self.use_default_switch_clause.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_generic_font_names.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } - if let Some(rule) = self.use_sorted_classes.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } + if let Some(rule) = self.use_sorted_classes.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -3060,21 +3070,26 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.use_generic_font_names.as_ref() { + if let Some(rule) = self.use_default_switch_clause.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_generic_font_names.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } - if let Some(rule) = self.use_sorted_classes.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } + if let Some(rule) = self.use_sorted_classes.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -3195,6 +3210,10 @@ impl Nursery { .use_consistent_new_builtin .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "useDefaultSwitchClause" => self + .use_default_switch_clause + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "useGenericFontNames" => self .use_generic_font_names .as_ref() diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index b4b17102d655..a7e54eb6e584 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -135,6 +135,7 @@ define_categories! { "lint/nursery/useBiomeSuppressionComment": "https://biomejs.dev/linter/rules/use-biome-suppression-comment", "lint/nursery/useConsistentNewBuiltin": "https://biomejs.dev/linter/rules/use-consistent-new-builtin", "lint/nursery/useGenericFontNames": "https://biomejs.dev/linter/rules/use-generic-font-names", + "lint/nursery/useDefaultSwitchClause": "https://biomejs.dev/linter/rules/use-default-switch-clause", "lint/nursery/useImportRestrictions": "https://biomejs.dev/linter/rules/use-import-restrictions", "lint/nursery/useSortedClasses": "https://biomejs.dev/linter/rules/use-sorted-classes", "lint/performance/noAccumulatingSpread": "https://biomejs.dev/linter/rules/no-accumulating-spread", diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index a52c3f728808..1e5ccd6bce2f 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -16,6 +16,7 @@ pub mod no_undeclared_dependencies; pub mod no_useless_undefined_initialization; pub mod use_array_literals; pub mod use_consistent_new_builtin; +pub mod use_default_switch_clause; pub mod use_import_restrictions; pub mod use_sorted_classes; @@ -37,6 +38,7 @@ declare_group! { self :: no_useless_undefined_initialization :: NoUselessUndefinedInitialization , self :: use_array_literals :: UseArrayLiterals , self :: use_consistent_new_builtin :: UseConsistentNewBuiltin , + self :: use_default_switch_clause :: UseDefaultSwitchClause , self :: use_import_restrictions :: UseImportRestrictions , self :: use_sorted_classes :: UseSortedClasses , ] diff --git a/crates/biome_js_analyze/src/lint/nursery/use_default_switch_clause.rs b/crates/biome_js_analyze/src/lint/nursery/use_default_switch_clause.rs new file mode 100644 index 000000000000..24de16cce92f --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/use_default_switch_clause.rs @@ -0,0 +1,85 @@ +use std::ops::Not; + +use biome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic, RuleSource}; +use biome_console::markup; +use biome_js_syntax::JsSwitchStatement; +use biome_rowan::AstNode; + +declare_rule! { + /// Require the default clause in switch statements. + /// + /// Some code conventions require that all switch statements have a default clause. The thinking is that it’s better + /// to always explicitly state what the default behavior should be so that it’s clear whether or not the developer + /// forgot to include the default behavior by mistake. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// switch (a) { + /// case 1: + /// /* code */ + /// break; + /// } + /// ``` + /// + /// ### Valid + /// + /// ```js + /// switch (a) { + /// case 1: + /// /* code */ + /// break; + /// + /// default: + /// /* code */ + /// break; + /// } + /// ``` + pub UseDefaultSwitchClause { + version: "next", + name: "useDefaultSwitchClause", + sources: &[RuleSource::Eslint("default-case")], + recommended: false, + } +} + +impl Rule for UseDefaultSwitchClause { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let node = ctx.query(); + + let is_missing_default_case = node + .cases() + .into_iter() + .any(|clause| clause.as_js_default_clause().is_some()) + .not(); + + is_missing_default_case.then_some(()) + } + + fn diagnostic(ctx: &RuleContext, _state: &Self::State) -> Option { + let node = ctx.query(); + + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Expected a default switch clause." + }, + ) + .note(markup! { + "The lack of a default clause can be a possible omission." + }) + .note(markup! { + "Consider adding a default clause." + }), + ) + } +} diff --git a/crates/biome_js_analyze/src/options.rs b/crates/biome_js_analyze/src/options.rs index 4f873250e96b..59e36288f3eb 100644 --- a/crates/biome_js_analyze/src/options.rs +++ b/crates/biome_js_analyze/src/options.rs @@ -263,6 +263,7 @@ pub type UseConsistentArrayType = < lint :: style :: use_consistent_array_type : pub type UseConsistentNewBuiltin = < lint :: nursery :: use_consistent_new_builtin :: UseConsistentNewBuiltin as biome_analyze :: Rule > :: Options ; pub type UseConst = ::Options; pub type UseDefaultParameterLast = < lint :: style :: use_default_parameter_last :: UseDefaultParameterLast as biome_analyze :: Rule > :: Options ; +pub type UseDefaultSwitchClause = < lint :: nursery :: use_default_switch_clause :: UseDefaultSwitchClause as biome_analyze :: Rule > :: Options ; pub type UseDefaultSwitchClauseLast = < lint :: suspicious :: use_default_switch_clause_last :: UseDefaultSwitchClauseLast as biome_analyze :: Rule > :: Options ; pub type UseEnumInitializers = ::Options; diff --git a/crates/biome_js_analyze/tests/specs/nursery/useDefaultSwitchClause/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/useDefaultSwitchClause/invalid.js new file mode 100644 index 000000000000..fe25892dfeb1 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useDefaultSwitchClause/invalid.js @@ -0,0 +1,7 @@ +switch (a) { + case 1: + break; +} + +switch (a) { +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/useDefaultSwitchClause/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/useDefaultSwitchClause/invalid.js.snap new file mode 100644 index 000000000000..a151bfd45343 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useDefaultSwitchClause/invalid.js.snap @@ -0,0 +1,57 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```jsx +switch (a) { + case 1: + break; +} + +switch (a) { +} + +``` + +# Diagnostics +``` +invalid.js:1:1 lint/nursery/useDefaultSwitchClause ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Expected a default switch clause. + + > 1 │ switch (a) { + │ ^^^^^^^^^^^^ + > 2 │ case 1: + > 3 │ break; + > 4 │ } + │ ^ + 5 │ + 6 │ switch (a) { + + i The lack of a default clause can be a possible omission. + + i Consider adding a default clause. + + +``` + +``` +invalid.js:6:1 lint/nursery/useDefaultSwitchClause ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Expected a default switch clause. + + 4 │ } + 5 │ + > 6 │ switch (a) { + │ ^^^^^^^^^^^^ + > 7 │ } + │ ^ + 8 │ + + i The lack of a default clause can be a possible omission. + + i Consider adding a default clause. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/useDefaultSwitchClause/valid.js b/crates/biome_js_analyze/tests/specs/nursery/useDefaultSwitchClause/valid.js new file mode 100644 index 000000000000..40245060e263 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useDefaultSwitchClause/valid.js @@ -0,0 +1,14 @@ +switch (a) { + case 1: + break; + default: + break; +} + +switch (a) { + case 1: + break; + case 2: + default: + break; +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/useDefaultSwitchClause/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/useDefaultSwitchClause/valid.js.snap new file mode 100644 index 000000000000..2004f9fb29d9 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useDefaultSwitchClause/valid.js.snap @@ -0,0 +1,22 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```jsx +switch (a) { + case 1: + break; + default: + break; +} + +switch (a) { + case 1: + break; + case 2: + default: + break; +} + +``` diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index bacb9882a2fc..3a13cf387fcf 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -996,6 +996,10 @@ export interface Nursery { * Enforce the use of new for all builtins, except String, Number, Boolean, Symbol and BigInt. */ useConsistentNewBuiltin?: RuleConfiguration_for_Null; + /** + * Require the default clause in switch statements. + */ + useDefaultSwitchClause?: RuleConfiguration_for_Null; /** * Disallow a missing generic family keyword within font families. */ @@ -1998,6 +2002,7 @@ export type Category = | "lint/nursery/useBiomeSuppressionComment" | "lint/nursery/useConsistentNewBuiltin" | "lint/nursery/useGenericFontNames" + | "lint/nursery/useDefaultSwitchClause" | "lint/nursery/useImportRestrictions" | "lint/nursery/useSortedClasses" | "lint/performance/noAccumulatingSpread" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 7b69c0e28f4e..09f1dda29a2d 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1584,6 +1584,13 @@ { "type": "null" } ] }, + "useDefaultSwitchClause": { + "description": "Require the default clause in switch statements.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "useGenericFontNames": { "description": "Disallow a missing generic family keyword within font families.", "anyOf": [