eslint-plugin-code-style 1.0.41 → 1.1.10
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 +856 -658
- package/index.d.ts +8 -0
- package/index.js +499 -37
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -50,6 +50,10 @@ export type RuleNames =
|
|
|
50
50
|
| "code-style/simple-call-single-line"
|
|
51
51
|
| "code-style/single-argument-on-one-line"
|
|
52
52
|
| "code-style/string-property-spacing"
|
|
53
|
+
| "code-style/enum-format"
|
|
54
|
+
| "code-style/interface-format"
|
|
55
|
+
| "code-style/type-format"
|
|
56
|
+
| "code-style/typescript-definition-location"
|
|
53
57
|
| "code-style/variable-naming-convention";
|
|
54
58
|
|
|
55
59
|
/**
|
|
@@ -119,6 +123,10 @@ interface PluginRules {
|
|
|
119
123
|
"simple-call-single-line": Rule.RuleModule;
|
|
120
124
|
"single-argument-on-one-line": Rule.RuleModule;
|
|
121
125
|
"string-property-spacing": Rule.RuleModule;
|
|
126
|
+
"enum-format": Rule.RuleModule;
|
|
127
|
+
"interface-format": Rule.RuleModule;
|
|
128
|
+
"type-format": Rule.RuleModule;
|
|
129
|
+
"typescript-definition-location": Rule.RuleModule;
|
|
122
130
|
"variable-naming-convention": Rule.RuleModule;
|
|
123
131
|
}
|
|
124
132
|
|
package/index.js
CHANGED
|
@@ -2515,7 +2515,9 @@ const absoluteImportsOnly = {
|
|
|
2515
2515
|
"constants",
|
|
2516
2516
|
"contexts",
|
|
2517
2517
|
"data",
|
|
2518
|
+
"enums",
|
|
2518
2519
|
"hooks",
|
|
2520
|
+
"interfaces",
|
|
2519
2521
|
"layouts",
|
|
2520
2522
|
"middlewares",
|
|
2521
2523
|
"providers",
|
|
@@ -3139,6 +3141,7 @@ const moduleIndexExports = {
|
|
|
3139
3141
|
|
|
3140
3142
|
// Default module folders
|
|
3141
3143
|
const defaultModuleFolders = [
|
|
3144
|
+
"actions",
|
|
3142
3145
|
"apis",
|
|
3143
3146
|
"assets",
|
|
3144
3147
|
"atoms",
|
|
@@ -3146,17 +3149,23 @@ const moduleIndexExports = {
|
|
|
3146
3149
|
"constants",
|
|
3147
3150
|
"contexts",
|
|
3148
3151
|
"data",
|
|
3152
|
+
"enums",
|
|
3149
3153
|
"hooks",
|
|
3154
|
+
"interfaces",
|
|
3150
3155
|
"layouts",
|
|
3151
3156
|
"middlewares",
|
|
3152
3157
|
"providers",
|
|
3158
|
+
"reducers",
|
|
3153
3159
|
"redux",
|
|
3154
3160
|
"requests",
|
|
3155
3161
|
"routes",
|
|
3156
3162
|
"schemas",
|
|
3157
3163
|
"services",
|
|
3164
|
+
"store",
|
|
3158
3165
|
"styles",
|
|
3159
3166
|
"theme",
|
|
3167
|
+
"thunks",
|
|
3168
|
+
"types",
|
|
3160
3169
|
"utils",
|
|
3161
3170
|
"views",
|
|
3162
3171
|
];
|
|
@@ -3185,8 +3194,12 @@ const moduleIndexExports = {
|
|
|
3185
3194
|
"__mocks__",
|
|
3186
3195
|
"*.test.js",
|
|
3187
3196
|
"*.test.jsx",
|
|
3197
|
+
"*.test.ts",
|
|
3198
|
+
"*.test.tsx",
|
|
3188
3199
|
"*.spec.js",
|
|
3189
3200
|
"*.spec.jsx",
|
|
3201
|
+
"*.spec.ts",
|
|
3202
|
+
"*.spec.tsx",
|
|
3190
3203
|
];
|
|
3191
3204
|
|
|
3192
3205
|
// Files/folders to ignore
|
|
@@ -3320,7 +3333,13 @@ const moduleIndexExports = {
|
|
|
3320
3333
|
}
|
|
3321
3334
|
|
|
3322
3335
|
// Handle cases like ./folder/index
|
|
3323
|
-
if (isDirectory && (
|
|
3336
|
+
if (isDirectory && (
|
|
3337
|
+
source === `./${itemName}/index`
|
|
3338
|
+
|| source === `./${itemName}/index.js`
|
|
3339
|
+
|| source === `./${itemName}/index.jsx`
|
|
3340
|
+
|| source === `./${itemName}/index.ts`
|
|
3341
|
+
|| source === `./${itemName}/index.tsx`
|
|
3342
|
+
)) {
|
|
3324
3343
|
return true;
|
|
3325
3344
|
}
|
|
3326
3345
|
|
|
@@ -3371,12 +3390,19 @@ const moduleIndexExports = {
|
|
|
3371
3390
|
|
|
3372
3391
|
if (moduleFolders.includes(folderName) && fileName !== "index") {
|
|
3373
3392
|
const dirPath = nodePath.dirname(filename);
|
|
3374
|
-
const
|
|
3393
|
+
const indexPathJs = nodePath.join(dirPath, "index.js");
|
|
3375
3394
|
const indexPathJsx = nodePath.join(dirPath, "index.jsx");
|
|
3395
|
+
const indexPathTs = nodePath.join(dirPath, "index.ts");
|
|
3396
|
+
const indexPathTsx = nodePath.join(dirPath, "index.tsx");
|
|
3376
3397
|
|
|
3377
|
-
|
|
3398
|
+
const hasIndexFile = fs.existsSync(indexPathJs)
|
|
3399
|
+
|| fs.existsSync(indexPathJsx)
|
|
3400
|
+
|| fs.existsSync(indexPathTs)
|
|
3401
|
+
|| fs.existsSync(indexPathTsx);
|
|
3402
|
+
|
|
3403
|
+
if (!hasIndexFile) {
|
|
3378
3404
|
context.report({
|
|
3379
|
-
message: `Module folder "${folderName}" is missing an index file. Create index
|
|
3405
|
+
message: `Module folder "${folderName}" is missing an index file. Create an index file to export all modules.`,
|
|
3380
3406
|
node,
|
|
3381
3407
|
});
|
|
3382
3408
|
}
|
|
@@ -8465,6 +8491,450 @@ const variableNamingConvention = {
|
|
|
8465
8491
|
},
|
|
8466
8492
|
};
|
|
8467
8493
|
|
|
8494
|
+
/*
|
|
8495
|
+
* typescript-definition-location
|
|
8496
|
+
*
|
|
8497
|
+
* Enforce that TypeScript definitions are declared in their designated folders:
|
|
8498
|
+
* - Interfaces must be in files inside the "interfaces" folder
|
|
8499
|
+
* - Enums must be in files inside the "enums" folder
|
|
8500
|
+
* - Types must be in files inside the "types" folder
|
|
8501
|
+
*
|
|
8502
|
+
* ✓ Good:
|
|
8503
|
+
* // src/interfaces/user.ts
|
|
8504
|
+
* export interface UserInterface { ... }
|
|
8505
|
+
*
|
|
8506
|
+
* // src/enums/status.ts
|
|
8507
|
+
* export enum StatusEnum { ... }
|
|
8508
|
+
*
|
|
8509
|
+
* // src/types/config.ts
|
|
8510
|
+
* export type ConfigType = { ... }
|
|
8511
|
+
*
|
|
8512
|
+
* ✗ Bad:
|
|
8513
|
+
* // src/components/user.tsx
|
|
8514
|
+
* export interface UserInterface { ... } // Interface not in interfaces folder
|
|
8515
|
+
*/
|
|
8516
|
+
/*
|
|
8517
|
+
* interface-format
|
|
8518
|
+
*
|
|
8519
|
+
* Enforce consistent formatting for TypeScript interfaces:
|
|
8520
|
+
* - Interface name must be PascalCase and end with "Interface" suffix
|
|
8521
|
+
* - Properties must be in camelCase
|
|
8522
|
+
* - No empty lines between properties
|
|
8523
|
+
* - Each property must end with comma (,) not semicolon (;)
|
|
8524
|
+
*
|
|
8525
|
+
* ✓ Good:
|
|
8526
|
+
* export interface UserInterface {
|
|
8527
|
+
* firstName: string,
|
|
8528
|
+
* lastName: string,
|
|
8529
|
+
* age: number,
|
|
8530
|
+
* }
|
|
8531
|
+
*
|
|
8532
|
+
* ✗ Bad:
|
|
8533
|
+
* export interface User { // Missing "Interface" suffix
|
|
8534
|
+
* first_name: string; // snake_case and semicolon
|
|
8535
|
+
*
|
|
8536
|
+
* lastName: string; // Empty line above and semicolon
|
|
8537
|
+
* }
|
|
8538
|
+
*/
|
|
8539
|
+
/*
|
|
8540
|
+
* enum-format
|
|
8541
|
+
*
|
|
8542
|
+
* Enforce consistent formatting for TypeScript enums:
|
|
8543
|
+
* - Enum name must be PascalCase and end with "Enum" suffix
|
|
8544
|
+
* - Member names must be UPPER_CASE (e.g., DELETE, GET, POST)
|
|
8545
|
+
* - No empty lines between members
|
|
8546
|
+
* - Each member must end with comma (,) not semicolon (;)
|
|
8547
|
+
*
|
|
8548
|
+
* ✓ Good:
|
|
8549
|
+
* export enum HttpMethodEnum {
|
|
8550
|
+
* DELETE = "delete",
|
|
8551
|
+
* GET = "get",
|
|
8552
|
+
* POST = "post",
|
|
8553
|
+
* }
|
|
8554
|
+
*
|
|
8555
|
+
* ✗ Bad:
|
|
8556
|
+
* export enum HttpMethod { // Missing "Enum" suffix
|
|
8557
|
+
* delete = "delete"; // lowercase and semicolon
|
|
8558
|
+
*
|
|
8559
|
+
* Get = "get"; // PascalCase, empty line, semicolon
|
|
8560
|
+
* }
|
|
8561
|
+
*/
|
|
8562
|
+
/*
|
|
8563
|
+
* type-format
|
|
8564
|
+
*
|
|
8565
|
+
* Enforce consistent formatting for TypeScript type aliases:
|
|
8566
|
+
* - Type name must be PascalCase and end with "Type" suffix
|
|
8567
|
+
* - If object-like, properties must be in camelCase
|
|
8568
|
+
* - No empty lines between properties (for object types)
|
|
8569
|
+
* - Each property must end with comma (,) not semicolon (;)
|
|
8570
|
+
*
|
|
8571
|
+
* ✓ Good:
|
|
8572
|
+
* export type UserType = {
|
|
8573
|
+
* firstName: string,
|
|
8574
|
+
* lastName: string,
|
|
8575
|
+
* }
|
|
8576
|
+
*
|
|
8577
|
+
* export type IdType = string | number;
|
|
8578
|
+
*
|
|
8579
|
+
* ✗ Bad:
|
|
8580
|
+
* export type User = { // Missing "Type" suffix
|
|
8581
|
+
* first_name: string; // snake_case and semicolon
|
|
8582
|
+
* }
|
|
8583
|
+
*/
|
|
8584
|
+
const typeFormat = {
|
|
8585
|
+
create(context) {
|
|
8586
|
+
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
8587
|
+
|
|
8588
|
+
const pascalCaseRegex = /^[A-Z][a-zA-Z0-9]*$/;
|
|
8589
|
+
const camelCaseRegex = /^[a-z][a-zA-Z0-9]*$/;
|
|
8590
|
+
|
|
8591
|
+
const checkTypeLiteralHandler = (node, members) => {
|
|
8592
|
+
members.forEach((member, index) => {
|
|
8593
|
+
// Check property name is camelCase
|
|
8594
|
+
if (member.type === "TSPropertySignature" && member.key && member.key.type === "Identifier") {
|
|
8595
|
+
const propName = member.key.name;
|
|
8596
|
+
|
|
8597
|
+
if (!camelCaseRegex.test(propName)) {
|
|
8598
|
+
context.report({
|
|
8599
|
+
message: `Type property "${propName}" must be camelCase`,
|
|
8600
|
+
node: member.key,
|
|
8601
|
+
});
|
|
8602
|
+
}
|
|
8603
|
+
}
|
|
8604
|
+
|
|
8605
|
+
// Check for empty lines between properties
|
|
8606
|
+
if (index > 0) {
|
|
8607
|
+
const prevMember = members[index - 1];
|
|
8608
|
+
const currentLine = member.loc.start.line;
|
|
8609
|
+
const prevLine = prevMember.loc.end.line;
|
|
8610
|
+
|
|
8611
|
+
if (currentLine - prevLine > 1) {
|
|
8612
|
+
context.report({
|
|
8613
|
+
fix(fixer) {
|
|
8614
|
+
const textBetween = sourceCode.getText().slice(
|
|
8615
|
+
prevMember.range[1],
|
|
8616
|
+
member.range[0],
|
|
8617
|
+
);
|
|
8618
|
+
const newText = textBetween.replace(/\n\s*\n/g, "\n");
|
|
8619
|
+
|
|
8620
|
+
return fixer.replaceTextRange(
|
|
8621
|
+
[prevMember.range[1], member.range[0]],
|
|
8622
|
+
newText,
|
|
8623
|
+
);
|
|
8624
|
+
},
|
|
8625
|
+
message: "No empty lines allowed between type properties",
|
|
8626
|
+
node: member,
|
|
8627
|
+
});
|
|
8628
|
+
}
|
|
8629
|
+
}
|
|
8630
|
+
|
|
8631
|
+
// Check property ends with comma, not semicolon
|
|
8632
|
+
const memberText = sourceCode.getText(member);
|
|
8633
|
+
|
|
8634
|
+
if (memberText.trimEnd().endsWith(";")) {
|
|
8635
|
+
context.report({
|
|
8636
|
+
fix(fixer) {
|
|
8637
|
+
const lastChar = memberText.lastIndexOf(";");
|
|
8638
|
+
const absolutePos = member.range[0] + lastChar;
|
|
8639
|
+
|
|
8640
|
+
return fixer.replaceTextRange([absolutePos, absolutePos + 1], ",");
|
|
8641
|
+
},
|
|
8642
|
+
message: "Type properties must end with comma (,) not semicolon (;)",
|
|
8643
|
+
node: member,
|
|
8644
|
+
});
|
|
8645
|
+
}
|
|
8646
|
+
});
|
|
8647
|
+
};
|
|
8648
|
+
|
|
8649
|
+
return {
|
|
8650
|
+
TSTypeAliasDeclaration(node) {
|
|
8651
|
+
const typeName = node.id.name;
|
|
8652
|
+
|
|
8653
|
+
// Check type name is PascalCase and ends with Type
|
|
8654
|
+
if (!pascalCaseRegex.test(typeName)) {
|
|
8655
|
+
context.report({
|
|
8656
|
+
message: `Type name "${typeName}" must be PascalCase`,
|
|
8657
|
+
node: node.id,
|
|
8658
|
+
});
|
|
8659
|
+
} else if (!typeName.endsWith("Type")) {
|
|
8660
|
+
context.report({
|
|
8661
|
+
fix(fixer) {
|
|
8662
|
+
return fixer.replaceText(node.id, `${typeName}Type`);
|
|
8663
|
+
},
|
|
8664
|
+
message: `Type name "${typeName}" must end with "Type" suffix`,
|
|
8665
|
+
node: node.id,
|
|
8666
|
+
});
|
|
8667
|
+
}
|
|
8668
|
+
|
|
8669
|
+
// Check if it's an object type (TSTypeLiteral)
|
|
8670
|
+
if (node.typeAnnotation && node.typeAnnotation.type === "TSTypeLiteral") {
|
|
8671
|
+
checkTypeLiteralHandler(node, node.typeAnnotation.members);
|
|
8672
|
+
}
|
|
8673
|
+
|
|
8674
|
+
// Also check intersection types that contain object types
|
|
8675
|
+
if (node.typeAnnotation && node.typeAnnotation.type === "TSIntersectionType") {
|
|
8676
|
+
node.typeAnnotation.types.forEach((type) => {
|
|
8677
|
+
if (type.type === "TSTypeLiteral") {
|
|
8678
|
+
checkTypeLiteralHandler(node, type.members);
|
|
8679
|
+
}
|
|
8680
|
+
});
|
|
8681
|
+
}
|
|
8682
|
+
},
|
|
8683
|
+
};
|
|
8684
|
+
},
|
|
8685
|
+
meta: {
|
|
8686
|
+
docs: { description: "Enforce type naming (PascalCase + Type suffix), camelCase properties, no empty lines, and trailing commas" },
|
|
8687
|
+
fixable: "code",
|
|
8688
|
+
schema: [],
|
|
8689
|
+
type: "suggestion",
|
|
8690
|
+
},
|
|
8691
|
+
};
|
|
8692
|
+
|
|
8693
|
+
const enumFormat = {
|
|
8694
|
+
create(context) {
|
|
8695
|
+
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
8696
|
+
|
|
8697
|
+
const pascalCaseRegex = /^[A-Z][a-zA-Z0-9]*$/;
|
|
8698
|
+
const upperCaseRegex = /^[A-Z][A-Z0-9_]*$/;
|
|
8699
|
+
|
|
8700
|
+
return {
|
|
8701
|
+
TSEnumDeclaration(node) {
|
|
8702
|
+
const enumName = node.id.name;
|
|
8703
|
+
|
|
8704
|
+
// Check enum name is PascalCase and ends with Enum
|
|
8705
|
+
if (!pascalCaseRegex.test(enumName)) {
|
|
8706
|
+
context.report({
|
|
8707
|
+
message: `Enum name "${enumName}" must be PascalCase`,
|
|
8708
|
+
node: node.id,
|
|
8709
|
+
});
|
|
8710
|
+
} else if (!enumName.endsWith("Enum")) {
|
|
8711
|
+
context.report({
|
|
8712
|
+
fix(fixer) {
|
|
8713
|
+
return fixer.replaceText(node.id, `${enumName}Enum`);
|
|
8714
|
+
},
|
|
8715
|
+
message: `Enum name "${enumName}" must end with "Enum" suffix`,
|
|
8716
|
+
node: node.id,
|
|
8717
|
+
});
|
|
8718
|
+
}
|
|
8719
|
+
|
|
8720
|
+
// Check members
|
|
8721
|
+
const members = node.members;
|
|
8722
|
+
|
|
8723
|
+
members.forEach((member, index) => {
|
|
8724
|
+
// Check member name is UPPER_CASE
|
|
8725
|
+
if (member.id && member.id.type === "Identifier") {
|
|
8726
|
+
const memberName = member.id.name;
|
|
8727
|
+
|
|
8728
|
+
if (!upperCaseRegex.test(memberName)) {
|
|
8729
|
+
context.report({
|
|
8730
|
+
message: `Enum member "${memberName}" must be UPPER_CASE (e.g., ${memberName.toUpperCase()})`,
|
|
8731
|
+
node: member.id,
|
|
8732
|
+
});
|
|
8733
|
+
}
|
|
8734
|
+
}
|
|
8735
|
+
|
|
8736
|
+
// Check for empty lines between members
|
|
8737
|
+
if (index > 0) {
|
|
8738
|
+
const prevMember = members[index - 1];
|
|
8739
|
+
const currentLine = member.loc.start.line;
|
|
8740
|
+
const prevLine = prevMember.loc.end.line;
|
|
8741
|
+
|
|
8742
|
+
if (currentLine - prevLine > 1) {
|
|
8743
|
+
context.report({
|
|
8744
|
+
fix(fixer) {
|
|
8745
|
+
const textBetween = sourceCode.getText().slice(
|
|
8746
|
+
prevMember.range[1],
|
|
8747
|
+
member.range[0],
|
|
8748
|
+
);
|
|
8749
|
+
const newText = textBetween.replace(/\n\s*\n/g, "\n");
|
|
8750
|
+
|
|
8751
|
+
return fixer.replaceTextRange(
|
|
8752
|
+
[prevMember.range[1], member.range[0]],
|
|
8753
|
+
newText,
|
|
8754
|
+
);
|
|
8755
|
+
},
|
|
8756
|
+
message: "No empty lines allowed between enum members",
|
|
8757
|
+
node: member,
|
|
8758
|
+
});
|
|
8759
|
+
}
|
|
8760
|
+
}
|
|
8761
|
+
|
|
8762
|
+
// Check member ends with comma, not semicolon
|
|
8763
|
+
const memberText = sourceCode.getText(member);
|
|
8764
|
+
|
|
8765
|
+
if (memberText.trimEnd().endsWith(";")) {
|
|
8766
|
+
context.report({
|
|
8767
|
+
fix(fixer) {
|
|
8768
|
+
const lastChar = memberText.lastIndexOf(";");
|
|
8769
|
+
const absolutePos = member.range[0] + lastChar;
|
|
8770
|
+
|
|
8771
|
+
return fixer.replaceTextRange([absolutePos, absolutePos + 1], ",");
|
|
8772
|
+
},
|
|
8773
|
+
message: "Enum members must end with comma (,) not semicolon (;)",
|
|
8774
|
+
node: member,
|
|
8775
|
+
});
|
|
8776
|
+
}
|
|
8777
|
+
});
|
|
8778
|
+
},
|
|
8779
|
+
};
|
|
8780
|
+
},
|
|
8781
|
+
meta: {
|
|
8782
|
+
docs: { description: "Enforce enum naming (PascalCase + Enum suffix), UPPER_CASE members, no empty lines, and trailing commas" },
|
|
8783
|
+
fixable: "code",
|
|
8784
|
+
schema: [],
|
|
8785
|
+
type: "suggestion",
|
|
8786
|
+
},
|
|
8787
|
+
};
|
|
8788
|
+
|
|
8789
|
+
const interfaceFormat = {
|
|
8790
|
+
create(context) {
|
|
8791
|
+
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
8792
|
+
|
|
8793
|
+
const pascalCaseRegex = /^[A-Z][a-zA-Z0-9]*$/;
|
|
8794
|
+
const camelCaseRegex = /^[a-z][a-zA-Z0-9]*$/;
|
|
8795
|
+
|
|
8796
|
+
return {
|
|
8797
|
+
TSInterfaceDeclaration(node) {
|
|
8798
|
+
const interfaceName = node.id.name;
|
|
8799
|
+
|
|
8800
|
+
// Check interface name is PascalCase and ends with Interface
|
|
8801
|
+
if (!pascalCaseRegex.test(interfaceName)) {
|
|
8802
|
+
context.report({
|
|
8803
|
+
message: `Interface name "${interfaceName}" must be PascalCase`,
|
|
8804
|
+
node: node.id,
|
|
8805
|
+
});
|
|
8806
|
+
} else if (!interfaceName.endsWith("Interface")) {
|
|
8807
|
+
context.report({
|
|
8808
|
+
fix(fixer) {
|
|
8809
|
+
return fixer.replaceText(node.id, `${interfaceName}Interface`);
|
|
8810
|
+
},
|
|
8811
|
+
message: `Interface name "${interfaceName}" must end with "Interface" suffix`,
|
|
8812
|
+
node: node.id,
|
|
8813
|
+
});
|
|
8814
|
+
}
|
|
8815
|
+
|
|
8816
|
+
// Check properties
|
|
8817
|
+
const members = node.body.body;
|
|
8818
|
+
|
|
8819
|
+
members.forEach((member, index) => {
|
|
8820
|
+
// Check property name is camelCase
|
|
8821
|
+
if (member.type === "TSPropertySignature" && member.key && member.key.type === "Identifier") {
|
|
8822
|
+
const propName = member.key.name;
|
|
8823
|
+
|
|
8824
|
+
if (!camelCaseRegex.test(propName)) {
|
|
8825
|
+
context.report({
|
|
8826
|
+
message: `Interface property "${propName}" must be camelCase`,
|
|
8827
|
+
node: member.key,
|
|
8828
|
+
});
|
|
8829
|
+
}
|
|
8830
|
+
}
|
|
8831
|
+
|
|
8832
|
+
// Check for empty lines between properties
|
|
8833
|
+
if (index > 0) {
|
|
8834
|
+
const prevMember = members[index - 1];
|
|
8835
|
+
const currentLine = member.loc.start.line;
|
|
8836
|
+
const prevLine = prevMember.loc.end.line;
|
|
8837
|
+
|
|
8838
|
+
if (currentLine - prevLine > 1) {
|
|
8839
|
+
context.report({
|
|
8840
|
+
fix(fixer) {
|
|
8841
|
+
const textBetween = sourceCode.getText().slice(
|
|
8842
|
+
prevMember.range[1],
|
|
8843
|
+
member.range[0],
|
|
8844
|
+
);
|
|
8845
|
+
const newText = textBetween.replace(/\n\s*\n/g, "\n");
|
|
8846
|
+
|
|
8847
|
+
return fixer.replaceTextRange(
|
|
8848
|
+
[prevMember.range[1], member.range[0]],
|
|
8849
|
+
newText,
|
|
8850
|
+
);
|
|
8851
|
+
},
|
|
8852
|
+
message: "No empty lines allowed between interface properties",
|
|
8853
|
+
node: member,
|
|
8854
|
+
});
|
|
8855
|
+
}
|
|
8856
|
+
}
|
|
8857
|
+
|
|
8858
|
+
// Check property ends with comma, not semicolon
|
|
8859
|
+
const memberText = sourceCode.getText(member);
|
|
8860
|
+
|
|
8861
|
+
if (memberText.trimEnd().endsWith(";")) {
|
|
8862
|
+
context.report({
|
|
8863
|
+
fix(fixer) {
|
|
8864
|
+
const lastChar = memberText.lastIndexOf(";");
|
|
8865
|
+
const absolutePos = member.range[0] + lastChar;
|
|
8866
|
+
|
|
8867
|
+
return fixer.replaceTextRange([absolutePos, absolutePos + 1], ",");
|
|
8868
|
+
},
|
|
8869
|
+
message: "Interface properties must end with comma (,) not semicolon (;)",
|
|
8870
|
+
node: member,
|
|
8871
|
+
});
|
|
8872
|
+
}
|
|
8873
|
+
});
|
|
8874
|
+
},
|
|
8875
|
+
};
|
|
8876
|
+
},
|
|
8877
|
+
meta: {
|
|
8878
|
+
docs: { description: "Enforce interface naming (PascalCase + Interface suffix), camelCase properties, no empty lines, and trailing commas" },
|
|
8879
|
+
fixable: "code",
|
|
8880
|
+
schema: [],
|
|
8881
|
+
type: "suggestion",
|
|
8882
|
+
},
|
|
8883
|
+
};
|
|
8884
|
+
|
|
8885
|
+
const typescriptDefinitionLocation = {
|
|
8886
|
+
create(context) {
|
|
8887
|
+
const filename = context.filename || context.getFilename();
|
|
8888
|
+
const normalizedFilename = filename.replace(/\\/g, "/");
|
|
8889
|
+
|
|
8890
|
+
const isInFolderHandler = (folderName) => {
|
|
8891
|
+
const pattern = new RegExp(`/${folderName}/[^/]+\\.(ts|tsx)$`);
|
|
8892
|
+
|
|
8893
|
+
return pattern.test(normalizedFilename);
|
|
8894
|
+
};
|
|
8895
|
+
|
|
8896
|
+
const isTypeScriptFileHandler = () => /\.(ts|tsx)$/.test(normalizedFilename);
|
|
8897
|
+
|
|
8898
|
+
return {
|
|
8899
|
+
TSInterfaceDeclaration(node) {
|
|
8900
|
+
if (!isTypeScriptFileHandler()) return;
|
|
8901
|
+
|
|
8902
|
+
if (!isInFolderHandler("interfaces")) {
|
|
8903
|
+
context.report({
|
|
8904
|
+
message: "Interfaces must be declared in files inside the \"interfaces\" folder",
|
|
8905
|
+
node: node.id || node,
|
|
8906
|
+
});
|
|
8907
|
+
}
|
|
8908
|
+
},
|
|
8909
|
+
TSEnumDeclaration(node) {
|
|
8910
|
+
if (!isTypeScriptFileHandler()) return;
|
|
8911
|
+
|
|
8912
|
+
if (!isInFolderHandler("enums")) {
|
|
8913
|
+
context.report({
|
|
8914
|
+
message: "Enums must be declared in files inside the \"enums\" folder",
|
|
8915
|
+
node: node.id || node,
|
|
8916
|
+
});
|
|
8917
|
+
}
|
|
8918
|
+
},
|
|
8919
|
+
TSTypeAliasDeclaration(node) {
|
|
8920
|
+
if (!isTypeScriptFileHandler()) return;
|
|
8921
|
+
|
|
8922
|
+
if (!isInFolderHandler("types")) {
|
|
8923
|
+
context.report({
|
|
8924
|
+
message: "Type aliases must be declared in files inside the \"types\" folder",
|
|
8925
|
+
node: node.id || node,
|
|
8926
|
+
});
|
|
8927
|
+
}
|
|
8928
|
+
},
|
|
8929
|
+
};
|
|
8930
|
+
},
|
|
8931
|
+
meta: {
|
|
8932
|
+
docs: { description: "Enforce that interfaces are in interfaces folder, enums in enums folder, and types in types folder" },
|
|
8933
|
+
schema: [],
|
|
8934
|
+
type: "suggestion",
|
|
8935
|
+
},
|
|
8936
|
+
};
|
|
8937
|
+
|
|
8468
8938
|
export default {
|
|
8469
8939
|
meta: {
|
|
8470
8940
|
name: packageJson.name,
|
|
@@ -8481,28 +8951,33 @@ export default {
|
|
|
8481
8951
|
"arrow-function-simplify": arrowFunctionSimplify,
|
|
8482
8952
|
"curried-arrow-same-line": curriedArrowSameLine,
|
|
8483
8953
|
|
|
8484
|
-
//
|
|
8485
|
-
"
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
"
|
|
8954
|
+
// Call expression rules
|
|
8955
|
+
"function-arguments-format": functionArgumentsFormat,
|
|
8956
|
+
"nested-call-closing-brackets": nestedCallClosingBrackets,
|
|
8957
|
+
"no-empty-lines-in-function-calls": noEmptyLinesInFunctionCalls,
|
|
8958
|
+
"opening-brackets-same-line": openingBracketsSameLine,
|
|
8959
|
+
"simple-call-single-line": simpleCallSingleLine,
|
|
8960
|
+
"single-argument-on-one-line": singleArgumentOnOneLine,
|
|
8489
8961
|
|
|
8490
8962
|
// Comment rules
|
|
8491
8963
|
"comment-format": commentFormat,
|
|
8492
8964
|
|
|
8965
|
+
// Control flow rules
|
|
8966
|
+
"block-statement-newlines": blockStatementNewlines,
|
|
8967
|
+
"if-statement-format": ifStatementFormat,
|
|
8968
|
+
"multiline-if-conditions": multilineIfConditions,
|
|
8969
|
+
"no-empty-lines-in-switch-cases": noEmptyLinesInSwitchCases,
|
|
8970
|
+
|
|
8493
8971
|
// Function rules
|
|
8494
8972
|
"function-call-spacing": functionCallSpacing,
|
|
8495
8973
|
"function-naming-convention": functionNamingConvention,
|
|
8496
8974
|
"function-params-per-line": functionParamsPerLine,
|
|
8975
|
+
"no-empty-lines-in-function-params": noEmptyLinesInFunctionParams,
|
|
8497
8976
|
|
|
8498
8977
|
// Hook rules
|
|
8499
8978
|
"hook-callback-format": hookCallbackFormat,
|
|
8500
8979
|
"hook-deps-per-line": hookDepsPerLine,
|
|
8501
8980
|
|
|
8502
|
-
// If statement rules
|
|
8503
|
-
"if-statement-format": ifStatementFormat,
|
|
8504
|
-
"multiline-if-conditions": multilineIfConditions,
|
|
8505
|
-
|
|
8506
8981
|
// Import/Export rules
|
|
8507
8982
|
"absolute-imports-only": absoluteImportsOnly,
|
|
8508
8983
|
"export-format": exportFormat,
|
|
@@ -8521,37 +8996,24 @@ export default {
|
|
|
8521
8996
|
"jsx-simple-element-one-line": jsxSimpleElementOneLine,
|
|
8522
8997
|
"jsx-string-value-trim": jsxStringValueTrim,
|
|
8523
8998
|
"jsx-ternary-format": jsxTernaryFormat,
|
|
8524
|
-
|
|
8525
|
-
// Member expression rules
|
|
8526
|
-
"member-expression-bracket-spacing": memberExpressionBracketSpacing,
|
|
8527
|
-
|
|
8528
|
-
// Function arguments formatting rule
|
|
8529
|
-
"function-arguments-format": functionArgumentsFormat,
|
|
8530
|
-
|
|
8531
|
-
// Nested call rules
|
|
8532
|
-
"nested-call-closing-brackets": nestedCallClosingBrackets,
|
|
8533
|
-
|
|
8534
|
-
// No empty lines rules
|
|
8535
|
-
"no-empty-lines-in-function-calls": noEmptyLinesInFunctionCalls,
|
|
8536
|
-
"no-empty-lines-in-function-params": noEmptyLinesInFunctionParams,
|
|
8537
8999
|
"no-empty-lines-in-jsx": noEmptyLinesInJsx,
|
|
8538
|
-
"no-empty-lines-in-objects": noEmptyLinesInObjects,
|
|
8539
|
-
"no-empty-lines-in-switch-cases": noEmptyLinesInSwitchCases,
|
|
8540
9000
|
|
|
8541
|
-
// Object
|
|
9001
|
+
// Object rules
|
|
9002
|
+
"no-empty-lines-in-objects": noEmptyLinesInObjects,
|
|
8542
9003
|
"object-property-per-line": objectPropertyPerLine,
|
|
8543
9004
|
"object-property-value-brace": objectPropertyValueBrace,
|
|
8544
9005
|
"object-property-value-format": objectPropertyValueFormat,
|
|
9006
|
+
"string-property-spacing": stringPropertySpacing,
|
|
8545
9007
|
|
|
8546
|
-
//
|
|
8547
|
-
"
|
|
8548
|
-
|
|
8549
|
-
// Simple call/Single argument rules
|
|
8550
|
-
"simple-call-single-line": simpleCallSingleLine,
|
|
8551
|
-
"single-argument-on-one-line": singleArgumentOnOneLine,
|
|
9008
|
+
// Spacing rules
|
|
9009
|
+
"assignment-value-same-line": assignmentValueSameLine,
|
|
9010
|
+
"member-expression-bracket-spacing": memberExpressionBracketSpacing,
|
|
8552
9011
|
|
|
8553
|
-
//
|
|
8554
|
-
"
|
|
9012
|
+
// TypeScript rules
|
|
9013
|
+
"enum-format": enumFormat,
|
|
9014
|
+
"interface-format": interfaceFormat,
|
|
9015
|
+
"type-format": typeFormat,
|
|
9016
|
+
"typescript-definition-location": typescriptDefinitionLocation,
|
|
8555
9017
|
|
|
8556
9018
|
// Variable rules
|
|
8557
9019
|
"variable-naming-convention": variableNamingConvention,
|
package/package.json
CHANGED