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: add eslint plugin no-html-links #8156

Merged
merged 17 commits into from
Dec 14, 2022

Conversation

JohnVicke
Copy link
Contributor

@JohnVicke JohnVicke commented Oct 1, 2022

Pre-flight checklist

Motivation

Described in: #6472

Test Plan

Jest unit tests are included.

Output of `yarn test no-html-plugins`
PASS packages/eslint-plugin/src/rules/__tests__/no-html-links.test.ts
  prefer-docusaurus-link
    valid
      ✓ <Link to="/test">test</Link> (239 ms)
      ✓ <Link to="https://twitter.com/docusaurus">Twitter</Link> (2 ms)
    invalid
      ✓ <a href="/test">test</a> (3 ms)
      ✓ <a href="https://twitter.com/docusaurus" target="_blank">test</a> (2 ms)
      ✓ <a href="https://twitter.com/docusaurus" target="_blank" rel="noopener noreferrer">test</a> (1 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        0.67 s, estimated 1 s
Ran all test suites matching /no-html-links/i.

Test links

Docs preview: https://deploy-preview-8156--docusaurus-2.netlify.app/docs/api/misc/@docusaurus/eslint-plugin/no-html-links

Comments

The solution is fairly simple, I played around with when the message should be returned to the user (e.g. a tag has no attributes). However, if we want to enforce docusaurus links all the time this feels more accurate.

@facebook-github-bot facebook-github-bot added the CLA Signed Signed Facebook CLA label Oct 1, 2022
@netlify
Copy link

netlify bot commented Oct 1, 2022

[V2]

Name Link
🔨 Latest commit 741e6ef
🔍 Latest deploy log https://app.netlify.com/sites/docusaurus-2/deploys/639a04498d4ae30009f80f2e
😎 Deploy Preview https://deploy-preview-8156--docusaurus-2.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@github-actions
Copy link

github-actions bot commented Oct 1, 2022

⚡️ Lighthouse report for the deploy preview of this PR

URL Performance Accessibility Best Practices SEO PWA Report
/ 🔴 36 🟢 97 🟢 100 🟢 100 🟢 90 Report
/docs/installation 🟠 73 🟢 100 🟢 100 🟢 100 🟢 90 Report

Copy link
Collaborator

@slorber slorber left a comment

Choose a reason for hiding this comment

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

Thanks, LGTM 👍

Wonder if we want to be that strict and never allow any absolute URL by default, so maybe adding an option can be convenient?

We mostly want to prevent bad relative links IMHO

Any opinion @Josh-Cena ?

</blockquote>
<figcaption>
<a href={profileUrl} target="_blank" rel="noreferrer nofollow">
Copy link
Collaborator

Choose a reason for hiding this comment

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

<Link> doesn't automatically apply nofollow.

Maybe use <Link rel="nofollow">. Open question: should the rel be merged into existing ones ("noreferrer noopener") or override?

I assume merging is fine because we generally don't want to remove "noopener noreferrer" from external links (and could use <a> as an escape hatch)

website/docs/api/misc/eslint-plugin/README.md Outdated Show resolved Hide resolved
<a
href={`#${heading.id}`}
<Link
to={`#${heading.id}`}
Copy link
Collaborator

Choose a reason for hiding this comment

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

wonder if we could also allow <a> for hardcoded hash links?

(not all links can be evaluated but hardcoded ones could? Maybe eslint can know it starts with a hash? 🤷‍♂️)

Copy link
Collaborator

Choose a reason for hiding this comment

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

It's not undoable. Even for template literals, you just need to check quasis[0][0] === "#".

Copy link
Contributor Author

@JohnVicke JohnVicke Oct 5, 2022

Choose a reason for hiding this comment

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

Good point!

Lets say that we add this to the plugin, should it still warn (encourage) the user to use <Link> instead of the a tag?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess we can just ignore hashes. This is a bonus, we can merge this PR without this feature if complicated to implement.

@Josh-Cena
Copy link
Collaborator

Wonder if we want to be that strict and never allow any absolute URL by default, so maybe adding an option can be convenient?

An option like ignoreFullyResolved would be my go-to, yes. It would be tricky to check template literal values, though. Recommending always using <Link> is not that bad and may allow us to do other useful things in the future.

@slorber
Copy link
Collaborator

slorber commented Oct 5, 2022

ignoreFullyResolved seems fine, even if it only allows hardcoded href props that can already be convenient.

Not sure "fully resolved" is very common, where does it come from?


Recommending always using is not that bad and may allow us to do other useful things in the future.

Agree, but maybe we'll need to improve it for external links then , because for example not merging rel (current behavior) is a bit annoying

@JohnVicke
Copy link
Contributor Author

JohnVicke commented Oct 5, 2022

ignoreFullyResolved seems fine, even if it only allows hardcoded href props that can already be convenient.

Thanks for the thorough feedback, will add some changes tomorrow 🙇 . ignoreFullyResolved got me thinking of another use-case we might want to consider. Should we add an option to ignore mailto or let them pass as default behaviour?

@Josh-Cena
Copy link
Collaborator

I'd say, anything that starts with a protocol should be ignored.

@slorber slorber added the pr: new feature This PR adds a new API or behavior. label Oct 7, 2022
@slorber
Copy link
Collaborator

slorber commented Oct 7, 2022

I'd say, anything that starts with a protocol should be ignored.

agree

At the same time is fullyResolved the term to use for an URL that starts with a protocol? 🤷‍♂️ I don't know.

It looks like the concept of "fully qualified" exist for domain names (https://www.rfc-editor.org/rfc/rfc6454 https://en.wikipedia.org/wiki/Fully_qualified_domain_name) but can't really find any official ref of "fully qualified URL" in any spec).

I don't know where "fully resolved" comes from but it doesn't seem like an existing concept either.

I'm more used to see the term "fully qualified" and some sources seem agree despite being web authorities (https://gateway.force.com/DPGCustHelp/s/article/Fully-qualified-URL)

@Josh-Cena
Copy link
Collaborator

I was thinking about "fully resolved paths". I don't know where that term crosses my mind either.

Copy link
Collaborator

@slorber slorber left a comment

Choose a reason for hiding this comment

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

LGTM, good enough to merge

Just wondering if "ignoreFullyResolved" is the best term we can use to represent the option, any opinion before merging?


| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `ignoreFullyResolved` | `boolean` | `false` | Set to true will not report any `<a>` tags with absolute URLs. |
Copy link
Collaborator

Choose a reason for hiding this comment

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

if "fully resolved" === "absolute url" then maybe we'd want to be consistent?

https://www.rfc-editor.org/rfc/rfc3986#section-4.3

Maybe we just want "ignoreAbsoluteUrls" instead, it would be easier for users to understand?

This is just wording, but it remains important.

Now we can still do a breaking change later if we change our mind, not a huge deal.

@Josh-Cena
Copy link
Collaborator

I want to review this before merging. I think I can find some time this weekend

# Conflicts:
#	packages/docusaurus-theme-classic/src/theme/SkipToContent/index.tsx
@slorber
Copy link
Collaborator

slorber commented Oct 21, 2022

conflict resolved

@Josh-Cena do you still want to review it?

@slorber
Copy link
Collaborator

slorber commented Oct 26, 2022

@Josh-Cena ?

Copy link
Collaborator

@Josh-Cena Josh-Cena left a comment

Choose a reason for hiding this comment

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

Extremely sorry for missing this @JohnVicke. The implementation looks good although I'd like to work on enhancements after this is merged. Just requesting a bit more tests (as ESLint plugin maintainers always do...) I could also do this myself this week so it gets merged ASAP

(attr): attr is TSESTree.JSXAttribute => attr.type === 'JSXAttribute' && attr.name.name === 'href',
);

if (hrefAttr?.value?.type === 'Literal') {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you add tests for when this is not a literal? Especially template literals. In the future I definitely want this rule to recognize some common interpolation patterns (e.g. if the template starts with / then it's definitely not fullyResolved), so it's better if we can have some tests to verify how the behaviors change.

website/docs/api/misc/eslint-plugin/no-html-links.md Outdated Show resolved Hide resolved
@slorber
Copy link
Collaborator

slorber commented Dec 9, 2022

@Josh-Cena let me know if you want to do something about this PR 🤪 otherwise I'll apply some suggestions, do a few minor changes and merge it

@Josh-Cena
Copy link
Collaborator

Mostly LGTM. I wonder if we should add an option to ignore fully dynamic and not statically analyzable href props, but that could be an extension because using Link can't harm in any case.

@slorber
Copy link
Collaborator

slorber commented Dec 14, 2022

Look good enough to merge

Added some extra code to handle hardcoded/static template literal strings too href={"https://xyz.com"} as it wasn't the case.

For more dynamic cases where static analysis is possible, that can be handled in a follow-up PR


Was wondering if it's possible to pre-evaluate AST nodes with TSESTree @Josh-Cena @JoshuaKGoldberg

Apparently, Babel is able to do that successfully:

CleanShot 2022-12-14 at 18 04 52@2x

Feel free to complete the missing cases if you know how to ;)

@slorber slorber added the to backport This PR is planned to be backported to a stable version of Docusaurus label Dec 14, 2022
@slorber slorber merged commit 4a44877 into facebook:main Dec 14, 2022
@slorber slorber removed the to backport This PR is planned to be backported to a stable version of Docusaurus label Jan 26, 2023
This was referenced Oct 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed Signed Facebook CLA pr: new feature This PR adds a new API or behavior.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants