eslint-plugin-code-style 1.14.3 β†’ 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,125 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.17.0] - 2026-02-09
11
+
12
+ **New Rule + Enhancements to Naming & Import Rules**
13
+
14
+ **Version Range:** v1.16.0 β†’ v1.17.0
15
+
16
+ ### Added
17
+
18
+ **New Rules (1)**
19
+ - `inline-export-declaration` - Enforce inline export declarations (`export const x = ...`) instead of grouped export statements (`export { x }`) in non-index files πŸ”§
20
+ - Auto-fixable: adds `export` to each declaration and removes the grouped export statement
21
+ - Skips index files (barrel re-exports) and aliased exports (`export { a as b }`)
22
+
23
+ ### Enhanced
24
+
25
+ - **`folder-based-naming-convention`** - Extended camelCase suffix enforcement for data/constants/strings/services/reducers folders
26
+ - Exports in `data/` must end with `Data` (e.g., `buttonTypeData`)
27
+ - Exports in `constants/` must end with `Constants` (e.g., `localeConstants`)
28
+ - Exports in `strings/` must end with `Strings` (e.g., `appStrings`)
29
+ - Exports in `services/` must end with `Service`, `reducers/` with `Reducer`
30
+ - **`absolute-imports-only`** - Files within the same module folder must use relative imports instead of absolute to avoid circular dependencies πŸ”§
31
+ - Detects files at any depth inside module folders (e.g., `data/auth/login/guest.tsx`)
32
+ - Allows both `./` and `../` relative imports within the same module folder
33
+ - Auto-fixes absolute imports to own module folder (e.g., `@/data/auth/login/guest` β†’ `../../login/guest`)
34
+ - Now marked as auto-fixable (`fixable: "code"`)
35
+ - **`folder-structure-consistency`** - Added loose module file detection: standalone files matching module folder names (e.g., `data.js`, `strings.js`) are flagged β€” must use folder structure instead
36
+
37
+ ### Stats
38
+
39
+ - Total Rules: 79 (was 78)
40
+ - Auto-fixable: 70 rules (was 69) πŸ”§
41
+ - Configurable: 19 rules (was 18)
42
+ - Report-only: 9 rules (was 10)
43
+
44
+ **Full Changelog:** [v1.16.0...v1.17.0](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.16.0...v1.17.0)
45
+
46
+ ---
47
+
48
+ ## [1.16.0] - 2026-02-09
49
+
50
+ **New Rule + Enhancements + Rule Renames**
51
+
52
+ **Version Range:** v1.15.0 β†’ v1.16.0
53
+
54
+ ### Added
55
+
56
+ **New Rules (1)**
57
+ - `folder-structure-consistency` - Enforce consistent folder structure (flat vs wrapped) in module folders
58
+ - Applies to all module folders (same list as `module-index-exports`)
59
+ - Detects mixed structures (some flat files, some in subfolders)
60
+ - Flags unnecessary wrapper folders when each has only one file
61
+ - Configurable `moduleFolders` and `extraModuleFolders` options
62
+
63
+ ### Enhanced
64
+
65
+ - **`folder-based-naming-convention`** (renamed from `folder-component-suffix`)
66
+ - Support nested files with chained folder names (e.g., `layouts/auth/login.tsx` β†’ `LoginAuthLayout`)
67
+ - Match files at any depth within module folders
68
+ - Expanded to cover: views, layouts, pages, providers, reducers, services, contexts, themes (with suffix), atoms and components (chaining only, no suffix)
69
+ - Added `VariableDeclarator` detection for non-JSX folders (contexts, themes)
70
+ - **`no-redundant-folder-suffix`** - Also check folder names for redundant suffixes (e.g., `views/access-control-view/` is now flagged)
71
+
72
+ ### Renamed
73
+
74
+ - `folder-component-suffix` β†’ `folder-based-naming-convention` (now handles more than just components)
75
+ - `svg-component-icon-naming` β†’ `svg-icon-naming-convention` (consistent with other naming convention rules)
76
+
77
+ ### Stats
78
+
79
+ - Total Rules: 78 (was 77)
80
+ - Auto-fixable: 67 rules
81
+ - Configurable: 18 rules (was 17)
82
+ - Report-only: 11 rules (was 10)
83
+
84
+ **Full Changelog:** [v1.15.0...v1.16.0](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.15.0...v1.16.0)
85
+
86
+ ---
87
+
88
+ ## [1.15.0] - 2026-02-06
89
+
90
+ **New Rule: no-redundant-folder-suffix**
91
+
92
+ **Version Range:** v1.14.1 β†’ v1.15.0
93
+
94
+ ### Added
95
+
96
+ **New Rules (1)**
97
+ - `no-redundant-folder-suffix` - Disallow file names that redundantly include the parent or ancestor folder name as a suffix
98
+ - Flags files like `layouts/main-layout.tsx` (redundant "-layout" since already in `layouts/`)
99
+ - Checks all ancestor folders from `src/` onwards
100
+ - Singularizes folder names automatically (layouts→layout, categories→category, classes→class)
101
+ - Skips index files
102
+
103
+ ### Enhanced
104
+
105
+ - **`folder-component-suffix`** - Add `layouts` folder support: components in `layouts/` must end with "Layout" suffix (with auto-fix) (v1.14.4)
106
+ - **`type-annotation-spacing`** - Add auto-fix to collapse function types with ≀2 params to one line; add spacing rules for async keyword and function types (v1.14.2–v1.14.3)
107
+ - **`interface-format`** - Fix circular fix conflict by skipping collapse when property has multi-line function type (v1.14.3)
108
+ - **`function-naming-convention`** - Detect functions destructured from hooks without proper naming, with auto-fix (v1.14.1)
109
+
110
+ ### Stats
111
+
112
+ - Total Rules: 77 (was 76)
113
+ - Auto-fixable: 67 rules πŸ”§
114
+ - Configurable: 17 rules βš™οΈ
115
+ - Report-only: 10 rules (was 9)
116
+
117
+ **Full Changelog:** [v1.14.1...v1.15.0](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.14.1...v1.15.0)
118
+
119
+ ---
120
+
121
+ ## [1.14.4] - 2026-02-06
122
+
123
+ ### Enhanced
124
+
125
+ - **`folder-component-suffix`** - Add `layouts` folder support: components in `layouts/` must end with "Layout" suffix (with auto-fix)
126
+
127
+ ---
128
+
10
129
  ## [1.14.3] - 2026-02-05
