@lvce-editor/eslint-plugin-virtual-dom 17.2.0 → 17.3.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.
Files changed (2) hide show
  1. package/dist/index.js +541 -65
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -52,8 +52,19 @@ const getProperty = (node, name) => {
52
52
  const hasProperty = (node, name) => {
53
53
  return Boolean(getProperty(node, name));
54
54
  };
55
+ const virtualDomElementNames = new Set(['A', 'Abbr', 'Article', 'Aside', 'Audio', 'Br', 'Button', 'Cite', 'Code', 'Col', 'ColGroup', 'Data', 'Dd', 'Del', 'Div', 'Dl', 'Dt', 'Figcaption', 'Figure', 'Footer', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Header', 'Hr', 'I', 'Img', 'Input', 'Ins', 'Kbd', 'Label', 'Li', 'Nav', 'Ol', 'Option', 'P', 'Pre', 'Reference', 'Root', 'Search', 'Section', 'Select', 'Span', 'Table', 'TBody', 'Td', 'Text', 'TextArea', 'Tfoot', 'Th', 'THead', 'Time', 'Tr', 'Ul', 'Video']);
56
+ const getVirtualDomElementName = node => {
57
+ if (!isMemberExpressionNode(node) || node.computed || !isIdentifierNode(node.object) || !isIdentifierNode(node.property) || !node.object.name.endsWith('Elements') || !virtualDomElementNames.has(node.property.name)) {
58
+ return undefined;
59
+ }
60
+ return node.property.name;
61
+ };
55
62
  const isVirtualDomNode = node => {
56
- return isObjectExpressionNode(node) && hasProperty(node, 'type');
63
+ if (!isObjectExpressionNode(node)) {
64
+ return false;
65
+ }
66
+ const typeProperty = getProperty(node, 'type');
67
+ return Boolean(typeProperty && (hasProperty(node, 'childCount') || getVirtualDomElementName(typeProperty.value)));
57
68
  };
58
69
  const isTextCall = node => {
59
70
  return isCallExpressionNode(node) && isIdentifierNode(node.callee) && node.callee.name === 'text';
@@ -77,7 +88,67 @@ const isStringLiteral = node => {
77
88
  return isLiteralNode(node) && typeof node.value === 'string';
78
89
  };
79
90
 
80
- const meta$f = {
91
+ const controlsRequiringDirectName = new Set(['Input', 'TextArea']);
92
+ const controlsAllowingChildren = new Set(['A', 'Button']);
93
+ const hasDirectName = node => {
94
+ return Boolean(getProperty(node, 'ariaLabel') || getProperty(node, 'ariaLabelledBy') || getProperty(node, 'title'));
95
+ };
96
+ const meta$p = {
97
+ docs: {
98
+ description: 'Require a statically determinable accessible name for virtual-dom controls'
99
+ },
100
+ messages: {
101
+ accessibleControlName: 'Add an accessible name to this virtual-dom `{{name}}` control.'
102
+ },
103
+ type: 'problem'
104
+ };
105
+ const create$p = context => {
106
+ return {
107
+ ObjectExpression(node) {
108
+ if (!isVirtualDomNode(node) || hasDirectName(node)) {
109
+ return;
110
+ }
111
+ const typeProperty = getProperty(node, 'type');
112
+ const elementName = typeProperty && getVirtualDomElementName(typeProperty.value);
113
+ if (!elementName) {
114
+ return;
115
+ }
116
+ if (controlsRequiringDirectName.has(elementName)) {
117
+ context.report({
118
+ data: {
119
+ name: elementName
120
+ },
121
+ messageId: 'accessibleControlName',
122
+ node
123
+ });
124
+ return;
125
+ }
126
+ if (!controlsAllowingChildren.has(elementName)) {
127
+ return;
128
+ }
129
+ const childCountProperty = getProperty(node, 'childCount');
130
+ const childCount = childCountProperty && getStaticNumberValue(childCountProperty.value);
131
+ if (childCount !== 0) {
132
+ return;
133
+ }
134
+ context.report({
135
+ data: {
136
+ name: elementName
137
+ },
138
+ messageId: 'accessibleControlName',
139
+ node
140
+ });
141
+ }
142
+ };
143
+ };
144
+
145
+ const accessibleControlName = {
146
+ __proto__: null,
147
+ create: create$p,
148
+ meta: meta$p
149
+ };
150
+
151
+ const meta$o = {
81
152
  docs: {
82
153
  description: 'Require an explicit role on clickable virtual-dom div nodes'
83
154
  },
@@ -86,7 +157,7 @@ const meta$f = {
86
157
  },
87
158
  type: 'problem'
88
159
  };
89
- const create$f = context => {
160
+ const create$o = context => {
90
161
  return {
91
162
  ObjectExpression(node) {
92
163
  if (!isVirtualDomNode(node)) {
@@ -108,11 +179,11 @@ const create$f = context => {
108
179
 
109
180
  const clickableDivNeedsRole = {
110
181
  __proto__: null,
111
- create: create$f,
112
- meta: meta$f
182
+ create: create$o,
183
+ meta: meta$o
113
184
  };
114
185
 
115
- const meta$e = {
186
+ const meta$n = {
116
187
  docs: {
117
188
  description: 'Require static virtual-dom nodes created inside functions to be hoisted to module scope'
118
189
  },
@@ -223,7 +294,7 @@ const isStaticExpression = (sourceCode, node) => {
223
294
  return false;
224
295
  }
225
296
  };
226
- const create$e = context => {
297
+ const create$n = context => {
227
298
  return {
228
299
  ObjectExpression(node) {
229
300
  if (!isVirtualDomElement(node)) {
@@ -248,11 +319,52 @@ const create$e = context => {
248
319
 
249
320
  const hoistStaticNodes = {
250
321
  __proto__: null,
251
- create: create$e,
252
- meta: meta$e
322
+ create: create$n,
323
+ meta: meta$n
253
324
  };
254
325
 
255
- const meta$d = {
326
+ const meta$m = {
327
+ docs: {
328
+ description: 'Disallow conditional spreads in virtual-dom arrays'
329
+ },
330
+ messages: {
331
+ noConditionalSpread: 'Move conditional virtual-dom children into a helper function and spread the helper result.'
332
+ },
333
+ type: 'suggestion'
334
+ };
335
+ const isConditionalSpread = node => {
336
+ return node?.type === 'SpreadElement' && node.argument.type === 'ConditionalExpression';
337
+ };
338
+ const isVirtualDomArray$1 = node => {
339
+ return isArrayExpressionNode(node) && node.elements.some(element => isVirtualDomNode(element) || isTextCall(element));
340
+ };
341
+ const addsVirtualDomChildren = spread => {
342
+ return isVirtualDomArray$1(spread.argument.consequent) || isVirtualDomArray$1(spread.argument.alternate);
343
+ };
344
+ const create$m = context => {
345
+ return {
346
+ ArrayExpression(node) {
347
+ const conditionalSpreads = node.elements.filter(isConditionalSpread);
348
+ if (!isVirtualDomArray$1(node) && !conditionalSpreads.some(addsVirtualDomChildren)) {
349
+ return;
350
+ }
351
+ for (const spread of conditionalSpreads) {
352
+ context.report({
353
+ messageId: 'noConditionalSpread',
354
+ node: spread
355
+ });
356
+ }
357
+ }
358
+ };
359
+ };
360
+
361
+ const noConditionalSpread = {
362
+ __proto__: null,
363
+ create: create$m,
364
+ meta: meta$m
365
+ };
366
+
367
+ const meta$l = {
256
368
  docs: {
257
369
  description: 'Disallow empty ARIA values in virtual-dom nodes'
258
370
  },
@@ -264,7 +376,7 @@ const meta$d = {
264
376
  const isAriaProperty = name => {
265
377
  return /^aria[A-Z]/.test(name);
266
378
  };
267
- const create$d = context => {
379
+ const create$l = context => {
268
380
  return {
269
381
  ObjectExpression(node) {
270
382
  if (!isVirtualDomNode(node)) {
@@ -289,11 +401,11 @@ const create$d = context => {
289
401
 
290
402
  const noEmptyAria = {
291
403
  __proto__: null,
292
- create: create$d,
293
- meta: meta$d
404
+ create: create$l,
405
+ meta: meta$l
294
406
  };
295
407
 
296
- const meta$c = {
408
+ const meta$k = {
297
409
  docs: {
298
410
  description: 'Disallow empty className values in virtual-dom nodes'
299
411
  },
@@ -302,7 +414,7 @@ const meta$c = {
302
414
  },
303
415
  type: 'problem'
304
416
  };
305
- const create$c = context => {
417
+ const create$k = context => {
306
418
  return {
307
419
  ObjectExpression(node) {
308
420
  if (!isVirtualDomNode(node)) {
@@ -322,11 +434,11 @@ const create$c = context => {
322
434
 
323
435
  const noEmptyClassName = {
324
436
  __proto__: null,
325
- create: create$c,
326
- meta: meta$c
437
+ create: create$k,
438
+ meta: meta$k
327
439
  };
328
440
 
329
- const meta$b = {
441
+ const meta$j = {
330
442
  docs: {
331
443
  description: 'Disallow inline event handlers in virtual-dom nodes'
332
444
  },
@@ -341,7 +453,7 @@ const isEventPropertyName = name => {
341
453
  const isInlineEventHandler = node => {
342
454
  return isStringLiteral(node) || isTemplateLiteralNode(node) || isArrowFunctionExpressionNode(node) || isFunctionExpressionNode(node);
343
455
  };
344
- const create$b = context => {
456
+ const create$j = context => {
345
457
  return {
346
458
  ObjectExpression(node) {
347
459
  if (!isVirtualDomNode(node)) {
@@ -366,11 +478,11 @@ const create$b = context => {
366
478
 
367
479
  const noInlineEventHandlers = {
368
480
  __proto__: null,
369
- create: create$b,
370
- meta: meta$b
481
+ create: create$j,
482
+ meta: meta$j
371
483
  };
372
484
 
373
- const meta$a = {
485
+ const meta$i = {
374
486
  docs: {
375
487
  description: 'Disallow inline style attributes in virtual-dom nodes'
376
488
  },
@@ -379,7 +491,7 @@ const meta$a = {
379
491
  },
380
492
  type: 'problem'
381
493
  };
382
- const create$a = context => {
494
+ const create$i = context => {
383
495
  return {
384
496
  ObjectExpression(node) {
385
497
  if (!isVirtualDomNode(node)) {
@@ -399,11 +511,59 @@ const create$a = context => {
399
511
 
400
512
  const noInlineStyle = {
401
513
  __proto__: null,
402
- create: create$a,
403
- meta: meta$a
514
+ create: create$i,
515
+ meta: meta$i
404
516
  };
405
517
 
406
- const meta$9 = {
518
+ const ignoredProperties$1 = new Set(['childCount', 'type']);
519
+ const isOptionalExpression = node => {
520
+ return typeof node === 'object' && node !== null && ('type' in node && node.type === 'ChainExpression' || 'optional' in node && node.optional === true);
521
+ };
522
+ const isNullish = node => {
523
+ return isLiteralNode(node) && node.value === null || isIdentifierNode(node) && node.name === 'undefined' || isOptionalExpression(node);
524
+ };
525
+ const meta$h = {
526
+ docs: {
527
+ description: 'Disallow nullish virtual-dom attribute values'
528
+ },
529
+ messages: {
530
+ noNullishAttributeValue: 'Omit `{{name}}` instead of assigning a nullish value.'
531
+ },
532
+ type: 'problem'
533
+ };
534
+ const create$h = context => {
535
+ return {
536
+ ObjectExpression(node) {
537
+ if (!isVirtualDomNode(node)) {
538
+ return;
539
+ }
540
+ for (const property of node.properties) {
541
+ if (!isPropertyNode(property)) {
542
+ continue;
543
+ }
544
+ const name = getStaticPropertyName(property);
545
+ if (!name || ignoredProperties$1.has(name) || !isNullish(property.value)) {
546
+ continue;
547
+ }
548
+ context.report({
549
+ data: {
550
+ name
551
+ },
552
+ messageId: 'noNullishAttributeValue',
553
+ node: property.value
554
+ });
555
+ }
556
+ }
557
+ };
558
+ };
559
+
560
+ const noNullishAttributeValues = {
561
+ __proto__: null,
562
+ create: create$h,
563
+ meta: meta$h
564
+ };
565
+
566
+ const meta$g = {
407
567
  docs: {
408
568
  description: 'Disallow object-like attribute values in virtual-dom nodes'
409
569
  },
@@ -416,7 +576,7 @@ const ignoredProperties = new Set(['childCount', 'type']);
416
576
  const isObjectLikeAttributeValue = node => {
417
577
  return isObjectExpressionNode(node) || isArrayExpressionNode(node) || isArrowFunctionExpressionNode(node) || isFunctionExpressionNode(node);
418
578
  };
419
- const create$9 = context => {
579
+ const create$g = context => {
420
580
  return {
421
581
  ObjectExpression(node) {
422
582
  if (!isVirtualDomNode(node)) {
@@ -441,11 +601,45 @@ const create$9 = context => {
441
601
 
442
602
  const noObjectAttributeValues = {
443
603
  __proto__: null,
444
- create: create$9,
445
- meta: meta$9
604
+ create: create$g,
605
+ meta: meta$g
446
606
  };
447
607
 
448
- const meta$8 = {
608
+ const meta$f = {
609
+ docs: {
610
+ description: 'Disallow positive virtual-dom tabIndex values'
611
+ },
612
+ messages: {
613
+ noPositiveTabIndex: 'Use a `tabIndex` of 0 or -1 instead of a positive value.'
614
+ },
615
+ type: 'problem'
616
+ };
617
+ const create$f = context => {
618
+ return {
619
+ ObjectExpression(node) {
620
+ if (!isVirtualDomNode(node)) {
621
+ return;
622
+ }
623
+ const tabIndexProperty = getProperty(node, 'tabIndex');
624
+ const tabIndex = tabIndexProperty && getStaticNumberValue(tabIndexProperty.value);
625
+ if (!tabIndexProperty || tabIndex === undefined || tabIndex <= 0) {
626
+ return;
627
+ }
628
+ context.report({
629
+ messageId: 'noPositiveTabIndex',
630
+ node: tabIndexProperty.value
631
+ });
632
+ }
633
+ };
634
+ };
635
+
636
+ const noPositiveTabIndex = {
637
+ __proto__: null,
638
+ create: create$f,
639
+ meta: meta$f
640
+ };
641
+
642
+ const meta$e = {
449
643
  docs: {
450
644
  description: 'Disallow raw string children in virtual-dom arrays'
451
645
  },
@@ -457,7 +651,7 @@ const meta$8 = {
457
651
  const isVirtualDomArray = node => {
458
652
  return isArrayExpressionNode(node) && node.elements.some(element => isVirtualDomNode(element) || isTextCall(element));
459
653
  };
460
- const create$8 = context => {
654
+ const create$e = context => {
461
655
  return {
462
656
  ArrayExpression(node) {
463
657
  if (!isVirtualDomArray(node)) {
@@ -478,11 +672,11 @@ const create$8 = context => {
478
672
 
479
673
  const noRawTextChildren = {
480
674
  __proto__: null,
481
- create: create$8,
482
- meta: meta$8
675
+ create: create$e,
676
+ meta: meta$e
483
677
  };
484
678
 
485
- const meta$7 = {
679
+ const meta$d = {
486
680
  docs: {
487
681
  description: 'Require each virtual-dom event listener to call its dedicated handler'
488
682
  },
@@ -491,7 +685,7 @@ const meta$7 = {
491
685
  },
492
686
  type: 'problem'
493
687
  };
494
- const create$7 = context => {
688
+ const create$d = context => {
495
689
  return {
496
690
  ObjectExpression(node) {
497
691
  if (!isObjectExpressionNode(node)) {
@@ -521,16 +715,50 @@ const create$7 = context => {
521
715
 
522
716
  const noSharedEventListenerHandlers = {
523
717
  __proto__: null,
524
- create: create$7,
525
- meta: meta$7
718
+ create: create$d,
719
+ meta: meta$d
526
720
  };
527
721
 
528
- const meta$6 = {
722
+ const meta$c = {
723
+ docs: {
724
+ description: 'Prefer constants for single literal virtual-dom class names'
725
+ },
726
+ messages: {
727
+ preferClassNameConstant: 'Use a `ClassNames.*` constant instead of a literal `className`.'
728
+ },
729
+ type: 'suggestion'
730
+ };
731
+ const create$c = context => {
732
+ return {
733
+ ObjectExpression(node) {
734
+ if (!isVirtualDomNode(node)) {
735
+ return;
736
+ }
737
+ const classNameProperty = getProperty(node, 'className');
738
+ if (!classNameProperty || !isStringLiteral(classNameProperty.value) || classNameProperty.value.value === '' || /\s/.test(classNameProperty.value.value)) {
739
+ return;
740
+ }
741
+ context.report({
742
+ messageId: 'preferClassNameConstant',
743
+ node: classNameProperty.value
744
+ });
745
+ }
746
+ };
747
+ };
748
+
749
+ const preferClassNameConstants = {
750
+ __proto__: null,
751
+ create: create$c,
752
+ meta: meta$c
753
+ };
754
+
755
+ const meta$b = {
529
756
  docs: {
530
757
  description: 'Prefer virtual-dom constants over raw type, role, aria boolean, and tabIndex values'
531
758
  },
532
759
  messages: {
533
760
  preferAriaBooleanConstant: 'Use an ARIA boolean constant instead of a raw string.',
761
+ preferEventListenerConstant: 'Use a named event listener constant instead of a raw numeric id.',
534
762
  preferRoleConstant: 'Use `AriaRoles.*` instead of a raw role string.',
535
763
  preferTabIndexConstant: 'Use `TabIndex.*` instead of a raw tabIndex number.',
536
764
  preferTypeConstant: 'Use `VirtualDomElements.*` instead of a raw element type number.'
@@ -540,7 +768,7 @@ const meta$6 = {
540
768
  const isAriaBooleanProperty = name => {
541
769
  return /^aria[A-Z]/.test(name);
542
770
  };
543
- const create$6 = context => {
771
+ const create$b = context => {
544
772
  return {
545
773
  ObjectExpression(node) {
546
774
  if (!isVirtualDomNode(node)) {
@@ -575,6 +803,13 @@ const create$6 = context => {
575
803
  });
576
804
  continue;
577
805
  }
806
+ if (/^on[A-Z]/.test(name) && isNumericLiteral(property.value)) {
807
+ context.report({
808
+ messageId: 'preferEventListenerConstant',
809
+ node: property.value
810
+ });
811
+ continue;
812
+ }
578
813
  if (isAriaBooleanProperty(name) && isStringLiteral(property.value) && (property.value.value === 'true' || property.value.value === 'false')) {
579
814
  context.report({
580
815
  messageId: 'preferAriaBooleanConstant',
@@ -588,11 +823,11 @@ const create$6 = context => {
588
823
 
589
824
  const preferConstants = {
590
825
  __proto__: null,
591
- create: create$6,
592
- meta: meta$6
826
+ create: create$b,
827
+ meta: meta$b
593
828
  };
594
829
 
595
- const meta$5 = {
830
+ const meta$a = {
596
831
  docs: {
597
832
  description: 'Require Intl.DateTimeFormat instances to be created lazily'
598
833
  },
@@ -607,7 +842,7 @@ const isFunctionNode = node => {
607
842
  const isDateTimeFormat = node => {
608
843
  return node.type === 'MemberExpression' && !node.computed && node.object.type === 'Identifier' && node.object.name === 'Intl' && node.property.type === 'Identifier' && node.property.name === 'DateTimeFormat';
609
844
  };
610
- const create$5 = context => {
845
+ const create$a = context => {
611
846
  const isCreatedLazily = node => {
612
847
  return context.sourceCode.getAncestors(node).some(isFunctionNode);
613
848
  };
@@ -628,8 +863,8 @@ const create$5 = context => {
628
863
 
629
864
  const preferLazyDateTimeFormat = {
630
865
  __proto__: null,
631
- create: create$5,
632
- meta: meta$5
866
+ create: create$a,
867
+ meta: meta$a
633
868
  };
634
869
 
635
870
  const isClassNameKey = node => {
@@ -677,7 +912,7 @@ const getParent = node => {
677
912
  }
678
913
  return undefined;
679
914
  };
680
- const meta$4 = {
915
+ const meta$9 = {
681
916
  docs: {
682
917
  description: 'Prefer mergeClassNames for composing virtual-dom className values'
683
918
  },
@@ -686,7 +921,7 @@ const meta$4 = {
686
921
  },
687
922
  type: 'suggestion'
688
923
  };
689
- const create$4 = context => {
924
+ const create$9 = context => {
690
925
  const reportNamespacedClassNameComposition = node => {
691
926
  if (!isManualClassNameConcatenation(node) || !hasClassNamesReference(node)) {
692
927
  return;
@@ -717,11 +952,11 @@ const create$4 = context => {
717
952
 
718
953
  const preferMergeClassNames = {
719
954
  __proto__: null,
720
- create: create$4,
721
- meta: meta$4
955
+ create: create$9,
956
+ meta: meta$9
722
957
  };
723
958
 
724
- const meta$3 = {
959
+ const meta$8 = {
725
960
  docs: {
726
961
  description: 'Prefer destructuring state properties before use'
727
962
  },
@@ -748,7 +983,7 @@ const isWriteExpression = node => {
748
983
  }
749
984
  return parent.type === 'UnaryExpression' && parent.operator === 'delete' && parent.argument === node;
750
985
  };
751
- const create$3 = context => {
986
+ const create$8 = context => {
752
987
  return {
753
988
  MemberExpression(node) {
754
989
  if (node.computed) {
@@ -776,11 +1011,11 @@ const create$3 = context => {
776
1011
 
777
1012
  const preferStateDestructuring = {
778
1013
  __proto__: null,
779
- create: create$3,
780
- meta: meta$3
1014
+ create: create$8,
1015
+ meta: meta$8
781
1016
  };
782
1017
 
783
- const meta$2 = {
1018
+ const meta$7 = {
784
1019
  docs: {
785
1020
  description: 'Require event listener options for handlers with specific browser event behavior'
786
1021
  },
@@ -790,7 +1025,7 @@ const meta$2 = {
790
1025
  type: 'problem'
791
1026
  };
792
1027
  const listenersRequiringPreventDefault = new Set(['handleDragOver', 'handleDrop']);
793
- const create$2 = context => {
1028
+ const create$7 = context => {
794
1029
  return {
795
1030
  ObjectExpression(node) {
796
1031
  if (!isObjectExpressionNode(node) || !getProperty(node, 'params')) {
@@ -817,11 +1052,44 @@ const create$2 = context => {
817
1052
 
818
1053
  const requireEventListenerOptions = {
819
1054
  __proto__: null,
820
- create: create$2,
821
- meta: meta$2
1055
+ create: create$7,
1056
+ meta: meta$7
822
1057
  };
823
1058
 
824
- const meta$1 = {
1059
+ const meta$6 = {
1060
+ docs: {
1061
+ description: 'Require alt text on virtual-dom image nodes'
1062
+ },
1063
+ messages: {
1064
+ requireImageAlt: 'Add an `alt` attribute to this virtual-dom image.'
1065
+ },
1066
+ type: 'problem'
1067
+ };
1068
+ const create$6 = context => {
1069
+ return {
1070
+ ObjectExpression(node) {
1071
+ if (!isVirtualDomNode(node)) {
1072
+ return;
1073
+ }
1074
+ const typeProperty = getProperty(node, 'type');
1075
+ if (!typeProperty || getVirtualDomElementName(typeProperty.value) !== 'Img' || getProperty(node, 'alt')) {
1076
+ return;
1077
+ }
1078
+ context.report({
1079
+ messageId: 'requireImageAlt',
1080
+ node
1081
+ });
1082
+ }
1083
+ };
1084
+ };
1085
+
1086
+ const requireImageAlt = {
1087
+ __proto__: null,
1088
+ create: create$6,
1089
+ meta: meta$6
1090
+ };
1091
+
1092
+ const meta$5 = {
825
1093
  docs: {
826
1094
  description: 'Require secure attributes on virtual-dom links'
827
1095
  },
@@ -835,7 +1103,7 @@ const hasStringPropertyValue = (node, name, value) => {
835
1103
  const property = getProperty(node, name);
836
1104
  return Boolean(property && isStringLiteral(property.value) && property.value.value === value);
837
1105
  };
838
- const create$1 = context => {
1106
+ const create$5 = context => {
839
1107
  return {
840
1108
  ObjectExpression(node) {
841
1109
  if (!isVirtualDomNode(node)) {
@@ -864,11 +1132,97 @@ const create$1 = context => {
864
1132
 
865
1133
  const secureLinks = {
866
1134
  __proto__: null,
867
- create: create$1,
868
- meta: meta$1
1135
+ create: create$5,
1136
+ meta: meta$5
869
1137
  };
870
1138
 
871
- const meta = {
1139
+ const validAriaProperties = new Set(['ariaActivedescendant', 'ariaAtomic', 'ariaAutoComplete', 'ariaBrailleLabel', 'ariaBrailleRoleDescription', 'ariaBusy', 'ariaChecked', 'ariaColCount', 'ariaColIndex', 'ariaColIndexText', 'ariaColSpan', 'ariaControls', 'ariaCurrent', 'ariaDescribedBy', 'ariaDescription', 'ariaDetails', 'ariaDisabled', 'ariaErrorMessage', 'ariaExpanded', 'ariaFlowTo', 'ariaHasPopup', 'ariaHidden', 'ariaInvalid', 'ariaKeyShortcuts', 'ariaLabel', 'ariaLabelledBy', 'ariaLevel', 'ariaLive', 'ariaModal', 'ariaMultiLine', 'ariaMultiSelectable', 'ariaOrientation', 'ariaOwns', 'ariaPlaceholder', 'ariaPosInSet', 'ariaPressed', 'ariaReadOnly', 'ariaRelevant', 'ariaRequired', 'ariaRoleDescription', 'ariaRowCount', 'ariaRowIndex', 'ariaRowIndexText', 'ariaRowSpan', 'ariaSelected', 'ariaSetSize', 'ariaSort', 'ariaValueMax', 'ariaValueMin', 'ariaValueNow', 'ariaValueText']);
1140
+ const meta$4 = {
1141
+ docs: {
1142
+ description: 'Validate virtual-dom ARIA property names'
1143
+ },
1144
+ messages: {
1145
+ validAriaProperty: '`{{name}}` is not a valid virtual-dom ARIA property.'
1146
+ },
1147
+ type: 'problem'
1148
+ };
1149
+ const create$4 = context => {
1150
+ return {
1151
+ ObjectExpression(node) {
1152
+ if (!isVirtualDomNode(node)) {
1153
+ return;
1154
+ }
1155
+ for (const property of node.properties) {
1156
+ if (!isPropertyNode(property)) {
1157
+ continue;
1158
+ }
1159
+ const name = getStaticPropertyName(property);
1160
+ if (!name?.startsWith('aria') || validAriaProperties.has(name)) {
1161
+ continue;
1162
+ }
1163
+ context.report({
1164
+ data: {
1165
+ name
1166
+ },
1167
+ messageId: 'validAriaProperty',
1168
+ node: property
1169
+ });
1170
+ }
1171
+ }
1172
+ };
1173
+ };
1174
+
1175
+ const validAriaProperties$1 = {
1176
+ __proto__: null,
1177
+ create: create$4,
1178
+ meta: meta$4
1179
+ };
1180
+
1181
+ const allowedValues = new Map([['ariaAtomic', new Set(['false', 'true'])], ['ariaAutoComplete', new Set(['both', 'inline', 'list', 'none'])], ['ariaBusy', new Set(['false', 'true'])], ['ariaChecked', new Set(['false', 'mixed', 'true'])], ['ariaCurrent', new Set(['date', 'false', 'location', 'page', 'step', 'time', 'true'])], ['ariaDisabled', new Set(['false', 'true'])], ['ariaExpanded', new Set(['false', 'true'])], ['ariaHasPopup', new Set(['dialog', 'false', 'grid', 'listbox', 'menu', 'tree', 'true'])], ['ariaHidden', new Set(['false', 'true'])], ['ariaInvalid', new Set(['false', 'grammar', 'spelling', 'true'])], ['ariaLive', new Set(['assertive', 'off', 'polite'])], ['ariaModal', new Set(['false', 'true'])], ['ariaMultiLine', new Set(['false', 'true'])], ['ariaMultiSelectable', new Set(['false', 'true'])], ['ariaOrientation', new Set(['horizontal', 'undefined', 'vertical'])], ['ariaPressed', new Set(['false', 'mixed', 'true'])], ['ariaReadOnly', new Set(['false', 'true'])], ['ariaRelevant', new Set(['additions', 'additions removals', 'additions text', 'all', 'removals', 'removals additions', 'removals text', 'text', 'text additions', 'text removals'])], ['ariaRequired', new Set(['false', 'true'])], ['ariaSelected', new Set(['false', 'true'])], ['ariaSort', new Set(['ascending', 'descending', 'none', 'other'])]]);
1182
+ const meta$3 = {
1183
+ docs: {
1184
+ description: 'Validate literal virtual-dom ARIA values'
1185
+ },
1186
+ messages: {
1187
+ validAriaValue: '`{{value}}` is not a valid value for `{{name}}`.'
1188
+ },
1189
+ type: 'problem'
1190
+ };
1191
+ const create$3 = context => {
1192
+ return {
1193
+ ObjectExpression(node) {
1194
+ if (!isVirtualDomNode(node)) {
1195
+ return;
1196
+ }
1197
+ for (const property of node.properties) {
1198
+ if (!isPropertyNode(property)) {
1199
+ continue;
1200
+ }
1201
+ const name = getStaticPropertyName(property);
1202
+ const values = name && allowedValues.get(name);
1203
+ if (!name || !values || !isStringLiteral(property.value) || values.has(property.value.value)) {
1204
+ continue;
1205
+ }
1206
+ context.report({
1207
+ data: {
1208
+ name,
1209
+ value: property.value.value
1210
+ },
1211
+ messageId: 'validAriaValue',
1212
+ node: property.value
1213
+ });
1214
+ }
1215
+ }
1216
+ };
1217
+ };
1218
+
1219
+ const validAriaValues = {
1220
+ __proto__: null,
1221
+ create: create$3,
1222
+ meta: meta$3
1223
+ };
1224
+
1225
+ const meta$2 = {
872
1226
  docs: {
873
1227
  description: 'Validate statically analyzable virtual-dom childCount values'
874
1228
  },
@@ -943,7 +1297,7 @@ const hasImpossibleChildCount = (elements, index) => {
943
1297
  }
944
1298
  return false;
945
1299
  };
946
- const create = context => {
1300
+ const create$2 = context => {
947
1301
  return {
948
1302
  ArrayExpression(node) {
949
1303
  if (!isArrayExpressionNode(node) || !node.elements.some(isVirtualDomNode) || !node.elements.every(isAnalyzableVirtualDomElement)) {
@@ -972,6 +1326,103 @@ const create = context => {
972
1326
  };
973
1327
 
974
1328
  const validChildCount = {
1329
+ __proto__: null,
1330
+ create: create$2,
1331
+ meta: meta$2
1332
+ };
1333
+
1334
+ const supportedEventProperties = new Set(['onBeforeInput', 'onBlur', 'onChange', 'onClick', 'onContextMenu', 'onDblClick', 'onDragEnd', 'onDragEnter', 'onDragLeave', 'onDragOver', 'onDragStart', 'onDrop', 'onFocus', 'onFocusIn', 'onFocusOut', 'onInput', 'onKeyDown', 'onKeyUp', 'onMouseDown', 'onMouseMove', 'onMouseOut', 'onMouseOver', 'onMouseUp', 'onPointerDown', 'onPointerMove', 'onPointerOut', 'onPointerOver', 'onScroll', 'onSelectionChange', 'onSubmit', 'onWheel']);
1335
+ const meta$1 = {
1336
+ docs: {
1337
+ description: 'Require virtual-dom event properties supported by the renderer'
1338
+ },
1339
+ messages: {
1340
+ validEventProperty: '`{{name}}` is not a supported virtual-dom event property.'
1341
+ },
1342
+ type: 'problem'
1343
+ };
1344
+ const create$1 = context => {
1345
+ return {
1346
+ ObjectExpression(node) {
1347
+ if (!isVirtualDomNode(node)) {
1348
+ return;
1349
+ }
1350
+ for (const property of node.properties) {
1351
+ if (!isPropertyNode(property)) {
1352
+ continue;
1353
+ }
1354
+ const name = getStaticPropertyName(property);
1355
+ if (!name?.startsWith('on') || supportedEventProperties.has(name)) {
1356
+ continue;
1357
+ }
1358
+ context.report({
1359
+ data: {
1360
+ name
1361
+ },
1362
+ messageId: 'validEventProperty',
1363
+ node: property
1364
+ });
1365
+ }
1366
+ }
1367
+ };
1368
+ };
1369
+
1370
+ const validEventProperties = {
1371
+ __proto__: null,
1372
+ create: create$1,
1373
+ meta: meta$1
1374
+ };
1375
+
1376
+ const voidElements = new Set(['Br', 'Col', 'Hr', 'Img', 'Input']);
1377
+ const meta = {
1378
+ docs: {
1379
+ description: 'Require canonical virtual-dom node shapes'
1380
+ },
1381
+ messages: {
1382
+ elementTextProperty: 'Use a `text(...)` child instead of a `text` property on an element node.',
1383
+ missingChildCount: 'Add an explicit `childCount` to this virtual-dom node.',
1384
+ voidElementChildren: '`VirtualDomElements.{{name}}` must have a `childCount` of 0.'
1385
+ },
1386
+ type: 'problem'
1387
+ };
1388
+ const create = context => {
1389
+ return {
1390
+ ObjectExpression(node) {
1391
+ if (!isObjectExpressionNode(node)) {
1392
+ return;
1393
+ }
1394
+ const typeProperty = getProperty(node, 'type');
1395
+ const elementName = typeProperty && getVirtualDomElementName(typeProperty.value);
1396
+ if (!typeProperty || !elementName) {
1397
+ return;
1398
+ }
1399
+ const childCountProperty = getProperty(node, 'childCount');
1400
+ if (!childCountProperty) {
1401
+ context.report({
1402
+ messageId: 'missingChildCount',
1403
+ node
1404
+ });
1405
+ } else if (voidElements.has(elementName) && getStaticNumberValue(childCountProperty.value) !== 0) {
1406
+ context.report({
1407
+ data: {
1408
+ name: elementName
1409
+ },
1410
+ messageId: 'voidElementChildren',
1411
+ node: childCountProperty
1412
+ });
1413
+ }
1414
+ const textProperty = getProperty(node, 'text');
1415
+ if (textProperty && elementName !== 'Text') {
1416
+ context.report({
1417
+ messageId: 'elementTextProperty',
1418
+ node: textProperty
1419
+ });
1420
+ }
1421
+ }
1422
+ };
1423
+ };
1424
+
1425
+ const validNodeShape = {
975
1426
  __proto__: null,
976
1427
  create,
977
1428
  meta
@@ -984,22 +1435,32 @@ const plugin = {
984
1435
  version: '0.0.1'
985
1436
  },
986
1437
  rules: {
1438
+ 'accessible-control-name': accessibleControlName,
987
1439
  'clickable-div-needs-role': clickableDivNeedsRole,
988
1440
  'hoist-static-nodes': hoistStaticNodes,
1441
+ 'no-conditional-spread': noConditionalSpread,
989
1442
  'no-empty-aria': noEmptyAria,
990
1443
  'no-empty-class-name': noEmptyClassName,
991
1444
  'no-inline-event-handlers': noInlineEventHandlers,
992
1445
  'no-inline-style': noInlineStyle,
1446
+ 'no-nullish-attribute-values': noNullishAttributeValues,
993
1447
  'no-object-attribute-values': noObjectAttributeValues,
1448
+ 'no-positive-tab-index': noPositiveTabIndex,
994
1449
  'no-raw-text-children': noRawTextChildren,
995
1450
  'no-shared-event-listener-handlers': noSharedEventListenerHandlers,
1451
+ 'prefer-class-name-constants': preferClassNameConstants,
996
1452
  'prefer-constants': preferConstants,
997
1453
  'prefer-lazy-date-time-format': preferLazyDateTimeFormat,
998
1454
  'prefer-merge-class-names': preferMergeClassNames,
999
1455
  'prefer-state-destructuring': preferStateDestructuring,
1000
1456
  'require-event-listener-options': requireEventListenerOptions,
1457
+ 'require-image-alt': requireImageAlt,
1001
1458
  'secure-links': secureLinks,
1002
- 'valid-child-count': validChildCount
1459
+ 'valid-aria-properties': validAriaProperties$1,
1460
+ 'valid-aria-values': validAriaValues,
1461
+ 'valid-child-count': validChildCount,
1462
+ 'valid-event-properties': validEventProperties,
1463
+ 'valid-node-shape': validNodeShape
1003
1464
  }
1004
1465
  };
1005
1466
  const recommended = [{
@@ -1010,6 +1471,7 @@ const recommended = [{
1010
1471
  rules: {
1011
1472
  'virtual-dom/clickable-div-needs-role': 'error',
1012
1473
  'virtual-dom/hoist-static-nodes': 'error',
1474
+ 'virtual-dom/no-conditional-spread': 'error',
1013
1475
  'virtual-dom/no-empty-aria': 'error',
1014
1476
  'virtual-dom/no-empty-class-name': 'error',
1015
1477
  'virtual-dom/no-inline-event-handlers': 'error',
@@ -1031,5 +1493,19 @@ const recommended = [{
1031
1493
  'virtual-dom/hoist-static-nodes': 'off'
1032
1494
  }
1033
1495
  }];
1496
+ const strict = [...recommended, {
1497
+ files: ['**/*.{js,mjs,cjs,ts,mts,cts}'],
1498
+ rules: {
1499
+ 'virtual-dom/accessible-control-name': 'error',
1500
+ 'virtual-dom/no-nullish-attribute-values': 'error',
1501
+ 'virtual-dom/no-positive-tab-index': 'error',
1502
+ 'virtual-dom/prefer-class-name-constants': 'error',
1503
+ 'virtual-dom/require-image-alt': 'error',
1504
+ 'virtual-dom/valid-aria-properties': 'error',
1505
+ 'virtual-dom/valid-aria-values': 'error',
1506
+ 'virtual-dom/valid-event-properties': 'error',
1507
+ 'virtual-dom/valid-node-shape': 'error'
1508
+ }
1509
+ }];
1034
1510
 
1035
- export { recommended as default };
1511
+ export { recommended as default, strict };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/eslint-plugin-virtual-dom",
3
- "version": "17.2.0",
3
+ "version": "17.3.0",
4
4
  "main": "dist/index.js",
5
5
  "repository": {
6
6
  "type": "git",