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 committed Jan 7, 2021
1 parent 4406aba commit b914d29
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 17 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -24,6 +24,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 Down Expand Up @@ -56,6 +57,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) {
// eslint 5 uses `node.attributes`; eslint 6+ uses `node.parent.attributes`
const nodeWithAttrs = node.parent.attributes ? node.parent : node;
// eslint 5 does not provide a `name` property on JSXSpreadElements
const relAttribute = nodeWithAttrs.attributes.find((attr) => attr.name && attr.name.name === 'rel');

if (!relAttribute) {
return fixer.insertTextAfter(nodeWithAttrs.attributes.slice(-1)[0], ' rel="noreferrer"');
}

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

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

if (relAttribute.value.type === 'JSXExpressionContainer') {
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, '"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
73 changes: 57 additions & 16 deletions tests/lib/rules/jsx-no-target-blank.js
Expand Up @@ -111,52 +111,91 @@ ruleTester.run('jsx-no-target-blank', rule, {
}
],
invalid: [{
code: '<a target="_blank" href="http://example.com"></a>',
code: '<a target="_blank" href="http://example.com/1"></a>',
output: '<a target="_blank" href="http://example.com/1" rel="noreferrer"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" rel="" href="http://example.com"></a>',
code: '<a target="_blank" rel="" href="http://example.com/2"></a>',
output: '<a target="_blank" rel="noreferrer" href="http://example.com/2"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" rel="noopenernoreferrer" href="http://example.com"></a>',
code: '<a target="_blank" rel={0} href="http://example.com/3"></a>',
output: '<a target="_blank" rel="noreferrer" href="http://example.com/3"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" rel="no referrer" href="http://example.com"></a>',
code: '<a target="_blank" rel={1} href="http://example.com/3"></a>',
output: '<a target="_blank" rel="noreferrer" href="http://example.com/3"></a>',
errors: defaultErrors
}, {
code: '<a target="_BLANK" href="http://example.com"></a>',
code: '<a target="_blank" rel={false} href="http://example.com/4"></a>',
output: '<a target="_blank" rel="noreferrer" href="http://example.com/4"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com"></a>',
code: '<a target="_blank" rel={null} href="http://example.com/5"></a>',
output: '<a target="_blank" rel="noreferrer" href="http://example.com/5"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com" rel={true}></a>',
code: '<a target="_blank" rel="noopenernoreferrer" href="http://example.com/6"></a>',
output: '<a target="_blank" rel="noopener noreferrer" href="http://example.com/6"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com" rel={3}></a>',
code: '<a target="_blank" rel="no referrer" href="http://example.com/7"></a>',
output: '<a target="_blank" rel="no referrer noreferrer" href="http://example.com/7"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com" rel={null}></a>',
code: '<a target="_BLANK" href="http://example.com/8"></a>',
output: '<a target="_BLANK" href="http://example.com/8" rel="noreferrer"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com" rel={"noopenernoreferrer"}></a>',
code: '<a target="_blank" href="//example.com/9"></a>',
output: '<a target="_blank" href="//example.com/9" rel="noreferrer"></a>',
errors: defaultErrors
}, {
code: '<a target={"_blank"} href={"//example.com"} rel={"noopenernoreferrer"}></a>',
code: '<a target="_blank" href="//example.com/10" rel={true}></a>',
output: '<a target="_blank" href="//example.com/10" rel="noreferrer"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com" rel></a>',
code: '<a target="_blank" href="//example.com/11" rel={3}></a>',
output: '<a target="_blank" href="//example.com/11" rel="noreferrer"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com/12" rel={null}></a>',
output: '<a target="_blank" href="//example.com/12" rel="noreferrer"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com/13" rel={getRel()}></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com/14" rel={"noopenernoreferrer"}></a>',
output: '<a target="_blank" href="//example.com/14" rel={"noopener noreferrer"}></a>',
errors: defaultErrors
}, {
code: '<a target={"_blank"} href={"//example.com/15"} rel={"noopenernoreferrer"}></a>',
output: '<a target={"_blank"} href={"//example.com/15"} rel={"noopener noreferrer"}></a>',
errors: defaultErrors
}, {
code: '<a target={"_blank"} href={"//example.com/16"} rel={"noopenernoreferrernoreferrernoreferrernoreferrernoreferrer"}></a>',
output: '<a target={"_blank"} href={"//example.com/16"} rel={"noopener noreferrer"}></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href="//example.com/17" rel></a>',
output: '<a target="_blank" href="//example.com/17" rel="noreferrer"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href={ dynamicLink }></a>',
output: '<a target="_blank" href={ dynamicLink } rel="noreferrer"></a>',
errors: defaultErrors
}, {
code: '<a target={\'_blank\'} href="//example.com"></a>',
code: '<a target={\'_blank\'} href="//example.com/18"></a>',
output: '<a target={\'_blank\'} href="//example.com/18" rel="noreferrer"></a>',
errors: defaultErrors
}, {
code: '<a target={"_blank"} href="//example.com"></a>',
code: '<a target={"_blank"} href="//example.com/19"></a>',
output: '<a target={"_blank"} href="//example.com/19" rel="noreferrer"></a>',
errors: defaultErrors
}, {
code: '<a target="_blank" href={ dynamicLink }></a>',
output: '<a target="_blank" href={ dynamicLink } rel="noreferrer"></a>',
options: [{enforceDynamicLinks: 'always'}],
errors: defaultErrors
}, {
Expand All @@ -172,20 +211,22 @@ ruleTester.run('jsx-no-target-blank', rule, {
options: [{enforceDynamicLinks: 'always', warnOnSpreadAttributes: true}],
errors: defaultErrors
}, {
code: '<a href="foobar" target="_blank" {...someObject}></a>',
code: '<a href="foobar" target="_blank" rel="noreferrer" {...someObject}></a>',
options: [{enforceDynamicLinks: 'always', warnOnSpreadAttributes: true}],
errors: defaultErrors
}, {
code: '<a href="foobar" target="_blank" rel="noreferrer" {...someObject}></a>',
code: '<a href="foobar" target="_blank" {...someObject}></a>',
options: [{enforceDynamicLinks: 'always', warnOnSpreadAttributes: true}],
errors: defaultErrors
}, {
code: '<Link target="_blank" href={ dynamicLink }></Link>',
output: '<Link target="_blank" href={ dynamicLink } rel="noreferrer"></Link>',
options: [{enforceDynamicLinks: 'always'}],
settings: {linkComponents: ['Link']},
errors: defaultErrors
}, {
code: '<Link target="_blank" to={ dynamicLink }></Link>',
output: '<Link target="_blank" to={ dynamicLink } rel="noreferrer"></Link>',
options: [{enforceDynamicLinks: 'always'}],
settings: {linkComponents: {name: 'Link', linkAttribute: 'to'}},
errors: defaultErrors
Expand Down

0 comments on commit b914d29

Please sign in to comment.