mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter): html-has-lang for eslint-plugin-jsx-a11y (#1436)
It's my first time trying to implement the rule of linter. **html-has-lang** for #1141
This commit is contained in:
parent
27dc1ed1b2
commit
822ce76402
3 changed files with 158 additions and 0 deletions
|
|
@ -197,6 +197,7 @@ mod unicorn {
|
|||
mod jsx_a11y {
|
||||
pub mod alt_text;
|
||||
pub mod anchor_has_content;
|
||||
pub mod html_has_lang;
|
||||
}
|
||||
|
||||
oxc_macros::declare_all_lint_rules! {
|
||||
|
|
@ -369,4 +370,5 @@ oxc_macros::declare_all_lint_rules! {
|
|||
import::no_amd,
|
||||
jsx_a11y::alt_text,
|
||||
jsx_a11y::anchor_has_content,
|
||||
jsx_a11y::html_has_lang
|
||||
}
|
||||
|
|
|
|||
123
crates/oxc_linter/src/rules/jsx_a11y/html_has_lang.rs
Normal file
123
crates/oxc_linter/src/rules/jsx_a11y/html_has_lang.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
use oxc_ast::{
|
||||
ast::{
|
||||
JSXAttributeItem, 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::has_jsx_prop_lowercase, AstNode};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct HtmlHasLang;
|
||||
|
||||
declare_oxc_lint!(
|
||||
/// ### What it does
|
||||
///
|
||||
/// Ensures that every HTML document has a lang attribute
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// If the language of a webpage is not specified,
|
||||
/// the screen reader assumes the default language set by the user.
|
||||
/// Language settings become an issue for users who speak multiple languages
|
||||
/// and access website in more than one language.
|
||||
///
|
||||
///
|
||||
/// ### Example
|
||||
/// ```javascript
|
||||
/// // Bad
|
||||
/// <html />
|
||||
///
|
||||
/// // Good
|
||||
/// <html lang="en" />
|
||||
/// ```
|
||||
HtmlHasLang,
|
||||
correctness
|
||||
);
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
enum HtmlHasLangDiagnostic {
|
||||
#[error("eslint-plugin-jsx-a11y(html-has-lang): Missing lang attribute.")]
|
||||
#[diagnostic(severity(warning), help("Add a lang attribute to the html element whose value represents the primary language of document."))]
|
||||
MissingLangProp(#[label] Span),
|
||||
|
||||
#[error("eslint-plugin-jsx-a11y(html-has-lang): Missing value for lang attribute")]
|
||||
#[diagnostic(severity(warning), help("Must have meaningful value for `lang` prop."))]
|
||||
MissingLangValue(#[label] Span),
|
||||
}
|
||||
|
||||
fn get_prop_value<'a, 'b>(item: &'b JSXAttributeItem<'a>) -> Option<&'b JSXAttributeValue<'a>> {
|
||||
if let JSXAttributeItem::Attribute(attr) = item {
|
||||
attr.0.value.as_ref()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_lang_prop(item: &JSXAttributeItem) -> bool {
|
||||
match get_prop_value(item) {
|
||||
Some(JSXAttributeValue::ExpressionContainer(JSXExpressionContainer {
|
||||
expression: JSXExpression::Expression(expr),
|
||||
..
|
||||
})) => !expr.is_undefined(),
|
||||
Some(JSXAttributeValue::StringLiteral(str)) => !str.value.as_str().is_empty(),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
impl Rule for HtmlHasLang {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
let AstKind::JSXOpeningElement(jsx_el) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
let JSXElementName::Identifier(identifier) = &jsx_el.name else {
|
||||
return;
|
||||
};
|
||||
|
||||
let name = identifier.name.as_str();
|
||||
if name != "html" {
|
||||
return;
|
||||
}
|
||||
|
||||
has_jsx_prop_lowercase(jsx_el, "lang").map_or_else(
|
||||
|| ctx.diagnostic(HtmlHasLangDiagnostic::MissingLangProp(identifier.span)),
|
||||
|lang_prop| {
|
||||
if !is_valid_lang_prop(lang_prop) {
|
||||
ctx.diagnostic(HtmlHasLangDiagnostic::MissingLangValue(jsx_el.span));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
use crate::tester::Tester;
|
||||
|
||||
let pass = vec![
|
||||
(r"<div />;", None),
|
||||
(r#"<html lang="en" />"#, None),
|
||||
(r#"<html lang="en-US" />"#, None),
|
||||
(r"<html lang={foo} />;", None),
|
||||
(r"<html lang />;", None),
|
||||
(r"<HTML />;", None),
|
||||
// TODO: When polymorphic components are supported
|
||||
// (r#"<HTMLTop lang="en" />"#, None),
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
(r"<html />;", None),
|
||||
(r"<html {...props} />;", None),
|
||||
(r"<html lang={undefined} />;", None),
|
||||
(r#"<html lang="" />;"#, None),
|
||||
// TODO: When polymorphic components are supported
|
||||
// (r"<HTMLTop />;", None),
|
||||
];
|
||||
|
||||
Tester::new(HtmlHasLang::NAME, pass, fail).with_jsx_a11y_plugin(true).test_and_snapshot();
|
||||
}
|
||||
33
crates/oxc_linter/src/snapshots/html_has_lang.snap
Normal file
33
crates/oxc_linter/src/snapshots/html_has_lang.snap
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
source: crates/oxc_linter/src/tester.rs
|
||||
expression: html_has_lang
|
||||
---
|
||||
⚠ eslint-plugin-jsx-a11y(html-has-lang): Missing lang attribute.
|
||||
╭─[html_has_lang.tsx:1:1]
|
||||
1 │ <html />;
|
||||
· ────
|
||||
╰────
|
||||
help: Add a lang attribute to the html element whose value represents the primary language of document.
|
||||
|
||||
⚠ eslint-plugin-jsx-a11y(html-has-lang): Missing lang attribute.
|
||||
╭─[html_has_lang.tsx:1:1]
|
||||
1 │ <html {...props} />;
|
||||
· ────
|
||||
╰────
|
||||
help: Add a lang attribute to the html element whose value represents the primary language of document.
|
||||
|
||||
⚠ eslint-plugin-jsx-a11y(html-has-lang): Missing value for lang attribute
|
||||
╭─[html_has_lang.tsx:1:1]
|
||||
1 │ <html lang={undefined} />;
|
||||
· ─────────────────────────
|
||||
╰────
|
||||
help: Must have meaningful value for `lang` prop.
|
||||
|
||||
⚠ eslint-plugin-jsx-a11y(html-has-lang): Missing value for lang attribute
|
||||
╭─[html_has_lang.tsx:1:1]
|
||||
1 │ <html lang="" />;
|
||||
· ────────────────
|
||||
╰────
|
||||
help: Must have meaningful value for `lang` prop.
|
||||
|
||||
|
||||
Loading…
Reference in a new issue