Skip to content

Commit

Permalink
[New] jsx-no-target-blank: add fixer
Browse files Browse the repository at this point in the history
  • Loading branch information
Nokel81 authored and ljharb committed Nov 25, 2020
1 parent 77e1b35 commit 907c8e8
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -11,6 +11,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
* [`jsx-no-constructed-context-values`]: add new rule which checks when the value passed to a Context Provider will cause needless rerenders ([#2763][] @dylanOshima)
* [`jsx-indent-props`]: add `ignoreTernaryOperator` option ([#2846][] @SebastianZimmer)
* [`jsx-no-target-blank`]: Add `warnOnSpreadAttributes` option ([#2855][] @michael-yx-wu)
* [`jsx-no-target-blank`]: add fixer ([#2862][] @Nokel81)

### Fixed
* [`display-name`]/component detection: avoid a crash on anonymous components ([#2840][] @ljharb)
Expand All @@ -36,6 +37,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
[#2871]: https://github.com/yannickcr/eslint-plugin-react/issues/2871
[#2870]: https://github.com/yannickcr/eslint-plugin-react/issues/2870
[#2869]: https://github.com/yannickcr/eslint-plugin-react/issues/2869
[#2862]: https://github.com/yannickcr/eslint-plugin-react/pull/2862
[#2855]: https://github.com/yannickcr/eslint-plugin-react/pull/2855
[#2852]: https://github.com/yannickcr/eslint-plugin-react/pull/2852
[#2851]: https://github.com/yannickcr/eslint-plugin-react/issues/2851
Expand Down
46 changes: 45 additions & 1 deletion lib/rules/jsx-no-target-blank.js
Expand Up @@ -87,6 +87,7 @@ function hasSecureRel(node, allowReferrer, warnOnSpreadAttributes, spreadAttribu

module.exports = {
meta: {
fixable: 'code',
docs: {
description: 'Forbid `target="_blank"` attribute without `rel="noreferrer"`',
category: 'Best Practices',
Expand Down Expand Up @@ -135,7 +136,50 @@ module.exports = {
context.report({
node,
message: 'Using target="_blank" without rel="noreferrer" '
+ 'is a security risk: see https://html.spec.whatwg.org/multipage/links.html#link-type-noopener'
+ 'is a security risk: see https://html.spec.whatwg.org/multipage/links.html#link-type-noopener',
fix(fixer) {
const relAttribute = node.parent.attributes.find((attr) => attr.name.name === 'rel');
if (!relAttribute) {
return fixer.insertTextAfter(node, ' rel="noreferrer"');
}

if (!relAttribute.value) {
return fixer.insertTextAfter(relAttribute, '="noreferrer"');
}

if (relAttribute.value.type === 'Literal') {
if (!relAttribute.value.value.trim()) {
return fixer.replaceText(relAttribute.value, '"noreferrer"');
}

const parts = relAttribute.value.value
.split('noreferrer')
.filter(Boolean);
return fixer.replaceText(relAttribute.value, `"${parts.concat('noreferrer').join(' ')}"`);
}

if (relAttribute.value.expression.type === 'Literal') {
if (
typeof relAttribute.value.expression.value === 'undefined'
|| typeof relAttribute.value.expression.value === 'boolean'
|| typeof relAttribute.value.expression.value === 'number'
|| typeof relAttribute.value.expression.value === 'symbol'
|| typeof relAttribute.value.expression.value === 'bigint'
|| relAttribute.value.expression.value === null
) {
return fixer.replaceText(relAttribute.value.expression.parent, '"noreferrer"');
}

if (typeof relAttribute.value.expression.value === 'string') {
const parts = relAttribute.value.expression.value
.split('noreferrer')
.filter(Boolean);
return fixer.replaceText(relAttribute.value.expression, `"${parts.concat('noreferrer').join(' ')}"`);
}
}

return null;
}
});
}
}
Expand Down
37 changes: 37 additions & 0 deletions tests/lib/rules/jsx-no-target-blank.js
Expand Up @@ -112,51 +112,86 @@ ruleTester.run('jsx-no-target-blank', rule, {
],
invalid: [{
code: '<a target="_blank" href="http://example.com"></a>',
output: '<a target="_blank" rel="noreferrer" href="http://example.com"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" rel="" href="http://example.com"></a>',
output: '<a target="_blank" rel="noreferrer" href="http://example.com"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" rel={0} href="http://example.com"></a>',
output: '<a target="_blank" rel="noreferrer" href="http://example.com"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" rel={false} href="http://example.com"></a>',
output: '<a target="_blank" rel="noreferrer" href="http://example.com"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" rel={null} href="http://example.com"></a>',
output: '<a target="_blank" rel="noreferrer" href="http://example.com"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" rel="noopenernoreferrer" href="http://example.com"></a>',
output: '<a target="_blank" rel="noopener noreferrer" href="http://example.com"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" rel="no referrer" href="http://example.com"></a>',
output: '<a target="_blank" rel="no referrer noreferrer" href="http://example.com"></a>',
errors: defaultErrors
}, {
code: '<a target="_BLANK" href="http://example.com"></a>',
output: '<a target="_BLANK" rel="noreferrer" href="http://example.com"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com"></a>',
output: '<a target="_blank" rel="noreferrer" href="//example.com"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com" rel={true}></a>',
output: '<a target="_blank" href="//example.com" rel="noreferrer"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com" rel={3}></a>',
output: '<a target="_blank" href="//example.com" rel="noreferrer"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com" rel={null}></a>',
output: '<a target="_blank" href="//example.com" rel="noreferrer"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com" rel={getRel()}></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com" rel={"noopenernoreferrer"}></a>',
output: '<a target="_blank" href="//example.com" rel={"noopener noreferrer"}></a>',
errors: defaultErrors
}, {
code: '<a target={"_blank"} href={"//example.com"} rel={"noopenernoreferrer"}></a>',
output: '<a target={"_blank"} href={"//example.com"} rel={"noopener noreferrer"}></a>',
errors: defaultErrors
}, {
code: '<a target={"_blank"} href={"//example.com"} rel={"noopenernoreferrernoreferrernoreferrernoreferrernoreferrer"}></a>',
output: '<a target={"_blank"} href={"//example.com"} rel={"noopener noreferrer"}></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com" rel></a>',
output: '<a target="_blank" href="//example.com" rel="noreferrer"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href={ dynamicLink }></a>',
output: '<a target="_blank" rel="noreferrer" href={ dynamicLink }></a>',
errors: defaultErrors
}, {
code: '<a target={\'_blank\'} href="//example.com"></a>',
output: '<a target={\'_blank\'} rel="noreferrer" href="//example.com"></a>',
errors: defaultErrors
}, {
code: '<a target={"_blank"} href="//example.com"></a>',
output: '<a target={"_blank"} rel="noreferrer" href="//example.com"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href={ dynamicLink }></a>',
output: '<a target="_blank" rel="noreferrer" href={ dynamicLink }></a>',
options: [{enforceDynamicLinks: 'always'}],
errors: defaultErrors
}, {
Expand All @@ -181,11 +216,13 @@ ruleTester.run('jsx-no-target-blank', rule, {
errors: defaultErrors
}, {
code: '<Link target="_blank" href={ dynamicLink }></Link>',
output: '<Link target="_blank" rel="noreferrer" href={ dynamicLink }></Link>',
options: [{enforceDynamicLinks: 'always'}],
settings: {linkComponents: ['Link']},
errors: defaultErrors
}, {
code: '<Link target="_blank" to={ dynamicLink }></Link>',
output: '<Link target="_blank" rel="noreferrer" to={ dynamicLink }></Link>',
options: [{enforceDynamicLinks: 'always'}],
settings: {linkComponents: {name: 'Link', linkAttribute: 'to'}},
errors: defaultErrors
Expand Down

0 comments on commit 907c8e8

Please sign in to comment.