diff --git a/.changeset/twenty-mice-cough.md b/.changeset/twenty-mice-cough.md new file mode 100644 index 000000000000..0f1db744c1a6 --- /dev/null +++ b/.changeset/twenty-mice-cough.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Added new nursery rule [`use-consistent-enum-value-type`](https://biomejs.dev/linter/rules/use-consistent-enum-value-type). This rule disallows enums from having both number and string members. 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 dceea184c5d5..34b0c59c169d 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 @@ -505,6 +505,18 @@ pub(crate) fn migrate_eslint_any_rule( .get_or_insert(Default::default()); rule.set_level(rule.level().max(rule_severity.into())); } + "@typescript-eslint/no-mixed-enums" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .use_consistent_enum_value_type + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } "@typescript-eslint/no-namespace" => { let group = rules.style.get_or_insert_with(Default::default); let rule = group diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index 7cce36661ba6..908d7d8f6ab1 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -388,6 +388,7 @@ pub enum RuleName { UseConsistentArrowReturn, UseConsistentBuiltinInstantiation, UseConsistentCurlyBraces, + UseConsistentEnumValueType, UseConsistentGraphqlDescriptions, UseConsistentMemberAccessibility, UseConsistentObjectDefinitions, @@ -824,6 +825,7 @@ impl RuleName { Self::UseConsistentArrowReturn => "useConsistentArrowReturn", Self::UseConsistentBuiltinInstantiation => "useConsistentBuiltinInstantiation", Self::UseConsistentCurlyBraces => "useConsistentCurlyBraces", + Self::UseConsistentEnumValueType => "useConsistentEnumValueType", Self::UseConsistentGraphqlDescriptions => "useConsistentGraphqlDescriptions", Self::UseConsistentMemberAccessibility => "useConsistentMemberAccessibility", Self::UseConsistentObjectDefinitions => "useConsistentObjectDefinitions", @@ -1256,6 +1258,7 @@ impl RuleName { Self::UseConsistentArrowReturn => RuleGroup::Nursery, Self::UseConsistentBuiltinInstantiation => RuleGroup::Style, Self::UseConsistentCurlyBraces => RuleGroup::Style, + Self::UseConsistentEnumValueType => RuleGroup::Nursery, Self::UseConsistentGraphqlDescriptions => RuleGroup::Nursery, Self::UseConsistentMemberAccessibility => RuleGroup::Style, Self::UseConsistentObjectDefinitions => RuleGroup::Style, @@ -1697,6 +1700,7 @@ impl std::str::FromStr for RuleName { "useConsistentArrowReturn" => Ok(Self::UseConsistentArrowReturn), "useConsistentBuiltinInstantiation" => Ok(Self::UseConsistentBuiltinInstantiation), "useConsistentCurlyBraces" => Ok(Self::UseConsistentCurlyBraces), + "useConsistentEnumValueType" => Ok(Self::UseConsistentEnumValueType), "useConsistentGraphqlDescriptions" => Ok(Self::UseConsistentGraphqlDescriptions), "useConsistentMemberAccessibility" => Ok(Self::UseConsistentMemberAccessibility), "useConsistentObjectDefinitions" => Ok(Self::UseConsistentObjectDefinitions), diff --git a/crates/biome_configuration/src/generated/domain_selector.rs b/crates/biome_configuration/src/generated/domain_selector.rs index ab3462d8ec06..9567cc2d95bc 100644 --- a/crates/biome_configuration/src/generated/domain_selector.rs +++ b/crates/biome_configuration/src/generated/domain_selector.rs @@ -33,6 +33,7 @@ static PROJECT_FILTERS: LazyLock>> = LazyLock::new(|| { RuleFilter::Rule("nursery", "noUnresolvedImports"), RuleFilter::Rule("nursery", "useArraySortCompare"), RuleFilter::Rule("nursery", "useAwaitThenable"), + RuleFilter::Rule("nursery", "useConsistentEnumValueType"), RuleFilter::Rule("nursery", "useExhaustiveSwitchCases"), RuleFilter::Rule("nursery", "useFind"), RuleFilter::Rule("nursery", "useRegexpExec"), diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index fe28862a8e1e..8ff9c94d2c7e 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -193,6 +193,7 @@ define_categories! { "lint/nursery/noLeakedRender": "https://biomejs.dev/linter/rules/no-leaked-render", "lint/nursery/noMissingGenericFamilyKeyword": "https://biomejs.dev/linter/rules/no-missing-generic-family-keyword", "lint/nursery/noMisusedPromises": "https://biomejs.dev/linter/rules/no-misused-promises", + "lint/nursery/useConsistentEnumValueType": "https://biomejs.dev/linter/rules/use-consistent-enum-value-type", "lint/nursery/noMultiAssign": "https://biomejs.dev/linter/rules/no-multi-assign", "lint/nursery/noMultiStr": "https://biomejs.dev/linter/rules/no-multi-str", "lint/nursery/noNextAsyncClientComponent": "https://biomejs.dev/linter/rules/no-next-async-client-component", diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index 6502e160c83d..2172741ef3a7 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -50,6 +50,7 @@ pub mod no_vue_setup_props_reactivity_loss; pub mod use_array_sort_compare; pub mod use_await_thenable; pub mod use_consistent_arrow_return; +pub mod use_consistent_enum_value_type; pub mod use_destructuring; pub mod use_error_cause; pub mod use_exhaustive_switch_cases; @@ -65,4 +66,4 @@ pub mod use_spread; pub mod use_vue_consistent_define_props_declaration; pub mod use_vue_define_macros_order; pub mod use_vue_multi_word_component_names; -declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_ambiguous_anchor_text :: NoAmbiguousAnchorText , self :: no_before_interactive_script_outside_document :: NoBeforeInteractiveScriptOutsideDocument , self :: no_continue :: NoContinue , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_div_regex :: NoDivRegex , self :: no_duplicate_enum_values :: NoDuplicateEnumValues , self :: no_duplicated_spread_props :: NoDuplicatedSpreadProps , self :: no_empty_source :: NoEmptySource , self :: no_equals_to_null :: NoEqualsToNull , self :: no_excessive_classes_per_file :: NoExcessiveClassesPerFile , self :: no_excessive_lines_per_file :: NoExcessiveLinesPerFile , self :: no_floating_classes :: NoFloatingClasses , self :: no_floating_promises :: NoFloatingPromises , self :: no_for_in :: NoForIn , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_jsx_props_bind :: NoJsxPropsBind , self :: no_leaked_render :: NoLeakedRender , self :: no_misused_promises :: NoMisusedPromises , self :: no_multi_assign :: NoMultiAssign , self :: no_multi_str :: NoMultiStr , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_proto :: NoProto , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_return_assign :: NoReturnAssign , self :: no_script_url :: NoScriptUrl , self :: no_shadow :: NoShadow , self :: no_sync_scripts :: NoSyncScripts , self :: no_ternary :: NoTernary , self :: no_undeclared_env_vars :: NoUndeclaredEnvVars , self :: no_unknown_attribute :: NoUnknownAttribute , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_options_api :: NoVueOptionsApi , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: no_vue_setup_props_reactivity_loss :: NoVueSetupPropsReactivityLoss , self :: use_array_sort_compare :: UseArraySortCompare , self :: use_await_thenable :: UseAwaitThenable , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_destructuring :: UseDestructuring , self :: use_error_cause :: UseErrorCause , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_find :: UseFind , self :: use_inline_script_id :: UseInlineScriptId , self :: use_max_params :: UseMaxParams , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_regexp_exec :: UseRegexpExec , self :: use_sorted_classes :: UseSortedClasses , self :: use_spread :: UseSpread , self :: use_vue_consistent_define_props_declaration :: UseVueConsistentDefinePropsDeclaration , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } } +declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_ambiguous_anchor_text :: NoAmbiguousAnchorText , self :: no_before_interactive_script_outside_document :: NoBeforeInteractiveScriptOutsideDocument , self :: no_continue :: NoContinue , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_div_regex :: NoDivRegex , self :: no_duplicate_enum_values :: NoDuplicateEnumValues , self :: no_duplicated_spread_props :: NoDuplicatedSpreadProps , self :: no_empty_source :: NoEmptySource , self :: no_equals_to_null :: NoEqualsToNull , self :: no_excessive_classes_per_file :: NoExcessiveClassesPerFile , self :: no_excessive_lines_per_file :: NoExcessiveLinesPerFile , self :: no_floating_classes :: NoFloatingClasses , self :: no_floating_promises :: NoFloatingPromises , self :: no_for_in :: NoForIn , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_jsx_props_bind :: NoJsxPropsBind , self :: no_leaked_render :: NoLeakedRender , self :: no_misused_promises :: NoMisusedPromises , self :: no_multi_assign :: NoMultiAssign , self :: no_multi_str :: NoMultiStr , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_proto :: NoProto , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_return_assign :: NoReturnAssign , self :: no_script_url :: NoScriptUrl , self :: no_shadow :: NoShadow , self :: no_sync_scripts :: NoSyncScripts , self :: no_ternary :: NoTernary , self :: no_undeclared_env_vars :: NoUndeclaredEnvVars , self :: no_unknown_attribute :: NoUnknownAttribute , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_options_api :: NoVueOptionsApi , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: no_vue_setup_props_reactivity_loss :: NoVueSetupPropsReactivityLoss , self :: use_array_sort_compare :: UseArraySortCompare , self :: use_await_thenable :: UseAwaitThenable , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_consistent_enum_value_type :: UseConsistentEnumValueType , self :: use_destructuring :: UseDestructuring , self :: use_error_cause :: UseErrorCause , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_find :: UseFind , self :: use_inline_script_id :: UseInlineScriptId , self :: use_max_params :: UseMaxParams , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_regexp_exec :: UseRegexpExec , self :: use_sorted_classes :: UseSortedClasses , self :: use_spread :: UseSpread , self :: use_vue_consistent_define_props_declaration :: UseVueConsistentDefinePropsDeclaration , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } } diff --git a/crates/biome_js_analyze/src/lint/nursery/use_consistent_enum_value_type.rs b/crates/biome_js_analyze/src/lint/nursery/use_consistent_enum_value_type.rs new file mode 100644 index 000000000000..b4598b194719 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/use_consistent_enum_value_type.rs @@ -0,0 +1,161 @@ +use biome_analyze::{ + Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule, +}; +use biome_console::markup; +use biome_js_syntax::TsEnumDeclaration; +use biome_rowan::{AstNode, TextRange}; +use biome_rule_options::use_consistent_enum_value_type::UseConsistentEnumValueTypeOptions; + +use crate::services::typed::Typed; + +declare_lint_rule! { + /// Disallow enums from having both number and string members. + /// + /// TypeScript enums are allowed to assign numeric or string values to their members. + /// Most enums contain either all numbers or all strings, but in theory you can mix-and-match within the same enum. + /// Mixing enum member types is generally considered confusing and a bad practice. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```ts,expect_diagnostic + /// enum Status { + /// Unknown, + /// Closed = 1, + /// Open = 'open', + /// } + /// ``` + /// + /// ### Valid + /// + /// ```ts + /// enum Status { + /// Unknown = 0, + /// Closed = 1, + /// Open = 2, + /// } + /// ``` + /// + /// ```ts + /// enum Status { + /// Unknown, + /// Closed, + /// Open, + /// } + /// ``` + /// + /// ```ts + /// enum Status { + /// Unknown = 'unknown', + /// Closed = 'closed', + /// Open = 'open', + /// } + /// ``` + /// + pub UseConsistentEnumValueType { + version: "next", + name: "useConsistentEnumValueType", + language: "ts", + recommended: false, + domains: &[RuleDomain::Project], + sources: &[RuleSource::EslintTypeScript("no-mixed-enums").same()], + } +} + +#[derive(Eq, PartialEq, Clone, Debug)] +pub enum EnumValueType { + Number, + String, + Unknown, +} + +impl Rule for UseConsistentEnumValueType { + type Query = Typed; + type State = Vec; + type Signals = Option; + type Options = UseConsistentEnumValueTypeOptions; + + fn run(ctx: &RuleContext) -> Self::Signals { + let node = ctx.query(); + let mut found = vec![]; + let mut enum_type: Option = None; + + for member in node.members() { + let Some(member) = member.ok() else { + continue; + }; + + let Some(initializer) = member.initializer() else { + if let Some(enum_type) = enum_type.clone() { + if enum_type != EnumValueType::Number { + found.push(member.range()); + } + } else { + enum_type = Some(EnumValueType::Number); + } + continue; + }; + let Some(expr) = initializer.expression().ok() else { + continue; + }; + + let expr_type = ctx.type_of_expression(&expr); + + if expr_type.is_string_or_string_literal() { + if let Some(enum_type) = enum_type.clone() { + if enum_type != EnumValueType::String { + found.push(member.range()); + } + } else { + enum_type = Some(EnumValueType::String); + } + continue; + } + + if expr_type.is_number_or_number_literal() { + if let Some(enum_type) = enum_type.clone() { + if enum_type != EnumValueType::Number { + found.push(member.range()); + } + } else { + enum_type = Some(EnumValueType::Number); + } + continue; + } + + if let Some(enum_type) = enum_type.clone() { + if enum_type != EnumValueType::Unknown { + found.push(member.range()); + } + } else { + enum_type = Some(EnumValueType::Unknown); + } + } + + if found.is_empty() { None } else { Some(found) } + } + + fn diagnostic(_ctx: &RuleContext, state: &Self::State) -> Option { + let mut diagnostic = RuleDiagnostic::new( + rule_category!(), + state.first()?, + markup! { + "Inconsistent enum value type." + }, + ); + + for range in &state[1..] { + diagnostic = diagnostic.detail( + range, + markup! { + "Another inconsistent enum value type." + }, + ); + } + + Some(diagnostic.note(markup! { + "Mixing number and string enums can be confusing. Make sure to use a consistent value type within your enum." + })) + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/useConsistentEnumValueType/invalid.ts b/crates/biome_js_analyze/tests/specs/nursery/useConsistentEnumValueType/invalid.ts new file mode 100644 index 000000000000..64278f0a5d7d --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useConsistentEnumValueType/invalid.ts @@ -0,0 +1,16 @@ +/* should generate diagnostics */ +enum Invalid1 { + Unknown, + Closed = 1, + Open = 'open', +} + +function getInvalidValue() { + return 0 +} + +enum Invalid2 { + Unknown = getInvalidValue(), + Closed = "closed", + Open = getInvalidValue(), +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/useConsistentEnumValueType/invalid.ts.snap b/crates/biome_js_analyze/tests/specs/nursery/useConsistentEnumValueType/invalid.ts.snap new file mode 100644 index 000000000000..e6e5369fe1a9 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useConsistentEnumValueType/invalid.ts.snap @@ -0,0 +1,63 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.ts +--- +# Input +```ts +/* should generate diagnostics */ +enum Invalid1 { + Unknown, + Closed = 1, + Open = 'open', +} + +function getInvalidValue() { + return 0 +} + +enum Invalid2 { + Unknown = getInvalidValue(), + Closed = "closed", + Open = getInvalidValue(), +} + +``` + +# Diagnostics +``` +invalid.ts:5:2 lint/nursery/useConsistentEnumValueType ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Inconsistent enum value type. + + 3 │ Unknown, + 4 │ Closed = 1, + > 5 │ Open = 'open', + │ ^^^^^^^^^^^^^ + 6 │ } + 7 │ + + i Mixing number and string enums can be confusing. Make sure to use a consistent value type within your enum. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + +``` + +``` +invalid.ts:14:2 lint/nursery/useConsistentEnumValueType ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Inconsistent enum value type. + + 12 │ enum Invalid2 { + 13 │ Unknown = getInvalidValue(), + > 14 │ Closed = "closed", + │ ^^^^^^^^^^^^^^^^^ + 15 │ Open = getInvalidValue(), + 16 │ } + + i Mixing number and string enums can be confusing. Make sure to use a consistent value type within your enum. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/useConsistentEnumValueType/valid.ts b/crates/biome_js_analyze/tests/specs/nursery/useConsistentEnumValueType/valid.ts new file mode 100644 index 000000000000..6f527be19d58 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useConsistentEnumValueType/valid.ts @@ -0,0 +1,29 @@ +/* should not generate diagnostics */ + +enum Valid1 { + Unknown = 0, + Closed = 1, + Open = 2, +} + +enum Valid2 { + Unknown, + Closed, + Open, +} + +enum Valid3 { + Unknown = 'unknown', + Closed = 'closed', + Open = 'open', +} + +function getValidValue() { + return 0 +} + +enum Valid4 { + Unknown = getValidValue(), + Closed = 1, + Open = getValidValue(), +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/useConsistentEnumValueType/valid.ts.snap b/crates/biome_js_analyze/tests/specs/nursery/useConsistentEnumValueType/valid.ts.snap new file mode 100644 index 000000000000..f37e061332dc --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useConsistentEnumValueType/valid.ts.snap @@ -0,0 +1,37 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.ts +--- +# Input +```ts +/* should not generate diagnostics */ + +enum Valid1 { + Unknown = 0, + Closed = 1, + Open = 2, +} + +enum Valid2 { + Unknown, + Closed, + Open, +} + +enum Valid3 { + Unknown = 'unknown', + Closed = 'closed', + Open = 'open', +} + +function getValidValue() { + return 0 +} + +enum Valid4 { + Unknown = getValidValue(), + Closed = 1, + Open = getValidValue(), +} + +``` diff --git a/crates/biome_rule_options/src/lib.rs b/crates/biome_rule_options/src/lib.rs index 1d42bff598a0..13943800a24d 100644 --- a/crates/biome_rule_options/src/lib.rs +++ b/crates/biome_rule_options/src/lib.rs @@ -302,6 +302,7 @@ pub mod use_consistent_array_type; pub mod use_consistent_arrow_return; pub mod use_consistent_builtin_instantiation; pub mod use_consistent_curly_braces; +pub mod use_consistent_enum_value_type; pub mod use_consistent_graphql_descriptions; pub mod use_consistent_member_accessibility; pub mod use_consistent_object_definitions; diff --git a/crates/biome_rule_options/src/use_consistent_enum_value_type.rs b/crates/biome_rule_options/src/use_consistent_enum_value_type.rs new file mode 100644 index 000000000000..93547ff491e8 --- /dev/null +++ b/crates/biome_rule_options/src/use_consistent_enum_value_type.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::{Deserializable, Merge}; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct UseConsistentEnumValueTypeOptions {} diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index c8687080c84f..460958b73aba 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -2148,6 +2148,11 @@ See https://biomejs.dev/linter/rules/use-consistent-arrow-return */ useConsistentArrowReturn?: UseConsistentArrowReturnConfiguration; /** + * Disallow enums from having both number and string members. +See https://biomejs.dev/linter/rules/use-consistent-enum-value-type + */ + useConsistentEnumValueType?: UseConsistentEnumValueTypeConfiguration; + /** * Require all descriptions to follow the same style (either block or inline) to maintain consistency and improve readability across the schema. See https://biomejs.dev/linter/rules/use-consistent-graphql-descriptions */ @@ -3922,6 +3927,9 @@ export type UseAwaitThenableConfiguration = export type UseConsistentArrowReturnConfiguration = | RulePlainConfiguration | RuleWithUseConsistentArrowReturnOptions; +export type UseConsistentEnumValueTypeConfiguration = + | RulePlainConfiguration + | RuleWithUseConsistentEnumValueTypeOptions; export type UseConsistentGraphqlDescriptionsConfiguration = | RulePlainConfiguration | RuleWithUseConsistentGraphqlDescriptionsOptions; @@ -5495,6 +5503,10 @@ export interface RuleWithUseConsistentArrowReturnOptions { level: RulePlainConfiguration; options?: UseConsistentArrowReturnOptions; } +export interface RuleWithUseConsistentEnumValueTypeOptions { + level: RulePlainConfiguration; + options?: UseConsistentEnumValueTypeOptions; +} export interface RuleWithUseConsistentGraphqlDescriptionsOptions { level: RulePlainConfiguration; options?: UseConsistentGraphqlDescriptionsOptions; @@ -6887,6 +6899,7 @@ This option is only applicable when used in conjunction with the `asNeeded` opti */ style?: UseConsistentArrowReturnStyle; } +export type UseConsistentEnumValueTypeOptions = {}; export interface UseConsistentGraphqlDescriptionsOptions { /** * The description style to enforce. Defaults to "block" @@ -7740,6 +7753,7 @@ export type Category = | "lint/nursery/noLeakedRender" | "lint/nursery/noMissingGenericFamilyKeyword" | "lint/nursery/noMisusedPromises" + | "lint/nursery/useConsistentEnumValueType" | "lint/nursery/noMultiAssign" | "lint/nursery/noMultiStr" | "lint/nursery/noNextAsyncClientComponent" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 8d37ea489a99..6b9592e9317f 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -5765,6 +5765,13 @@ { "type": "null" } ] }, + "useConsistentEnumValueType": { + "description": "Disallow enums from having both number and string members.\nSee https://biomejs.dev/linter/rules/use-consistent-enum-value-type", + "anyOf": [ + { "$ref": "#/$defs/UseConsistentEnumValueTypeConfiguration" }, + { "type": "null" } + ] + }, "useConsistentGraphqlDescriptions": { "description": "Require all descriptions to follow the same style (either block or inline) to maintain consistency and improve readability across the schema.\nSee https://biomejs.dev/linter/rules/use-consistent-graphql-descriptions", "anyOf": [ @@ -9353,6 +9360,15 @@ "additionalProperties": false, "required": ["level"] }, + "RuleWithUseConsistentEnumValueTypeOptions": { + "type": "object", + "properties": { + "level": { "$ref": "#/$defs/RulePlainConfiguration" }, + "options": { "$ref": "#/$defs/UseConsistentEnumValueTypeOptions" } + }, + "additionalProperties": false, + "required": ["level"] + }, "RuleWithUseConsistentGraphqlDescriptionsOptions": { "type": "object", "properties": { @@ -12289,6 +12305,16 @@ "type": "object", "additionalProperties": false }, + "UseConsistentEnumValueTypeConfiguration": { + "oneOf": [ + { "$ref": "#/$defs/RulePlainConfiguration" }, + { "$ref": "#/$defs/RuleWithUseConsistentEnumValueTypeOptions" } + ] + }, + "UseConsistentEnumValueTypeOptions": { + "type": "object", + "additionalProperties": false + }, "UseConsistentGraphqlDescriptionsConfiguration": { "oneOf": [ { "$ref": "#/$defs/RulePlainConfiguration" },