Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lint/noStaticElementInteractions): add rule #2981

Open
wants to merge 23 commits into
base: main
Choose a base branch
from

Conversation

ryo-ebata
Copy link

@ryo-ebata ryo-ebata commented May 25, 2024

Summary

Enforce that non-interactive, visible elements (such as <div>) that have click handlers use the role attribute.

Implements #527

Test Plan

Added snaps for valid/invalid cases.

Basically, it conforms to the test cases of eslint-plugin-jsx-a11y/no-static-element-interactions, and test cases that cannot be handled by the resources within Biome are handled individually.

ref:
https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/__tests__/src/rules/no-static-element-interactions-test.js

@github-actions github-actions bot added A-Project Area: project A-Linter Area: linter L-JavaScript Language: JavaScript and super languages A-Diagnostic Area: diagnostocis labels May 25, 2024
@github-actions github-actions bot added the A-CLI Area: CLI label May 25, 2024
Copy link

codspeed-hq bot commented May 25, 2024

CodSpeed Performance Report

Merging #2981 will not alter performance

Comparing ryo-ebata:ryo-ebata/no-static-element-interactions (71c5004) with main (a4b050b)

Summary

✅ 90 untouched benchmarks

@ryo-ebata
Copy link
Author

ryo-ebata commented Jun 2, 2024

@unvalley

As this comment states, the following two methods were created to bridge the gap between eslint-plugin-jsx-a11y and Biome

  • is_valid_element
  • is_invalid_element

However, I thought it was necessary to optimize the Biome definition based on the official documentation, rather than discussing the two options of which way to go.

Is this an issue that should be addressed in this PR?
If necessary, we will address it in this PR, but I think there is an option to address it in another Issue, PR due to the number of elements.

@unvalley
Copy link
Member

unvalley commented Jun 3, 2024

@ryo-ebata

Is this an issue that should be addressed in this PR?
If necessary, we will address it in this PR, but I think there is an option to address it in another Issue, PR due to the number of elements.

Let's address that in another PR.

