eslint-plugin-code-style 1.0.14 → 1.0.16

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/README.md +54 -0
  2. package/index.js +234 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -195,6 +195,7 @@ rules: {
195
195
  "code-style/export-format": "error",
196
196
  "code-style/import-format": "error",
197
197
  "code-style/import-source-spacing": "error",
198
+ "code-style/index-export-style": "error",
198
199
  "code-style/module-index-exports": "error",
199
200
  "code-style/jsx-children-on-new-line": "error",
200
201
  "code-style/jsx-closing-bracket-spacing": "error",
@@ -257,6 +258,7 @@ rules: {
257
258
  | `export-format` | Format exports: collapse 1-3 specifiers, multiline for 4+ |
258
259
  | `import-format` | Format imports: collapse 1-3 specifiers, multiline for 4+ |
259
260
  | `import-source-spacing` | Enforce no extra spaces inside import path quotes |
261
+ | `index-export-style` | Enforce consistent export style in index files (shorthand or import-then-export) ⚙️ |
260
262
  | `module-index-exports` | Enforce proper exports in index files ⚙️ |
261
263
  | `jsx-children-on-new-line` | Enforce JSX children on separate lines from parent tags |
262
264
  | `jsx-closing-bracket-spacing` | No space before `>` or `/>` in JSX tags |
@@ -785,6 +787,58 @@ import { Button } from " @mui/material ";
785
787
 
786
788
  ---
787
789
 
790
+ ### `index-export-style`
791
+
792
+ Enforce consistent export style in index files. Choose between shorthand re-exports or import-then-export pattern.
793
+
794
+ **Style: "shorthand" (default)**
795
+ ```javascript
796
+ // Good - shorthand re-exports
797
+ export { Button } from "./button";
798
+ export { Input, Select } from "./form";
799
+ export { StyledCard, StyledCardWithActions } from "./card";
800
+ ```
801
+
802
+ **Style: "import-export"**
803
+ ```javascript
804
+ // Good - import then export
805
+ import { Button } from "./button";
806
+ import { Input, Select } from "./form";
807
+ import { StyledCard, StyledCardWithActions } from "./card";
808
+
809
+ export {
810
+ Button,
811
+ Input,
812
+ Select,
813
+ StyledCard,
814
+ StyledCardWithActions,
815
+ };
816
+ ```
817
+
818
+ **Bad - mixing styles**
819
+ ```javascript
820
+ // Bad - don't mix shorthand with import-then-export
821
+ export { Button } from "./button";
822
+ import { Input } from "./input";
823
+ export { Input };
824
+ ```
825
+
826
+ **Customization Options:**
827
+
828
+ | Option | Type | Default | Description |
829
+ |--------|------|---------|-------------|
830
+ | `style` | `"shorthand"` \| `"import-export"` | `"shorthand"` | The export style to enforce |
831
+
832
+ ```javascript
833
+ // Example: Use shorthand style (default)
834
+ "code-style/index-export-style": "error"
835
+
836
+ // Example: Use import-then-export style
837
+ "code-style/index-export-style": ["error", { style: "import-export" }]
838
+ ```
839
+
840
+ ---
841
+
788
842
  ### `module-index-exports`
789
843
 
790
844
  Ensure module folders have index files that export all contents. Each module folder must have an index file that exports all subfolders and files in the module.
package/index.js CHANGED
@@ -3387,6 +3387,239 @@ const moduleIndexExports = {
3387
3387
  },
3388
3388
  };
3389
3389
 
3390
+ /**
3391
+ * ───────────────────────────────────────────────────────────────
3392
+ * Rule: Index Export Style
3393
+ * ───────────────────────────────────────────────────────────────
3394
+ *
3395
+ * Description:
3396
+ * Enforce consistent export style in index files. Choose between
3397
+ * shorthand re-exports or import-then-export pattern.
3398
+ *
3399
+ * Options:
3400
+ * - style: "shorthand" (default) | "import-export"
3401
+ * - "shorthand": export { a } from "./file";
3402
+ * - "import-export": import { a } from "./file"; export { a };
3403
+ *
3404
+ * ✓ Good (style: "shorthand" - default):
3405
+ * export { Button } from "./button";
3406
+ * export { Input, Select } from "./form";
3407
+ *
3408
+ * ✓ Good (style: "import-export"):
3409
+ * import { Button } from "./button";
3410
+ * import { Input, Select } from "./form";
3411
+ * export { Button, Input, Select };
3412
+ *
3413
+ * ✗ Bad (mixing styles):
3414
+ * export { Button } from "./button";
3415
+ * import { Input } from "./input";
3416
+ * export { Input };
3417
+ *
3418
+ * Configuration Example:
3419
+ * "code-style/index-export-style": ["error", { style: "shorthand" }]
3420
+ * "code-style/index-export-style": ["error", { style: "import-export" }]
3421
+ */
3422
+ const indexExportStyle = {
3423
+ create(context) {
3424
+ const options = context.options[0] || {};
3425
+ const preferredStyle = options.style || "shorthand";
3426
+ const sourceCode = context.sourceCode || context.getSourceCode();
3427
+ const filename = context.filename || context.getFilename();
3428
+ const normalizedFilename = filename.replace(/\\/g, "/");
3429
+
3430
+ // Only apply to index files
3431
+ const isIndexFile = /\/index\.(js|jsx|ts|tsx)$/.test(normalizedFilename);
3432
+
3433
+ if (!isIndexFile) return {};
3434
+
3435
+ return {
3436
+ Program(node) {
3437
+ const imports = [];
3438
+ const shorthandExports = [];
3439
+ const standaloneExports = [];
3440
+ const importSourceMap = new Map();
3441
+
3442
+ // Collect all imports and exports
3443
+ node.body.forEach((statement) => {
3444
+ if (statement.type === "ImportDeclaration" && statement.source) {
3445
+ imports.push(statement);
3446
+
3447
+ // Map imported names to their source
3448
+ statement.specifiers.forEach((spec) => {
3449
+ if (spec.local && spec.local.name) {
3450
+ importSourceMap.set(spec.local.name, {
3451
+ source: statement.source.value,
3452
+ statement,
3453
+ });
3454
+ }
3455
+ });
3456
+ }
3457
+
3458
+ if (statement.type === "ExportNamedDeclaration") {
3459
+ if (statement.source) {
3460
+ // Shorthand: export { a } from "./file"
3461
+ shorthandExports.push(statement);
3462
+ } else if (statement.specifiers && statement.specifiers.length > 0 && !statement.declaration) {
3463
+ // Standalone: export { a }
3464
+ standaloneExports.push(statement);
3465
+ }
3466
+ }
3467
+ });
3468
+
3469
+ // Skip if no exports to check
3470
+ if (shorthandExports.length === 0 && standaloneExports.length === 0) return;
3471
+
3472
+ // Check for mixed styles
3473
+ const hasShorthand = shorthandExports.length > 0;
3474
+ const hasImportExport = standaloneExports.length > 0 && imports.length > 0;
3475
+
3476
+ if (hasShorthand && hasImportExport) {
3477
+ context.report({
3478
+ message: `Mixed export styles detected. Use consistent "${preferredStyle}" style throughout the index file.`,
3479
+ node,
3480
+ });
3481
+
3482
+ return;
3483
+ }
3484
+
3485
+ if (preferredStyle === "shorthand") {
3486
+ // Check if using import-then-export pattern when shorthand is preferred
3487
+ standaloneExports.forEach((exportStmt) => {
3488
+ const specifiersToConvert = [];
3489
+
3490
+ exportStmt.specifiers.forEach((spec) => {
3491
+ const exportedName = spec.local ? spec.local.name : spec.exported.name;
3492
+ const importInfo = importSourceMap.get(exportedName);
3493
+
3494
+ if (importInfo) {
3495
+ specifiersToConvert.push({
3496
+ exportedName,
3497
+ importInfo,
3498
+ localName: spec.local ? spec.local.name : exportedName,
3499
+ spec,
3500
+ });
3501
+ }
3502
+ });
3503
+
3504
+ if (specifiersToConvert.length > 0) {
3505
+ context.report({
3506
+ fix(fixer) {
3507
+ const fixes = [];
3508
+
3509
+ // Group specifiers by source
3510
+ const bySource = new Map();
3511
+
3512
+ specifiersToConvert.forEach(({ exportedName, importInfo, localName }) => {
3513
+ const source = importInfo.source;
3514
+
3515
+ if (!bySource.has(source)) {
3516
+ bySource.set(source, []);
3517
+ }
3518
+
3519
+ if (exportedName === localName) {
3520
+ bySource.get(source).push(exportedName);
3521
+ } else {
3522
+ bySource.get(source).push(`${localName} as ${exportedName}`);
3523
+ }
3524
+ });
3525
+
3526
+ // Create shorthand exports
3527
+ const newExports = [];
3528
+
3529
+ bySource.forEach((specifiers, source) => {
3530
+ newExports.push(`export { ${specifiers.join(", ")} } from "${source}";`);
3531
+ });
3532
+
3533
+ // Remove the standalone export
3534
+ fixes.push(fixer.remove(exportStmt));
3535
+
3536
+ // Remove associated imports
3537
+ const importsToRemove = new Set();
3538
+
3539
+ specifiersToConvert.forEach(({ importInfo }) => {
3540
+ importsToRemove.add(importInfo.statement);
3541
+ });
3542
+
3543
+ importsToRemove.forEach((importStmt) => {
3544
+ // Check if all specifiers from this import are being converted
3545
+ const allSpecifiersConverted = importStmt.specifiers.every((spec) => {
3546
+ const name = spec.local ? spec.local.name : spec.imported.name;
3547
+
3548
+ return specifiersToConvert.some((s) => s.localName === name);
3549
+ });
3550
+
3551
+ if (allSpecifiersConverted) {
3552
+ fixes.push(fixer.remove(importStmt));
3553
+ }
3554
+ });
3555
+
3556
+ // Insert new shorthand exports at the position of removed export
3557
+ fixes.push(fixer.insertTextAfter(exportStmt, "\n" + newExports.join("\n")));
3558
+
3559
+ return fixes;
3560
+ },
3561
+ message: `Use shorthand export: export { ... } from "source" instead of import then export.`,
3562
+ node: exportStmt,
3563
+ });
3564
+ }
3565
+ });
3566
+ } else if (preferredStyle === "import-export") {
3567
+ // Check if using shorthand when import-export is preferred
3568
+ shorthandExports.forEach((exportStmt) => {
3569
+ const source = exportStmt.source.value;
3570
+ const specifiers = exportStmt.specifiers.map((spec) => {
3571
+ const imported = spec.local ? spec.local.name : spec.exported.name;
3572
+ const exported = spec.exported.name;
3573
+
3574
+ if (imported === exported) {
3575
+ return imported;
3576
+ }
3577
+
3578
+ return `${imported} as ${exported}`;
3579
+ });
3580
+
3581
+ context.report({
3582
+ fix(fixer) {
3583
+ const importSpecifiers = exportStmt.specifiers.map((spec) => {
3584
+ const imported = spec.local ? spec.local.name : spec.exported.name;
3585
+
3586
+ return imported;
3587
+ });
3588
+
3589
+ const exportSpecifiers = exportStmt.specifiers.map((spec) => spec.exported.name);
3590
+
3591
+ const importLine = `import { ${importSpecifiers.join(", ")} } from "${source}";`;
3592
+ const exportLine = `export { ${exportSpecifiers.join(", ")} };`;
3593
+
3594
+ return fixer.replaceText(exportStmt, `${importLine}\n${exportLine}`);
3595
+ },
3596
+ message: `Use import-then-export style: import { ... } from "source"; export { ... };`,
3597
+ node: exportStmt,
3598
+ });
3599
+ });
3600
+ }
3601
+ },
3602
+ };
3603
+ },
3604
+ meta: {
3605
+ docs: { description: "Enforce consistent export style in index files (shorthand or import-then-export)" },
3606
+ fixable: "code",
3607
+ schema: [
3608
+ {
3609
+ additionalProperties: false,
3610
+ properties: {
3611
+ style: {
3612
+ enum: ["shorthand", "import-export"],
3613
+ type: "string",
3614
+ },
3615
+ },
3616
+ type: "object",
3617
+ },
3618
+ ],
3619
+ type: "layout",
3620
+ },
3621
+ };
3622
+
3390
3623
  /**
3391
3624
  * ───────────────────────────────────────────────────────────────
3392
3625
  * Rule: JSX Children On New Line
@@ -7821,6 +8054,7 @@ export default {
7821
8054
  "export-format": exportFormat,
7822
8055
  "import-format": importFormat,
7823
8056
  "import-source-spacing": importSourceSpacing,
8057
+ "index-export-style": indexExportStyle,
7824
8058
  "module-index-exports": moduleIndexExports,
7825
8059
 
7826
8060
  // JSX rules
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-code-style",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
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",