eslint-plugin-code-style 1.17.2 → 2.0.1

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/README.md CHANGED
@@ -47,7 +47,7 @@ When combined with ESLint's native rules and other popular plugins, this package
47
47
  [Installation](#-installation) •
48
48
  [Quick Start](#-quick-start) •
49
49
  [Recommended Configs](#-recommended-configurations) •
50
- [Rules](#-rules-reference) •
50
+ [Rules](#-rules-categories) •
51
51
  [Contributing](#-contributing)
52
52
 
53
53
  </div>
@@ -71,8 +71,8 @@ We provide **ready-to-use ESLint flat configuration files** that combine `eslint
71
71
  |---------------|-------------|--------|
72
72
  | **React** | React.js projects (JavaScript, JSX) | [View Config](./recommended-configs/react/) |
73
73
  | **React + TS + Tailwind** | React + TypeScript + Tailwind CSS | [View Config](./recommended-configs/react-ts-tw/) |
74
- | **React + TypeScript** | React + TypeScript projects | Coming Soon |
75
- | **React + Tailwind** | React + Tailwind CSS projects | Coming Soon |
74
+ | **React + TypeScript** | React + TypeScript projects | [View Config](./recommended-configs/react-ts/) |
75
+ | **React + Tailwind** | React + Tailwind CSS projects | [View Config](./recommended-configs/react-tw/) |
76
76
 
77
77
  ### ⚡ Quick Start with Recommended Config
78
78
 
@@ -269,7 +269,7 @@ rules: {
269
269
 
270
270
  ## 📖 Rules Categories
271
271
 
272
- > **79 rules total** — 70 with auto-fix 🔧, 19 configurable ⚙️, 9 report-only. See detailed examples in [Rules Reference](#-rules-reference) below.
272
+ > **79 rules total** — 70 with auto-fix 🔧, 19 configurable ⚙️, 9 report-only. See detailed examples in the [Rules Reference](./docs/rules/).
273
273
  >
274
274
  > **Legend:** 🔧 Auto-fixable with `eslint --fix` • ⚙️ Customizable options
275
275
 
@@ -378,3707 +378,27 @@ rules: {
378
378
 
379
379
  ## 📖 Rules Reference
380
380
 
381
- > Rules marked with 🔧 are **auto-fixable** using `eslint --fix`
381
+ For detailed documentation with examples, configuration options, and best practices for each rule, see the **[Rules Reference Documentation](./docs/rules/)**.
382
382
 
383
- <br />
384
-
385
- ## 📚 Array Rules
386
-
387
- ### `array-callback-destructure`
388
-
389
- **What it does:** When destructuring parameters in array method callbacks (map, filter, find, etc.), enforces each property on its own line when there are 2 or more properties.
390
-
391
- **Why use it:** Improves readability of array transformations by making destructured properties easy to scan vertically.
392
-
393
- ```javascript
394
- // Good each destructured property on its own line
395
- const result = items.map(({
396
- name,
397
- value,
398
- }) => `${name}: ${value}`);
399
-
400
- const filtered = users.filter(({
401
- age,
402
- isActive,
403
- }) => age > 18 && isActive);
404
-
405
- // ✅ Good — single property stays inline
406
- const names = items.map(({ name }) => name);
407
-
408
- // ❌ Bad — multiple properties on same line
409
- const result = items.map(({ name, value, id }) => `${name}: ${value}`);
410
-
411
- // ❌ Bad — hard to scan properties
412
- const data = records.filter(({ status, type, category }) => status === "active");
413
- ```
414
-
415
- ---
416
-
417
- ### `array-items-per-line`
418
-
419
- **What it does:** Controls array formatting based on the number of items. Short arrays stay on one line for compactness, while longer arrays get expanded with each item on its own line for better readability.
420
-
421
- **Why use it:** Prevents overly long single-line arrays that are hard to scan, while avoiding unnecessary vertical expansion for simple arrays.
422
-
423
- ```javascript
424
- // ✅ Good — 3 or fewer items stay compact
425
- const colors = ["red", "green", "blue"];
426
- const nums = [1, 2, 3];
427
-
428
- // ✅ Good — 4+ items expand for readability
429
- const weekdays = [
430
- "Monday",
431
- "Tuesday",
432
- "Wednesday",
433
- "Thursday",
434
- "Friday",
435
- ];
436
-
437
- // ❌ Bad — too many items on one line
438
- const weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"];
439
-
440
- // ❌ Bad — inconsistent formatting
441
- const items = [item1,
442
- item2, item3,
443
- item4];
444
- ```
445
-
446
- **Options:**
447
-
448
- | Option | Type | Default | Description |
449
- |--------|------|---------|-------------|
450
- | `maxItems` | `integer` | `3` | Maximum items to keep on single line |
451
-
452
- ```javascript
453
- // Example: Allow up to 4 items on single line
454
- "code-style/array-items-per-line": ["error", { maxItems: 4 }]
455
- ```
456
-
457
- ---
458
-
459
- ### `array-objects-on-new-lines`
460
-
461
- **What it does:** In arrays containing objects, ensures each object starts on its own line regardless of object size.
462
-
463
- **Why use it:** Object literals in arrays are visually complex. Putting each on its own line makes it easier to scan, compare, and edit individual items.
464
-
465
- ```javascript
466
- // ✅ Good — each object clearly separated
467
- const users = [
468
- { id: 1, name: "Alice", role: "admin" },
469
- { id: 2, name: "Bob", role: "user" },
470
- { id: 3, name: "Charlie", role: "user" },
471
- ];
472
-
473
- // ✅ Good — even short objects get their own line
474
- const points = [
475
- { x: 0, y: 0 },
476
- { x: 10, y: 20 },
477
- ];
478
-
479
- // ❌ Bad — objects crammed together
480
- const users = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
481
-
482
- // ❌ Bad — inconsistent line breaks
483
- const items = [{ id: 1 },
484
- { id: 2 }, { id: 3 }];
485
- ```
486
-
487
- <br />
488
-
489
- ## ➡️ Arrow Function Rules
490
-
491
- ### `arrow-function-block-body`
492
-
493
- **What it does:** Ensures arrow functions with multiline expressions use block body with explicit return, wrapped in parentheses when needed.
494
-
495
- **Why use it:** Multiline expressions without block body can be confusing. Clear boundaries with `{` and `}` make the function body obvious.
496
-
497
- ```javascript
498
- // ✅ Good — block body for complex logic
499
- const handleSubmit = () => {
500
- validateForm();
501
- submitData();
502
- return result;
503
- };
504
-
505
- // ✅ Good — multiline JSX wrapped properly
506
- const Button = () => (
507
- <button className="primary">
508
- Click me
509
- </button>
510
- );
511
-
512
- // ❌ Bad — comma operator is confusing
513
- const handleSubmit = () => (validateForm(), submitData(), result);
514
-
515
- // ❌ Bad — multiline without clear boundaries
516
- const Button = () => <button className="primary">
517
- Click me
518
- </button>;
519
- ```
520
-
521
- ---
522
-
523
- ### `arrow-function-simple-jsx`
524
-
525
- **What it does:** Collapses arrow functions that return a single simple JSX element onto one line by removing unnecessary parentheses and line breaks.
526
-
527
- **Why use it:** Simple component wrappers don't need multi-line formatting. Single-line is more scannable and reduces vertical space.
528
-
529
- ```javascript
530
- // ✅ Good — simple JSX on one line
531
- export const Layout = ({ children }) => <Container>{children}</Container>;
532
- export const Icon = () => <SVGIcon />;
533
- const Wrapper = (props) => <div {...props} />;
534
-
535
- // ❌ Bad — unnecessary multi-line for simple JSX
536
- export const Layout = ({ children }) => (
537
- <Container>{children}</Container>
538
- );
539
-
540
- // ❌ Bad — extra parentheses not needed
541
- const Icon = () => (
542
- <SVGIcon />
543
- );
544
- ```
545
-
546
- ---
547
-
548
- ### `arrow-function-simplify`
549
-
550
- **What it does:** Converts arrow functions with a single return statement to use implicit return, removing the block body and `return` keyword.
551
-
552
- **Why use it:** Implicit returns are more concise and idiomatic JavaScript. They reduce noise and make the code easier to read.
553
-
554
- ```javascript
555
- // ✅ Good — implicit return
556
- const double = (x) => x * 2;
557
- const getName = (user) => user.name;
558
- const items = data.map((item) => item.value);
559
- const isValid = (x) => x > 0 && x < 100;
560
-
561
- // ❌ Bad — unnecessary block body and return
562
- const double = (x) => { return x * 2; };
563
- const getName = (user) => { return user.name; };
564
- const items = data.map((item) => { return item.value; });
565
- const isValid = (x) => { return x > 0 && x < 100; };
566
- ```
567
-
568
- ---
569
-
570
- ### `curried-arrow-same-line`
571
-
572
- **What it does:** Ensures that when an arrow function returns another function, the returned function starts on the same line as `=>`.
573
-
574
- **Why use it:** Curried functions are easier to read when the chain is visible. Breaking after `=>` obscures the function structure.
575
-
576
- ```javascript
577
- // ✅ Good — curried function visible on same line
578
- const createAction = (type) => (payload) => ({ type, payload });
579
-
580
- const withLogger = (fn) => (...args) => {
581
- console.log("Called with:", args);
582
- return fn(...args);
583
- };
584
-
585
- const mapDispatch = () => async (dispatch) => {
586
- await dispatch(fetchData());
587
- };
588
-
589
- // ❌ Bad — chain broken across lines
590
- const createAction = (type) =>
591
- (payload) => ({ type, payload });
592
-
593
- const mapDispatch = () =>
594
- async (dispatch) => {
595
- await dispatch(fetchData());
596
- };
597
- ```
598
-
599
- <br />
600
-
601
- ## 📞 Call Expression Rules
602
-
603
- ### `function-arguments-format`
604
-
605
- **What it does:** Enforces consistent formatting for function call arguments:
606
- - Single simple argument stays on one line
607
- - 2+ arguments get one per line
608
- - Multiline arguments trigger full expansion
609
- - React hooks are skipped by default (they have their own rule)
610
-
611
- **Why use it:** Consistent argument formatting makes function calls scannable and diffs clean when adding/removing arguments.
612
-
613
- ```javascript
614
- // ✅ Good — single argument stays compact
615
- fetchUser(userId);
616
- console.log(message);
617
- dispatch(action);
618
-
619
- // ✅ Good — 2+ arguments get one per line
620
- setValue(
621
- "email",
622
- "user@example.com",
623
- );
624
-
625
- createUser(
626
- name,
627
- email,
628
- password,
629
- );
630
-
631
- // ✅ Good — multiline argument triggers expansion
632
- processData(
633
- {
634
- id: 1,
635
- name: "test",
636
- },
637
- );
638
-
639
- // ✅ Good — callback with body triggers expansion
640
- items.forEach(
641
- (item) => {
642
- process(item);
643
- save(item);
644
- },
645
- );
646
-
647
- // ❌ Bad — multiple arguments on same line
648
- setValue("email", "user@example.com");
649
- createUser(name, email, password);
650
-
651
- // ❌ Bad — inconsistent formatting
652
- fn(arg1,
653
- arg2, arg3);
654
- ```
655
-
656
- **Options:**
657
-
658
- | Option | Type | Default | Description |
659
- |--------|------|---------|-------------|
660
- | `minArgs` | `integer` | `2` | Minimum arguments to enforce multiline |
661
- | `skipHooks` | `boolean` | `true` | Skip React hooks (useEffect, etc.) |
662
- | `skipSingleArg` | `boolean` | `true` | Skip calls with single complex argument |
663
-
664
- ```javascript
665
- // Example: Require multiline for 3+ arguments
666
- "code-style/function-arguments-format": ["error", { minArgs: 3 }]
667
-
668
- // Example: Don't skip React hooks
669
- "code-style/function-arguments-format": ["error", { skipHooks: false }]
670
- ```
671
-
672
- ---
673
-
674
- ### `nested-call-closing-brackets`
675
-
676
- **What it does:** Ensures nested function calls (common in styled-components, HOCs) have closing brackets on the same line: `}));`
677
-
678
- **Why use it:** Scattered closing brackets (`}\n);\n` ) waste vertical space and make it harder to see where expressions end.
679
-
680
- ```javascript
681
- // ✅ Good — closing brackets together
682
- const StyledCard = styled(Card)(({ theme }) => ({
683
- color: theme.palette.text.primary,
684
- padding: theme.spacing(2),
685
- }));
686
-
687
- const StyledButton = styled("button")(({ theme }) => ({
688
- backgroundColor: theme.colors.primary,
689
- }));
690
-
691
- // ✅ Good — multiple levels
692
- const Component = connect(
693
- mapStateToProps,
694
- mapDispatchToProps,
695
- )(withRouter(MyComponent));
696
-
697
- // ❌ Bad — closing brackets scattered
698
- const StyledCard = styled(Card)(({ theme }) => ({
699
- color: theme.palette.text.primary,
700
- })
701
- );
702
-
703
- // ❌ Bad — each bracket on its own line
704
- const StyledCard = styled(Card)(({ theme }) => ({
705
- color: theme.colors.primary,
706
- })
707
- )
708
- ;
709
- ```
710
-
711
- ---
712
-
713
- ### `no-empty-lines-in-function-calls`
714
-
715
- **What it does:** Removes empty lines within function call argument lists — between arguments and after opening/before closing parentheses.
716
-
717
- **Why use it:** Empty lines between arguments break visual grouping. Arguments should flow as a cohesive list.
718
-
719
- ```javascript
720
- // ✅ Good — no empty lines
721
- createUser(
722
- name,
723
- email,
724
- password,
725
- role,
726
- );
727
-
728
- fetchData(
729
- url,
730
- {
731
- method: "POST",
732
- body: data,
733
- },
734
- );
735
-
736
- // ❌ Bad — empty line between arguments
737
- createUser(
738
- name,
739
-
740
- email,
741
-
742
- password,
743
- );
744
-
745
- // ❌ Bad — empty line after opening paren
746
- fetchData(
747
-
748
- url,
749
- options,
750
- );
751
-
752
- // ❌ Bad — empty line before closing paren
753
- fetchData(
754
- url,
755
- options,
756
-
757
- );
758
- ```
759
-
760
- ---
761
-
762
- ### `opening-brackets-same-line`
763
-
764
- **What it does:** Ensures opening brackets (`{`, `[`, `(`) in function arguments stay on the same line as the function call.
765
-
766
- **Why use it:** Opening brackets on new lines create unnecessary indentation and vertical space.
767
-
768
- ```javascript
769
- // ✅ Good — brackets on same line as call
770
- fn({ key: value });
771
- process([1, 2, 3]);
772
- items.map(({ id }) => id);
773
- configure({ debug: true });
774
-
775
- // ✅ Good — multiline content is fine
776
- fn({
777
- key: value,
778
- other: data,
779
- });
780
-
781
- items.map(({ id, name }) => (
782
- <Item key={id} name={name} />
783
- ));
784
-
785
- // ❌ Bad — opening bracket on new line
786
- fn(
787
- { key: value }
788
- );
789
-
790
- process(
791
- [1, 2, 3]
792
- );
793
-
794
- items.map(
795
- ({ id }) => id
796
- );
797
- ```
798
-
799
- ---
800
-
801
- ### `simple-call-single-line`
802
-
803
- **What it does:** Collapses simple function calls with an arrow function onto one line when the result fits within 120 characters. Handles:
804
- - Zero-param callbacks: `lazy(() => import("./Page"))`
805
- - Callbacks with params and simple expression bodies: `.find((f) => f.code === x)`
806
- - Optional chaining: `.find(...)?.symbol`
807
-
808
- **Why use it:** Common patterns like `lazy(() => import(...))` and `.find((item) => item.id === id)` don't need multiline formatting. Single line is cleaner.
809
-
810
- ```javascript
811
- // ✅ Good — simple patterns on one line
812
- const Page = lazy(() => import("./Page"));
813
- setTimeout(() => callback(), 100);
814
- const symbol = items.find(({ code }) => code === currency)?.symbol;
815
-
816
- // ✅ Good — complex callbacks stay multiline
817
- const Page = lazy(() => {
818
- console.log("Loading page");
819
- return import("./Page");
820
- });
821
-
822
- // ❌ Bad — unnecessary multiline for simple pattern
823
- const Page = lazy(
824
- () => import("./Page"),
825
- );
826
-
827
- const symbol = items.find(({ code }) =>
828
- code === currency)?.symbol;
829
-
830
- const symbol = items.find(({ code }) => code === currency)?.
831
- symbol;
832
- ```
833
-
834
- ---
835
-
836
- ### `single-argument-on-one-line`
837
-
838
- **What it does:** Ensures function calls with a single simple argument (literal, identifier, member expression) stay on one line.
839
-
840
- **Why use it:** Single-argument calls don't need multiline formatting. Expanding them wastes vertical space.
841
-
842
- ```javascript
843
- // ✅ Good — single argument on one line
844
- fetchUser(userId);
845
- console.log(message);
846
- process(data.items);
847
- dispatch(action);
848
- setValue("key");
849
- getElement(document.body);
850
-
851
- // ✅ Good — complex single argument can be multiline
852
- processConfig({
853
- key: value,
854
- other: data,
855
- });
856
-
857
- // ❌ Bad — simple argument expanded unnecessarily
858
- fetchUser(
859
- userId,
860
- );
861
-
862
- console.log(
863
- message,
864
- );
865
-
866
- dispatch(
867
- action,
868
- );
869
- ```
870
-
871
- <br />
872
-
873
- ## 💬 Comment Rules
874
-
875
- ### `comment-format`
876
-
877
- **What it does:** Enforces proper comment formatting:
878
- - Space after `//` in line comments
879
- - Space after `/*` and before `*/` in block comments
880
- - Single-line block comments converted to line comments
881
- - No blank lines between consecutive comments at file top
882
-
883
- **Why use it:** Consistent comment formatting improves readability and maintains a clean, professional codebase.
884
-
885
- ```javascript
886
- // ✅ Good — proper spacing
887
- // This is a comment
888
- /* This is a block comment */
889
-
890
- /*
891
- * This is a multi-line
892
- * block comment
893
- */
894
-
895
- // ✅ Good — file-top comments without gaps
896
- // File: utils.js
897
- // Author: John Doe
898
- // License: MIT
899
-
900
- // ❌ Bad — missing space after //
901
- //This is a comment
902
-
903
- // ❌ Bad — no space in block comment
904
- /*No space*/
905
-
906
- // ❌ Bad — single-line block should be line comment
907
- /* This should use // syntax */
908
- ```
909
-
910
- <br />
911
-
912
- ## 🏛️ Class Rules
913
-
914
- ### `class-method-definition-format`
915
-
916
- **What it does:** Enforces consistent spacing in class and method definitions:
917
- - Space before opening brace `{` in class declarations
918
- - No space between method name and opening parenthesis `(`
919
- - Space before opening brace `{` in method definitions
920
- - Opening brace must be on same line as class/method signature
921
-
922
- **Why use it:** Consistent formatting makes code more readable and prevents common spacing inconsistencies in class definitions.
923
-
924
- ```javascript
925
- // ✅ Good — proper spacing in class and methods
926
- class ApiServiceClass {
927
- getDataHandler(): string {
928
- return "data";
929
- }
930
-
931
- async fetchUserHandler(id: string): Promise<User> {
932
- return await this.fetch(id);
933
- }
934
- }
935
-
936
- // ❌ Bad — missing space before { in class
937
- class ApiServiceClass{
938
- getDataHandler(): string {
939
- return "data";
940
- }
941
- }
942
-
943
- // ❌ Bad — space between method name and (
944
- class ApiServiceClass {
945
- getDataHandler (): string {
946
- return "data";
947
- }
948
- }
949
-
950
- // ❌ Bad — missing space before { in method
951
- class ApiServiceClass {
952
- getDataHandler(): string{
953
- return "data";
954
- }
955
- }
956
-
957
- // ❌ Bad — opening brace on different line
958
- class ApiServiceClass {
959
- getDataHandler(): string
960
- {
961
- return "data";
962
- }
963
- }
964
- ```
965
-
966
- ---
967
-
968
- ### `class-naming-convention`
969
-
970
- **What it does:** Enforces that class declarations must end with "Class" suffix. This distinguishes class definitions from other PascalCase names like React components or type definitions.
971
-
972
- **Why use it:** Clear naming conventions prevent confusion between classes, components, and types. The "Class" suffix immediately identifies the construct.
973
-
974
- ```javascript
975
- // ✅ Good — class ends with "Class"
976
- class ApiServiceClass {
977
- constructor() {}
978
- fetch() {}
979
- }
980
-
981
- class UserRepositoryClass {
982
- save(user) {}
983
- }
984
-
985
- // ❌ Bad — missing "Class" suffix
986
- class ApiService {
987
- constructor() {}
988
- }
989
-
990
- class UserRepository {
991
- save(user) {}
992
- }
993
- ```
994
-
995
- <br />
996
-
997
- ## 🔀 Control Flow Rules
998
-
999
- ### `block-statement-newlines`
1000
-
1001
- **What it does:** Enforces newlines after the opening brace `{` and before the closing brace `}` in block statements (if, for, while, etc.).
1002
-
1003
- **Why use it:** Consistent block formatting improves readability. Single-line blocks are harder to scan and edit.
1004
-
1005
- ```javascript
1006
- // ✅ Good — proper block formatting
1007
- if (condition) {
1008
- doSomething();
1009
- }
1010
-
1011
- for (const item of items) {
1012
- process(item);
1013
- }
1014
-
1015
- while (running) {
1016
- tick();
1017
- }
1018
-
1019
- // ❌ Bad — everything on one line
1020
- if (condition) { doSomething(); }
1021
-
1022
- // ❌ Bad — no space after brace
1023
- if (condition) {doSomething();}
1024
-
1025
- // ❌ Bad — inconsistent formatting
1026
- for (const item of items) { process(item);
1027
- }
1028
- ```
1029
-
1030
- ---
1031
-
1032
- ### `empty-line-after-block`
1033
-
1034
- **What it does:** Requires an empty line between a closing brace `}` of a block statement (if, try, for, while, etc.) and the next statement, unless the next statement is part of the same construct (else, catch, finally).
1035
-
1036
- **Why use it:** Visual separation between logical blocks improves code readability and makes the structure clearer.
1037
-
1038
- > **Note:** Consecutive if statements are handled by `if-else-spacing` rule.
1039
-
1040
- ```javascript
1041
- // ✅ Good — empty line after block
1042
- if (condition) {
1043
- doSomething();
1044
- }
1045
-
1046
- const x = 1;
1047
-
1048
- // ✅ Good — else is part of same construct (no empty line needed)
1049
- if (condition) {
1050
- doSomething();
1051
- } else {
1052
- doOther();
1053
- }
1054
-
1055
- // ❌ Bad — no empty line after block
1056
- if (condition) {
1057
- doSomething();
1058
- }
1059
- const x = 1;
1060
- ```
1061
-
1062
- ---
1063
-
1064
- ### `if-else-spacing`
1065
-
1066
- **What it does:** Enforces proper spacing between if statements and if-else chains:
1067
- - Consecutive if statements with block bodies must have an empty line between them
1068
- - Single-line if and else should NOT have empty lines between them
1069
-
1070
- **Why use it:** Maintains visual separation between distinct conditional blocks while keeping related single-line if-else pairs compact.
1071
-
1072
- ```javascript
1073
- // ✅ Good — empty line between consecutive if blocks
1074
- if (!hasValidParams) return null;
1075
-
1076
- if (status === "loading") {
1077
- return <Loading />;
1078
- }
1079
-
1080
- if (status === "error") {
1081
- return <Error />;
1082
- }
1083
-
1084
- // ✅ Good — no empty line between single-line if-else
1085
- if (error) prom.reject(error);
1086
- else prom.resolve(token);
1087
-
1088
- // ❌ Bad — no empty line between if blocks
1089
- if (!hasValidParams) return null;
1090
- if (status === "loading") {
1091
- return <Loading />;
1092
- }
1093
- if (status === "error") {
1094
- return <Error />;
1095
- }
1096
-
1097
- // ❌ Bad — empty line between single-line if-else
1098
- if (error) prom.reject(error);
1099
-
1100
- else prom.resolve(token);
1101
- ```
1102
-
1103
- ---
1104
-
1105
- ### `if-statement-format`
1106
-
1107
- **What it does:** Enforces consistent if/else formatting:
1108
- - Opening `{` on the same line as `if`/`else if`/`else`
1109
- - `else` on the same line as the closing `}`
1110
- - Proper spacing around keywords
1111
-
1112
- **Why use it:** Consistent brace placement reduces visual noise and follows the most common JavaScript style (K&R / "one true brace style").
1113
-
1114
- ```javascript
1115
- // ✅ Good — consistent formatting
1116
- if (condition) {
1117
- doSomething();
1118
-
1119
- doMore();
1120
- }
1121
-
1122
- if (condition) {
1123
- doSomething();
1124
-
1125
- doMore();
1126
- } else {
1127
- doOther();
1128
-
1129
- doAnother();
1130
- }
1131
-
1132
- if (conditionA) {
1133
- handleA();
1134
-
1135
- processA();
1136
- } else if (conditionB) {
1137
- handleB();
1138
-
1139
- processB();
1140
- } else {
1141
- handleDefault();
1142
-
1143
- processDefault();
1144
- }
1145
-
1146
- // ❌ Bad — brace on new line
1147
- if (condition)
1148
- {
1149
- doSomething();
1150
-
1151
- doMore();
1152
- }
1153
-
1154
- // ❌ Bad — else on new line
1155
- if (condition) {
1156
- doSomething();
1157
-
1158
- doMore();
1159
- }
1160
- else {
1161
- doOther();
1162
-
1163
- doAnother();
1164
- }
1165
-
1166
- // ❌ Bad — inconsistent formatting
1167
- if (condition)
1168
- {
1169
- doSomething();
1170
-
1171
- doMore();
1172
- }
1173
- else
1174
- {
1175
- doOther();
1176
-
1177
- doAnother();
1178
- }
1179
- ```
1180
-
1181
- ---
1182
-
1183
- ### `logical-expression-multiline`
1184
-
1185
- **What it does:** When a logical expression (`&&`, `||`) has more operands than the threshold (default: 3), each operand goes on its own line with the operator at the start.
1186
-
1187
- **Why use it:** Long logical expressions are hard to read on one line. One operand per line makes each part clear and easy to modify.
1188
-
1189
- ```javascript
1190
- // ✅ Good — 3 or fewer operands stay inline
1191
- const isValid = a && b && c;
1192
- const result = x || y;
1193
-
1194
- // ✅ Good — 4+ operands get one per line
1195
- const err = data.error
1196
- || data.message
1197
- || data.status
1198
- || data.fallback;
1199
-
1200
- const isComplete = hasName
1201
- && hasEmail
1202
- && hasPhone
1203
- && hasAddress;
1204
-
1205
- // ❌ Bad — 4+ operands on single line
1206
- const err = data.error || data.message || data.status || data.fallback;
1207
- ```
1208
-
1209
- **Options:**
1210
-
1211
- | Option | Type | Default | Description |
1212
- |--------|------|---------|-------------|
1213
- | `maxOperands` | `integer` | `3` | Maximum operands allowed on a single line |
1214
-
1215
- ```javascript
1216
- // Configuration example - allow up to 4 operands on single line
1217
- "code-style/logical-expression-multiline": ["error", { maxOperands: 4 }]
1218
- ```
1219
-
1220
- ---
1221
-
1222
- ### `multiline-if-conditions`
1223
-
1224
- **What it does:** When an if statement has more conditions than the threshold (default: 3), each condition goes on its own line with proper indentation.
1225
-
1226
- **Why use it:** Long conditions are hard to read on one line. One per line makes each condition clear and easy to modify.
1227
-
1228
- ```javascript
1229
- // ✅ Good — 3 or fewer conditions stay inline
1230
- if (isValid && isActive) {}
1231
- if (a && b && c) {}
1232
-
1233
- // ✅ Good — 4+ conditions get one per line
1234
- if (
1235
- isAuthenticated &&
1236
- hasPermission &&
1237
- !isExpired &&
1238
- isEnabled
1239
- ) {
1240
- allowAccess();
1241
- }
1242
-
1243
- if (
1244
- user.isAdmin ||
1245
- user.isModerator ||
1246
- user.hasSpecialAccess ||
1247
- isPublicResource
1248
- ) {
1249
- showContent();
1250
- }
1251
-
1252
- // ❌ Bad — too many conditions on one line
1253
- if (isAuthenticated && hasPermission && !isExpired && isEnabled) {}
1254
-
1255
- // ❌ Bad — inconsistent formatting
1256
- if (isAuthenticated &&
1257
- hasPermission && !isExpired &&
1258
- isEnabled) {}
1259
- ```
1260
-
1261
- **Options:**
1262
-
1263
- | Option | Type | Default | Description |
1264
- |--------|------|---------|-------------|
1265
- | `maxOperands` | `integer` | `3` | Maximum operands to keep on single line. Also applies to nested groups |
1266
-
1267
- ```javascript
1268
- // Example: Allow up to 4 operands on single line
1269
- "code-style/multiline-if-conditions": ["error", { maxOperands: 4 }]
1270
- ```
1271
-
1272
- **Auto-formatting:** Nested groups with >maxOperands are formatted multiline inline:
1273
-
1274
- ```javascript
1275
- // ❌ Before (nested group has 4 operands)
1276
- if ((a || b || c || d) && e) {}
1277
-
1278
- // ✅ After auto-fix — formats nested group multiline
1279
- if ((
1280
- a
1281
- || b
1282
- || c
1283
- || d
1284
- ) && e) {}
1285
- ```
1286
-
1287
- **Double nesting:** Both levels expand when both exceed maxOperands:
1288
-
1289
- ```javascript
1290
- // ❌ Before (both parent and nested have 4 operands)
1291
- if ((a || (c && d && a && b) || c || d) && e) {}
1292
-
1293
- // ✅ After auto-fix — both levels formatted multiline
1294
- if ((
1295
- a
1296
- || (
1297
- c
1298
- && d
1299
- && a
1300
- && b
1301
- )
1302
- || c
1303
- || d
1304
- ) && e) {}
1305
- ```
1306
-
1307
- **Extraction:** Groups exceeding nesting level 2 are extracted to variables:
1308
-
1309
- ```javascript
1310
- // ❌ Before (level 3 nesting)
1311
- if ((a && (b || (c && d))) || e) {}
1312
-
1313
- // ✅ After auto-fix — extracts deepest nested group
1314
- const isCAndD = (c && d);
1315
- if ((a && (b || isCAndD)) || e) {}
1316
- ```
1317
-
1318
- ---
1319
-
1320
- ### `no-empty-lines-in-switch-cases`
1321
-
1322
- **What it does:** Removes empty lines at the start of case blocks and between consecutive case statements.
1323
-
1324
- **Why use it:** Empty lines inside switch cases create unnecessary gaps. Cases should flow together as a cohesive block.
1325
-
1326
- ```javascript
1327
- // ✅ Good — no empty lines
1328
- switch (status) {
1329
- case "pending":
1330
- return "Waiting...";
1331
- case "success":
1332
- return "Done!";
1333
- case "error":
1334
- return "Failed";
1335
- default:
1336
- return "Unknown";
1337
- }
1338
-
1339
- // ✅ Good — fall-through cases grouped
1340
- switch (day) {
1341
- case "Saturday":
1342
- case "Sunday":
1343
- return "Weekend";
1344
- default:
1345
- return "Weekday";
1346
- }
1347
-
1348
- // ❌ Bad — empty line after case label
1349
- switch (status) {
1350
- case "pending":
1351
-
1352
- return "Waiting...";
1353
- case "success":
1354
- return "Done!";
1355
- }
1356
-
1357
- // ❌ Bad — empty lines between cases
1358
- switch (status) {
1359
- case "pending":
1360
- return "Waiting...";
1361
-
1362
- case "success":
1363
- return "Done!";
1364
-
1365
- default:
1366
- return "Unknown";
1367
- }
1368
- ```
1369
-
1370
- ---
1371
-
1372
- ### `ternary-condition-multiline`
1373
-
1374
- **What it does:** Formats ternary expressions based on condition operand count:
1375
- - ≤maxOperands (default: 3): Always collapse to single line regardless of line length
1376
- - \>maxOperands: Expand to multiline with each operand on its own line
1377
- - Simple parenthesized nested ternaries (≤maxOperands) count as 1 operand and collapse
1378
- - Complex nested ternaries (>maxOperands in their condition) are skipped for manual formatting
1379
- - Nesting level is fixed at 2 to prevent overly complex conditions
1380
-
1381
- **Why use it:** Consistent formatting based on complexity, not line length. Simple conditions stay readable on one line; complex conditions get proper multiline formatting.
1382
-
1383
- **Options:**
1384
-
1385
- | Option | Type | Default | Description |
1386
- |--------|------|---------|-------------|
1387
- | `maxOperands` | `integer` | `3` | Maximum condition operands to keep ternary on single line. Also applies to nested groups |
1388
-
1389
- ```javascript
1390
- // ✅ Good — ≤3 operands always on single line
1391
- const x = a && b && c ? "yes" : "no";
1392
- const url = lang === "ar" ? `${apiEndpoints.exam.status}/${jobId}?lang=ar` : `${apiEndpoints.exam.status}/${jobId}`;
1393
-
1394
- // ✅ Good — parenthesized nested ternary counts as 1 operand
1395
- const inputType = showToggle ? (showPassword ? "text" : "password") : type;
1396
-
1397
- // ✅ Good — >3 operands formatted multiline
1398
- const style = variant === "ghost"
1399
- || variant === "ghost-danger"
1400
- || variant === "muted"
1401
- || variant === "primary"
1402
- ? "transparent"
1403
- : "solid";
1404
-
1405
- // ✅ Good — nested group with >3 operands formatted multiline inline
1406
- const result = (
1407
- a
1408
- || (
1409
- c
1410
- && d
1411
- && a
1412
- && b
1413
- )
1414
- || c
1415
- || d
1416
- ) && e ? "yes" : "no";
1417
-
1418
- // ❌ Bad — ≤3 operands split across lines
1419
- const x = a && b && c
1420
- ? "yes"
1421
- : "no";
1422
-
1423
- // ❌ Bad — >3 operands crammed on one line
1424
- const style = variant === "ghost" || variant === "ghost-danger" || variant === "muted" || variant === "primary" ? "transparent" : "solid";
1425
- ```
1426
-
1427
- **Auto-extraction:** Nested groups are auto-extracted to variables only when nesting depth exceeds 2 levels:
1428
-
1429
- ```javascript
1430
- // ❌ Before (level 3 nesting exceeds limit)
1431
- const result = (a && (b || (c && d))) || e ? "yes" : "no";
1432
-
1433
- // ✅ After auto-fix — extracts deepest nested group
1434
- const isCAndD = (c && d);
1435
- const result = (a && (b || isCAndD)) || e ? "yes" : "no";
1436
- ```
1437
-
1438
- **Note:** When nested groups exceed `maxOperands` but stay within the 2-level nesting limit, they are formatted multiline inline (not extracted).
1439
-
1440
- <br />
1441
-
1442
- ## ⚡ Function Rules
1443
-
1444
- ### `function-call-spacing`
1445
-
1446
- **What it does:** Removes any space between a function name and its opening parenthesis.
1447
-
1448
- **Why use it:** Standard JavaScript convention. `fn()` is correct, `fn ()` looks like a typo and can cause confusion.
1449
-
1450
- ```javascript
1451
- // ✅ Good — no space before parenthesis
1452
- useDispatch();
1453
- myFunction(arg);
1454
- console.log("message");
1455
- array.map((x) => x * 2);
1456
- obj.method();
1457
-
1458
- // ❌ Bad — space before parenthesis
1459
- useDispatch ();
1460
- myFunction (arg);
1461
- console.log ("message");
1462
- array.map ((x) => x * 2);
1463
- ```
1464
-
1465
- ---
1466
-
1467
- ### `function-declaration-style`
1468
-
1469
- **What it does:** Converts function declarations to `const` arrow function expressions. This is the auto-fixable companion to ESLint's built-in `func-style` rule.
1470
-
1471
- **Why use it:** The built-in `func-style: ["error", "expression"]` rule reports function declarations but does not auto-fix them. This rule provides the auto-fix. Both rules should be used together for the best experience.
1472
-
1473
- > **Important:** This rule depends on `func-style: ["error", "expression"]` being configured. If `func-style` is set to `"declaration"` or is disabled, do not enable this rule — it would conflict.
1474
-
1475
- ```typescript
1476
- // ✅ Good — arrow function expression
1477
- export const getToken = (): string | null => getCookie(tokenKey);
1478
-
1479
- export const clearAuth = (): void => {
1480
- removeToken();
1481
- clearStorage();
1482
- };
1483
-
1484
- const isAuthenticated = (): boolean => {
1485
- const token = getToken();
1486
- return !!token;
1487
- };
1488
-
1489
- // ❌ Bad — function declaration
1490
- export function getToken(): string | null {
1491
- return getCookie(tokenKey);
1492
- }
1493
-
1494
- export function clearAuth(): void {
1495
- removeToken();
1496
- clearStorage();
1497
- }
1498
-
1499
- function isAuthenticated(): boolean {
1500
- const token = getToken();
1501
- return !!token;
1502
- }
1503
- ```
1504
-
1505
- ---
1506
-
1507
- ### `function-naming-convention`
1508
-
1509
- **What it does:** Enforces naming conventions for functions:
1510
- - **camelCase** required
1511
- - **Verb prefix** required (get, set, fetch, is, has, can, should, click, submit, etc.)
1512
- - **Handler suffix** required (all functions must end with `Handler`)
1513
- - **Auto-fixes** `handleXxx` to `xxxHandler` (avoids redundant `handleClickHandler`)
1514
- - **Auto-fixes** PascalCase to camelCase for verb-prefixed functions
1515
-
1516
- **Why use it:** Function names should describe actions. Verb prefixes make the purpose immediately clear, and consistent Handler suffix makes event handlers easy to identify.
1517
-
1518
- ```javascript
1519
- // ✅ Good — verb prefix + Handler suffix
1520
- function getUserDataHandler() {}
1521
- function setUserNameHandler(name) {}
1522
- function clickHandler() {}
1523
- function submitHandler() {}
1524
- function isValidEmailHandler(email) {}
1525
- function hasPermissionHandler(user) {}
1526
- function canAccessHandler(resource) {}
1527
- const fetchUsersHandler = async () => {};
1528
-
1529
- // ❌ Bad (auto-fixed) — handleXxx → xxxHandler
1530
- function handleClick() {} // → clickHandler
1531
- function handleSubmit() {} // → submitHandler
1532
- function handleChange() {} // → changeHandler
1533
-
1534
- // ❌ Bad (auto-fixed) — missing Handler suffix
1535
- function getUserData() {} // → getUserDataHandler
1536
- function setUserName() {} // → setUserNameHandler
1537
- function fetchUsers() {} // → fetchUsersHandler
1538
-
1539
- // ❌ Bad (auto-fixed) — PascalCase to camelCase
1540
- function GetUserData() {} // → getUserDataHandler
1541
- function FetchStatus() {} // → fetchStatusHandler
1542
- ```
1543
-
1544
- ---
1545
-
1546
- ### `function-object-destructure`
1547
-
1548
- **What it does:** Enforces that non-component functions should not destructure parameters in the function signature. Instead, use a typed parameter and destructure at the top of the function body. Also reports when parameters are accessed via dot notation (suggesting destructuring).
1549
-
1550
- **Why use it:** Keeping function signatures clean and short improves readability. Destructuring in the body makes it clear what properties are being used. For React components, this rule does NOT apply — components should destructure props in the signature.
1551
-
1552
- ```typescript
1553
- // ✅ Good — typed param with destructuring in body
1554
- const createUserHandler = async (data: CreateUserParamsInterface) => {
1555
- const { age, email, isActive, name } = data;
1556
-
1557
- // Use age, email, isActive, name...
1558
- };
1559
-
1560
- const updateUserHandler = (params: UpdateParamsInterface) => {
1561
- const { id, updates } = params;
1562
-
1563
- // Use id, updates...
1564
- };
1565
-
1566
- // ✅ Good — React components CAN destructure in signature
1567
- const UserCard = ({
1568
- name,
1569
- email,
1570
- } : {
1571
- name: string,
1572
- email: string,
1573
- }) => (
1574
- <div>{name} - {email}</div>
1575
- );
1576
-
1577
- // ❌ Bad — non-component function destructures in signature
1578
- const createUserHandler = async ({
1579
- age,
1580
- email,
1581
- isActive,
1582
- name,
1583
- }: CreateUserParamsInterface) => {
1584
- // ...
1585
- };
1586
-
1587
- // ❌ Bad — accessing param via dot notation (should destructure)
1588
- const processDataHandler = (data: DataInterface) => {
1589
- console.log(data.id); // Bad: use destructuring
1590
- console.log(data.name); // Bad: use destructuring
1591
- return data.value * 2; // Bad: use destructuring
1592
- };
1593
- ```
1594
-
1595
- ---
1596
-
1597
- ### `function-params-per-line`
1598
-
1599
- **What it does:** When function parameters span multiple lines, ensures each parameter is on its own line with consistent indentation.
1600
-
1601
- **Why use it:** Mixed formatting (some params on same line, some on different lines) is confusing. One per line is scannable and easy to edit.
1602
-
1603
- ```javascript
1604
- // ✅ Good — each param on own line
1605
- function createUser(
1606
- name,
1607
- email,
1608
- password,
1609
- role,
1610
- ) {}
1611
-
1612
- const handler = (
1613
- event,
1614
- context,
1615
- callback,
1616
- ) => {};
1617
-
1618
- // ✅ Good — short params can stay on one line
1619
- function add(a, b) {}
1620
-
1621
- // ❌ Bad — mixed formatting
1622
- function createUser(name,
1623
- email, password,
1624
- role) {}
1625
-
1626
- // ❌ Bad — some on same line, some not
1627
- const handler = (event, context,
1628
- callback) => {};
1629
- ```
1630
-
1631
- ---
1632
-
1633
- ### `no-empty-lines-in-function-params`
1634
-
1635
- **What it does:** Removes empty lines within function parameter lists — between parameters and after opening/before closing parentheses.
1636
-
1637
- **Why use it:** Empty lines in parameter lists waste space and make parameters harder to scan as a group.
1638
-
1639
- ```javascript
1640
- // ✅ Good — no empty lines
1641
- function createUser(
1642
- name,
1643
- email,
1644
- role,
1645
- ) {}
1646
-
1647
- const handler = (
1648
- event,
1649
- context,
1650
- ) => {};
1651
-
1652
- // ❌ Bad — empty line between params
1653
- function createUser(
1654
- name,
1655
-
1656
- email,
1657
-
1658
- role,
1659
- ) {}
1660
-
1661
- // ❌ Bad — empty line after opening paren
1662
- const handler = (
1663
-
1664
- event,
1665
- context,
1666
- ) => {};
1667
- ```
1668
-
1669
- <br />
1670
-
1671
- ## 🪝 Hook Rules
1672
-
1673
- ### `hook-callback-format`
1674
-
1675
- **What it does:** Enforces consistent multi-line formatting for React hooks that take a callback and dependency array (useEffect, useCallback, useMemo, useLayoutEffect).
1676
-
1677
- **Why use it:** Hooks with callbacks and dependencies are complex. Multi-line formatting makes the callback, return cleanup, and dependencies clearly visible.
1678
-
1679
- ```javascript
1680
- // ✅ Good — callback and deps clearly separated
1681
- useEffect(
1682
- () => {
1683
- fetchData();
1684
- },
1685
- [userId],
1686
- );
1687
-
1688
- useCallback(
1689
- () => {
1690
- handleSubmit(data);
1691
- },
1692
- [data, handleSubmit],
1693
- );
1694
-
1695
- useMemo(
1696
- () => expensiveCalculation(items),
1697
- [items],
1698
- );
1699
-
1700
- // ✅ Good — cleanup function visible
1701
- useEffect(
1702
- () => {
1703
- const subscription = subscribe();
1704
-
1705
- return () => subscription.unsubscribe();
1706
- },
1707
- [subscribe],
1708
- );
1709
-
1710
- // ❌ Bad — everything crammed on one line
1711
- useEffect(() => { fetchData(); }, [userId]);
1712
-
1713
- // ❌ Bad — hard to see dependencies
1714
- useCallback(() => { handleSubmit(data); }, [data, handleSubmit]);
1715
- ```
1716
-
1717
- ---
1718
-
1719
- ### `hook-deps-per-line`
1720
-
1721
- **What it does:** When a hook's dependency array exceeds the threshold (default: 2), each dependency goes on its own line.
1722
-
1723
- **Why use it:** Long dependency arrays are hard to scan and diff. One per line makes it easy to see what changed and catch missing/extra dependencies.
1724
-
1725
- ```javascript
1726
- // ✅ Good — 2 or fewer deps stay inline
1727
- useEffect(() => {}, [userId]);
1728
- useEffect(() => {}, [userId, token]);
1729
-
1730
- // ✅ Good — 3+ deps get one per line
1731
- useEffect(
1732
- () => {},
1733
- [
1734
- userId,
1735
- token,
1736
- refreshToken,
1737
- ],
1738
- );
1739
-
1740
- useCallback(
1741
- () => handleSubmit(data),
1742
- [
1743
- data,
1744
- handleSubmit,
1745
- validateForm,
1746
- showError,
1747
- ],
1748
- );
1749
-
1750
- // ❌ Bad — too many deps on one line
1751
- useEffect(() => {}, [userId, token, refreshToken, apiUrl]);
1752
-
1753
- // ❌ Bad — deps should be one per line when expanded
1754
- useEffect(() => {}, [
1755
- userId, token, refreshToken,
1756
- ]);
1757
- ```
1758
-
1759
- **Options:**
1760
-
1761
- | Option | Type | Default | Description |
1762
- |--------|------|---------|-------------|
1763
- | `maxDeps` | `integer` | `2` | Maximum dependencies to keep on single line |
1764
-
1765
- ```javascript
1766
- // Example: Allow up to 3 dependencies on single line
1767
- "code-style/hook-deps-per-line": ["error", { maxDeps: 3 }]
1768
- ```
1769
-
1770
- <br />
1771
-
1772
- ### `use-state-naming-convention`
1773
-
1774
- **What it does:** Enforces boolean useState variables to start with valid prefixes (is, has, with, without).
1775
-
1776
- **Why use it:** Consistent boolean state naming makes code more predictable and self-documenting. When you see `isLoading`, you immediately know it's a boolean state.
1777
-
1778
- ```typescript
1779
- // ✅ Good — boolean state with proper prefix
1780
- const [isLoading, setIsLoading] = useState(false);
1781
- const [hasError, setHasError] = useState<boolean>(false);
1782
- const [isAuthenticated, setIsAuthenticated] = useState(true);
1783
- const [withBorder, setWithBorder] = useState(false);
1784
-
1785
- // ❌ Bad — boolean state without prefix
1786
- const [loading, setLoading] = useState(false);
1787
- const [authenticated, setAuthenticated] = useState<boolean>(true);
1788
- const [error, setError] = useState<boolean>(false);
1789
- ```
1790
-
1791
- **Customization Options:**
1792
-
1793
- | Option | Type | Default | Description |
1794
- |--------|------|---------|-------------|
1795
- | `booleanPrefixes` | `string[]` | `["is", "has", "with", "without"]` | Replace default prefixes entirely |
1796
- | `extendBooleanPrefixes` | `string[]` | `[]` | Add additional prefixes to defaults |
1797
- | `allowPastVerbBoolean` | `boolean` | `false` | Allow past verb names without prefix (disabled, selected) |
1798
- | `allowContinuousVerbBoolean` | `boolean` | `false` | Allow continuous verb names without prefix (loading, saving) |
1799
-
1800
- ```javascript
1801
- // Example: Allow "loading" and "disabled" without prefix
1802
- "code-style/use-state-naming-convention": ["error", {
1803
- allowPastVerbBoolean: true,
1804
- allowContinuousVerbBoolean: true
1805
- }]
1806
-
1807
- // Example: Add "should" prefix
1808
- "code-style/use-state-naming-convention": ["error", {
1809
- extendBooleanPrefixes: ["should"]
1810
- }]
1811
- ```
1812
-
1813
- <br />
1814
-
1815
- ## 📥 Import/Export Rules
1816
-
1817
- ### `absolute-imports-only`
1818
-
1819
- **What it does:** Enforces importing from folder index files using absolute paths (aliases like `@/`) instead of relative paths or deep file imports. Files within the same module folder must use relative imports (`./` or `../`) instead of absolute paths to avoid circular dependencies through the index file. Auto-fixes absolute imports to own module folder into relative paths. 🔧
1820
-
1821
- **Why use it:**
1822
- - Absolute imports are cleaner than `../../../components`
1823
- - Index imports create a public API for each folder
1824
- - Refactoring file locations doesn't break imports
1825
- - Encourages proper module organization
1826
- - Relative imports within the same module folder avoid circular dependencies
1827
-
1828
- ```javascript
1829
- // ✅ Good — import from index files using alias
1830
- import { Button, Input } from "@/components";
1831
- import { useAuth, useUser } from "@/hooks";
1832
- import { fetchUsers } from "@/apis";
1833
- import { formatDate } from "@/utils";
1834
-
1835
- // ✅ Good — assets allow deep imports by default
1836
- import logo from "@/assets/images/logo.png";
1837
-
1838
- // ✅ Good — relative import within the same module folder (siblings)
1839
- // File: utils/formatters.js
1840
- import { isNumber } from "./validators";
1841
-
1842
- // ✅ Good — relative import within the same module folder (nested)
1843
- // File: data/auth/forget-password/index.ts
1844
- import { guestLoginData } from "../../login/guest";
1845
-
1846
- // ❌ Bad — absolute import to own module folder (should use relative)
1847
- // File: data/auth/forget-password/index.ts
1848
- import { guestLoginData } from "@/data";
1849
- // → use relative import instead: import { guestLoginData } from "../../login/guest";
1850
-
1851
- // ❌ Bad — relative imports across different folders
1852
- import { Button } from "../../components";
1853
- import { useAuth } from "../../../hooks";
1854
-
1855
- // ❌ Bad — deep imports into component internals
1856
- import { Button } from "@/components/buttons/primary-button";
1857
- import { useAuth } from "@/hooks/auth/useAuth";
1858
- import { fetchUsers } from "@/apis/users/fetchUsers";
1859
- ```
1860
-
1861
- **Default Allowed Folders:**
1862
- `actions`, `apis`, `assets`, `atoms`, `components`, `config`, `configs`, `constants`, `contexts`, `data`, `enums`, `helpers`, `hooks`, `interfaces`, `layouts`, `lib`, `middlewares`, `pages`, `providers`, `reducers`, `redux`, `requests`, `routes`, `schemas`, `services`, `store`, `styles`, `theme`, `thunks`, `types`, `ui`, `utils`, `utilities`, `views`
1863
-
1864
- **Customization Options:**
1865
-
1866
- | Option | Type | Description |
1867
- |--------|------|-------------|
1868
- | `extraAllowedFolders` | `string[]` | Add custom folders that can be imported with `@/folder`. Extends defaults without replacing them. Use when your project has folders like `features/`, `modules/`, etc. |
1869
- | `extraReduxSubfolders` | `string[]` | Add Redux-related subfolders that can be imported directly (`@/selectors`) or nested (`@/redux/selectors`). Default subfolders: `actions`, `reducers`, `store`, `thunks`, `types` |
1870
- | `extraDeepImportFolders` | `string[]` | Add folders where direct file imports are allowed (`@/assets/images/logo.svg`). Use for folders without index files like images, fonts, etc. Default: `assets` |
1871
- | `aliasPrefix` | `string` | Change the path alias prefix if your project uses something other than `@/` (e.g., `~/`, `src/`) |
1872
- | `allowedFolders` | `string[]` | Completely replace the default allowed folders list. Use only if you need full control over which folders are valid |
1873
- | `reduxSubfolders` | `string[]` | Completely replace the default Redux subfolders list |
1874
- | `deepImportFolders` | `string[]` | Completely replace the default deep import folders list |
1875
-
1876
- ```javascript
1877
- // Example: Add custom folders to the defaults
1878
- "code-style/absolute-imports-only": ["error", {
1879
- extraAllowedFolders: ["features", "modules"],
1880
- extraDeepImportFolders: ["images", "fonts"]
1881
- }]
1882
- ```
1883
-
1884
- ---
1885
-
1886
- ### `export-format`
1887
-
1888
- **What it does:** Formats export statements consistently:
1889
- - `export {` always on the same line as `export` keyword
1890
- - ≤3 specifiers stay on one line (collapsed)
1891
- - 4+ specifiers get one per line (expanded)
1892
- - Proper spacing and trailing commas
1893
-
1894
- **Why use it:** Consistent export formatting improves readability. Short exports stay compact, long exports become scannable.
1895
-
1896
- ```javascript
1897
- // ✅ Good — 3 or fewer specifiers stay compact
1898
- export { Button };
1899
- export { Button, Input };
1900
- export { Button, Input, Select };
1901
-
1902
- // ✅ Good — 4+ specifiers expand with one per line
1903
- export {
1904
- Button,
1905
- Input,
1906
- Select,
1907
- Checkbox,
1908
- };
1909
-
1910
- // ✅ Good — re-exports follow same rules
1911
- export { Button, Input, Select } from "./components";
1912
- export {
1913
- createUser,
1914
- updateUser,
1915
- deleteUser,
1916
- getUser,
1917
- } from "./api";
1918
-
1919
- // ❌ Bad — no spaces
1920
- export {Button,Input,Select};
1921
-
1922
- // ❌ Bad — keyword on different line
1923
- export
1924
- { Button };
1925
-
1926
- // ❌ Bad — too many on one line
1927
- export { Button, Input, Select, Checkbox, Radio };
1928
- ```
1929
-
1930
- **Options:**
1931
-
1932
- | Option | Type | Default | Description |
1933
- |--------|------|---------|-------------|
1934
- | `maxSpecifiers` | `integer` | `3` | Maximum specifiers to keep on single line |
1935
-
1936
- ```javascript
1937
- "code-style/export-format": ["error", { maxSpecifiers: 4 }]
1938
- ```
1939
-
1940
- ---
1941
-
1942
- ### `import-format`
1943
-
1944
- **What it does:** Formats import statements consistently:
1945
- - `import {` on the same line as `import` keyword
1946
- - `} from` on the same line as closing brace
1947
- - ≤3 specifiers stay on one line (collapsed)
1948
- - 4+ specifiers get one per line (expanded)
1949
-
1950
- **Why use it:** Consistent import formatting improves readability and makes diffs cleaner when adding/removing imports.
1951
-
1952
- ```javascript
1953
- // ✅ Good — 3 or fewer specifiers stay compact
1954
- import { useState } from "react";
1955
- import { Button, Input } from "@/components";
1956
- import { get, post, put } from "@/api";
1957
-
1958
- // ✅ Good — 4+ specifiers expand with one per line
1959
- import {
1960
- useState,
1961
- useEffect,
1962
- useCallback,
1963
- useMemo,
1964
- } from "react";
1965
-
1966
- import {
1967
- Button,
1968
- Input,
1969
- Select,
1970
- Checkbox,
1971
- } from "@/components";
1972
-
1973
- // ❌ Bad — no spaces
1974
- import {useState,useEffect} from "react";
1975
-
1976
- // ❌ Bad — keyword on different line
1977
- import
1978
- { Button } from "@/components";
1979
-
1980
- // ❌ Bad — from on different line
1981
- import { Button }
1982
- from "@/components";
1983
-
1984
- // ❌ Bad — too many on one line
1985
- import { useState, useEffect, useCallback, useMemo, useRef } from "react";
1986
- ```
1987
-
1988
- **Options:**
1989
-
1990
- | Option | Type | Default | Description |
1991
- |--------|------|---------|-------------|
1992
- | `maxSpecifiers` | `integer` | `3` | Maximum specifiers to keep on single line |
1993
-
1994
- ```javascript
1995
- "code-style/import-format": ["error", { maxSpecifiers: 4 }]
1996
- ```
1997
-
1998
- ---
1999
-
2000
- ### `import-source-spacing`
2001
-
2002
- **What it does:** Removes any leading or trailing whitespace inside import path strings.
2003
-
2004
- **Why use it:** Spaces in module paths are almost always typos and can cause import resolution issues.
2005
-
2006
- ```javascript
2007
- // ✅ Good — no extra spaces
2008
- import { Button } from "@mui/material";
2009
- import React from "react";
2010
- import styles from "./styles.css";
2011
-
2012
- // ❌ Bad — leading space
2013
- import { Button } from " @mui/material";
2014
-
2015
- // ❌ Bad — trailing space
2016
- import React from "react ";
2017
-
2018
- // ❌ Bad — both
2019
- import styles from " ./styles.css ";
2020
- ```
2021
-
2022
- ---
2023
-
2024
- ### `index-export-style`
2025
-
2026
- **What it does:** Enforces different export formatting rules for index files vs regular files:
2027
- - **Index files**: No blank lines between exports, use shorthand or import-export style
2028
- - **Regular files**: Require blank lines between exports
2029
-
2030
- **Why use it:** Index files are re-export aggregators and should be compact. Regular files benefit from spacing between exports for readability.
2031
-
2032
- **Regular files (non-index):**
2033
- ```javascript
2034
- // ✅ Good — blank lines between exports
2035
- export const API_URL = "/api";
2036
-
2037
- export const MAX_RETRIES = 3;
2038
-
2039
- export const fetchData = async () => {};
2040
-
2041
- // ❌ Bad — no blank lines in regular file
2042
- export const API_URL = "/api";
2043
- export const MAX_RETRIES = 3;
2044
- export const fetchData = async () => {};
2045
- ```
2046
-
2047
- **Index files — Style: "shorthand" (default):**
2048
- ```javascript
2049
- // ✅ Good — shorthand re-exports, no blank lines
2050
- export { Button } from "./button";
2051
- export { Input, Select } from "./form";
2052
- export { Modal } from "./modal";
2053
- export { useAuth, useUser } from "./hooks";
2054
- ```
2055
-
2056
- **Index files — Style: "import-export":**
2057
- ```javascript
2058
- // ✅ Good — imports grouped, single export at bottom
2059
- import { Button } from "./button";
2060
- import { Input, Select } from "./form";
2061
- import { Modal } from "./modal";
2062
-
2063
- export {
2064
- Button,
2065
- Input,
2066
- Modal,
2067
- Select,
2068
- };
2069
- ```
2070
-
2071
- **Options:**
2072
-
2073
- | Option | Type | Default | Description |
2074
- |--------|------|---------|-------------|
2075
- | `style` | `"shorthand"` \| `"import-export"` | `"shorthand"` | Export style for index files |
2076
-
2077
- ```javascript
2078
- "code-style/index-export-style": ["error", { style: "import-export" }]
2079
- ```
2080
-
2081
- ---
2082
-
2083
- ### `index-exports-only`
2084
-
2085
- **What it does:** Index files (`index.ts`, `index.tsx`, `index.js`, `index.jsx`) should only contain imports and re-exports, not any code definitions. All definitions (types, interfaces, functions, variables, classes) should be moved to separate files.
2086
-
2087
- **Why use it:** Index files should be "barrels" that aggregate exports from a module. Mixing definitions with re-exports makes the codebase harder to navigate and can cause circular dependency issues.
2088
-
2089
- ```javascript
2090
- // ✅ Good — index.ts with only imports and re-exports
2091
- export { Button } from "./Button";
2092
- export { helper } from "./utils";
2093
- export type { ButtonProps } from "./types";
2094
- export * from "./constants";
2095
-
2096
- // ❌ Bad — index.ts with code definitions
2097
- export type ButtonVariant = "primary" | "secondary"; // Move to types.ts
2098
- export interface ButtonProps { ... } // Move to types.ts
2099
- export const CONSTANT = "value"; // Move to constants.ts
2100
- export function helper() { ... } // Move to utils.ts
2101
- ```
2102
-
2103
- ---
2104
-
2105
- ### `inline-export-declaration`
2106
-
2107
- **What it does:** Enforces that exports are declared inline with the declaration (`export const`, `export function`) instead of using grouped export statements (`export { ... }`). Auto-fixable: adds `export` to each declaration and removes the grouped export statement.
2108
-
2109
- **Why use it:** Inline exports make it immediately clear which declarations are public. Grouped exports at the bottom of a file require scrolling to discover what's exported, and they can become stale or inconsistent with the actual declarations.
2110
-
2111
- **Important exceptions:**
2112
- - **Index files** (barrel re-exports) are skipped entirely -- they should use grouped/re-export syntax
2113
- - **Aliased exports** (`export { a as b }`) are skipped since they cannot be expressed as inline exports
2114
-
2115
- ```javascript
2116
- // ✅ Good — inline export declarations
2117
- export const strings = {
2118
- title: "Hello",
2119
- subtitle: "World",
2120
- };
2121
-
2122
- export const MAX_RETRIES = 3;
2123
-
2124
- export function fetchData() {
2125
- return fetch("/api/data");
2126
- }
2127
-
2128
- // ❌ Bad — grouped export statement
2129
- const strings = {
2130
- title: "Hello",
2131
- subtitle: "World",
2132
- };
2133
-
2134
- const MAX_RETRIES = 3;
2135
-
2136
- function fetchData() {
2137
- return fetch("/api/data");
2138
- }
2139
-
2140
- export { strings, MAX_RETRIES, fetchData };
2141
- ```
2142
-
2143
- ---
2144
-
2145
- ### `module-index-exports`
2146
-
2147
- **What it does:** Ensures module folders have index files that export all their contents, creating a proper public API for each module.
2148
-
2149
- **Why use it:** Index files allow importing from the folder level (`@/components`) instead of deep paths (`@/components/Button/Button`). This enforces proper module boundaries.
2150
-
2151
- ```javascript
2152
- // ✅ Good — components/index.js exports everything
2153
- export { Button } from "./Button";
2154
- export { Input } from "./Input";
2155
- export { Select } from "./Select";
2156
- export { Modal } from "./Modal";
2157
-
2158
- // Then consumers can import cleanly:
2159
- import { Button, Input, Select } from "@/components";
2160
-
2161
- // ❌ Bad — missing exports in index.js
2162
- // If Button exists but isn't exported from index.js,
2163
- // consumers have to use deep imports:
2164
- import { Button } from "@/components/Button/Button"; // Avoid this!
2165
- ```
2166
-
2167
- **Default Module Folders:**
2168
- `actions`, `apis`, `assets`, `atoms`, `components`, `config`, `configs`, `constants`, `contexts`, `data`, `enums`, `helpers`, `hooks`, `interfaces`, `layouts`, `lib`, `middlewares`, `pages`, `providers`, `reducers`, `redux`, `requests`, `routes`, `schemas`, `services`, `store`, `styles`, `theme`, `thunks`, `types`, `ui`, `utils`, `utilities`, `views`
2169
-
2170
- **Default Ignore Patterns:**
2171
- `index.js`, `index.jsx`, `index.ts`, `index.tsx`, `.DS_Store`, `__tests__`, `__mocks__`, `*.test.js`, `*.test.jsx`, `*.test.ts`, `*.test.tsx`, `*.spec.js`, `*.spec.jsx`, `*.spec.ts`, `*.spec.tsx`
2172
-
2173
- **Customization Options:**
2174
-
2175
- | Option | Type | Description |
2176
- |--------|------|-------------|
2177
- | `extraModuleFolders` | `string[]` | Add folders that should have an `index.js` re-exporting all public files. Use for project-specific folders like `features/`, `modules/` that follow the same pattern |
2178
- | `extraLazyLoadFolders` | `string[]` | Add folders exempt from index file requirements. Use for route/page components loaded via dynamic `import()`. Default: `pages`, `views` |
2179
- | `extraIgnorePatterns` | `string[]` | Add file patterns to skip when checking for index exports. Supports wildcards like `*.stories.js`, `*.mock.js` |
2180
- | `moduleFolders` | `string[]` | Completely replace the default module folders list. Use only if you need full control over which folders require index files |
2181
- | `lazyLoadFolders` | `string[]` | Completely replace the default lazy load folders list |
2182
- | `ignorePatterns` | `string[]` | Completely replace the default ignore patterns list |
2183
-
2184
- ```javascript
2185
- // Example: Add custom folders and patterns
2186
- "code-style/module-index-exports": ["error", {
2187
- extraModuleFolders: ["features", "modules"],
2188
- extraLazyLoadFolders: ["screens"],
2189
- extraIgnorePatterns: ["*.stories.js", "*.mock.js"]
2190
- }]
2191
- ```
2192
-
2193
- <br />
2194
-
2195
- ## ⚛️ JSX Rules
2196
-
2197
- ### `classname-dynamic-at-end`
2198
-
2199
- **What it does:** Enforces that dynamic expressions in className template literals are placed at the end, after all static class names. Also applies to variables with names containing "class" or "Class".
2200
-
2201
- **Why use it:** When using Tailwind CSS with `tailwindcss/classnames-order`, static classes are automatically sorted. However, dynamic expressions like `${className}` or `${styles.button}` can break the visual order if placed in the middle. This rule ensures dynamic parts come last for consistent, readable class strings.
2202
-
2203
- ```javascript
2204
- // ✅ Good — dynamic expressions at the end (JSX)
2205
- <div className={`flex items-center gap-4 ${className}`} />
2206
-
2207
- // ✅ Good — dynamic expressions at the end (variable)
2208
- const buttonClasses = `flex items-center ${className} ${styles.button}`;
2209
-
2210
- // ❌ Bad — dynamic expression at the beginning
2211
- <div className={`${className} flex items-center gap-4`} />
2212
-
2213
- // ❌ Bad — dynamic expression in the middle (variable)
2214
- const buttonClasses = `flex ${className} items-center gap-4`;
2215
- ```
2216
-
2217
- ---
2218
-
2219
- ### `classname-multiline`
2220
-
2221
- **What it does:** Enforces that long className strings are broken into multiple lines, with each class on its own line. Triggers when either the class count exceeds `maxClassCount` (default: 3) or the string length exceeds `maxLength` (default: 80). Also enforces:
2222
- - JSX `className` with no dynamic expressions uses `"..."` string literal format
2223
- - JSX `className` with dynamic expressions uses `` {`...`} `` template literal format
2224
- - Variables/object properties use `` `...` `` template literal for multiline (JS requires it)
2225
- - No empty lines between classes or before/after the quotes
2226
- - Under-threshold multiline classes are collapsed back to a single line
2227
-
2228
- Applies to JSX `className` attributes, variables with class-related names, and object properties within class-related objects.
2229
-
2230
- **Why use it:** Long single-line class strings are hard to read and review. Breaking them into one class per line makes diffs cleaner and classes easier to scan. Using string literals when no expressions are needed keeps the code simpler.
2231
-
2232
- ```javascript
2233
- // ✅ Good — JSX with no expressions uses "..." format
2234
- <div
2235
- className="
2236
- flex
2237
- items-center
2238
- justify-center
2239
- rounded-lg
2240
- p-4
2241
- "
2242
- />
2243
-
2244
- // ✅ Good — JSX with expressions uses {`...`} format
2245
- <div
2246
- className={`
2247
- flex
2248
- items-center
2249
- justify-center
2250
- ${className}
2251
- `}
2252
- />
2253
-
2254
- // ✅ Good — variable multiline uses template literal
2255
- const buttonClasses = `
2256
- flex
2257
- items-center
2258
- justify-center
2259
- ${className}
2260
- `;
2261
-
2262
- // ✅ Good — object property multiline uses template literal
2263
- const variantClasses = {
2264
- danger: `
2265
- flex
2266
- items-center
2267
- justify-center
2268
- bg-red-500
2269
- `,
2270
- };
2271
-
2272
- // ✅ Good — short class strings stay on one line
2273
- <div className="flex items-center" />
2274
-
2275
- // ❌ Bad — too many classes on one line
2276
- <div className="flex items-center justify-center rounded-lg p-4 font-bold" />
2277
-
2278
- // ❌ Bad — using template literal in JSX when no expressions
2279
- <div className={`
2280
- flex
2281
- items-center
2282
- justify-center
2283
- rounded-lg
2284
- `} />
2285
-
2286
- // ❌ Bad — empty lines between classes
2287
- <div className="
2288
- flex
2289
-
2290
- items-center
2291
- justify-center
2292
- " />
2293
- ```
2294
-
2295
- ---
2296
-
2297
- ### `classname-no-extra-spaces`
2298
-
2299
- **What it does:** Removes multiple consecutive spaces and leading/trailing spaces inside className values. Applies to:
2300
- - JSX `className` attributes (string literals and template literals)
2301
- - Variables with names containing "class" (e.g., `buttonClasses`, `variantClasses`)
2302
- - Object properties within class-related objects
2303
-
2304
- **Why use it:** Extra spaces between class names are usually unintentional and can cause issues. This rule normalizes spacing and removes unnecessary whitespace.
2305
-
2306
- ```javascript
2307
- // ✅ Good — single space between classes
2308
- <div className="flex items-center gap-4 rounded-lg" />
2309
- const buttonClasses = `flex items-center ${className}`;
2310
- const variantClasses = { primary: "bg-blue-500 text-white" };
2311
-
2312
- // ❌ Bad — multiple consecutive spaces
2313
- <div className="flex items-center gap-4" />
2314
- const buttonClasses = `flex items-center`;
2315
- const variantClasses = { primary: "bg-blue-500 text-white" };
2316
-
2317
- // ❌ Bad — leading/trailing spaces in template literal
2318
- const buttonClasses = ` flex items-center ${className} `;
2319
- ```
2320
-
2321
- ---
2322
-
2323
- ### `classname-order`
2324
-
2325
- **What it does:** Enforces Tailwind CSS class ordering in variables, object properties, and return statements. Uses smart detection to identify Tailwind class strings.
2326
-
2327
- **Why use it:** This rule complements the official `tailwindcss/classnames-order` plugin by handling areas it doesn't cover:
2328
- - **`tailwindcss/classnames-order`** — Handles JSX `className` attributes directly
2329
- - **`classname-order`** — Handles class strings in variables, object properties, and return statements
2330
-
2331
- Both rules should be enabled together for complete Tailwind class ordering coverage.
2332
-
2333
- **Order enforced:** layout (flex, grid) → positioning → sizing (w, h) → spacing (p, m) → typography (text, font) → colors (bg, text) → effects (shadow, opacity) → transitions → states (hover, focus)
2334
-
2335
- ```javascript
2336
- // ✅ Good — classes in correct order (variable)
2337
- const buttonClasses = "flex items-center px-4 py-2 text-white bg-blue-500 hover:bg-blue-600";
2338
-
2339
- // ✅ Good — classes in correct order (object property)
2340
- const variants = {
2341
- primary: "flex items-center bg-blue-500 hover:bg-blue-600",
2342
- secondary: "flex items-center bg-gray-500 hover:bg-gray-600",
2343
- };
2344
-
2345
- // ✅ Good — classes in correct order (return statement)
2346
- const getInputStyles = () => {
2347
- return "border-error text-error placeholder-error/50 focus:border-error";
2348
- };
2349
-
2350
- // ❌ Bad — hover state before base color (variable)
2351
- const buttonClasses = "flex items-center hover:bg-blue-600 bg-blue-500";
2352
-
2353
- // ❌ Bad — unordered classes (object property)
2354
- const variants = {
2355
- primary: "hover:bg-blue-600 bg-blue-500 flex items-center",
2356
- };
2357
-
2358
- // ❌ Bad — unordered classes (return statement)
2359
- const getInputStyles = () => {
2360
- return "focus:border-error text-error border-error";
2361
- };
2362
- ```
2363
-
2364
- ---
2365
-
2366
- ### `jsx-children-on-new-line`
2367
-
2368
- **What it does:** When a JSX element has multiple children, ensures each child is on its own line with proper indentation.
2369
-
2370
- **Why use it:** Multiple children on one line are hard to scan. Individual lines make the component structure clear.
2371
-
2372
- ```javascript
2373
- // ✅ Good — each child on its own line
2374
- <Container>
2375
- <Header />
2376
- <Content />
2377
- <Footer />
2378
- </Container>
2379
-
2380
- <Form>
2381
- <Input name="email" />
2382
- <Input name="password" />
2383
- <Button type="submit">Login</Button>
2384
- </Form>
2385
-
2386
- // ✅ Good — single child can stay inline
2387
- <Button><Icon /></Button>
2388
-
2389
- // ❌ Bad — multiple children crammed together
2390
- <Container><Header /><Content /><Footer /></Container>
2391
-
2392
- // ❌ Bad — inconsistent formatting
2393
- <Form><Input name="email" />
2394
- <Input name="password" />
2395
- <Button>Login</Button></Form>
2396
- ```
2397
-
2398
- ---
2399
-
2400
- ### `jsx-closing-bracket-spacing`
2401
-
2402
- **What it does:** Removes any space before `>` or `/>` in JSX tags.
2403
-
2404
- **Why use it:** Standard JSX convention. Spaces before closing brackets look inconsistent and can be confusing.
2405
-
2406
- ```javascript
2407
- // ✅ Good — no space before closing
2408
- <Button />
2409
- <Input type="text" />
2410
- <div className="container">
2411
- <Modal isOpen={true}>
2412
-
2413
- // ❌ Bad — space before />
2414
- <Button / >
2415
- <Input type="text" / >
2416
-
2417
- // ❌ Bad — space before >
2418
- <div className="container" >
2419
- <Modal isOpen={true} >
2420
- ```
2421
-
2422
- ---
2423
-
2424
- ### `jsx-element-child-new-line`
2425
-
2426
- **What it does:** When a JSX element contains another JSX element as a child, the child must be on its own line.
2427
-
2428
- **Why use it:** Nested elements on the same line hide the component structure. New lines make nesting visible.
2429
-
2430
- ```javascript
2431
- // ✅ Good — nested element on new line
2432
- <Button>
2433
- <Icon name="check" />
2434
- </Button>
2435
-
2436
- <Card>
2437
- <CardHeader>
2438
- <Title>Hello</Title>
2439
- </CardHeader>
2440
- </Card>
2441
-
2442
- // ✅ Good — text children can stay inline
2443
- <Button>Click me</Button>
2444
- <Title>{title}</Title>
2445
-
2446
- // ❌ Bad — nested element inline
2447
- <Button><Icon name="check" /></Button>
2448
-
2449
- // ❌ Bad — complex nesting all inline
2450
- <Card><CardHeader><Title>Hello</Title></CardHeader></Card>
2451
- ```
2452
-
2453
- ---
2454
-
2455
- ### `jsx-logical-expression-simplify`
2456
-
2457
- **What it does:** Removes unnecessary parentheses around conditions and JSX elements in logical expressions.
2458
-
2459
- **Why use it:** Extra parentheses add visual noise. Simple conditions and elements don't need wrapping.
2460
-
2461
- ```javascript
2462
- // ✅ Good — clean logical expressions
2463
- {isLoading && <Spinner />}
2464
- {error && <ErrorMessage>{error}</ErrorMessage>}
2465
- {items.length > 0 && <List items={items} />}
2466
- {user.isAdmin && <AdminPanel />}
2467
-
2468
- // ❌ Bad — unnecessary parentheses around condition
2469
- {(isLoading) && <Spinner />}
2470
- {(error) && <ErrorMessage />}
2471
-
2472
- // ❌ Bad — unnecessary parentheses around JSX
2473
- {isLoading && (<Spinner />)}
2474
- {error && (<ErrorMessage />)}
2475
-
2476
- // ❌ Bad — both
2477
- {(isLoading) && (<Spinner />)}
2478
- ```
2479
-
2480
- ---
2481
-
2482
- ### `jsx-parentheses-position`
2483
-
2484
- **What it does:** Ensures the opening parenthesis `(` for multiline JSX is on the same line as `return` or `=>`, not on a new line.
2485
-
2486
- **Why use it:** Parenthesis on new line wastes vertical space and looks disconnected from the statement it belongs to.
2487
-
2488
- ```javascript
2489
- // ✅ Good — parenthesis on same line as =>
2490
- const Card = () => (
2491
- <div className="card">
2492
- <h1>Title</h1>
2493
- </div>
2494
- );
2495
-
2496
- // ✅ Good — parenthesis on same line as return
2497
- function Card() {
2498
- return (
2499
- <div className="card">
2500
- <h1>Title</h1>
2501
- </div>
2502
- );
2503
- }
2504
-
2505
- // ❌ Bad — parenthesis on new line after =>
2506
- const Card = () =>
2507
- (
2508
- <div className="card">
2509
- <h1>Title</h1>
2510
- </div>
2511
- );
2512
-
2513
- // ❌ Bad — parenthesis on new line after return
2514
- function Card() {
2515
- return
2516
- (
2517
- <div className="card">
2518
- <h1>Title</h1>
2519
- </div>
2520
- );
2521
- }
2522
- ```
2523
-
2524
- ---
2525
-
2526
- ### `jsx-prop-naming-convention`
2527
-
2528
- **What it does:** Enforces camelCase naming for JSX props, with exceptions for:
2529
- - `data-*` attributes (kebab-case allowed)
2530
- - `aria-*` attributes (kebab-case allowed)
2531
- - Props that reference components (PascalCase allowed)
2532
-
2533
- **Why use it:** Consistent prop naming follows React conventions and makes code predictable.
2534
-
2535
- ```javascript
2536
- // ✅ Good — camelCase props
2537
- <Button onClick={handleClick} isDisabled={false} />
2538
- <Input onChange={handleChange} autoFocus />
2539
- <Modal onClose={close} isVisible={true} />
2540
-
2541
- // ✅ Good — data-* and aria-* use kebab-case
2542
- <Button data-testid="submit-btn" aria-label="Submit" />
2543
- <Input data-cy="email-input" aria-describedby="help" />
2544
-
2545
- // ✅ Good — component reference props use PascalCase
2546
- <Modal ContentComponent={Panel} />
2547
- <Route Component={HomePage} />
2548
-
2549
- // ❌ Bad — snake_case props
2550
- <Button on_click={handler} is_disabled={false} />
2551
- <Input on_change={handler} auto_focus />
2552
-
2553
- // ❌ Bad — kebab-case for regular props
2554
- <Button is-disabled={false} />
2555
- ```
2556
-
2557
- ---
2558
-
2559
- ### `jsx-simple-element-one-line`
2560
-
2561
- **What it does:** Collapses simple JSX elements (single text or expression child) onto one line.
2562
-
2563
- **Why use it:** Simple elements don't need multi-line formatting. Single line is more compact and scannable.
2564
-
2565
- ```javascript
2566
- // ✅ Good — simple elements on one line
2567
- <Button>{buttonText}</Button>
2568
- <Title>Welcome</Title>
2569
- <span>{count}</span>
2570
- <Label>{user.name}</Label>
2571
-
2572
- // ✅ Good — complex children stay multiline
2573
- <Button>
2574
- <Icon />
2575
- {buttonText}
2576
- </Button>
2577
-
2578
- // ❌ Bad — unnecessary multiline for simple content
2579
- <Button>
2580
- {buttonText}
2581
- </Button>
2582
-
2583
- <Title>
2584
- Welcome
2585
- </Title>
2586
-
2587
- <span>
2588
- {count}
2589
- </span>
2590
- ```
2591
-
2592
- ---
2593
-
2594
- ### `jsx-string-value-trim`
2595
-
2596
- **What it does:** Removes leading and trailing whitespace inside JSX string attribute values.
2597
-
2598
- **Why use it:** Whitespace in class names and other string values is usually unintentional and can cause bugs (e.g., CSS class not matching).
2599
-
2600
- ```javascript
2601
- // ✅ Good — no extra whitespace
2602
- <Button className="primary" />
2603
- <Input placeholder="Enter email" />
2604
- <a href="/home">Home</a>
2605
-
2606
- // ❌ Bad — leading whitespace
2607
- <Button className=" primary" />
2608
- <Input placeholder=" Enter email" />
2609
-
2610
- // ❌ Bad — trailing whitespace
2611
- <Button className="primary " />
2612
- <a href="/home ">Home</a>
2613
-
2614
- // ❌ Bad — both
2615
- <Button className=" primary " />
2616
- ```
2617
-
2618
- ---
2619
-
2620
- ### `jsx-ternary-format`
2621
-
2622
- **What it does:** Formats ternary expressions in JSX consistently:
2623
- - Simple branches stay on one line
2624
- - Complex/multiline branches get parentheses with proper indentation
2625
-
2626
- **Why use it:** Consistent ternary formatting makes conditional rendering predictable and readable.
2627
-
2628
- ```javascript
2629
- // ✅ Good — simple ternary on one line
2630
- {isLoading ? <Spinner /> : <Content />}
2631
- {error ? <Error /> : <Success />}
2632
-
2633
- // ✅ Good — complex branches get parentheses
2634
- {isLoading ? (
2635
- <Spinner size="large" />
2636
- ) : (
2637
- <Content>
2638
- <Header />
2639
- <Body />
2640
- </Content>
2641
- )}
2642
-
2643
- // ✅ Good — one simple, one complex
2644
- {isLoading ? <Spinner /> : (
2645
- <Content>
2646
- <Header />
2647
- <Body />
2648
- </Content>
2649
- )}
2650
-
2651
- // ❌ Bad — awkward line breaks
2652
- {isLoading
2653
- ? <Spinner />
2654
- : <Content />}
2655
-
2656
- // ❌ Bad — missing parentheses for complex branch
2657
- {isLoading ? <Spinner /> : <Content>
2658
- <Header />
2659
- <Body />
2660
- </Content>}
2661
- ```
2662
-
2663
- ---
2664
-
2665
- ### `no-empty-lines-in-jsx`
2666
-
2667
- **What it does:** Removes empty lines inside JSX elements — between children and after opening/before closing tags.
2668
-
2669
- **Why use it:** Empty lines inside JSX create visual gaps that break the component's visual structure.
2670
-
2671
- ```javascript
2672
- // ✅ Good — no empty lines inside
2673
- <div>
2674
- <Header />
2675
- <Content />
2676
- <Footer />
2677
- </div>
2678
-
2679
- <Form>
2680
- <Input name="email" />
2681
- <Input name="password" />
2682
- <Button>Submit</Button>
2683
- </Form>
2684
-
2685
- // ❌ Bad — empty line after opening tag
2686
- <div>
2687
-
2688
- <Header />
2689
- <Content />
2690
- </div>
2691
-
2692
- // ❌ Bad — empty lines between children
2693
- <Form>
2694
- <Input name="email" />
2695
-
2696
- <Input name="password" />
2697
-
2698
- <Button>Submit</Button>
2699
- </Form>
2700
-
2701
- // ❌ Bad — empty line before closing tag
2702
- <div>
2703
- <Content />
2704
-
2705
- </div>
2706
- ```
2707
-
2708
- <br />
2709
-
2710
- ## 📦 Object Rules
2711
-
2712
- ### `no-empty-lines-in-objects`
2713
-
2714
- **What it does:** Removes empty lines within object literals — between properties and after opening/before closing braces.
2715
-
2716
- **Why use it:** Empty lines inside objects break the visual grouping of properties. Properties should flow as a cohesive unit.
2717
-
2718
- ```javascript
2719
- // ✅ Good — no empty lines
2720
- const user = {
2721
- name: "John",
2722
- email: "john@example.com",
2723
- role: "admin",
2724
- };
2725
-
2726
- const config = {
2727
- host: "localhost",
2728
- port: 3000,
2729
- debug: true,
2730
- };
2731
-
2732
- // ❌ Bad — empty line between properties
2733
- const user = {
2734
- name: "John",
2735
-
2736
- email: "john@example.com",
2737
-
2738
- role: "admin",
2739
- };
2740
-
2741
- // ❌ Bad — empty line after opening brace
2742
- const config = {
2743
-
2744
- host: "localhost",
2745
- port: 3000,
2746
- };
2747
-
2748
- // ❌ Bad — empty line before closing brace
2749
- const config = {
2750
- host: "localhost",
2751
- port: 3000,
2752
-
2753
- };
2754
- ```
2755
-
2756
- ---
2757
-
2758
- ### `object-property-per-line`
2759
-
2760
- **What it does:** Controls object formatting based on property count:
2761
- - 1 property: stays on single line `{ name: "John" }`
2762
- - 2+ properties: expands with `{` and `}` on own lines, each property on its own line
2763
-
2764
- **Why use it:** Single-property objects are clear on one line. Multiple properties need expansion for readability and clean diffs.
2765
-
2766
- ```javascript
2767
- // ✅ Good — single property stays compact
2768
- const point = { x: 10 };
2769
- const config = { debug: true };
2770
- fn({ callback: handleClick });
2771
-
2772
- // ✅ Good — 2+ properties get full expansion
2773
- const point = {
2774
- x: 10,
2775
- y: 20,
2776
- };
2777
-
2778
- const user = {
2779
- name: "John",
2780
- email: "john@example.com",
2781
- role: "admin",
2782
- };
2783
-
2784
- // ✅ Good — nested objects follow same rules
2785
- const config = {
2786
- server: { port: 3000 },
2787
- database: {
2788
- host: "localhost",
2789
- name: "mydb",
2790
- },
2791
- };
2792
-
2793
- // ❌ Bad — multiple properties on one line
2794
- const point = { x: 10, y: 20 };
2795
- const user = { name: "John", email: "john@example.com" };
2796
-
2797
- // ❌ Bad — inconsistent formatting
2798
- const point = { x: 10,
2799
- y: 20 };
2800
- ```
2801
-
2802
- **Options:**
2803
-
2804
- | Option | Type | Default | Description |
2805
- |--------|------|---------|-------------|
2806
- | `minProperties` | `integer` | `2` | Minimum properties to trigger expansion |
2807
-
2808
- ```javascript
2809
- // Example: Require 3+ properties for expansion
2810
- "code-style/object-property-per-line": ["error", { minProperties: 3 }]
2811
- ```
2812
-
2813
- ---
2814
-
2815
- ### `object-property-value-brace`
2816
-
2817
- **What it does:** Ensures opening braces of object values start on the same line as the colon, not on a new line.
2818
-
2819
- **Why use it:** Braces on new lines waste vertical space and disconnect the property key from its value.
2820
-
2821
- ```javascript
2822
- // ✅ Good — brace on same line as colon
2823
- const styles = {
2824
- "& a": { color: "red" },
2825
- "& button": { padding: "10px" },
2826
- };
2827
-
2828
- const config = {
2829
- server: {
2830
- host: "localhost",
2831
- port: 3000,
2832
- },
2833
- };
2834
-
2835
- // ❌ Bad — brace on new line
2836
- const styles = {
2837
- "& a":
2838
- { color: "red" },
2839
- "& button":
2840
- { padding: "10px" },
2841
- };
2842
-
2843
- // ❌ Bad — inconsistent
2844
- const config = {
2845
- server:
2846
- {
2847
- host: "localhost",
2848
- },
2849
- };
2850
- ```
2851
-
2852
- ---
2853
-
2854
- ### `object-property-value-format`
2855
-
2856
- **What it does:** Ensures property values start on the same line as the colon for simple values (strings, numbers, identifiers).
2857
-
2858
- **Why use it:** Values on new lines after the colon waste space and look disconnected from their keys.
2859
-
2860
- ```javascript
2861
- // ✅ Good — values on same line as colon
2862
- const user = {
2863
- name: "John",
2864
- age: 30,
2865
- isActive: true,
2866
- role: userRole,
2867
- };
2868
-
2869
- // ✅ Good — complex values can span lines
2870
- const config = {
2871
- handler: (event) => {
2872
- process(event);
2873
- },
2874
- items: [
2875
- "first",
2876
- "second",
2877
- ],
2878
- };
2879
-
2880
- // ❌ Bad — simple values on new line
2881
- const user = {
2882
- name:
2883
- "John",
2884
- age:
2885
- 30,
2886
- isActive:
2887
- true,
2888
- };
2889
- ```
2890
-
2891
- ---
2892
-
2893
- ### `string-property-spacing`
2894
-
2895
- **What it does:** Removes leading and trailing whitespace inside string property keys.
2896
-
2897
- **Why use it:** Whitespace in property keys is usually unintentional and can cause bugs when accessing properties.
2898
-
2899
- ```javascript
2900
- // ✅ Good — no extra whitespace
2901
- const styles = {
2902
- "& a": { color: "red" },
2903
- "& .button": { padding: "10px" },
2904
- "data-testid": "myElement",
2905
- };
2906
-
2907
- const obj = {
2908
- "Content-Type": "application/json",
2909
- "X-Custom-Header": "value",
2910
- };
2911
-
2912
- // ❌ Bad — leading whitespace
2913
- const styles = {
2914
- " & a": { color: "red" },
2915
- " & .button": { padding: "10px" },
2916
- };
2917
-
2918
- // ❌ Bad — trailing whitespace
2919
- const obj = {
2920
- "Content-Type ": "application/json",
2921
- };
2922
-
2923
- // ❌ Bad — both
2924
- const styles = {
2925
- " & a ": { color: "red" },
2926
- };
2927
- ```
2928
-
2929
- <br />
2930
-
2931
- ## 📐 Spacing Rules
2932
-
2933
- ### `assignment-value-same-line`
2934
-
2935
- **What it does:** Ensures the assigned value starts on the same line as the `=` sign, not on a new line.
2936
-
2937
- **Why use it:** Breaking after `=` creates awkward formatting and wastes vertical space. Keeping values on the same line as `=` is more readable.
2938
-
2939
- ```javascript
2940
- // ✅ Good — value starts on same line as =
2941
- const name = "John";
2942
- const config = {
2943
- host: "localhost",
2944
- port: 3000,
2945
- };
2946
- const items = [
2947
- "first",
2948
- "second",
2949
- ];
2950
-
2951
- // ❌ Bad — value on new line after =
2952
- const name =
2953
- "John";
2954
- const config =
2955
- {
2956
- host: "localhost",
2957
- port: 3000,
2958
- };
2959
- const items =
2960
- [
2961
- "first",
2962
- "second",
2963
- ];
2964
- ```
2965
-
2966
- ---
2967
-
2968
- ### `member-expression-bracket-spacing`
2969
-
2970
- **What it does:** Removes spaces inside brackets in computed member expressions (array access, dynamic property access).
2971
-
2972
- **Why use it:** Consistent with JavaScript conventions. Spaces inside brackets look inconsistent with array literals and other bracket usage.
2973
-
2974
- ```javascript
2975
- // ✅ Good — no spaces inside brackets
2976
- const value = arr[0];
2977
- const name = obj[key];
2978
- const item = data[index];
2979
- const nested = matrix[row][col];
2980
-
2981
- // ❌ Bad — spaces inside brackets
2982
- const value = arr[ 0 ];
2983
- const name = obj[ key ];
2984
- const item = data[ index ];
2985
- ```
2986
-
2987
- <br />
2988
-
2989
- ## 🧩 Component Rules
2990
-
2991
- ### `component-props-destructure`
2992
-
2993
- **What it does:** Enforces that React component props must be destructured in the function parameter, not received as a single `props` object.
2994
-
2995
- **Why use it:** Destructured props make it immediately clear what props a component uses. It improves readability and helps catch unused props.
2996
-
2997
- ```typescript
2998
- // ✅ Good — props are destructured
2999
- export const Button = ({ label, onClick, variant = "primary" }) => (
3000
- <button onClick={onClick} type="button">
3001
- {label}
3002
- </button>
3003
- );
3004
-
3005
- export const Card = ({
3006
- children,
3007
- className = "",
3008
- title,
3009
- } : {
3010
- children: ReactNode,
3011
- className?: string,
3012
- title: string,
3013
- }) => (
3014
- <div className={className}>
3015
- <h2>{title}</h2>
3016
- {children}
3017
- </div>
3018
- );
3019
-
3020
- // ❌ Bad — props received as single object
3021
- export const Button = (props) => (
3022
- <button onClick={props.onClick} type="button">
3023
- {props.label}
3024
- </button>
3025
- );
3026
-
3027
- export const Card = (props: CardPropsInterface) => (
3028
- <div className={props.className}>
3029
- <h2>{props.title}</h2>
3030
- {props.children}
3031
- </div>
3032
- );
3033
- ```
3034
-
3035
- ---
3036
-
3037
- ### `component-props-inline-type`
3038
-
3039
- **What it does:** Enforces that React component props must use inline type annotation instead of referencing an interface or type alias. Also enforces:
3040
- - Exactly one space before and after colon: `} : {`
3041
- - Props in type must match exactly with destructured props (no missing or extra)
3042
- - Each prop type on its own line when there are multiple props
3043
- - First prop type must be on new line after `{` when multiple props
3044
- - No empty lines after opening brace or before closing brace
3045
- - No space before `?` in optional properties (`prop?: type` not `prop ?: type`)
3046
- - Trailing commas (not semicolons) for each prop type
3047
- - No empty lines between prop types
3048
-
3049
- **Why use it:** Inline types keep the prop definitions colocated with the component, making it easier to understand and modify the component without jumping to separate interface definitions. Enforcing prop matching ensures type safety and prevents unused type properties.
3050
-
3051
- ```typescript
3052
- // ✅ Good — inline type annotation with matching props
3053
- export const Button = ({ label } : { label: string }) => (
3054
- <button type="button">{label}</button>
3055
- );
3056
-
3057
- export const Card = ({
3058
- className = "",
3059
- description,
3060
- title,
3061
- } : {
3062
- className?: string,
3063
- description?: string,
3064
- title: string,
3065
- }) => (
3066
- <div className={className}>
3067
- <h1>{title}</h1>
3068
- {description && <p>{description}</p>}
3069
- </div>
3070
- );
3071
-
3072
- // ❌ Bad — interface reference instead of inline type
3073
- interface ButtonPropsInterface {
3074
- label: string,
3075
- }
3076
- export const Button = ({ label }: ButtonPropsInterface) => (
3077
- <button type="button">{label}</button>
3078
- );
3079
-
3080
- // ❌ Bad — missing space before and after colon
3081
- export const Button = ({ label }:{ label: string }) => (
3082
- <button type="button">{label}</button>
3083
- );
3084
-
3085
- // ❌ Bad — props don't match (extra 'flag' in type, missing in destructured)
3086
- export const Card = ({
3087
- title,
3088
- } : {
3089
- flag: boolean,
3090
- title: string,
3091
- }) => (
3092
- <div>{title}</div>
3093
- );
3094
-
3095
- // ❌ Bad — semicolons instead of commas
3096
- export const Card = ({ title } : { title: string; }) => (
3097
- <div>{title}</div>
3098
- );
3099
-
3100
- // ❌ Bad — first prop on same line as opening brace
3101
- export const Card = ({
3102
- title,
3103
- } : { title: string,
3104
- className?: string,
3105
- }) => (
3106
- <div>{title}</div>
3107
- );
3108
-
3109
- // ❌ Bad — space before ? in optional property
3110
- export const Card = ({ title } : { title ?: string }) => (
3111
- <div>{title}</div>
3112
- );
3113
-
3114
- // ❌ Bad — props on same line when multiple
3115
- export const Card = ({ a, b } : { a: string, b: string }) => (
3116
- <div>{a}{b}</div>
3117
- );
3118
- ```
3119
-
3120
- ---
3121
-
3122
- ### `folder-based-naming-convention`
3123
-
3124
- **What it does:** Enforces naming conventions based on folder location, with chained folder names for nested files. Also enforces camelCase suffix for data/constants/strings/services/reducers folders (e.g., `authData`, `apiConstants`, `loginStrings`, `userServices`):
3125
-
3126
- | Folder | Suffix | Example |
3127
- |--------|--------|---------|
3128
- | `views/` | View | `DashboardView` |
3129
- | `layouts/` | Layout | `MainLayout` |
3130
- | `pages/` | Page | `HomePage` |
3131
- | `providers/` | Provider | `AuthProvider` |
3132
- | `reducers/` | Reducer | `UserReducer` |
3133
- | `contexts/` | Context | `AuthContext` |
3134
- | `theme/` / `themes/` | Theme | `DarkTheme` |
3135
- | `data/` | Data (camelCase) | `authData` |
3136
- | `constants/` | Constants (camelCase) | `apiConstants` |
3137
- | `strings/` | Strings (camelCase) | `loginStrings` |
3138
- | `services/` | Services (camelCase) | `userServices` |
3139
- | `atoms/` | *(none)* | `Button` |
3140
- | `components/` | *(none)* | `Card` |
3141
-
3142
- Nested files chain folder names (e.g., `layouts/auth/login.tsx` → `LoginAuthLayout`, `atoms/input/password.tsx` → `PasswordInput`).
3143
-
3144
- **Why use it:** Consistent naming based on folder structure makes purpose immediately clear. The chained naming encodes the full path context into the name. The camelCase suffix for data/constants/strings/services/reducers folders distinguishes these utility modules from PascalCase component-like entities.
3145
-
3146
- ```tsx
3147
- // ✅ Good — suffix folders
3148
- // in views/dashboard.tsx
3149
- export const DashboardView = () => <div>Dashboard</div>;
3150
-
3151
- // in layouts/auth/login.tsx (chained: Login + Auth + Layout)
3152
- export const LoginAuthLayout = () => <div>Login</div>;
3153
-
3154
- // in providers/auth.tsx
3155
- export const AuthProvider = ({ children }) => <AuthContext.Provider>{children}</AuthContext.Provider>;
3156
-
3157
- // in contexts/auth.ts
3158
- export const AuthContext = createContext(null);
3159
-
3160
- // in reducers/user.ts
3161
- export const UserReducer = (state, action) => { ... };
3162
-
3163
- // in themes/dark.ts
3164
- export const DarkTheme = { primary: "#000" };
3165
-
3166
- // ✅ Good — camelCase suffix folders
3167
- // in data/auth.ts
3168
- export const authData = { ... };
3169
-
3170
- // in constants/api.ts
3171
- export const apiConstants = { ... };
3172
-
3173
- // in strings/login.ts
3174
- export const loginStrings = { ... };
3175
-
3176
- // in services/user.ts
3177
- export const userServices = { ... };
3178
-
3179
- // ✅ Good — no-suffix folders (chaining only)
3180
- // in atoms/input/password.tsx (chained: Password + Input)
3181
- export const PasswordInput = () => <input type="password" />;
3182
-
3183
- // in atoms/button.tsx
3184
- export const Button = () => <button>Click</button>;
3185
-
3186
- // in components/card.tsx
3187
- export const Card = () => <div>Card</div>;
3188
-
3189
- // ❌ Bad
3190
- // in layouts/auth/login.tsx (should be "LoginAuthLayout")
3191
- export const Login = () => <div>Login</div>;
3192
-
3193
- // in reducers/user.ts (should be "UserReducer")
3194
- export const User = (state, action) => { ... };
3195
-
3196
- // in atoms/input/password.tsx (should be "PasswordInput")
3197
- export const Password = () => <input type="password" />;
3198
- ```
3199
-
3200
- > **Note:** Module barrel files (e.g., `views/index.ts`) are skipped. Interfaces, enums, and types have their own naming rules (`interface-format`, `enum-format`, `type-format`). Auto-fix renames the identifier and all its references.
3201
-
3202
- ---
3203
-
3204
- ### `folder-structure-consistency`
3205
-
3206
- **What it does:** Enforces that module folders have a consistent internal structure — either all flat files or all wrapped in subfolders. Wrapped mode is only justified when at least one subfolder contains 2+ files. Applies to the same folders as `module-index-exports`: atoms, components, hooks, utils, enums, types, interfaces, reducers, layouts, views, pages, and more.
3207
-
3208
- **Why use it:** Mixing flat files and wrapped folders in the same directory creates inconsistency. This rule ensures a uniform structure — if one item needs a folder (because it has multiple files), all items should be wrapped; if none do, keep everything flat.
3209
-
3210
- **Configurable options:**
3211
- | Option | Default | Description |
3212
- |--------|---------|-------------|
3213
- | `moduleFolders` | Same as `module-index-exports` (atoms, components, hooks, utils, enums, types, views, layouts, pages, etc.) | Replace the entire folder list |
3214
- | `extraModuleFolders` | `[]` | Add extra folders on top of the defaults |
3215
-
3216
- ```
3217
- // ✅ Good — flat mode (all direct files)
3218
- atoms/input.tsx
3219
- atoms/calendar.tsx
3220
-
3221
- // ✅ Good — wrapped mode (justified — input has multiple files)
3222
- atoms/input/index.tsx
3223
- atoms/input/helpers.ts
3224
- atoms/calendar/index.tsx
3225
-
3226
- // ❌ Bad — mixed (some flat, some wrapped with justification)
3227
- atoms/input.tsx → "all items should be wrapped in folders"
3228
- atoms/calendar/index.tsx
3229
- atoms/calendar/helpers.ts
3230
-
3231
- // ❌ Bad — wrapped but unnecessary (each folder has only 1 file)
3232
- atoms/input/index.tsx → "use direct files instead"
3233
- atoms/calendar/index.tsx
3234
-
3235
- // ❌ Bad — mixed without justification
3236
- atoms/input.tsx
3237
- atoms/calendar/index.tsx → "use direct files instead"
3238
- ```
3239
-
3240
- > **Note:** This rule applies equally to all module folders — component folders (atoms, components, views), data folders (enums, types, interfaces), and utility folders (hooks, utils, helpers). The `folder-based-naming-convention` naming rule is separate and enforces export naming based on folder location.
3241
-
3242
- ---
3243
-
3244
- ### `no-redundant-folder-suffix`
3245
-
3246
- **What it does:** Flags files and folders whose name redundantly includes the parent (or ancestor) folder name as a suffix. Since the folder already provides context, the name doesn't need to repeat it.
3247
-
3248
- **Why use it:** This complements `folder-based-naming-convention` — the *file name* should stay clean while the *exported component name* carries the suffix. For example, `layouts/main.tsx` exporting `MainLayout` is better than `layouts/main-layout.tsx` exporting `MainLayout`.
3249
-
3250
- ```
3251
- // ✅ Good — names don't repeat the folder name
3252
- layouts/main.tsx → export const MainLayout = ...
3253
- atoms/button.tsx → file "button" has no redundant suffix
3254
- views/dashboard.tsx → file "dashboard" has no redundant suffix
3255
- views/access-control/... → folder "access-control" has no redundant suffix
3256
- atoms/input/index.tsx → uses "index" inside folder (correct)
3257
-
3258
- // ❌ Bad — file name matches parent folder name (use index instead)
3259
- atoms/input/input.tsx → use "input/index.tsx" instead
3260
- components/card/card.tsx → use "card/index.tsx" instead
3261
-
3262
- // ❌ Bad — file names redundantly include the folder suffix
3263
- layouts/main-layout.tsx → redundant "-layout" (already in layouts/)
3264
- atoms/button-atom.tsx → redundant "-atom" (already in atoms/)
3265
- views/dashboard-view.tsx → redundant "-view" (already in views/)
3266
-
3267
- // ❌ Bad — folder names redundantly include the ancestor suffix
3268
- views/access-control-view/ → redundant "-view" (already in views/)
3269
-
3270
- // Nested names are also checked against ancestor folders
3271
- atoms/forms/input-atom.tsx → redundant "-atom" from ancestor "atoms/"
3272
- ```
3273
-
3274
- > **Note:** Index files (`index.ts`, `index.js`, etc.) are skipped for file name checks. Folder names are singularized automatically (e.g., `layouts` → `layout`, `categories` → `category`, `classes` → `class`).
3275
-
3276
- ---
3277
-
3278
- ### `svg-icon-naming-convention`
3279
-
3280
- **What it does:** Enforces naming conventions for SVG icon components:
3281
- - Components that return only an SVG element must have a name ending with "Icon"
3282
- - Components with "Icon" suffix must return an SVG element
3283
-
3284
- **Why use it:** Consistent naming makes it immediately clear which components render icons, improving code readability and making icon components easier to find in large codebases.
3285
-
3286
- ```tsx
3287
- // ✅ Good — returns SVG and ends with "Icon"
3288
- export const SuccessIcon = ({ className = "" }: { className?: string }) => (
3289
- <svg className={className}>
3290
- <path d="M9 12l2 2 4-4" />
3291
- </svg>
3292
- );
3293
-
3294
- // ✅ Good — returns non-SVG and doesn't end with "Icon"
3295
- export const Button = ({ children }: { children: React.ReactNode }) => (
3296
- <button>{children}</button>
3297
- );
3298
-
3299
- // ❌ Bad — returns SVG but doesn't end with "Icon"
3300
- export const Success = ({ className = "" }: { className?: string }) => (
3301
- <svg className={className}>
3302
- <path d="M9 12l2 2 4-4" />
3303
- </svg>
3304
- );
3305
- // Error: Component "Success" returns an SVG element and should end with "Icon" suffix
3306
-
3307
- // ❌ Bad — ends with "Icon" but doesn't return SVG
3308
- export const ButtonIcon = ({ children }: { children: React.ReactNode }) => (
3309
- <button>{children}</button>
3310
- );
3311
- // Error: Component "ButtonIcon" has "Icon" suffix but doesn't return an SVG element
3312
- ```
3313
-
3314
- <br />
3315
-
3316
- ## 🔷 TypeScript Rules
3317
-
3318
- ### `enum-format`
3319
-
3320
- **What it does:** Enforces consistent formatting for TypeScript enums:
3321
- - Enum names must be PascalCase and end with `Enum` suffix
3322
- - Enum members must be UPPER_CASE (for string enums) or PascalCase (for numeric enums)
3323
- - No empty lines between enum members
3324
- - Members must end with commas, not semicolons
3325
-
3326
- **Why use it:** Consistent enum naming makes enums instantly recognizable. UPPER_CASE members follow common conventions for constants.
3327
-
3328
- ```typescript
3329
- // ✅ Good — proper enum format
3330
- export enum StatusEnum {
3331
- ACTIVE = "active",
3332
- INACTIVE = "inactive",
3333
- PENDING = "pending",
3334
- }
3335
-
3336
- export enum HttpMethodEnum {
3337
- DELETE = "DELETE",
3338
- GET = "GET",
3339
- POST = "POST",
3340
- PUT = "PUT",
3341
- }
3342
-
3343
- // ❌ Bad — wrong naming
3344
- export enum Status { // Missing Enum suffix
3345
- active = "active", // Should be UPPER_CASE
3346
- inactive = "inactive"; // Should use comma, not semicolon
3347
- }
3348
-
3349
- // ❌ Bad — empty lines between members
3350
- export enum UserStatusEnum {
3351
- ACTIVE = "active",
3352
-
3353
- INACTIVE = "inactive",
3354
- }
3355
- ```
3356
-
3357
- ---
3358
-
3359
- ### `enum-type-enforcement`
3360
-
3361
- **What it does:** When a variable or parameter has a type ending in `Type` (like `ButtonVariantType`), enforces using the corresponding enum (`ButtonVariantEnum.VALUE`) instead of string literals.
3362
-
3363
- **Why use it:** Using enum values instead of string literals provides type safety, autocompletion, and prevents typos. Changes to enum values automatically propagate.
3364
-
3365
- ```javascript
3366
- // ✅ Good — using enum values
3367
- const Button = ({
3368
- variant = ButtonVariantEnum.PRIMARY,
3369
- }: {
3370
- variant?: ButtonVariantType,
3371
- }) => { ... };
3372
-
3373
- if (variant === ButtonVariantEnum.GHOST) {
3374
- // ...
3375
- }
3376
-
3377
- // ❌ Bad — using string literals
3378
- const Button = ({
3379
- variant = "primary", // Should use ButtonVariantEnum.PRIMARY
3380
- }: {
3381
- variant?: ButtonVariantType,
3382
- }) => { ... };
3383
-
3384
- if (variant === "ghost") { // Should use ButtonVariantEnum.GHOST
3385
- // ...
3386
- }
3387
- ```
3388
-
3389
- ---
3390
-
3391
- ### `interface-format`
3392
-
3393
- **What it does:** Enforces consistent formatting for TypeScript interfaces:
3394
- - Interface names must be PascalCase and end with `Interface` suffix
3395
- - Properties must be camelCase
3396
- - No empty lines between properties
3397
- - Properties must end with commas, not semicolons
3398
-
3399
- **Why use it:** Consistent interface naming makes interfaces instantly recognizable. The suffix clearly distinguishes interfaces from types and classes.
3400
-
3401
- ```typescript
3402
- // ✅ Good — proper interface format
3403
- export interface UserInterface {
3404
- email: string,
3405
- id: string,
3406
- isActive: boolean,
3407
- name: string,
3408
- }
3409
-
3410
- export interface ApiResponseInterface<T> {
3411
- data: T,
3412
- error: string | null,
3413
- status: number,
3414
- success: boolean,
3415
- }
3416
-
3417
- // ❌ Bad — wrong naming
3418
- export interface User { // Missing Interface suffix
3419
- Email: string; // Should be camelCase
3420
- ID: string; // Should be camelCase
3421
- is_active: boolean; // Should be camelCase, use comma
3422
- }
3423
-
3424
- // ❌ Bad — semicolons and empty lines
3425
- export interface UserInterface {
3426
- email: string; // Should use comma
3427
-
3428
- name: string; // Empty line not allowed
3429
- }
3430
- ```
3431
-
3432
- ---
3433
-
3434
- ### `no-inline-type-definitions`
3435
-
3436
- **What it does:** Reports when function parameters have inline union types that are too complex (too many members or too long). These should be extracted to a named type in a types file.
3437
-
3438
- **Why use it:** Complex inline types make function signatures hard to read. Named types are reusable, self-documenting, and easier to maintain.
3439
-
3440
- **Options:**
3441
-
3442
- | Option | Type | Default | Description |
3443
- |--------|------|---------|-------------|
3444
- | `maxUnionMembers` | `integer` | `2` | Maximum union members before requiring extraction |
3445
- | `maxLength` | `integer` | `50` | Maximum character length before requiring extraction |
3446
-
3447
- ```javascript
3448
- // ✅ Good — type extracted to separate file
3449
- // types.ts
3450
- export type ButtonVariantType = "primary" | "muted" | "danger";
3451
-
3452
- // Button.tsx
3453
- import { ButtonVariantType } from "./types";
3454
- export const Button = ({
3455
- variant,
3456
- }: {
3457
- variant?: ButtonVariantType,
3458
- }) => { ... };
3459
-
3460
- // ❌ Bad — complex inline union type
3461
- export const Button = ({
3462
- variant,
3463
- }: {
3464
- variant?: "primary" | "muted" | "danger", // Extract to named type
3465
- }) => { ... };
3466
- ```
3467
-
3468
- ---
3469
-
3470
- ### `prop-naming-convention`
3471
-
3472
- **What it does:** Enforces naming conventions for boolean and callback props in TypeScript interfaces, types, and inline type definitions:
3473
- - Boolean props must start with: `is`, `has`, `with`, or `without` (followed by capital letter)
3474
- - Callback props must start with: `on` (followed by capital letter)
3475
- - Detects React event handler types: `MouseEventHandler`, `ChangeEventHandler`, `FormEventHandler`, `KeyboardEventHandler`, etc.
3476
- - Applies to all nesting levels (nested object types are checked recursively)
3477
- - Does NOT apply to JSX element attributes (external components have their own props)
3478
-
3479
- **Why use it:** Consistent prop naming makes props self-documenting. Boolean prefixes clarify intent (`isLoading` vs `loading`), and `on` prefix clearly identifies event handlers.
3480
-
3481
- **Options:**
3482
-
3483
- | Option | Type | Default | Description |
3484
- |--------|------|---------|-------------|
3485
- | `booleanPrefixes` | `string[]` | - | Replace default prefixes entirely (overrides defaults) |
3486
- | `extendBooleanPrefixes` | `string[]` | `[]` | Add to default prefixes (`is`, `has`, `with`, `without`) |
3487
- | `allowPastVerbBoolean` | `boolean` | `false` | Allow past verb booleans (e.g., `disabled`, `selected`, `checked`, `opened`) |
3488
- | `allowContinuousVerbBoolean` | `boolean` | `false` | Allow continuous verb booleans (e.g., `loading`, `saving`, `fetching`) |
3489
- | `callbackPrefix` | `string` | `"on"` | Required prefix for callback props |
3490
- | `allowActionSuffix` | `boolean` | `false` | Allow `xxxAction` pattern for callbacks |
3491
-
3492
- ```typescript
3493
- // ✅ Good — proper prop naming
3494
- interface ButtonPropsInterface {
3495
- isDisabled: boolean,
3496
- isLoading: boolean,
3497
- hasError: boolean,
3498
- onClick: () => void,
3499
- onSubmit: (data: FormData) => void,
3500
- }
3501
-
3502
- type CardPropsType = {
3503
- isExpanded: boolean,
3504
- hasChildren: boolean,
3505
- onToggle: () => void,
3506
- };
3507
-
3508
- // ✅ Good — nested types are also checked
3509
- interface FormPropsInterface {
3510
- isValid: boolean,
3511
- config: {
3512
- isEnabled: boolean, // Nested - checked
3513
- onValidate: () => void, // Nested - checked
3514
- settings: {
3515
- isActive: boolean, // Deep nested - also checked
3516
- },
3517
- },
3518
- }
3519
-
3520
- // ✅ Good — inline component props
3521
- const Button = ({
3522
- isLoading,
3523
- onClick,
3524
- }: {
3525
- isLoading: boolean,
3526
- onClick: () => void,
3527
- }) => { ... };
3528
-
3529
- // ❌ Bad — missing prefixes
3530
- interface ButtonPropsInterface {
3531
- disabled: boolean, // Should be isDisabled
3532
- loading: boolean, // Should be isLoading
3533
- error: boolean, // Should be hasError
3534
- click: () => void, // Should be onClick
3535
- handleSubmit: () => void, // Should be onSubmit
3536
- }
3537
-
3538
- // ❌ Bad — nested types also checked
3539
- type PropsType = {
3540
- config: {
3541
- enabled: boolean, // Should be isEnabled
3542
- toggle: () => void, // Should be onToggle
3543
- },
3544
- };
3545
- ```
3546
-
3547
- **Past Verb Booleans** (`allowPastVerbBoolean: true`):
3548
-
3549
- When enabled, allows boolean props that are past tense verbs (ending in `-ed`):
3550
-
3551
- ```typescript
3552
- // ✅ Allowed with allowPastVerbBoolean: true
3553
- interface PropsInterface {
3554
- disabled: boolean, // Past verb - ends with -ed
3555
- selected: boolean, // Past verb - ends with -ed
3556
- checked: boolean, // Past verb - ends with -ed
3557
- opened: boolean, // Past verb - ends with -ed
3558
- closed: boolean, // Past verb - ends with -ed
3559
- expanded: boolean, // Past verb - ends with -ed
3560
- collapsed: boolean, // Past verb - ends with -ed
3561
- focused: boolean, // Past verb - ends with -ed
3562
- hidden: boolean, // Past verb - ends with -ed
3563
- connected: boolean, // Past verb - ends with -ed
3564
- }
3565
- ```
3566
-
3567
- **Continuous Verb Booleans** (`allowContinuousVerbBoolean: true`):
3568
-
3569
- When enabled, allows boolean props that are continuous tense verbs (ending in `-ing`):
3570
-
3571
- ```typescript
3572
- // ✅ Allowed with allowContinuousVerbBoolean: true
3573
- interface PropsInterface {
3574
- loading: boolean, // Continuous verb - ends with -ing
3575
- saving: boolean, // Continuous verb - ends with -ing
3576
- fetching: boolean, // Continuous verb - ends with -ing
3577
- closing: boolean, // Continuous verb - ends with -ing
3578
- opening: boolean, // Continuous verb - ends with -ing
3579
- submitting: boolean, // Continuous verb - ends with -ing
3580
- processing: boolean, // Continuous verb - ends with -ing
3581
- updating: boolean, // Continuous verb - ends with -ing
3582
- deleting: boolean, // Continuous verb - ends with -ing
3583
- pending: boolean, // Continuous verb - ends with -ing
3584
- }
3585
- ```
3586
-
3587
- **Configuration Examples:**
3588
-
3589
- ```javascript
3590
- // Default configuration (strict)
3591
- "code-style/prop-naming-convention": "error"
3592
-
3593
- // Allow past verb booleans (disabled, selected, checked, etc.)
3594
- "code-style/prop-naming-convention": ["error", {
3595
- allowPastVerbBoolean: true,
3596
- }]
3597
-
3598
- // Allow continuous verb booleans (loading, saving, fetching, etc.)
3599
- "code-style/prop-naming-convention": ["error", {
3600
- allowContinuousVerbBoolean: true,
3601
- }]
3602
-
3603
- // Allow both past and continuous verb booleans
3604
- "code-style/prop-naming-convention": ["error", {
3605
- allowPastVerbBoolean: true,
3606
- allowContinuousVerbBoolean: true,
3607
- }]
3608
-
3609
- // Extend default prefixes with additional ones
3610
- "code-style/prop-naming-convention": ["error", {
3611
- extendBooleanPrefixes: ["should", "can", "will", "did"],
3612
- }]
3613
-
3614
- // Replace default prefixes entirely
3615
- "code-style/prop-naming-convention": ["error", {
3616
- booleanPrefixes: ["is", "has"], // Only these prefixes allowed
3617
- }]
3618
-
3619
- // Allow "xxxAction" suffix for callbacks
3620
- "code-style/prop-naming-convention": ["error", {
3621
- allowActionSuffix: true, // Allows: submitAction, copyAction, deleteAction
3622
- }]
3623
-
3624
- // Full custom configuration
3625
- "code-style/prop-naming-convention": ["error", {
3626
- extendBooleanPrefixes: ["should", "can"],
3627
- allowPastVerbBoolean: true,
3628
- allowContinuousVerbBoolean: true,
3629
- callbackPrefix: "on",
3630
- allowActionSuffix: true,
3631
- }]
3632
- ```
3633
-
3634
- ---
3635
-
3636
- ### `type-format`
3637
-
3638
- **What it does:** Enforces consistent formatting for TypeScript type aliases:
3639
- - Type names must be PascalCase and end with `Type` suffix
3640
- - Properties must be camelCase
3641
- - No empty lines between properties
3642
- - Properties must end with commas, not semicolons
3643
- - Union types with 5+ members must be multiline (one per line)
3644
- - Union types with <5 members must be single line
3645
-
3646
- **Why use it:** Consistent type naming makes types instantly recognizable. The suffix clearly distinguishes types from interfaces and classes.
3647
-
3648
- ```typescript
3649
- // ✅ Good — proper type format
3650
- export type UserType = {
3651
- email: string,
3652
- id: string,
3653
- name: string,
3654
- };
3655
-
3656
- export type ApiResponseType<T> = {
3657
- data: T,
3658
- error: string | null,
3659
- status: number,
3660
- };
3661
-
3662
- // ✅ Good — union type with 6 members (multiline)
3663
- export type ButtonVariantType =
3664
- "danger"
3665
- | "ghost"
3666
- | "ghost-danger"
3667
- | "link"
3668
- | "muted"
3669
- | "primary";
3670
-
3671
- // ✅ Good — union type with 2 members (single line)
3672
- export type CodeLayoutVariantType = "default" | "error";
3673
-
3674
- // ❌ Bad — 6 members should be multiline
3675
- export type BadUnionType = "a" | "b" | "c" | "d" | "e" | "f";
3676
-
3677
- // ❌ Bad — 2 members should be single line
3678
- export type BadSingleType =
3679
- "default"
3680
- | "error";
3681
-
3682
- // ❌ Bad — wrong naming
3683
- export type User = { // Missing Type suffix
3684
- Email: string; // Should be camelCase
3685
- ID: string; // Should use comma
3686
- };
3687
-
3688
- // ❌ Bad — empty lines
3689
- export type ConfigType = {
3690
- debug: boolean,
3691
-
3692
- port: number, // Empty line not allowed
3693
- };
3694
- ```
3695
-
3696
- **Options:**
3697
-
3698
- | Option | Type | Default | Description |
3699
- |--------|------|---------|-------------|
3700
- | `minUnionMembersForMultiline` | `integer` | `5` | Minimum number of union members to require multiline format |
3701
-
3702
- ```javascript
3703
- // Configuration example - require multiline for 4+ union members
3704
- "code-style/type-format": ["error", { minUnionMembersForMultiline: 4 }]
3705
- ```
3706
-
3707
- ---
3708
-
3709
- ### `type-annotation-spacing`
3710
-
3711
- **What it does:** Enforces consistent spacing in TypeScript type annotations:
3712
- - No space before the colon in type annotations: `name: string` not `name : string`
3713
- - One space after the colon: `name: string` not `name:string`
3714
- - No space before generic type parameters: `Array<T>` not `Array <T>`
3715
- - No space before array brackets: `string[]` not `string []`
3716
-
3717
- **Why use it:** Consistent type annotation spacing follows TypeScript conventions and improves code readability.
3718
-
3719
- ```typescript
3720
- // ✅ Good — proper spacing
3721
- const name: string = "John";
3722
- const items: string[] = [];
3723
- const data: Array<number> = [];
3724
- const handler = (value: string): boolean => true;
3725
-
3726
- function getData<T>(id: string): Promise<T> {
3727
- return fetch(id);
3728
- }
3729
-
3730
- // ❌ Bad — space before colon
3731
- const name : string = "John";
3732
- const handler = (value : string) : boolean => true;
3733
-
3734
- // ❌ Bad — no space after colon
3735
- const name:string = "John";
3736
- const handler = (value:string):boolean => true;
3737
-
3738
- // ❌ Bad — space before generic
3739
- const data: Array <number> = [];
3740
- function getData <T>(id: string): Promise <T> {}
3741
-
3742
- // ❌ Bad — space before array brackets
3743
- const items: string [] = [];
3744
- ```
3745
-
3746
- ---
3747
-
3748
- ### `typescript-definition-location`
3749
-
3750
- **What it does:** Enforces that TypeScript definitions are placed in their designated folders:
3751
- - Interfaces must be in files inside the `interfaces` folder
3752
- - Types must be in files inside the `types` folder
3753
- - Enums must be in files inside the `enums` folder
3754
-
3755
- **Why use it:** Separating type definitions by category makes them easier to find, maintain, and share across the codebase. It promotes a clean and organized project structure.
3756
-
3757
- ```typescript
3758
- // ✅ Good — definitions in correct folders
3759
- // src/interfaces/user.ts
3760
- export interface UserInterface {
3761
- id: string,
3762
- name: string,
3763
- }
3764
-
3765
- // src/types/config.ts
3766
- export type ConfigType = {
3767
- apiUrl: string,
3768
- timeout: number,
3769
- };
3770
-
3771
- // src/enums/status.ts
3772
- export enum UserRoleEnum {
3773
- ADMIN = "admin",
3774
- USER = "user",
3775
- }
3776
-
3777
- // ❌ Bad — definitions in wrong folders
3778
- // src/components/user-card.tsx
3779
- interface UserProps { // Should be in interfaces folder
3780
- name: string,
3781
- }
3782
-
3783
- // src/types/user.ts
3784
- export interface UserInterface { // Should be in interfaces folder, not types
3785
- id: string,
3786
- }
3787
-
3788
- export enum StatusEnum { // Should be in enums folder, not types
3789
- ACTIVE = "active",
3790
- }
3791
- ```
3792
-
3793
- <br />
3794
-
3795
- ## ⚛️ React Rules
3796
-
3797
- ### `react-code-order`
3798
-
3799
- **What it does:** Enforces a consistent ordering of code blocks within React components and custom hooks. The order follows a logical dependency chain where declarations appear before their usage.
3800
-
3801
- **Order (top to bottom):**
3802
- 1. Props/params destructure (in function signature: `({ prop1, prop2 })`)
3803
- 2. Props/params destructure in body (`const { x } = propValue` where propValue is a prop)
3804
- 3. `useRef` declarations
3805
- 4. `useState` declarations
3806
- 5. `useReducer` declarations
3807
- 6. `useSelector` / `useDispatch` (Redux hooks)
3808
- 7. Router hooks (`useNavigate`, `useLocation`, `useParams`, `useSearchParams`)
3809
- 8. Context hooks (`useContext`, `useToast`, etc.)
3810
- 9. Custom hooks (`use*` pattern)
3811
- 10. Derived state / variables (computed from hooks above, e.g., `const isSearching = term.length > 0`)
3812
- 11. `useMemo` declarations
3813
- 12. `useCallback` declarations
3814
- 13. Handler functions (`const handleX = () => {}`)
3815
- 14. `useEffect` / `useLayoutEffect`
3816
- 15. Return statement
3817
-
3818
- **Why use it:** A consistent code structure makes components and hooks predictable and easier to navigate. Placing hooks before derived values ensures dependencies are defined before use. Effects come last because they typically depend on everything else.
3819
-
3820
- ```typescript
3821
- // ✅ Good — Component follows the correct order
3822
- const UserDashboard = ({ title }) => {
3823
- // 1. useRef
3824
- const inputRef = useRef(null);
3825
-
3826
- // 2. useState
3827
- const [count, setCount] = useState(0);
3828
- const [isLoading, setIsLoading] = useState(false);
3829
-
3830
- // 3. Redux hooks
3831
- const dispatch = useDispatch();
3832
- const user = useSelector((state) => state.user);
3833
-
3834
- // 4. Router hooks
3835
- const navigate = useNavigate();
3836
- const { id } = useParams();
3837
-
3838
- // 5. Custom hooks
3839
- const { data, loading } = useFetchData(id);
3840
-
3841
- // 6. Derived state
3842
- const isReady = !loading && data !== null;
3843
- const displayName = user?.name ?? "Guest";
3844
-
3845
- // 7. useMemo
3846
- const filteredItems = useMemo(
3847
- () => data?.filter((item) => item.active),
3848
- [data],
3849
- );
3850
-
3851
- // 8. useCallback
3852
- const handleSubmit = useCallback(
3853
- () => {
3854
- dispatch(submitAction());
3855
- },
3856
- [dispatch],
3857
- );
3858
-
3859
- // 9. Handler functions
3860
- const resetHandler = () => {
3861
- setCount(0);
3862
- setIsLoading(false);
3863
- };
3864
-
3865
- // 10. useEffect
3866
- useEffect(
3867
- () => {
3868
- inputRef.current?.focus();
3869
- },
3870
- [],
3871
- );
3872
-
3873
- // 11. Return
3874
- return (
3875
- <div>
3876
- <h1>{title}</h1>
3877
- <span>{displayName}</span>
3878
- </div>
3879
- );
3880
- };
3881
-
3882
- // ✅ Good — Custom hook follows the correct order
3883
- const useCreateAccount = () => {
3884
- // 1. useState
3885
- const [loading, setLoading] = useState(false);
3886
- const [created, setCreated] = useState(false);
3887
-
3888
- // 2. Redux hooks
3889
- const dispatch = useDispatch();
3890
-
3891
- // 3. Context hooks
3892
- const { toast } = useToast();
3893
-
3894
- // 4. Handler functions
3895
- const createAccountHandler = async (data: AccountData) => {
3896
- setLoading(true);
3897
- try {
3898
- await api.createAccount(data);
3899
- setCreated(true);
3900
- } catch (error) {
3901
- toast({ description: "Failed to create account" });
3902
- } finally {
3903
- setLoading(false);
3904
- }
3905
- };
3906
-
3907
- // 5. useEffect
3908
- useEffect(
3909
- () => {
3910
- if (created) {
3911
- setTimeout(() => setCreated(false), 50);
3912
- }
3913
- },
3914
- [created],
3915
- );
3916
-
3917
- // 6. Return
3918
- return { createAccountHandler, created, loading };
3919
- };
3920
-
3921
- // ❌ Bad — useEffect before useState
3922
- const BadComponent = ({ title }) => {
3923
- useEffect(() => {
3924
- console.log("mounted");
3925
- }, []);
3926
-
3927
- const [count, setCount] = useState(0);
3928
-
3929
- return <div>{title}</div>;
3930
- };
3931
-
3932
- // ❌ Bad — context hook before useState in custom hook
3933
- const useBadHook = () => {
3934
- const { toast } = useToast(); // Should come after useState
3935
- const [loading, setLoading] = useState(false);
3936
- return { loading };
3937
- };
3938
-
3939
- // ❌ Bad — handler before hooks
3940
- const AnotherBadComponent = ({ title }) => {
3941
- const handleClick = () => {
3942
- console.log("clicked");
3943
- };
3944
-
3945
- const dispatch = useDispatch();
3946
- const [count, setCount] = useState(0);
3947
-
3948
- return <div onClick={handleClick}>{title}</div>;
3949
- };
3950
-
3951
- // ❌ Bad — derived state after handler
3952
- const YetAnotherBad = ({ title }) => {
3953
- const [items, setItems] = useState([]);
3954
-
3955
- const handleAdd = () => {
3956
- setItems([...items, "new"]);
3957
- };
3958
-
3959
- const itemCount = items.length; // Should come before handleAdd
3960
-
3961
- return <div>{itemCount}</div>;
3962
- };
3963
- ```
3964
-
3965
- <br />
3966
-
3967
- ## 📝 String Rules
3968
-
3969
- ### `no-hardcoded-strings`
3970
-
3971
- **What it does:** Enforces that user-facing strings should be imported from constants/strings/data modules rather than hardcoded inline. This promotes maintainability, consistency, and enables easier internationalization.
3972
-
3973
- **Why use it:** Hardcoded strings scattered throughout your codebase are hard to maintain, translate, and keep consistent. Centralizing strings in constants makes them easy to find, update, and potentially translate.
3974
-
3975
- **Special detection (should be imported from `@/enums` or `@/data`):**
3976
- - **HTTP status codes** — 2xx, 4xx, 5xx like "200", "404", "500"
3977
- - **HTTP methods** — "GET", "POST", "PUT", "DELETE", "PATCH", etc.
3978
- - **Role/permission names** — "admin", "user", "moderator", "editor", etc.
3979
- - **Environment names** — "production", "development", "staging", "test", etc.
3980
- - **Log levels** — "debug", "info", "warn", "error", "fatal", etc.
3981
- - **Status strings** — "active", "pending", "approved", "rejected", "completed", etc.
3982
- - **Priority levels** — "high", "medium", "low", "critical", "urgent", etc.
3983
-
3984
- **Options:**
3985
-
3986
- | Option | Type | Default | Description |
3987
- |--------|------|---------|-------------|
3988
- | `ignoreAttributes` | `string[]` | See below | JSX attributes to ignore (replaces defaults) |
3989
- | `extraIgnoreAttributes` | `string[]` | `[]` | Additional JSX attributes to ignore (extends defaults) |
3990
- | `ignorePatterns` | `string[]` | `[]` | Regex patterns for strings to ignore |
3991
-
3992
- **Default ignored attributes:** `className`, `id`, `type`, `name`, `href`, `src`, `alt`, `role`, `style`, `key`, `data-*`, `aria-*`, and many more HTML/SVG attributes.
3993
-
3994
- **Default ignored patterns:** Empty strings, single characters, CSS units (`px`, `em`, `%`), colors, URLs, paths, file extensions, MIME types, UUIDs, dates, camelCase/snake_case identifiers, HTTP methods, and other technical strings.
3995
-
3996
- ```javascript
3997
- // ✅ Good — strings imported from constants
3998
- import { BUTTON_LABEL, ERROR_MESSAGE, welcomeText } from "@/constants";
3999
- import { FORM_LABELS } from "@/strings";
4000
- import { HttpStatus, UserRole } from "@/enums";
4001
-
4002
- const ComponentHandler = () => (
4003
- <div>
4004
- <button>{BUTTON_LABEL}</button>
4005
- <span>{ERROR_MESSAGE}</span>
4006
- <p>{welcomeText}</p>
4007
- </div>
4008
- );
4009
-
4010
- const getMessageHandler = () => ERROR_MESSAGE;
4011
-
4012
- // ✅ Good — using enums for status codes and roles
4013
- if (status === HttpStatus.NOT_FOUND) { ... }
4014
- if (role === UserRole.ADMIN) { ... }
4015
-
4016
- // ✅ Good — technical strings are allowed
4017
- <input type="text" className="input-field" />
4018
- <a href="/dashboard">Link</a>
4019
- const url = `/api/users/${id}`;
4020
- const size = "100px";
4021
-
4022
- // ❌ Bad — hardcoded user-facing strings
4023
- <button>Submit Form</button>
4024
- <span>Something went wrong</span>
4025
- const message = "Welcome to the application";
4026
- return "User not found";
4027
-
4028
- // ❌ Bad — hardcoded status codes and roles
4029
- if (status === "404") { ... }
4030
- if (role === "admin") { ... }
4031
- ```
4032
-
4033
- **Configuration example:**
4034
-
4035
- ```javascript
4036
- // Allow more attributes, add custom ignore patterns
4037
- "code-style/no-hardcoded-strings": ["error", {
4038
- extraIgnoreAttributes: ["tooltip", "placeholder"],
4039
- ignorePatterns: ["^TODO:", "^FIXME:"]
4040
- }]
4041
- ```
4042
-
4043
- **Valid import paths for strings:**
4044
- - `@/data`
4045
- - `@/strings` or `@/@strings`
4046
- - `@/constants` or `@/@constants`
4047
-
4048
- **Valid import paths for enums (status codes, roles):**
4049
- - `@/enums`
4050
- - `@/data`
4051
-
4052
- ---
4053
-
4054
- <br />
4055
-
4056
- ## 📝 Variable Rules
4057
-
4058
- ### `variable-naming-convention`
4059
-
4060
- **What it does:** Enforces naming conventions for variables:
4061
- - **camelCase** for all variables and constants
4062
- - **PascalCase** for React components and classes
4063
- - **camelCase with `use` prefix** for hooks
4064
-
4065
- **Why use it:** Consistent naming makes code predictable. You can tell what something is by how it's named.
4066
-
4067
- ```javascript
4068
- // ✅ Good — correct conventions
4069
- const userName = "John"; // camelCase for variables
4070
- const itemCount = 42; // camelCase for variables
4071
- const maxRetries = 3; // camelCase for constants
4072
- const apiBaseUrl = "/api"; // camelCase for constants
4073
- const UserProfile = () => <div />; // PascalCase for components
4074
- const useAuth = () => {}; // camelCase with use prefix for hooks
4075
-
4076
- // ❌ Bad — wrong conventions
4077
- const user_name = "John"; // snake_case
4078
- const MAX_RETRIES = 3; // should be camelCase
4079
- const userProfile = () => <div />; // should be PascalCase
4080
- const UseAuth = () => {}; // hooks should be camelCase
4081
- ```
383
+ | Category | Rules | Highlights |
384
+ |----------|:-----:|------------|
385
+ | [Arrays](./docs/rules/arrays.md) | 3 | Callback destructuring, items-per-line, objects-on-new-lines |
386
+ | [Arrow Functions](./docs/rules/arrow-functions.md) | 4 | Block body, simple JSX, implicit return, curried arrows |
387
+ | [Call Expressions](./docs/rules/call-expressions.md) | 6 | Argument formatting, nested brackets, single-line calls |
388
+ | [Classes](./docs/rules/classes.md) | 2 | Method formatting, naming conventions |
389
+ | [Comments](./docs/rules/comments.md) | 1 | Comment spacing and format |
390
+ | [Components](./docs/rules/components.md) | 6 | Props destructure, folder naming, structure consistency |
391
+ | [Control Flow](./docs/rules/control-flow.md) | 8 | Block newlines, if/else, logical expressions, ternaries |
392
+ | [Functions](./docs/rules/functions.md) | 6 | Call spacing, declaration style, naming, params |
393
+ | [Hooks](./docs/rules/hooks.md) | 3 | Callback format, deps-per-line, useState naming |
394
+ | [Imports/Exports](./docs/rules/imports-exports.md) | 8 | Absolute imports, format, index exports, module exports |
395
+ | [JSX](./docs/rules/jsx.md) | 14 | ClassName handling, children, logical expressions |
396
+ | [Objects](./docs/rules/objects.md) | 5 | Property formatting, empty lines, string properties |
397
+ | [React](./docs/rules/react.md) | 1 | Component/hook code ordering |
398
+ | [Spacing](./docs/rules/spacing.md) | 2 | Assignment values, bracket spacing |
399
+ | [Strings](./docs/rules/strings.md) | 1 | No hardcoded strings |
400
+ | [TypeScript](./docs/rules/typescript.md) | 8 | Enum/interface/type formatting, definition location |
401
+ | [Variables](./docs/rules/variables.md) | 1 | Naming conventions (camelCase, PascalCase) |
4082
402
 
4083
403
  <br />
4084
404
 
@@ -4129,9 +449,11 @@ Contributions are welcome! Please feel free to submit a Pull Request.
4129
449
 
4130
450
  1. Fork the repository
4131
451
  2. Create your feature branch (`git checkout -b feature/amazing-feature`)
4132
- 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4133
- 4. Push to the branch (`git push origin feature/amazing-feature`)
4134
- 5. Open a Pull Request
452
+ 3. Build the project (`npm run build`)
453
+ 4. Test your changes in all 4 test projects (`cd _tests_/react-ts-tw && npm run lint`)
454
+ 5. Commit your changes (`git commit -m 'Add some amazing feature'`)
455
+ 6. Push to the branch (`git push origin feature/amazing-feature`)
456
+ 7. Open a Pull Request
4135
457
 
4136
458
  <br />
4137
459