diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index 630a3c4cb1e28..8aae18680594e 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -13,12 +13,15 @@ use oxc_semantic::NodeId; use oxc_span::{GetSpan, Span}; use oxc_syntax::{identifier::is_identifier_name, keyword::is_reserved_keyword_or_global_object}; +use schemars::JsonSchema; +use serde::Deserialize; + use crate::{ AstNode, ast_util::get_function_name_with_kind, context::LintContext, fixer::{RuleFix, RuleFixer}, - rule::Rule, + rule::{Rule, TupleRuleConfig}, }; fn named_diagnostic(function_name: &str, span: Span) -> OxcDiagnostic { @@ -33,32 +36,43 @@ fn unnamed_diagnostic(inferred_name_or_description: &str, span: Span) -> OxcDiag .with_help("Consider giving this function expression a name.") } -#[derive(Debug, Clone, Default)] -struct FuncNamesConfig { - functions: FuncNamesConfigType, - generators: FuncNamesConfigType, -} - -#[derive(Debug, Default, Clone)] -pub struct FuncNames { - config: FuncNamesConfig, -} - -#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, JsonSchema, Deserialize)] +#[serde(rename_all = "kebab-case")] enum FuncNamesConfigType { + /// Requires all function expressions to have a name. #[default] Always, + /// Requires a name only if one is not automatically inferred. AsNeeded, + /// Disallows names for function expressions. Never, } -impl From<&serde_json::Value> for FuncNamesConfigType { - fn from(raw: &serde_json::Value) -> Self { - match raw.as_str() { - Some("as-needed") => Self::AsNeeded, - Some("never") => Self::Never, - _ => Self::Always, - } +#[derive(Debug, Default, Clone, JsonSchema, Deserialize)] +#[serde(default)] +pub struct FuncNames(FuncNamesConfigType, FuncNamesGeneratorsConfig); + +#[derive(Debug, Default, Clone, JsonSchema, Deserialize)] +#[serde(rename_all = "camelCase", default, deny_unknown_fields)] +pub struct FuncNamesGeneratorsConfig { + /// Configuration for generator function expressions. If not specified, uses the + /// primary configuration. + /// + /// Accepts `always`, `as-needed`, or `never`. + /// + /// Generator functions are those defined using the `function*` syntax. + /// ```js + /// function* foobar(i) { + /// yield i; + /// yield i + 10; + /// } + /// ``` + generators: Option, +} + +impl FuncNames { + fn generators_config(&self) -> FuncNamesConfigType { + self.1.generators.unwrap_or(self.0) } } @@ -74,24 +88,6 @@ declare_oxc_lint!( /// This makes it more difficult to find where an error is thrown. /// Providing an explicit name also improves readability and consistency. /// - /// ### Options - /// - /// First option: - /// - Type: `string` - /// - Default: `"always"` - /// - Possible values: - /// - `"always"` - requires all function expressions to have a name. - /// - `"as-needed"` - requires a name only if one is not automatically inferred. - /// - `"never"` - disallows names for function expressions. - /// - /// Second option: - /// - Type: `object` - /// - Properties: - /// - `generators`: `("always" | "as-needed" | "never")` (default: falls back to first option) - /// - `"always"` - require named generator function expressions. - /// - `"as-needed"` - require a name only when not inferred. - /// - `"never"` - disallow names for generator function expressions. - /// /// Example configuration: /// ```json /// { @@ -221,30 +217,19 @@ declare_oxc_lint!( FuncNames, eslint, style, - conditional_fix_suggestion + conditional_fix_suggestion, + config = FuncNames, ); impl Rule for FuncNames { fn from_configuration(value: serde_json::Value) -> Result { - let Some(functions_config) = value.get(0) else { - return Ok(Self::default()); - }; - let generators_config = - value.get(1).and_then(|v| v.get("generators")).unwrap_or(functions_config); - - Ok(Self { - config: FuncNamesConfig { - functions: FuncNamesConfigType::from(functions_config), - generators: FuncNamesConfigType::from(generators_config), - }, - }) + serde_json::from_value::>(value).map(TupleRuleConfig::into_inner) } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::Function(func) = node.kind() { let parent_node = ctx.nodes().parent_node(node.id()); - let config = - if func.generator { self.config.generators } else { self.config.functions }; + let config = if func.generator { self.generators_config() } else { self.0 }; if is_invalid_function(config, func, parent_node) { // For named functions, check if they're recursive (need their name for recursion) diff --git a/crates/oxc_linter/tests/rule_configuration_documentation_test.rs b/crates/oxc_linter/tests/rule_configuration_documentation_test.rs index 75003cc81f966..3f136a5263abb 100644 --- a/crates/oxc_linter/tests/rule_configuration_documentation_test.rs +++ b/crates/oxc_linter/tests/rule_configuration_documentation_test.rs @@ -30,7 +30,6 @@ fn test_rules_with_custom_configuration_have_schema() { // list - newly-created rules should always be documented before being merged! let exceptions: &[&str] = &[ // eslint - "eslint/func-names", "eslint/no-empty-function", "eslint/no-restricted-imports", "eslint/no-warning-comments",