From b914d29332ff8213f5d9b0456de0125dcecb71d0 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 25 Nov 2020 11:12:01 -0500 Subject: [PATCH] [NEW]: `jsx-no-target-blank`: add fixer --- CHANGELOG.md | 2 + lib/rules/jsx-no-target-blank.js | 46 +++++++++++++++- tests/lib/rules/jsx-no-target-blank.js | 73 ++++++++++++++++++++------ 3 files changed, 104 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7549cb37c..e87e60dbf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) @@ -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 diff --git a/lib/rules/jsx-no-target-blank.js b/lib/rules/jsx-no-target-blank.js index 656cd3009f..501e0a94bc 100644 --- a/lib/rules/jsx-no-target-blank.js +++ b/lib/rules/jsx-no-target-blank.js @@ -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', @@ -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; + } }); } } diff --git a/tests/lib/rules/jsx-no-target-blank.js b/tests/lib/rules/jsx-no-target-blank.js index dbe9a0e528..eef3c13cb7 100644 --- a/tests/lib/rules/jsx-no-target-blank.js +++ b/tests/lib/rules/jsx-no-target-blank.js @@ -111,52 +111,91 @@ ruleTester.run('jsx-no-target-blank', rule, { } ], invalid: [{ - code: '', + code: '', + output: '', errors: defaultErrors }, { - code: '', + code: '', + output: '', errors: defaultErrors }, { - code: '', + code: '', + output: '', errors: defaultErrors }, { - code: '', + code: '', + output: '', errors: defaultErrors }, { - code: '', + code: '', + output: '', errors: defaultErrors }, { - code: '', + code: '', + output: '', errors: defaultErrors }, { - code: '', + code: '', + output: '', errors: defaultErrors }, { - code: '', + code: '', + output: '', errors: defaultErrors }, { - code: '', + code: '', + output: '', errors: defaultErrors }, { - code: '', + code: '', + output: '', errors: defaultErrors }, { - code: '', + code: '', + output: '', errors: defaultErrors }, { - code: '', + code: '', + output: '', + errors: defaultErrors + }, { + code: '', + output: '', + errors: defaultErrors + }, { + code: '', + errors: defaultErrors + }, { + code: '', + output: '', + errors: defaultErrors + }, { + code: '', + output: '', + errors: defaultErrors + }, { + code: '', + output: '', + errors: defaultErrors + }, { + code: '', + output: '', errors: defaultErrors }, { code: '', + output: '', errors: defaultErrors }, { - code: '', + code: '', + output: '', errors: defaultErrors }, { - code: '', + code: '', + output: '', errors: defaultErrors }, { code: '', + output: '', options: [{enforceDynamicLinks: 'always'}], errors: defaultErrors }, { @@ -172,20 +211,22 @@ ruleTester.run('jsx-no-target-blank', rule, { options: [{enforceDynamicLinks: 'always', warnOnSpreadAttributes: true}], errors: defaultErrors }, { - code: '', + code: '', options: [{enforceDynamicLinks: 'always', warnOnSpreadAttributes: true}], errors: defaultErrors }, { - code: '', + code: '', options: [{enforceDynamicLinks: 'always', warnOnSpreadAttributes: true}], errors: defaultErrors }, { code: '', + output: '', options: [{enforceDynamicLinks: 'always'}], settings: {linkComponents: ['Link']}, errors: defaultErrors }, { code: '', + output: '', options: [{enforceDynamicLinks: 'always'}], settings: {linkComponents: {name: 'Link', linkAttribute: 'to'}}, errors: defaultErrors