jsx-no-target-blank.js 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. /**
  2. * @fileoverview Forbid target='_blank' attribute
  3. * @author Kevin Miller
  4. */
  5. 'use strict';
  6. const docsUrl = require('../util/docsUrl');
  7. // ------------------------------------------------------------------------------
  8. // Rule Definition
  9. // ------------------------------------------------------------------------------
  10. function isTargetBlank(attr) {
  11. return attr.name.name === 'target' &&
  12. attr.value.type === 'Literal' &&
  13. attr.value.value.toLowerCase() === '_blank';
  14. }
  15. function hasExternalLink(element) {
  16. return element.attributes.some(attr => attr.name &&
  17. attr.name.name === 'href' &&
  18. attr.value.type === 'Literal' &&
  19. /^(?:\w+:|\/\/)/.test(attr.value.value));
  20. }
  21. function hasDynamicLink(element) {
  22. return element.attributes.some(attr => attr.name &&
  23. attr.name.name === 'href' &&
  24. attr.value.type === 'JSXExpressionContainer');
  25. }
  26. function hasSecureRel(element) {
  27. return element.attributes.find(attr => {
  28. if (attr.type === 'JSXAttribute' && attr.name.name === 'rel') {
  29. const tags = attr.value && attr.value.type === 'Literal' && attr.value.value.toLowerCase().split(' ');
  30. return tags && (tags.indexOf('noopener') >= 0 && tags.indexOf('noreferrer') >= 0);
  31. }
  32. return false;
  33. });
  34. }
  35. module.exports = {
  36. meta: {
  37. docs: {
  38. description: 'Forbid target="_blank" attribute without rel="noopener noreferrer"',
  39. category: 'Best Practices',
  40. recommended: true,
  41. url: docsUrl('jsx-no-target-blank')
  42. },
  43. schema: [{
  44. type: 'object',
  45. properties: {
  46. enforceDynamicLinks: {
  47. enum: ['always', 'never']
  48. }
  49. },
  50. additionalProperties: false
  51. }]
  52. },
  53. create: function(context) {
  54. const configuration = context.options[0] || {};
  55. const enforceDynamicLinks = configuration.enforceDynamicLinks || 'always';
  56. return {
  57. JSXAttribute: function(node) {
  58. if (node.parent.name.name !== 'a' || !isTargetBlank(node) || hasSecureRel(node.parent)) {
  59. return;
  60. }
  61. if (hasExternalLink(node.parent) || (enforceDynamicLinks === 'always' && hasDynamicLink(node.parent))) {
  62. context.report(node, 'Using target="_blank" without rel="noopener noreferrer" ' +
  63. 'is a security risk: see https://mathiasbynens.github.io/rel-noopener');
  64. }
  65. }
  66. };
  67. }
  68. };