UNPKG

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