From 525edd24c95c05d19bb308a5164e64a55a928917 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Wed, 11 Sep 2024 22:32:10 +0200 Subject: [PATCH 1/3] feat(eslint/jsx_a11y): implement anchor_ambiguous_text --- crates/oxc_linter/src/rules.rs | 2 + .../rules/jsx_a11y/anchor_ambiguous_text.rs | 312 ++++++++++++++++++ .../src/snapshots/anchor_ambiguous_text.snap | 170 ++++++++++ 3 files changed, 484 insertions(+) create mode 100644 crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs create mode 100644 crates/oxc_linter/src/snapshots/anchor_ambiguous_text.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index d15aa0b82568e..59bf43ce6c1f4 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -379,6 +379,7 @@ mod unicorn { mod jsx_a11y { pub mod alt_text; + pub mod anchor_ambiguous_text; pub mod anchor_has_content; pub mod anchor_is_valid; pub mod aria_activedescendant_has_tabindex; @@ -741,6 +742,7 @@ oxc_macros::declare_all_lint_rules! { jsx_a11y::role_supports_aria_props, jsx_a11y::scope, jsx_a11y::tabindex_no_positive, + jsx_a11y::anchor_ambiguous_text, nextjs::google_font_display, nextjs::google_font_preconnect, nextjs::inline_script_id, diff --git a/crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs b/crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs new file mode 100644 index 0000000000000..adbf0d5888984 --- /dev/null +++ b/crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs @@ -0,0 +1,312 @@ +use cow_utils::CowUtils; +use std::borrow::Cow; + +use oxc_ast::{ + ast::{JSXChild, JSXElement}, + AstKind, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{CompactStr, Span}; + +use crate::{ + context::LintContext, + rule::Rule, + utils::{ + get_element_type, get_string_literal_prop_value, has_jsx_prop_ignore_case, + is_hidden_from_screen_reader, + }, + AstNode, +}; + +fn anchor_has_ambiguous_text(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Unexpected ambigious anchor link text.").with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct AnchorAmbiguousText(Box); + +#[derive(Debug, Clone)] +pub struct AnchorAmbiguousTextConfig { + words: Vec, +} + +impl Default for AnchorAmbiguousTextConfig { + fn default() -> Self { + Self { + words: vec![ + CompactStr::new("click here"), + CompactStr::new("here"), + CompactStr::new("link"), + CompactStr::new("a link"), + CompactStr::new("learn more"), + ], + } + } +} + +impl std::ops::Deref for AnchorAmbiguousText { + type Target = AnchorAmbiguousTextConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Inspects anchor link text for the use of ambigious words. + /// + /// This rule checks the text from the anchor element `aria-label` if available. + /// In absence of an anchor `aria-label` it combines the following text of it's children: + /// * `aria-label` if avaialble + /// * if the child is an image, the `alt` text + /// * the text content of the HTML element + /// + /// ### Why is this bad? + /// + /// Screenreaders users rely on link text for context, ambigious words such as "click here" do + /// not provide enough context. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```jsx + /// link + /// click here + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```jsx + /// read this tutorial + /// click here + /// ``` + AnchorAmbiguousText, + restriction, +); + +impl Rule for AnchorAmbiguousText { + fn from_configuration(value: serde_json::Value) -> Self { + let mut config = AnchorAmbiguousTextConfig::default(); + + if let Some(words_array) = + value.get(0).and_then(|v| v.get("words")).and_then(serde_json::Value::as_array) + { + config.words = words_array + .iter() + .filter_map(serde_json::Value::as_str) + .map(CompactStr::from) + .collect(); + } + + Self(Box::new(config)) + } + + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::JSXElement(jsx_el) = node.kind() else { + return; + }; + + let Some(name) = get_element_type(ctx, &jsx_el.opening_element) else { + return; + }; + + if name != "a" { + return; + } + + let Some(text) = get_accessible_text(jsx_el, ctx) else { + return; + }; + + if text.trim() == "" { + return; + } + + if self.words.contains(&normalize_str(text)) { + ctx.diagnostic(anchor_has_ambiguous_text(jsx_el.span)); + } + } +} + +// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/65c9338c62c558d3c1c2dbf5ecc55cf04dbfe80c/src/util/getAccessibleChildText.js#L15 +fn normalize_str(text: Cow<'_, str>) -> CompactStr { + let mut normalized_str = text.cow_to_lowercase().to_string(); + normalized_str.retain(|c| { + c != ',' + && c != '.' + && c != '?' + && c != '¿' + && c != '!' + && c != '‽' + && c != '¡' + && c != ';' + && c != ':' + }); + + if normalized_str.contains(char::is_whitespace) { + let parts: Vec = + normalized_str.split_whitespace().map(std::string::ToString::to_string).collect(); + return CompactStr::from(parts.join(" ")); + } + + CompactStr::from(normalized_str) +} + +// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/65c9338c62c558d3c1c2dbf5ecc55cf04dbfe80c/src/util/getAccessibleChildText.js#L31 +fn get_accessible_text<'a, 'b>( + jsx_el: &'b JSXElement<'a>, + ctx: &LintContext<'a>, +) -> Option> { + if let Some(aria_label) = has_jsx_prop_ignore_case(&jsx_el.opening_element, "aria-label") { + if let Some(label_text) = get_string_literal_prop_value(aria_label) { + return Some(Cow::Borrowed(label_text)); + }; + } + + if let Some(name) = get_element_type(ctx, &jsx_el.opening_element) { + if name == "img" { + if let Some(alt_text) = has_jsx_prop_ignore_case(&jsx_el.opening_element, "alt") { + if let Some(text) = get_string_literal_prop_value(alt_text) { + return Some(Cow::Borrowed(text)); + }; + }; + } + }; + + if is_hidden_from_screen_reader(ctx, &jsx_el.opening_element) { + return None; + } + + let text: Vec> = jsx_el + .children + .iter() + .filter_map(|child| match child { + JSXChild::Element(child_el) => get_accessible_text(child_el, ctx), + JSXChild::Text(text_el) => Some(Cow::Borrowed(text_el.value.as_str())), + _ => None, + }) + .collect(); + + Some(Cow::Owned(text.join(" "))) +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ( + r#"documentation;"#, + None, + Some( + serde_json::json!({ "settings": { "jsx-a11y": { "components": { "Image": "img" } } } }), + ), + ), + ("documentation;", None, None), + ("${here};", None, None), + (r#"click here;"#, None, None), + ( + r#"click here;"#, + None, + None, + ), + (r#"documentation;"#, None, None), + ( + "click here", + Some(serde_json::json!([{ "words": ["disabling the defaults"], }])), + None, + ), + ( + "documentation;", + None, + Some( + serde_json::json!({ "settings": { "jsx-a11y": { "components": { "Link": "a" } } } }), + ), + ), + ( + r#"documentation;"#, + None, + Some( + serde_json::json!({ "settings": { "jsx-a11y": { "components": { "Image": "img" } } } }), + ), + ), + ( + "${here};", + None, + Some( + serde_json::json!({ "settings": { "jsx-a11y": { "components": { "Link": "a" } } } }), + ), + ), + ( + r#"click here;"#, + None, + Some( + serde_json::json!({ "settings": { "jsx-a11y": { "components": { "Link": "a" } } } }), + ), + ), + ( + "click here", + Some( + serde_json::json!([{ "words": ["disabling the defaults with components"], }]), + ), + Some( + serde_json::json!({ "settings": { "jsx-a11y": { "components": { "Link": "a" } } } }), + ), + ), + ]; + + let fail = vec![ + ("here;", None, None), + ("HERE;", None, None), + ("click here;", None, None), + ("learn more;", None, None), + ("learn more;", None, None), + ("learn more.;", None, None), + ("learn more?;", None, None), + ("learn more,;", None, None), + ("learn more!;", None, None), + ("learn more;;", None, None), + ("learn more:;", None, None), + ("link;", None, None), + ("a link;", None, None), + (r#"something;"#, None, None), + (" a link ;", None, None), + ("a link;", None, None), + ("a link;", None, None), + ("click here;", None, None), + (" click here;", None, None), + ("more textlearn more;", None, None), + (r#"learn more;"#, None, None), + (r#"click here;"#, None, None), + (r#"click here;"#, None, None), + ( + r#"click here;"#, + None, + None, + ), + ("click here;", None, None), + ( + "here", + None, + Some( + serde_json::json!({ "settings": { "jsx-a11y": { "components": { "Link": "a" } } } }), + ), + ), + ( + r#"click here"#, + None, + Some( + serde_json::json!({ "settings": { "jsx-a11y": { "components": { "Image": "img" } } } }), + ), + ), + ( + "a disallowed word", + Some(serde_json::json!([{ "words": ["a disallowed word"], }])), + None, + ), + ]; + + Tester::new(AnchorAmbiguousText::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/anchor_ambiguous_text.snap b/crates/oxc_linter/src/snapshots/anchor_ambiguous_text.snap new file mode 100644 index 0000000000000..7c72f45f5146a --- /dev/null +++ b/crates/oxc_linter/src/snapshots/anchor_ambiguous_text.snap @@ -0,0 +1,170 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ here; + · ─────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ HERE; + · ─────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ click here; + · ───────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ learn more; + · ───────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ learn more; + · ────────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ learn more.; + · ────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ learn more?; + · ────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ learn more,; + · ────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ learn more!; + · ────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ learn more;; + · ────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ learn more:; + · ────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ link; + · ─────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ a link; + · ───────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ something; + · ──────────────────────────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ a link ; + · ─────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ a link; + · ──────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ a link; + · ──────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ click here; + · ────────────────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ click here; + · ──────────────────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ more textlearn more; + · ─────────────────────────────────────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ learn more; + · ────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ click here; + · ────────────────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ click here; + · ──────────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ click here; + · ───────────────────────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ click here; + · ──────────────────────────────────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ here + · ───────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ click here + · ───────────────────────────────── + ╰──── + + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ╭─[anchor_ambiguous_text.tsx:1:1] + 1 │ a disallowed word + · ──────────────────────── + ╰──── From 81deb92058a7c15e83700855e2435ac03284fd25 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Mon, 25 Nov 2024 09:24:54 +0000 Subject: [PATCH 2/3] minor cleanup --- .../src/rules/jsx_a11y/anchor_ambiguous_text.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs b/crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs index adbf0d5888984..ef2cd7c59fa55 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs @@ -1,4 +1,3 @@ -use cow_utils::CowUtils; use std::borrow::Cow; use oxc_ast::{ @@ -56,17 +55,17 @@ impl std::ops::Deref for AnchorAmbiguousText { declare_oxc_lint!( /// ### What it does /// - /// Inspects anchor link text for the use of ambigious words. + /// Inspects anchor link text for the use of ambiguous words. /// /// This rule checks the text from the anchor element `aria-label` if available. /// In absence of an anchor `aria-label` it combines the following text of it's children: - /// * `aria-label` if avaialble + /// * `aria-label` if available /// * if the child is an image, the `alt` text /// * the text content of the HTML element /// /// ### Why is this bad? /// - /// Screenreaders users rely on link text for context, ambigious words such as "click here" do + /// Screen readers users rely on link text for context, ambiguous words such as "click here" do /// not provide enough context. /// /// ### Examples @@ -124,15 +123,18 @@ impl Rule for AnchorAmbiguousText { return; } - if self.words.contains(&normalize_str(text)) { + if self.words.contains(&normalize_str(&text)) { ctx.diagnostic(anchor_has_ambiguous_text(jsx_el.span)); } } } // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/65c9338c62c558d3c1c2dbf5ecc55cf04dbfe80c/src/util/getAccessibleChildText.js#L15 -fn normalize_str(text: Cow<'_, str>) -> CompactStr { - let mut normalized_str = text.cow_to_lowercase().to_string(); +fn normalize_str(text: &str) -> CompactStr { + // `to_lowercase` is disallowed. however we need to remove certain chars later which requires converting to a String + // the overhead of going &str -> cow string -> string is greater than just using to_lowercase + #[allow(clippy::disallowed_methods)] + let mut normalized_str = text.to_lowercase(); normalized_str.retain(|c| { c != ',' && c != '.' From 7b3c919401b939cf0a4f41e2287597e070e18a9a Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Mon, 25 Nov 2024 10:30:15 +0000 Subject: [PATCH 3/3] typos --- .../rules/jsx_a11y/anchor_ambiguous_text.rs | 2 +- .../src/snapshots/anchor_ambiguous_text.snap | 56 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs b/crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs index ef2cd7c59fa55..941a2ecf0bbfe 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs @@ -19,7 +19,7 @@ use crate::{ }; fn anchor_has_ambiguous_text(span: Span) -> OxcDiagnostic { - OxcDiagnostic::warn("Unexpected ambigious anchor link text.").with_label(span) + OxcDiagnostic::warn("Unexpected ambagious anchor link text.").with_label(span) } #[derive(Debug, Default, Clone)] diff --git a/crates/oxc_linter/src/snapshots/anchor_ambiguous_text.snap b/crates/oxc_linter/src/snapshots/anchor_ambiguous_text.snap index 7c72f45f5146a..91bd6b1b715f4 100644 --- a/crates/oxc_linter/src/snapshots/anchor_ambiguous_text.snap +++ b/crates/oxc_linter/src/snapshots/anchor_ambiguous_text.snap @@ -1,169 +1,169 @@ --- source: crates/oxc_linter/src/tester.rs --- - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ here; · ─────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ HERE; · ─────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ click here; · ───────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ learn more; · ───────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ learn more; · ────────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ learn more.; · ────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ learn more?; · ────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ learn more,; · ────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ learn more!; · ────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ learn more;; · ────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ learn more:; · ────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ link; · ─────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ a link; · ───────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ something; · ──────────────────────────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ a link ; · ─────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ a link; · ──────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ a link; · ──────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ click here; · ────────────────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ click here; · ──────────────────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ more textlearn more; · ─────────────────────────────────────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ learn more; · ────────────────────────────────────────────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ click here; · ────────────────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ click here; · ──────────────────────────────────────────────────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ click here; · ───────────────────────────────────────────────────────────────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ click here; · ──────────────────────────────────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ here · ───────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ click here · ───────────────────────────────── ╰──── - ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambigious anchor link text. + ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] 1 │ a disallowed word · ────────────────────────