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