diff --git a/docs/rules/jsx-no-target-blank.md b/docs/rules/jsx-no-target-blank.md
index ae92669f39..906e852b4a 100644
--- a/docs/rules/jsx-no-target-blank.md
+++ b/docs/rules/jsx-no-target-blank.md
@@ -4,14 +4,28 @@ When creating a JSX element that has an `a` tag, it is often desired to have
the link open in a new tab using the `target='_blank'` attribute. Using this
attribute unaccompanied by `rel='noreferrer noopener'`, however, is a severe
security vulnerability ([see here for more details](https://mathiasbynens.github.io/rel-noopener))
-This rules requires that you accompany all `target='_blank'` attributes with `rel='noreferrer noopener'`.
+This rules requires that you accompany `target='_blank'` attributes with `rel='noreferrer noopener'`.
## Rule Details
-The following patterns are considered errors:
+This rule aims to prevent user generated links from creating security vulerabilities by requiring
+`rel='noreferrer noopener'` for external links, and optionally any dynamically generated links.
+
+## Rule Options
+
+There are two main options for the rule:
+
+* `{"enforceDynamicLinks": "always"}` enforces the rule if the href is a dyanamic link (default)
+* `{"enforceDynamicLinks": "never"}` does not enforce the rule if the href is a dyamic link
+
+
+### always (default)
+
+When {"enforceDynamicLinks": "always"} is set, the following patterns are considered errors:
```jsx
var Hello =
+var Hello =
```
The following patterns are **not** considered errors:
@@ -24,6 +38,14 @@ var Hello =
var Hello =
```
+### never
+
+When {"enforceDynamicLinks": "never"} is set, the following patterns are **not** considered errors:
+
+```jsx
+var Hello =
+```
+
## When Not To Use It
If you do not have any external links, you can disable this rule
diff --git a/lib/rules/jsx-no-target-blank.js b/lib/rules/jsx-no-target-blank.js
index a5b34c2d19..e4492874e0 100644
--- a/lib/rules/jsx-no-target-blank.js
+++ b/lib/rules/jsx-no-target-blank.js
@@ -23,6 +23,12 @@ function hasExternalLink(element) {
/^(?:\w+:|\/\/)/.test(attr.value.value));
}
+function hasDynamicLink(element) {
+ return element.attributes.some(attr => attr.name &&
+ attr.name.name === 'href' &&
+ attr.value.type === 'JSXExpressionContainer');
+}
+
function hasSecureRel(element) {
return element.attributes.find(attr => {
if (attr.type === 'JSXAttribute' && attr.name.name === 'rel') {
@@ -41,21 +47,28 @@ module.exports = {
recommended: true,
url: docsUrl('jsx-no-target-blank')
},
- schema: []
+ schema: [{
+ type: 'object',
+ properties: {
+ enforceDynamicLinks: {
+ enum: ['always', 'never']
+ }
+ },
+ additionalProperties: false
+ }]
},
create: function(context) {
+ const configuration = context.options[0] || {};
+ const enforceDynamicLinks = configuration.enforceDynamicLinks || 'always';
+
return {
JSXAttribute: function(node) {
- if (node.parent.name.name !== 'a') {
+ if (node.parent.name.name !== 'a' || !isTargetBlank(node) || hasSecureRel(node.parent)) {
return;
}
- if (
- isTargetBlank(node) &&
- hasExternalLink(node.parent) &&
- !hasSecureRel(node.parent)
- ) {
+ if (hasExternalLink(node.parent) || (enforceDynamicLinks === 'always' && hasDynamicLink(node.parent))) {
context.report(node, 'Using target="_blank" without rel="noopener noreferrer" ' +
'is a security risk: see https://mathiasbynens.github.io/rel-noopener');
}
diff --git a/tests/lib/rules/jsx-no-target-blank.js b/tests/lib/rules/jsx-no-target-blank.js
index a0c756cbcc..aba9d080bb 100644
--- a/tests/lib/rules/jsx-no-target-blank.js
+++ b/tests/lib/rules/jsx-no-target-blank.js
@@ -25,6 +25,11 @@ const parserOptions = {
// ------------------------------------------------------------------------------
const ruleTester = new RuleTester({parserOptions});
+const defaultErrors = [{
+ message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
+ ' see https://mathiasbynens.github.io/rel-noopener'
+}];
+
ruleTester.run('jsx-no-target-blank', rule, {
valid: [
{code: ''},
@@ -38,61 +43,45 @@ ruleTester.run('jsx-no-target-blank', rule, {
{code: ''},
{code: ''},
{code: ''},
- {code: ''}
+ {code: ''},
+ {
+ code: '',
+ options: [{enforceDynamicLinks: 'never'}]
+ }
],
invalid: [{
code: '',
- errors: [{
- message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
- ' see https://mathiasbynens.github.io/rel-noopener'
- }]
+ errors: defaultErrors
}, {
code: '',
- errors: [{
- message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
- ' see https://mathiasbynens.github.io/rel-noopener'
- }]
+ errors: defaultErrors
}, {
code: '',
- errors: [{
- message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
- ' see https://mathiasbynens.github.io/rel-noopener'
- }]
+ errors: defaultErrors
}, {
code: '',
- errors: [{
- message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
- ' see https://mathiasbynens.github.io/rel-noopener'
- }]
+ errors: defaultErrors
}, {
code: '',
- errors: [{
- message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
- ' see https://mathiasbynens.github.io/rel-noopener'
- }]
+ errors: defaultErrors
}, {
code: '',
- errors: [{
- message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
- ' see https://mathiasbynens.github.io/rel-noopener'
- }]
+ errors: defaultErrors
}, {
code: '',
- errors: [{
- message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
- ' see https://mathiasbynens.github.io/rel-noopener'
- }]
+ errors: defaultErrors
}, {
code: '',
- errors: [{
- message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
- ' see https://mathiasbynens.github.io/rel-noopener'
- }]
+ errors: defaultErrors
}, {
code: '',
- errors: [{
- message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
- ' see https://mathiasbynens.github.io/rel-noopener'
- }]
+ errors: defaultErrors
+ }, {
+ code: '',
+ errors: defaultErrors
+ }, {
+ code: '',
+ options: [{enforceDynamicLinks: 'always'}],
+ errors: defaultErrors
}]
});