1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | 'use strict';
|
8 |
|
9 | const has = require('has');
|
10 |
|
11 | const Components = require('../util/Components');
|
12 | const docsUrl = require('../util/docsUrl');
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 | const VOID_DOM_ELEMENTS = {
|
21 | area: true,
|
22 | base: true,
|
23 | br: true,
|
24 | col: true,
|
25 | embed: true,
|
26 | hr: true,
|
27 | img: true,
|
28 | input: true,
|
29 | keygen: true,
|
30 | link: true,
|
31 | menuitem: true,
|
32 | meta: true,
|
33 | param: true,
|
34 | source: true,
|
35 | track: true,
|
36 | wbr: true
|
37 | };
|
38 |
|
39 | function isVoidDOMElement(elementName) {
|
40 | return has(VOID_DOM_ELEMENTS, elementName);
|
41 | }
|
42 |
|
43 | function errorMessage(elementName) {
|
44 | return `Void DOM element <${elementName} /> cannot receive children.`;
|
45 | }
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | module.exports = {
|
52 | meta: {
|
53 | docs: {
|
54 | description: 'Prevent passing of children to void DOM elements (e.g. `<br />`).',
|
55 | category: 'Best Practices',
|
56 | recommended: false,
|
57 | url: docsUrl('void-dom-elements-no-children')
|
58 | },
|
59 | schema: []
|
60 | },
|
61 |
|
62 | create: Components.detect((context, components, utils) => ({
|
63 | JSXElement(node) {
|
64 | const elementName = node.openingElement.name.name;
|
65 |
|
66 | if (!isVoidDOMElement(elementName)) {
|
67 |
|
68 | return;
|
69 | }
|
70 |
|
71 | if (node.children.length > 0) {
|
72 |
|
73 | context.report({
|
74 | node,
|
75 | message: errorMessage(elementName)
|
76 | });
|
77 | }
|
78 |
|
79 | const attributes = node.openingElement.attributes;
|
80 |
|
81 | const hasChildrenAttributeOrDanger = attributes.some((attribute) => {
|
82 | if (!attribute.name) {
|
83 | return false;
|
84 | }
|
85 |
|
86 | return attribute.name.name === 'children' || attribute.name.name === 'dangerouslySetInnerHTML';
|
87 | });
|
88 |
|
89 | if (hasChildrenAttributeOrDanger) {
|
90 |
|
91 | context.report({
|
92 | node,
|
93 | message: errorMessage(elementName)
|
94 | });
|
95 | }
|
96 | },
|
97 |
|
98 | CallExpression(node) {
|
99 | if (node.callee.type !== 'MemberExpression' && node.callee.type !== 'Identifier') {
|
100 | return;
|
101 | }
|
102 |
|
103 | if (!utils.isCreateElement(node)) {
|
104 | return;
|
105 | }
|
106 |
|
107 | const args = node.arguments;
|
108 |
|
109 | if (args.length < 1) {
|
110 |
|
111 | return;
|
112 | }
|
113 |
|
114 | const elementName = args[0].value;
|
115 |
|
116 | if (!isVoidDOMElement(elementName)) {
|
117 |
|
118 | return;
|
119 | }
|
120 |
|
121 | if (args.length < 2 || args[1].type !== 'ObjectExpression') {
|
122 | return;
|
123 | }
|
124 |
|
125 | const firstChild = args[2];
|
126 | if (firstChild) {
|
127 |
|
128 | context.report({
|
129 | node,
|
130 | message: errorMessage(elementName)
|
131 | });
|
132 | }
|
133 |
|
134 | const props = args[1].properties;
|
135 |
|
136 | const hasChildrenPropOrDanger = props.some((prop) => {
|
137 | if (!prop.key) {
|
138 | return false;
|
139 | }
|
140 |
|
141 | return prop.key.name === 'children' || prop.key.name === 'dangerouslySetInnerHTML';
|
142 | });
|
143 |
|
144 | if (hasChildrenPropOrDanger) {
|
145 |
|
146 | context.report({
|
147 | node,
|
148 | message: errorMessage(elementName)
|
149 | });
|
150 | }
|
151 | }
|
152 | }))
|
153 | };
|