@drskillissue/ganko 0.2.83 → 0.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.
@@ -202,7 +202,7 @@ var RULES = [
202
202
  "description": "Disallow removing outline without explicit focus-visible replacement.",
203
203
  "fixable": false,
204
204
  "category": "css-a11y",
205
- "plugin": "css",
205
+ "plugin": "compilation",
206
206
  "messages": {
207
207
  "missingFocusVisible": "Focus outline removed without matching `:focus-visible` replacement."
208
208
  }
@@ -210,10 +210,10 @@ var RULES = [
210
210
  {
211
211
  "id": "css-policy-contrast",
212
212
  "severity": "warn",
213
- "description": "Enforce minimum contrast ratio between foreground and background colors per accessibility policy.",
213
+ "description": "Enforce minimum contrast ratio per accessibility policy.",
214
214
  "fixable": false,
215
215
  "category": "css-a11y",
216
- "plugin": "css",
216
+ "plugin": "compilation",
217
217
  "messages": {
218
218
  "insufficientContrast": "Contrast ratio `{{ratio}}:1` between `{{fg}}` and `{{bg}}` is below the minimum `{{min}}:1` for `{{textSize}}` text in policy `{{policy}}`."
219
219
  }
@@ -221,14 +221,15 @@ var RULES = [
221
221
  {
222
222
  "id": "css-policy-spacing",
223
223
  "severity": "warn",
224
- "description": "Enforce minimum letter-spacing, word-spacing, and paragraph spacing per accessibility policy.",
224
+ "description": "Enforce minimum spacing per accessibility policy.",
225
225
  "fixable": false,
226
226
  "category": "css-a11y",
227
- "plugin": "css",
227
+ "plugin": "compilation",
228
228
  "messages": {
229
229
  "letterSpacingTooSmall": "Letter spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`.",
230
230
  "wordSpacingTooSmall": "Word spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`.",
231
- "paragraphSpacingTooSmall": "Paragraph spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` ({{minMultiplier}}\xD7 font-size) for policy `{{policy}}`."
231
+ "paragraphSpacingTooSmall": "Paragraph spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` ({{minMultiplier}}\xD7 font-size) for policy `{{policy}}`.",
232
+ "touchTargetTooSmall": "`{{property}}: {{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for interactive elements in policy `{{policy}}`."
232
233
  }
233
234
  },
234
235
  {
@@ -237,7 +238,7 @@ var RULES = [
237
238
  "description": "Enforce minimum font sizes and line heights per accessibility policy.",
238
239
  "fixable": false,
239
240
  "category": "css-a11y",
240
- "plugin": "css",
241
+ "plugin": "compilation",
241
242
  "messages": {
242
243
  "fontTooSmall": "Font size `{{value}}` ({{resolved}}px) is below the `{{context}}` minimum of `{{min}}px` for policy `{{policy}}`.",
243
244
  "lineHeightTooSmall": "Line height `{{value}}` is below the `{{context}}` minimum of `{{min}}` for policy `{{policy}}`."
@@ -249,7 +250,7 @@ var RULES = [
249
250
  "description": "Require reduced-motion override for animated selectors.",
250
251
  "fixable": false,
251
252
  "category": "css-a11y",
252
- "plugin": "css",
253
+ "plugin": "compilation",
253
254
  "messages": {
254
255
  "missingReducedMotion": "Animated selector `{{selector}}` lacks prefers-reduced-motion override."
255
256
  }
@@ -260,7 +261,7 @@ var RULES = [
260
261
  "description": "Enforce minimum interactive element sizes per accessibility policy via resolved layout signals.",
261
262
  "fixable": false,
262
263
  "category": "css-a11y",
263
- "plugin": "cross-file",
264
+ "plugin": "compilation",
264
265
  "messages": {
265
266
  "heightTooSmall": "`{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.",
266
267
  "widthTooSmall": "`{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.",
@@ -275,7 +276,7 @@ var RULES = [
275
276
  "description": "Disallow transitions on discrete CSS properties.",
276
277
  "fixable": false,
277
278
  "category": "css-animation",
278
- "plugin": "css",
279
+ "plugin": "compilation",
279
280
  "messages": {
280
281
  "discreteTransition": "Property `{{property}}` is discrete and should not be transitioned."
281
282
  }
@@ -286,7 +287,7 @@ var RULES = [
286
287
  "description": "Disallow empty @keyframes rules.",
287
288
  "fixable": false,
288
289
  "category": "css-animation",
289
- "plugin": "css",
290
+ "plugin": "compilation",
290
291
  "messages": {
291
292
  "emptyKeyframes": "@keyframes `{{name}}` has no effective keyframes."
292
293
  }
@@ -297,7 +298,7 @@ var RULES = [
297
298
  "description": "Disallow animating layout-affecting properties.",
298
299
  "fixable": false,
299
300
  "category": "css-animation",
300
- "plugin": "css",
301
+ "plugin": "compilation",
301
302
  "messages": {
302
303
  "avoidLayoutAnimation": "Avoid animating layout property `{{property}}`. Prefer transform or opacity to reduce layout thrashing."
303
304
  }
@@ -305,10 +306,10 @@ var RULES = [
305
306
  {
306
307
  "id": "no-transition-all",
307
308
  "severity": "warn",
308
- "description": "Disallow transition: all and transition-property: all.",
309
+ "description": "Disallow transition: all.",
309
310
  "fixable": false,
310
311
  "category": "css-animation",
311
- "plugin": "css",
312
+ "plugin": "compilation",
312
313
  "messages": {
313
314
  "avoidTransitionAll": "Avoid `transition: all`. Transition specific properties to reduce unnecessary style and paint work."
314
315
  }
@@ -319,7 +320,7 @@ var RULES = [
319
320
  "description": "Disallow animation names that do not match declared keyframes.",
320
321
  "fixable": false,
321
322
  "category": "css-animation",
322
- "plugin": "css",
323
+ "plugin": "compilation",
323
324
  "messages": {
324
325
  "unknownAnimationName": "Animation name `{{name}}` in `{{property}}` does not match any declared @keyframes."
325
326
  }
@@ -330,7 +331,7 @@ var RULES = [
330
331
  "description": "Disallow unused @keyframes declarations.",
331
332
  "fixable": false,
332
333
  "category": "css-animation",
333
- "plugin": "css",
334
+ "plugin": "compilation",
334
335
  "messages": {
335
336
  "unusedKeyframes": "@keyframes `{{name}}` is never referenced by animation declarations."
336
337
  }
@@ -341,7 +342,7 @@ var RULES = [
341
342
  "description": "Disallow duplicate declarations of the same property within a single rule block.",
342
343
  "fixable": false,
343
344
  "category": "css-cascade",
344
- "plugin": "css",
345
+ "plugin": "compilation",
345
346
  "messages": {
346
347
  "overriddenWithinRule": "Declaration `{{property}}` is overridden later in the same rule. Keep one final declaration per property."
347
348
  }
@@ -352,7 +353,7 @@ var RULES = [
352
353
  "description": "Disallow conflicting declarations in partially overlapping media queries.",
353
354
  "fixable": false,
354
355
  "category": "css-cascade",
355
- "plugin": "css",
356
+ "plugin": "compilation",
356
357
  "messages": {
357
358
  "mediaOverlapConflict": "Overlapping media queries set different `{{property}}` values for `{{selector}}` in the same overlap range."
358
359
  }
@@ -363,7 +364,7 @@ var RULES = [
363
364
  "description": "Disallow lower-specificity selectors after higher-specificity selectors for the same property.",
364
365
  "fixable": false,
365
366
  "category": "css-cascade",
366
- "plugin": "css",
367
+ "plugin": "compilation",
367
368
  "messages": {
368
369
  "descendingSpecificity": "Lower-specificity selector `{{laterSelector}}` appears after `{{earlierSelector}}` for `{{property}}`, creating brittle cascade behavior."
369
370
  }
@@ -374,7 +375,7 @@ var RULES = [
374
375
  "description": "Disallow source-order assumptions that are inverted by layer precedence.",
375
376
  "fixable": false,
376
377
  "category": "css-cascade",
377
- "plugin": "css",
378
+ "plugin": "compilation",
378
379
  "messages": {
379
380
  "layerOrderInversion": "Declaration for `{{property}}` in selector `{{selector}}` appears later but is overridden by an earlier declaration due to @layer precedence."
380
381
  }
@@ -385,7 +386,7 @@ var RULES = [
385
386
  "description": "Disallow declarations that are deterministically overridden in the same selector context.",
386
387
  "fixable": false,
387
388
  "category": "css-cascade",
388
- "plugin": "css",
389
+ "plugin": "compilation",
389
390
  "messages": {
390
391
  "redundantOverride": "Declaration `{{property}}` is always overridden later by the same selector in the same cascade context."
391
392
  }
@@ -396,7 +397,7 @@ var RULES = [
396
397
  "description": "Detect CSS classes that are never referenced by static JSX class attributes.",
397
398
  "fixable": false,
398
399
  "category": "css-jsx",
399
- "plugin": "cross-file",
400
+ "plugin": "compilation",
400
401
  "messages": {
401
402
  "unreferencedClass": "CSS class '{{className}}' is defined but not referenced by static JSX class attributes"
402
403
  }
@@ -407,7 +408,7 @@ var RULES = [
407
408
  "description": "Require classList values to be boolean-like expressions.",
408
409
  "fixable": false,
409
410
  "category": "css-jsx",
410
- "plugin": "cross-file",
411
+ "plugin": "compilation",
411
412
  "messages": {
412
413
  "nonBooleanValue": "classList value for `{{name}}` must be boolean."
413
414
  }
@@ -418,7 +419,7 @@ var RULES = [
418
419
  "description": "Disallow passing accessor references directly as classList values.",
419
420
  "fixable": false,
420
421
  "category": "css-jsx",
421
- "plugin": "cross-file",
422
+ "plugin": "compilation",
422
423
  "messages": {
423
424
  "accessorReference": "Signal accessor `{{name}}` must be called in classList value (use {{name}}())."
424
425
  }
@@ -429,7 +430,7 @@ var RULES = [
429
430
  "description": "Disallow classList entries with constant true/false values.",
430
431
  "fixable": false,
431
432
  "category": "css-jsx",
432
- "plugin": "cross-file",
433
+ "plugin": "compilation",
433
434
  "messages": {
434
435
  "constantEntry": "classList entry `{{name}}: {{value}}` is constant; move it to static class."
435
436
  }
@@ -440,7 +441,7 @@ var RULES = [
440
441
  "description": "Require classList keys to be static and non-computed.",
441
442
  "fixable": false,
442
443
  "category": "css-jsx",
443
- "plugin": "cross-file",
444
+ "plugin": "compilation",
444
445
  "messages": {
445
446
  "nonStaticKey": "classList key must be statically known for reliable class mapping."
446
447
  }
@@ -451,7 +452,7 @@ var RULES = [
451
452
  "description": "Flag classList-driven class toggles that map to layout-affecting CSS geometry changes.",
452
453
  "fixable": false,
453
454
  "category": "css-jsx",
454
- "plugin": "cross-file",
455
+ "plugin": "compilation",
455
456
  "messages": {
456
457
  "classListGeometryToggle": "classList toggles '{{className}}', and matching CSS changes layout-affecting '{{property}}', which can cause CLS."
457
458
  }
@@ -462,7 +463,7 @@ var RULES = [
462
463
  "description": "Require stable parent size and positioning for fill-image component usage.",
463
464
  "fixable": false,
464
465
  "category": "css-jsx",
465
- "plugin": "cross-file",
466
+ "plugin": "compilation",
466
467
  "messages": {
467
468
  "unsizedFillParent": "Fill-image component '{{component}}' is inside a parent without stable size/position; add parent sizing (height/min-height/aspect-ratio) and non-static position to avoid CLS."
468
469
  }
@@ -473,7 +474,7 @@ var RULES = [
473
474
  "description": "Require consistent intrinsic aspect ratios across <picture> sources and fallback image.",
474
475
  "fixable": false,
475
476
  "category": "css-jsx",
476
- "plugin": "cross-file",
477
+ "plugin": "compilation",
477
478
  "messages": {
478
479
  "inconsistentPictureRatio": "`<picture>` source ratio {{sourceRatio}} differs from fallback img ratio {{imgRatio}}, which can cause reserved-space mismatch and CLS."
479
480
  }
@@ -484,7 +485,7 @@ var RULES = [
484
485
  "description": "Flag dynamic inline style values on layout-sensitive properties that can trigger CLS.",
485
486
  "fixable": false,
486
487
  "category": "css-jsx",
487
- "plugin": "cross-file",
488
+ "plugin": "compilation",
488
489
  "messages": {
489
490
  "unstableLayoutStyleToggle": "Dynamic style value for '{{property}}' can toggle layout geometry at runtime and cause CLS."
490
491
  }
@@ -495,7 +496,7 @@ var RULES = [
495
496
  "description": "Disallow duplicate class tokens between class and classList on the same JSX element.",
496
497
  "fixable": false,
497
498
  "category": "css-jsx",
498
- "plugin": "cross-file",
499
+ "plugin": "compilation",
499
500
  "messages": {
500
501
  "duplicateClassToken": "Class token `{{name}}` appears in both class and classList."
501
502
  }
@@ -506,7 +507,7 @@ var RULES = [
506
507
  "description": "Detect undefined CSS class names in JSX",
507
508
  "fixable": false,
508
509
  "category": "css-jsx",
509
- "plugin": "cross-file",
510
+ "plugin": "compilation",
510
511
  "messages": {
511
512
  "undefinedClass": "CSS class '{{className}}' is not defined in project CSS files"
512
513
  }
@@ -517,7 +518,7 @@ var RULES = [
517
518
  "description": "Require kebab-case keys in JSX style object literals.",
518
519
  "fixable": false,
519
520
  "category": "css-jsx",
520
- "plugin": "cross-file",
521
+ "plugin": "compilation",
521
522
  "messages": {
522
523
  "kebabStyleKey": "Style key `{{name}}` should be `{{kebab}}` in Solid style objects."
523
524
  }
@@ -528,7 +529,7 @@ var RULES = [
528
529
  "description": "Disallow function values in JSX style objects.",
529
530
  "fixable": false,
530
531
  "category": "css-jsx",
531
- "plugin": "cross-file",
532
+ "plugin": "compilation",
532
533
  "messages": {
533
534
  "functionStyleValue": "Style value for `{{name}}` is a function; pass computed value instead."
534
535
  }
@@ -539,7 +540,7 @@ var RULES = [
539
540
  "description": "Detect inline style custom properties that are never consumed by CSS var() references.",
540
541
  "fixable": false,
541
542
  "category": "css-jsx",
542
- "plugin": "cross-file",
543
+ "plugin": "compilation",
543
544
  "messages": {
544
545
  "unusedInlineVar": "Inline custom property `{{name}}` is never read via var({{name}})."
545
546
  }
@@ -550,7 +551,7 @@ var RULES = [
550
551
  "description": "Enforce accessibility policy thresholds on inline JSX style objects.",
551
552
  "fixable": false,
552
553
  "category": "css-jsx",
553
- "plugin": "cross-file",
554
+ "plugin": "compilation",
554
555
  "messages": {
555
556
  "fontTooSmall": "Inline style `{{prop}}: {{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for policy `{{policy}}`.",
556
557
  "lineHeightTooSmall": "Inline style `line-height: {{value}}` is below the minimum `{{min}}` for policy `{{policy}}`.",
@@ -565,7 +566,7 @@ var RULES = [
565
566
  "description": "Disallow keyframe animations that mutate layout-affecting properties and can trigger CLS.",
566
567
  "fixable": false,
567
568
  "category": "css-layout",
568
- "plugin": "cross-file",
569
+ "plugin": "compilation",
569
570
  "messages": {
570
571
  "animationLayoutProperty": "Animation '{{animation}}' mutates layout-affecting '{{property}}', which can trigger CLS. Prefer transform/opacity or reserve geometry."
571
572
  }
@@ -576,7 +577,7 @@ var RULES = [
576
577
  "description": "Disallow conditional box-sizing mode toggles when box chrome contributes to geometry shifts.",
577
578
  "fixable": false,
578
579
  "category": "css-layout",
579
- "plugin": "cross-file",
580
+ "plugin": "compilation",
580
581
  "messages": {
581
582
  "boxSizingToggleWithChrome": "Conditional `box-sizing` toggle on '{{tag}}' combines with non-zero padding/border, which can shift layout and trigger CLS."
582
583
  }
@@ -587,7 +588,7 @@ var RULES = [
587
588
  "description": "Disallow conditional display collapse in flow without reserved geometry.",
588
589
  "fixable": false,
589
590
  "category": "css-layout",
590
- "plugin": "cross-file",
591
+ "plugin": "compilation",
591
592
  "messages": {
592
593
  "conditionalDisplayCollapse": "Conditional display sets '{{display}}' on '{{tag}}' without stable reserved space, which can collapse/expand layout and cause CLS."
593
594
  }
@@ -598,7 +599,7 @@ var RULES = [
598
599
  "description": "Disallow conditional non-zero block-axis offsets that can trigger layout shifts.",
599
600
  "fixable": false,
600
601
  "category": "css-layout",
601
- "plugin": "cross-file",
602
+ "plugin": "compilation",
602
603
  "messages": {
603
604
  "conditionalOffsetShift": "Conditional style applies non-zero '{{property}}' offset ({{value}}), which can cause layout shifts when conditions toggle."
604
605
  }
@@ -609,7 +610,7 @@ var RULES = [
609
610
  "description": "Disallow conditional white-space wrapping mode toggles that can trigger CLS.",
610
611
  "fixable": false,
611
612
  "category": "css-layout",
612
- "plugin": "cross-file",
613
+ "plugin": "compilation",
613
614
  "messages": {
614
615
  "conditionalWhiteSpaceShift": "Conditional white-space '{{whiteSpace}}' on '{{tag}}' can reflow text and shift siblings; keep wrapping behavior stable or reserve geometry."
615
616
  }
@@ -620,7 +621,7 @@ var RULES = [
620
621
  "description": "Require intrinsic size reservation when using content-visibility auto to avoid late layout shifts.",
621
622
  "fixable": false,
622
623
  "category": "css-layout",
623
- "plugin": "cross-file",
624
+ "plugin": "compilation",
624
625
  "messages": {
625
626
  "missingIntrinsicSize": "`content-visibility: auto` on '{{tag}}' lacks intrinsic size reservation (`contain-intrinsic-size`/min-height/height/aspect-ratio), which can cause CLS."
626
627
  }
@@ -631,7 +632,7 @@ var RULES = [
631
632
  "description": "Require reserved block space for dynamic content containers to avoid layout shifts.",
632
633
  "fixable": false,
633
634
  "category": "css-layout",
634
- "plugin": "cross-file",
635
+ "plugin": "compilation",
635
636
  "messages": {
636
637
  "dynamicSlotNoReservedSpace": "Dynamic content container '{{tag}}' does not reserve block space (min-height/height/aspect-ratio/contain-intrinsic-size), which can cause CLS."
637
638
  }
@@ -642,7 +643,7 @@ var RULES = [
642
643
  "description": "Require metric overrides for swapping webfonts to reduce layout shifts during font load.",
643
644
  "fixable": false,
644
645
  "category": "css-layout",
645
- "plugin": "cross-file",
646
+ "plugin": "compilation",
646
647
  "messages": {
647
648
  "unstableFontSwap": "`@font-face` for '{{family}}' uses `font-display: {{display}}` without metric overrides (for example `size-adjust`), which can cause CLS when the webfont swaps in."
648
649
  }
@@ -653,7 +654,7 @@ var RULES = [
653
654
  "description": "Disallow overflow-anchor none on dynamic or scrollable containers prone to visible layout shifts.",
654
655
  "fixable": false,
655
656
  "category": "css-layout",
656
- "plugin": "cross-file",
657
+ "plugin": "compilation",
657
658
  "messages": {
658
659
  "unstableOverflowAnchor": "Element '{{tag}}' sets `overflow-anchor: none` on a {{context}} container; disabling scroll anchoring can amplify visible layout shifts."
659
660
  }
@@ -664,7 +665,7 @@ var RULES = [
664
665
  "description": "Disallow conditional overflow mode switches that can introduce scrollbar-induced layout shifts.",
665
666
  "fixable": false,
666
667
  "category": "css-layout",
667
- "plugin": "cross-file",
668
+ "plugin": "compilation",
668
669
  "messages": {
669
670
  "overflowModeToggle": "Conditional overflow mode changes scrolling ('{{overflow}}') on '{{tag}}' without `scrollbar-gutter: stable`, which can trigger CLS."
670
671
  }
@@ -675,7 +676,7 @@ var RULES = [
675
676
  "description": "Require stable scrollbar gutters for scrollable containers to reduce layout shifts.",
676
677
  "fixable": false,
677
678
  "category": "css-layout",
678
- "plugin": "cross-file",
679
+ "plugin": "compilation",
679
680
  "messages": {
680
681
  "missingScrollbarGutter": "Scrollable container '{{tag}}' uses overflow auto/scroll without `scrollbar-gutter: stable`, which can trigger CLS when scrollbars appear."
681
682
  }
@@ -686,7 +687,7 @@ var RULES = [
686
687
  "description": "Detect vertical alignment outliers between sibling elements in shared layout containers.",
687
688
  "fixable": false,
688
689
  "category": "css-layout",
689
- "plugin": "cross-file",
690
+ "plugin": "compilation",
690
691
  "messages": {
691
692
  "misalignedSibling": "Vertically misaligned '{{subject}}' in '{{parent}}'.{{fix}}{{offsetClause}}"
692
693
  }
@@ -697,7 +698,7 @@ var RULES = [
697
698
  "description": "Disallow stateful selector changes that alter element geometry and trigger layout shifts.",
698
699
  "fixable": false,
699
700
  "category": "css-layout",
700
- "plugin": "cross-file",
701
+ "plugin": "compilation",
701
702
  "messages": {
702
703
  "statefulBoxModelShift": "State selector '{{selector}}' changes layout-affecting '{{property}}'. Keep geometry stable across states to avoid CLS."
703
704
  }
@@ -708,7 +709,7 @@ var RULES = [
708
709
  "description": "Disallow transitions that animate layout-affecting geometry properties.",
709
710
  "fixable": false,
710
711
  "category": "css-layout",
711
- "plugin": "cross-file",
712
+ "plugin": "compilation",
712
713
  "messages": {
713
714
  "transitionLayoutProperty": "Transition '{{property}}' in '{{declaration}}' animates layout-affecting geometry. Prefer transform/opacity to avoid CLS."
714
715
  }
@@ -719,7 +720,7 @@ var RULES = [
719
720
  "description": "Require stable reserved geometry for replaced media elements to prevent layout shifts.",
720
721
  "fixable": false,
721
722
  "category": "css-layout",
722
- "plugin": "cross-file",
723
+ "plugin": "compilation",
723
724
  "messages": {
724
725
  "unsizedReplacedElement": "Replaced element '{{tag}}' has no stable reserved size (width/height or aspect-ratio with a dimension), which can cause CLS."
725
726
  }
@@ -730,7 +731,7 @@ var RULES = [
730
731
  "description": "Disallow cycles in custom property references.",
731
732
  "fixable": false,
732
733
  "category": "css-property",
733
- "plugin": "css",
734
+ "plugin": "compilation",
734
735
  "messages": {
735
736
  "variableCycle": "Custom property cycle detected involving `{{name}}`."
736
737
  }
@@ -741,7 +742,7 @@ var RULES = [
741
742
  "description": "Disallow hardcoded positive z-index literals.",
742
743
  "fixable": false,
743
744
  "category": "css-property",
744
- "plugin": "css",
745
+ "plugin": "compilation",
745
746
  "messages": {
746
747
  "hardcodedZ": "Use a z-index token variable instead of literal `{{value}}`."
747
748
  }
@@ -752,18 +753,29 @@ var RULES = [
752
753
  "description": "Disallow 100vh in viewport sizing declarations.",
753
754
  "fixable": false,
754
755
  "category": "css-property",
755
- "plugin": "css",
756
+ "plugin": "compilation",
756
757
  "messages": {
757
758
  "avoidLegacyVh": "Use 100dvh/100svh instead of `100vh` for mobile-safe viewport sizing."
758
759
  }
759
760
  },
761
+ {
762
+ "id": "css-prefer-logical-properties",
763
+ "severity": "warn",
764
+ "description": "Prefer logical properties over physical left/right properties.",
765
+ "fixable": false,
766
+ "category": "css-property",
767
+ "plugin": "compilation",
768
+ "messages": {
769
+ "preferLogical": "Use logical property `{{logical}}` instead of `{{physical}}`."
770
+ }
771
+ },
760
772
  {
761
773
  "id": "css-z-index-requires-positioned-context",
762
774
  "severity": "warn",
763
775
  "description": "Require positioned context when using z-index.",
764
776
  "fixable": false,
765
777
  "category": "css-property",
766
- "plugin": "css",
778
+ "plugin": "compilation",
767
779
  "messages": {
768
780
  "zIndexNoContext": "`z-index` has no guaranteed effect without a positioned context."
769
781
  }
@@ -774,7 +786,7 @@ var RULES = [
774
786
  "description": "Disallow !important declarations.",
775
787
  "fixable": false,
776
788
  "category": "css-property",
777
- "plugin": "css",
789
+ "plugin": "compilation",
778
790
  "messages": {
779
791
  "avoidImportant": "Avoid `!important` on `{{property}}`. It increases override cost and usually signals specificity debt."
780
792
  }
@@ -785,7 +797,7 @@ var RULES = [
785
797
  "description": "Disallow unresolved custom property references.",
786
798
  "fixable": false,
787
799
  "category": "css-property",
788
- "plugin": "css",
800
+ "plugin": "compilation",
789
801
  "messages": {
790
802
  "unresolvedCustomProperty": "Custom property reference `{{name}}` is unresolved in `{{property}}`. Define it or provide a fallback value."
791
803
  }
@@ -796,7 +808,7 @@ var RULES = [
796
808
  "description": "Disallow unused CSS custom properties.",
797
809
  "fixable": false,
798
810
  "category": "css-property",
799
- "plugin": "css",
811
+ "plugin": "compilation",
800
812
  "messages": {
801
813
  "unusedCustomProperty": "Custom property `{{name}}` is never referenced within the project CSS."
802
814
  }
@@ -807,7 +819,7 @@ var RULES = [
807
819
  "description": "Disallow deep selectors that are expensive to match.",
808
820
  "fixable": false,
809
821
  "category": "css-selector",
810
- "plugin": "css",
822
+ "plugin": "compilation",
811
823
  "messages": {
812
824
  "selectorTooDeep": "Selector `{{selector}}` has depth {{depth}}. Deep selectors increase style recalculation cost and are fragile across component rerenders."
813
825
  }
@@ -818,7 +830,7 @@ var RULES = [
818
830
  "description": "Disallow duplicate selector blocks.",
819
831
  "fixable": false,
820
832
  "category": "css-selector",
821
- "plugin": "css",
833
+ "plugin": "compilation",
822
834
  "messages": {
823
835
  "duplicateSelector": "Selector `{{selector}}` is duplicated {{count}} times. Merge declarations to avoid cascade ambiguity."
824
836
  }
@@ -829,7 +841,7 @@ var RULES = [
829
841
  "description": "Disallow ID selectors.",
830
842
  "fixable": false,
831
843
  "category": "css-selector",
832
- "plugin": "css",
844
+ "plugin": "compilation",
833
845
  "messages": {
834
846
  "avoidId": "Avoid ID selector in `{{selector}}`. IDs raise specificity and make component-level styling harder to maintain."
835
847
  }
@@ -837,13 +849,13 @@ var RULES = [
837
849
  {
838
850
  "id": "selector-max-attribute-and-universal",
839
851
  "severity": "off",
840
- "description": "Disallow selectors with attribute or universal selectors beyond configured limits.",
852
+ "description": "Disallow selectors with attribute or universal selectors.",
841
853
  "fixable": false,
842
854
  "category": "css-selector",
843
- "plugin": "css",
855
+ "plugin": "compilation",
844
856
  "messages": {
845
- "tooManyAttributes": "Selector `{{selector}}` uses {{count}} attribute selector(s). Maximum allowed is {{max}}.",
846
- "tooManyUniversals": "Selector `{{selector}}` uses {{count}} universal selector(s). Maximum allowed is {{max}}."
857
+ "tooManyAttributes": "Selector `{{selector}}` uses attribute selector(s). Maximum allowed is 0.",
858
+ "tooManyUniversals": "Selector `{{selector}}` uses universal selector(s). Maximum allowed is 0."
847
859
  }
848
860
  },
849
861
  {
@@ -852,7 +864,7 @@ var RULES = [
852
864
  "description": "Disallow selectors that exceed a specificity threshold.",
853
865
  "fixable": false,
854
866
  "category": "css-selector",
855
- "plugin": "css",
867
+ "plugin": "compilation",
856
868
  "messages": {
857
869
  "maxSpecificity": "Selector `{{selector}}` specificity {{specificity}} exceeds max {{max}}. Reduce selector weight to keep the cascade predictable."
858
870
  }
@@ -863,7 +875,7 @@ var RULES = [
863
875
  "description": "Disallow empty CSS rules.",
864
876
  "fixable": false,
865
877
  "category": "css-structure",
866
- "plugin": "css",
878
+ "plugin": "compilation",
867
879
  "messages": {
868
880
  "emptyRule": "Empty rule `{{selector}}` should be removed."
869
881
  }
@@ -874,7 +886,7 @@ var RULES = [
874
886
  "description": "Disallow unknown named containers in @container queries.",
875
887
  "fixable": false,
876
888
  "category": "css-structure",
877
- "plugin": "css",
889
+ "plugin": "compilation",
878
890
  "messages": {
879
891
  "unknownContainer": "Unknown container name `{{name}}` in @container query."
880
892
  }
@@ -885,7 +897,7 @@ var RULES = [
885
897
  "description": "Disallow unused named containers.",
886
898
  "fixable": false,
887
899
  "category": "css-structure",
888
- "plugin": "css",
900
+ "plugin": "compilation",
889
901
  "messages": {
890
902
  "unusedContainer": "Container name `{{name}}` is declared but never queried."
891
903
  }
@@ -896,7 +908,7 @@ var RULES = [
896
908
  "description": "Require style rules to be inside @layer when the file defines layers.",
897
909
  "fixable": false,
898
910
  "category": "css-structure",
899
- "plugin": "css",
911
+ "plugin": "compilation",
900
912
  "messages": {
901
913
  "missingLayer": "Rule `{{selector}}` is not inside any @layer block while this file uses @layer. Place component rules inside an explicit layer."
902
914
  }
@@ -1927,14 +1939,14 @@ var RULES = [
1927
1939
  ];
1928
1940
  var RULES_BY_CATEGORY = {
1929
1941
  "correctness": [{ "id": "avoid-conditional-spreads", "severity": "error", "description": "Disallow conditional spread operators that create empty objects. Patterns like `...(condition ? {...} : {})` are fragile and create unnecessary object creations.", "fixable": false, "category": "correctness", "plugin": "solid", "messages": { "avoidConditionalSpread": "Avoid conditional spread with empty object fallback. Instead of `...(cond ? {...} : {})`, build the object first with conditional property assignment, then spread once.", "avoidLogicalAndSpread": "Avoid logical AND spread pattern. Instead of `...(cond && {...})`, use explicit conditional property assignment for clarity." } }, { "id": "avoid-non-null-assertions", "severity": "error", "description": "Disallow non-null assertion operator (`!`). Use optional chaining, nullish coalescing, or proper type narrowing instead.", "fixable": true, "category": "correctness", "plugin": "solid", "messages": { "avoidNonNull": 'Avoid non-null assertion on "{{name}}". Non-null assertions bypass type safety. Use optional chaining (`?.`), nullish coalescing (`??`), or proper type narrowing instead.' } }, { "id": "avoid-object-assign", "severity": "error", "description": "Disallow Object.assign(). Prefer object spread syntax or structuredClone() for copying objects.", "fixable": true, "category": "correctness", "plugin": "solid", "messages": { "avoidMerge": "Avoid Object.assign() for merging. Use object spread syntax { ...obj } instead.", "avoidMutation": "Avoid Object.assign() for mutation. Consider immutable patterns like { ...existing, ...props }." } }, { "id": "avoid-object-spread", "severity": "error", "description": "Disallow object spread operators that break Solid's fine-grained reactivity.", "fixable": true, "category": "correctness", "plugin": "solid", "messages": { "avoidObjectCopy": "Avoid object spread for copying. Use direct property access.", "avoidObjectMerge": "Avoid object spread for merging. Use mergeProps() from 'solid-js'.", "avoidObjectUpdate": "Avoid object spread for updates. Use produce() or direct assignment.", "avoidJsxSpread": "Avoid JSX prop spreading. Use splitProps() to separate props.", "avoidRestDestructure": "Avoid rest destructuring. Use splitProps() from 'solid-js'.", "avoidPropsSpread": "Spreading props breaks reactivity. Use splitProps() to separate known props.", "avoidStoreSpread": "Spreading store creates a static snapshot. Access properties directly.", "avoidSignalSpread": "Spreading signal result captures current value. Wrap in createMemo().", "avoidClassListSpread": "Spreading in classList breaks reactivity. Wrap in createMemo().", "avoidStyleSpread": "Spreading in style breaks reactivity. Wrap in createMemo().", "unnecessarySplitProps": "Unnecessary splitProps with empty array. Remove it and use {{source}} directly." } }, { "id": "avoid-type-casting", "severity": "error", "description": "Disallow type casting methods that bypass TypeScript's type safety. Includes unnecessary casts, double assertions, casting to any, type predicates, and unsafe generic assertions.", "fixable": true, "category": "correctness", "plugin": "solid", "messages": { "unnecessaryCast": `Unnecessary type assertion: "{{name}}" is already of type "{{exprType}}", which is assignable to "{{type}}". Remove the cast - it adds noise and suggests you don't understand the types.`, "doubleAssertion": 'Double assertion detected: "{{name}}" is cast through unknown/any to "{{type}}". This bypasses type safety. You are creating sloppy architecture.', "castToAny": 'Casting "{{name}}" to `any` disables all type checking. Use `unknown` with proper type guards, or fix the underlying type issue.', "castToUnknown": "Casting to `unknown` requires runtime type checks before use. You are creating sloppy architecture.", "simpleAssertion": 'Type assertion on "{{name}}" to "{{type}}" bypasses type checking. Why are you doing this? Do you EVEN need this? This is sloppy architecture.', "assertionInLoop": 'Type assertion on "{{name}}" inside a loop. Repeated casts to "{{type}}" without validation can mask type errors. Consider validating the type once before the loop.', "importAssertion": 'Type assertion on dynamic import to "{{type}}". Import types should be validated at runtime or use proper module type declarations.', "typePredicate": 'Type predicate function asserts "{{param}}" is "{{type}}". Why are you doing this? Do you EVEN need this? This is sloppy architecture.', "unsafeGeneric": 'Casting to generic type parameter "{{typeParam}}" without runtime validation. The function returns an unverified type. This is sloppy architecture.' } }, { "id": "avoid-unsafe-type-annotations", "severity": "error", "description": "Disallow `any` and `unknown` in value-level type annotation positions (parameters, returns, variables, properties)", "fixable": false, "category": "correctness", "plugin": "solid", "messages": { "anyParameter": "Parameter '{{name}}' is typed `any`{{inFunction}}. This disables type checking for all callers. Use a specific type, a generic, or `unknown` with proper type narrowing.", "anyReturn": "Function '{{name}}' returns `any`. This disables type checking for all callers. Use a specific return type.", "anyVariable": "Variable '{{name}}' is typed `any`. This disables all type checking on this variable. Use a specific type or `unknown` with type narrowing.", "anyProperty": "Property '{{name}}' is typed `any`. This disables type checking for all accesses. Use a specific type.", "unknownParameter": "Parameter '{{name}}' is typed `unknown`{{inFunction}}. Callers can pass anything and the function body requires type narrowing on every use. Use a specific type or a generic constraint.", "unknownReturn": "Function '{{name}}' returns `unknown`. Callers must narrow the return value before use. Use a specific return type or a generic.", "unknownVariable": "Variable '{{name}}' is typed `unknown`. Every use requires type narrowing. Use a specific type or parse the value at the boundary.", "unknownProperty": "Property '{{name}}' is typed `unknown`. Every access requires type narrowing. Use a specific type." } }, { "id": "event-handlers", "severity": "error", "description": "Enforce naming DOM element event handlers consistently and prevent Solid's analysis from misunderstanding whether a prop should be an event handler.", "fixable": true, "category": "correctness", "plugin": "solid", "messages": { "detectedAttr": 'The "{{name}}" prop looks like an event handler but has a static value ({{staticValue}}), so Solid will treat it as an attribute instead of attaching an event listener. Use attr:{{name}} to make this explicit, or provide a function value.', "naming": 'The "{{name}}" prop is ambiguous. Solid cannot determine if this is an event handler or an attribute. Use {{handlerName}} for an event handler, or {{attrName}} for an attribute.', "capitalization": 'The "{{name}}" prop should be {{fixedName}} for Solid to recognize it as an event handler. Event handlers use camelCase with an uppercase letter after "on".', "nonstandard": 'The "{{name}}" prop uses a nonstandard event name. Use {{fixedName}} instead, which is the standard DOM event name that Solid recognizes.', "makeHandler": "Change {{name}} to {{handlerName}} (event handler).", "makeAttr": "Change {{name}} to {{attrName}} (attribute).", "spreadHandler": 'The "{{name}}" prop is being spread into JSX, which prevents Solid from attaching it as an event listener. Add it directly as a JSX attribute instead: {{name}}={...}.' } }, { "id": "missing-jsdoc-comments", "severity": "error", "description": "Require JSDoc comments on functions with appropriate tags for parameters, return values, and throws.", "fixable": false, "category": "correctness", "plugin": "solid", "messages": { "missingJsdoc": "Function '{{name}}' is missing a JSDoc comment.", "missingParam": "JSDoc for '{{name}}' is missing @param tag for '{{param}}'.", "missingReturn": "JSDoc for '{{name}}' is missing @returns tag.", "missingThrows": "JSDoc for '{{name}}' is missing @throws tag.", "missingExample": "JSDoc for '{{name}}' is missing @example tag.", "missingClassJsdoc": "Class '{{name}}' is missing a JSDoc comment.", "missingPropertyJsdoc": "Property '{{name}}' is missing a JSDoc comment." } }, { "id": "no-ai-slop-comments", "severity": "error", "description": "Disallow comments containing specified forbidden words or phrases. Useful for enforcing comment style guidelines and detecting AI-generated boilerplate.", "fixable": true, "category": "correctness", "plugin": "solid", "messages": { "forbiddenWord": "Comment contains forbidden word '{{word}}'." } }, { "id": "no-array-handlers", "severity": "error", "description": "Disallow array handlers in JSX event properties.", "fixable": false, "category": "correctness", "plugin": "solid", "messages": { "noArrayHandlers": 'Passing an array to "{{handlerName}}" is type-unsafe. The array syntax `[handler, data]` passes data as the first argument, making the event object the second argument. Use a closure instead: `{{handlerName}}={() => handler(data)}`.' } }, { "id": "no-banner-comments", "severity": "error", "description": "Disallow banner-style comments with repeated separator characters.", "fixable": true, "category": "correctness", "plugin": "solid", "messages": { "banner": "Avoid banner-style comments with repeated separator characters. Use simple comments instead." } }, { "id": "no-destructure", "severity": "error", "description": "Disallow destructuring props in Solid components. Props must be accessed via property access (props.x) to preserve reactivity.", "fixable": false, "category": "correctness", "plugin": "solid", "messages": { "noDestructure": "Destructuring component props breaks Solid's reactivity. Props are reactive getters, so `{ a }` captures the value at component creation time and won't update. Use `props.a` to access props reactively.", "noDestructureWithDefaults": "Destructuring component props breaks Solid's reactivity. For default values, use `mergeProps({ a: defaultValue }, props)` instead of `{ a = defaultValue }`.", "noDestructureWithRest": "Destructuring component props breaks Solid's reactivity. For rest patterns, use `splitProps(props, ['a', 'b'])` instead of `{ a, b, ...rest }`.", "noDestructureWithBoth": "Destructuring component props breaks Solid's reactivity. For default values with rest, use `splitProps(mergeProps({ a: defaultValue }, props), ['a'])` to combine both patterns." } }, { "id": "no-inline-imports", "severity": "error", "description": "Disallow inline type imports. Import types at the top of the file for clarity and maintainability.", "fixable": false, "category": "correctness", "plugin": "solid", "messages": { "inlineImport": "Avoid inline imports. Import `{{specifier}}` at the top of the file instead." } }, { "id": "string-concat-in-loop", "severity": "error", "description": "Disallow string concatenation with += inside loops. Use array.push() and .join() instead.", "fixable": false, "category": "correctness", "plugin": "solid", "messages": { "stringConcatInLoop": "Avoid string concatenation with += inside loops. Use an array with .push() and .join() instead." } }],
1930
- "css-a11y": [{ "id": "css-no-outline-none-without-focus-visible", "severity": "error", "description": "Disallow removing outline without explicit focus-visible replacement.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "missingFocusVisible": "Focus outline removed without matching `:focus-visible` replacement." } }, { "id": "css-policy-contrast", "severity": "warn", "description": "Enforce minimum contrast ratio between foreground and background colors per accessibility policy.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "insufficientContrast": "Contrast ratio `{{ratio}}:1` between `{{fg}}` and `{{bg}}` is below the minimum `{{min}}:1` for `{{textSize}}` text in policy `{{policy}}`." } }, { "id": "css-policy-spacing", "severity": "warn", "description": "Enforce minimum letter-spacing, word-spacing, and paragraph spacing per accessibility policy.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "letterSpacingTooSmall": "Letter spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`.", "wordSpacingTooSmall": "Word spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`.", "paragraphSpacingTooSmall": "Paragraph spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` ({{minMultiplier}}\xD7 font-size) for policy `{{policy}}`." } }, { "id": "css-policy-typography", "severity": "warn", "description": "Enforce minimum font sizes and line heights per accessibility policy.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "fontTooSmall": "Font size `{{value}}` ({{resolved}}px) is below the `{{context}}` minimum of `{{min}}px` for policy `{{policy}}`.", "lineHeightTooSmall": "Line height `{{value}}` is below the `{{context}}` minimum of `{{min}}` for policy `{{policy}}`." } }, { "id": "css-require-reduced-motion-override", "severity": "warn", "description": "Require reduced-motion override for animated selectors.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "missingReducedMotion": "Animated selector `{{selector}}` lacks prefers-reduced-motion override." } }, { "id": "jsx-layout-policy-touch-target", "severity": "warn", "description": "Enforce minimum interactive element sizes per accessibility policy via resolved layout signals.", "fixable": false, "category": "css-a11y", "plugin": "cross-file", "messages": { "heightTooSmall": "`{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.", "widthTooSmall": "`{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.", "paddingTooSmall": "Horizontal padding `{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.", "noReservedBlockSize": "Interactive element `<{{tag}}>` has no declared height (minimum `{{min}}px` required by policy `{{policy}}`). The element is content-sized and may not meet the touch-target threshold.", "noReservedInlineSize": "Interactive element `<{{tag}}>` has no declared width (minimum `{{min}}px` required by policy `{{policy}}`). The element is content-sized and may not meet the touch-target threshold." } }],
1931
- "css-animation": [{ "id": "css-no-discrete-transition", "severity": "error", "description": "Disallow transitions on discrete CSS properties.", "fixable": false, "category": "css-animation", "plugin": "css", "messages": { "discreteTransition": "Property `{{property}}` is discrete and should not be transitioned." } }, { "id": "css-no-empty-keyframes", "severity": "error", "description": "Disallow empty @keyframes rules.", "fixable": false, "category": "css-animation", "plugin": "css", "messages": { "emptyKeyframes": "@keyframes `{{name}}` has no effective keyframes." } }, { "id": "no-layout-property-animation", "severity": "warn", "description": "Disallow animating layout-affecting properties.", "fixable": false, "category": "css-animation", "plugin": "css", "messages": { "avoidLayoutAnimation": "Avoid animating layout property `{{property}}`. Prefer transform or opacity to reduce layout thrashing." } }, { "id": "no-transition-all", "severity": "warn", "description": "Disallow transition: all and transition-property: all.", "fixable": false, "category": "css-animation", "plugin": "css", "messages": { "avoidTransitionAll": "Avoid `transition: all`. Transition specific properties to reduce unnecessary style and paint work." } }, { "id": "no-unknown-animation-name", "severity": "error", "description": "Disallow animation names that do not match declared keyframes.", "fixable": false, "category": "css-animation", "plugin": "css", "messages": { "unknownAnimationName": "Animation name `{{name}}` in `{{property}}` does not match any declared @keyframes." } }, { "id": "no-unused-keyframes", "severity": "warn", "description": "Disallow unused @keyframes declarations.", "fixable": false, "category": "css-animation", "plugin": "css", "messages": { "unusedKeyframes": "@keyframes `{{name}}` is never referenced by animation declarations." } }],
1932
- "css-cascade": [{ "id": "declaration-no-overridden-within-rule", "severity": "warn", "description": "Disallow duplicate declarations of the same property within a single rule block.", "fixable": false, "category": "css-cascade", "plugin": "css", "messages": { "overriddenWithinRule": "Declaration `{{property}}` is overridden later in the same rule. Keep one final declaration per property." } }, { "id": "media-query-overlap-conflict", "severity": "warn", "description": "Disallow conflicting declarations in partially overlapping media queries.", "fixable": false, "category": "css-cascade", "plugin": "css", "messages": { "mediaOverlapConflict": "Overlapping media queries set different `{{property}}` values for `{{selector}}` in the same overlap range." } }, { "id": "no-descending-specificity-conflict", "severity": "warn", "description": "Disallow lower-specificity selectors after higher-specificity selectors for the same property.", "fixable": false, "category": "css-cascade", "plugin": "css", "messages": { "descendingSpecificity": "Lower-specificity selector `{{laterSelector}}` appears after `{{earlierSelector}}` for `{{property}}`, creating brittle cascade behavior." } }, { "id": "no-layer-order-inversion", "severity": "warn", "description": "Disallow source-order assumptions that are inverted by layer precedence.", "fixable": false, "category": "css-cascade", "plugin": "css", "messages": { "layerOrderInversion": "Declaration for `{{property}}` in selector `{{selector}}` appears later but is overridden by an earlier declaration due to @layer precedence." } }, { "id": "no-redundant-override-pairs", "severity": "warn", "description": "Disallow declarations that are deterministically overridden in the same selector context.", "fixable": false, "category": "css-cascade", "plugin": "css", "messages": { "redundantOverride": "Declaration `{{property}}` is always overridden later by the same selector in the same cascade context." } }],
1933
- "css-jsx": [{ "id": "css-no-unreferenced-component-class", "severity": "warn", "description": "Detect CSS classes that are never referenced by static JSX class attributes.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "unreferencedClass": "CSS class '{{className}}' is defined but not referenced by static JSX class attributes" } }, { "id": "jsx-classlist-boolean-values", "severity": "error", "description": "Require classList values to be boolean-like expressions.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "nonBooleanValue": "classList value for `{{name}}` must be boolean." } }, { "id": "jsx-classlist-no-accessor-reference", "severity": "error", "description": "Disallow passing accessor references directly as classList values.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "accessorReference": "Signal accessor `{{name}}` must be called in classList value (use {{name}}())." } }, { "id": "jsx-classlist-no-constant-literals", "severity": "warn", "description": "Disallow classList entries with constant true/false values.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "constantEntry": "classList entry `{{name}}: {{value}}` is constant; move it to static class." } }, { "id": "jsx-classlist-static-keys", "severity": "error", "description": "Require classList keys to be static and non-computed.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "nonStaticKey": "classList key must be statically known for reliable class mapping." } }, { "id": "jsx-layout-classlist-geometry-toggle", "severity": "warn", "description": "Flag classList-driven class toggles that map to layout-affecting CSS geometry changes.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "classListGeometryToggle": "classList toggles '{{className}}', and matching CSS changes layout-affecting '{{property}}', which can cause CLS." } }, { "id": "jsx-layout-fill-image-parent-must-be-sized", "severity": "warn", "description": "Require stable parent size and positioning for fill-image component usage.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "unsizedFillParent": "Fill-image component '{{component}}' is inside a parent without stable size/position; add parent sizing (height/min-height/aspect-ratio) and non-static position to avoid CLS." } }, { "id": "jsx-layout-picture-source-ratio-consistency", "severity": "warn", "description": "Require consistent intrinsic aspect ratios across <picture> sources and fallback image.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "inconsistentPictureRatio": "`<picture>` source ratio {{sourceRatio}} differs from fallback img ratio {{imgRatio}}, which can cause reserved-space mismatch and CLS." } }, { "id": "jsx-layout-unstable-style-toggle", "severity": "warn", "description": "Flag dynamic inline style values on layout-sensitive properties that can trigger CLS.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "unstableLayoutStyleToggle": "Dynamic style value for '{{property}}' can toggle layout geometry at runtime and cause CLS." } }, { "id": "jsx-no-duplicate-class-token-class-classlist", "severity": "warn", "description": "Disallow duplicate class tokens between class and classList on the same JSX element.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "duplicateClassToken": "Class token `{{name}}` appears in both class and classList." } }, { "id": "jsx-no-undefined-css-class", "severity": "error", "description": "Detect undefined CSS class names in JSX", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "undefinedClass": "CSS class '{{className}}' is not defined in project CSS files" } }, { "id": "jsx-style-kebab-case-keys", "severity": "error", "description": "Require kebab-case keys in JSX style object literals.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "kebabStyleKey": "Style key `{{name}}` should be `{{kebab}}` in Solid style objects." } }, { "id": "jsx-style-no-function-values", "severity": "error", "description": "Disallow function values in JSX style objects.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "functionStyleValue": "Style value for `{{name}}` is a function; pass computed value instead." } }, { "id": "jsx-style-no-unused-custom-prop", "severity": "warn", "description": "Detect inline style custom properties that are never consumed by CSS var() references.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "unusedInlineVar": "Inline custom property `{{name}}` is never read via var({{name}})." } }, { "id": "jsx-style-policy", "severity": "warn", "description": "Enforce accessibility policy thresholds on inline JSX style objects.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "fontTooSmall": "Inline style `{{prop}}: {{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for policy `{{policy}}`.", "lineHeightTooSmall": "Inline style `line-height: {{value}}` is below the minimum `{{min}}` for policy `{{policy}}`.", "heightTooSmall": "Inline style `{{prop}}: {{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for interactive elements in policy `{{policy}}`.", "letterSpacingTooSmall": "Inline style `letter-spacing: {{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`.", "wordSpacingTooSmall": "Inline style `word-spacing: {{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`." } }],
1934
- "css-layout": [{ "id": "css-layout-animation-layout-property", "severity": "warn", "description": "Disallow keyframe animations that mutate layout-affecting properties and can trigger CLS.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "animationLayoutProperty": "Animation '{{animation}}' mutates layout-affecting '{{property}}', which can trigger CLS. Prefer transform/opacity or reserve geometry." } }, { "id": "css-layout-box-sizing-toggle-with-chrome", "severity": "warn", "description": "Disallow conditional box-sizing mode toggles when box chrome contributes to geometry shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "boxSizingToggleWithChrome": "Conditional `box-sizing` toggle on '{{tag}}' combines with non-zero padding/border, which can shift layout and trigger CLS." } }, { "id": "css-layout-conditional-display-collapse", "severity": "warn", "description": "Disallow conditional display collapse in flow without reserved geometry.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "conditionalDisplayCollapse": "Conditional display sets '{{display}}' on '{{tag}}' without stable reserved space, which can collapse/expand layout and cause CLS." } }, { "id": "css-layout-conditional-offset-shift", "severity": "warn", "description": "Disallow conditional non-zero block-axis offsets that can trigger layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "conditionalOffsetShift": "Conditional style applies non-zero '{{property}}' offset ({{value}}), which can cause layout shifts when conditions toggle." } }, { "id": "css-layout-conditional-white-space-wrap-shift", "severity": "warn", "description": "Disallow conditional white-space wrapping mode toggles that can trigger CLS.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "conditionalWhiteSpaceShift": "Conditional white-space '{{whiteSpace}}' on '{{tag}}' can reflow text and shift siblings; keep wrapping behavior stable or reserve geometry." } }, { "id": "css-layout-content-visibility-no-intrinsic-size", "severity": "warn", "description": "Require intrinsic size reservation when using content-visibility auto to avoid late layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "missingIntrinsicSize": "`content-visibility: auto` on '{{tag}}' lacks intrinsic size reservation (`contain-intrinsic-size`/min-height/height/aspect-ratio), which can cause CLS." } }, { "id": "css-layout-dynamic-slot-no-reserved-space", "severity": "warn", "description": "Require reserved block space for dynamic content containers to avoid layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "dynamicSlotNoReservedSpace": "Dynamic content container '{{tag}}' does not reserve block space (min-height/height/aspect-ratio/contain-intrinsic-size), which can cause CLS." } }, { "id": "css-layout-font-swap-instability", "severity": "warn", "description": "Require metric overrides for swapping webfonts to reduce layout shifts during font load.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "unstableFontSwap": "`@font-face` for '{{family}}' uses `font-display: {{display}}` without metric overrides (for example `size-adjust`), which can cause CLS when the webfont swaps in." } }, { "id": "css-layout-overflow-anchor-instability", "severity": "warn", "description": "Disallow overflow-anchor none on dynamic or scrollable containers prone to visible layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "unstableOverflowAnchor": "Element '{{tag}}' sets `overflow-anchor: none` on a {{context}} container; disabling scroll anchoring can amplify visible layout shifts." } }, { "id": "css-layout-overflow-mode-toggle-instability", "severity": "warn", "description": "Disallow conditional overflow mode switches that can introduce scrollbar-induced layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "overflowModeToggle": "Conditional overflow mode changes scrolling ('{{overflow}}') on '{{tag}}' without `scrollbar-gutter: stable`, which can trigger CLS." } }, { "id": "css-layout-scrollbar-gutter-instability", "severity": "warn", "description": "Require stable scrollbar gutters for scrollable containers to reduce layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "missingScrollbarGutter": "Scrollable container '{{tag}}' uses overflow auto/scroll without `scrollbar-gutter: stable`, which can trigger CLS when scrollbars appear." } }, { "id": "css-layout-sibling-alignment-outlier", "severity": "warn", "description": "Detect vertical alignment outliers between sibling elements in shared layout containers.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "misalignedSibling": "Vertically misaligned '{{subject}}' in '{{parent}}'.{{fix}}{{offsetClause}}" } }, { "id": "css-layout-stateful-box-model-shift", "severity": "warn", "description": "Disallow stateful selector changes that alter element geometry and trigger layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "statefulBoxModelShift": "State selector '{{selector}}' changes layout-affecting '{{property}}'. Keep geometry stable across states to avoid CLS." } }, { "id": "css-layout-transition-layout-property", "severity": "warn", "description": "Disallow transitions that animate layout-affecting geometry properties.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "transitionLayoutProperty": "Transition '{{property}}' in '{{declaration}}' animates layout-affecting geometry. Prefer transform/opacity to avoid CLS." } }, { "id": "css-layout-unsized-replaced-element", "severity": "warn", "description": "Require stable reserved geometry for replaced media elements to prevent layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "unsizedReplacedElement": "Replaced element '{{tag}}' has no stable reserved size (width/height or aspect-ratio with a dimension), which can cause CLS." } }],
1935
- "css-property": [{ "id": "css-no-custom-property-cycle", "severity": "error", "description": "Disallow cycles in custom property references.", "fixable": false, "category": "css-property", "plugin": "css", "messages": { "variableCycle": "Custom property cycle detected involving `{{name}}`." } }, { "id": "css-no-hardcoded-z-index", "severity": "warn", "description": "Disallow hardcoded positive z-index literals.", "fixable": false, "category": "css-property", "plugin": "css", "messages": { "hardcodedZ": "Use a z-index token variable instead of literal `{{value}}`." } }, { "id": "css-no-legacy-vh-100", "severity": "warn", "description": "Disallow 100vh in viewport sizing declarations.", "fixable": false, "category": "css-property", "plugin": "css", "messages": { "avoidLegacyVh": "Use 100dvh/100svh instead of `100vh` for mobile-safe viewport sizing." } }, { "id": "css-z-index-requires-positioned-context", "severity": "warn", "description": "Require positioned context when using z-index.", "fixable": false, "category": "css-property", "plugin": "css", "messages": { "zIndexNoContext": "`z-index` has no guaranteed effect without a positioned context." } }, { "id": "no-important", "severity": "warn", "description": "Disallow !important declarations.", "fixable": false, "category": "css-property", "plugin": "css", "messages": { "avoidImportant": "Avoid `!important` on `{{property}}`. It increases override cost and usually signals specificity debt." } }, { "id": "no-unresolved-custom-properties", "severity": "error", "description": "Disallow unresolved custom property references.", "fixable": false, "category": "css-property", "plugin": "css", "messages": { "unresolvedCustomProperty": "Custom property reference `{{name}}` is unresolved in `{{property}}`. Define it or provide a fallback value." } }, { "id": "no-unused-custom-properties", "severity": "warn", "description": "Disallow unused CSS custom properties.", "fixable": false, "category": "css-property", "plugin": "css", "messages": { "unusedCustomProperty": "Custom property `{{name}}` is never referenced within the project CSS." } }],
1936
- "css-selector": [{ "id": "no-complex-selectors", "severity": "warn", "description": "Disallow deep selectors that are expensive to match.", "fixable": false, "category": "css-selector", "plugin": "css", "messages": { "selectorTooDeep": "Selector `{{selector}}` has depth {{depth}}. Deep selectors increase style recalculation cost and are fragile across component rerenders." } }, { "id": "no-duplicate-selectors", "severity": "warn", "description": "Disallow duplicate selector blocks.", "fixable": false, "category": "css-selector", "plugin": "css", "messages": { "duplicateSelector": "Selector `{{selector}}` is duplicated {{count}} times. Merge declarations to avoid cascade ambiguity." } }, { "id": "no-id-selectors", "severity": "warn", "description": "Disallow ID selectors.", "fixable": false, "category": "css-selector", "plugin": "css", "messages": { "avoidId": "Avoid ID selector in `{{selector}}`. IDs raise specificity and make component-level styling harder to maintain." } }, { "id": "selector-max-attribute-and-universal", "severity": "off", "description": "Disallow selectors with attribute or universal selectors beyond configured limits.", "fixable": false, "category": "css-selector", "plugin": "css", "messages": { "tooManyAttributes": "Selector `{{selector}}` uses {{count}} attribute selector(s). Maximum allowed is {{max}}.", "tooManyUniversals": "Selector `{{selector}}` uses {{count}} universal selector(s). Maximum allowed is {{max}}." } }, { "id": "selector-max-specificity", "severity": "warn", "description": "Disallow selectors that exceed a specificity threshold.", "fixable": false, "category": "css-selector", "plugin": "css", "messages": { "maxSpecificity": "Selector `{{selector}}` specificity {{specificity}} exceeds max {{max}}. Reduce selector weight to keep the cascade predictable." } }],
1937
- "css-structure": [{ "id": "css-no-empty-rule", "severity": "warn", "description": "Disallow empty CSS rules.", "fixable": false, "category": "css-structure", "plugin": "css", "messages": { "emptyRule": "Empty rule `{{selector}}` should be removed." } }, { "id": "css-no-unknown-container-name", "severity": "error", "description": "Disallow unknown named containers in @container queries.", "fixable": false, "category": "css-structure", "plugin": "css", "messages": { "unknownContainer": "Unknown container name `{{name}}` in @container query." } }, { "id": "css-no-unused-container-name", "severity": "warn", "description": "Disallow unused named containers.", "fixable": false, "category": "css-structure", "plugin": "css", "messages": { "unusedContainer": "Container name `{{name}}` is declared but never queried." } }, { "id": "layer-requirement-for-component-rules", "severity": "warn", "description": "Require style rules to be inside @layer when the file defines layers.", "fixable": false, "category": "css-structure", "plugin": "css", "messages": { "missingLayer": "Rule `{{selector}}` is not inside any @layer block while this file uses @layer. Place component rules inside an explicit layer." } }],
1942
+ "css-a11y": [{ "id": "css-no-outline-none-without-focus-visible", "severity": "error", "description": "Disallow removing outline without explicit focus-visible replacement.", "fixable": false, "category": "css-a11y", "plugin": "compilation", "messages": { "missingFocusVisible": "Focus outline removed without matching `:focus-visible` replacement." } }, { "id": "css-policy-contrast", "severity": "warn", "description": "Enforce minimum contrast ratio per accessibility policy.", "fixable": false, "category": "css-a11y", "plugin": "compilation", "messages": { "insufficientContrast": "Contrast ratio `{{ratio}}:1` between `{{fg}}` and `{{bg}}` is below the minimum `{{min}}:1` for `{{textSize}}` text in policy `{{policy}}`." } }, { "id": "css-policy-spacing", "severity": "warn", "description": "Enforce minimum spacing per accessibility policy.", "fixable": false, "category": "css-a11y", "plugin": "compilation", "messages": { "letterSpacingTooSmall": "Letter spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`.", "wordSpacingTooSmall": "Word spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`.", "paragraphSpacingTooSmall": "Paragraph spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` ({{minMultiplier}}\xD7 font-size) for policy `{{policy}}`.", "touchTargetTooSmall": "`{{property}}: {{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for interactive elements in policy `{{policy}}`." } }, { "id": "css-policy-typography", "severity": "warn", "description": "Enforce minimum font sizes and line heights per accessibility policy.", "fixable": false, "category": "css-a11y", "plugin": "compilation", "messages": { "fontTooSmall": "Font size `{{value}}` ({{resolved}}px) is below the `{{context}}` minimum of `{{min}}px` for policy `{{policy}}`.", "lineHeightTooSmall": "Line height `{{value}}` is below the `{{context}}` minimum of `{{min}}` for policy `{{policy}}`." } }, { "id": "css-require-reduced-motion-override", "severity": "warn", "description": "Require reduced-motion override for animated selectors.", "fixable": false, "category": "css-a11y", "plugin": "compilation", "messages": { "missingReducedMotion": "Animated selector `{{selector}}` lacks prefers-reduced-motion override." } }, { "id": "jsx-layout-policy-touch-target", "severity": "warn", "description": "Enforce minimum interactive element sizes per accessibility policy via resolved layout signals.", "fixable": false, "category": "css-a11y", "plugin": "compilation", "messages": { "heightTooSmall": "`{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.", "widthTooSmall": "`{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.", "paddingTooSmall": "Horizontal padding `{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.", "noReservedBlockSize": "Interactive element `<{{tag}}>` has no declared height (minimum `{{min}}px` required by policy `{{policy}}`). The element is content-sized and may not meet the touch-target threshold.", "noReservedInlineSize": "Interactive element `<{{tag}}>` has no declared width (minimum `{{min}}px` required by policy `{{policy}}`). The element is content-sized and may not meet the touch-target threshold." } }],
1943
+ "css-animation": [{ "id": "css-no-discrete-transition", "severity": "error", "description": "Disallow transitions on discrete CSS properties.", "fixable": false, "category": "css-animation", "plugin": "compilation", "messages": { "discreteTransition": "Property `{{property}}` is discrete and should not be transitioned." } }, { "id": "css-no-empty-keyframes", "severity": "error", "description": "Disallow empty @keyframes rules.", "fixable": false, "category": "css-animation", "plugin": "compilation", "messages": { "emptyKeyframes": "@keyframes `{{name}}` has no effective keyframes." } }, { "id": "no-layout-property-animation", "severity": "warn", "description": "Disallow animating layout-affecting properties.", "fixable": false, "category": "css-animation", "plugin": "compilation", "messages": { "avoidLayoutAnimation": "Avoid animating layout property `{{property}}`. Prefer transform or opacity to reduce layout thrashing." } }, { "id": "no-transition-all", "severity": "warn", "description": "Disallow transition: all.", "fixable": false, "category": "css-animation", "plugin": "compilation", "messages": { "avoidTransitionAll": "Avoid `transition: all`. Transition specific properties to reduce unnecessary style and paint work." } }, { "id": "no-unknown-animation-name", "severity": "error", "description": "Disallow animation names that do not match declared keyframes.", "fixable": false, "category": "css-animation", "plugin": "compilation", "messages": { "unknownAnimationName": "Animation name `{{name}}` in `{{property}}` does not match any declared @keyframes." } }, { "id": "no-unused-keyframes", "severity": "warn", "description": "Disallow unused @keyframes declarations.", "fixable": false, "category": "css-animation", "plugin": "compilation", "messages": { "unusedKeyframes": "@keyframes `{{name}}` is never referenced by animation declarations." } }],
1944
+ "css-cascade": [{ "id": "declaration-no-overridden-within-rule", "severity": "warn", "description": "Disallow duplicate declarations of the same property within a single rule block.", "fixable": false, "category": "css-cascade", "plugin": "compilation", "messages": { "overriddenWithinRule": "Declaration `{{property}}` is overridden later in the same rule. Keep one final declaration per property." } }, { "id": "media-query-overlap-conflict", "severity": "warn", "description": "Disallow conflicting declarations in partially overlapping media queries.", "fixable": false, "category": "css-cascade", "plugin": "compilation", "messages": { "mediaOverlapConflict": "Overlapping media queries set different `{{property}}` values for `{{selector}}` in the same overlap range." } }, { "id": "no-descending-specificity-conflict", "severity": "warn", "description": "Disallow lower-specificity selectors after higher-specificity selectors for the same property.", "fixable": false, "category": "css-cascade", "plugin": "compilation", "messages": { "descendingSpecificity": "Lower-specificity selector `{{laterSelector}}` appears after `{{earlierSelector}}` for `{{property}}`, creating brittle cascade behavior." } }, { "id": "no-layer-order-inversion", "severity": "warn", "description": "Disallow source-order assumptions that are inverted by layer precedence.", "fixable": false, "category": "css-cascade", "plugin": "compilation", "messages": { "layerOrderInversion": "Declaration for `{{property}}` in selector `{{selector}}` appears later but is overridden by an earlier declaration due to @layer precedence." } }, { "id": "no-redundant-override-pairs", "severity": "warn", "description": "Disallow declarations that are deterministically overridden in the same selector context.", "fixable": false, "category": "css-cascade", "plugin": "compilation", "messages": { "redundantOverride": "Declaration `{{property}}` is always overridden later by the same selector in the same cascade context." } }],
1945
+ "css-jsx": [{ "id": "css-no-unreferenced-component-class", "severity": "warn", "description": "Detect CSS classes that are never referenced by static JSX class attributes.", "fixable": false, "category": "css-jsx", "plugin": "compilation", "messages": { "unreferencedClass": "CSS class '{{className}}' is defined but not referenced by static JSX class attributes" } }, { "id": "jsx-classlist-boolean-values", "severity": "error", "description": "Require classList values to be boolean-like expressions.", "fixable": false, "category": "css-jsx", "plugin": "compilation", "messages": { "nonBooleanValue": "classList value for `{{name}}` must be boolean." } }, { "id": "jsx-classlist-no-accessor-reference", "severity": "error", "description": "Disallow passing accessor references directly as classList values.", "fixable": false, "category": "css-jsx", "plugin": "compilation", "messages": { "accessorReference": "Signal accessor `{{name}}` must be called in classList value (use {{name}}())." } }, { "id": "jsx-classlist-no-constant-literals", "severity": "warn", "description": "Disallow classList entries with constant true/false values.", "fixable": false, "category": "css-jsx", "plugin": "compilation", "messages": { "constantEntry": "classList entry `{{name}}: {{value}}` is constant; move it to static class." } }, { "id": "jsx-classlist-static-keys", "severity": "error", "description": "Require classList keys to be static and non-computed.", "fixable": false, "category": "css-jsx", "plugin": "compilation", "messages": { "nonStaticKey": "classList key must be statically known for reliable class mapping." } }, { "id": "jsx-layout-classlist-geometry-toggle", "severity": "warn", "description": "Flag classList-driven class toggles that map to layout-affecting CSS geometry changes.", "fixable": false, "category": "css-jsx", "plugin": "compilation", "messages": { "classListGeometryToggle": "classList toggles '{{className}}', and matching CSS changes layout-affecting '{{property}}', which can cause CLS." } }, { "id": "jsx-layout-fill-image-parent-must-be-sized", "severity": "warn", "description": "Require stable parent size and positioning for fill-image component usage.", "fixable": false, "category": "css-jsx", "plugin": "compilation", "messages": { "unsizedFillParent": "Fill-image component '{{component}}' is inside a parent without stable size/position; add parent sizing (height/min-height/aspect-ratio) and non-static position to avoid CLS." } }, { "id": "jsx-layout-picture-source-ratio-consistency", "severity": "warn", "description": "Require consistent intrinsic aspect ratios across <picture> sources and fallback image.", "fixable": false, "category": "css-jsx", "plugin": "compilation", "messages": { "inconsistentPictureRatio": "`<picture>` source ratio {{sourceRatio}} differs from fallback img ratio {{imgRatio}}, which can cause reserved-space mismatch and CLS." } }, { "id": "jsx-layout-unstable-style-toggle", "severity": "warn", "description": "Flag dynamic inline style values on layout-sensitive properties that can trigger CLS.", "fixable": false, "category": "css-jsx", "plugin": "compilation", "messages": { "unstableLayoutStyleToggle": "Dynamic style value for '{{property}}' can toggle layout geometry at runtime and cause CLS." } }, { "id": "jsx-no-duplicate-class-token-class-classlist", "severity": "warn", "description": "Disallow duplicate class tokens between class and classList on the same JSX element.", "fixable": false, "category": "css-jsx", "plugin": "compilation", "messages": { "duplicateClassToken": "Class token `{{name}}` appears in both class and classList." } }, { "id": "jsx-no-undefined-css-class", "severity": "error", "description": "Detect undefined CSS class names in JSX", "fixable": false, "category": "css-jsx", "plugin": "compilation", "messages": { "undefinedClass": "CSS class '{{className}}' is not defined in project CSS files" } }, { "id": "jsx-style-kebab-case-keys", "severity": "error", "description": "Require kebab-case keys in JSX style object literals.", "fixable": false, "category": "css-jsx", "plugin": "compilation", "messages": { "kebabStyleKey": "Style key `{{name}}` should be `{{kebab}}` in Solid style objects." } }, { "id": "jsx-style-no-function-values", "severity": "error", "description": "Disallow function values in JSX style objects.", "fixable": false, "category": "css-jsx", "plugin": "compilation", "messages": { "functionStyleValue": "Style value for `{{name}}` is a function; pass computed value instead." } }, { "id": "jsx-style-no-unused-custom-prop", "severity": "warn", "description": "Detect inline style custom properties that are never consumed by CSS var() references.", "fixable": false, "category": "css-jsx", "plugin": "compilation", "messages": { "unusedInlineVar": "Inline custom property `{{name}}` is never read via var({{name}})." } }, { "id": "jsx-style-policy", "severity": "warn", "description": "Enforce accessibility policy thresholds on inline JSX style objects.", "fixable": false, "category": "css-jsx", "plugin": "compilation", "messages": { "fontTooSmall": "Inline style `{{prop}}: {{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for policy `{{policy}}`.", "lineHeightTooSmall": "Inline style `line-height: {{value}}` is below the minimum `{{min}}` for policy `{{policy}}`.", "heightTooSmall": "Inline style `{{prop}}: {{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for interactive elements in policy `{{policy}}`.", "letterSpacingTooSmall": "Inline style `letter-spacing: {{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`.", "wordSpacingTooSmall": "Inline style `word-spacing: {{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`." } }],
1946
+ "css-layout": [{ "id": "css-layout-animation-layout-property", "severity": "warn", "description": "Disallow keyframe animations that mutate layout-affecting properties and can trigger CLS.", "fixable": false, "category": "css-layout", "plugin": "compilation", "messages": { "animationLayoutProperty": "Animation '{{animation}}' mutates layout-affecting '{{property}}', which can trigger CLS. Prefer transform/opacity or reserve geometry." } }, { "id": "css-layout-box-sizing-toggle-with-chrome", "severity": "warn", "description": "Disallow conditional box-sizing mode toggles when box chrome contributes to geometry shifts.", "fixable": false, "category": "css-layout", "plugin": "compilation", "messages": { "boxSizingToggleWithChrome": "Conditional `box-sizing` toggle on '{{tag}}' combines with non-zero padding/border, which can shift layout and trigger CLS." } }, { "id": "css-layout-conditional-display-collapse", "severity": "warn", "description": "Disallow conditional display collapse in flow without reserved geometry.", "fixable": false, "category": "css-layout", "plugin": "compilation", "messages": { "conditionalDisplayCollapse": "Conditional display sets '{{display}}' on '{{tag}}' without stable reserved space, which can collapse/expand layout and cause CLS." } }, { "id": "css-layout-conditional-offset-shift", "severity": "warn", "description": "Disallow conditional non-zero block-axis offsets that can trigger layout shifts.", "fixable": false, "category": "css-layout", "plugin": "compilation", "messages": { "conditionalOffsetShift": "Conditional style applies non-zero '{{property}}' offset ({{value}}), which can cause layout shifts when conditions toggle." } }, { "id": "css-layout-conditional-white-space-wrap-shift", "severity": "warn", "description": "Disallow conditional white-space wrapping mode toggles that can trigger CLS.", "fixable": false, "category": "css-layout", "plugin": "compilation", "messages": { "conditionalWhiteSpaceShift": "Conditional white-space '{{whiteSpace}}' on '{{tag}}' can reflow text and shift siblings; keep wrapping behavior stable or reserve geometry." } }, { "id": "css-layout-content-visibility-no-intrinsic-size", "severity": "warn", "description": "Require intrinsic size reservation when using content-visibility auto to avoid late layout shifts.", "fixable": false, "category": "css-layout", "plugin": "compilation", "messages": { "missingIntrinsicSize": "`content-visibility: auto` on '{{tag}}' lacks intrinsic size reservation (`contain-intrinsic-size`/min-height/height/aspect-ratio), which can cause CLS." } }, { "id": "css-layout-dynamic-slot-no-reserved-space", "severity": "warn", "description": "Require reserved block space for dynamic content containers to avoid layout shifts.", "fixable": false, "category": "css-layout", "plugin": "compilation", "messages": { "dynamicSlotNoReservedSpace": "Dynamic content container '{{tag}}' does not reserve block space (min-height/height/aspect-ratio/contain-intrinsic-size), which can cause CLS." } }, { "id": "css-layout-font-swap-instability", "severity": "warn", "description": "Require metric overrides for swapping webfonts to reduce layout shifts during font load.", "fixable": false, "category": "css-layout", "plugin": "compilation", "messages": { "unstableFontSwap": "`@font-face` for '{{family}}' uses `font-display: {{display}}` without metric overrides (for example `size-adjust`), which can cause CLS when the webfont swaps in." } }, { "id": "css-layout-overflow-anchor-instability", "severity": "warn", "description": "Disallow overflow-anchor none on dynamic or scrollable containers prone to visible layout shifts.", "fixable": false, "category": "css-layout", "plugin": "compilation", "messages": { "unstableOverflowAnchor": "Element '{{tag}}' sets `overflow-anchor: none` on a {{context}} container; disabling scroll anchoring can amplify visible layout shifts." } }, { "id": "css-layout-overflow-mode-toggle-instability", "severity": "warn", "description": "Disallow conditional overflow mode switches that can introduce scrollbar-induced layout shifts.", "fixable": false, "category": "css-layout", "plugin": "compilation", "messages": { "overflowModeToggle": "Conditional overflow mode changes scrolling ('{{overflow}}') on '{{tag}}' without `scrollbar-gutter: stable`, which can trigger CLS." } }, { "id": "css-layout-scrollbar-gutter-instability", "severity": "warn", "description": "Require stable scrollbar gutters for scrollable containers to reduce layout shifts.", "fixable": false, "category": "css-layout", "plugin": "compilation", "messages": { "missingScrollbarGutter": "Scrollable container '{{tag}}' uses overflow auto/scroll without `scrollbar-gutter: stable`, which can trigger CLS when scrollbars appear." } }, { "id": "css-layout-sibling-alignment-outlier", "severity": "warn", "description": "Detect vertical alignment outliers between sibling elements in shared layout containers.", "fixable": false, "category": "css-layout", "plugin": "compilation", "messages": { "misalignedSibling": "Vertically misaligned '{{subject}}' in '{{parent}}'.{{fix}}{{offsetClause}}" } }, { "id": "css-layout-stateful-box-model-shift", "severity": "warn", "description": "Disallow stateful selector changes that alter element geometry and trigger layout shifts.", "fixable": false, "category": "css-layout", "plugin": "compilation", "messages": { "statefulBoxModelShift": "State selector '{{selector}}' changes layout-affecting '{{property}}'. Keep geometry stable across states to avoid CLS." } }, { "id": "css-layout-transition-layout-property", "severity": "warn", "description": "Disallow transitions that animate layout-affecting geometry properties.", "fixable": false, "category": "css-layout", "plugin": "compilation", "messages": { "transitionLayoutProperty": "Transition '{{property}}' in '{{declaration}}' animates layout-affecting geometry. Prefer transform/opacity to avoid CLS." } }, { "id": "css-layout-unsized-replaced-element", "severity": "warn", "description": "Require stable reserved geometry for replaced media elements to prevent layout shifts.", "fixable": false, "category": "css-layout", "plugin": "compilation", "messages": { "unsizedReplacedElement": "Replaced element '{{tag}}' has no stable reserved size (width/height or aspect-ratio with a dimension), which can cause CLS." } }],
1947
+ "css-property": [{ "id": "css-no-custom-property-cycle", "severity": "error", "description": "Disallow cycles in custom property references.", "fixable": false, "category": "css-property", "plugin": "compilation", "messages": { "variableCycle": "Custom property cycle detected involving `{{name}}`." } }, { "id": "css-no-hardcoded-z-index", "severity": "warn", "description": "Disallow hardcoded positive z-index literals.", "fixable": false, "category": "css-property", "plugin": "compilation", "messages": { "hardcodedZ": "Use a z-index token variable instead of literal `{{value}}`." } }, { "id": "css-no-legacy-vh-100", "severity": "warn", "description": "Disallow 100vh in viewport sizing declarations.", "fixable": false, "category": "css-property", "plugin": "compilation", "messages": { "avoidLegacyVh": "Use 100dvh/100svh instead of `100vh` for mobile-safe viewport sizing." } }, { "id": "css-prefer-logical-properties", "severity": "warn", "description": "Prefer logical properties over physical left/right properties.", "fixable": false, "category": "css-property", "plugin": "compilation", "messages": { "preferLogical": "Use logical property `{{logical}}` instead of `{{physical}}`." } }, { "id": "css-z-index-requires-positioned-context", "severity": "warn", "description": "Require positioned context when using z-index.", "fixable": false, "category": "css-property", "plugin": "compilation", "messages": { "zIndexNoContext": "`z-index` has no guaranteed effect without a positioned context." } }, { "id": "no-important", "severity": "warn", "description": "Disallow !important declarations.", "fixable": false, "category": "css-property", "plugin": "compilation", "messages": { "avoidImportant": "Avoid `!important` on `{{property}}`. It increases override cost and usually signals specificity debt." } }, { "id": "no-unresolved-custom-properties", "severity": "error", "description": "Disallow unresolved custom property references.", "fixable": false, "category": "css-property", "plugin": "compilation", "messages": { "unresolvedCustomProperty": "Custom property reference `{{name}}` is unresolved in `{{property}}`. Define it or provide a fallback value." } }, { "id": "no-unused-custom-properties", "severity": "warn", "description": "Disallow unused CSS custom properties.", "fixable": false, "category": "css-property", "plugin": "compilation", "messages": { "unusedCustomProperty": "Custom property `{{name}}` is never referenced within the project CSS." } }],
1948
+ "css-selector": [{ "id": "no-complex-selectors", "severity": "warn", "description": "Disallow deep selectors that are expensive to match.", "fixable": false, "category": "css-selector", "plugin": "compilation", "messages": { "selectorTooDeep": "Selector `{{selector}}` has depth {{depth}}. Deep selectors increase style recalculation cost and are fragile across component rerenders." } }, { "id": "no-duplicate-selectors", "severity": "warn", "description": "Disallow duplicate selector blocks.", "fixable": false, "category": "css-selector", "plugin": "compilation", "messages": { "duplicateSelector": "Selector `{{selector}}` is duplicated {{count}} times. Merge declarations to avoid cascade ambiguity." } }, { "id": "no-id-selectors", "severity": "warn", "description": "Disallow ID selectors.", "fixable": false, "category": "css-selector", "plugin": "compilation", "messages": { "avoidId": "Avoid ID selector in `{{selector}}`. IDs raise specificity and make component-level styling harder to maintain." } }, { "id": "selector-max-attribute-and-universal", "severity": "off", "description": "Disallow selectors with attribute or universal selectors.", "fixable": false, "category": "css-selector", "plugin": "compilation", "messages": { "tooManyAttributes": "Selector `{{selector}}` uses attribute selector(s). Maximum allowed is 0.", "tooManyUniversals": "Selector `{{selector}}` uses universal selector(s). Maximum allowed is 0." } }, { "id": "selector-max-specificity", "severity": "warn", "description": "Disallow selectors that exceed a specificity threshold.", "fixable": false, "category": "css-selector", "plugin": "compilation", "messages": { "maxSpecificity": "Selector `{{selector}}` specificity {{specificity}} exceeds max {{max}}. Reduce selector weight to keep the cascade predictable." } }],
1949
+ "css-structure": [{ "id": "css-no-empty-rule", "severity": "warn", "description": "Disallow empty CSS rules.", "fixable": false, "category": "css-structure", "plugin": "compilation", "messages": { "emptyRule": "Empty rule `{{selector}}` should be removed." } }, { "id": "css-no-unknown-container-name", "severity": "error", "description": "Disallow unknown named containers in @container queries.", "fixable": false, "category": "css-structure", "plugin": "compilation", "messages": { "unknownContainer": "Unknown container name `{{name}}` in @container query." } }, { "id": "css-no-unused-container-name", "severity": "warn", "description": "Disallow unused named containers.", "fixable": false, "category": "css-structure", "plugin": "compilation", "messages": { "unusedContainer": "Container name `{{name}}` is declared but never queried." } }, { "id": "layer-requirement-for-component-rules", "severity": "warn", "description": "Require style rules to be inside @layer when the file defines layers.", "fixable": false, "category": "css-structure", "plugin": "compilation", "messages": { "missingLayer": "Rule `{{selector}}` is not inside any @layer block while this file uses @layer. Place component rules inside an explicit layer." } }],
1938
1950
  "jsx": [{ "id": "components-return-once", "severity": "error", "description": "Disallow early returns in components. Solid components only run once, and so conditionals should be inside JSX.", "fixable": true, "category": "jsx", "plugin": "solid", "messages": { "noEarlyReturn": "Early returns in Solid components break reactivity because the component function only runs once. Use <Show> or <Switch>/<Match> inside the JSX to conditionally render content instead of returning early from the function.", "noConditionalReturn": "Conditional expressions in return statements break reactivity because Solid components only run once. Wrap the condition in <Show when={...}> for a single condition, or <Switch>/<Match> for multiple conditions." } }, { "id": "jsx-no-duplicate-props", "severity": "error", "description": "Disallow passing the same prop twice in JSX.", "fixable": true, "category": "jsx", "plugin": "solid", "messages": { "noDuplicateProps": "Duplicate prop detected. Each prop should only be specified once; the second value will override the first.", "noDuplicateClass": "Duplicate `class` prop detected. While this might appear to work, it can break unexpectedly because only one class binding is applied. Use `classList` to conditionally apply multiple classes.", "noDuplicateChildren": "Conflicting children: {{used}}. Only one method of setting children is allowed at a time." } }, { "id": "jsx-no-script-url", "severity": "error", "description": "Disallow javascript: URLs.", "fixable": true, "category": "jsx", "plugin": "solid", "messages": { "noJSURL": "Using javascript: URLs is a security risk because it can enable cross-site scripting (XSS) attacks. Use an event handler like onClick instead, or navigate programmatically with useNavigate()." } }, { "id": "jsx-no-undef", "severity": "error", "description": "Disallow references to undefined variables in JSX. Handles custom directives.", "fixable": false, "category": "jsx", "plugin": "solid", "messages": { "customDirectiveUndefined": "Custom directive '{{identifier}}' is not defined. Directives must be imported or declared in scope before use (e.g., `const {{identifier}} = (el, accessor) => { ... }`)." } }, { "id": "jsx-uses-vars", "severity": "warn", "description": "Detect imported components and directives that are never used in JSX.", "fixable": false, "category": "jsx", "plugin": "solid", "messages": { "unusedComponent": "Component '{{name}}' is imported but never used in JSX.", "unusedDirective": "Directive '{{name}}' is imported but never used in JSX." } }, { "id": "no-innerhtml", "severity": "error", "description": "Disallow usage of the innerHTML attribute, which can lead to security vulnerabilities.", "fixable": true, "category": "jsx", "plugin": "solid", "messages": { "dangerous": "Using innerHTML with dynamic content is a security risk. Unsanitized user input can lead to cross-site scripting (XSS) attacks. Use a sanitization library or render content safely.", "conflict": "The innerHTML prop will overwrite all child elements. Remove the children or use innerHTML on an empty element.", "notHtml": "The innerHTML value doesn't appear to be HTML. If you're setting text content, use innerText instead for clarity and safety.", "dangerouslySetInnerHTML": "The dangerouslySetInnerHTML is a React prop that Solid doesn't support. Use innerHTML instead." } }, { "id": "no-unknown-namespaces", "severity": "error", "description": "Enforce using only Solid-specific namespaced attribute names (i.e. `'on:'` in `<div on:click={...} />`).", "fixable": false, "category": "jsx", "plugin": "solid", "messages": { "unknownNamespace": "'{{namespace}}:' is not a recognized Solid namespace. Valid namespaces are: {{validNamespaces}}.", "styleNamespace": "The 'style:' namespace works but is discouraged. Use the style prop with an object instead: style={{ {{property}}: value }}.", "classNamespace": `The 'class:' namespace works but is discouraged. Use the classList prop instead: classList={{ "{{className}}": condition }}.`, "componentNamespace": "Namespaced attributes like '{{namespace}}:' only work on DOM elements, not components. The '{{fullName}}' attribute will be passed as a regular prop named '{{fullName}}'." } }, { "id": "show-truthy-conversion", "severity": "error", "description": "Detect <Show when={expr}> where expr is not explicitly boolean, which may have unexpected truthy/falsy behavior.", "fixable": true, "category": "jsx", "plugin": "solid", "messages": { "showNonBoolean": "<Show when={{{{expr}}}}> uses truthy/falsy conversion. Value '0' or empty string '' will hide content. Use explicit boolean: when={Boolean({{expr}})} or when={{{expr}}} != null}" } }, { "id": "suspense-boundary-missing", "severity": "error", "description": "Detect missing fallback props on Suspense/ErrorBoundary, and lazy components without Suspense wrapper.", "fixable": false, "category": "jsx", "plugin": "solid", "messages": { "suspenseNoFallback": "<Suspense> should have a fallback prop to show while children are loading. Add: fallback={<Loading />}", "errorBoundaryNoFallback": "<ErrorBoundary> should have a fallback prop to show when an error occurs. Add: fallback={(err) => <Error error={err} />}", "lazyNoSuspense": "Lazy component '{{name}}' must be wrapped in a <Suspense> boundary. Add a <Suspense fallback={...}> ancestor." } }, { "id": "validate-jsx-nesting", "severity": "error", "description": "Validates that HTML elements are nested according to the HTML5 specification.", "fixable": false, "category": "jsx", "plugin": "solid", "messages": { "invalidNesting": "Invalid HTML nesting: <{{child}}> cannot be a child of <{{parent}}>. {{reason}}.", "voidElementWithChildren": "<{{parent}}> is a void element and cannot have children. Found <{{child}}> as a child.", "invalidListChild": "<{{child}}> is not a valid direct child of <{{parent}}>. Only <li> elements can be direct children of <ul> and <ol>.", "invalidSelectChild": "<{{child}}> is not a valid direct child of <select>. Only <option> and <optgroup> elements are allowed.", "invalidTableChild": "<{{child}}> is not a valid direct child of <{{parent}}>. Expected: {{expected}}.", "invalidDlChild": "<{{child}}> is not a valid direct child of <dl>. Only <dt>, <dd>, and <div> elements are allowed." } }],
1939
1951
  "performance": [{ "id": "avoid-arguments-object", "severity": "warn", "description": "Disallow arguments object (use rest parameters instead).", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "avoidArguments": "arguments object can prevent V8 optimization. Use rest parameters (...args) instead." } }, { "id": "avoid-chained-array-methods", "severity": "warn", "description": "Flags chained array methods creating 3+ intermediate arrays, or filter().map() pattern.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "avoidChainedArrayMethods": "Chain creates {{count}} intermediate array(s). Consider reduce() or a loop. Chain: {{chain}}", "mapJoinHotPath": "map().join() inside loops allocates intermediate arrays on a hot path. Prefer single-pass string construction." } }, { "id": "avoid-defensive-copy-for-scalar-stat", "severity": "warn", "description": "Disallow defensive array copies passed into scalar statistic calls.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "defensiveCopy": "Defensive copy before scalar statistic '{{stat}}' allocates unnecessarily. Prefer readonly/non-mutating scalar computation." } }, { "id": "avoid-delete-operator", "severity": "warn", "description": "Disallow delete operator on objects (causes V8 deoptimization).", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "avoidDelete": "delete operator transitions object to slow mode. Use `obj.prop = undefined` or destructuring instead." } }, { "id": "avoid-function-allocation-in-hot-loop", "severity": "warn", "description": "Disallow creating closures inside loops.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "closureInLoop": "Function created inside loop allocates new closure per iteration. Consider hoisting or using event delegation." } }, { "id": "avoid-hidden-class-transition", "severity": "warn", "description": "Suggest consistent object shapes to avoid V8 hidden class transitions.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "hiddenClassTransition": "Property '{{property}}' added conditionally to '{{object}}' creates inconsistent object shapes. Initialize '{{property}}' in the object literal." } }, { "id": "avoid-intermediate-map-copy", "severity": "warn", "description": "Disallow temporary Map allocations that are copied key-for-key into another Map.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "intermediateMapCopy": "Intermediate Map '{{tempName}}' is copied into '{{outName}}' key-for-key. Build output directly to avoid extra allocation." } }, { "id": "avoid-megamorphic-property-access", "severity": "warn", "description": "Avoid property access on `any` or wide union types to prevent V8 deoptimization.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "megamorphicAccess": "Property access on `any` or wide union type causes V8 deoptimization. Consider narrowing the type." } }, { "id": "avoid-quadratic-pair-comparison", "severity": "warn", "description": "Disallow nested for-loops over the same collection creating O(n\xB2) pair comparison.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "quadraticPair": "Nested loops over `{{collection}}` create O(n\xB2) pair comparison. Group by a key property first." } }, { "id": "avoid-quadratic-spread", "severity": "error", "description": "Disallow spreading accumulator in reduce callbacks (O(n\xB2) complexity).", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "quadraticSpread": "Spreading accumulator in reduce creates O(n\xB2) complexity. Use push() instead." } }, { "id": "avoid-repeated-indexof-check", "severity": "warn", "description": "Disallow 3+ .indexOf() calls on the same array variable in one function.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "repeatedIndexOf": "{{count}} .indexOf() calls on `{{name}}` in the same function. Use a Set, regex, or single-pass scan instead." } }, { "id": "avoid-slice-sort-pattern", "severity": "warn", "description": "Disallow .slice().sort() and .slice().reverse() chains. Use .toSorted()/.toReversed().", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "sliceSort": ".slice().sort() creates an intermediate array. Use .toSorted() instead.", "sliceReverse": ".slice().reverse() creates an intermediate array. Use .toReversed() instead.", "spreadSort": "[...array].sort() creates an intermediate array. Use .toSorted() instead.", "spreadReverse": "[...array].reverse() creates an intermediate array. Use .toReversed() instead." } }, { "id": "avoid-sparse-arrays", "severity": "warn", "description": "Disallow new Array(n) without fill (creates holey array).", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "sparseArray": "new Array(n) creates a holey array. Use Array.from() or .fill() instead." } }, { "id": "avoid-spread-sort-map-join-pipeline", "severity": "warn", "description": "Disallow [...iterable].sort().map().join() pipelines on hot paths.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "spreadSortMapJoin": "Spread+sort+map+join pipeline allocates multiple intermediates. Prefer single-pass string construction on hot paths." } }, { "id": "bounded-worklist-traversal", "severity": "warn", "description": "Detect queue/worklist traversals with unbounded growth and no guard.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "boundedWorklist": "Worklist '{{name}}' grows via push() without visited set or explicit size bound. Add traversal guard to prevent pathological growth." } }, { "id": "closure-captured-scope", "severity": "warn", "description": "Detect closures returned from scopes containing large allocations that may be retained.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "capturedScope": "Returned closure shares scope with large allocation '{{name}}'. V8 may retain the allocation via scope capture even though the closure doesn't reference it. Move the allocation to an inner scope." } }, { "id": "closure-dom-circular", "severity": "warn", "description": "Detect event handler property assignments that create closure-DOM circular references.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "circularRef": "Event handler on '{{param}}' creates a closure that captures '{{param}}', forming a closure-DOM circular reference. Use addEventListener with a named handler for easier cleanup." } }, { "id": "create-root-dispose", "severity": "warn", "description": "Detect createRoot with unused dispose parameter.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "unusedDispose": "createRoot() dispose parameter is never used. The reactive tree will never be cleaned up. Call dispose(), return it, or pass it to onCleanup()." } }, { "id": "detached-dom-reference", "severity": "warn", "description": "Detect DOM query results stored in module-scoped variables that may hold detached nodes.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "detachedRef": "DOM query result from '{{method}}' stored in module-scoped variable '{{name}}'. If the DOM node is removed, this reference prevents garbage collection. Use a local variable or WeakRef instead." } }, { "id": "effect-outside-root", "severity": "error", "description": "Detect reactive computations created outside a reactive root (no Owner).", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "orphanedEffect": "{{primitive}}() called outside a reactive root. Without an Owner, this computation is never disposed and leaks memory. Wrap in a component, createRoot, or runWithOwner." } }, { "id": "finalization-registry-leak", "severity": "error", "description": "Detect FinalizationRegistry.register() where heldValue references the target.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "selfReference": "FinalizationRegistry.register() heldValue references the target '{{name}}'. This strong reference prevents the target from being garbage collected, defeating the purpose of the registry." } }, { "id": "no-char-array-materialization", "severity": "warn", "description": 'Disallow split(""), Array.from(str), or [...str] in parsing loops.', "fixable": false, "category": "performance", "plugin": "solid", "messages": { "charArrayMaterialization": "Character array materialization via {{pattern}} in parsing loops allocates O(n) extra memory. Prefer index-based scanning." } }, { "id": "no-double-pass-delimiter-count", "severity": "warn", "description": "Disallow split-based delimiter counting followed by additional split passes.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "doublePassDelimiterCount": "Delimiter counting via `split(...).length` plus another `split(...)` repeats full-string passes. Prefer one indexed scan." } }, { "id": "no-full-split-in-hot-parse", "severity": "warn", "description": "Disallow full split() materialization inside hot string parsing loops.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "fullSplitInHotParse": "`split()` inside parsing loops materializes full token arrays each iteration. Prefer cursor/index scanning." } }, { "id": "no-heavy-parser-constructor-in-loop", "severity": "warn", "description": "Disallow constructing heavy parsing helpers inside loops.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "heavyParserConstructor": "`new {{ctor}}(...)` inside parsing loops repeatedly allocates heavy parser helpers. Hoist and reuse instances." } }, { "id": "no-leaked-abort-controller", "severity": "warn", "description": "Detect AbortController in effects without abort() in onCleanup.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "leakedAbort": "new AbortController() inside a reactive effect without onCleanup. Add onCleanup(() => controller.abort())." } }, { "id": "no-leaked-animation-frame", "severity": "warn", "description": "Detect requestAnimationFrame in effects without cancelAnimationFrame in onCleanup.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "leakedRaf": "requestAnimationFrame() inside a reactive effect without onCleanup. Add onCleanup(() => cancelAnimationFrame(id))." } }, { "id": "no-leaked-event-listener", "severity": "warn", "description": "Detect addEventListener in effects without removeEventListener in onCleanup.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "leakedListener": "addEventListener() inside a reactive effect without onCleanup. Each re-run leaks a listener. Add onCleanup(() => removeEventListener(...))." } }, { "id": "no-leaked-observer", "severity": "warn", "description": "Detect Observer APIs in effects without disconnect() in onCleanup.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "leakedObserver": "new {{type}}() inside a reactive effect without onCleanup. Add onCleanup(() => observer.disconnect())." } }, { "id": "no-leaked-subscription", "severity": "warn", "description": "Detect WebSocket/EventSource/BroadcastChannel in effects without close() in onCleanup.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "leakedSubscription": "new {{type}}() inside a reactive effect without onCleanup. Add onCleanup(() => instance.close())." } }, { "id": "no-leaked-timer", "severity": "warn", "description": "Detect setInterval/setTimeout in effects without onCleanup to clear them.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "leakedTimer": "{{setter}}() inside a reactive effect without onCleanup. Each re-run leaks a timer. Add onCleanup(() => {{clearer}}(id))." } }, { "id": "no-loop-string-plus-equals", "severity": "warn", "description": "Disallow repeated string += accumulation in parsing loops.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "loopStringPlusEquals": "Repeated string `+=` in parsing loops creates avoidable allocations. Buffer chunks and join once." } }, { "id": "no-multipass-split-pipeline", "severity": "warn", "description": "Disallow multipass split/map/filter pipelines in parsing code.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "multipassSplit": "`split()` followed by multiple array passes allocates heavily on parsing paths. Prefer single-pass parsing." } }, { "id": "no-per-char-substring-scan", "severity": "warn", "description": "Disallow per-character substring/charAt scanning patterns in loops.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "perCharSubstring": "Per-character `{{method}}()` scanning in loops allocates extra strings. Prefer index + charCodeAt scanning." } }, { "id": "no-repeated-token-normalization", "severity": "warn", "description": "Disallow repeated trim/lower/upper normalization chains on the same token in one function.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "repeatedTokenNormalization": "Repeated token normalization `{{chain}}` on `{{name}}` in one function. Compute once and reuse." } }, { "id": "no-rescan-indexof-loop", "severity": "warn", "description": "Disallow repeated indexOf/includes scans from start in parsing loops.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "rescanIndexOf": "Repeated `{{method}}()` from string start inside loops rescans prior text. Pass a cursor start index." } }, { "id": "no-rest-slice-loop", "severity": "warn", "description": "Disallow repeated self-slice reassignment loops in string parsing code.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "restSliceLoop": "Repeated `{{name}} = {{name}}.{{method}}(...)` in loops creates string churn. Track cursor indexes instead." } }, { "id": "no-shift-splice-head-consume", "severity": "warn", "description": "Disallow shift/splice(0,1) head-consume patterns in loops.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "headConsume": "Head-consuming `{{method}}()` inside loops causes array reindexing costs. Use index cursor iteration instead." } }, { "id": "no-write-only-index", "severity": "warn", "description": "Detect index structures that are written but never queried by key.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "writeOnlyIndex": "Index '{{name}}' is built via writes but never queried by key. Remove it or use direct collection flow." } }, { "id": "prefer-charcode-over-regex-test", "severity": "warn", "description": "Prefer charCodeAt() range checks over regex .test() for single-character classification.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "regexTest": "Regex `{{pattern}}`.test() on a single character. Use charCodeAt() range checks instead." } }, { "id": "prefer-index-scan-over-string-iterator", "severity": "warn", "description": "Prefer index-based string scanning over for-of iteration in ASCII parser code.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "preferIndexScan": "ASCII parsing loops should avoid `for...of` string iteration. Prefer indexed scanning with charCodeAt for lower overhead." } }, { "id": "prefer-lazy-property-access", "severity": "warn", "description": "Suggests moving property access after early returns when not used immediately.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "preferLazyPropertyAccess": "Property '{{propertyName}}' assigned to '{{variableName}}' before early return but not used there. Move assignment after early returns." } }, { "id": "prefer-map-lookup-over-linear-scan", "severity": "warn", "description": "Disallow repeated linear scans over fixed literal collections in hot paths.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "preferMapLookup": "Linear scan over fixed collection '{{name}}' in '{{fnName}}'. Precompute Map/Set lookup for O(1) access." } }, { "id": "prefer-map-over-object-dictionary", "severity": "warn", "description": "Suggest Map for dictionary-like objects with dynamic keys.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "preferMap": "Dynamic key assignment on dictionary object causes hidden class transitions. Consider using Map." } }, { "id": "prefer-precompiled-regex", "severity": "warn", "description": "Prefer hoisting regex literals to module-level constants to avoid repeated compilation.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "inlineRegex": "Regex `{{pattern}}` is compiled on every call. Hoist to a module-level constant." } }, { "id": "prefer-set-has-over-equality-chain", "severity": "warn", "description": "Disallow 4+ guard-style equality checks against string literals on the same variable. Use a Set.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "equalityChain": "{{count}} equality checks against `{{name}}`. Extract literals to a Set and use .has() instead." } }, { "id": "prefer-set-lookup-in-loop", "severity": "warn", "description": "Disallow linear search methods (.includes/.indexOf) on arrays inside loops.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "preferSet": "`.{{method}}()` on `{{name}}` called inside a loop. Convert to a Set for O(1) lookups." } }, { "id": "recursive-timer", "severity": "warn", "description": "Detect setTimeout that recursively calls its enclosing function.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "recursiveTimer": "setTimeout() recursively calls '{{name}}', creating an unbreakable polling loop. Add a termination condition or use setInterval with cleanup." } }, { "id": "self-referencing-store", "severity": "error", "description": "Detect setStore() where the value argument references the store itself.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "selfReference": "setStore() value references the store variable '{{name}}', creating a circular proxy reference. This prevents garbage collection and can cause infinite loops." } }, { "id": "unbounded-collection", "severity": "warn", "description": "Detect module-scoped Map/Set/Array that only grow without removal.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "unboundedCollection": "Module-scoped {{type}} '{{name}}' only uses additive methods ({{methods}}). Without removal or clearing, this grows unbounded. Consider WeakMap, LRU eviction, or periodic clear()." } }, { "id": "unbounded-signal-accumulation", "severity": "warn", "description": "Detect signal setters that accumulate data without truncation via spread+append pattern.", "fixable": false, "category": "performance", "plugin": "solid", "messages": { "unbounded": "Signal setter '{{name}}' accumulates data without bounds. The array grows monotonically via spread+append. Add truncation (e.g. prev.slice(-limit)) to prevent unbounded growth." } }],
1940
1952
  "reactivity": [{ "id": "async-tracked", "severity": "error", "description": "Disallow async functions in tracked scopes (createEffect, createMemo, etc.)", "fixable": false, "category": "reactivity", "plugin": "solid", "messages": { "asyncCreateEffect": "Async function{{fnName}} in createEffect loses tracking after await. Read all signals before the first await, or use createResource for async data fetching.", "asyncCreateMemo": "Async function{{fnName}} in createMemo won't work correctly. createMemo must be synchronous. For async derived data, use createResource instead.", "asyncCreateComputed": "Async function{{fnName}} in createComputed won't track properly. createComputed must be synchronous\u2014signal reads after await won't trigger re-computation.", "asyncCreateRenderEffect": "Async function{{fnName}} in createRenderEffect breaks DOM update timing. createRenderEffect must be synchronous. Move async work to onMount or createResource.", "asyncTrackedGeneric": "Async function{{fnName}} in {{source}} won't track reactivity after await. Solid's tracking only works synchronously\u2014signal reads after await are ignored." } }, { "id": "children-helper-misuse", "severity": "error", "description": "Detect misuse of the children() helper that causes unnecessary re-computation or breaks reactivity", "fixable": false, "category": "reactivity", "plugin": "solid", "messages": { "multipleChildrenCalls": "The children() helper should only be called once per component. Each call re-resolves children, causing unnecessary computation. Store the result and reuse the accessor.", "directChildrenAccess": "Access props.children through the children() helper in reactive contexts. Direct access won't properly resolve or track children. Use: const resolved = children(() => props.children);" } }, { "id": "cleanup-scope", "severity": "error", "description": "Detect onCleanup called outside of a valid reactive scope", "fixable": false, "category": "reactivity", "plugin": "solid", "messages": { "cleanupOutsideScope": "onCleanup() called outside a reactive scope ({{location}}). The cleanup function will never execute unless this code runs within a component, effect, createRoot, or runWithOwner." } }, { "id": "derived-signal", "severity": "error", "description": "Detect functions that capture reactive values but are called in untracked contexts", "fixable": false, "category": "reactivity", "plugin": "solid", "messages": { "moduleScopeInit": "Assigning '{{fnName}}()' to '{{varName}}' at module scope runs once at startup. It captures {{vars}} which won't trigger updates.", "moduleScopeCall": "'{{fnName}}()' at module scope executes once when the module loads. It captures {{vars}}\u2014changes won't cause this to re-run.", "componentTopLevelInit": "'{{fnName}}()' assigned to '{{varName}}' in '{{componentName}}' captures a one-time snapshot of {{vars}}. Changes won't update '{{varName}}'. Call in JSX or use createMemo().", "componentTopLevelCall": "'{{fnName}}()' at top-level of '{{componentName}}' runs once and captures a snapshot of {{vars}}. Changes won't re-run this. Move inside JSX: {{{fnName}}()} or wrap with createMemo().", "utilityFnCall": "'{{fnName}}()' inside '{{utilityName}}' won't be reactive. Call '{{utilityName}}' from a tracked scope (createEffect, JSX), or pass {{vars}} as parameters.", "syncCallbackCall": "'{{fnName}}()' inside {{methodName}}() callback runs outside a tracking scope. The result captures a snapshot of {{vars}} that won't update.", "untrackedCall": "'{{fnName}}()' called in an untracked context. It captures {{vars}} which won't trigger updates here. Move to JSX or a tracked scope." } }, { "id": "effect-as-memo", "severity": "error", "description": "Detect createEffect that only sets a derived signal value, which should be createMemo instead", "fixable": true, "category": "reactivity", "plugin": "solid", "messages": { "effectAsMemo": "This createEffect only computes a derived value. Use createMemo() instead: const {{signalName}} = createMemo(() => {{expression}});" } }, { "id": "effect-as-mount", "severity": "error", "description": "Detect createEffect/createRenderEffect with no reactive dependencies that should be onMount instead", "fixable": true, "category": "reactivity", "plugin": "solid", "messages": { "effectAsMount": "This {{primitive}} has no reactive dependencies and runs only once. Use onMount() for initialization logic that doesn't need to re-run." } }, { "id": "inline-component", "severity": "error", "description": "Detect component functions defined inside other components, which causes remount on every parent update", "fixable": false, "category": "reactivity", "plugin": "solid", "messages": { "inlineComponent": "Component '{{name}}' is defined inside another component. This creates a new component type on every render, causing unmount/remount. Move the component definition outside." } }, { "id": "no-top-level-signal-call", "severity": "error", "description": "Disallow calling signals at component top-level (captures stale snapshots)", "fixable": false, "category": "reactivity", "plugin": "solid", "messages": { "assignedToVar": "'{{name}}()' assigned to '{{varName}}' in {{componentName}} captures a one-time snapshot. '{{varName}}' won't update when {{name}} changes. Use createMemo(): `const {{varName}} = createMemo(() => {{name}}());`", "computedValue": "'{{name}}()' in computation at top-level of {{componentName}} captures a stale snapshot. Wrap with createMemo(): `const {{varName}} = createMemo(() => /* computation using {{name}}() */);`", "templateLiteral": "'{{name}}()' in template literal at top-level of {{componentName}} captures a stale snapshot. Use createMemo() or compute directly in JSX: `{`Hello, ${{{name}}()}!`}`", "destructuring": "Destructuring '{{name}}()' at top-level of {{componentName}} captures a stale snapshot. Access properties in JSX or createMemo(): `{{{name}}().propertyName}`", "objectLiteral": "'{{name}}()' in object literal at top-level of {{componentName}} captures a stale snapshot. Use createMemo() for the object, or spread in JSX.", "arrayCreation": "'{{name}}()' in array creation at top-level of {{componentName}} captures a stale snapshot. Wrap with createMemo(): `const items = createMemo(() => Array.from(...));`", "earlyReturn": "'{{name}}()' in early return at top-level of {{componentName}} captures a stale snapshot. Use <Show when={{{name}}()}> for conditional rendering instead.", "conditionalAssign": "'{{name}}()' in ternary at top-level of {{componentName}} captures a stale snapshot. Use createMemo() or compute in JSX: `{{{name}}() ? 'Yes' : 'No'}`", "functionArgument": "'{{name}}()' passed as argument at top-level of {{componentName}} captures a stale snapshot. Move to createEffect() or compute in JSX.", "syncCallback": "'{{name}}()' inside {{methodName}}() at top-level of {{componentName}} captures a stale snapshot. Wrap the entire computation in createMemo(): `const result = createMemo(() => items.{{methodName}}(...));`", "topLevelCall": "'{{name}}()' at top-level of {{componentName}} captures a one-time snapshot. Changes to {{name}} won't update the result. Call directly in JSX or wrap in createMemo()." } }, { "id": "ref-early-access", "severity": "error", "description": "Detect accessing refs before they are assigned (before mount)", "fixable": false, "category": "reactivity", "plugin": "solid", "messages": { "refBeforeMount": "Ref '{{name}}' is accessed before component mounts. Refs are undefined until after mount. Access in onMount(), createEffect(), or event handlers." } }, { "id": "resource-access-unchecked", "severity": "error", "description": "Detect accessing resource data without checking loading/error state.", "fixable": false, "category": "reactivity", "plugin": "solid", "messages": { "resourceUnchecked": "Accessing resource '{{name}}' without checking loading/error state may return undefined. Wrap in <Show when={!{{name}}.loading}> or <Suspense>." } }, { "id": "resource-implicit-suspense", "severity": "warn", "description": "Detect createResource that implicitly triggers or permanently breaks Suspense boundaries.", "fixable": true, "category": "reactivity", "plugin": "solid", "messages": { "loadingMismatch": "createResource '{{name}}' has no initialValue but uses {{name}}.loading for manual loading UI. Suspense intercepts before your loading UI renders \u2014 the component is unmounted before the <Show>/<Switch> evaluates. Replace createResource with onMount + createSignal to decouple from Suspense entirely.", "conditionalSuspense": "createResource '{{name}}' is inside a conditional mount point ({{mountTag}}) with a distant Suspense boundary. The SuspenseContext increment fires when the fetcher's Promise is pending and unmounts the entire page subtree \u2014 initialValue does NOT prevent this. Replace createResource with onMount + createSignal to avoid Suspense interaction.", "missingErrorBoundary": "createResource '{{name}}' has no <ErrorBoundary> between its component and the nearest <Suspense>. When the fetcher throws (network error, 401/403/503, timeout), the error propagates to Suspense which has no error handling \u2014 the boundary breaks permanently. Wrap the component in <ErrorBoundary> or replace createResource with onMount + createSignal and catch errors in the fetcher." } }, { "id": "resource-refetch-loop", "severity": "error", "description": "Detect refetch() calls inside createEffect which can cause infinite loops", "fixable": false, "category": "reactivity", "plugin": "solid", "messages": { "refetchInEffect": "Calling {{name}}.refetch() inside createEffect may cause infinite loops. The resource tracks its own dependencies. Move refetch to an event handler or use on() to control dependencies." } }, { "id": "signal-call", "severity": "error", "description": "Require signals to be called as functions when used in tracked contexts", "fixable": true, "category": "reactivity", "plugin": "solid", "messages": { "signalInJsxText": "Signal '{{name}}' in JSX text should be called: {{{name}}()}. Without (), you're rendering the function, not its value.", "signalInJsxAttribute": "Signal '{{name}}' in JSX attribute should be called: {{attr}}={{{name}}()}. Without (), the attribute won't update reactively.", "signalInTernary": "Signal '{{name}}' in ternary should be called: {{name}}() ? ... : .... The condition won't react to changes without ().", "signalInLogical": "Signal '{{name}}' in logical expression should be called: {{name}}() && .... Without (), this always evaluates to truthy (functions are truthy).", "signalInComparison": "Signal '{{name}}' in comparison should be called: {{name}}() === .... Comparing functions always returns false.", "signalInArithmetic": "Signal '{{name}}' in arithmetic should be called: {{name}}() + .... Math on functions produces NaN.", "signalInTemplate": "Signal '{{name}}' in template literal should be called: `...${{{name}}()}...`. Without (), you're embedding '[Function]'.", "signalInTrackedScope": "Signal '{{name}}' in {{where}} should be called: {{name}}(). Without (), reactivity is lost.", "badSignal": "The reactive variable '{{name}}' should be called as a function when used in {{where}}." } }, { "id": "signal-in-loop", "severity": "error", "description": "Detect problematic signal usage inside For/Index loop callbacks", "fixable": false, "category": "reactivity", "plugin": "solid", "messages": { "signalInLoop": "Creating signals inside <{{component}}> callback creates new signals on each render. Use a store at the parent level, or derive state from the index.", "signalCallInvariant": "Signal '{{name}}' called inside <{{component}}> produces the same value for every item. Extract to a variable or memoize with createMemo() before the loop.", "derivedCallInvariant": "'{{name}}()' inside <{{component}}> captures {{captures}} but doesn't use the loop item. Extract the call before the loop or pass the item as a parameter." } }, { "id": "store-reactive-break", "severity": "error", "description": "Detect patterns that break store reactivity: spreading stores, top-level property extraction, or destructuring", "fixable": false, "category": "reactivity", "plugin": "solid", "messages": { "storeSpread": "Spreading a store ({...store}) creates a static snapshot that won't update. Access store properties directly in JSX or tracked contexts.", "storeTopLevelAccess": "Accessing store property '{{property}}' at component top-level captures the value once. Access store.{{property}} directly in JSX or wrap in createMemo().", "storeDestructure": "Destructuring a store breaks reactivity. Access properties via store.{{property}} instead of destructuring." } }, { "id": "transition-pending-unchecked", "severity": "error", "description": "Detect useTransition usage without handling the isPending state", "fixable": false, "category": "reactivity", "plugin": "solid", "messages": { "pendingUnchecked": "useTransition returns [isPending, startTransition]. The isPending state should be used to show loading UI during transitions." } }],
@@ -1955,4 +1967,4 @@ export {
1955
1967
  RULE_CATEGORIES,
1956
1968
  getRule
1957
1969
  };
1958
- //# sourceMappingURL=chunk-NFDA6LAI.js.map
1970
+ //# sourceMappingURL=chunk-TNKZGWOR.js.map