@lvce-editor/eslint-plugin-virtual-dom 13.2.0 → 13.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +533 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
const isPropertyNode = node => {
|
|
2
2
|
return typeof node === 'object' && node !== null && 'type' in node && node.type === 'Property' && 'key' in node && 'value' in node && 'computed' in node;
|
|
3
3
|
};
|
|
4
|
+
const isObjectExpressionNode = node => {
|
|
5
|
+
return typeof node === 'object' && node !== null && 'type' in node && node.type === 'ObjectExpression' && 'properties' in node;
|
|
6
|
+
};
|
|
7
|
+
const isArrayExpressionNode = node => {
|
|
8
|
+
return typeof node === 'object' && node !== null && 'type' in node && node.type === 'ArrayExpression' && 'elements' in node;
|
|
9
|
+
};
|
|
4
10
|
const isIdentifierNode = node => {
|
|
5
11
|
return typeof node === 'object' && node !== null && 'type' in node && node.type === 'Identifier' && 'name' in node;
|
|
6
12
|
};
|
|
@@ -13,25 +19,412 @@ const isTemplateLiteralNode = node => {
|
|
|
13
19
|
const isBinaryExpressionNode = node => {
|
|
14
20
|
return typeof node === 'object' && node !== null && 'type' in node && node.type === 'BinaryExpression' && 'operator' in node && 'left' in node && 'right' in node;
|
|
15
21
|
};
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
const isCallExpressionNode = node => {
|
|
23
|
+
return typeof node === 'object' && node !== null && 'type' in node && node.type === 'CallExpression' && 'callee' in node && 'arguments' in node;
|
|
24
|
+
};
|
|
25
|
+
const isUnaryExpressionNode = node => {
|
|
26
|
+
return typeof node === 'object' && node !== null && 'type' in node && node.type === 'UnaryExpression' && 'operator' in node && 'argument' in node;
|
|
27
|
+
};
|
|
28
|
+
const isMemberExpressionNode = node => {
|
|
29
|
+
return typeof node === 'object' && node !== null && 'type' in node && node.type === 'MemberExpression' && 'object' in node && 'property' in node && 'computed' in node;
|
|
30
|
+
};
|
|
31
|
+
const isArrowFunctionExpressionNode = node => {
|
|
32
|
+
return typeof node === 'object' && node !== null && 'type' in node && node.type === 'ArrowFunctionExpression';
|
|
33
|
+
};
|
|
34
|
+
const isFunctionExpressionNode = node => {
|
|
35
|
+
return typeof node === 'object' && node !== null && 'type' in node && node.type === 'FunctionExpression';
|
|
36
|
+
};
|
|
37
|
+
const getStaticPropertyName = property => {
|
|
38
|
+
if (property.computed) {
|
|
39
|
+
return undefined;
|
|
19
40
|
}
|
|
20
|
-
if (isIdentifierNode(
|
|
21
|
-
return
|
|
41
|
+
if (isIdentifierNode(property.key)) {
|
|
42
|
+
return property.key.name;
|
|
22
43
|
}
|
|
23
|
-
if (isLiteralNode(
|
|
24
|
-
return
|
|
44
|
+
if (isLiteralNode(property.key) && typeof property.key.value === 'string') {
|
|
45
|
+
return property.key.value;
|
|
25
46
|
}
|
|
26
|
-
return
|
|
47
|
+
return undefined;
|
|
48
|
+
};
|
|
49
|
+
const getProperty = (node, name) => {
|
|
50
|
+
return node.properties.find(property => isPropertyNode(property) && getStaticPropertyName(property) === name);
|
|
51
|
+
};
|
|
52
|
+
const hasProperty = (node, name) => {
|
|
53
|
+
return Boolean(getProperty(node, name));
|
|
54
|
+
};
|
|
55
|
+
const isVirtualDomNode = node => {
|
|
56
|
+
return isObjectExpressionNode(node) && hasProperty(node, 'type');
|
|
57
|
+
};
|
|
58
|
+
const isTextCall = node => {
|
|
59
|
+
return isCallExpressionNode(node) && isIdentifierNode(node.callee) && node.callee.name === 'text';
|
|
60
|
+
};
|
|
61
|
+
const isMemberExpressionWithProperty = (node, propertyName) => {
|
|
62
|
+
return isMemberExpressionNode(node) && !node.computed && isIdentifierNode(node.property) && node.property.name === propertyName;
|
|
63
|
+
};
|
|
64
|
+
const isNumericLiteral = node => {
|
|
65
|
+
return isLiteralNode(node) && typeof node.value === 'number';
|
|
66
|
+
};
|
|
67
|
+
const getStaticNumberValue = node => {
|
|
68
|
+
if (isNumericLiteral(node)) {
|
|
69
|
+
return node.value;
|
|
70
|
+
}
|
|
71
|
+
if (isUnaryExpressionNode(node) && node.operator === '-' && isNumericLiteral(node.argument)) {
|
|
72
|
+
return -node.argument.value;
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
};
|
|
76
|
+
const isStringLiteral = node => {
|
|
77
|
+
return isLiteralNode(node) && typeof node.value === 'string';
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const meta$9 = {
|
|
81
|
+
docs: {
|
|
82
|
+
description: 'Require an explicit role on clickable virtual-dom div nodes'
|
|
83
|
+
},
|
|
84
|
+
messages: {
|
|
85
|
+
clickableDivNeedsRole: 'Add an explicit `role` to clickable virtual-dom div nodes.'
|
|
86
|
+
},
|
|
87
|
+
type: 'problem'
|
|
88
|
+
};
|
|
89
|
+
const create$9 = context => {
|
|
90
|
+
return {
|
|
91
|
+
ObjectExpression(node) {
|
|
92
|
+
if (!isVirtualDomNode(node)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const typeProperty = getProperty(node, 'type');
|
|
96
|
+
const clickProperty = getProperty(node, 'onClick');
|
|
97
|
+
const roleProperty = getProperty(node, 'role');
|
|
98
|
+
if (!typeProperty || !clickProperty || roleProperty || !isMemberExpressionWithProperty(typeProperty.value, 'Div')) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
context.report({
|
|
102
|
+
messageId: 'clickableDivNeedsRole',
|
|
103
|
+
node: clickProperty
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const clickableDivNeedsRole = {
|
|
110
|
+
__proto__: null,
|
|
111
|
+
create: create$9,
|
|
112
|
+
meta: meta$9
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const meta$8 = {
|
|
116
|
+
docs: {
|
|
117
|
+
description: 'Disallow empty ARIA values in virtual-dom nodes'
|
|
118
|
+
},
|
|
119
|
+
messages: {
|
|
120
|
+
noEmptyAria: 'Omit empty ARIA attributes instead of using an empty string.'
|
|
121
|
+
},
|
|
122
|
+
type: 'problem'
|
|
123
|
+
};
|
|
124
|
+
const isAriaProperty = name => {
|
|
125
|
+
return /^aria[A-Z]/.test(name);
|
|
126
|
+
};
|
|
127
|
+
const create$8 = context => {
|
|
128
|
+
return {
|
|
129
|
+
ObjectExpression(node) {
|
|
130
|
+
if (!isVirtualDomNode(node)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
for (const property of node.properties) {
|
|
134
|
+
if (!isPropertyNode(property)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const name = getStaticPropertyName(property);
|
|
138
|
+
if (!name || !isAriaProperty(name) || !isStringLiteral(property.value) || property.value.value !== '') {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
context.report({
|
|
142
|
+
messageId: 'noEmptyAria',
|
|
143
|
+
node: property.value
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const noEmptyAria = {
|
|
151
|
+
__proto__: null,
|
|
152
|
+
create: create$8,
|
|
153
|
+
meta: meta$8
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const meta$7 = {
|
|
157
|
+
docs: {
|
|
158
|
+
description: 'Disallow empty className values in virtual-dom nodes'
|
|
159
|
+
},
|
|
160
|
+
messages: {
|
|
161
|
+
noEmptyClassName: 'Omit `className` instead of using an empty string.'
|
|
162
|
+
},
|
|
163
|
+
type: 'problem'
|
|
164
|
+
};
|
|
165
|
+
const create$7 = context => {
|
|
166
|
+
return {
|
|
167
|
+
ObjectExpression(node) {
|
|
168
|
+
if (!isVirtualDomNode(node)) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const classNameProperty = getProperty(node, 'className');
|
|
172
|
+
if (!classNameProperty || !isStringLiteral(classNameProperty.value) || classNameProperty.value.value !== '') {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
context.report({
|
|
176
|
+
messageId: 'noEmptyClassName',
|
|
177
|
+
node: classNameProperty.value
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const noEmptyClassName = {
|
|
184
|
+
__proto__: null,
|
|
185
|
+
create: create$7,
|
|
186
|
+
meta: meta$7
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const meta$6 = {
|
|
190
|
+
docs: {
|
|
191
|
+
description: 'Disallow inline event handlers in virtual-dom nodes'
|
|
192
|
+
},
|
|
193
|
+
messages: {
|
|
194
|
+
noInlineEventHandlers: 'Use a registered event listener id instead of an inline event handler.'
|
|
195
|
+
},
|
|
196
|
+
type: 'problem'
|
|
197
|
+
};
|
|
198
|
+
const isEventPropertyName = name => {
|
|
199
|
+
return /^on[A-Z]/.test(name);
|
|
200
|
+
};
|
|
201
|
+
const isInlineEventHandler = node => {
|
|
202
|
+
return isStringLiteral(node) || isTemplateLiteralNode(node) || isArrowFunctionExpressionNode(node) || isFunctionExpressionNode(node);
|
|
203
|
+
};
|
|
204
|
+
const create$6 = context => {
|
|
205
|
+
return {
|
|
206
|
+
ObjectExpression(node) {
|
|
207
|
+
if (!isVirtualDomNode(node)) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
for (const property of node.properties) {
|
|
211
|
+
if (!isPropertyNode(property)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const name = getStaticPropertyName(property);
|
|
215
|
+
if (!name || !isEventPropertyName(name) || !isInlineEventHandler(property.value)) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
context.report({
|
|
219
|
+
messageId: 'noInlineEventHandlers',
|
|
220
|
+
node: property.value
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const noInlineEventHandlers = {
|
|
228
|
+
__proto__: null,
|
|
229
|
+
create: create$6,
|
|
230
|
+
meta: meta$6
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const meta$5 = {
|
|
234
|
+
docs: {
|
|
235
|
+
description: 'Disallow inline style attributes in virtual-dom nodes'
|
|
236
|
+
},
|
|
237
|
+
messages: {
|
|
238
|
+
noInlineStyle: 'Use class names or generated CSS instead of inline `style`.'
|
|
239
|
+
},
|
|
240
|
+
type: 'problem'
|
|
241
|
+
};
|
|
242
|
+
const create$5 = context => {
|
|
243
|
+
return {
|
|
244
|
+
ObjectExpression(node) {
|
|
245
|
+
if (!isVirtualDomNode(node)) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const styleProperty = getProperty(node, 'style');
|
|
249
|
+
if (!styleProperty) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
context.report({
|
|
253
|
+
messageId: 'noInlineStyle',
|
|
254
|
+
node: styleProperty
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const noInlineStyle = {
|
|
261
|
+
__proto__: null,
|
|
262
|
+
create: create$5,
|
|
263
|
+
meta: meta$5
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const meta$4 = {
|
|
267
|
+
docs: {
|
|
268
|
+
description: 'Disallow object-like attribute values in virtual-dom nodes'
|
|
269
|
+
},
|
|
270
|
+
messages: {
|
|
271
|
+
noObjectAttributeValues: 'Avoid object, array, or function attribute values because virtual-dom diffing compares attributes by reference.'
|
|
272
|
+
},
|
|
273
|
+
type: 'problem'
|
|
274
|
+
};
|
|
275
|
+
const ignoredProperties = new Set(['childCount', 'type']);
|
|
276
|
+
const isObjectLikeAttributeValue = node => {
|
|
277
|
+
return isObjectExpressionNode(node) || isArrayExpressionNode(node) || isArrowFunctionExpressionNode(node) || isFunctionExpressionNode(node);
|
|
278
|
+
};
|
|
279
|
+
const create$4 = context => {
|
|
280
|
+
return {
|
|
281
|
+
ObjectExpression(node) {
|
|
282
|
+
if (!isVirtualDomNode(node)) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
for (const property of node.properties) {
|
|
286
|
+
if (!isPropertyNode(property)) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const name = getStaticPropertyName(property);
|
|
290
|
+
if (!name || ignoredProperties.has(name) || !isObjectLikeAttributeValue(property.value)) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
context.report({
|
|
294
|
+
messageId: 'noObjectAttributeValues',
|
|
295
|
+
node: property.value
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const noObjectAttributeValues = {
|
|
303
|
+
__proto__: null,
|
|
304
|
+
create: create$4,
|
|
305
|
+
meta: meta$4
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const meta$3 = {
|
|
309
|
+
docs: {
|
|
310
|
+
description: 'Disallow raw string children in virtual-dom arrays'
|
|
311
|
+
},
|
|
312
|
+
messages: {
|
|
313
|
+
noRawTextChildren: 'Use `text(...)` instead of a raw string in virtual-dom arrays.'
|
|
314
|
+
},
|
|
315
|
+
type: 'problem'
|
|
316
|
+
};
|
|
317
|
+
const isVirtualDomArray = node => {
|
|
318
|
+
return isArrayExpressionNode(node) && node.elements.some(element => isVirtualDomNode(element) || isTextCall(element));
|
|
319
|
+
};
|
|
320
|
+
const create$3 = context => {
|
|
321
|
+
return {
|
|
322
|
+
ArrayExpression(node) {
|
|
323
|
+
if (!isVirtualDomArray(node)) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
for (const element of node.elements) {
|
|
327
|
+
if (!isStringLiteral(element)) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
context.report({
|
|
331
|
+
messageId: 'noRawTextChildren',
|
|
332
|
+
node: element
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const noRawTextChildren = {
|
|
340
|
+
__proto__: null,
|
|
341
|
+
create: create$3,
|
|
342
|
+
meta: meta$3
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const meta$2 = {
|
|
346
|
+
docs: {
|
|
347
|
+
description: 'Prefer virtual-dom constants over raw type, role, aria boolean, and tabIndex values'
|
|
348
|
+
},
|
|
349
|
+
messages: {
|
|
350
|
+
preferAriaBooleanConstant: 'Use an ARIA boolean constant instead of a raw string.',
|
|
351
|
+
preferRoleConstant: 'Use `AriaRoles.*` instead of a raw role string.',
|
|
352
|
+
preferTabIndexConstant: 'Use `TabIndex.*` instead of a raw tabIndex number.',
|
|
353
|
+
preferTypeConstant: 'Use `VirtualDomElements.*` instead of a raw element type number.'
|
|
354
|
+
},
|
|
355
|
+
type: 'suggestion'
|
|
356
|
+
};
|
|
357
|
+
const isAriaBooleanProperty = name => {
|
|
358
|
+
return /^aria[A-Z]/.test(name);
|
|
359
|
+
};
|
|
360
|
+
const create$2 = context => {
|
|
361
|
+
return {
|
|
362
|
+
ObjectExpression(node) {
|
|
363
|
+
if (!isVirtualDomNode(node)) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
for (const property of node.properties) {
|
|
367
|
+
if (!isPropertyNode(property)) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const name = getStaticPropertyName(property);
|
|
371
|
+
if (!name) {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (name === 'type' && isNumericLiteral(property.value)) {
|
|
375
|
+
context.report({
|
|
376
|
+
messageId: 'preferTypeConstant',
|
|
377
|
+
node: property.value
|
|
378
|
+
});
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (name === 'role' && isStringLiteral(property.value)) {
|
|
382
|
+
context.report({
|
|
383
|
+
messageId: 'preferRoleConstant',
|
|
384
|
+
node: property.value
|
|
385
|
+
});
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (name === 'tabIndex' && isNumericLiteral(property.value)) {
|
|
389
|
+
context.report({
|
|
390
|
+
messageId: 'preferTabIndexConstant',
|
|
391
|
+
node: property.value
|
|
392
|
+
});
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (isAriaBooleanProperty(name) && isStringLiteral(property.value) && (property.value.value === 'true' || property.value.value === 'false')) {
|
|
396
|
+
context.report({
|
|
397
|
+
messageId: 'preferAriaBooleanConstant',
|
|
398
|
+
node: property.value
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const preferConstants = {
|
|
407
|
+
__proto__: null,
|
|
408
|
+
create: create$2,
|
|
409
|
+
meta: meta$2
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const isClassNameKey = node => {
|
|
413
|
+
return getStaticPropertyName(node) === 'className';
|
|
27
414
|
};
|
|
28
415
|
const isStringLiteralWithSpace = node => {
|
|
29
|
-
return
|
|
416
|
+
return isStringLiteral(node) && /\s/.test(node.value);
|
|
417
|
+
};
|
|
418
|
+
const isStaticMultiClassName = node => {
|
|
419
|
+
return isStringLiteral(node) && /\s/.test(node.value) && node.value.trim().split(/\s+/).length > 1;
|
|
30
420
|
};
|
|
31
421
|
const hasTemplateClassSeparator = node => {
|
|
32
422
|
return node.quasis.some(quasi => /\s/.test(quasi.value.raw));
|
|
33
423
|
};
|
|
34
424
|
const isManualClassNameConcatenation = node => {
|
|
425
|
+
if (isStaticMultiClassName(node)) {
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
35
428
|
if (isTemplateLiteralNode(node)) {
|
|
36
429
|
return node.expressions.length > 0 && hasTemplateClassSeparator(node);
|
|
37
430
|
}
|
|
@@ -40,7 +433,7 @@ const isManualClassNameConcatenation = node => {
|
|
|
40
433
|
}
|
|
41
434
|
return isStringLiteralWithSpace(node.left) || isStringLiteralWithSpace(node.right) || isManualClassNameConcatenation(node.left) || isManualClassNameConcatenation(node.right);
|
|
42
435
|
};
|
|
43
|
-
const meta = {
|
|
436
|
+
const meta$1 = {
|
|
44
437
|
docs: {
|
|
45
438
|
description: 'Prefer mergeClassNames for composing virtual-dom className values'
|
|
46
439
|
},
|
|
@@ -49,7 +442,7 @@ const meta = {
|
|
|
49
442
|
},
|
|
50
443
|
type: 'suggestion'
|
|
51
444
|
};
|
|
52
|
-
const create = context => {
|
|
445
|
+
const create$1 = context => {
|
|
53
446
|
return {
|
|
54
447
|
Property(node) {
|
|
55
448
|
if (!isPropertyNode(node) || !isClassNameKey(node) || !isManualClassNameConcatenation(node.value)) {
|
|
@@ -64,6 +457,115 @@ const create = context => {
|
|
|
64
457
|
};
|
|
65
458
|
|
|
66
459
|
const preferMergeClassNames = {
|
|
460
|
+
__proto__: null,
|
|
461
|
+
create: create$1,
|
|
462
|
+
meta: meta$1
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const meta = {
|
|
466
|
+
docs: {
|
|
467
|
+
description: 'Validate statically analyzable virtual-dom childCount values'
|
|
468
|
+
},
|
|
469
|
+
messages: {
|
|
470
|
+
invalidChildCount: '`childCount` must be a non-negative integer.',
|
|
471
|
+
validChildCount: '`childCount` declares more children than this static virtual-dom array contains.'
|
|
472
|
+
},
|
|
473
|
+
type: 'problem'
|
|
474
|
+
};
|
|
475
|
+
const getChildCountNode = node => {
|
|
476
|
+
if (!isVirtualDomNode(node)) {
|
|
477
|
+
return undefined;
|
|
478
|
+
}
|
|
479
|
+
return getProperty(node, 'childCount')?.value;
|
|
480
|
+
};
|
|
481
|
+
const getStaticChildCount = node => {
|
|
482
|
+
if (isTextCall(node)) {
|
|
483
|
+
return 0;
|
|
484
|
+
}
|
|
485
|
+
if (!isVirtualDomNode(node)) {
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
488
|
+
const childCountProperty = getProperty(node, 'childCount');
|
|
489
|
+
if (!childCountProperty) {
|
|
490
|
+
return 0;
|
|
491
|
+
}
|
|
492
|
+
const childCount = getStaticNumberValue(childCountProperty.value);
|
|
493
|
+
if (childCount === undefined) {
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
496
|
+
return childCount;
|
|
497
|
+
};
|
|
498
|
+
const isValidChildCount = childCount => {
|
|
499
|
+
return Number.isSafeInteger(childCount) && childCount >= 0;
|
|
500
|
+
};
|
|
501
|
+
const isAnalyzableVirtualDomElement = node => {
|
|
502
|
+
return isTextCall(node) || isVirtualDomNode(node);
|
|
503
|
+
};
|
|
504
|
+
const computeSubtreeEnd = (elements, index) => {
|
|
505
|
+
const childCount = getStaticChildCount(elements[index]);
|
|
506
|
+
if (childCount === undefined || !isValidChildCount(childCount)) {
|
|
507
|
+
return undefined;
|
|
508
|
+
}
|
|
509
|
+
let next = index + 1;
|
|
510
|
+
for (let childIndex = 0; childIndex < childCount; childIndex++) {
|
|
511
|
+
if (next >= elements.length) {
|
|
512
|
+
return undefined;
|
|
513
|
+
}
|
|
514
|
+
const childEnd = computeSubtreeEnd(elements, next);
|
|
515
|
+
if (childEnd === undefined) {
|
|
516
|
+
return undefined;
|
|
517
|
+
}
|
|
518
|
+
next = childEnd;
|
|
519
|
+
}
|
|
520
|
+
return next;
|
|
521
|
+
};
|
|
522
|
+
const hasImpossibleChildCount = (elements, index) => {
|
|
523
|
+
const childCount = getStaticChildCount(elements[index]);
|
|
524
|
+
if (childCount === undefined || !isValidChildCount(childCount)) {
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
let next = index + 1;
|
|
528
|
+
for (let childIndex = 0; childIndex < childCount; childIndex++) {
|
|
529
|
+
if (next >= elements.length) {
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
const childEnd = computeSubtreeEnd(elements, next);
|
|
533
|
+
if (childEnd === undefined) {
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
next = childEnd;
|
|
537
|
+
}
|
|
538
|
+
return false;
|
|
539
|
+
};
|
|
540
|
+
const create = context => {
|
|
541
|
+
return {
|
|
542
|
+
ArrayExpression(node) {
|
|
543
|
+
if (!isArrayExpressionNode(node) || !node.elements.some(isVirtualDomNode) || !node.elements.every(isAnalyzableVirtualDomElement)) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
for (let index = 0; index < node.elements.length; index++) {
|
|
547
|
+
const element = node.elements[index];
|
|
548
|
+
const childCount = getStaticChildCount(element);
|
|
549
|
+
const childCountNode = getChildCountNode(element);
|
|
550
|
+
if (childCount !== undefined && !isValidChildCount(childCount) && childCountNode) {
|
|
551
|
+
context.report({
|
|
552
|
+
messageId: 'invalidChildCount',
|
|
553
|
+
node: childCountNode
|
|
554
|
+
});
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
if (childCountNode && hasImpossibleChildCount(node.elements, index)) {
|
|
558
|
+
context.report({
|
|
559
|
+
messageId: 'validChildCount',
|
|
560
|
+
node: childCountNode
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
const validChildCount = {
|
|
67
569
|
__proto__: null,
|
|
68
570
|
create,
|
|
69
571
|
meta
|
|
@@ -76,7 +578,16 @@ const plugin = {
|
|
|
76
578
|
version: '0.0.1'
|
|
77
579
|
},
|
|
78
580
|
rules: {
|
|
79
|
-
'
|
|
581
|
+
'clickable-div-needs-role': clickableDivNeedsRole,
|
|
582
|
+
'no-empty-aria': noEmptyAria,
|
|
583
|
+
'no-empty-class-name': noEmptyClassName,
|
|
584
|
+
'no-inline-event-handlers': noInlineEventHandlers,
|
|
585
|
+
'no-inline-style': noInlineStyle,
|
|
586
|
+
'no-object-attribute-values': noObjectAttributeValues,
|
|
587
|
+
'no-raw-text-children': noRawTextChildren,
|
|
588
|
+
'prefer-constants': preferConstants,
|
|
589
|
+
'prefer-merge-class-names': preferMergeClassNames,
|
|
590
|
+
'valid-child-count': validChildCount
|
|
80
591
|
}
|
|
81
592
|
};
|
|
82
593
|
const recommended = [{
|
|
@@ -85,7 +596,16 @@ const recommended = [{
|
|
|
85
596
|
'virtual-dom': plugin
|
|
86
597
|
},
|
|
87
598
|
rules: {
|
|
88
|
-
'virtual-dom/
|
|
599
|
+
'virtual-dom/clickable-div-needs-role': 'error',
|
|
600
|
+
'virtual-dom/no-empty-aria': 'error',
|
|
601
|
+
'virtual-dom/no-empty-class-name': 'error',
|
|
602
|
+
'virtual-dom/no-inline-event-handlers': 'error',
|
|
603
|
+
'virtual-dom/no-inline-style': 'error',
|
|
604
|
+
'virtual-dom/no-object-attribute-values': 'error',
|
|
605
|
+
'virtual-dom/no-raw-text-children': 'error',
|
|
606
|
+
'virtual-dom/prefer-constants': 'error',
|
|
607
|
+
'virtual-dom/prefer-merge-class-names': 'error',
|
|
608
|
+
'virtual-dom/valid-child-count': 'error'
|
|
89
609
|
}
|
|
90
610
|
}];
|
|
91
611
|
|