From e7ce633d8c8dc147dc4d70a6db7f296a4218897a Mon Sep 17 00:00:00 2001 From: Yuto Yoshino Date: Tue, 20 Feb 2024 21:17:10 +0900 Subject: [PATCH] linter: implement from_configuration for jsx-a11y anchor-is-valid (#2425) - fix: #2361 Created the `from_configuration` function to recognize links written during testing as valid. This function takes a JSON configuration and generates an `AnchorIsValidConfig` object, which determines whether specific hrefs are valid. This change allows us to easily specify valid hrefs in our tests, ensuring that our link validation logic works as expected. --- .../src/rules/jsx_a11y/anchor_is_valid.rs | 114 +++++++++++++----- 1 file changed, 85 insertions(+), 29 deletions(-) diff --git a/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs b/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs index 2699a52b4..603c5ecad 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs @@ -34,7 +34,12 @@ enum AnchorIsValidDiagnostic { } #[derive(Debug, Default, Clone)] -pub struct AnchorIsValid; +pub struct AnchorIsValid(Box); + +#[derive(Debug, Default, Clone)] +struct AnchorIsValidConfig { + valid_hrefs: Vec, +} declare_oxc_lint!( /// ### What it does @@ -113,7 +118,13 @@ declare_oxc_lint!( ); impl Rule for AnchorIsValid { - // TODO: Implement from_configuration() and test it + fn from_configuration(value: serde_json::Value) -> Self { + let valid_hrefs = + value.get("validHrefs").and_then(|v| v.as_array()).map_or_else(Vec::new, |array| { + array.iter().filter_map(|v| v.as_str().map(String::from)).collect::>() + }); + Self(Box::new(AnchorIsValidConfig { valid_hrefs })) + } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::JSXElement(jsx_el) = node.kind() { @@ -127,7 +138,7 @@ impl Rule for AnchorIsValid { match herf_attr { JSXAttributeItem::Attribute(attr) => match &attr.value { Some(value) => { - let is_empty = check_value_is_empty(value); + let is_empty = check_value_is_empty(value, &self.0.valid_hrefs); if is_empty { if has_jsx_prop_lowercase(&jsx_el.opening_element, "onclick") .is_some() @@ -148,7 +159,6 @@ impl Rule for AnchorIsValid { return; } }, - JSXAttributeItem::SpreadAttribute(_) => { // pass return; @@ -162,24 +172,24 @@ impl Rule for AnchorIsValid { JSXAttributeItem::SpreadAttribute(_) => true, JSXAttributeItem::Attribute(_) => false, }); - if has_spreed_attr { return; } - ctx.diagnostic(AnchorIsValidDiagnostic::MissingHrefAttribute(ident.span)); } } } } -fn check_value_is_empty(value: &JSXAttributeValue) -> bool { +fn check_value_is_empty(value: &JSXAttributeValue, valid_hrefs: &[String]) -> bool { match value { JSXAttributeValue::Element(_) => false, JSXAttributeValue::StringLiteral(str_lit) => { - str_lit.value.is_empty() - || str_lit.value == "#" - || str_lit.value == "javascript:void(0)" + let href_value = str_lit.value.to_string(); // Assuming Atom implements ToString + href_value.is_empty() + || href_value == "#" + || href_value == "javascript:void(0)" + || !valid_hrefs.contains(&href_value) } JSXAttributeValue::ExpressionContainer(exp) => { if let JSXExpression::Expression(jsexp) = &exp.expression { @@ -190,9 +200,11 @@ fn check_value_is_empty(value: &JSXAttributeValue) -> bool { } else if let Expression::NullLiteral(_) = jsexp { return true; } else if let Expression::StringLiteral(str_lit) = jsexp { - return str_lit.value.is_empty() - || str_lit.value == "#" - || str_lit.value == "javascript:void(0)"; + let href_value = str_lit.value.to_string(); // Assuming Atom implements ToString + return href_value.is_empty() + || href_value == "#" + || href_value == "javascript:void(0)" + || !valid_hrefs.contains(&href_value); } }; false @@ -221,20 +233,44 @@ fn test() { let pass = vec![ (r"", None, None), (r"", None, None), - (r"", None, None), + (r"", Some(serde_json::json!({ "validHrefs": ["foo"] })), None), (r"", None, None), - (r"", None, None), - (r"", None, None), + (r"", Some(serde_json::json!({ "validHrefs": ["/foo"] })), None), + ( + r"", + Some(serde_json::json!({ "validHrefs": ["https://foo.bar.com"] })), + None, + ), (r"
", None, None), - (r"", None, None), - (r"", None, None), + ( + r"", + Some(serde_json::json!({ "validHrefs": ["javascript"] })), + None, + ), + ( + r"", + Some(serde_json::json!({ "validHrefs": ["javascriptFoo"] })), + None, + ), (r"", None, None), - (r"", None, None), - (r"", None, None), + (r"", Some(serde_json::json!({ "validHrefs": ["foo"] })), None), + ( + r"", + Some(serde_json::json!({ "validHrefs": ["javascript"] })), + None, + ), (r"", None, None), - (r"", None, None), - (r"", None, None), - (r"", None, None), + (r"", Some(serde_json::json!({ "validHrefs": ["#foo"] })), None), + ( + r"", + Some(serde_json::json!({ "validHrefs": ["#javascript"] })), + None, + ), + ( + r"", + Some(serde_json::json!({ "validHrefs": ["#javascriptFoo"] })), + None, + ), (r"test", None, None), (r"", None, None), // (r#""#, Some(serde_json::json!(components))), @@ -257,7 +293,7 @@ fn test() { // (r#""#, Some(serde_json::json!(components))), ( r"", - None, + Some(serde_json::json!({ "validHrefs": ["#foo"] })), Some( serde_json::json!({ "jsx-a11y": { "components": { "Anchor": "a", "Link": "a" } } }), ), @@ -298,14 +334,34 @@ fn test() { // (r#""#, Some(serde_json::json!(componentsAndSpecialLink))), // (r#"test"#, Some(serde_json::json!(componentsAndSpecialLink))), (r" void 0} />", None, None), - (r" void 0} />", None, None), + ( + r" void 0} />", + Some(serde_json::json!({ "validHrefs": ["foo"] })), + None, + ), (r" void 0} />", None, None), - (r" void 0} />", None, None), - (r" void 0} />", None, None), + ( + r" void 0} />", + Some(serde_json::json!({ "validHrefs": ["/foo"] })), + None, + ), + ( + r" void 0} />", + Some(serde_json::json!({ "validHrefs": ["https://foo.bar.com"] })), + None, + ), (r"