@agilebot/eslint-plugin 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
package/dist/index.d.ts CHANGED
@@ -1,20 +1,10 @@
1
- declare namespace _default {
2
- export { rules };
3
- export namespace configs {
1
+ export default plugin;
2
+ declare namespace plugin {
3
+ let rules: import('eslint').Linter.RulesRecord;
4
+ namespace configs {
4
5
  namespace recommended {
5
6
  export let plugins: string[];
6
- let rules_1: {
7
- '@agilebot/no-unnecessary-template-literals': string;
8
- '@agilebot/no-async-array-methods': string;
9
- '@agilebot/react-prefer-named-property-access': string;
10
- '@agilebot/react-hook-use-ref': string;
11
- '@agilebot/react-prefer-sx-prop': string;
12
- '@agilebot/tss-unused-classes': string;
13
- '@agilebot/tss-no-color-value': string;
14
- '@agilebot/tss-class-naming': string;
15
- '@agilebot/import-enforce-icon-alias': string;
16
- '@agilebot/import-monorepo': string;
17
- };
7
+ let rules_1: import('eslint').Linter.RulesRecord;
18
8
  export { rules_1 as rules };
19
9
  export namespace settings {
20
10
  namespace react {
@@ -24,8 +14,3 @@ declare namespace _default {
24
14
  }
25
15
  }
26
16
  }
27
- export default _default;
28
- /**
29
- * @type {import('eslint').Linter.RulesRecord}
30
- */
31
- declare const rules: import('eslint').Linter.RulesRecord;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license @agilebot/eslint-plugin v0.3.3
2
+ * @license @agilebot/eslint-plugin v0.3.4
3
3
  *
4
4
  * Copyright (c) Agilebot, Inc. and its affiliates.
5
5
  *
@@ -41,7 +41,10 @@ var enforceIconAlias = {
41
41
  recommended: true
42
42
  },
43
43
  fixable: 'code',
44
- schema: []
44
+ schema: [],
45
+ messages: {
46
+ iconAlias: 'Import for {{ name }} should be aliased.'
47
+ }
45
48
  },
46
49
  create(context) {
47
50
  return {
@@ -59,7 +62,10 @@ var enforceIconAlias = {
59
62
  if (specifier.imported.name === specifier.local.name) {
60
63
  context.report({
61
64
  node,
62
- message: `Import for ${node.source.value} should be aliased.`,
65
+ messageId: 'iconAlias',
66
+ data: {
67
+ name: node.source.value
68
+ },
63
69
  fix: fixer =>
64
70
  fixer.replaceText(
65
71
  specifier,
@@ -86,7 +92,10 @@ var monorepo = {
86
92
  recommended: true
87
93
  },
88
94
  fixable: 'code',
89
- schema: []
95
+ schema: [],
96
+ messages: {
97
+ monorepoImport: 'Import for {{ module }} should not contains src folder.'
98
+ }
90
99
  },
91
100
  create(context) {
92
101
  return {
@@ -110,7 +119,10 @@ var monorepo = {
110
119
  if (values[2] === 'src') {
111
120
  context.report({
112
121
  node,
113
- message: `Import for ${node.source.value} should not contains src folder.`,
122
+ messageId: 'monorepoImport',
123
+ data: {
124
+ module: node.source.value
125
+ },
114
126
  fix: fixer => {
115
127
  const correctedPath = values
116
128
  .filter((_, index) => index !== 2)
@@ -240,8 +252,7 @@ var idMissing = {
240
252
  meta: {
241
253
  docs: {
242
254
  description: 'Validates intl message ids are in locale file',
243
- category: 'Intl',
244
- recommended: true
255
+ category: 'Intl'
245
256
  },
246
257
  fixable: undefined,
247
258
  schema: []
@@ -322,8 +333,7 @@ var idPrefix = {
322
333
  meta: {
323
334
  docs: {
324
335
  description: 'Validates intl message ids has correct prefixes',
325
- category: 'Intl',
326
- recommended: true
336
+ category: 'Intl'
327
337
  },
328
338
  fixable: undefined,
329
339
  schema: [
@@ -400,8 +410,7 @@ var idUnused = {
400
410
  meta: {
401
411
  docs: {
402
412
  description: 'Finds unused intl message ids in locale file',
403
- category: 'Intl',
404
- recommended: true
413
+ category: 'Intl'
405
414
  },
406
415
  fixable: undefined,
407
416
  schema: []
@@ -483,8 +492,7 @@ var noDefault = {
483
492
  meta: {
484
493
  docs: {
485
494
  description: 'Validates defaultMessage is not used with react-intl',
486
- category: 'Intl',
487
- recommended: true
495
+ category: 'Intl'
488
496
  },
489
497
  fixable: undefined,
490
498
  schema: []
@@ -521,387 +529,56 @@ var noDefault = {
521
529
  }
522
530
  };
523
531
 
524
- function getBasicIdentifier(node) {
525
- if (node.type === 'Identifier') {
526
- return node.name;
527
- }
528
- if (node.type === 'Literal') {
529
- return node.value;
530
- }
531
- if (node.type === 'TemplateLiteral') {
532
- if (node.expressions.length > 0) {
533
- return null;
534
- }
535
- return node.quasis[0].value.raw;
536
- }
537
- return null;
538
- }
539
- function getBaseIdentifier(node) {
540
- switch (node.type) {
541
- case 'Identifier': {
542
- return node;
543
- }
544
- case 'CallExpression': {
545
- return getBaseIdentifier(node.callee);
546
- }
547
- case 'MemberExpression': {
548
- return getBaseIdentifier(node.object);
549
- }
550
- }
551
- return null;
552
- }
553
- function getStyesObj(node) {
554
- const isMakeStyles = node.callee.name === 'makeStyles';
555
- const isModernApi =
556
- node.callee.type === 'MemberExpression' &&
557
- node.callee.property.name === 'create' &&
558
- getBaseIdentifier(node.callee.object) &&
559
- getBaseIdentifier(node.callee.object).name === 'tss';
560
- if (!isMakeStyles && !isModernApi) {
561
- return;
562
- }
563
- const styles = (() => {
564
- if (isMakeStyles) {
565
- return node.parent.arguments[0];
566
- }
567
- if (isModernApi) {
568
- return node.callee.parent.arguments[0];
569
- }
570
- })();
571
- if (!styles) {
572
- return;
573
- }
574
- switch (styles.type) {
575
- case 'ObjectExpression':
576
- return styles;
577
- case 'ArrowFunctionExpression':
578
- {
579
- const { body } = styles;
580
- switch (body.type) {
581
- case 'ObjectExpression':
582
- return body;
583
- case 'BlockStatement': {
584
- let stylesObj;
585
- body.body.forEach(bodyNode => {
586
- if (
587
- bodyNode.type === 'ReturnStatement' &&
588
- bodyNode.argument.type === 'ObjectExpression'
589
- ) {
590
- stylesObj = bodyNode.argument;
591
- }
592
- });
593
- return stylesObj;
594
- }
595
- }
596
- }
597
- break;
598
- }
599
- }
600
-
601
- var classNaming = {
602
- meta: {
603
- type: 'problem'
604
- },
605
- create: function rule(context) {
606
- return {
607
- CallExpression(node) {
608
- const stylesObj = getStyesObj(node);
609
- if (stylesObj === undefined) {
610
- return;
611
- }
612
- stylesObj.properties.forEach(property => {
613
- if (property.computed) {
614
- return;
615
- }
616
- if (
617
- property.type === 'ExperimentalSpreadProperty' ||
618
- property.type === 'SpreadElement'
619
- ) {
620
- return;
621
- }
622
- const className = property.key.value || property.key.name;
623
- if (!eslintUtils.isCamelCase(className)) {
624
- context.report({
625
- node: property,
626
- message: `Class \`${className}\` must be camelCase in TSS.`
627
- });
628
- }
629
- });
630
- }
631
- };
632
- }
633
- };
634
-
635
- var noColorValue = {
532
+ var betterExhaustiveDeps = {
636
533
  meta: {
637
- type: 'problem',
534
+ type: 'suggestion',
638
535
  docs: {
639
536
  description:
640
- 'Enforce the use of color variables instead of color codes within TSS'
641
- }
642
- },
643
- create: function (context) {
644
- const parserOptions = context.parserOptions;
645
- if (!parserOptions || !parserOptions.project) {
646
- return {};
647
- }
648
- return {
649
- CallExpression(node) {
650
- const stylesObj = getStyesObj(node);
651
- if (!stylesObj) {
652
- return;
653
- }
654
- function checkColorLiteral(value) {
655
- if (value.type === 'Literal' && typeof value.value === 'string') {
656
- const colorCodePattern =
657
- /#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|rgb\?\(\s*(\d{1,3}\s*,\s*){2}\d{1,3}(?:\s*,\s*\d*(?:\.\d+)?)?\s*\)/g;
658
- const isColorCode = colorCodePattern.test(value.value);
659
- if (isColorCode) {
660
- context.report({
661
- node: value,
662
- message: 'Use color variables instead of color codes in TSS.'
663
- });
664
- }
665
- }
666
- }
667
- function loopStylesObj(obj) {
668
- if (obj && obj.type === 'ObjectExpression') {
669
- obj.properties.forEach(property => {
670
- if (property.type === 'Property' && property.value) {
671
- if (property.value.type === 'ObjectExpression') {
672
- loopStylesObj(property.value);
673
- } else {
674
- checkColorLiteral(property.value);
537
+ 'verifies the list of dependencies for Hooks like useEffect and similar',
538
+ url: 'https://github.com/facebook/react/issues/14920'
539
+ },
540
+ fixable: 'code',
541
+ hasSuggestions: true,
542
+ schema: [
543
+ {
544
+ type: 'object',
545
+ additionalProperties: false,
546
+ enableDangerousAutofixThisMayCauseInfiniteLoops: false,
547
+ properties: {
548
+ additionalHooks: {
549
+ type: 'string'
550
+ },
551
+ enableDangerousAutofixThisMayCauseInfiniteLoops: {
552
+ type: 'boolean'
553
+ },
554
+ staticHooks: {
555
+ type: 'object',
556
+ additionalProperties: {
557
+ oneOf: [
558
+ {
559
+ type: 'boolean'
560
+ },
561
+ {
562
+ type: 'array',
563
+ items: {
564
+ type: 'boolean'
565
+ }
566
+ },
567
+ {
568
+ type: 'object',
569
+ additionalProperties: {
570
+ type: 'boolean'
571
+ }
675
572
  }
676
- }
677
- });
573
+ ]
574
+ }
575
+ },
576
+ checkMemoizedVariableIsStatic: {
577
+ type: 'boolean'
678
578
  }
679
579
  }
680
- loopStylesObj(stylesObj);
681
580
  }
682
- };
683
- }
684
- };
685
-
686
- var unusedClasses = {
687
- meta: {
688
- type: 'problem'
689
- },
690
- create: function rule(context) {
691
- const usedClasses = {};
692
- const definedClasses = {};
693
- return {
694
- CallExpression(node) {
695
- const stylesObj = getStyesObj(node);
696
- if (stylesObj === undefined) {
697
- return;
698
- }
699
- stylesObj.properties.forEach(property => {
700
- if (property.computed) {
701
- return;
702
- }
703
- if (
704
- property.type === 'ExperimentalSpreadProperty' ||
705
- property.type === 'SpreadElement'
706
- ) {
707
- return;
708
- }
709
- definedClasses[property.key.value || property.key.name] = property;
710
- });
711
- },
712
- MemberExpression(node) {
713
- if (
714
- node.object.type === 'Identifier' &&
715
- node.object.name === 'classes'
716
- ) {
717
- const whichClass = getBasicIdentifier(node.property);
718
- if (whichClass) {
719
- usedClasses[whichClass] = true;
720
- }
721
- return;
722
- }
723
- const classIdentifier = getBasicIdentifier(node.property);
724
- if (!classIdentifier) {
725
- return;
726
- }
727
- if (classIdentifier !== 'classes') {
728
- return;
729
- }
730
- const { parent } = node;
731
- if (parent.type !== 'MemberExpression') {
732
- return;
733
- }
734
- if (
735
- node.object.object &&
736
- node.object.object.type !== 'ThisExpression'
737
- ) {
738
- return;
739
- }
740
- const propsIdentifier = getBasicIdentifier(parent.object);
741
- if (propsIdentifier && propsIdentifier !== 'props') {
742
- return;
743
- }
744
- if (!propsIdentifier && parent.object.type !== 'MemberExpression') {
745
- return;
746
- }
747
- if (parent.parent.type === 'MemberExpression') {
748
- return;
749
- }
750
- const parentClassIdentifier = getBasicIdentifier(parent.property);
751
- if (parentClassIdentifier) {
752
- usedClasses[parentClassIdentifier] = true;
753
- }
754
- },
755
- 'Program:exit': () => {
756
- Object.keys(definedClasses).forEach(definedClassKey => {
757
- if (!usedClasses[definedClassKey]) {
758
- context.report({
759
- node: definedClasses[definedClassKey],
760
- message: `Class \`${definedClassKey}\` is unused`
761
- });
762
- }
763
- });
764
- }
765
- };
766
- }
767
- };
768
-
769
- var noAsyncArrayMethods = {
770
- meta: {
771
- docs: {
772
- description:
773
- 'No async callback for Array methods forEach, map, filter, reduce, some, every, etc.',
774
- category: 'Array',
775
- recommended: true
776
- },
777
- fixable: undefined,
778
- schema: []
779
- },
780
- create: function (context) {
781
- return {
782
- ExpressionStatement: function (node) {
783
- const notAllowedArrayMethods = [
784
- 'forEach',
785
- 'filter',
786
- 'some',
787
- 'every',
788
- 'map',
789
- 'reduce',
790
- 'reduceRight',
791
- 'flatMap',
792
- 'find',
793
- 'findIndex',
794
- 'findLast',
795
- 'findLastIndex'
796
- ];
797
- const { callee } = node.expression;
798
- if (!callee || !callee.property || !callee.property.name) {
799
- return;
800
- }
801
- if (notAllowedArrayMethods.includes(callee.property.name)) {
802
- const functionArguments = node.expression.arguments.find(n => {
803
- return ['ArrowFunctionExpression', 'FunctionExpression'].includes(
804
- n.type
805
- );
806
- });
807
- if (functionArguments && functionArguments.async) {
808
- context.report({
809
- node,
810
- message: `No async function in method '${callee.property.name}'`
811
- });
812
- }
813
- }
814
- }
815
- };
816
- }
817
- };
818
-
819
- var noUnnecessaryTemplateLiterals = {
820
- meta: {
821
- type: 'problem',
822
- docs: {
823
- description: 'Check if a template string contains only one ${}',
824
- recommended: true
825
- },
826
- fixable: 'code',
827
- schema: []
828
- },
829
- create(context) {
830
- return {
831
- TemplateLiteral(node) {
832
- const code = context.sourceCode.getText(node);
833
- if (
834
- code.startsWith('`${') &&
835
- code.endsWith('}`') &&
836
- code.split('${').length === 2
837
- ) {
838
- context.report({
839
- node,
840
- message: 'Unnecessary template string with only one ${}.',
841
- fix(fixer) {
842
- return fixer.replaceText(
843
- node,
844
- code.substring(3, code.length - 2)
845
- );
846
- }
847
- });
848
- }
849
- }
850
- };
851
- }
852
- };
853
-
854
- var betterExhaustiveDeps = {
855
- meta: {
856
- type: 'suggestion',
857
- docs: {
858
- description:
859
- 'verifies the list of dependencies for Hooks like useEffect and similar',
860
- recommended: true,
861
- url: 'https://github.com/facebook/react/issues/14920'
862
- },
863
- fixable: 'code',
864
- hasSuggestions: true,
865
- schema: [
866
- {
867
- type: 'object',
868
- additionalProperties: false,
869
- enableDangerousAutofixThisMayCauseInfiniteLoops: false,
870
- properties: {
871
- additionalHooks: {
872
- type: 'string'
873
- },
874
- enableDangerousAutofixThisMayCauseInfiniteLoops: {
875
- type: 'boolean'
876
- },
877
- staticHooks: {
878
- type: 'object',
879
- additionalProperties: {
880
- oneOf: [
881
- {
882
- type: 'boolean'
883
- },
884
- {
885
- type: 'array',
886
- items: {
887
- type: 'boolean'
888
- }
889
- },
890
- {
891
- type: 'object',
892
- additionalProperties: {
893
- type: 'boolean'
894
- }
895
- }
896
- ]
897
- }
898
- },
899
- checkMemoizedVariableIsStatic: {
900
- type: 'boolean'
901
- }
902
- }
903
- }
904
- ]
581
+ ]
905
582
  },
906
583
  create(context) {
907
584
  const additionalHooks =
@@ -2394,7 +2071,7 @@ var hookUseRef = {
2394
2071
  meta: {
2395
2072
  docs: {
2396
2073
  description: 'Ensure naming of useRef hook value.',
2397
- recommended: false
2074
+ recommended: true
2398
2075
  },
2399
2076
  schema: [],
2400
2077
  type: 'suggestion',
@@ -2455,7 +2132,8 @@ var preferNamedPropertyAccess = utils.ESLintUtils.RuleCreator.withoutDocs({
2455
2132
  fixable: 'code',
2456
2133
  docs: {
2457
2134
  description:
2458
- 'Enforce importing each member of React namespace separately instead of accessing them through React namespace'
2135
+ 'Enforce importing each member of React namespace separately instead of accessing them through React namespace',
2136
+ recommended: 'recommended'
2459
2137
  },
2460
2138
  messages: {
2461
2139
  illegalReactPropertyAccess:
@@ -2509,7 +2187,7 @@ var preferSxProp = {
2509
2187
  docs: {
2510
2188
  description: 'Prefer using sx prop instead of inline styles',
2511
2189
  category: 'Best Practices',
2512
- recommended: false
2190
+ recommended: true
2513
2191
  },
2514
2192
  messages: {
2515
2193
  preferSxProp:
@@ -2582,55 +2260,458 @@ var preferSxProp = {
2582
2260
  }
2583
2261
  };
2584
2262
 
2585
- var ruleFiles = /*#__PURE__*/Object.freeze({
2586
- __proto__: null,
2587
- rules_import_enforce_icon_alias: enforceIconAlias,
2588
- rules_import_monorepo: monorepo,
2589
- rules_intl_id_missing: idMissing,
2590
- rules_intl_id_prefix: idPrefix,
2591
- rules_intl_id_unused: idUnused,
2592
- rules_intl_no_default: noDefault,
2593
- rules_react_better_exhaustive_deps: betterExhaustiveDeps,
2594
- rules_react_hook_use_ref: hookUseRef,
2595
- rules_react_prefer_named_property_access: preferNamedPropertyAccess,
2596
- rules_react_prefer_sx_prop: preferSxProp,
2597
- rules_tss_class_naming: classNaming,
2598
- rules_tss_no_color_value: noColorValue,
2599
- rules_tss_unused_classes: unusedClasses,
2600
- rules_unprefixed_no_async_array_methods: noAsyncArrayMethods,
2601
- rules_unprefixed_no_unnecessary_template_literals: noUnnecessaryTemplateLiterals
2602
- });
2603
-
2604
- const rules = {};
2605
- Object.keys(ruleFiles).forEach(key => {
2606
- const ruleKey = key.replace(/^rules_/, '').replace(/^unprefixed_/, '');
2607
- const finalKey = ruleKey.replace(/_/g, '-');
2608
- rules[finalKey] = ruleFiles[key];
2609
- });
2610
- var index = {
2611
- rules,
2612
- configs: {
2613
- recommended: {
2614
- plugins: ['@agilebot'],
2615
- rules: {
2616
- '@agilebot/no-unnecessary-template-literals': 'error',
2617
- '@agilebot/no-async-array-methods': 'error',
2618
- '@agilebot/react-prefer-named-property-access': 'error',
2619
- '@agilebot/react-hook-use-ref': 'warn',
2620
- '@agilebot/react-prefer-sx-prop': 'error',
2621
- '@agilebot/tss-unused-classes': 'warn',
2622
- '@agilebot/tss-no-color-value': 'error',
2623
- '@agilebot/tss-class-naming': 'error',
2624
- '@agilebot/import-enforce-icon-alias': 'error',
2625
- '@agilebot/import-monorepo': 'error'
2626
- },
2627
- settings: {
2628
- react: {
2629
- version: '18.0.0'
2630
- }
2631
- }
2263
+ function getBasicIdentifier(node) {
2264
+ if (node.type === 'Identifier') {
2265
+ return node.name;
2266
+ }
2267
+ if (node.type === 'Literal') {
2268
+ return node.value;
2269
+ }
2270
+ if (node.type === 'TemplateLiteral') {
2271
+ if (node.expressions.length > 0) {
2272
+ return null;
2632
2273
  }
2274
+ return node.quasis[0].value.raw;
2633
2275
  }
2634
- };
2276
+ return null;
2277
+ }
2278
+ function getBaseIdentifier(node) {
2279
+ switch (node.type) {
2280
+ case 'Identifier': {
2281
+ return node;
2282
+ }
2283
+ case 'CallExpression': {
2284
+ return getBaseIdentifier(node.callee);
2285
+ }
2286
+ case 'MemberExpression': {
2287
+ return getBaseIdentifier(node.object);
2288
+ }
2289
+ }
2290
+ return null;
2291
+ }
2292
+ function getStyesObj(node) {
2293
+ const isMakeStyles = node.callee.name === 'makeStyles';
2294
+ const isModernApi =
2295
+ node.callee.type === 'MemberExpression' &&
2296
+ node.callee.property.name === 'create' &&
2297
+ getBaseIdentifier(node.callee.object) &&
2298
+ getBaseIdentifier(node.callee.object).name === 'tss';
2299
+ if (!isMakeStyles && !isModernApi) {
2300
+ return;
2301
+ }
2302
+ const styles = (() => {
2303
+ if (isMakeStyles) {
2304
+ return node.parent.arguments[0];
2305
+ }
2306
+ if (isModernApi) {
2307
+ return node.callee.parent.arguments[0];
2308
+ }
2309
+ })();
2310
+ if (!styles) {
2311
+ return;
2312
+ }
2313
+ switch (styles.type) {
2314
+ case 'ObjectExpression':
2315
+ return styles;
2316
+ case 'ArrowFunctionExpression':
2317
+ {
2318
+ const { body } = styles;
2319
+ switch (body.type) {
2320
+ case 'ObjectExpression':
2321
+ return body;
2322
+ case 'BlockStatement': {
2323
+ let stylesObj;
2324
+ body.body.forEach(bodyNode => {
2325
+ if (
2326
+ bodyNode.type === 'ReturnStatement' &&
2327
+ bodyNode.argument.type === 'ObjectExpression'
2328
+ ) {
2329
+ stylesObj = bodyNode.argument;
2330
+ }
2331
+ });
2332
+ return stylesObj;
2333
+ }
2334
+ }
2335
+ }
2336
+ break;
2337
+ }
2338
+ }
2339
+
2340
+ var classNaming = {
2341
+ meta: {
2342
+ type: 'problem',
2343
+ docs: {
2344
+ description: 'Enforce camelCase class names in TSS',
2345
+ category: 'Best Practices',
2346
+ recommended: true
2347
+ }
2348
+ },
2349
+ create: function rule(context) {
2350
+ return {
2351
+ CallExpression(node) {
2352
+ const stylesObj = getStyesObj(node);
2353
+ if (stylesObj === undefined) {
2354
+ return;
2355
+ }
2356
+ stylesObj.properties.forEach(property => {
2357
+ if (property.computed) {
2358
+ return;
2359
+ }
2360
+ if (
2361
+ property.type === 'ExperimentalSpreadProperty' ||
2362
+ property.type === 'SpreadElement'
2363
+ ) {
2364
+ return;
2365
+ }
2366
+ const className = property.key.value || property.key.name;
2367
+ if (!eslintUtils.isCamelCase(className)) {
2368
+ context.report({
2369
+ node: property,
2370
+ message: `Class \`${className}\` must be camelCase in TSS.`
2371
+ });
2372
+ }
2373
+ });
2374
+ }
2375
+ };
2376
+ }
2377
+ };
2378
+
2379
+ var noColorValue = {
2380
+ meta: {
2381
+ type: 'problem',
2382
+ docs: {
2383
+ description:
2384
+ 'Enforce the use of color variables instead of color codes within TSS',
2385
+ recommended: true
2386
+ }
2387
+ },
2388
+ create: function (context) {
2389
+ const parserOptions = context.parserOptions;
2390
+ if (!parserOptions || !parserOptions.project) {
2391
+ return {};
2392
+ }
2393
+ return {
2394
+ CallExpression(node) {
2395
+ const stylesObj = getStyesObj(node);
2396
+ if (!stylesObj) {
2397
+ return;
2398
+ }
2399
+ function checkColorLiteral(value) {
2400
+ if (value.type === 'Literal' && typeof value.value === 'string') {
2401
+ const colorCodePattern =
2402
+ /#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|rgb\?\(\s*(\d{1,3}\s*,\s*){2}\d{1,3}(?:\s*,\s*\d*(?:\.\d+)?)?\s*\)/g;
2403
+ const isColorCode = colorCodePattern.test(value.value);
2404
+ if (isColorCode) {
2405
+ context.report({
2406
+ node: value,
2407
+ message: 'Use color variables instead of color codes in TSS.'
2408
+ });
2409
+ }
2410
+ }
2411
+ }
2412
+ function loopStylesObj(obj) {
2413
+ if (obj && obj.type === 'ObjectExpression') {
2414
+ obj.properties.forEach(property => {
2415
+ if (property.type === 'Property' && property.value) {
2416
+ if (property.value.type === 'ObjectExpression') {
2417
+ loopStylesObj(property.value);
2418
+ } else {
2419
+ checkColorLiteral(property.value);
2420
+ }
2421
+ }
2422
+ });
2423
+ }
2424
+ }
2425
+ loopStylesObj(stylesObj);
2426
+ }
2427
+ };
2428
+ }
2429
+ };
2430
+
2431
+ var unusedClasses = {
2432
+ meta: {
2433
+ type: 'suggestion',
2434
+ docs: {
2435
+ description: 'Disallow unused classes in tss',
2436
+ category: 'Best Practices',
2437
+ recommended: true
2438
+ }
2439
+ },
2440
+ create: function rule(context) {
2441
+ const usedClasses = {};
2442
+ const definedClasses = {};
2443
+ return {
2444
+ CallExpression(node) {
2445
+ const stylesObj = getStyesObj(node);
2446
+ if (stylesObj === undefined) {
2447
+ return;
2448
+ }
2449
+ stylesObj.properties.forEach(property => {
2450
+ if (property.computed) {
2451
+ return;
2452
+ }
2453
+ if (
2454
+ property.type === 'ExperimentalSpreadProperty' ||
2455
+ property.type === 'SpreadElement'
2456
+ ) {
2457
+ return;
2458
+ }
2459
+ definedClasses[property.key.value || property.key.name] = property;
2460
+ });
2461
+ },
2462
+ MemberExpression(node) {
2463
+ if (
2464
+ node.object.type === 'Identifier' &&
2465
+ node.object.name === 'classes'
2466
+ ) {
2467
+ const whichClass = getBasicIdentifier(node.property);
2468
+ if (whichClass) {
2469
+ usedClasses[whichClass] = true;
2470
+ }
2471
+ return;
2472
+ }
2473
+ const classIdentifier = getBasicIdentifier(node.property);
2474
+ if (!classIdentifier) {
2475
+ return;
2476
+ }
2477
+ if (classIdentifier !== 'classes') {
2478
+ return;
2479
+ }
2480
+ const { parent } = node;
2481
+ if (parent.type !== 'MemberExpression') {
2482
+ return;
2483
+ }
2484
+ if (
2485
+ node.object.object &&
2486
+ node.object.object.type !== 'ThisExpression'
2487
+ ) {
2488
+ return;
2489
+ }
2490
+ const propsIdentifier = getBasicIdentifier(parent.object);
2491
+ if (propsIdentifier && propsIdentifier !== 'props') {
2492
+ return;
2493
+ }
2494
+ if (!propsIdentifier && parent.object.type !== 'MemberExpression') {
2495
+ return;
2496
+ }
2497
+ if (parent.parent.type === 'MemberExpression') {
2498
+ return;
2499
+ }
2500
+ const parentClassIdentifier = getBasicIdentifier(parent.property);
2501
+ if (parentClassIdentifier) {
2502
+ usedClasses[parentClassIdentifier] = true;
2503
+ }
2504
+ },
2505
+ 'Program:exit': () => {
2506
+ Object.keys(definedClasses).forEach(definedClassKey => {
2507
+ if (!usedClasses[definedClassKey]) {
2508
+ context.report({
2509
+ node: definedClasses[definedClassKey],
2510
+ message: `Class \`${definedClassKey}\` is unused`
2511
+ });
2512
+ }
2513
+ });
2514
+ }
2515
+ };
2516
+ }
2517
+ };
2518
+
2519
+ var noAsyncArrayMethods = {
2520
+ meta: {
2521
+ docs: {
2522
+ description:
2523
+ 'No async callback for Array methods forEach, map, filter, reduce, some, every, etc.',
2524
+ category: 'Array',
2525
+ recommended: true
2526
+ },
2527
+ fixable: undefined,
2528
+ schema: []
2529
+ },
2530
+ create: function (context) {
2531
+ return {
2532
+ ExpressionStatement: function (node) {
2533
+ const notAllowedArrayMethods = [
2534
+ 'forEach',
2535
+ 'filter',
2536
+ 'some',
2537
+ 'every',
2538
+ 'map',
2539
+ 'reduce',
2540
+ 'reduceRight',
2541
+ 'flatMap',
2542
+ 'find',
2543
+ 'findIndex',
2544
+ 'findLast',
2545
+ 'findLastIndex'
2546
+ ];
2547
+ const { callee } = node.expression;
2548
+ if (!callee || !callee.property || !callee.property.name) {
2549
+ return;
2550
+ }
2551
+ if (notAllowedArrayMethods.includes(callee.property.name)) {
2552
+ const functionArguments = node.expression.arguments.find(n => {
2553
+ return ['ArrowFunctionExpression', 'FunctionExpression'].includes(
2554
+ n.type
2555
+ );
2556
+ });
2557
+ if (functionArguments && functionArguments.async) {
2558
+ context.report({
2559
+ node,
2560
+ message: `No async function in method '${callee.property.name}'`
2561
+ });
2562
+ }
2563
+ }
2564
+ }
2565
+ };
2566
+ }
2567
+ };
2568
+
2569
+ var noThenCatchFinally = {
2570
+ meta: {
2571
+ type: 'suggestion',
2572
+ docs: {
2573
+ description:
2574
+ 'Disallow the use of then()/catch()/finally() when invoking specific functions.'
2575
+ },
2576
+ schema: [
2577
+ {
2578
+ type: 'object',
2579
+ properties: {
2580
+ restrictedFunctions: {
2581
+ type: 'array',
2582
+ uniqueItems: true,
2583
+ items: { type: 'string' }
2584
+ }
2585
+ }
2586
+ }
2587
+ ],
2588
+ messages: {
2589
+ forbiddenThenCatchFinally: `then()/catch()/finally() is forbidden when invoke {{ name }}().`
2590
+ }
2591
+ },
2592
+ create(context) {
2593
+ const configuration = context.options[0] || {};
2594
+ const restrictedFunctions = configuration.restrictedFunctions || [];
2595
+ function isTopLevelScoped() {
2596
+ return context.getScope().block.type === 'Program';
2597
+ }
2598
+ function isThenCatchFinally(node) {
2599
+ return (
2600
+ node.property &&
2601
+ (node.property.name === 'then' ||
2602
+ node.property.name === 'catch' ||
2603
+ node.property.name === 'finally')
2604
+ );
2605
+ }
2606
+ return {
2607
+ 'CallExpression > MemberExpression.callee'(node) {
2608
+ if (isTopLevelScoped()) {
2609
+ return;
2610
+ }
2611
+ if (!isThenCatchFinally(node)) {
2612
+ return;
2613
+ }
2614
+ const callExpression = node.object;
2615
+ if (
2616
+ callExpression.type === 'CallExpression' &&
2617
+ callExpression.callee.type === 'Identifier' &&
2618
+ restrictedFunctions.includes(callExpression.callee.name)
2619
+ ) {
2620
+ context.report({
2621
+ node: node.property,
2622
+ messageId: 'forbiddenThenCatchFinally',
2623
+ data: {
2624
+ name: callExpression.callee.name
2625
+ }
2626
+ });
2627
+ }
2628
+ }
2629
+ };
2630
+ }
2631
+ };
2632
+
2633
+ var noUnnecessaryTemplateLiterals = {
2634
+ meta: {
2635
+ type: 'problem',
2636
+ docs: {
2637
+ description: 'Check if a template string contains only one ${}',
2638
+ recommended: true
2639
+ },
2640
+ fixable: 'code',
2641
+ schema: [],
2642
+ messages: {
2643
+ unnecessaryTemplateString:
2644
+ 'Unnecessary template string with only one ${}.'
2645
+ }
2646
+ },
2647
+ create(context) {
2648
+ return {
2649
+ TemplateLiteral(node) {
2650
+ const code = context.sourceCode.getText(node);
2651
+ if (
2652
+ code.startsWith('`${') &&
2653
+ code.endsWith('}`') &&
2654
+ code.split('${').length === 2
2655
+ ) {
2656
+ context.report({
2657
+ node,
2658
+ messageId: 'unnecessaryTemplateString',
2659
+ fix(fixer) {
2660
+ return fixer.replaceText(
2661
+ node,
2662
+ code.substring(3, code.length - 2)
2663
+ );
2664
+ }
2665
+ });
2666
+ }
2667
+ }
2668
+ };
2669
+ }
2670
+ };
2671
+
2672
+ var ruleFiles = /*#__PURE__*/Object.freeze({
2673
+ __proto__: null,
2674
+ rules_import_enforce_icon_alias: enforceIconAlias,
2675
+ rules_import_monorepo: monorepo,
2676
+ rules_intl_id_missing: idMissing,
2677
+ rules_intl_id_prefix: idPrefix,
2678
+ rules_intl_id_unused: idUnused,
2679
+ rules_intl_no_default: noDefault,
2680
+ rules_react_better_exhaustive_deps: betterExhaustiveDeps,
2681
+ rules_react_hook_use_ref: hookUseRef,
2682
+ rules_react_prefer_named_property_access: preferNamedPropertyAccess,
2683
+ rules_react_prefer_sx_prop: preferSxProp,
2684
+ rules_tss_class_naming: classNaming,
2685
+ rules_tss_no_color_value: noColorValue,
2686
+ rules_tss_unused_classes: unusedClasses,
2687
+ rules_unprefixed_no_async_array_methods: noAsyncArrayMethods,
2688
+ rules_unprefixed_no_then_catch_finally: noThenCatchFinally,
2689
+ rules_unprefixed_no_unnecessary_template_literals: noUnnecessaryTemplateLiterals
2690
+ });
2691
+
2692
+ const plugin = {
2693
+ rules: {},
2694
+ configs: {
2695
+ recommended: {
2696
+ plugins: ['@agilebot'],
2697
+ rules: {},
2698
+ settings: {
2699
+ react: {
2700
+ version: '18.0.0'
2701
+ }
2702
+ }
2703
+ }
2704
+ }
2705
+ };
2706
+ Object.keys(ruleFiles).forEach(key => {
2707
+ const ruleKey = key.replace(/^rules_/, '').replace(/^unprefixed_/, '');
2708
+ const finalKey = ruleKey.replace(/_/g, '-');
2709
+ const rule = ruleFiles[key];
2710
+ plugin.rules[finalKey] = rule;
2711
+ if (rule.meta && rule.meta.docs && rule.meta.docs.recommended) {
2712
+ plugin.configs.recommended.rules[`@agilebot/${finalKey}`] =
2713
+ rule.meta.type === 'suggestion' ? 'warn' : 'error';
2714
+ }
2715
+ });
2635
2716
 
2636
- module.exports = index;
2717
+ module.exports = plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agilebot/eslint-plugin",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "Agilebot's ESLint plugin",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,7 +20,7 @@
20
20
  "dependencies": {
21
21
  "@typescript-eslint/utils": "~7.7.0",
22
22
  "eslint-plugin-react": "^7.34.1",
23
- "@agilebot/eslint-utils": "0.3.3"
23
+ "@agilebot/eslint-utils": "0.3.4"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "eslint": "^7.0.0 || ^8.0.0"
@@ -29,7 +29,8 @@
29
29
  "dist"
30
30
  ],
31
31
  "devDependencies": {
32
- "@types/estree": "^1.0.5"
32
+ "@types/estree": "^1.0.5",
33
+ "eslint-vitest-rule-tester": "^0.3.2"
33
34
  },
34
35
  "scripts": {
35
36
  "build": "rollup -c rollup.config.mjs && nr dts",