Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changeset/fifty-webs-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
"@biomejs/biome": minor
---

Added the `useHtmlLang` lint rule for HTML. The rule enforces that the `html` element has a `lang` attribute.

Invalid:

```html
<html></html>
<html lang></html>
<html lang=""></html>
```

Valid:

```html
<html lang="en"></html>
```
3 changes: 2 additions & 1 deletion crates/biome_html_analyze/src/lint/a11y.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ use biome_analyze::declare_lint_group;
pub mod no_access_key;
pub mod no_header_scope;
pub mod use_button_type;
declare_lint_group! { pub A11y { name : "a11y" , rules : [self :: no_access_key :: NoAccessKey , self :: no_header_scope :: NoHeaderScope , self :: use_button_type :: UseButtonType ,] } }
pub mod use_html_lang;
declare_lint_group! { pub A11y { name : "a11y" , rules : [self :: no_access_key :: NoAccessKey , self :: no_header_scope :: NoHeaderScope , self :: use_button_type :: UseButtonType , self :: use_html_lang :: UseHtmlLang ,] } }
90 changes: 90 additions & 0 deletions crates/biome_html_analyze/src/lint/a11y/use_html_lang.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use biome_analyze::{
Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
};
use biome_console::markup;
use biome_diagnostics::Severity;
use biome_html_syntax::AnyHtmlElement;
use biome_rowan::{AstNode, TextRange};
use biome_rule_options::use_html_lang::UseHtmlLangOptions;

declare_lint_rule! {
/// Enforce that `html` element has `lang` attribute.
///
/// ## Examples
///
/// ### Invalid
///
/// ```html,expect_diagnostic
/// <html></html>
/// ```
///
/// ```html,expect_diagnostic
/// <html lang=""></html>
/// ```
///
/// ### Valid
///
/// ```html
/// <html lang="en"></html>
/// ```
///
/// ## Accessibility guidelines
///
/// - [WCAG 3.1.1](https://www.w3.org/WAI/WCAG21/Understanding/language-of-page)
///
pub UseHtmlLang {
version: "next",
name: "useHtmlLang",
language: "html",
sources: &[RuleSource::EslintJsxA11y("html-has-lang").same()],
recommended: true,
severity: Severity::Error,
}
}

impl Rule for UseHtmlLang {
type Query = Ast<AnyHtmlElement>;
type State = TextRange;
type Signals = Option<Self::State>;
type Options = UseHtmlLangOptions;

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let element = ctx.query();

if !is_html_element(element) {
return None;
}

if let Some(lang_attribute) = element.find_attribute_by_name("lang")
&& let Some(initializer) = lang_attribute.initializer()
&& let Ok(value) = initializer.value()
&& let Some(value) = value.string_value()
&& !value.trim_ascii().is_empty()
{
return None;
}

Some(element.syntax().text_trimmed_range())
}

fn diagnostic(_ctx: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> {
Some(RuleDiagnostic::new(
rule_category!(),
state,
markup! {
"Provide a "<Emphasis>"lang"</Emphasis>" attribute when using the "<Emphasis>"html"</Emphasis>" element."
}
).note(
markup! {
"Setting a "<Emphasis>"lang"</Emphasis>" attribute on HTML document elements configures the language"
"used by screen readers when no user default is specified."
}
))
}
}

fn is_html_element(element: &AnyHtmlElement) -> bool {
element
.name()
.is_some_and(|token_text| token_text.eq_ignore_ascii_case("html"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!-- should generate diagnostics -->
<html />
<html></html>
<html lang></html>
<html lang=""></html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
source: crates/biome_html_analyze/tests/spec_tests.rs
expression: invalid.html
---
# Input
```html
<!-- should generate diagnostics -->
<html />
<html></html>
<html lang></html>
<html lang=""></html>

```

# Diagnostics
```
invalid.html:2:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

× Provide a lang attribute when using the html element.

1 │ <!-- should generate diagnostics -->
> 2 │ <html />
│ ^^^^^^^^
3 │ <html></html>
4 │ <html lang></html>

i Setting a lang attribute on HTML document elements configures the languageused by screen readers when no user default is specified.


```

```
invalid.html:3:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

× Provide a lang attribute when using the html element.

1 │ <!-- should generate diagnostics -->
2 │ <html />
> 3 │ <html></html>
│ ^^^^^^^^^^^^^
4 │ <html lang></html>
5 │ <html lang=""></html>

i Setting a lang attribute on HTML document elements configures the languageused by screen readers when no user default is specified.


```

```
invalid.html:4:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

× Provide a lang attribute when using the html element.

2 │ <html />
3 │ <html></html>
> 4 │ <html lang></html>
│ ^^^^^^^^^^^^^^^^^^
5 │ <html lang=""></html>
6 │

i Setting a lang attribute on HTML document elements configures the languageused by screen readers when no user default is specified.


```

```
invalid.html:5:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

× Provide a lang attribute when using the html element.

3 │ <html></html>
4 │ <html lang></html>
> 5 │ <html lang=""></html>
│ ^^^^^^^^^^^^^^^^^^^^^
6 │

i Setting a lang attribute on HTML document elements configures the languageused by screen readers when no user default is specified.


```
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<!-- should not generate diagnostics -->
<html lang="en"></html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
source: crates/biome_html_analyze/tests/spec_tests.rs
expression: valid.html
---
# Input
```html
<!-- should not generate diagnostics -->
<html lang="en"></html>

```
Loading