diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 94951355cb6e0..f3e4de3482d14 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -218,6 +218,7 @@ mod jsx_a11y { pub mod anchor_is_valid; pub mod heading_has_content; pub mod html_has_lang; + pub mod iframe_has_title; pub mod img_redundant_alt; } @@ -412,5 +413,6 @@ oxc_macros::declare_all_lint_rules! { jsx_a11y::anchor_is_valid, jsx_a11y::html_has_lang, jsx_a11y::heading_has_content, - jsx_a11y::img_redundant_alt + jsx_a11y::iframe_has_title, + jsx_a11y::img_redundant_alt, } diff --git a/crates/oxc_linter/src/rules/jsx_a11y/iframe_has_title.rs b/crates/oxc_linter/src/rules/jsx_a11y/iframe_has_title.rs new file mode 100644 index 0000000000000..6aa6d025101ab --- /dev/null +++ b/crates/oxc_linter/src/rules/jsx_a11y/iframe_has_title.rs @@ -0,0 +1,156 @@ +use oxc_ast::{ + ast::{Expression, JSXAttributeValue, JSXElementName, JSXExpression, JSXExpressionContainer}, + AstKind, +}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{ + context::LintContext, + rule::Rule, + utils::{get_prop_value, has_jsx_prop_lowercase}, + AstNode, +}; + +#[derive(Debug, Error, Diagnostic)] +#[error( + "eslint-plugin-jsx-a11y(iframe-has-title): Missing `title` attribute for the `iframe` element." +)] +#[diagnostic(severity(warning), help("Provide title property for iframe element."))] +struct IframeHasTitleDiagnostic(#[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct IframeHasTitle; + +declare_oxc_lint!( + /// ### What it does + /// + /// Enforce iframe elements have a title attribute. + /// + /// ### Why is this necessary? + /// + /// Screen reader users rely on a iframe title to describe the contents of the iframe. + /// Navigating through iframe and iframe elements quickly becomes difficult and confusing for users of this technology if the markup does not contain a title attribute. + /// + /// ### What it checks + /// + /// This rule checks for title property on iframe element. + /// + /// ### Example + /// ```javascript + /// // Bad + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// // Good + /// + /// + /// ``` + IframeHasTitle, + correctness +); + +impl Rule for IframeHasTitle { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::JSXOpeningElement(jsx_el) = node.kind() else { + return; + }; + + let JSXElementName::Identifier(iden) = &jsx_el.name else { + return; + }; + + let name = iden.name.as_str(); + + if name != "iframe" { + return; + } + + let alt_prop = if let Some(prop) = has_jsx_prop_lowercase(jsx_el, "title") { + prop + } else { + ctx.diagnostic(IframeHasTitleDiagnostic(iden.span)); + return; + }; + + match get_prop_value(alt_prop) { + Some(JSXAttributeValue::StringLiteral(str)) => { + if !str.value.as_str().is_empty() { + return; + } + } + Some(JSXAttributeValue::ExpressionContainer(JSXExpressionContainer { + expression: JSXExpression::Expression(expr), + .. + })) => { + if expr.is_string_literal() { + if let Expression::StringLiteral(str) = expr { + if !str.value.as_str().is_empty() { + return; + } + } + if let Expression::TemplateLiteral(tmpl) = expr { + if !tmpl.quasis.is_empty() + & !tmpl.expressions.is_empty() + & tmpl.quasis.iter().any(|q| !q.value.raw.as_str().is_empty()) + { + return; + } + } + } + + if expr.is_identifier_reference() & !expr.is_undefined() { + return; + } + } + _ => {} + } + + ctx.diagnostic(IframeHasTitleDiagnostic(iden.span)); + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + // DEFAULT ELEMENT TESTS + (r"
;", None), + (r"", None), + (r"", None), + (r"