diff --git a/crates/oxc_linter/src/config/mod.rs b/crates/oxc_linter/src/config/mod.rs index 583754145..822ac7cc8 100644 --- a/crates/oxc_linter/src/config/mod.rs +++ b/crates/oxc_linter/src/config/mod.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::{collections::HashMap, path::PathBuf}; pub mod errors; use oxc_diagnostics::{Error, FailedToOpenFileError, Report}; @@ -7,7 +7,7 @@ use serde_json::Value; use crate::{ rules::{RuleEnum, RULES}, - AllowWarnDeny, + AllowWarnDeny, JsxA11y, LintSettings, }; use self::errors::{ @@ -17,6 +17,7 @@ use self::errors::{ pub struct ESLintConfig { rules: std::vec::Vec, + settings: LintSettings, } impl ESLintConfig { @@ -66,6 +67,8 @@ impl ESLintConfig { } }; + let settings = parse_settings_from_root(&file); + // `extends` provides the defaults // `rules` provides the overrides let rules = RULES.clone().into_iter().filter_map(|rule| { @@ -91,12 +94,16 @@ impl ESLintConfig { } }); - Ok(Self { rules: rules.collect::>() }) + Ok(Self { rules: rules.collect::>(), settings }) } - pub fn into_rules(mut self) -> Vec { + pub fn into_rules(mut self) -> Self { self.rules.sort_unstable_by_key(RuleEnum::name); - self.rules + self + } + + pub fn get_config(self) -> (std::vec::Vec, LintSettings) { + (self.rules, self.settings) } } @@ -151,6 +158,42 @@ fn parse_rules( .collect::, Error>>() } +fn parse_settings_from_root(root_json: &Value) -> LintSettings { + let Value::Object(root_object) = root_json else { return LintSettings::default() }; + + let Some(settings_value) = root_object.get("settings") else { return LintSettings::default() }; + + parse_settings(settings_value) +} + +pub fn parse_settings(setting_value: &Value) -> LintSettings { + if let Value::Object(settings_object) = setting_value { + if let Some(Value::Object(jsx_a11y)) = settings_object.get("jsx-a11y") { + let mut jsx_a11y_setting = + JsxA11y { polymorphic_prop_name: None, components: HashMap::new() }; + + if let Some(Value::Object(components)) = jsx_a11y.get("components") { + let components_map: HashMap = components + .iter() + .map(|(key, value)| (String::from(key), String::from(value.as_str().unwrap()))) + .collect(); + + jsx_a11y_setting.set_components(components_map); + } + + if let Some(Value::String(polymorphic_prop_name)) = jsx_a11y.get("polymorphicPropName") + { + jsx_a11y_setting + .set_polymorphic_prop_name(Some(String::from(polymorphic_prop_name))); + } + + return LintSettings { jsx_a11y: jsx_a11y_setting }; + } + } + + LintSettings::default() +} + pub const EXTENDS_MAP: Map<&'static str, &'static str> = phf_map! { "eslint:recommended" => "eslint", "plugin:react/recommended" => "react", diff --git a/crates/oxc_linter/src/context.rs b/crates/oxc_linter/src/context.rs index 6b27043a0..cf47c2d3a 100644 --- a/crates/oxc_linter/src/context.rs +++ b/crates/oxc_linter/src/context.rs @@ -8,7 +8,7 @@ use oxc_span::SourceType; use crate::{ disable_directives::{DisableDirectives, DisableDirectivesBuilder}, fixer::{Fix, Message}, - AstNode, + AstNode, LintSettings, }; pub struct LintContext<'a> { @@ -24,10 +24,12 @@ pub struct LintContext<'a> { current_rule_name: &'static str, file_path: Box, + + settings: LintSettings, } impl<'a> LintContext<'a> { - pub fn new(file_path: Box, semantic: &Rc>) -> Self { + pub fn new(file_path: Box, semantic: &Rc>, settings: LintSettings) -> Self { let disable_directives = DisableDirectivesBuilder::new(semantic.source_text(), semantic.trivias()).build(); Self { @@ -37,6 +39,7 @@ impl<'a> LintContext<'a> { fix: false, current_rule_name: "", file_path, + settings, } } @@ -54,6 +57,10 @@ impl<'a> LintContext<'a> { &self.disable_directives } + pub fn settings(&self) -> LintSettings { + self.settings.clone() + } + pub fn source_text(&self) -> &'a str { self.semantic().source_text() } diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index 51dae0688..294fac9ee 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -17,7 +17,7 @@ mod rules; mod service; mod utils; -use std::{self, fs, io::Write, rc::Rc, time::Duration}; +use std::{self, collections::HashMap, fs, io::Write, rc::Rc, time::Duration}; use oxc_diagnostics::Report; pub(crate) use oxc_semantic::AstNode; @@ -33,10 +33,38 @@ pub use crate::{ }; pub(crate) use rules::{RuleEnum, RULES}; +#[derive(Debug, Clone)] +pub struct LintSettings { + jsx_a11y: JsxA11y, +} + +impl Default for LintSettings { + fn default() -> Self { + Self { jsx_a11y: JsxA11y { polymorphic_prop_name: None, components: HashMap::new() } } + } +} + +#[derive(Debug, Clone)] +pub struct JsxA11y { + polymorphic_prop_name: Option, + components: HashMap, +} + +impl JsxA11y { + pub fn set_components(&mut self, components: HashMap) { + self.components = components; + } + + pub fn set_polymorphic_prop_name(&mut self, name: Option) { + self.polymorphic_prop_name = name; + } +} + #[derive(Debug)] pub struct Linter { rules: Vec, options: LintOptions, + settings: LintSettings, } impl Default for Linter { @@ -52,15 +80,21 @@ impl Linter { .filter(|&rule| rule.category() == RuleCategory::Correctness) .cloned() .collect::>(); - Self { rules, options: LintOptions::default() } + Self { + rules, + options: LintOptions::default(), + settings: LintSettings { + jsx_a11y: JsxA11y { polymorphic_prop_name: None, components: HashMap::new() }, + }, + } } /// # Errors /// /// Returns `Err` if there are any errors parsing the configuration file. pub fn from_options(options: LintOptions) -> Result { - let rules = options.derive_rules()?; - Ok(Self { rules, options }) + let (rules, settings) = options.derive_rules_and_settings()?; + Ok(Self { rules, options, settings }) } #[must_use] @@ -69,6 +103,12 @@ impl Linter { self } + #[must_use] + pub fn with_settings(mut self, settings: LintSettings) -> Self { + self.settings = settings; + self + } + pub fn rules(&self) -> &Vec { &self.rules } @@ -120,6 +160,9 @@ impl Linter { ctx.into_message() } + pub fn get_settings(&self) -> LintSettings { + self.settings.clone() + } #[allow(unused)] fn read_rules_configuration() -> Option> { fs::read_to_string(".eslintrc.json") diff --git a/crates/oxc_linter/src/options.rs b/crates/oxc_linter/src/options.rs index ef21c7317..abc1af65a 100644 --- a/crates/oxc_linter/src/options.rs +++ b/crates/oxc_linter/src/options.rs @@ -9,7 +9,7 @@ use crate::{ ESLintConfig, }, rules::RULES, - RuleCategory, RuleEnum, + LintSettings, RuleCategory, RuleEnum, }; use oxc_diagnostics::{Error, Report}; use rustc_hash::FxHashSet; @@ -145,12 +145,12 @@ const JSX_A11Y_PLUGIN_NAME: &str = "jsx_a11y"; impl LintOptions { /// # Errors /// Returns `Err` if there are any errors parsing the configuration file. - pub fn derive_rules(&self) -> Result, Report> { + pub fn derive_rules_and_settings(&self) -> Result<(Vec, LintSettings), Report> { let mut rules: FxHashSet = FxHashSet::default(); if let Some(path) = &self.config_path { - let rules = ESLintConfig::new(path)?.into_rules(); - return Ok(rules); + let (rules, settings) = ESLintConfig::new(path)?.into_rules().get_config(); + return Ok((rules, settings)); } let all_rules = self.get_filtered_rules(); @@ -195,7 +195,7 @@ impl LintOptions { let mut rules = rules.into_iter().collect::>(); // for stable diagnostics output ordering rules.sort_unstable_by_key(RuleEnum::name); - Ok(rules) + Ok((rules, LintSettings::default())) } // get final filtered rules by reading `self.jest_plugin` and `self.jsx_a11y_plugin` diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs index 7a9528372..ff36dc5c0 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs @@ -1,6 +1,6 @@ use phf::phf_set; -use oxc_ast::{ast::JSXElementName, AstKind}; +use oxc_ast::AstKind; use oxc_diagnostics::{ miette::{self, Diagnostic}, thiserror::Error, @@ -8,7 +8,12 @@ use oxc_diagnostics::{ use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, rule::Rule, utils::has_jsx_prop, AstNode}; +use crate::{ + context::LintContext, + rule::Rule, + utils::{get_element_type, has_jsx_prop}, + AstNode, +}; #[derive(Debug, Error, Diagnostic)] #[error("eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users")] @@ -86,12 +91,11 @@ impl Rule for NoAutofocus { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::JSXElement(jsx_el) = node.kind() { if let Option::Some(autofocus) = has_jsx_prop(&jsx_el.opening_element, "autoFocus") { + let Some(element_type) = get_element_type(ctx, &jsx_el.opening_element) else { + return; + }; if self.ignore_non_dom { - let JSXElementName::Identifier(ident) = &jsx_el.opening_element.name else { - return; - }; - let name = ident.name.as_str(); - if HTML_TAG.contains(name) { + if HTML_TAG.contains(&element_type) { if let oxc_ast::ast::JSXAttributeItem::Attribute(attr) = autofocus { ctx.diagnostic(NoAutofocusDiagnostic(attr.span)); } @@ -262,38 +266,46 @@ const HTML_TAG: phf::Set<&'static str> = phf_set! { #[test] fn test() { use crate::tester::Tester; - fn array() -> serde_json::Value { + fn config() -> serde_json::Value { serde_json::json!([2,{ "ignoreNonDOM": true }]) } + fn settings() -> serde_json::Value { + serde_json::json!({ + "jsx-a11y": { + "components": { + "Button": "button", + } + } + }) + } + let pass = vec![ - ("
;", None), - ("
;", None), - (";", None), - ("", None), - ("