UNPKG

3.38 kBJavaScriptView Raw
1/**
2 * @fileoverview Forbid target='_blank' attribute
3 * @author Kevin Miller
4 */
5
6'use strict';
7
8const docsUrl = require('../util/docsUrl');
9const linkComponentsUtil = require('../util/linkComponents');
10
11// ------------------------------------------------------------------------------
12// Rule Definition
13// ------------------------------------------------------------------------------
14
15function isTargetBlank(attr) {
16 return attr.name
17 && attr.name.name === 'target'
18 && attr.value
19 && ((
20 attr.value.type === 'Literal'
21 && attr.value.value.toLowerCase() === '_blank'
22 ) || (
23 attr.value.type === 'JSXExpressionContainer'
24 && attr.value.expression
25 && attr.value.expression.value
26 && attr.value.expression.value.toLowerCase() === '_blank'
27 ));
28}
29
30function hasExternalLink(element, linkAttribute) {
31 return element.attributes.some((attr) => attr.name
32 && attr.name.name === linkAttribute
33 && attr.value.type === 'Literal'
34 && /^(?:\w+:|\/\/)/.test(attr.value.value));
35}
36
37function hasDynamicLink(element, linkAttribute) {
38 return element.attributes.some((attr) => attr.name
39 && attr.name.name === linkAttribute
40 && attr.value.type === 'JSXExpressionContainer');
41}
42
43function hasSecureRel(element, allowReferrer) {
44 return element.attributes.find((attr) => {
45 if (attr.type === 'JSXAttribute' && attr.name.name === 'rel') {
46 const value = attr.value
47 && ((
48 attr.value.type === 'Literal'
49 && attr.value.value
50 ) || (
51 attr.value.type === 'JSXExpressionContainer'
52 && attr.value.expression
53 && attr.value.expression.value
54 ));
55 const tags = value && value.toLowerCase && value.toLowerCase().split(' ');
56 return tags && (allowReferrer ? tags.indexOf('noopener') >= 0 : tags.indexOf('noreferrer') >= 0);
57 }
58 return false;
59 });
60}
61
62module.exports = {
63 meta: {
64 docs: {
65 description: 'Forbid `target="_blank"` attribute without `rel="noreferrer"`',
66 category: 'Best Practices',
67 recommended: true,
68 url: docsUrl('jsx-no-target-blank')
69 },
70 schema: [{
71 type: 'object',
72 properties: {
73 allowReferrer: {
74 type: 'boolean'
75 },
76 enforceDynamicLinks: {
77 enum: ['always', 'never']
78 }
79 },
80 additionalProperties: false
81 }]
82 },
83
84 create(context) {
85 const configuration = context.options[0] || {};
86 const allowReferrer = configuration.allowReferrer || false;
87 const enforceDynamicLinks = configuration.enforceDynamicLinks || 'always';
88 const components = linkComponentsUtil.getLinkComponents(context);
89
90 return {
91 JSXAttribute(node) {
92 if (
93 !components.has(node.parent.name.name)
94 || !isTargetBlank(node)
95 || hasSecureRel(node.parent, allowReferrer)
96 ) {
97 return;
98 }
99
100 const linkAttribute = components.get(node.parent.name.name);
101
102 if (hasExternalLink(node.parent, linkAttribute) || (enforceDynamicLinks === 'always' && hasDynamicLink(node.parent, linkAttribute))) {
103 context.report({
104 node,
105 message: 'Using target="_blank" without rel="noreferrer" '
106 + 'is a security risk: see https://html.spec.whatwg.org/multipage/links.html#link-type-noopener'
107 });
108 }
109 }
110 };
111 }
112};