mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 12:51:57 +00:00
feat(linter): add jsx-a11y settings (#1668)
When we developed linter for #1141 , we needed to configure some settings for `jsx-a11y`, which was not supported before, but I am trying to support it now. like this: ``` 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![ ("<Button />", Some(config()), Some(settings())), ]; ```
This commit is contained in:
parent
cf91379d1b
commit
6a90cd4af4
13 changed files with 255 additions and 77 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
use std::path::PathBuf;
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
use oxc_diagnostics::{Error, FailedToOpenFileError, Report};
|
use oxc_diagnostics::{Error, FailedToOpenFileError, Report};
|
||||||
|
|
@ -7,7 +7,7 @@ use serde_json::Value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
rules::{RuleEnum, RULES},
|
rules::{RuleEnum, RULES},
|
||||||
AllowWarnDeny,
|
AllowWarnDeny, JsxA11y, LintSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::errors::{
|
use self::errors::{
|
||||||
|
|
@ -17,6 +17,7 @@ use self::errors::{
|
||||||
|
|
||||||
pub struct ESLintConfig {
|
pub struct ESLintConfig {
|
||||||
rules: std::vec::Vec<RuleEnum>,
|
rules: std::vec::Vec<RuleEnum>,
|
||||||
|
settings: LintSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ESLintConfig {
|
impl ESLintConfig {
|
||||||
|
|
@ -66,6 +67,8 @@ impl ESLintConfig {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let settings = parse_settings_from_root(&file);
|
||||||
|
|
||||||
// `extends` provides the defaults
|
// `extends` provides the defaults
|
||||||
// `rules` provides the overrides
|
// `rules` provides the overrides
|
||||||
let rules = RULES.clone().into_iter().filter_map(|rule| {
|
let rules = RULES.clone().into_iter().filter_map(|rule| {
|
||||||
|
|
@ -91,12 +94,16 @@ impl ESLintConfig {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Self { rules: rules.collect::<Vec<_>>() })
|
Ok(Self { rules: rules.collect::<Vec<_>>(), settings })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_rules(mut self) -> Vec<RuleEnum> {
|
pub fn into_rules(mut self) -> Self {
|
||||||
self.rules.sort_unstable_by_key(RuleEnum::name);
|
self.rules.sort_unstable_by_key(RuleEnum::name);
|
||||||
self.rules
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_config(self) -> (std::vec::Vec<RuleEnum>, LintSettings) {
|
||||||
|
(self.rules, self.settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,6 +158,42 @@ fn parse_rules(
|
||||||
.collect::<Result<Vec<_>, Error>>()
|
.collect::<Result<Vec<_>, 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<String, String> = 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! {
|
pub const EXTENDS_MAP: Map<&'static str, &'static str> = phf_map! {
|
||||||
"eslint:recommended" => "eslint",
|
"eslint:recommended" => "eslint",
|
||||||
"plugin:react/recommended" => "react",
|
"plugin:react/recommended" => "react",
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use oxc_span::SourceType;
|
||||||
use crate::{
|
use crate::{
|
||||||
disable_directives::{DisableDirectives, DisableDirectivesBuilder},
|
disable_directives::{DisableDirectives, DisableDirectivesBuilder},
|
||||||
fixer::{Fix, Message},
|
fixer::{Fix, Message},
|
||||||
AstNode,
|
AstNode, LintSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct LintContext<'a> {
|
pub struct LintContext<'a> {
|
||||||
|
|
@ -24,10 +24,12 @@ pub struct LintContext<'a> {
|
||||||
current_rule_name: &'static str,
|
current_rule_name: &'static str,
|
||||||
|
|
||||||
file_path: Box<Path>,
|
file_path: Box<Path>,
|
||||||
|
|
||||||
|
settings: LintSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> LintContext<'a> {
|
impl<'a> LintContext<'a> {
|
||||||
pub fn new(file_path: Box<Path>, semantic: &Rc<Semantic<'a>>) -> Self {
|
pub fn new(file_path: Box<Path>, semantic: &Rc<Semantic<'a>>, settings: LintSettings) -> Self {
|
||||||
let disable_directives =
|
let disable_directives =
|
||||||
DisableDirectivesBuilder::new(semantic.source_text(), semantic.trivias()).build();
|
DisableDirectivesBuilder::new(semantic.source_text(), semantic.trivias()).build();
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -37,6 +39,7 @@ impl<'a> LintContext<'a> {
|
||||||
fix: false,
|
fix: false,
|
||||||
current_rule_name: "",
|
current_rule_name: "",
|
||||||
file_path,
|
file_path,
|
||||||
|
settings,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,6 +57,10 @@ impl<'a> LintContext<'a> {
|
||||||
&self.disable_directives
|
&self.disable_directives
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn settings(&self) -> LintSettings {
|
||||||
|
self.settings.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn source_text(&self) -> &'a str {
|
pub fn source_text(&self) -> &'a str {
|
||||||
self.semantic().source_text()
|
self.semantic().source_text()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ mod rules;
|
||||||
mod service;
|
mod service;
|
||||||
mod utils;
|
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;
|
use oxc_diagnostics::Report;
|
||||||
pub(crate) use oxc_semantic::AstNode;
|
pub(crate) use oxc_semantic::AstNode;
|
||||||
|
|
@ -33,10 +33,38 @@ pub use crate::{
|
||||||
};
|
};
|
||||||
pub(crate) use rules::{RuleEnum, RULES};
|
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<String>,
|
||||||
|
components: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsxA11y {
|
||||||
|
pub fn set_components(&mut self, components: HashMap<String, String>) {
|
||||||
|
self.components = components;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_polymorphic_prop_name(&mut self, name: Option<String>) {
|
||||||
|
self.polymorphic_prop_name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Linter {
|
pub struct Linter {
|
||||||
rules: Vec<RuleEnum>,
|
rules: Vec<RuleEnum>,
|
||||||
options: LintOptions,
|
options: LintOptions,
|
||||||
|
settings: LintSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Linter {
|
impl Default for Linter {
|
||||||
|
|
@ -52,15 +80,21 @@ impl Linter {
|
||||||
.filter(|&rule| rule.category() == RuleCategory::Correctness)
|
.filter(|&rule| rule.category() == RuleCategory::Correctness)
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
Self { rules, options: LintOptions::default() }
|
Self {
|
||||||
|
rules,
|
||||||
|
options: LintOptions::default(),
|
||||||
|
settings: LintSettings {
|
||||||
|
jsx_a11y: JsxA11y { polymorphic_prop_name: None, components: HashMap::new() },
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns `Err` if there are any errors parsing the configuration file.
|
/// Returns `Err` if there are any errors parsing the configuration file.
|
||||||
pub fn from_options(options: LintOptions) -> Result<Self, Report> {
|
pub fn from_options(options: LintOptions) -> Result<Self, Report> {
|
||||||
let rules = options.derive_rules()?;
|
let (rules, settings) = options.derive_rules_and_settings()?;
|
||||||
Ok(Self { rules, options })
|
Ok(Self { rules, options, settings })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
@ -69,6 +103,12 @@ impl Linter {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_settings(mut self, settings: LintSettings) -> Self {
|
||||||
|
self.settings = settings;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn rules(&self) -> &Vec<RuleEnum> {
|
pub fn rules(&self) -> &Vec<RuleEnum> {
|
||||||
&self.rules
|
&self.rules
|
||||||
}
|
}
|
||||||
|
|
@ -120,6 +160,9 @@ impl Linter {
|
||||||
ctx.into_message()
|
ctx.into_message()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_settings(&self) -> LintSettings {
|
||||||
|
self.settings.clone()
|
||||||
|
}
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn read_rules_configuration() -> Option<serde_json::Map<String, serde_json::Value>> {
|
fn read_rules_configuration() -> Option<serde_json::Map<String, serde_json::Value>> {
|
||||||
fs::read_to_string(".eslintrc.json")
|
fs::read_to_string(".eslintrc.json")
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use crate::{
|
||||||
ESLintConfig,
|
ESLintConfig,
|
||||||
},
|
},
|
||||||
rules::RULES,
|
rules::RULES,
|
||||||
RuleCategory, RuleEnum,
|
LintSettings, RuleCategory, RuleEnum,
|
||||||
};
|
};
|
||||||
use oxc_diagnostics::{Error, Report};
|
use oxc_diagnostics::{Error, Report};
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
@ -145,12 +145,12 @@ const JSX_A11Y_PLUGIN_NAME: &str = "jsx_a11y";
|
||||||
impl LintOptions {
|
impl LintOptions {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Returns `Err` if there are any errors parsing the configuration file.
|
/// Returns `Err` if there are any errors parsing the configuration file.
|
||||||
pub fn derive_rules(&self) -> Result<Vec<RuleEnum>, Report> {
|
pub fn derive_rules_and_settings(&self) -> Result<(Vec<RuleEnum>, LintSettings), Report> {
|
||||||
let mut rules: FxHashSet<RuleEnum> = FxHashSet::default();
|
let mut rules: FxHashSet<RuleEnum> = FxHashSet::default();
|
||||||
|
|
||||||
if let Some(path) = &self.config_path {
|
if let Some(path) = &self.config_path {
|
||||||
let rules = ESLintConfig::new(path)?.into_rules();
|
let (rules, settings) = ESLintConfig::new(path)?.into_rules().get_config();
|
||||||
return Ok(rules);
|
return Ok((rules, settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
let all_rules = self.get_filtered_rules();
|
let all_rules = self.get_filtered_rules();
|
||||||
|
|
@ -195,7 +195,7 @@ impl LintOptions {
|
||||||
let mut rules = rules.into_iter().collect::<Vec<_>>();
|
let mut rules = rules.into_iter().collect::<Vec<_>>();
|
||||||
// for stable diagnostics output ordering
|
// for stable diagnostics output ordering
|
||||||
rules.sort_unstable_by_key(RuleEnum::name);
|
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`
|
// get final filtered rules by reading `self.jest_plugin` and `self.jsx_a11y_plugin`
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use phf::phf_set;
|
use phf::phf_set;
|
||||||
|
|
||||||
use oxc_ast::{ast::JSXElementName, AstKind};
|
use oxc_ast::AstKind;
|
||||||
use oxc_diagnostics::{
|
use oxc_diagnostics::{
|
||||||
miette::{self, Diagnostic},
|
miette::{self, Diagnostic},
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
|
|
@ -8,7 +8,12 @@ use oxc_diagnostics::{
|
||||||
use oxc_macros::declare_oxc_lint;
|
use oxc_macros::declare_oxc_lint;
|
||||||
use oxc_span::Span;
|
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)]
|
#[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")]
|
#[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>) {
|
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||||
if let AstKind::JSXElement(jsx_el) = node.kind() {
|
if let AstKind::JSXElement(jsx_el) = node.kind() {
|
||||||
if let Option::Some(autofocus) = has_jsx_prop(&jsx_el.opening_element, "autoFocus") {
|
if let Option::Some(autofocus) = has_jsx_prop(&jsx_el.opening_element, "autoFocus") {
|
||||||
if self.ignore_non_dom {
|
let Some(element_type) = get_element_type(ctx, &jsx_el.opening_element) else {
|
||||||
let JSXElementName::Identifier(ident) = &jsx_el.opening_element.name else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let name = ident.name.as_str();
|
if self.ignore_non_dom {
|
||||||
if HTML_TAG.contains(name) {
|
if HTML_TAG.contains(&element_type) {
|
||||||
if let oxc_ast::ast::JSXAttributeItem::Attribute(attr) = autofocus {
|
if let oxc_ast::ast::JSXAttributeItem::Attribute(attr) = autofocus {
|
||||||
ctx.diagnostic(NoAutofocusDiagnostic(attr.span));
|
ctx.diagnostic(NoAutofocusDiagnostic(attr.span));
|
||||||
}
|
}
|
||||||
|
|
@ -262,38 +266,46 @@ const HTML_TAG: phf::Set<&'static str> = phf_set! {
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
use crate::tester::Tester;
|
use crate::tester::Tester;
|
||||||
fn array() -> serde_json::Value {
|
fn config() -> serde_json::Value {
|
||||||
serde_json::json!([2,{
|
serde_json::json!([2,{
|
||||||
"ignoreNonDOM": true
|
"ignoreNonDOM": true
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn settings() -> serde_json::Value {
|
||||||
|
serde_json::json!({
|
||||||
|
"jsx-a11y": {
|
||||||
|
"components": {
|
||||||
|
"Button": "button",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let pass = vec![
|
let pass = vec![
|
||||||
("<div />;", None),
|
("<div />;", None, None),
|
||||||
("<div autofocus />;", None),
|
("<div autofocus />;", None, None),
|
||||||
("<input autofocus='true' />;", None),
|
("<input autofocus='true' />;", None, None),
|
||||||
("<Foo bar />", None),
|
("<Foo bar />", None, None),
|
||||||
("<Button />", None),
|
("<Button />", None, None),
|
||||||
("<Foo autoFocus />", Some(array())),
|
("<Foo autoFocus />", Some(config()), None),
|
||||||
("<div><div autofocus /></div>", Some(array())),
|
("<div><div autofocus /></div>", Some(config()), None),
|
||||||
// TODO we need components_settings to test this
|
("<Button />", None, Some(settings())),
|
||||||
// ("<Button />", Some(serde_json::json!(ignoreNonDOMSchema))),
|
("<Button />", Some(config()), Some(settings())),
|
||||||
// ("<Button />", Some(serde_json::json!(ignoreNonDOMSchema)), setting),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let fail = vec![
|
let fail = vec![
|
||||||
("<div autoFocus />", None),
|
("<div autoFocus />", None, None),
|
||||||
("<div autoFocus={true} />", None),
|
("<div autoFocus={true} />", None, None),
|
||||||
("<div autoFocus={false} />", None),
|
("<div autoFocus={false} />", None, None),
|
||||||
("<div autoFocus={undefined} />", None),
|
("<div autoFocus={undefined} />", None, None),
|
||||||
("<div autoFocus='true' />", None),
|
("<div autoFocus='true' />", None, None),
|
||||||
("<div autoFocus='false' />", None),
|
("<div autoFocus='false' />", None, None),
|
||||||
("<input autoFocus />", None),
|
("<input autoFocus />", None, None),
|
||||||
("<Foo autoFocus />", None),
|
("<Foo autoFocus />", None, None),
|
||||||
("<Button autoFocus />", None),
|
("<Button autoFocus />", None, None),
|
||||||
// TODO we need components_settings to test this
|
("<Button autoFocus />", Some(config()), Some(settings())),
|
||||||
// ("<Button autoFocus />", Some(array())),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
Tester::new(NoAutofocus::NAME, pass, fail).test_and_snapshot();
|
Tester::new_with_settings(NoAutofocus::NAME, pass, fail).test_and_snapshot();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,7 @@ impl Runtime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn process_source<'a>(
|
fn process_source<'a>(
|
||||||
&self,
|
&self,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
|
|
@ -233,8 +234,11 @@ impl Runtime {
|
||||||
return semantic_ret.errors.into_iter().map(|err| Message::new(err, None)).collect();
|
return semantic_ret.errors.into_iter().map(|err| Message::new(err, None)).collect();
|
||||||
};
|
};
|
||||||
|
|
||||||
let lint_ctx =
|
let lint_ctx = LintContext::new(
|
||||||
LintContext::new(path.to_path_buf().into_boxed_path(), &Rc::new(semantic_ret.semantic));
|
path.to_path_buf().into_boxed_path(),
|
||||||
|
&Rc::new(semantic_ret.semantic),
|
||||||
|
self.linter.get_settings(),
|
||||||
|
);
|
||||||
self.linter.run(lint_ctx)
|
self.linter.run(lint_ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: crates/oxc_linter/src/tester.rs
|
source: crates/oxc_linter/src/tester.rs
|
||||||
assertion_line: 119
|
assertion_line: 130
|
||||||
expression: no_autofocus
|
expression: no_autofocus
|
||||||
---
|
---
|
||||||
⚠ eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users
|
⚠ eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users
|
||||||
|
|
@ -66,4 +66,11 @@ expression: no_autofocus
|
||||||
╰────
|
╰────
|
||||||
help: Remove `autofocus` attribute
|
help: Remove `autofocus` attribute
|
||||||
|
|
||||||
|
⚠ eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users
|
||||||
|
╭─[no_autofocus.tsx:1:1]
|
||||||
|
1 │ <Button autoFocus />
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: Remove `autofocus` attribute
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@ use oxc_diagnostics::miette::{GraphicalReportHandler, GraphicalTheme, NamedSourc
|
||||||
use oxc_diagnostics::DiagnosticService;
|
use oxc_diagnostics::DiagnosticService;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::{rules::RULES, Fixer, LintOptions, LintService, Linter, RuleEnum};
|
use crate::{
|
||||||
|
config::parse_settings, rules::RULES, Fixer, LintOptions, LintService, LintSettings, Linter,
|
||||||
|
RuleEnum,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Eq, PartialEq)]
|
#[derive(Eq, PartialEq)]
|
||||||
enum TestResult {
|
enum TestResult {
|
||||||
|
|
@ -20,8 +23,8 @@ enum TestResult {
|
||||||
pub struct Tester {
|
pub struct Tester {
|
||||||
rule_name: &'static str,
|
rule_name: &'static str,
|
||||||
rule_path: PathBuf,
|
rule_path: PathBuf,
|
||||||
expect_pass: Vec<(String, Option<Value>)>,
|
expect_pass: Vec<(String, Option<Value>, Option<Value>)>,
|
||||||
expect_fail: Vec<(String, Option<Value>)>,
|
expect_fail: Vec<(String, Option<Value>, Option<Value>)>,
|
||||||
expect_fix: Vec<(String, String, Option<Value>)>,
|
expect_fix: Vec<(String, String, Option<Value>)>,
|
||||||
snapshot: String,
|
snapshot: String,
|
||||||
current_working_directory: Box<Path>,
|
current_working_directory: Box<Path>,
|
||||||
|
|
@ -35,10 +38,28 @@ impl Tester {
|
||||||
rule_name: &'static str,
|
rule_name: &'static str,
|
||||||
expect_pass: Vec<(S, Option<Value>)>,
|
expect_pass: Vec<(S, Option<Value>)>,
|
||||||
expect_fail: Vec<(S, Option<Value>)>,
|
expect_fail: Vec<(S, Option<Value>)>,
|
||||||
|
) -> Self {
|
||||||
|
let expect_pass =
|
||||||
|
expect_pass.into_iter().map(|(s, r)| (s.into(), r, None)).collect::<Vec<_>>();
|
||||||
|
let expect_fail =
|
||||||
|
expect_fail.into_iter().map(|(s, r)| (s.into(), r, None)).collect::<Vec<_>>();
|
||||||
|
Self::new_with_settings(rule_name, expect_pass, expect_fail)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_settings<S: Into<String>>(
|
||||||
|
rule_name: &'static str,
|
||||||
|
expect_pass: Vec<(S, Option<Value>, Option<Value>)>,
|
||||||
|
expect_fail: Vec<(S, Option<Value>, Option<Value>)>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let rule_path = PathBuf::from(rule_name.replace('-', "_")).with_extension("tsx");
|
let rule_path = PathBuf::from(rule_name.replace('-', "_")).with_extension("tsx");
|
||||||
let expect_pass = expect_pass.into_iter().map(|(s, r)| (s.into(), r)).collect::<Vec<_>>();
|
let expect_pass = expect_pass
|
||||||
let expect_fail = expect_fail.into_iter().map(|(s, r)| (s.into(), r)).collect::<Vec<_>>();
|
.into_iter()
|
||||||
|
.map(|(s, r, settings)| (s.into(), r, settings))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let expect_fail = expect_fail
|
||||||
|
.into_iter()
|
||||||
|
.map(|(s, r, settings)| (s.into(), r, settings))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
let current_working_directory =
|
let current_working_directory =
|
||||||
env::current_dir().unwrap().join("fixtures/import").into_boxed_path();
|
env::current_dir().unwrap().join("fixtures/import").into_boxed_path();
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -60,9 +81,11 @@ impl Tester {
|
||||||
expect_pass: Vec<S>,
|
expect_pass: Vec<S>,
|
||||||
expect_fail: Vec<S>,
|
expect_fail: Vec<S>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let expect_pass = expect_pass.into_iter().map(|s| (s.into(), None)).collect::<Vec<_>>();
|
let expect_pass =
|
||||||
let expect_fail = expect_fail.into_iter().map(|s| (s.into(), None)).collect::<Vec<_>>();
|
expect_pass.into_iter().map(|s| (s.into(), None, None)).collect::<Vec<_>>();
|
||||||
Self::new(rule_name, expect_pass, expect_fail)
|
let expect_fail =
|
||||||
|
expect_fail.into_iter().map(|s| (s.into(), None, None)).collect::<Vec<_>>();
|
||||||
|
Self::new_with_settings(rule_name, expect_pass, expect_fail)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_expect_pass_fail<S: Into<String>>(
|
pub fn update_expect_pass_fail<S: Into<String>>(
|
||||||
|
|
@ -70,8 +93,10 @@ impl Tester {
|
||||||
expect_pass: Vec<S>,
|
expect_pass: Vec<S>,
|
||||||
expect_fail: Vec<S>,
|
expect_fail: Vec<S>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.expect_pass = expect_pass.into_iter().map(|s| (s.into(), None)).collect::<Vec<_>>();
|
self.expect_pass =
|
||||||
self.expect_fail = expect_fail.into_iter().map(|s| (s.into(), None)).collect::<Vec<_>>();
|
expect_pass.into_iter().map(|s| (s.into(), None, None)).collect::<Vec<_>>();
|
||||||
|
self.expect_fail =
|
||||||
|
expect_fail.into_iter().map(|s| (s.into(), None, None)).collect::<Vec<_>>();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,16 +146,16 @@ impl Tester {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_pass(&mut self) {
|
fn test_pass(&mut self) {
|
||||||
for (test, config) in self.expect_pass.clone() {
|
for (test, config, settings) in self.expect_pass.clone() {
|
||||||
let result = self.run(&test, config, false);
|
let result = self.run(&test, config, false, &settings);
|
||||||
let passed = result == TestResult::Passed;
|
let passed = result == TestResult::Passed;
|
||||||
assert!(passed, "expect test to pass: {test} {}", self.snapshot);
|
assert!(passed, "expect test to pass: {test} {}", self.snapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_fail(&mut self) {
|
fn test_fail(&mut self) {
|
||||||
for (test, config) in self.expect_fail.clone() {
|
for (test, config, settings) in self.expect_fail.clone() {
|
||||||
let result = self.run(&test, config, false);
|
let result = self.run(&test, config, false, &settings);
|
||||||
let failed = result == TestResult::Failed;
|
let failed = result == TestResult::Failed;
|
||||||
assert!(failed, "expect test to fail: {test}");
|
assert!(failed, "expect test to fail: {test}");
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +163,7 @@ impl Tester {
|
||||||
|
|
||||||
fn test_fix(&mut self) {
|
fn test_fix(&mut self) {
|
||||||
for (test, expected, config) in self.expect_fix.clone() {
|
for (test, expected, config) in self.expect_fix.clone() {
|
||||||
let result = self.run(&test, config, true);
|
let result = self.run(&test, config, true, &None);
|
||||||
if let TestResult::Fixed(fixed_str) = result {
|
if let TestResult::Fixed(fixed_str) = result {
|
||||||
assert_eq!(expected, fixed_str);
|
assert_eq!(expected, fixed_str);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -147,15 +172,26 @@ impl Tester {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&mut self, source_text: &str, config: Option<Value>, is_fix: bool) -> TestResult {
|
fn run(
|
||||||
|
&mut self,
|
||||||
|
source_text: &str,
|
||||||
|
config: Option<Value>,
|
||||||
|
is_fix: bool,
|
||||||
|
settings: &Option<Value>,
|
||||||
|
) -> TestResult {
|
||||||
let allocator = Allocator::default();
|
let allocator = Allocator::default();
|
||||||
let rule = self.find_rule().read_json(config);
|
let rule = self.find_rule().read_json(config);
|
||||||
|
let lint_settings: LintSettings =
|
||||||
|
settings.as_ref().map_or_else(LintSettings::default, parse_settings);
|
||||||
let options = LintOptions::default()
|
let options = LintOptions::default()
|
||||||
.with_fix(is_fix)
|
.with_fix(is_fix)
|
||||||
.with_import_plugin(self.import_plugin)
|
.with_import_plugin(self.import_plugin)
|
||||||
.with_jest_plugin(self.jest_plugin)
|
.with_jest_plugin(self.jest_plugin)
|
||||||
.with_jsx_a11y_plugin(self.jsx_a11y_plugin);
|
.with_jsx_a11y_plugin(self.jsx_a11y_plugin);
|
||||||
let linter = Linter::from_options(options).unwrap().with_rules(vec![rule]);
|
let linter = Linter::from_options(options)
|
||||||
|
.unwrap()
|
||||||
|
.with_rules(vec![rule])
|
||||||
|
.with_settings(lint_settings);
|
||||||
let path_to_lint = if self.import_plugin {
|
let path_to_lint = if self.import_plugin {
|
||||||
self.current_working_directory.join(&self.rule_path)
|
self.current_working_directory.join(&self.rule_path)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use oxc_ast::{
|
||||||
};
|
};
|
||||||
use oxc_semantic::{AstNode, SymbolFlags};
|
use oxc_semantic::{AstNode, SymbolFlags};
|
||||||
|
|
||||||
use crate::LintContext;
|
use crate::{JsxA11y, LintContext, LintSettings};
|
||||||
|
|
||||||
pub fn is_create_element_call(call_expr: &CallExpression) -> bool {
|
pub fn is_create_element_call(call_expr: &CallExpression) -> bool {
|
||||||
if let Some(member_expr) = call_expr.callee.get_member_expr() {
|
if let Some(member_expr) = call_expr.callee.get_member_expr() {
|
||||||
|
|
@ -163,3 +163,18 @@ pub fn get_parent_es6_component<'a, 'b>(ctx: &'b LintContext<'a>) -> Option<&'b
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
pub fn get_element_type(context: &LintContext, element: &JSXOpeningElement) -> Option<String> {
|
||||||
|
let JSXElementName::Identifier(ident) = &element.name else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut element_type = String::from(ident.name.as_str());
|
||||||
|
|
||||||
|
let LintSettings { jsx_a11y } = context.settings();
|
||||||
|
let JsxA11y { polymorphic_prop_name: _, components } = jsx_a11y;
|
||||||
|
if let Some(val) = components.get(&element_type) {
|
||||||
|
element_type = String::from(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(element_type)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ fn run_individual_test(
|
||||||
) -> std::result::Result<Vec<Report>, Vec<Report>> {
|
) -> std::result::Result<Vec<Report>, Vec<Report>> {
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use oxc_linter::LintContext;
|
use oxc_linter::{LintContext, LintSettings};
|
||||||
|
|
||||||
let file_path = &test.relative_path.last().expect("there to be atleast 1 path part");
|
let file_path = &test.relative_path.last().expect("there to be atleast 1 path part");
|
||||||
let source_text = &test.code;
|
let source_text = &test.code;
|
||||||
|
|
@ -48,8 +48,11 @@ fn run_individual_test(
|
||||||
|
|
||||||
let semantic = Rc::new(semantic);
|
let semantic = Rc::new(semantic);
|
||||||
|
|
||||||
let mut lint_ctx =
|
let mut lint_ctx = LintContext::new(
|
||||||
LintContext::new(PathBuf::from(file_path).into_boxed_path(), &Rc::clone(&semantic));
|
PathBuf::from(file_path).into_boxed_path(),
|
||||||
|
&Rc::clone(&semantic),
|
||||||
|
LintSettings::default(),
|
||||||
|
);
|
||||||
|
|
||||||
let result = plugin.lint_file_with_rule(
|
let result = plugin.lint_file_with_rule(
|
||||||
&mut lint_ctx,
|
&mut lint_ctx,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use oxc::{
|
||||||
span::SourceType,
|
span::SourceType,
|
||||||
transformer::{TransformOptions, TransformTarget, Transformer},
|
transformer::{TransformOptions, TransformTarget, Transformer},
|
||||||
};
|
};
|
||||||
use oxc_linter::{LintContext, Linter};
|
use oxc_linter::{LintContext, LintSettings, Linter};
|
||||||
use oxc_prettier::{Prettier, PrettierOptions};
|
use oxc_prettier::{Prettier, PrettierOptions};
|
||||||
use oxc_query::{schema, Adapter, SCHEMA_TEXT};
|
use oxc_query::{schema, Adapter, SCHEMA_TEXT};
|
||||||
use oxc_type_synthesis::{synthesize_program, Diagnostic as TypeCheckDiagnostic};
|
use oxc_type_synthesis::{synthesize_program, Diagnostic as TypeCheckDiagnostic};
|
||||||
|
|
@ -205,7 +205,8 @@ impl Oxc {
|
||||||
self.save_diagnostics(semantic_ret.errors);
|
self.save_diagnostics(semantic_ret.errors);
|
||||||
|
|
||||||
let semantic = Rc::new(semantic_ret.semantic);
|
let semantic = Rc::new(semantic_ret.semantic);
|
||||||
let lint_ctx = LintContext::new(path.into_boxed_path(), &semantic);
|
let lint_ctx =
|
||||||
|
LintContext::new(path.into_boxed_path(), &semantic, LintSettings::default());
|
||||||
let linter_ret = Linter::new().run(lint_ctx);
|
let linter_ret = Linter::new().run(lint_ctx);
|
||||||
let diagnostics = linter_ret.into_iter().map(|e| e.error).collect();
|
let diagnostics = linter_ret.into_iter().map(|e| e.error).collect();
|
||||||
self.save_diagnostics(diagnostics);
|
self.save_diagnostics(diagnostics);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use crate::walk::Walk;
|
||||||
use miette::NamedSource;
|
use miette::NamedSource;
|
||||||
use oxc_allocator::Allocator;
|
use oxc_allocator::Allocator;
|
||||||
use oxc_diagnostics::{miette, Error, Severity};
|
use oxc_diagnostics::{miette, Error, Severity};
|
||||||
use oxc_linter::{LintContext, Linter};
|
use oxc_linter::{LintContext, LintSettings, Linter};
|
||||||
use oxc_linter_plugin::{make_relative_path_parts, LinterPlugin};
|
use oxc_linter_plugin::{make_relative_path_parts, LinterPlugin};
|
||||||
use oxc_parser::Parser;
|
use oxc_parser::Parser;
|
||||||
use oxc_semantic::SemanticBuilder;
|
use oxc_semantic::SemanticBuilder;
|
||||||
|
|
@ -270,8 +270,11 @@ impl IsolatedLintHandler {
|
||||||
return Some(Self::wrap_diagnostics(path, &source_text, reports));
|
return Some(Self::wrap_diagnostics(path, &source_text, reports));
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut lint_ctx =
|
let mut lint_ctx = LintContext::new(
|
||||||
LintContext::new(path.to_path_buf().into_boxed_path(), &Rc::new(semantic_ret.semantic));
|
path.to_path_buf().into_boxed_path(),
|
||||||
|
&Rc::new(semantic_ret.semantic),
|
||||||
|
LintSettings::default(),
|
||||||
|
);
|
||||||
{
|
{
|
||||||
if let Ok(guard) = plugin.read() {
|
if let Ok(guard) = plugin.read() {
|
||||||
if let Some(plugin) = &*guard {
|
if let Some(plugin) = &*guard {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use std::{path::PathBuf, rc::Rc};
|
||||||
|
|
||||||
use oxc_allocator::Allocator;
|
use oxc_allocator::Allocator;
|
||||||
use oxc_benchmark::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
use oxc_benchmark::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||||
use oxc_linter::{AllowWarnDeny, LintContext, LintOptions, Linter};
|
use oxc_linter::{AllowWarnDeny, LintContext, LintOptions, LintSettings, Linter};
|
||||||
use oxc_parser::Parser;
|
use oxc_parser::Parser;
|
||||||
use oxc_semantic::SemanticBuilder;
|
use oxc_semantic::SemanticBuilder;
|
||||||
use oxc_span::SourceType;
|
use oxc_span::SourceType;
|
||||||
|
|
@ -38,7 +38,11 @@ fn bench_linter(criterion: &mut Criterion) {
|
||||||
let linter = Linter::from_options(lint_options).unwrap();
|
let linter = Linter::from_options(lint_options).unwrap();
|
||||||
let semantic = Rc::new(semantic_ret.semantic);
|
let semantic = Rc::new(semantic_ret.semantic);
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
linter.run(LintContext::new(PathBuf::from("").into_boxed_path(), &semantic))
|
linter.run(LintContext::new(
|
||||||
|
PathBuf::from("").into_boxed_path(),
|
||||||
|
&semantic,
|
||||||
|
LintSettings::default(),
|
||||||
|
))
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue