eslint-plugin-code-style 1.17.0 → 1.17.2

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/index.js +97 -29
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.17.2] - 2026-02-09
11
+
12
+ **Fix: CamelCase Naming Auto-Fix & Prefix Enforcement**
13
+
14
+ **Version Range:** v1.17.1 → v1.17.2
15
+
16
+ ### Fixed
17
+
18
+ - **`folder-based-naming-convention`** - Fix camelCase naming enforcement for constants, data, reducers, services, and strings folders
19
+ - Auto-fix missing suffix: `common` → `commonConstants` on save (all camelCase folders)
20
+ - Near-match prefix enforcement: `routeConstants` → `routesConstants` when file is `routes.ts`
21
+ - Multi-export files with unrelated prefixes (e.g., `buttonTypeData` in `data/app.ts`) are not flagged
22
+
23
+ **Full Changelog:** [v1.17.1...v1.17.2](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.17.1...v1.17.2)
24
+
25
+ ---
26
+
27
+ ## [1.17.1] - 2026-02-09
28
+
29
+ **Fix: Index File Behavior in Wrapped Folders**
30
+
31
+ **Version Range:** v1.17.0 → v1.17.1
32
+
33
+ ### Fixed
34
+
35
+ - **`index-exports-only`** - Enforce dual index file behavior for wrapped folder structure
36
+ - Root module index (`views/index.ts`) → barrel only (re-exports)
37
+ - Subfolder index (`views/assessment/index.tsx`) → must contain component code, not just re-exports
38
+ - Only one barrel per module — subfolder index files that simulate a barrel are flagged
39
+ - **`no-redundant-folder-suffix`** - Detect file name matching parent folder name (e.g., `assessment/assessment.tsx` → use `assessment/index.tsx`)
40
+
41
+ **Full Changelog:** [v1.17.0...v1.17.1](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.17.0...v1.17.1)
42
+
43
+ ---
44
+
10
45
  ## [1.17.0] - 2026-02-09
11
46
 
12
47
  **New Rule + Enhancements to Naming & Import Rules**
@@ -1806,6 +1841,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1806
1841
 
1807
1842
  ---
1808
1843
 
