@agilebot/eslint-plugin 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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",