no-danger-with-children.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. /**
  2. * @fileoverview Report when a DOM element is using both children and dangerouslySetInnerHTML
  3. * @author David Petersen
  4. */
  5. 'use strict';
  6. const variableUtil = require('../util/variable');
  7. const docsUrl = require('../util/docsUrl');
  8. // ------------------------------------------------------------------------------
  9. // Rule Definition
  10. // ------------------------------------------------------------------------------
  11. module.exports = {
  12. meta: {
  13. docs: {
  14. description: 'Report when a DOM element is using both children and dangerouslySetInnerHTML',
  15. category: '',
  16. recommended: true,
  17. url: docsUrl('no-danger-with-children')
  18. },
  19. schema: [] // no options
  20. },
  21. create: function(context) {
  22. function findSpreadVariable(name) {
  23. return variableUtil.variablesInScope(context).find(item => item.name === name);
  24. }
  25. /**
  26. * Takes a ObjectExpression and returns the value of the prop if it has it
  27. * @param {object} node - ObjectExpression node
  28. * @param {string} propName - name of the prop to look for
  29. */
  30. function findObjectProp(node, propName, seenProps) {
  31. if (!node.properties) {
  32. return false;
  33. }
  34. return node.properties.find(prop => {
  35. if (prop.type === 'Property') {
  36. return prop.key.name === propName;
  37. } else if (prop.type === 'ExperimentalSpreadProperty' || prop.type === 'SpreadElement') {
  38. const variable = findSpreadVariable(prop.argument.name);
  39. if (variable && variable.defs.length && variable.defs[0].node.init) {
  40. if (seenProps.indexOf(prop.argument.name) > -1) {
  41. return false;
  42. }
  43. const newSeenProps = seenProps.concat(prop.argument.name || []);
  44. return findObjectProp(variable.defs[0].node.init, propName, newSeenProps);
  45. }
  46. }
  47. return false;
  48. });
  49. }
  50. /**
  51. * Takes a JSXElement and returns the value of the prop if it has it
  52. * @param {object} node - JSXElement node
  53. * @param {string} propName - name of the prop to look for
  54. */
  55. function findJsxProp(node, propName) {
  56. const attributes = node.openingElement.attributes;
  57. return attributes.find(attribute => {
  58. if (attribute.type === 'JSXSpreadAttribute') {
  59. const variable = findSpreadVariable(attribute.argument.name);
  60. if (variable && variable.defs.length && variable.defs[0].node.init) {
  61. return findObjectProp(variable.defs[0].node.init, propName, []);
  62. }
  63. }
  64. return attribute.name && attribute.name.name === propName;
  65. });
  66. }
  67. /**
  68. * Checks to see if a node is a line break
  69. * @param {ASTNode} node The AST node being checked
  70. * @returns {Boolean} True if node is a line break, false if not
  71. */
  72. function isLineBreak(node) {
  73. const isLiteral = node.type === 'Literal' || node.type === 'JSXText';
  74. const isMultiline = node.loc.start.line !== node.loc.end.line;
  75. const isWhiteSpaces = /^\s*$/.test(node.value);
  76. return isLiteral && isMultiline && isWhiteSpaces;
  77. }
  78. return {
  79. JSXElement: function (node) {
  80. let hasChildren = false;
  81. if (node.children.length && !isLineBreak(node.children[0])) {
  82. hasChildren = true;
  83. } else if (findJsxProp(node, 'children')) {
  84. hasChildren = true;
  85. }
  86. if (
  87. node.openingElement.attributes
  88. && hasChildren
  89. && findJsxProp(node, 'dangerouslySetInnerHTML')
  90. ) {
  91. context.report(node, 'Only set one of `children` or `props.dangerouslySetInnerHTML`');
  92. }
  93. },
  94. CallExpression: function (node) {
  95. if (
  96. node.callee
  97. && node.callee.type === 'MemberExpression'
  98. && node.callee.property.name === 'createElement'
  99. && node.arguments.length > 1
  100. ) {
  101. let hasChildren = false;
  102. let props = node.arguments[1];
  103. if (props.type === 'Identifier') {
  104. const variable = variableUtil.variablesInScope(context).find(item => item.name === props.name);
  105. if (variable && variable.defs.length && variable.defs[0].node.init) {
  106. props = variable.defs[0].node.init;
  107. }
  108. }
  109. const dangerously = findObjectProp(props, 'dangerouslySetInnerHTML', []);
  110. if (node.arguments.length === 2) {
  111. if (findObjectProp(props, 'children', [])) {
  112. hasChildren = true;
  113. }
  114. } else {
  115. hasChildren = true;
  116. }
  117. if (dangerously && hasChildren) {
  118. context.report(node, 'Only set one of `children` or `props.dangerouslySetInnerHTML`');
  119. }
  120. }
  121. }
  122. };
  123. }
  124. };