Skip to content

Commit 4186b83

Browse files
authored
feat(lint/html): add useHtmlLang (#8262)
1 parent 4291ff3 commit 4186b83

File tree

14 files changed

+309
-18
lines changed

14 files changed

+309
-18
lines changed

.changeset/fifty-webs-sneeze.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
"@biomejs/biome": minor
3+
---
4+
5+
Added the `useHtmlLang` lint rule for HTML. The rule enforces that the `html` element has a `lang` attribute.
6+
7+
Invalid:
8+
9+
```html
10+
<html></html>
11+
<html lang></html>
12+
<html lang=""></html>
13+
```
14+
15+
Valid:
16+
17+
```html
18+
<html lang="en"></html>
19+
```

crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/full_support.snap

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,23 @@ check ━━━━━━━━━━━━━━━━━━━━━━━━
5555

5656
# Emitted Messages
5757

58+
```block
59+
file.astro:10:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
60+
61+
× Provide a lang attribute when using the html element.
62+
63+
8 │ ---
64+
9 │
65+
> 10 │ <html><head><title>Astro</title></head><body></body></html>
66+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
67+
11 │
68+
12 │ <style>
69+
70+
i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified.
71+
72+
73+
```
74+
5875
```block
5976
file.astro:13:20 lint/a11y/useGenericFontNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
6077
@@ -80,5 +97,5 @@ file.astro:13:20 lint/a11y/useGenericFontNames ━━━━━━━━━━━
8097
8198
```block
8299
Checked 1 file in <TIME>. Fixed 1 file.
83-
Found 1 error.
100+
Found 2 errors.
84101
```

crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/full_support.snap

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,23 @@ check ━━━━━━━━━━━━━━━━━━━━━━━━
5656

5757
# Emitted Messages
5858

59+
```block
60+
file.svelte:9:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
61+
62+
× Provide a lang attribute when using the html element.
63+
64+
7 │ </script>
65+
8 │
66+
> 9 │ <html><head><title>Svelte</title></head><body></body></html>
67+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
68+
10 │
69+
11 │ <style>
70+
71+
i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified.
72+
73+
74+
```
75+
5976
```block
6077
file.svelte:12:20 lint/a11y/useGenericFontNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
6178
@@ -81,5 +98,5 @@ file.svelte:12:20 lint/a11y/useGenericFontNames ━━━━━━━━━━
8198
8299
```block
83100
Checked 1 file in <TIME>. Fixed 1 file.
84-
Found 1 error.
101+
Found 2 errors.
85102
```

crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/full_support_ts.snap

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,23 @@ check ━━━━━━━━━━━━━━━━━━━━━━━━
6161

6262
# Emitted Messages
6363

64+
```block
65+
file.svelte:14:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
66+
67+
× Provide a lang attribute when using the html element.
68+
69+
12 │ </script>
70+
13 │
71+
> 14 │ <html><head><title>Svelte</title></head><body></body></html>
72+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
73+
15 │
74+
16 │ <style>
75+
76+
i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified.
77+
78+
79+
```
80+
6481
```block
6582
file.svelte:17:20 lint/a11y/useGenericFontNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
6683
@@ -86,5 +103,5 @@ file.svelte:17:20 lint/a11y/useGenericFontNames ━━━━━━━━━━
86103
87104
```block
88105
Checked 1 file in <TIME>. Fixed 1 file.
89-
Found 1 error.
106+
Found 2 errors.
90107
```

crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/full_support.snap

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,23 @@ check ━━━━━━━━━━━━━━━━━━━━━━━━
5656

5757
# Emitted Messages
5858

59+
```block
60+
file.vue:9:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
61+
62+
× Provide a lang attribute when using the html element.
63+
64+
7 │ </script>
65+
8 │
66+
> 9 │ <html><head><title>Svelte</title></head><body></body></html>
67+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
68+
10 │
69+
11 │ <style>
70+
71+
i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified.
72+
73+
74+
```
75+
5976
```block
6077
file.vue:12:20 lint/a11y/useGenericFontNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
6178
@@ -81,5 +98,5 @@ file.vue:12:20 lint/a11y/useGenericFontNames ━━━━━━━━━━━
8198
8299
```block
83100
Checked 1 file in <TIME>. Fixed 1 file.
84-
Found 1 error.
101+
Found 2 errors.
85102
```

crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/full_support_ts.snap

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,23 @@ check ━━━━━━━━━━━━━━━━━━━━━━━━
6161

6262
# Emitted Messages
6363

64+
```block
65+
file.vue:14:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
66+
67+
× Provide a lang attribute when using the html element.
68+
69+
12 │ </script>
70+
13 │
71+
> 14 │ <html><head><title>Svelte</title></head><body></body></html>
72+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
73+
15 │
74+
16 │ <style>
75+
76+
i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified.
77+
78+
79+
```
80+
6481
```block
6582
file.vue:17:20 lint/a11y/useGenericFontNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
6683
@@ -86,5 +103,5 @@ file.vue:17:20 lint/a11y/useGenericFontNames ━━━━━━━━━━━
86103
87104
```block
88105
Checked 1 file in <TIME>. Fixed 1 file.
89-
Found 1 error.
106+
Found 2 errors.
90107
```

crates/biome_html_analyze/src/lint/a11y.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ use biome_analyze::declare_lint_group;
66
pub mod no_access_key;
77
pub mod no_header_scope;
88
pub mod use_button_type;
9-
declare_lint_group! { pub A11y { name : "a11y" , rules : [self :: no_access_key :: NoAccessKey , self :: no_header_scope :: NoHeaderScope , self :: use_button_type :: UseButtonType ,] } }
9+
pub mod use_html_lang;
10+
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 ,] } }
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use biome_analyze::{
2+
Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
3+
};
4+
use biome_console::markup;
5+
use biome_diagnostics::Severity;
6+
use biome_html_syntax::AnyHtmlElement;
7+
use biome_rowan::{AstNode, TextRange};
8+
use biome_rule_options::use_html_lang::UseHtmlLangOptions;
9+
10+
declare_lint_rule! {
11+
/// Enforce that `html` element has `lang` attribute.
12+
///
13+
/// ## Examples
14+
///
15+
/// ### Invalid
16+
///
17+
/// ```html,expect_diagnostic
18+
/// <html></html>
19+
/// ```
20+
///
21+
/// ```html,expect_diagnostic
22+
/// <html lang=""></html>
23+
/// ```
24+
///
25+
/// ### Valid
26+
///
27+
/// ```html
28+
/// <html lang="en"></html>
29+
/// ```
30+
///
31+
/// ## Accessibility guidelines
32+
///
33+
/// - [WCAG 3.1.1](https://www.w3.org/WAI/WCAG21/Understanding/language-of-page)
34+
///
35+
pub UseHtmlLang {
36+
version: "next",
37+
name: "useHtmlLang",
38+
language: "html",
39+
sources: &[RuleSource::EslintJsxA11y("html-has-lang").same()],
40+
recommended: true,
41+
severity: Severity::Error,
42+
}
43+
}
44+
45+
impl Rule for UseHtmlLang {
46+
type Query = Ast<AnyHtmlElement>;
47+
type State = TextRange;
48+
type Signals = Option<Self::State>;
49+
type Options = UseHtmlLangOptions;
50+
51+
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
52+
let element = ctx.query();
53+
54+
if !is_html_element(element) {
55+
return None;
56+
}
57+
58+
if let Some(lang_attribute) = element.find_attribute_by_name("lang")
59+
&& let Some(initializer) = lang_attribute.initializer()
60+
&& let Ok(value) = initializer.value()
61+
&& let Some(value) = value.string_value()
62+
&& !value.trim_ascii().is_empty()
63+
{
64+
return None;
65+
}
66+
67+
Some(element.syntax().text_trimmed_range())
68+
}
69+
70+
fn diagnostic(_ctx: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> {
71+
Some(RuleDiagnostic::new(
72+
rule_category!(),
73+
state,
74+
markup! {
75+
"Provide a "<Emphasis>"lang"</Emphasis>" attribute when using the "<Emphasis>"html"</Emphasis>" element."
76+
}
77+
).note(
78+
markup! {
79+
"Setting a "<Emphasis>"lang"</Emphasis>" attribute on HTML document elements configures the language "
80+
"used by screen readers when no user default is specified."
81+
}
82+
))
83+
}
84+
}
85+
86+
fn is_html_element(element: &AnyHtmlElement) -> bool {
87+
element
88+
.name()
89+
.is_some_and(|token_text| token_text.eq_ignore_ascii_case("html"))
90+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<!-- should generate diagnostics -->
2+
<html />
3+
<html></html>
4+
<html lang></html>
5+
<html lang=""></html>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
source: crates/biome_html_analyze/tests/spec_tests.rs
3+
expression: invalid.html
4+
---
5+
# Input
6+
```html
7+
<!-- should generate diagnostics -->
8+
<html />
9+
<html></html>
10+
<html lang></html>
11+
<html lang=""></html>
12+
13+
```
14+
15+
# Diagnostics
16+
```
17+
invalid.html:2:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
18+
19+
× Provide a lang attribute when using the html element.
20+
21+
1 │ <!-- should generate diagnostics -->
22+
> 2 │ <html />
23+
│ ^^^^^^^^
24+
3 │ <html></html>
25+
4 │ <html lang></html>
26+
27+
i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified.
28+
29+
30+
```
31+
32+
```
33+
invalid.html:3:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
34+
35+
× Provide a lang attribute when using the html element.
36+
37+
1 │ <!-- should generate diagnostics -->
38+
2 │ <html />
39+
> 3 │ <html></html>
40+
│ ^^^^^^^^^^^^^
41+
4 │ <html lang></html>
42+
5 │ <html lang=""></html>
43+
44+
i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified.
45+
46+
47+
```
48+
49+
```
50+
invalid.html:4:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
51+
52+
× Provide a lang attribute when using the html element.
53+
54+
2 │ <html />
55+
3 │ <html></html>
56+
> 4 │ <html lang></html>
57+
│ ^^^^^^^^^^^^^^^^^^
58+
5 │ <html lang=""></html>
59+
6 │
60+
61+
i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified.
62+
63+
64+
```
65+
66+
```
67+
invalid.html:5:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
68+
69+
× Provide a lang attribute when using the html element.
70+
71+
3 │ <html></html>
72+
4 │ <html lang></html>
73+
> 5 │ <html lang=""></html>
74+
│ ^^^^^^^^^^^^^^^^^^^^^
75+
6 │
76+
77+
i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified.
78+
79+
80+
```

0 commit comments

Comments
 (0)