| /** |
| * @fileoverview Forbid target='_blank' attribute |
| * @author Kevin Miller |
| */ |
| |
| 'use strict'; |
| |
| const docsUrl = require('../util/docsUrl'); |
| const linkComponentsUtil = require('../util/linkComponents'); |
| |
| // ------------------------------------------------------------------------------ |
| // Rule Definition |
| // ------------------------------------------------------------------------------ |
| |
| function isTargetBlank(attr) { |
| return attr.name && |
| attr.name.name === 'target' && |
| attr.value && |
| attr.value.type === 'Literal' && |
| attr.value.value.toLowerCase() === '_blank'; |
| } |
| |
| function hasExternalLink(element, linkAttribute) { |
| return element.attributes.some(attr => attr.name && |
| attr.name.name === linkAttribute && |
| attr.value.type === 'Literal' && |
| /^(?:\w+:|\/\/)/.test(attr.value.value)); |
| } |
| |
| function hasDynamicLink(element, linkAttribute) { |
| return element.attributes.some(attr => attr.name && |
| attr.name.name === linkAttribute && |
| attr.value.type === 'JSXExpressionContainer'); |
| } |
| |
| function hasSecureRel(element) { |
| return element.attributes.find((attr) => { |
| if (attr.type === 'JSXAttribute' && attr.name.name === 'rel') { |
| const tags = attr.value && attr.value.type === 'Literal' && attr.value.value.toLowerCase().split(' '); |
| return tags && (tags.indexOf('noopener') >= 0 && tags.indexOf('noreferrer') >= 0); |
| } |
| return false; |
| }); |
| } |
| |
| module.exports = { |
| meta: { |
| docs: { |
| description: 'Forbid target="_blank" attribute without rel="noopener noreferrer"', |
| category: 'Best Practices', |
| recommended: true, |
| url: docsUrl('jsx-no-target-blank') |
| }, |
| schema: [{ |
| type: 'object', |
| properties: { |
| enforceDynamicLinks: { |
| enum: ['always', 'never'] |
| } |
| }, |
| additionalProperties: false |
| }] |
| }, |
| |
| create(context) { |
| const configuration = context.options[0] || {}; |
| const enforceDynamicLinks = configuration.enforceDynamicLinks || 'always'; |
| const components = linkComponentsUtil.getLinkComponents(context); |
| |
| return { |
| JSXAttribute(node) { |
| if (!components.has(node.parent.name.name) || !isTargetBlank(node) || hasSecureRel(node.parent)) { |
| return; |
| } |
| |
| const linkAttribute = components.get(node.parent.name.name); |
| |
| if (hasExternalLink(node.parent, linkAttribute) || (enforceDynamicLinks === 'always' && hasDynamicLink(node.parent, linkAttribute))) { |
| context.report({ |
| node, |
| message: 'Using target="_blank" without rel="noopener noreferrer" ' + |
| 'is a security risk: see https://mathiasbynens.github.io/rel-noopener' |
| }); |
| } |
| } |
| }; |
| } |
| }; |