-
Notifications
You must be signed in to change notification settings - Fork 657
feat(rome_js_analyzer): add useAltText lint rule for JSX expression #3371
Conversation
❌ Deploy Preview for docs-rometools failed.
|
crates/rome_js_analyze/src/semantic_analyzers/nursery/use_alt_text.rs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the contribution! I left some suggestion and correction to the cases covered
} | ||
|
||
declare_node_union! { | ||
pub (crate) UseAltTextQuery = JsxSelfClosingElement | JsCallExpression |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should limit ourselves only to JsxSelfClosingElement
nodes. Which means that union is not needed anymore, and the Query
will become:
type Query = Ast<JsxSelfClosingElement>;
None | ||
} | ||
|
||
fn is_valid_tag(name: &JsxAnyElementName) -> Option<bool> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we document this function? "Valid tag" is not clear, unless we look at the implementation of the function.
} | ||
} | ||
|
||
fn is_valid_input(element: &JsxSelfClosingElement) -> Option<bool> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we document this function? "Valid input" needs to have a context; we have to tell developers what a valid input means in this case.
Also, this function could be called input_has_type_image
UseAltTextQuery::JsxSelfClosingElement(element) => { | ||
let name = element.name().ok()?; | ||
if is_valid_tag(&name)? { | ||
let _attributes = element.attributes(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't seem this is used... so we can remove it?
let is_spread_available = element | ||
.attributes() | ||
.iter() | ||
.any(|attribute| attribute.as_jsx_spread_attribute().is_some()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The usage of the spread props is sneaky and we need to be careful to where it's placed inside the component.
Here's two examples:
let first = <img {...props} alt />;
let second = <img alt {...props} />;
The first
case is invalid, meaning if should trigger the rule, because alt
overrides what's inside props
.
The second
case is valid, meaning it should not trigger the rule, because props
might have the alt
property and override the one passed before it. This is a case we don't have enough information to trigger the rule, so has rule of thumb, if we don't know if a rule should be triggered, we don't trigger it.
Check for the spread property might be complex, and I think we should have an API for that. What I would suggest for now, is to avoid checking for spread prop for the time being.
} | ||
return Some(UseAltTextState { | ||
node: UseAltTextNode::from(element.clone()), | ||
missing_alt_prop: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every time UseAltTextState
is returned, missing_alt_prop
is true
, which means that we don't need this property
// invalid | ||
|
||
<> | ||
<area {...props} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This case is valid. Inside props
we might have alt
. We don't have enough info so we should not trigger the rule.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This applies to other tags as well?
.footer( | ||
Severity::Note, | ||
markup! { | ||
"Meaningful alternative text on elements helps users relying on screen | ||
readers to understand content's purpose within a page." | ||
}, | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: you can footer_note
function and pass only the text.
|
||
impl Rule for UseAltText { | ||
type Query = Semantic<UseAltTextQuery>; | ||
type State = UseAltTextState; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think State
can be a TextRange
type State = TextRange
And when you find a property that is incorrect, you just do
return Some(element.syntaxt().text_range_trimmed())
And the state
can be used inside the diagnostic. As you noticed the state is only used to retrieve the range.
@ematipico I have updated the code, please check it.
|
// invalid | ||
|
||
<> | ||
<area alt={undefined} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we add a case for undefined
, I think we should also add a case for alt={null}
<img {...props} alt /> | ||
<img {...props} /> {/* Skipping*/} | ||
<img {...props} alt={undefined} /> {/* Skipping*/} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove these cases? It would be better to tackle spread props in a different PR (it's a feature missing in different rules, not just this one). And for example, the first and third cases are false positives.
return None; | ||
} | ||
let message = markup!( | ||
"Provide "<Emphasis>"alt"</Emphasis>" when using "<Emphasis>"img"</Emphasis>", "<Emphasis>"area"</Emphasis>", "<Emphasis>"input type='image'"</Emphasis>"" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Provide "<Emphasis>"alt"</Emphasis>" when using "<Emphasis>"img"</Emphasis>", "<Emphasis>"area"</Emphasis>", "<Emphasis>"input type='image'"</Emphasis>"" | |
"Provide the attribute"<Emphasis>"alt"</Emphasis>" when using "<Emphasis>"img"</Emphasis>", "<Emphasis>"area"</Emphasis>" or "<Emphasis>"input type='image'"</Emphasis>"" |
You would need to regenerate the snapshots and documentation
).to_owned(); | ||
|
||
Some( | ||
RuleDiagnostic::new(rule_category!(), state, message).footer( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just use footer_note
, so you don't need to pass Severity
/* | ||
This function checks for the attribute `type` for input element where we checking for the input type which is image. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/* | |
This function checks for the attribute `type` for input element where we checking for the input type which is image. | |
*/ | |
/// This function checks for the attribute `type` for input element where we checking for the input type which is image. |
We should use rust documentation comments
/* | ||
Function to check that the HTML/JSX tag is valid for the lint rule, which check if the tag should be `img`, `input type='image' and `area`. | ||
|
||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/* | |
Function to check that the HTML/JSX tag is valid for the lint rule, which check if the tag should be `img`, `input type='image' and `area`. | |
*/ | |
/// Function to check that the HTML/JSX tag is valid for the lint rule, which check if the tag should be `img`, `input type='image' and `area`. |
We should use rust documentation comments
let is_spread_available = element | ||
.attributes() | ||
.iter() | ||
.any(|attribute| attribute.as_jsx_spread_attribute().is_some()); | ||
|
||
// To-Do - Complex cases might invlove, not triggering rules. | ||
if is_spread_available { | ||
return None; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's best, for now, to remove the check for spread props. It's a feature/functionality that is missing from various rules and the implementation should do more checks. It's OK to remove it, I will work on it later.
@@ -0,0 +1,57 @@ | |||
--- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file seems incorrect. It should be called useAltText.md
. Can you delete it and try to generate it again?
<<<<<<< HEAD | ||
<h3 data-toc-exclude id="useAltText"> | ||
<a href="/docs/lint/rules/useAltText">useAltText (since v0.10.0)</a> | ||
<a class="header-anchor" href="#useAltText"></a> | ||
</h3> | ||
It asserts that alternative text to images or areas, help to rely on to screen readers to understand the purpose and the context of the image. | ||
======= |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's some issue with this file too... You should delete it and generate again
Function to check that the HTML/JSX tag is valid for the lint rule, which check if the tag should be `img`, `input type='image' and `area`. | ||
|
||
*/ | ||
fn is_valid_tag(name: &JsxAnyElementName) -> Option<bool> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"text_trimmed"and the navigation are expensive methods. You can simplify this method, and its call to:
match name.as_jsx_name().map(|x| x.text_trimmed()) {
Some("input") => {...}
Some("img" | "area") => {...}
}
return None; | ||
} | ||
|
||
let alt_prop = element.find_attribute_by_name("alt").ok()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may also look at: #3458.
It will improve the code here.
Summary
Closes #3298 following the document given for valid and in-valid conditions.
#3298
Test Plan
A lint rule to check for alt attribute in JSX for tags like
input type='image', img, area
.