From ff434a5e99613113493010d4d51038c91e5bb97d Mon Sep 17 00:00:00 2001 From: mrkldshv Date: Tue, 13 Dec 2022 21:04:07 +0000 Subject: [PATCH 1/3] feat(rome_js_analyzer): rule `useHtmlLang` --- .../src/categories.rs | 1 + crates/rome_js_analyze/src/analyzers/a11y.rs | 3 +- .../src/analyzers/a11y/use_html_lang.rs | 147 +++++++++++++++ .../tests/specs/a11y/useHtmlLang.jsx | 17 ++ .../tests/specs/a11y/useHtmlLang.jsx.snap | 171 ++++++++++++++++++ .../src/configuration/linter/rules.rs | 13 +- editors/vscode/configuration_schema.json | 11 ++ npm/backend-jsonrpc/src/workspace.ts | 5 + npm/rome/configuration_schema.json | 11 ++ website/src/pages/lint/rules/index.mdx | 10 + website/src/pages/lint/rules/useHtmlLang.md | 104 +++++++++++ 11 files changed, 488 insertions(+), 5 deletions(-) create mode 100644 crates/rome_js_analyze/src/analyzers/a11y/use_html_lang.rs create mode 100644 crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx create mode 100644 crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx.snap create mode 100644 website/src/pages/lint/rules/useHtmlLang.md diff --git a/crates/rome_diagnostics_categories/src/categories.rs b/crates/rome_diagnostics_categories/src/categories.rs index 58384037098..eaa9214f51c 100644 --- a/crates/rome_diagnostics_categories/src/categories.rs +++ b/crates/rome_diagnostics_categories/src/categories.rs @@ -19,6 +19,7 @@ define_dategories! { "lint/a11y/useKeyWithClickEvents": "https://docs.rome.tools/lint/rules/useKeyWithClickEvents", "lint/a11y/useKeyWithMouseEvents": "https://docs.rome.tools/lint/rules/useKeyWithMouseEvents", "lint/a11y/useValidAnchor": "https://docs.rome.tools/lint/rules/useValidAnchor", + "lint/a11y/useHtmlLang": "https://docs.rome.tools/lint/rules/useHtmlLang", // complexity "lint/complexity/noExtraBooleanCast": "https://docs.rome.tools/lint/rules/noExtraBooleanCast", diff --git a/crates/rome_js_analyze/src/analyzers/a11y.rs b/crates/rome_js_analyze/src/analyzers/a11y.rs index 1b6fb9f4294..461556a1b22 100644 --- a/crates/rome_js_analyze/src/analyzers/a11y.rs +++ b/crates/rome_js_analyze/src/analyzers/a11y.rs @@ -5,7 +5,8 @@ mod no_auto_focus; mod no_blank_target; mod use_alt_text; mod use_anchor_content; +mod use_html_lang; mod use_key_with_click_events; mod use_key_with_mouse_events; mod use_valid_anchor; -declare_group! { pub (crate) A11y { name : "a11y" , rules : [self :: no_auto_focus :: NoAutoFocus , self :: no_blank_target :: NoBlankTarget , self :: use_alt_text :: UseAltText , self :: use_anchor_content :: UseAnchorContent , self :: use_key_with_click_events :: UseKeyWithClickEvents , self :: use_key_with_mouse_events :: UseKeyWithMouseEvents , self :: use_valid_anchor :: UseValidAnchor ,] } } +declare_group! { pub (crate) A11y { name : "a11y" , rules : [self :: no_auto_focus :: NoAutoFocus , self :: no_blank_target :: NoBlankTarget , self :: use_alt_text :: UseAltText , self :: use_anchor_content :: UseAnchorContent , self :: use_html_lang :: UseHtmlLang , self :: use_key_with_click_events :: UseKeyWithClickEvents , self :: use_key_with_mouse_events :: UseKeyWithMouseEvents , self :: use_valid_anchor :: UseValidAnchor ,] } } diff --git a/crates/rome_js_analyze/src/analyzers/a11y/use_html_lang.rs b/crates/rome_js_analyze/src/analyzers/a11y/use_html_lang.rs new file mode 100644 index 00000000000..473fc1bba0b --- /dev/null +++ b/crates/rome_js_analyze/src/analyzers/a11y/use_html_lang.rs @@ -0,0 +1,147 @@ +use rome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic}; +use rome_console::markup; +use rome_js_syntax::{ + AnyJsxAttribute, AnyJsxAttributeValue, JsxAttribute, JsxOpeningElement, TextRange, +}; +use rome_rowan::AstNode; + +declare_rule! { + /// Enforce that `html` element has `lang` attribute. + /// `html` element must have a valid `lang` attribute or correspond to a valid language code + /// in order to provide a language preference for multilingual screen reader users. + /// This allows users to choose a language other than the default. + /// ## Examples + /// + /// ### Invalid + /// + /// ```jsx,expect_diagnostic + /// + /// ``` + /// + /// ```jsx,expect_diagnostic + /// + /// ``` + /// + /// ```jsx,expect_diagnostic + /// + /// ``` + /// + /// ```jsx,expect_diagnostic + /// + /// ``` + /// + /// ### Valid + /// + /// ```jsx + /// + /// ``` + /// + /// ```jsx + /// + /// ``` + /// + /// ```jsx + /// + /// ``` + /// + /// ## Accessibility guidelines + /// + /// [WCAG 3.1.1](https://www.w3.org/WAI/WCAG21/Understanding/language-of-page) + pub(crate) UseHtmlLang { + version: "12.0.0", + name: "useHtmlLang", + recommended: true, + } +} + +impl Rule for UseHtmlLang { + type Query = Ast; + type State = TextRange; + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let element = ctx.query(); + let name = element.name().ok()?; + let name = name.as_jsx_name()?.value_token().ok()?; + let name_trimmed = name.text_trimmed(); + if name_trimmed == "html" { + if let Some(lang_attribute) = element.find_attribute_by_name("lang").ok()? { + if element.has_trailing_spread_prop(lang_attribute.clone()) + || is_valid_lang_attribute(lang_attribute).is_some() + { + return None; + } + return Some(element.syntax().text_trimmed_range()); + } + if !has_spread_prop(element) { + return Some(element.syntax().text_trimmed_range()); + } + } + None + } + + fn diagnostic(_ctx: &RuleContext, state: &Self::State) -> Option { + Some(RuleDiagnostic::new( + rule_category!(), + state, + markup! { + "Provide a ""lang"" attribute when using the ""html"" element." + } + ).note( + markup! { + "Setting a ""lang"" attribute on HTML document elements configures the language +used by screen readers when no user default is specified." + } + )) + } +} + +fn is_valid_lang_attribute(attr: JsxAttribute) -> Option { + if attr.is_value_undefined_or_null() { + return None; + } + + let attribute_value = attr.initializer()?.value().ok()?; + + if let AnyJsxAttributeValue::JsxExpressionAttributeValue(expression) = attribute_value { + let expression = expression.expression().ok()?; + + if let Some(identifier_expression) = expression.as_js_identifier_expression() { + if !identifier_expression.text().is_empty() { + return Some(true); + } + return None; + } + + let bool_expression = expression + .as_any_js_literal_expression()? + .as_js_boolean_literal_expression(); + if bool_expression.is_some() { + return None; + } + + let string_expression = expression + .as_any_js_literal_expression()? + .as_js_string_literal_expression()?; + let string_expression_text = string_expression.inner_string_text().ok()?; + if string_expression_text.is_empty() { + return None; + } + + return Some(true); + } + let string_text = attribute_value.as_jsx_string()?.inner_string_text().ok()?; + if string_text.is_empty() { + return None; + } + + Some(true) +} + +fn has_spread_prop(element: &JsxOpeningElement) -> bool { + element + .attributes() + .into_iter() + .any(|attribute| matches!(attribute, AnyJsxAttribute::JsxSpreadAttribute(_))) +} diff --git a/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx b/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx new file mode 100644 index 00000000000..67864c26313 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx @@ -0,0 +1,17 @@ +<> + {/* invalid */} + + + + + + + + + {/* valid */} + + + + + + \ No newline at end of file diff --git a/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx.snap b/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx.snap new file mode 100644 index 00000000000..0c43249aa58 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx.snap @@ -0,0 +1,171 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: useHtmlLang.jsx +--- +# Input +```js +<> + {/* invalid */} + + + + + + + + + {/* valid */} + + + + + + +``` + +# Diagnostics +``` +useHtmlLang.jsx:3:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Provide a lang attribute when using the html element. + + 1 │ <> + 2 │ {/* invalid */} + > 3 │ + │ ^^^^^^ + 4 │ + 5 │ + + i Setting a lang attribute on HTML document elements configures the language + used by screen readers when no user default is specified. + + +``` + +``` +useHtmlLang.jsx:4:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Provide a lang attribute when using the html element. + + 2 │ {/* invalid */} + 3 │ + > 4 │ + │ ^^^^^^^^^^^^^^ + 5 │ + 6 │ + + i Setting a lang attribute on HTML document elements configures the language + used by screen readers when no user default is specified. + + +``` + +``` +useHtmlLang.jsx:5:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Provide a lang attribute when using the html element. + + 3 │ + 4 │ + > 5 │ + │ ^^^^^^^^^^^^^^^^ + 6 │ + 7 │ + + i Setting a lang attribute on HTML document elements configures the language + used by screen readers when no user default is specified. + + +``` + +``` +useHtmlLang.jsx:6:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Provide a lang attribute when using the html element. + + 4 │ + 5 │ + > 6 │ + │ ^^^^^^^^^^^^^^^^^^ + 7 │ + 8 │ + + i Setting a lang attribute on HTML document elements configures the language + used by screen readers when no user default is specified. + + +``` + +``` +useHtmlLang.jsx:7:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Provide a lang attribute when using the html element. + + 5 │ + 6 │ + > 7 │ + │ ^^^^^^^^^^^^^^^^^^^ + 8 │ + 9 │ + + i Setting a lang attribute on HTML document elements configures the language + used by screen readers when no user default is specified. + + +``` + +``` +useHtmlLang.jsx:8:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Provide a lang attribute when using the html element. + + 6 │ + 7 │ + > 8 │ + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 9 │ + 10 │ + + i Setting a lang attribute on HTML document elements configures the language + used by screen readers when no user default is specified. + + +``` + +``` +useHtmlLang.jsx:9:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Provide a lang attribute when using the html element. + + 7 │ + 8 │ + > 9 │ + │ ^^^^^^^^^^^^^^^^^^ + 10 │ + 11 │ {/* valid */} + + i Setting a lang attribute on HTML document elements configures the language + used by screen readers when no user default is specified. + + +``` + +``` +useHtmlLang.jsx:10:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Provide a lang attribute when using the html element. + + 8 │ + 9 │ + > 10 │ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ + 11 │ {/* valid */} + 12 │ + + i Setting a lang attribute on HTML document elements configures the language + used by screen readers when no user default is specified. + + +``` + + diff --git a/crates/rome_service/src/configuration/linter/rules.rs b/crates/rome_service/src/configuration/linter/rules.rs index fb170c5716f..4b612d62712 100644 --- a/crates/rome_service/src/configuration/linter/rules.rs +++ b/crates/rome_service/src/configuration/linter/rules.rs @@ -377,6 +377,8 @@ struct A11ySchema { use_anchor_content: Option, #[doc = "Enforces the usage of the attribute type for the element button"] use_button_type: Option, + #[doc = "Enforce that html element has lang attribute. html element must have a valid lang attribute or correspond to a valid language code in order to provide a language preference for multilingual screen reader users. This allows users to choose a language other than the default."] + use_html_lang: Option, #[doc = "Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event."] use_key_with_click_events: Option, #[doc = "Enforce that onMouseOver/onMouseOut are accompanied by onFocus/onBlur for keyboard-only users. It is important to take into account users with physical disabilities who cannot use a mouse, who use assistive technology or screenreader."] @@ -386,29 +388,31 @@ struct A11ySchema { } impl A11y { const CATEGORY_NAME: &'static str = "a11y"; - pub(crate) const CATEGORY_RULES: [&'static str; 9] = [ + pub(crate) const CATEGORY_RULES: [&'static str; 10] = [ "noAutofocus", "noBlankTarget", "noPositiveTabindex", "useAltText", "useAnchorContent", "useButtonType", + "useHtmlLang", "useKeyWithClickEvents", "useKeyWithMouseEvents", "useValidAnchor", ]; - const RECOMMENDED_RULES: [&'static str; 9] = [ + const RECOMMENDED_RULES: [&'static str; 10] = [ "noAutofocus", "noBlankTarget", "noPositiveTabindex", "useAltText", "useAnchorContent", "useButtonType", + "useHtmlLang", "useKeyWithClickEvents", "useKeyWithMouseEvents", "useValidAnchor", ]; - const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 9] = [ + const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 10] = [ RuleFilter::Rule("a11y", Self::CATEGORY_RULES[0]), RuleFilter::Rule("a11y", Self::CATEGORY_RULES[1]), RuleFilter::Rule("a11y", Self::CATEGORY_RULES[2]), @@ -418,6 +422,7 @@ impl A11y { RuleFilter::Rule("a11y", Self::CATEGORY_RULES[6]), RuleFilter::Rule("a11y", Self::CATEGORY_RULES[7]), RuleFilter::Rule("a11y", Self::CATEGORY_RULES[8]), + RuleFilter::Rule("a11y", Self::CATEGORY_RULES[9]), ]; pub(crate) fn is_recommended(&self) -> bool { !matches!(self.recommended, Some(false)) } pub(crate) fn get_enabled_rules(&self) -> IndexSet { @@ -444,7 +449,7 @@ impl A11y { pub(crate) fn is_recommended_rule(rule_name: &str) -> bool { Self::RECOMMENDED_RULES.contains(&rule_name) } - pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 9] { + pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 10] { Self::RECOMMENDED_RULES_AS_FILTERS } } diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 58a56acdcee..76a3eb8cda5 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -135,6 +135,17 @@ } ] }, + "useHtmlLang": { + "description": "Enforce that html element has lang attribute. html element must have a valid lang attribute or correspond to a valid language code in order to provide a language preference for multilingual screen reader users. This allows users to choose a language other than the default.", + "anyOf": [ + { + "$ref": "#/definitions/RuleConfiguration" + }, + { + "type": "null" + } + ] + }, "useKeyWithClickEvents": { "description": "Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.", "anyOf": [ diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index 1ce17ad4530..df3af206b28 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -182,6 +182,10 @@ export interface A11y { * Enforces the usage of the attribute type for the element button */ useButtonType?: RuleConfiguration; + /** + * Enforce that html element has lang attribute. html element must have a valid lang attribute or correspond to a valid language code in order to provide a language preference for multilingual screen reader users. This allows users to choose a language other than the default. + */ + useHtmlLang?: RuleConfiguration; /** * Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event. */ @@ -673,6 +677,7 @@ export type Category = | "lint/a11y/useKeyWithClickEvents" | "lint/a11y/useKeyWithMouseEvents" | "lint/a11y/useValidAnchor" + | "lint/a11y/useHtmlLang" | "lint/complexity/noExtraBooleanCast" | "lint/complexity/noMultipleSpacesInRegularExpressionLiterals" | "lint/complexity/noUselessFragments" diff --git a/npm/rome/configuration_schema.json b/npm/rome/configuration_schema.json index 58a56acdcee..76a3eb8cda5 100644 --- a/npm/rome/configuration_schema.json +++ b/npm/rome/configuration_schema.json @@ -135,6 +135,17 @@ } ] }, + "useHtmlLang": { + "description": "Enforce that html element has lang attribute. html element must have a valid lang attribute or correspond to a valid language code in order to provide a language preference for multilingual screen reader users. This allows users to choose a language other than the default.", + "anyOf": [ + { + "$ref": "#/definitions/RuleConfiguration" + }, + { + "type": "null" + } + ] + }, "useKeyWithClickEvents": { "description": "Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.", "anyOf": [ diff --git a/website/src/pages/lint/rules/index.mdx b/website/src/pages/lint/rules/index.mdx index 288c64f41fa..c3e792226ac 100644 --- a/website/src/pages/lint/rules/index.mdx +++ b/website/src/pages/lint/rules/index.mdx @@ -57,6 +57,16 @@ Enforce that anchor elements have content and that the content is accessible to Enforces the usage of the attribute type for the element button
+

+ useHtmlLang + recommended +

+Enforce that html element has lang attribute. +html element must have a valid lang attribute or correspond to a valid language code +in order to provide a language preference for multilingual screen reader users. +This allows users to choose a language other than the default. +
+

useKeyWithClickEvents recommended diff --git a/website/src/pages/lint/rules/useHtmlLang.md b/website/src/pages/lint/rules/useHtmlLang.md new file mode 100644 index 00000000000..c3bbf1ede49 --- /dev/null +++ b/website/src/pages/lint/rules/useHtmlLang.md @@ -0,0 +1,104 @@ +--- +title: Lint Rule useHtmlLang +parent: lint/rules/index +--- + +# useHtmlLang (since v12.0.0) + +> This rule is recommended by Rome. + +Enforce that `html` element has `lang` attribute. +`html` element must have a valid `lang` attribute or correspond to a valid language code +in order to provide a language preference for multilingual screen reader users. +This allows users to choose a language other than the default. + +## Examples + +### Invalid + +```jsx + +``` + +
a11y/useHtmlLang.js:1:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Provide a lang attribute when using the html element.
+  
+  > 1 │ <html></html>
+   ^^^^^^
+    2 │ 
+  
+   Setting a lang attribute on HTML document elements configures the language
+    used by screen readers when no user default is specified.
+  
+
+ +```jsx + +``` + +
a11y/useHtmlLang.js:1:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Provide a lang attribute when using the html element.
+  
+  > 1 │ <html lang={null}></html>
+   ^^^^^^^^^^^^^^^^^^
+    2 │ 
+  
+   Setting a lang attribute on HTML document elements configures the language
+    used by screen readers when no user default is specified.
+  
+
+ +```jsx + +``` + +
a11y/useHtmlLang.js:1:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Provide a lang attribute when using the html element.
+  
+  > 1 │ <html lang={undefined}></html>
+   ^^^^^^^^^^^^^^^^^^^^^^^
+    2 │ 
+  
+   Setting a lang attribute on HTML document elements configures the language
+    used by screen readers when no user default is specified.
+  
+
+ +```jsx + +``` + +
a11y/useHtmlLang.js:1:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Provide a lang attribute when using the html element.
+  
+  > 1 │ <html lang={true}></html>
+   ^^^^^^^^^^^^^^^^^^
+    2 │ 
+  
+   Setting a lang attribute on HTML document elements configures the language
+    used by screen readers when no user default is specified.
+  
+
+ +### Valid + +```jsx + +``` + +```jsx + +``` + +```jsx + +``` + +## Accessibility guidelines + +[WCAG 3.1.1](https://www.w3.org/WAI/WCAG21/Understanding/language-of-page) + From 2ac059869c720d6fcb86bcb420ab402daf896b72 Mon Sep 17 00:00:00 2001 From: mrkldshv Date: Wed, 14 Dec 2022 14:41:13 +0000 Subject: [PATCH 2/3] feat(rome_js_analyzer): adjust `useHtmlLang` rule and update docs --- .../src/analyzers/a11y/use_html_lang.rs | 26 ++-- .../tests/specs/a11y/useHtmlLang.jsx | 2 + .../tests/specs/a11y/useHtmlLang.jsx.snap | 126 ++++++++++++------ .../src/configuration/linter/rules.rs | 2 +- editors/vscode/configuration_schema.json | 2 +- npm/backend-jsonrpc/src/workspace.ts | 2 +- npm/rome/configuration_schema.json | 2 +- website/src/pages/lint/rules/index.mdx | 2 - website/src/pages/lint/rules/useHtmlLang.md | 23 +++- 9 files changed, 125 insertions(+), 62 deletions(-) diff --git a/crates/rome_js_analyze/src/analyzers/a11y/use_html_lang.rs b/crates/rome_js_analyze/src/analyzers/a11y/use_html_lang.rs index 473fc1bba0b..c60a4191f5a 100644 --- a/crates/rome_js_analyze/src/analyzers/a11y/use_html_lang.rs +++ b/crates/rome_js_analyze/src/analyzers/a11y/use_html_lang.rs @@ -1,14 +1,12 @@ use rome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic}; use rome_console::markup; use rome_js_syntax::{ - AnyJsxAttribute, AnyJsxAttributeValue, JsxAttribute, JsxOpeningElement, TextRange, + jsx_ext::AnyJsxElement, AnyJsxAttribute, AnyJsxAttributeValue, JsxAttribute, TextRange, }; use rome_rowan::AstNode; declare_rule! { /// Enforce that `html` element has `lang` attribute. - /// `html` element must have a valid `lang` attribute or correspond to a valid language code - /// in order to provide a language preference for multilingual screen reader users. /// This allows users to choose a language other than the default. /// ## Examples /// @@ -19,6 +17,10 @@ declare_rule! { /// ``` /// /// ```jsx,expect_diagnostic + /// + /// ``` + /// + /// ```jsx,expect_diagnostic /// /// ``` /// @@ -44,6 +46,10 @@ declare_rule! { /// /// ``` /// + /// ```jsx + /// + /// ``` + /// /// ## Accessibility guidelines /// /// [WCAG 3.1.1](https://www.w3.org/WAI/WCAG21/Understanding/language-of-page) @@ -55,7 +61,7 @@ declare_rule! { } impl Rule for UseHtmlLang { - type Query = Ast; + type Query = Ast; type State = TextRange; type Signals = Option; type Options = (); @@ -66,7 +72,7 @@ impl Rule for UseHtmlLang { let name = name.as_jsx_name()?.value_token().ok()?; let name_trimmed = name.text_trimmed(); if name_trimmed == "html" { - if let Some(lang_attribute) = element.find_attribute_by_name("lang").ok()? { + if let Some(lang_attribute) = element.find_attribute_by_name("lang") { if element.has_trailing_spread_prop(lang_attribute.clone()) || is_valid_lang_attribute(lang_attribute).is_some() { @@ -97,7 +103,7 @@ used by screen readers when no user default is specified." } } -fn is_valid_lang_attribute(attr: JsxAttribute) -> Option { +fn is_valid_lang_attribute(attr: JsxAttribute) -> Option<()> { if attr.is_value_undefined_or_null() { return None; } @@ -109,7 +115,7 @@ fn is_valid_lang_attribute(attr: JsxAttribute) -> Option { if let Some(identifier_expression) = expression.as_js_identifier_expression() { if !identifier_expression.text().is_empty() { - return Some(true); + return Some(()); } return None; } @@ -129,17 +135,17 @@ fn is_valid_lang_attribute(attr: JsxAttribute) -> Option { return None; } - return Some(true); + return Some(()); } let string_text = attribute_value.as_jsx_string()?.inner_string_text().ok()?; if string_text.is_empty() { return None; } - Some(true) + Some(()) } -fn has_spread_prop(element: &JsxOpeningElement) -> bool { +fn has_spread_prop(element: &AnyJsxElement) -> bool { element .attributes() .into_iter() diff --git a/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx b/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx index 67864c26313..44a53eb0400 100644 --- a/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx +++ b/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx @@ -1,8 +1,10 @@ <> {/* invalid */} + + diff --git a/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx.snap b/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx.snap index 0c43249aa58..6819ee0690f 100644 --- a/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx.snap +++ b/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx.snap @@ -6,9 +6,11 @@ expression: useHtmlLang.jsx ```js <> {/* invalid */} + + @@ -31,10 +33,10 @@ useHtmlLang.jsx:3:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━ 1 │ <> 2 │ {/* invalid */} - > 3 │ - │ ^^^^^^ - 4 │ - 5 │ + > 3 │ + │ ^^^^^^^^ + 4 │ + 5 │ i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified. @@ -48,11 +50,11 @@ useHtmlLang.jsx:4:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━ ! Provide a lang attribute when using the html element. 2 │ {/* invalid */} - 3 │ - > 4 │ - │ ^^^^^^^^^^^^^^ - 5 │ - 6 │ + 3 │ + > 4 │ + │ ^^^^^^ + 5 │ + 6 │ i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified. @@ -65,12 +67,12 @@ useHtmlLang.jsx:5:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━ ! Provide a lang attribute when using the html element. - 3 │ - 4 │ - > 5 │ - │ ^^^^^^^^^^^^^^^^ - 6 │ - 7 │ + 3 │ + 4 │ + > 5 │ + │ ^^^^^^^^^^^^^^ + 6 │ + 7 │ i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified. @@ -83,12 +85,12 @@ useHtmlLang.jsx:6:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━ ! Provide a lang attribute when using the html element. - 4 │ - 5 │ - > 6 │ - │ ^^^^^^^^^^^^^^^^^^ - 7 │ - 8 │ + 4 │ + 5 │ + > 6 │ + │ ^^^^^^^^^^^^^^^^ + 7 │ + 8 │ i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified. @@ -101,12 +103,12 @@ useHtmlLang.jsx:7:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━ ! Provide a lang attribute when using the html element. - 5 │ - 6 │ - > 7 │ - │ ^^^^^^^^^^^^^^^^^^^ - 8 │ - 9 │ + 5 │ + 6 │ + > 7 │ + │ ^^^^^^^^^^^^^^^^ + 8 │ + 9 │ i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified. @@ -119,12 +121,12 @@ useHtmlLang.jsx:8:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━ ! Provide a lang attribute when using the html element. - 6 │ - 7 │ - > 8 │ - │ ^^^^^^^^^^^^^^^^^^^^^^^ - 9 │ - 10 │ + 6 │ + 7 │ + > 8 │ + │ ^^^^^^^^^^^^^^^^^^ + 9 │ + 10 │ i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified. @@ -137,12 +139,12 @@ useHtmlLang.jsx:9:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━ ! Provide a lang attribute when using the html element. - 7 │ - 8 │ - > 9 │ - │ ^^^^^^^^^^^^^^^^^^ - 10 │ - 11 │ {/* valid */} + 7 │ + 8 │ + > 9 │ + │ ^^^^^^^^^^^^^^^^^^^ + 10 │ + 11 │ i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified. @@ -155,12 +157,48 @@ useHtmlLang.jsx:10:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━ ! Provide a lang attribute when using the html element. - 8 │ - 9 │ - > 10 │ + 8 │ + 9 │ + > 10 │ + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 11 │ + 12 │ + + i Setting a lang attribute on HTML document elements configures the language + used by screen readers when no user default is specified. + + +``` + +``` +useHtmlLang.jsx:11:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Provide a lang attribute when using the html element. + + 9 │ + 10 │ + > 11 │ + │ ^^^^^^^^^^^^^^^^^^ + 12 │ + 13 │ {/* valid */} + + i Setting a lang attribute on HTML document elements configures the language + used by screen readers when no user default is specified. + + +``` + +``` +useHtmlLang.jsx:12:5 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Provide a lang attribute when using the html element. + + 10 │ + 11 │ + > 12 │ │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - 11 │ {/* valid */} - 12 │ + 13 │ {/* valid */} + 14 │ i Setting a lang attribute on HTML document elements configures the language used by screen readers when no user default is specified. diff --git a/crates/rome_service/src/configuration/linter/rules.rs b/crates/rome_service/src/configuration/linter/rules.rs index 4b612d62712..89342ff83cf 100644 --- a/crates/rome_service/src/configuration/linter/rules.rs +++ b/crates/rome_service/src/configuration/linter/rules.rs @@ -377,7 +377,7 @@ struct A11ySchema { use_anchor_content: Option, #[doc = "Enforces the usage of the attribute type for the element button"] use_button_type: Option, - #[doc = "Enforce that html element has lang attribute. html element must have a valid lang attribute or correspond to a valid language code in order to provide a language preference for multilingual screen reader users. This allows users to choose a language other than the default."] + #[doc = "Enforce that html element has lang attribute. This allows users to choose a language other than the default."] use_html_lang: Option, #[doc = "Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event."] use_key_with_click_events: Option, diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 76a3eb8cda5..a14dccafee9 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -136,7 +136,7 @@ ] }, "useHtmlLang": { - "description": "Enforce that html element has lang attribute. html element must have a valid lang attribute or correspond to a valid language code in order to provide a language preference for multilingual screen reader users. This allows users to choose a language other than the default.", + "description": "Enforce that html element has lang attribute. This allows users to choose a language other than the default.", "anyOf": [ { "$ref": "#/definitions/RuleConfiguration" diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index df3af206b28..7d9d616b2d8 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -183,7 +183,7 @@ export interface A11y { */ useButtonType?: RuleConfiguration; /** - * Enforce that html element has lang attribute. html element must have a valid lang attribute or correspond to a valid language code in order to provide a language preference for multilingual screen reader users. This allows users to choose a language other than the default. + * Enforce that html element has lang attribute. This allows users to choose a language other than the default. */ useHtmlLang?: RuleConfiguration; /** diff --git a/npm/rome/configuration_schema.json b/npm/rome/configuration_schema.json index 76a3eb8cda5..a14dccafee9 100644 --- a/npm/rome/configuration_schema.json +++ b/npm/rome/configuration_schema.json @@ -136,7 +136,7 @@ ] }, "useHtmlLang": { - "description": "Enforce that html element has lang attribute. html element must have a valid lang attribute or correspond to a valid language code in order to provide a language preference for multilingual screen reader users. This allows users to choose a language other than the default.", + "description": "Enforce that html element has lang attribute. This allows users to choose a language other than the default.", "anyOf": [ { "$ref": "#/definitions/RuleConfiguration" diff --git a/website/src/pages/lint/rules/index.mdx b/website/src/pages/lint/rules/index.mdx index c3e792226ac..4fc270884ea 100644 --- a/website/src/pages/lint/rules/index.mdx +++ b/website/src/pages/lint/rules/index.mdx @@ -62,8 +62,6 @@ Enforces the usage of the attribute type for the element butt recommended

Enforce that html element has lang attribute. -html element must have a valid lang attribute or correspond to a valid language code -in order to provide a language preference for multilingual screen reader users. This allows users to choose a language other than the default.
diff --git a/website/src/pages/lint/rules/useHtmlLang.md b/website/src/pages/lint/rules/useHtmlLang.md index c3bbf1ede49..4bc4d109564 100644 --- a/website/src/pages/lint/rules/useHtmlLang.md +++ b/website/src/pages/lint/rules/useHtmlLang.md @@ -8,8 +8,6 @@ parent: lint/rules/index > This rule is recommended by Rome. Enforce that `html` element has `lang` attribute. -`html` element must have a valid `lang` attribute or correspond to a valid language code -in order to provide a language preference for multilingual screen reader users. This allows users to choose a language other than the default. ## Examples @@ -33,6 +31,23 @@ This allows users to choose a language other than the default. +```jsx + +``` + +
a11y/useHtmlLang.js:1:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Provide a lang attribute when using the html element.
+  
+  > 1 │ <html lang={""}></html>
+   ^^^^^^^^^^^^^^^^
+    2 │ 
+  
+   Setting a lang attribute on HTML document elements configures the language
+    used by screen readers when no user default is specified.
+  
+
+ ```jsx ``` @@ -98,6 +113,10 @@ This allows users to choose a language other than the default. ``` +```jsx + +``` + ## Accessibility guidelines [WCAG 3.1.1](https://www.w3.org/WAI/WCAG21/Understanding/language-of-page) From 646cf7ef1b1abc648120cdf78590e4dd5bf70c9b Mon Sep 17 00:00:00 2001 From: mrkldshv Date: Sat, 17 Dec 2022 10:06:13 +0000 Subject: [PATCH 3/3] feat(rome_js_analyzer): handle `JsTemplateExpression` --- .../src/analyzers/a11y/use_html_lang.rs | 22 ++++++++++++------- .../tests/specs/a11y/useHtmlLang.jsx | 1 + .../tests/specs/a11y/useHtmlLang.jsx.snap | 1 + 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/crates/rome_js_analyze/src/analyzers/a11y/use_html_lang.rs b/crates/rome_js_analyze/src/analyzers/a11y/use_html_lang.rs index c60a4191f5a..23dedc72d3b 100644 --- a/crates/rome_js_analyze/src/analyzers/a11y/use_html_lang.rs +++ b/crates/rome_js_analyze/src/analyzers/a11y/use_html_lang.rs @@ -113,24 +113,30 @@ fn is_valid_lang_attribute(attr: JsxAttribute) -> Option<()> { if let AnyJsxAttributeValue::JsxExpressionAttributeValue(expression) = attribute_value { let expression = expression.expression().ok()?; - if let Some(identifier_expression) = expression.as_js_identifier_expression() { - if !identifier_expression.text().is_empty() { + if expression.as_js_identifier_expression().is_some() { + return Some(()); + } + + if let Some(template_expression) = expression.as_js_template_expression() { + let template_element = template_expression + .elements() + .into_iter() + .find(|element| element.as_js_template_chunk_element().is_some()); + + if template_element.is_some() { return Some(()); - } - return None; + }; } - let bool_expression = expression + expression .as_any_js_literal_expression()? .as_js_boolean_literal_expression(); - if bool_expression.is_some() { - return None; - } let string_expression = expression .as_any_js_literal_expression()? .as_js_string_literal_expression()?; let string_expression_text = string_expression.inner_string_text().ok()?; + if string_expression_text.is_empty() { return None; } diff --git a/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx b/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx index 44a53eb0400..dcf50664a2a 100644 --- a/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx +++ b/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx @@ -13,6 +13,7 @@ {/* valid */} + diff --git a/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx.snap b/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx.snap index 6819ee0690f..1b3c63b4503 100644 --- a/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx.snap +++ b/crates/rome_js_analyze/tests/specs/a11y/useHtmlLang.jsx.snap @@ -19,6 +19,7 @@ expression: useHtmlLang.jsx {/* valid */} +