Comment on lines 51 to 74
m.insert("clipboard", vec!["onCopy", "onCut", "onPaste"]);
m.insert("composition", vec!["onCompositionEnd", "onCompositionStart", "onCompositionUpdate"]);
m.insert("keyboard", vec!["onKeyDown", "onKeyPress", "onKeyUp"]);
m.insert("focus", vec!["onFocus", "onBlur"]);
m.insert("form", vec!["onChange", "onInput", "onSubmit"]);
m.insert("mouse", vec![
"onClick", "onContextMenu", "onDblClick", "onDoubleClick", "onDrag", "onDragEnd",
"onDragEnter", "onDragExit", "onDragLeave", "onDragOver", "onDragStart", "onDrop",
"onMouseDown", "onMouseEnter", "onMouseLeave", "onMouseMove", "onMouseOut",
"onMouseOver", "onMouseUp"
]);
m.insert("selection", vec!["onSelect"]);
m.insert("touch", vec!["onTouchCancel", "onTouchEnd", "onTouchMove", "onTouchStart"]);
m.insert("ui", vec!["onScroll"]);
m.insert("wheel", vec!["onWheel"]);
m.insert("media", vec![
"onAbort", "onCanPlay", "onCanPlayThrough", "onDurationChange", "onEmptied",
"onEncrypted", "onEnded", "onError", "onLoadedData", "onLoadedMetadata", "onLoadStart",
"onPause", "onPlay", "onPlaying", "onProgress", "onRateChange", "onSeeked", "onSeeking",
"onStalled", "onSuspend", "onTimeUpdate", "onVolumeChange", "onWaiting"
]);
m.insert("image", vec!["onLoad", "onError"]);
m.insert("animation", vec!["onAnimationStart", "onAnimationEnd", "onAnimationIteration"]);
m.insert("transition", vec!["onTransitionEnd"]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need elements (and handlers) other than in CATEGORIES_TO_CHECK?
We only use focus, keyboard and mouse.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@unvalley
In ESlint, interactions are managed in independent files named eventHandlersByType.
ref: https://github.com/jsx-eslint/jsx-ast-utils/blob/main/src/eventHandlers.js

Among them, ESlint uses focus, keyboard and mouse by default.
ref: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/974275353598e9407c76bd4a50c331a953755cee/src/rules/no-static-element-interactions.js#L33-L37

In other words, the EVENT_TO_HANDLERS created in this PR was originally intended to be used for other rules, etc., but since it is currently unclear where to place them, they are implemented this way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. So, I guess it means we don't have any other rules that need this CATEGORIES_TO_CHECK yet.

Alright, we can locate the hashmap here, but I suggest commenting out elements that aren't used here. It may be a waste of overhead. Insertion for focus, keyboard, and mouse is enough.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@unvalley
I see, I understand!
I'll fix this.

Copy link
Member

@ematipico ematipico Jun 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to barge in during this review. I think this code would benefit from a refactor. I don't think it's a good custom to try to replicate JS code in Rust. For example, this code, while it's created once via lazy_static, still creates a bunch of vectors that are created at runtime (heap).

Instead, why not create simple arrays?

Also, I think we should explain what this code is. It's copied from another source, which is completely fine, but there's no comment that explains what are the keys and what are the values, how they are used and what they are meant to be.

For example composition -> [...]. What's composition? Where is it coming from? Is it an HTML element? Is it a role? Remember that in Biome, we want to aim for good standards, DX-wise and coding-wise.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@unvalley
fixed in this a9aa880

Copy link
Author

@ryo-ebata ryo-ebata Jun 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@unvalley
Sorry, I didn't address to additional comments, please leave them as they are.

Copy link
Author

@ryo-ebata ryo-ebata Jun 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ematipico @unvalley
Thank you for pointing this out.
As you said, it was causing overhead, so we defined it as a fixed-length array.

Also modified the comment to describe what is stored in this array and added the URL to the MDN reference describing each event handler

fixed in this commit b330131

@unvalley unvalley self-assigned this Jun 7, 2024
@ryo-ebata
Copy link
Author

@unvalley
Sorry for the delay in correcting the code in response to your comment...
Please check back as I have responded to your comments and corrected the points you raised!

Comment on lines 51 to 74
m.insert("clipboard", vec!["onCopy", "onCut", "onPaste"]);
m.insert("composition", vec!["onCompositionEnd", "onCompositionStart", "onCompositionUpdate"]);
m.insert("keyboard", vec!["onKeyDown", "onKeyPress", "onKeyUp"]);
m.insert("focus", vec!["onFocus", "onBlur"]);
m.insert("form", vec!["onChange", "onInput", "onSubmit"]);
m.insert("mouse", vec![
"onClick", "onContextMenu", "onDblClick", "onDoubleClick", "onDrag", "onDragEnd",
"onDragEnter", "onDragExit", "onDragLeave", "onDragOver", "onDragStart", "onDrop",
"onMouseDown", "onMouseEnter", "onMouseLeave", "onMouseMove", "onMouseOut",
"onMouseOver", "onMouseUp"
]);
m.insert("selection", vec!["onSelect"]);
m.insert("touch", vec!["onTouchCancel", "onTouchEnd", "onTouchMove", "onTouchStart"]);
m.insert("ui", vec!["onScroll"]);
m.insert("wheel", vec!["onWheel"]);
m.insert("media", vec![
"onAbort", "onCanPlay", "onCanPlayThrough", "onDurationChange", "onEmptied",
"onEncrypted", "onEnded", "onError", "onLoadedData", "onLoadedMetadata", "onLoadStart",
"onPause", "onPlay", "onPlaying", "onProgress", "onRateChange", "onSeeked", "onSeeking",
"onStalled", "onSuspend", "onTimeUpdate", "onVolumeChange", "onWaiting"
]);
m.insert("image", vec!["onLoad", "onError"]);
m.insert("animation", vec!["onAnimationStart", "onAnimationEnd", "onAnimationIteration"]);
m.insert("transition", vec!["onTransitionEnd"]);
Copy link
Member

@ematipico ematipico Jun 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to barge in during this review. I think this code would benefit from a refactor. I don't think it's a good custom to try to replicate JS code in Rust. For example, this code, while it's created once via lazy_static, still creates a bunch of vectors that are created at runtime (heap).

Instead, why not create simple arrays?

Also, I think we should explain what this code is. It's copied from another source, which is completely fine, but there's no comment that explains what are the keys and what are the values, how they are used and what they are meant to be.

For example composition -> [...]. What's composition? Where is it coming from? Is it an HTML element? Is it a role? Remember that in Biome, we want to aim for good standards, DX-wise and coding-wise.

Comment on lines +185 to +189
/// Checks if the given element name is considered invalid, inspired by eslint-plugin-jsx-a11y
fn is_invalid_element(element_name: &str) -> bool {
match element_name {
// These cases are interactive with the is_not_interactive_element method,
// but is an invalid test case element for eslint-plugin-jsx-a11y.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the mention to the plugin is appreciated, this information doesn't give us enough value. To you and all developers. What you want to do, instead, is to document when and why an element is considered valid. I am sure there's a context here, and it's the developers job to write it down.

Copy link
Author

@ryo-ebata ryo-ebata Jun 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ematipico
Thank you for your comment.
Your comment is correct, but we will hold it in abeyance because the way we respond will depend on the answers to the following questions.

#2981 (comment)

I will address this when this method continues to be needed.


/// This function ables non-interactive elements.
/// This is because this is an element that is abled by eslint-plugin-jsx-a11y.
fn is_valid_element(element_name: &str) -> bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "valid element" mean? Valid for what context? We should document it.

Plus, you would want to move this function inside biome_aria, as @unvalley suggested. Here's an example:

pub fn is_role_interactive(&self, role: &str) -> bool {
let role = self.get_role(role);
if let Some(role) = role {
role.is_interactive()
} else {
false
}
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ematipico cc @unvalley
Regarding this point, this method includes processing that considers elements generally deemed non-interactive as valid.
These elements were part of valid test cases in eslint-plugin-jsx-a11y.

When considering portability from ESLint, these test cases should be taken into account (meaning that if a project using ESLint transitions to Biome, many lint errors could occur). However, if we are overly concerned with the above point, we will never be able to lint according to Biome's rules.

Therefore, if we align with Biome's interactive criteria and adjust the rules accordingly, this method will become unnecessary. I would appreciate it if you could provide guidance on the direction we should take.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there was a misunderstanding; I'm sorry about it. I am not asking you to use the link I pasted. The link I pasted was an example. And I didn't ask to remove the logic you're porting from the plugin.

What I asked was to properly document the function. The phrase "This function ables non-interactive elements." doesn't provide any value to the function name, because it doesn't explain what is a valid element (or when it is valid).

I understand that English is not your first language, so I am willing to help if you explain a bit the logic of this function

Copy link
Member

@unvalley unvalley Jun 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can merge this PR after addressing this point.

Copy link
Author

@ryo-ebata ryo-ebata Jun 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ematipico cc @unvalley
Okay, thank you.
Then I need to see why Eslint is enabling these elements, but we have not yet found a clear reason.

So it may take a little time, but I may ask for your help in documenting it. Thank you in advance for your help at that time.

crates/biome_js_analyze/src/services/aria.rs Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-CLI Area: CLI A-Diagnostic Area: diagnostocis A-Linter Area: linter A-Project Area: project L-JavaScript Language: JavaScript and super languages
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants