Skip to content

Commit

Permalink
[New] jsx-no-target-blank: add support for linkComponents setting
Browse files Browse the repository at this point in the history
  • Loading branch information
gbakernet authored and ljharb committed Jan 11, 2019
1 parent da71ff4 commit da8c6b2
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 15 deletions.
42 changes: 33 additions & 9 deletions docs/rules/jsx-no-target-blank.md
Expand Up @@ -20,37 +20,61 @@ This rule aims to prevent user generated links from creating security vulnerabil

* enabled: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0.
* enforce: optional string, 'always' or 'never'
* Link components can be something other than an `<a>`, see [shared settings](https://github.com/yannickcr/eslint-plugin-react/blob/master/README.md#configuration) for `linkComponents` configuration)

### `enforceDynamicLinks`

#### always

### always (default)
`{"enforceDynamicLinks": "always"}` enforces the rule if the href is a dynamic link (default)

When {"enforceDynamicLinks": "always"} is set, the following patterns are considered errors:

```jsx
var Hello = <a target='_blank' href="http://example.com/"></a>
var Hello = <a target='_blank' href={ dynamicLink }></a>
var Hello = <a target='_blank' href={dynamicLink}></a>
```

The following patterns are **not** considered errors:

```jsx
var Hello = <p target='_blank'></p>
var Hello = <a target='_blank' rel='noopener noreferrer' href="http://example.com"></a>
var Hello = <a target='_blank' href="relative/path/in/the/host"></a>
var Hello = <a target='_blank' href="/absolute/path/in/the/host"></a>
var Hello = <p target="_blank"></p>
var Hello = <a target="_blank" rel="noopener noreferrer" href="http://example.com"></a>
var Hello = <a target="_blank" href="relative/path/in/the/host"></a>
var Hello = <a target="_blank" href="/absolute/path/in/the/host"></a>
var Hello = <a></a>
```

### never
#### never

`{"enforceDynamicLinks": "never"}` does not enforce the rule if the href is a dynamic link

When {"enforceDynamicLinks": "never"} is set, the following patterns are **not** considered errors:

```jsx
var Hello = <a target='_blank' href={ dynamicLink }></a>
var Hello = <a target='_blank' href={dynamicLink}></a>
```

### Link components

Link components can be something other than an `<a>`, see [shared settings](https://github.com/yannickcr/eslint-plugin-react/blob/master/README.md#configuration) for `linkComponents` configuration)

The following patterns are considered errors:

```jsx
var Hello = <Link target="_blank" to="http://example.com/"></Link>
var Hello = <Link target="_blank" to={dynamicLink}></Link>
```

The following patterns are **not** considered errors:

```jsx
var Hello = <Link target="_blank" rel="noopener noreferrer" to="http://example.com"></Link>
var Hello = <Link target="_blank" to="relative/path/in/the/host"></Link>
var Hello = <Link target="_blank" to="/absolute/path/in/the/host"></Link>
var Hello = <Link />
```

## When Not To Use It

If you do not have any external links, you can disable this rule
If you do not have any external links, you can disable this rule
16 changes: 10 additions & 6 deletions lib/rules/jsx-no-target-blank.js
Expand Up @@ -5,6 +5,7 @@
'use strict';

const docsUrl = require('../util/docsUrl');
const linkComponentsUtil = require('../util/linkComponents');

// ------------------------------------------------------------------------------
// Rule Definition
Expand All @@ -18,16 +19,16 @@ function isTargetBlank(attr) {
attr.value.value.toLowerCase() === '_blank';
}

function hasExternalLink(element) {
function hasExternalLink(element, linkAttribute) {
return element.attributes.some(attr => attr.name &&
attr.name.name === 'href' &&
attr.name.name === linkAttribute &&
attr.value.type === 'Literal' &&
/^(?:\w+:|\/\/)/.test(attr.value.value));
}

function hasDynamicLink(element) {
function hasDynamicLink(element, linkAttribute) {
return element.attributes.some(attr => attr.name &&
attr.name.name === 'href' &&
attr.name.name === linkAttribute &&
attr.value.type === 'JSXExpressionContainer');
}

Expand Down Expand Up @@ -63,14 +64,17 @@ module.exports = {
create: function(context) {
const configuration = context.options[0] || {};
const enforceDynamicLinks = configuration.enforceDynamicLinks || 'always';
const components = linkComponentsUtil.getLinkComponents(context);

return {
JSXAttribute: function(node) {
if (node.parent.name.name !== 'a' || !isTargetBlank(node) || hasSecureRel(node.parent)) {
if (!components.has(node.parent.name.name) || !isTargetBlank(node) || hasSecureRel(node.parent)) {
return;
}

if (hasExternalLink(node.parent) || (enforceDynamicLinks === 'always' && hasDynamicLink(node.parent))) {
const linkAttribute = components.get(node.parent.name.name);

if (hasExternalLink(node.parent, linkAttribute) || (enforceDynamicLinks === 'always' && hasDynamicLink(node.parent, linkAttribute))) {
context.report(node, 'Using target="_blank" without rel="noopener noreferrer" ' +
'is a security risk: see https://mathiasbynens.github.io/rel-noopener');
}
Expand Down
20 changes: 20 additions & 0 deletions tests/lib/rules/jsx-no-target-blank.js
Expand Up @@ -47,6 +47,16 @@ ruleTester.run('jsx-no-target-blank', rule, {
{
code: '<a target="_blank" href={ dynamicLink }></a>',
options: [{enforceDynamicLinks: 'never'}]
},
{
code: '<Link target="_blank" href={ dynamicLink }></Link>',
options: [{enforceDynamicLinks: 'never'}],
settings: {linkComponents: ['Link']}
},
{
code: '<Link target="_blank" to={ dynamicLink }></Link>',
options: [{enforceDynamicLinks: 'never'}],
settings: {linkComponents: {name: 'Link', linkAttribute: 'to'}}
}
],
invalid: [{
Expand Down Expand Up @@ -83,5 +93,15 @@ ruleTester.run('jsx-no-target-blank', rule, {
code: '<a target="_blank" href={ dynamicLink }></a>',
options: [{enforceDynamicLinks: 'always'}],
errors: defaultErrors
}, {
code: '<Link target="_blank" href={ dynamicLink }></Link>',
options: [{enforceDynamicLinks: 'always'}],
settings: {linkComponents: ['Link']},
errors: defaultErrors
}, {
code: '<Link target="_blank" to={ dynamicLink }></Link>',
options: [{enforceDynamicLinks: 'always'}],
settings: {linkComponents: {name: 'Link', linkAttribute: 'to'}},
errors: defaultErrors
}]
});

0 comments on commit da8c6b2

Please sign in to comment.