1844
+ [1.17.2]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.17.1...v1.17.2
1845
+ [1.17.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.17.0...v1.17.1
1809
1846
  [1.17.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.16.0...v1.17.0
1810
1847
  [1.16.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.15.0...v1.16.0
1811
1848
  [1.15.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.14.4...v1.15.0
package/index.js CHANGED
@@ -8022,23 +8022,37 @@ const indexExportsOnly = {
8022
8022
 
8023
8023
  if (!isIndexFile) return {};
8024
8024
 
8025
- // Helper to check if a node is an import or export statement
8026
- const isImportOrExportHandler = (node) => {
8025
+ // Determine if this is a subfolder index inside a module folder
8026
+ // e.g., views/assessment/index.tsx (depth >= 2 from module folder) = subfolder index
8027
+ // vs views/index.ts (depth == 1 from module folder) = root barrel
8028
+ const moduleFolders = [
8029
+ "actions", "apis", "assets", "atoms", "components", "config", "configs",
8030
+ "constants", "contexts", "data", "enums", "helpers", "hooks", "interfaces",
8031
+ "layouts", "lib", "middlewares", "pages", "providers", "reducers", "redux",
8032
+ "requests", "routes", "schemas", "services", "store", "strings", "styles",
8033
+ "theme", "themes", "thunks", "types", "ui", "utils", "utilities", "views",
8034
+ ];
8035
+ const parts = normalizedFilename.split("/");
8036
+ const indexPos = parts.length - 1;
8037
+ let isSubfolderIndex = false;
8038
+
8039
+ for (let i = 0; i < indexPos; i++) {
8040
+ if (moduleFolders.includes(parts[i])) {
8041
+ if (indexPos - i >= 2) isSubfolderIndex = true;
8042
+
8043
+ break;
8044
+ }
8045
+ }
8046
+
8047
+ // Helper to check if a node is an import or re-export (no inline declarations)
8048
+ const isImportOrReexportHandler = (node) => {
8027
8049
  const { type } = node;
8028
8050
 
8029
- // Import statements
8030
8051
  if (type === "ImportDeclaration") return true;
8031
8052
 
8032
- // Export statements (named, default, all)
8033
- if (type === "ExportNamedDeclaration") {
8034
- // Only allow re-exports (export { x } from "./module" or export { x })
8035
- // If it has a declaration, it's defining something (not allowed)
8036
- return !node.declaration;
8037
- }
8053
+ if (type === "ExportNamedDeclaration") return !node.declaration;
8038
8054
 
8039
8055
  if (type === "ExportDefaultDeclaration") {
8040
- // Allow export default <identifier> (re-exporting)
8041
- // Disallow export default function/class/object (defining something)
8042
8056
  return node.declaration && node.declaration.type === "Identifier";
8043
8057
  }
8044
8058
 
@@ -8047,6 +8061,21 @@ const indexExportsOnly = {
8047
8061
  return false;
8048
8062
  };
8049
8063
 
8064
+ // Helper to check if a node contains actual code (declarations, logic)
8065
+ const hasCodeDeclarationHandler = (node) => {
8066
+ const { type } = node;
8067
+
8068
+ if (type === "VariableDeclaration" || type === "FunctionDeclaration" || type === "ClassDeclaration") return true;
8069
+
8070
+ if (type === "ExportNamedDeclaration" && node.declaration) return true;
8071
+
8072
+ if (type === "ExportDefaultDeclaration") {
8073
+ return node.declaration && node.declaration.type !== "Identifier";
8074
+ }
8075
+
8076
+ return false;
8077
+ };
8078
+
8050
8079
  // Get a friendly description of what the disallowed node is
8051
8080
  const getNodeDescriptionHandler = (node) => {
8052
8081
  switch (node.type) {
@@ -8063,9 +8092,7 @@ const indexExportsOnly = {
8063
8092
  case "ClassDeclaration":
8064
8093
  return "Class declaration";
8065
8094
  case "ExportNamedDeclaration":
8066
- if (node.declaration) {
8067
- return getNodeDescriptionHandler(node.declaration);
8068
- }
8095
+ if (node.declaration) return getNodeDescriptionHandler(node.declaration);
8069
8096
 
8070
8097
  return "Export with inline declaration";
8071
8098
  case "ExportDefaultDeclaration":
@@ -8077,8 +8104,28 @@ const indexExportsOnly = {
8077
8104
 
8078
8105
  return {
8079
8106
  Program(programNode) {
8107
+ if (isSubfolderIndex) {
8108
+ // Subfolder index (e.g., views/assessment/index.tsx):
8109
+ // Must contain component code — must NOT be a barrel (re-exports only)
8110
+ // Only one barrel per module (the root index)
8111
+ const hasCode = programNode.body.some((node) => hasCodeDeclarationHandler(node));
8112
+
8113
+ if (!hasCode) {
8114
+ const subfolder = parts[indexPos - 1];
8115
+
8116
+ context.report({
8117
+ message: `Subfolder index file "${subfolder}/index" should contain component code, not just re-exports. Only the module root index file should be a barrel for imports and re-exports.`,
8118
+ node: programNode,
8119
+ });
8120
+ }
8121
+
8122
+ return;
8123
+ }
8124
+
8125
+ // Root module index (e.g., views/index.ts):
8126
+ // Must be barrel only — no code declarations allowed
8080
8127
  for (const node of programNode.body) {
8081
- if (!isImportOrExportHandler(node)) {
8128
+ if (!isImportOrReexportHandler(node)) {
8082
8129
  const description = getNodeDescriptionHandler(node);
8083
8130
 
8084
8131
  context.report({
@@ -8091,7 +8138,7 @@ const indexExportsOnly = {
8091
8138
  };
8092
8139
  },
8093
8140
  meta: {
8094
- docs: { description: "Index files should only contain imports and re-exports, not code definitions" },
8141
+ docs: { description: "Enforce index files as barrels (re-exports only) at module root, and as component entry points (with code) in subfolders" },
8095
8142
  schema: [],
8096
8143
  type: "suggestion",
8097
8144
  },
@@ -19281,17 +19328,38 @@ const folderBasedNamingConvention = {
19281
19328
  // Check if name starts with lowercase (camelCase)
19282
19329
  const isCamelCaseHandler = (name) => name && /^[a-z]/.test(name);
19283
19330
 
19284
- // Build suffix message for camelCase folders
19285
- const buildSuffixMessageHandler = (name, folder, suffix) => `"${name}" in "${folder}" folder must end with "${suffix}" suffix (e.g., "myItem${suffix}")`;
19286
-
19287
- // Check camelCase naming (suffix-only enforcement for camelCase folders)
19288
- const checkCamelCaseHandler = (name, folder, suffix, identifierNode) => {
19289
- // For camelCase folders, only enforce the suffix (not full chained name)
19290
- // because these folders often have multiple exports per file
19291
- // Suffix stays PascalCase even in camelCase names (e.g., buttonTypeData)
19331
+ // Check camelCase naming for camelCase folders (constants, data, reducers, services, strings)
19332
+ const checkCamelCaseHandler = (name, folder, suffix, identifierNode, scopeNode, moduleInfo) => {
19292
19333
  if (!name.endsWith(suffix)) {
19334
+ // Missing suffix — auto-fix by appending suffix
19335
+ const fixedName = name + suffix;
19336
+
19337
+ context.report({
19338
+ fix: createRenameFixer(scopeNode, name, fixedName, identifierNode),
19339
+ message: `"${name}" in "${folder}" folder must end with "${suffix}" suffix (should be "${fixedName}")`,
19340
+ node: identifierNode,
19341
+ });
19342
+
19343
+ return;
19344
+ }
19345
+
19346
+ // Has correct suffix — check if prefix is a near-match of expected file-based name
19347
+ // This catches cases like "routeConstants" → "routesConstants" (file is routes.ts)
19348
+ // but allows unrelated names like "buttonTypeData" in data/app.ts
19349
+ const expectedName = buildExpectedNameHandler(moduleInfo);
19350
+
19351
+ if (!expectedName || name === expectedName) return;
19352
+
19353
+ const actualPrefix = name.slice(0, -suffix.length);
19354
+ const expectedPrefix = expectedName.slice(0, -suffix.length);
19355
+
19356
+ const isNearMatch = (expectedPrefix.startsWith(actualPrefix) && (expectedPrefix.length - actualPrefix.length) <= 2)
19357
+ || (actualPrefix.startsWith(expectedPrefix) && (actualPrefix.length - expectedPrefix.length) <= 2);
19358
+
19359
+ if (isNearMatch) {
19293
19360
  context.report({
19294
- message: buildSuffixMessageHandler(name, folder, suffix),
19361
+ fix: createRenameFixer(scopeNode, name, expectedName, identifierNode),
19362
+ message: `"${name}" in "${folder}" folder should be "${expectedName}" to match the file name`,
19295
19363
  node: identifierNode,
19296
19364
  });
19297
19365
  }
@@ -19311,11 +19379,11 @@ const folderBasedNamingConvention = {
19311
19379
 
19312
19380
  const { folder, suffix } = moduleInfo;
19313
19381
 
19314
- // For camelCase folders, only enforce suffix
19382
+ // For camelCase folders, enforce suffix + near-match prefix check
19315
19383
  if (camelCaseFolders.has(folder)) {
19316
19384
  if (!isCamelCaseHandler(name) || !suffix) return;
19317
19385
 
19318
- checkCamelCaseHandler(name, folder, suffix, identifierNode);
19386
+ checkCamelCaseHandler(name, folder, suffix, identifierNode, node, moduleInfo);
19319
19387
 
19320
19388
  return;
19321
19389
  }
@@ -19357,11 +19425,11 @@ const folderBasedNamingConvention = {
19357
19425
 
19358
19426
  const name = node.id.name;
19359
19427
 
19360
- // For camelCase folders, only enforce suffix
19428
+ // For camelCase folders, enforce suffix + near-match prefix check
19361
19429
  if (camelCaseFolders.has(folder)) {
19362
19430
  if (!isCamelCaseHandler(name) || !suffix) return;
19363
19431
 
19364
- checkCamelCaseHandler(name, folder, suffix, node.id);
19432
+ checkCamelCaseHandler(name, folder, suffix, node.id, node, moduleInfo);
19365
19433
 
19366
19434
  return;
19367
19435
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-code-style",
3
- "version": "1.17.0",
3
+ "version": "1.17.2",
4
4
  "description": "A custom ESLint plugin for enforcing consistent code formatting and style rules in React/JSX projects",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",