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 +29 -3707
- package/dist/index.js +272 -0
- package/package.json +11 -8
- package/AGENTS.md +0 -1358
- package/CHANGELOG.md +0 -1978
- package/CLAUDE.md +0 -12
- package/index.js +0 -23217
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-
|
|
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 |
|
|
75
|
-
| **React + Tailwind** | React + Tailwind CSS projects |
|
|
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](
|
|
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
|
-
|
|
381
|
+
For detailed documentation with examples, configuration options, and best practices for each rule, see the **[Rules Reference Documentation](./docs/rules/)**.
|
|
382
382
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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.
|
|
4133
|
-
4.
|
|
4134
|
-
5.
|
|
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
|
|