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.
- package/README.md +54 -0
- package/index.js +234 -0
- 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