11
130
 
12
131
  ### Enhanced
@@ -1687,6 +1806,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1687
1806
 
1688
1807
  ---
1689
1808
 
1809
+ [1.17.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.16.0...v1.17.0
1810
+ [1.16.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.15.0...v1.16.0
1811
+ [1.15.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.14.4...v1.15.0
1812
+ [1.14.4]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.14.3...v1.14.4
1690
1813
  [1.14.3]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.14.2...v1.14.3
1691
1814
  [1.14.2]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.14.1...v1.14.2
1692
1815
  [1.14.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.14.0...v1.14.1
package/CLAUDE.md ADDED
@@ -0,0 +1,12 @@
1
+ # Claude Code Configuration
2
+
3
+ > For general project instructions, see [AGENTS.md](./AGENTS.md).
4
+
5
+ ## Claude-Specific Behavior
6
+
7
+ When working on this codebase, Claude Code should:
8
+
9
+ - Do NOT include `Co-Authored-By` lines in commits
10
+ - Do NOT include Claude Code signature/footer in commits
11
+ - Keep commit messages clean and standard (no AI attribution)
12
+ - When user asks to "commit" with approval, follow the full release workflow in [AGENTS.md](./AGENTS.md#release-steps)
package/README.md CHANGED
@@ -19,7 +19,7 @@
19
19
 
20
20
  **A powerful ESLint plugin for enforcing consistent code formatting and style rules in React/JSX projects.**
21
21
 
22
- *76 rules (67 auto-fixable, 17 configurable) to keep your codebase clean and consistent*
22
+ *79 rules (70 auto-fixable, 19 configurable) to keep your codebase clean and consistent*
23
23
 
24
24
  </div>
25
25
 
@@ -27,7 +27,7 @@
27
27
 
28
28
  ## 🎯 Why This Plugin?
29
29
 
30
- This plugin provides **75 custom rules** (66 auto-fixable, 17 configurable) for code formatting. Built for **ESLint v9 flat configs**.
30
+ This plugin provides **79 custom rules** (70 auto-fixable, 19 configurable) for code formatting. Built for **ESLint v9 flat configs**.
31
31
 
32
32
  > **Note:** ESLint [deprecated 79 formatting rules](https://eslint.org/blog/2023/10/deprecating-formatting-rules/) in v8.53.0. Our recommended configs use `@stylistic/eslint-plugin` as the replacement for these deprecated rules.
33
33
 
@@ -36,7 +36,7 @@ This plugin provides **75 custom rules** (66 auto-fixable, 17 configurable) for
36
36
  - **Works alongside existing tools** β€” Complements ESLint's built-in rules and packages like eslint-plugin-react, eslint-plugin-import, etc
37
37
  - **Self-sufficient rules** β€” Each rule handles complete formatting independently
38
38
  - **Consistency at scale** β€” Reduces code-style differences between team members by enforcing uniform formatting across your projects
39
- - **Highly automated** β€” 67 of 76 rules support auto-fix with `eslint --fix`
39
+ - **Highly automated** β€” 70 of 79 rules support auto-fix with `eslint --fix`
40
40
 
41
41
  When combined with ESLint's native rules and other popular plugins, this package helps create a complete code style solution that keeps your codebase clean and consistent.
42
42
 
@@ -60,7 +60,7 @@ We provide **ready-to-use ESLint flat configuration files** that combine `eslint
60
60
 
61
61
  ### πŸ’‘ Why Use These Configs?
62
62
 
63
- - **Complete Coverage** β€” Combines ESLint built-in rules, third-party plugins, and all 69 code-style rules
63
+ - **Complete Coverage** β€” Combines ESLint built-in rules, third-party plugins, and all 79 code-style rules
64
64
  - **Ready-to-Use** β€” Copy the config file and start linting immediately
65
65
  - **Battle-Tested** β€” These configurations have been refined through real-world usage
66
66
  - **Fully Documented** β€” Each config includes detailed instructions and explanations
@@ -97,7 +97,7 @@ We provide **ready-to-use ESLint flat configuration files** that combine `eslint
97
97
  <td width="50%">
98
98
 
99
99
  ### πŸ”§ Auto-Fixable Rules
100
- **67 rules** support automatic fixing with `eslint --fix`. **17 rules** have configurable options. 9 rules are report-only (require manual changes).
100
+ **70 rules** support automatic fixing with `eslint --fix`. **19 rules** have configurable options. 9 rules are report-only (require manual changes).
101
101
 
102
102
  </td>
103
103
  <td width="50%">
@@ -199,13 +199,15 @@ rules: {
199
199
  "code-style/comment-format": "error",
200
200
  "code-style/component-props-destructure": "error",
201
201
  "code-style/component-props-inline-type": "error",
202
- "code-style/svg-component-icon-naming": "error",
202
+ "code-style/svg-icon-naming-convention": "error",
203
203
  "code-style/curried-arrow-same-line": "error",
204
204
  "code-style/empty-line-after-block": "error",
205
205
  "code-style/enum-format": "error",
206
206
  "code-style/enum-type-enforcement": "error",
207
207
  "code-style/export-format": "error",
208
- "code-style/folder-component-suffix": "error",
208
+ "code-style/folder-based-naming-convention": "error",
209
+ "code-style/folder-structure-consistency": "error",
210
+ "code-style/no-redundant-folder-suffix": "error",
209
211
  "code-style/function-arguments-format": "error",
210
212
  "code-style/function-call-spacing": "error",
211
213
  "code-style/function-declaration-style": "error",
@@ -221,6 +223,7 @@ rules: {
221
223
  "code-style/import-source-spacing": "error",
222
224
  "code-style/index-export-style": "error",
223
225
  "code-style/index-exports-only": "error",
226
+ "code-style/inline-export-declaration": "error",
224
227
  "code-style/interface-format": "error",
225
228
  "code-style/jsx-children-on-new-line": "error",
226
229
  "code-style/jsx-closing-bracket-spacing": "error",
@@ -266,7 +269,7 @@ rules: {
266
269
 
267
270
  ## πŸ“– Rules Categories
268
271
 
269
- > **76 rules total** β€” 67 with auto-fix πŸ”§, 17 configurable βš™οΈ, 9 report-only. See detailed examples in [Rules Reference](#-rules-reference) below.
272
+ > **79 rules total** β€” 70 with auto-fix πŸ”§, 19 configurable βš™οΈ, 9 report-only. See detailed examples in [Rules Reference](#-rules-reference) below.
270
273
  >
271
274
  > **Legend:** πŸ”§ Auto-fixable with `eslint --fix` β€’ βš™οΈ Customizable options
272
275
 
@@ -293,8 +296,10 @@ rules: {
293
296
  | **Component Rules** | |
294
297
  | `component-props-destructure` | Component props must be destructured `({ prop })` not received as `(props)` πŸ”§ |
295
298
  | `component-props-inline-type` | Inline type annotation `} : {` with matching props, proper spacing, commas, no interface reference πŸ”§ |
296
- | `folder-component-suffix` | Components in `views/` folder must end with "View", components in `pages/` folder must end with "Page" |
297
- | `svg-component-icon-naming` | SVG components must end with "Icon" suffix; "Icon" suffix components must return SVG |
299
+ | `folder-based-naming-convention` | Enforce naming based on folder: suffix for views/layouts/pages/providers/reducers/contexts/themes, camelCase suffix for data/constants/strings/services/reducers folders, chained folder names for nested files πŸ”§ |
300
+ | `folder-structure-consistency` | Enforce consistent folder structure (flat vs wrapped) in module folders (atoms, components, hooks, enums, views, etc.) βš™οΈ |
301
+ | `no-redundant-folder-suffix` | Disallow file and folder names that redundantly include the parent folder name as a suffix |
302
+ | `svg-icon-naming-convention` | SVG components must end with "Icon" suffix; "Icon" suffix components must return SVG |
298
303
  | **Class Rules** | |
299
304
  | `class-method-definition-format` | Consistent spacing in class/method definitions: space before `{`, no space before `(` πŸ”§ |
300
305
  | `class-naming-convention` | Class declarations must end with "Class" suffix (e.g., `ApiServiceClass`) πŸ”§ |
@@ -319,12 +324,13 @@ rules: {
319
324
  | `hook-deps-per-line` | Collapse deps ≀ threshold to one line; expand larger arrays with each dep on own line (default: >2) πŸ”§ βš™οΈ |
320
325
  | `use-state-naming-convention` | Boolean useState variables must start with is/has/with/without prefix πŸ”§ βš™οΈ |
321
326
  | **Import/Export Rules** | |
322
- | `absolute-imports-only` | Use alias imports from index files only (not deep paths), no relative imports (default: `@/`) βš™οΈ |
327
+ | `absolute-imports-only` | Use alias imports from index files only (not deep paths), no relative imports; files within the same module folder must use relative imports β€” auto-fixes absolute imports to relative (default: `@/`) πŸ”§ βš™οΈ |
323
328
  | `export-format` | `export {` on same line; collapse ≀ threshold to one line; expand larger with each specifier on own line (default: ≀3) πŸ”§ βš™οΈ |
324
329
  | `import-format` | `import {` and `} from` on same line; collapse ≀ threshold; expand larger with each specifier on own line (default: ≀3) πŸ”§ βš™οΈ |
325
330
  | `import-source-spacing` | No leading/trailing spaces inside import path quotes πŸ”§ |
326
331
  | `index-export-style` | Index files: no blank lines, enforce shorthand or import-export style; Regular files: require blank lines between exports (default: shorthand) πŸ”§ βš™οΈ |
327
332
  | `index-exports-only` | Index files should only contain imports and re-exports, not code definitions (types, functions, variables, classes) |
333
+ | `inline-export-declaration` | Enforce inline export declarations instead of grouped export statements in non-index files πŸ”§ βš™οΈ |
328
334
  | `module-index-exports` | Index files must export all folder contents (files and subfolders) βš™οΈ |
329
335
  | **JSX Rules** | |
330
336
  | `classname-dynamic-at-end` | Dynamic expressions (`${className}`) must be at the end of class strings (JSX and variables) πŸ”§ |
@@ -1810,13 +1816,14 @@ const [error, setError] = useState<boolean>(false);
1810
1816
 
1811
1817
  ### `absolute-imports-only`
1812
1818
 
1813
- **What it does:** Enforces importing from folder index files using absolute paths (aliases like `@/`) instead of relative paths or deep file imports.
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. πŸ”§
1814
1820
 
1815
1821
  **Why use it:**
1816
1822
  - Absolute imports are cleaner than `../../../components`
1817
1823
  - Index imports create a public API for each folder
1818
1824
  - Refactoring file locations doesn't break imports
1819
1825
  - Encourages proper module organization
1826
+ - Relative imports within the same module folder avoid circular dependencies
1820
1827
 
1821
1828
  ```javascript
1822
1829
  // βœ… Good β€” import from index files using alias
@@ -1828,7 +1835,20 @@ import { formatDate } from "@/utils";
1828
1835
  // βœ… Good β€” assets allow deep imports by default
1829
1836
  import logo from "@/assets/images/logo.png";
1830
1837
 
1831
- // ❌ Bad β€” relative imports
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
1832
1852
  import { Button } from "../../components";
1833
1853
  import { useAuth } from "../../../hooks";
1834
1854
 
@@ -2082,6 +2102,46 @@ export function helper() { ... } // Move to utils.ts
2082
2102
 
2083
2103
  ---
2084
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
+
2085
2145
  ### `module-index-exports`
2086
2146
 
2087
2147
  **What it does:** Ensures module folders have index files that export all their contents, creating a proper public API for each module.
@@ -3059,31 +3119,163 @@ export const Card = ({ a, b } : { a: string, b: string }) => (
3059
3119
 
3060
3120
  ---
3061
3121
 
3062
- ### `folder-component-suffix`
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`):
3063
3125
 
3064
- **What it does:** Enforces naming conventions for components based on folder location:
3065
- - Components in `views/` folder must end with "View" suffix
3066
- - Components in `pages/` folder must end with "Page" suffix
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` |
3067
3141
 
3068
- **Why use it:** Consistent naming based on folder structure makes component purpose immediately clear. View components and page components have different responsibilities, and the suffix reflects this.
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.
3069
3145
 
3070
3146
  ```tsx
3071
- // βœ… Good β€” in views/dashboard-view.tsx
3147
+ // βœ… Good β€” suffix folders
3148
+ // in views/dashboard.tsx
3072
3149
  export const DashboardView = () => <div>Dashboard</div>;
3073
3150
 
3074
- // βœ… Good β€” in pages/home-page.tsx
3075
- export const HomePage = () => <div>Home</div>;
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" };
3076
3165
 
3077
- // ❌ Bad β€” in views/dashboard.tsx (missing "View" suffix)
3078
- export const Dashboard = () => <div>Dashboard</div>;
3166
+ // βœ… Good β€” camelCase suffix folders
3167
+ // in data/auth.ts
3168
+ export const authData = { ... };
3079
3169
 
3080
- // ❌ Bad β€” in pages/home.tsx (missing "Page" suffix)
3081
- export const Home = () => <div>Home</div>;
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" />;
3082
3198
  ```
3083
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
+
3084
3202
  ---
3085
3203
 
3086
- ### `svg-component-icon-naming`
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`
3087
3279
 
3088
3280
  **What it does:** Enforces naming conventions for SVG icon components:
3089
3281
  - Components that return only an SVG element must have a name ending with "Icon"
@@ -3894,7 +4086,7 @@ const UseAuth = () => {}; // hooks should be camelCase
3894
4086
 
3895
4087
  ## πŸ”§ Auto-fixing
3896
4088
 
3897
- 67 of 76 rules support auto-fixing. Run ESLint with the `--fix` flag:
4089
+ 70 of 79 rules support auto-fixing. Run ESLint with the `--fix` flag:
3898
4090
 
3899
4091
  ```bash
3900
4092
  # Fix all files in src directory
@@ -3947,6 +4139,8 @@ Contributions are welcome! Please feel free to submit a Pull Request.
3947
4139
 
3948
4140
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
3949
4141
 
4142
+ Copyright (c) 2026 Eslint Plugin Code Style. All rights reserved.
4143
+
3950
4144
  <br />
3951
4145
 
3952
4146
  ---
package/index.d.ts CHANGED
@@ -19,12 +19,13 @@ export type RuleNames =
19
19
  | "code-style/component-props-destructure"
20
20
  | "code-style/react-code-order"
21
21
  | "code-style/component-props-inline-type"
22
- | "code-style/svg-component-icon-naming"
22
+ | "code-style/svg-icon-naming-convention"
23
23
  | "code-style/curried-arrow-same-line"
24
24
  | "code-style/empty-line-after-block"
25
25
  | "code-style/enum-type-enforcement"
26
26
  | "code-style/export-format"
27
- | "code-style/folder-component-suffix"
27
+ | "code-style/folder-based-naming-convention"
28
+ | "code-style/folder-structure-consistency"
28
29
  | "code-style/function-arguments-format"
29
30
  | "code-style/function-call-spacing"
30
31
  | "code-style/function-declaration-style"
@@ -40,6 +41,7 @@ export type RuleNames =
40
41
  | "code-style/import-source-spacing"
41
42
  | "code-style/index-export-style"
42
43
  | "code-style/index-exports-only"
44
+ | "code-style/inline-export-declaration"
43
45
  | "code-style/classname-dynamic-at-end"
44
46
  | "code-style/classname-multiline"
45
47
  | "code-style/classname-no-extra-spaces"
@@ -59,6 +61,7 @@ export type RuleNames =
59
61
  | "code-style/multiline-if-conditions"
60
62
  | "code-style/nested-call-closing-brackets"
61
63
  | "code-style/no-inline-type-definitions"
64
+ | "code-style/no-redundant-folder-suffix"
62
65
  | "code-style/no-empty-lines-in-function-calls"
63
66
  | "code-style/no-empty-lines-in-function-params"
64
67
  | "code-style/no-empty-lines-in-jsx"
@@ -117,12 +120,13 @@ interface PluginRules {
117
120
  "component-props-destructure": Rule.RuleModule;
118
121
  "react-code-order": Rule.RuleModule;
119
122
  "component-props-inline-type": Rule.RuleModule;
120
- "svg-component-icon-naming": Rule.RuleModule;
123
+ "svg-icon-naming-convention": Rule.RuleModule;
121
124
  "curried-arrow-same-line": Rule.RuleModule;
122
125
  "empty-line-after-block": Rule.RuleModule;
123
126
  "enum-type-enforcement": Rule.RuleModule;
124
127
  "export-format": Rule.RuleModule;
125
- "folder-component-suffix": Rule.RuleModule;
128
+ "folder-based-naming-convention": Rule.RuleModule;
129
+ "folder-structure-consistency": Rule.RuleModule;
126
130
  "function-arguments-format": Rule.RuleModule;
127
131
  "function-call-spacing": Rule.RuleModule;
128
132
  "function-declaration-style": Rule.RuleModule;
@@ -137,6 +141,7 @@ interface PluginRules {
137
141
  "import-source-spacing": Rule.RuleModule;
138
142
  "index-export-style": Rule.RuleModule;
139
143
  "index-exports-only": Rule.RuleModule;
144
+ "inline-export-declaration": Rule.RuleModule;
140
145
  "classname-dynamic-at-end": Rule.RuleModule;
141
146
  "classname-multiline": Rule.RuleModule;
142
147
  "classname-no-extra-spaces": Rule.RuleModule;
@@ -156,6 +161,7 @@ interface PluginRules {
156
161
  "multiline-if-conditions": Rule.RuleModule;
157
162
  "nested-call-closing-brackets": Rule.RuleModule;
158
163
  "no-inline-type-definitions": Rule.RuleModule;
164
+ "no-redundant-folder-suffix": Rule.RuleModule;
159
165
  "no-empty-lines-in-function-calls": Rule.RuleModule;
160
166
  "no-empty-lines-in-function-params": Rule.RuleModule;
161
167
  "no-empty-lines-in-jsx": Rule.RuleModule;