-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Wrap around eslint-plugin-jsx-a11y/anchor-has-content to support createInterpolateElement usage #50065
base: trunk
Are you sure you want to change the base?
Wrap around eslint-plugin-jsx-a11y/anchor-has-content to support createInterpolateElement usage #50065
Conversation
👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @rowasc! In case you missed it, we'd love to have you join us in our Slack community, where we hold regularly weekly meetings open to anyone to coordinate with each other. If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information. |
packages/eslint-plugin/rules/__tests__/a11y-anchor-has-content.js
Outdated
Show resolved
Hide resolved
};`, | ||
}, | ||
{ | ||
// ⚠️ This is invalid code that would get flagged by jsx-a11y/anchor-is-valid, but NOT by eslint-plugin-jsx-a11y/anchor-has-content or @wordpress/a11-anchor-has-content. |
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 added these so that it's obvious to readers what's covered/not covered (also because I was afraid of us forgetting the scope of the linter otherwise), but LMK if they feel irrelevant or if you have suggestions on how to improve them.
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.
It’s also possible to add names for tests according to https://eslint.org/docs/latest/integrate/nodejs-api#ruletester. Maybe that would be a way to provide additional context instead of using inline comments.
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 so much, completely missed the name
field. I've switched to using name in most cases.
packages/eslint-plugin/rules/__tests__/a11y-anchor-has-content.js
Outdated
Show resolved
Hide resolved
schema: [], // no options | ||
}; | ||
|
||
const getATagsContent = ( str ) => { |
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 could improve this with a tokenizer a la createInterpolate element and catch more complex cases (e.g unmatched tags) if we want to. Originally did that, but felt it was adding more complexity than needed and the lint rules ended up being a bit flaky (better to do a focused createInterpolateA11y type linter for that).
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.
It’s better to start with something simpler and improve over time. The bigger the scope, the longer it takes to review and land. Everything is going to be better than disabling the rule everywhere the helper function is used.
@@ -7,6 +7,7 @@ module.exports = { | |||
'@wordpress/no-global-active-element': 'error', |
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 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.
https://github.com/WordPress/gutenberg/pull/50065/files?short_path=e4b51f2#diff-e4b51f2bcad9f627a441e4941e91e50987e0ece0d4e4873675c46daaca18ccbe some initial docs here, they cover all basic scenarios
packages/eslint-plugin/rules/__tests__/a11y-anchor-has-content.js
Outdated
Show resolved
Hide resolved
This is solid work, and this PR only needs some final touches to bring it to the finish line. Excellent job tackling this issue. I see that one of the CI jobs errors while running
The old rule is disabled now, so it should be safe to remove all those comments from the code. Do I miss something here? |
@gziolo thank you so much for the review, and I am sorry for the delay here, I got back from my meetup and finally got a chance to get back to this today.
No, not missing anything, sorry! I probably messed up the explanation, I will give it another go. Added the lint-ignore removal here now so that I can make sure they are all ✅ too. @tyxla I would appreciate a review if you get a chance, especially after all the changes from today, but no rush at all. Other than that I will just follow your/all reviewers' direction as needed :) moving to "Ready to review" now that I added some docs. |
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.
Lovely work, @rowasc! Really cool idea and nice work to wrap around the existing rule.
I've left a bunch of comments, but it's mostly code style and nothing major or blocking.
I think this already looks in a pretty stable state and the tests cover a great deal of the available scenarios. 🚀
packages/eslint-plugin/docs/rules/jsx-a11y-anchor-has-content.md
Outdated
Show resolved
Hide resolved
packages/eslint-plugin/docs/rules/jsx-a11y-anchor-has-content.md
Outdated
Show resolved
Hide resolved
packages/eslint-plugin/docs/rules/jsx-a11y-anchor-has-content.md
Outdated
Show resolved
Hide resolved
packages/eslint-plugin/docs/rules/jsx-a11y-anchor-has-content.md
Outdated
Show resolved
Hide resolved
* @param {*} tags markup used in createInterpolateElement | ||
* @param {*} tagNames names that we recognize as anchors, as configured in the eslint rules | ||
* @return {Array} tagNames an array of the content of the <a> (or other) tags |
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.
We could vertically align the different sections for better readability. ESLint will usually complain, but then it is likely just not enabled here.
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.
Thanks, agree. This is actually something I had to do because ESLint complained :( I had them aligned but it kept pestering me about it. Later I realized it was because the alignment rule is different if there is no description in the Doc block
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.
#50065 (comment) Apparently it also gets messed up in other cases - added more context in the link
const regex = new RegExp( `<(${ tagText })>(.*?)</(${ tagText })>`, 'g' ); | ||
const tagParts = [ ...tags.matchAll( regex ) ]; | ||
return tagParts.map( ( element ) => { | ||
return element[ 2 ].trim(); |
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.
Perhaps we actually intend to have the leading/trailing space?
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.
yep, but in that case, it would not result in an empty anchor content (for the purposes of the rule, that's all that we care about) - however, the comment made me realize I can improve this abstraction a bit since it's not really clear what the intention is. Thanks!
As an example <a> Hello world </a>
would be totally fine, because even if you remove trailing spaces you don't end up with an empty anchor, so the rule won't complain. However <a> </a>
is not valid, as the result would be a '' after the trim, and the rule would complain at you.
// An unknown function call in a translate function may produce a valid string | ||
// but verifying it is not straight-forward, so we bail | ||
if ( args.filter( Boolean ).length === 0 ) { | ||
nodeStr = null; |
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.
Why is this necessary if we override it immediately on L91?
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 was just a mistake while refactoring and I didn't have a test to cover it, thanks for catching it
Added a quick scenario to cover it https://github.com/WordPress/gutenberg/pull/50065/files#diff-4c555e1042f88bdebf5cba43ba582ae78b721c7d64c1d0cc527ea1975ef191edR61
const componentOptions = options.components || []; | ||
const typeCheck = [ 'a' ].concat( componentOptions ); | ||
if ( | ||
typeCheck.filter( ( type ) => type === node?.name?.name ) |
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.
Do we also need to filter out nullish values before we check the length
?
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 didn't think so, but I've added a check to be safe in case something is misconfigured, thanks for catching it
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.
Lovely work, @rowasc! Really cool idea and nice work to wrap around the existing rule.
I've left a bunch of comments, but it's mostly code style and nothing major or blocking.
I think this already looks in a pretty stable state and the tests cover a great deal of the available scenarios. 🚀
Excellent. Removing all comments that disable the original rule with all the unit tests covering possible cases is the best way to validate correctness and all we need to move forward 👍 For coding styles, you might want to try running ESLint with |
…t in two steps Before, we had a method that just returned the content of anchors and the actual validation was done by the main function in the linter. The problem there was that there wasn't a clear enough abstraction indicating the intent of the matcher that identified which tags were empty, causing confusion about it https://github.com/WordPress/gutenberg/pull/50065/files#r1187499276. Now, instead of getATagsContent we have validateAnchors which throws an error with the same code we report through eslint if anchors are empty or not valid. Co-Authored-By: Boro <[email protected]>
…hing useful :) - Fix issue where we were matching the wrong messageId for nodes with the latest refacotr
…ue, it won't be considered valid
Before we were automatically using the e.message thrown by the validator, which we had looked into and should not throw but to be safe, added a clause to rethrow if the unexpected thing happens instead of using the eslint report for normal non lint issues
Co-authored-by: Marin Atanasov <[email protected]>
Co-authored-by: Marin Atanasov <[email protected]>
Co-authored-by: Marin Atanasov <[email protected]>
Co-authored-by: Marin Atanasov <[email protected]>
Co-authored-by: Marin Atanasov <[email protected]>
Co-authored-by: Marin Atanasov <[email protected]>
Co-authored-by: Marin Atanasov <[email protected]>
The previous samples were including more context than strictly necessary, making it harder to understand the point of the correct/incorrect usage scenarios. Hopefully this is minimal and still clear enough to be a good UX
There was so much clutter that it was hard to understand the tests. We now get the same benefits with less code _shrugs_
Instead of re-throwing when there is a problem in the linter code, we use the eslint reporter to tell the user that this is not their fault :) we also give them context and nudge them to report it as a bug. This way they don't spend time chasing down something that is not their problem. Co-Authored-By: Boro <[email protected]>
5fb816e
to
8bc1ad9
Compare
What?
Fixes #21441.
Changes:
packages/eslint-plugin/configs/jsx-a11y.js: disables the rule 'jsx-a11y/anchor-has-content' in favor of wordpress/a11y-interpolated-anchor, which wraps around the former to allow both checking regular JSX and not failing on interpolated elements.
a11y-interpolated-anchor.js adds the new wrapper rule. This rule is then enabled in the custom.js rules of the eslint-plugin.
Why?
When using anchors and createInterpolateElement together, we need to eslint-ignore the line to avoid running into the anchor-has-content error even if the anchor is valid.
This pull request introduces a wrapper around the rule so that we do not need to constantly ignore it.
How?
Testing Instructions
Testing Instructions for Keyboard
<a> </a>
)@wordpress/a11y-anchor-has-content
linter error that saysAnchors must have content and the content must be accessible by a screen reader. Check the first parameter to createInterpolateElement
<a href=""></a></>
, without createInterpolateElement@wordpress/a11y-anchor-has-content
linter error that saysAnchors must have content and the content must be accessible by a screen reader
npm run test:unit -- packages/eslint-plugin/rules/__tests__/jsx-a11y-anchor-has-content.js
. Thea11y-anchor-has-content.js
tests should pass (and all others).