eslint-plugin-code-style 1.6.6 → 1.7.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 +38 -0
- package/README.md +12 -8
- package/index.d.ts +6 -0
- package/index.js +560 -73
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.7.0] - 2026-02-02
|
|
11
|
+
|
|
12
|
+
**New Rules for Blocks, Classes & Enum Enforcement + Multiple Enhancements**
|
|
13
|
+
|
|
14
|
+
**Version Range:** v1.6.1 → v1.7.0
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
**New Rules (3)**
|
|
19
|
+
- `empty-line-after-block` - Require empty line between closing `}` of block statement and next statement 🔧
|
|
20
|
+
- `class-naming-convention` - Enforce class declarations end with "Class" suffix 🔧
|
|
21
|
+
- `enum-type-enforcement` - Enforce using enum values instead of string literals for typed variables (e.g., `ButtonVariantEnum.PRIMARY` instead of `"primary"`) 🔧
|
|
22
|
+
|
|
23
|
+
### Enhanced
|
|
24
|
+
|
|
25
|
+
- **`ternary-condition-multiline`** - Now also collapses simple ternaries to single line when they fit within max line length (default: 120 chars). Added `maxLineLength` option.
|
|
26
|
+
- **`function-object-destructure`** - Add auto-fix (replaces destructured usages with dot notation), expand module paths (services, constants, config, api, utils, helpers, lib, apis, configs, utilities, routes)
|
|
27
|
+
- **`function-params-per-line`** - Handle callbacks with mixed params (destructured + simple like `({ item }, index)`)
|
|
28
|
+
- **`array-callback-destructure`** - Fix closing brace on same line as last property
|
|
29
|
+
- **`simple-call-single-line`** - Skip callbacks with 2+ params to avoid conflicts
|
|
30
|
+
- **`jsx-simple-element-one-line`**, **`jsx-children-on-new-line`**, **`jsx-element-child-new-line`** - Treat simple function calls (0-1 args) as simple expressions
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **`component-props-destructure`** - Detect body destructuring patterns even without type annotations, add auto-fix for body destructuring, preserve TypeScript type annotation when auto-fixing
|
|
35
|
+
|
|
36
|
+
### Stats
|
|
37
|
+
|
|
38
|
+
- Total Rules: 69 (was 66)
|
|
39
|
+
- Auto-fixable: 63 rules 🔧
|
|
40
|
+
- Report-only: 6 rules
|
|
41
|
+
|
|
42
|
+
**Full Changelog:** [v1.6.1...v1.7.0](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.1...v1.7.0)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
10
46
|
## [1.6.6] - 2026-02-01
|
|
11
47
|
|
|
12
48
|
### Fixed
|
|
@@ -1028,6 +1064,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1028
1064
|
|
|
1029
1065
|
---
|
|
1030
1066
|
|
|
1067
|
+
[1.7.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.6...v1.7.0
|
|
1068
|
+
[1.6.6]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.5...v1.6.6
|
|
1031
1069
|
[1.6.5]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.4...v1.6.5
|
|
1032
1070
|
[1.6.4]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.3...v1.6.4
|
|
1033
1071
|
[1.6.3]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.2...v1.6.3
|
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
|
-
*
|
|
22
|
+
*69 rules (63 auto-fixable) 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 **
|
|
30
|
+
This plugin provides **69 custom rules** (63 auto-fixable) 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 **66 custom rules** (60 auto-fixable) for code formatting.
|
|
|
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** —
|
|
39
|
+
- **Highly automated** — 63 of 69 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
|
|
63
|
+
- **Complete Coverage** — Combines ESLint built-in rules, third-party plugins, and all 69 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
|
-
**
|
|
100
|
+
**63 rules** support automatic fixing with `eslint --fix`. 6 rules are report-only (require manual changes).
|
|
101
101
|
|
|
102
102
|
</td>
|
|
103
103
|
<td width="50%">
|
|
@@ -252,7 +252,7 @@ rules: {
|
|
|
252
252
|
|
|
253
253
|
## 📖 Rules Categories
|
|
254
254
|
|
|
255
|
-
> **
|
|
255
|
+
> **69 rules total** — 63 with auto-fix 🔧, 6 report-only. See detailed examples in [Rules Reference](#-rules-reference) below.
|
|
256
256
|
>
|
|
257
257
|
> **Legend:** 🔧 Auto-fixable with `eslint --fix` • ⚙️ Customizable options
|
|
258
258
|
|
|
@@ -279,13 +279,16 @@ rules: {
|
|
|
279
279
|
| **Component Rules** | |
|
|
280
280
|
| `component-props-destructure` | Component props must be destructured `({ prop })` not received as `(props)` 🔧 |
|
|
281
281
|
| `component-props-inline-type` | Inline type annotation `} : {` with matching props, proper spacing, commas, no interface reference 🔧 |
|
|
282
|
+
| **Class Rules** | |
|
|
283
|
+
| `class-naming-convention` | Class declarations must end with "Class" suffix (e.g., `ApiServiceClass`) 🔧 |
|
|
282
284
|
| **Control Flow Rules** | |
|
|
283
285
|
| `block-statement-newlines` | Newline after `{` and before `}` in if/for/while/function blocks 🔧 |
|
|
286
|
+
| `empty-line-after-block` | Empty line required between closing `}` of block and next statement 🔧 |
|
|
284
287
|
| `if-else-spacing` | Empty line between consecutive if blocks, no empty line between single-line if/else 🔧 |
|
|
285
288
|
| `if-statement-format` | `{` on same line as `if`/`else if`, `else` on same line as `}`, proper spacing 🔧 |
|
|
286
289
|
| `multiline-if-conditions` | Conditions exceeding threshold get one operand per line with proper indentation (default: >3) 🔧 ⚙️ |
|
|
287
290
|
| `no-empty-lines-in-switch-cases` | No empty line after `case X:` before code, no empty lines between cases 🔧 |
|
|
288
|
-
| `ternary-condition-multiline` |
|
|
291
|
+
| `ternary-condition-multiline` | Collapse simple ternaries to single line; expand complex conditions (>3 operands) to multiline 🔧 ⚙️ |
|
|
289
292
|
| **Function Rules** | |
|
|
290
293
|
| `function-call-spacing` | No space between function name and `(`: `fn()` not `fn ()` 🔧 |
|
|
291
294
|
| `function-declaration-style` | Auto-fix for `func-style`: converts function declarations to arrow expressions 🔧 |
|
|
@@ -330,6 +333,7 @@ rules: {
|
|
|
330
333
|
| `member-expression-bracket-spacing` | No spaces inside brackets in computed member expressions: `arr[0]` not `arr[ 0 ]` 🔧 |
|
|
331
334
|
| **TypeScript Rules** | |
|
|
332
335
|
| `enum-format` | Enforce enum naming (PascalCase + Enum suffix), UPPER_CASE members, no empty lines, and trailing commas 🔧 |
|
|
336
|
+
| `enum-type-enforcement` | Enforce using enum values instead of string literals for variables typed with `*Type` (e.g., use `ButtonVariantEnum.PRIMARY` not `"primary"`) 🔧 |
|
|
333
337
|
| `interface-format` | Enforce interface naming (PascalCase + Interface suffix), camelCase properties, no empty lines, and trailing commas 🔧 |
|
|
334
338
|
| `no-inline-type-definitions` | Inline union types in function params should be extracted to named types ⚙️ |
|
|
335
339
|
| `type-annotation-spacing` | Enforce consistent spacing in type annotations: no space before colon/generic/array brackets, one space after colon 🔧 |
|
|
@@ -3066,7 +3070,7 @@ const UseAuth = () => {}; // hooks should be camelCase
|
|
|
3066
3070
|
|
|
3067
3071
|
## 🔧 Auto-fixing
|
|
3068
3072
|
|
|
3069
|
-
|
|
3073
|
+
63 of 69 rules support auto-fixing. Run ESLint with the `--fix` flag:
|
|
3070
3074
|
|
|
3071
3075
|
```bash
|
|
3072
3076
|
# Fix all files in src directory
|
package/index.d.ts
CHANGED
|
@@ -13,11 +13,14 @@ export type RuleNames =
|
|
|
13
13
|
| "code-style/arrow-function-simplify"
|
|
14
14
|
| "code-style/assignment-value-same-line"
|
|
15
15
|
| "code-style/block-statement-newlines"
|
|
16
|
+
| "code-style/class-naming-convention"
|
|
16
17
|
| "code-style/comment-format"
|
|
17
18
|
| "code-style/component-props-destructure"
|
|
18
19
|
| "code-style/react-code-order"
|
|
19
20
|
| "code-style/component-props-inline-type"
|
|
20
21
|
| "code-style/curried-arrow-same-line"
|
|
22
|
+
| "code-style/empty-line-after-block"
|
|
23
|
+
| "code-style/enum-type-enforcement"
|
|
21
24
|
| "code-style/export-format"
|
|
22
25
|
| "code-style/function-arguments-format"
|
|
23
26
|
| "code-style/function-call-spacing"
|
|
@@ -101,11 +104,14 @@ interface PluginRules {
|
|
|
101
104
|
"arrow-function-simplify": Rule.RuleModule;
|
|
102
105
|
"assignment-value-same-line": Rule.RuleModule;
|
|
103
106
|
"block-statement-newlines": Rule.RuleModule;
|
|
107
|
+
"class-naming-convention": Rule.RuleModule;
|
|
104
108
|
"comment-format": Rule.RuleModule;
|
|
105
109
|
"component-props-destructure": Rule.RuleModule;
|
|
106
110
|
"react-code-order": Rule.RuleModule;
|
|
107
111
|
"component-props-inline-type": Rule.RuleModule;
|
|
108
112
|
"curried-arrow-same-line": Rule.RuleModule;
|
|
113
|
+
"empty-line-after-block": Rule.RuleModule;
|
|
114
|
+
"enum-type-enforcement": Rule.RuleModule;
|
|
109
115
|
"export-format": Rule.RuleModule;
|
|
110
116
|
"function-arguments-format": Rule.RuleModule;
|
|
111
117
|
"function-call-spacing": Rule.RuleModule;
|
package/index.js
CHANGED
|
@@ -3890,6 +3890,7 @@ const ternaryConditionMultiline = {
|
|
|
3890
3890
|
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
3891
3891
|
const options = context.options[0] || {};
|
|
3892
3892
|
const maxOperands = options.maxOperands ?? 3;
|
|
3893
|
+
const maxLineLength = options.maxLineLength ?? 120;
|
|
3893
3894
|
|
|
3894
3895
|
// Check if node is wrapped in parentheses
|
|
3895
3896
|
const isParenthesizedHandler = (node) => {
|
|
@@ -3984,115 +3985,228 @@ const ternaryConditionMultiline = {
|
|
|
3984
3985
|
return false;
|
|
3985
3986
|
};
|
|
3986
3987
|
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3988
|
+
// Check if the test is a simple condition (not a complex logical expression)
|
|
3989
|
+
const isSimpleConditionHandler = (test) => {
|
|
3990
|
+
if (test.type === "Identifier") return true;
|
|
3990
3991
|
|
|
3991
|
-
|
|
3992
|
-
if (test.type !== "LogicalExpression") return;
|
|
3992
|
+
if (test.type === "MemberExpression") return true;
|
|
3993
3993
|
|
|
3994
|
-
|
|
3995
|
-
const testStartLine = test.loc.start.line;
|
|
3996
|
-
const testEndLine = test.loc.end.line;
|
|
3997
|
-
const isMultiLine = testStartLine !== testEndLine;
|
|
3994
|
+
if (test.type === "UnaryExpression") return true;
|
|
3998
3995
|
|
|
3999
|
-
|
|
4000
|
-
if (operands.length <= maxOperands) {
|
|
4001
|
-
const firstOperandStartLine = operands[0].loc.start.line;
|
|
4002
|
-
const allOperandsStartOnSameLine = operands.every(
|
|
4003
|
-
(op) => op.loc.start.line === firstOperandStartLine,
|
|
4004
|
-
);
|
|
3996
|
+
if (test.type === "BinaryExpression") return true;
|
|
4005
3997
|
|
|
4006
|
-
|
|
4007
|
-
(op) => isBinaryExpressionSplitHandler(op),
|
|
4008
|
-
);
|
|
3998
|
+
if (test.type === "CallExpression") return true;
|
|
4009
3999
|
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
if (n.type === "LogicalExpression" && !isParenthesizedHandler(n)) {
|
|
4015
|
-
const leftText = buildSameLineHandler(n.left);
|
|
4016
|
-
const rightText = buildSameLineHandler(n.right);
|
|
4000
|
+
// Logical expression with ≤maxOperands is still simple
|
|
4001
|
+
if (test.type === "LogicalExpression") {
|
|
4002
|
+
return collectOperandsHandler(test).length <= maxOperands;
|
|
4003
|
+
}
|
|
4017
4004
|
|
|
4018
|
-
|
|
4019
|
-
|
|
4005
|
+
return false;
|
|
4006
|
+
};
|
|
4020
4007
|
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4008
|
+
// Get the full ternary as single line text
|
|
4009
|
+
const getTernarySingleLineHandler = (node) => {
|
|
4010
|
+
const testText = sourceCode.getText(node.test).replace(/\s+/g, " ").trim();
|
|
4011
|
+
const consequentText = sourceCode.getText(node.consequent).replace(/\s+/g, " ").trim();
|
|
4012
|
+
const alternateText = sourceCode.getText(node.alternate).replace(/\s+/g, " ").trim();
|
|
4024
4013
|
|
|
4025
|
-
|
|
4026
|
-
|
|
4014
|
+
return `${testText} ? ${consequentText} : ${alternateText}`;
|
|
4015
|
+
};
|
|
4027
4016
|
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
node: test,
|
|
4032
|
-
});
|
|
4033
|
-
}
|
|
4017
|
+
// Get the indentation level for the line
|
|
4018
|
+
const getLineIndentHandler = (node) => {
|
|
4019
|
+
const lineText = sourceCode.lines[node.loc.start.line - 1];
|
|
4034
4020
|
|
|
4035
|
-
|
|
4036
|
-
|
|
4021
|
+
return lineText.match(/^\s*/)[0].length;
|
|
4022
|
+
};
|
|
4037
4023
|
|
|
4038
|
-
|
|
4039
|
-
|
|
4024
|
+
// Check if branches have complex objects (should stay multiline)
|
|
4025
|
+
const hasComplexObjectHandler = (n) => {
|
|
4026
|
+
if (n.type === "ObjectExpression" && n.properties.length >= 2) return true;
|
|
4040
4027
|
|
|
4041
|
-
|
|
4042
|
-
for (let i = 0; i < operands.length - 1; i += 1) {
|
|
4043
|
-
if (operands[i].loc.end.line === operands[i + 1].loc.start.line) {
|
|
4044
|
-
isCorrectionNeeded = true;
|
|
4045
|
-
break;
|
|
4046
|
-
}
|
|
4047
|
-
}
|
|
4028
|
+
if (n.type === "ArrayExpression" && n.elements.length >= 3) return true;
|
|
4048
4029
|
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4030
|
+
return false;
|
|
4031
|
+
};
|
|
4032
|
+
|
|
4033
|
+
// Handle simple ternaries - collapse to single line if they fit
|
|
4034
|
+
const handleSimpleTernaryHandler = (node) => {
|
|
4035
|
+
// Skip if already on single line
|
|
4036
|
+
if (node.loc.start.line === node.loc.end.line) return false;
|
|
4037
|
+
|
|
4038
|
+
// Skip nested ternaries
|
|
4039
|
+
if (node.consequent.type === "ConditionalExpression" || node.alternate.type === "ConditionalExpression") {
|
|
4040
|
+
return false;
|
|
4041
|
+
}
|
|
4042
|
+
|
|
4043
|
+
// Skip if branches have complex objects
|
|
4044
|
+
if (hasComplexObjectHandler(node.consequent) || hasComplexObjectHandler(node.alternate)) {
|
|
4045
|
+
return false;
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
// Calculate what the single line would look like
|
|
4049
|
+
const singleLineText = getTernarySingleLineHandler(node);
|
|
4050
|
+
const indent = getLineIndentHandler(node);
|
|
4051
|
+
|
|
4052
|
+
// Check if the parent needs prefix text (like "const x = ")
|
|
4053
|
+
let prefixLength = 0;
|
|
4054
|
+
const parent = node.parent;
|
|
4055
|
+
|
|
4056
|
+
if (parent && parent.type === "VariableDeclarator" && parent.init === node) {
|
|
4057
|
+
const declarationLine = sourceCode.lines[parent.loc.start.line - 1];
|
|
4058
|
+
const beforeTernary = declarationLine.slice(0, node.loc.start.column);
|
|
4059
|
+
|
|
4060
|
+
prefixLength = beforeTernary.length - indent;
|
|
4061
|
+
} else if (parent && parent.type === "AssignmentExpression" && parent.right === node) {
|
|
4062
|
+
const assignmentLine = sourceCode.lines[parent.loc.start.line - 1];
|
|
4063
|
+
const beforeTernary = assignmentLine.slice(0, node.loc.start.column);
|
|
4064
|
+
|
|
4065
|
+
prefixLength = beforeTernary.length - indent;
|
|
4066
|
+
}
|
|
4054
4067
|
|
|
4055
|
-
|
|
4068
|
+
// Check if single line would fit
|
|
4069
|
+
const totalLength = indent + prefixLength + singleLineText.length + 1;
|
|
4070
|
+
|
|
4071
|
+
if (totalLength <= maxLineLength) {
|
|
4072
|
+
context.report({
|
|
4073
|
+
fix: (fixer) => fixer.replaceText(node, singleLineText),
|
|
4074
|
+
message: "Simple ternary should be on a single line",
|
|
4075
|
+
node,
|
|
4076
|
+
});
|
|
4077
|
+
|
|
4078
|
+
return true;
|
|
4079
|
+
}
|
|
4080
|
+
|
|
4081
|
+
return false;
|
|
4082
|
+
};
|
|
4083
|
+
|
|
4084
|
+
// Handle complex logical expressions - format multiline
|
|
4085
|
+
const handleComplexLogicalTernaryHandler = (node) => {
|
|
4086
|
+
const { test } = node;
|
|
4087
|
+
const operands = collectOperandsHandler(test);
|
|
4088
|
+
const testStartLine = test.loc.start.line;
|
|
4089
|
+
const testEndLine = test.loc.end.line;
|
|
4090
|
+
const isMultiLine = testStartLine !== testEndLine;
|
|
4091
|
+
|
|
4092
|
+
// ≤maxOperands operands: keep condition on single line
|
|
4093
|
+
if (operands.length <= maxOperands) {
|
|
4094
|
+
const firstOperandStartLine = operands[0].loc.start.line;
|
|
4095
|
+
const allOperandsStartOnSameLine = operands.every(
|
|
4096
|
+
(op) => op.loc.start.line === firstOperandStartLine,
|
|
4097
|
+
);
|
|
4098
|
+
|
|
4099
|
+
const hasSplitBinaryExpression = operands.some(
|
|
4100
|
+
(op) => isBinaryExpressionSplitHandler(op),
|
|
4101
|
+
);
|
|
4102
|
+
|
|
4103
|
+
if (!allOperandsStartOnSameLine || hasSplitBinaryExpression) {
|
|
4056
4104
|
context.report({
|
|
4057
4105
|
fix: (fixer) => {
|
|
4058
|
-
|
|
4059
|
-
const lineText = sourceCode.lines[node.loc.start.line - 1];
|
|
4060
|
-
const baseIndent = lineText.match(/^\s*/)[0];
|
|
4061
|
-
const conditionIndent = baseIndent + " ";
|
|
4062
|
-
const branchIndent = baseIndent + " ";
|
|
4063
|
-
|
|
4064
|
-
const buildMultilineHandler = (n) => {
|
|
4106
|
+
const buildSameLineHandler = (n) => {
|
|
4065
4107
|
if (n.type === "LogicalExpression" && !isParenthesizedHandler(n)) {
|
|
4066
|
-
const leftText =
|
|
4067
|
-
const rightText =
|
|
4108
|
+
const leftText = buildSameLineHandler(n.left);
|
|
4109
|
+
const rightText = buildSameLineHandler(n.right);
|
|
4068
4110
|
|
|
4069
|
-
return `${leftText}
|
|
4111
|
+
return `${leftText} ${n.operator} ${rightText}`;
|
|
4112
|
+
}
|
|
4113
|
+
|
|
4114
|
+
if (n.type === "BinaryExpression" && isBinaryExpressionSplitHandler(n)) {
|
|
4115
|
+
return buildBinaryExpressionSingleLineHandler(n);
|
|
4070
4116
|
}
|
|
4071
4117
|
|
|
4072
4118
|
return getSourceTextWithGroupsHandler(n);
|
|
4073
4119
|
};
|
|
4074
4120
|
|
|
4075
|
-
|
|
4076
|
-
const alternateText = sourceCode.getText(node.alternate);
|
|
4077
|
-
|
|
4078
|
-
const newText = `\n${conditionIndent}${buildMultilineHandler(test)}\n${branchIndent}? ${consequentText}\n${branchIndent}: ${alternateText}`;
|
|
4079
|
-
|
|
4080
|
-
return fixer.replaceText(node, newText);
|
|
4121
|
+
return fixer.replaceText(test, buildSameLineHandler(test));
|
|
4081
4122
|
},
|
|
4082
|
-
message: `Ternary conditions with
|
|
4123
|
+
message: `Ternary conditions with ≤${maxOperands} operands should be single line`,
|
|
4083
4124
|
node: test,
|
|
4084
4125
|
});
|
|
4085
4126
|
}
|
|
4127
|
+
|
|
4128
|
+
return;
|
|
4129
|
+
}
|
|
4130
|
+
|
|
4131
|
+
// More than maxOperands: each on its own line
|
|
4132
|
+
let isCorrectionNeeded = !isMultiLine;
|
|
4133
|
+
|
|
4134
|
+
if (isMultiLine) {
|
|
4135
|
+
for (let i = 0; i < operands.length - 1; i += 1) {
|
|
4136
|
+
if (operands[i].loc.end.line === operands[i + 1].loc.start.line) {
|
|
4137
|
+
isCorrectionNeeded = true;
|
|
4138
|
+
break;
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
|
|
4142
|
+
// Check if any operator is at end of line (should be at beginning)
|
|
4143
|
+
if (!isCorrectionNeeded && hasOperatorAtEndOfLineHandler(test)) {
|
|
4144
|
+
isCorrectionNeeded = true;
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4148
|
+
if (isCorrectionNeeded) {
|
|
4149
|
+
context.report({
|
|
4150
|
+
fix: (fixer) => {
|
|
4151
|
+
// Get the indentation based on where the ternary starts
|
|
4152
|
+
const lineText = sourceCode.lines[node.loc.start.line - 1];
|
|
4153
|
+
const baseIndent = lineText.match(/^\s*/)[0];
|
|
4154
|
+
const conditionIndent = baseIndent + " ";
|
|
4155
|
+
const branchIndent = baseIndent + " ";
|
|
4156
|
+
|
|
4157
|
+
const buildMultilineHandler = (n) => {
|
|
4158
|
+
if (n.type === "LogicalExpression" && !isParenthesizedHandler(n)) {
|
|
4159
|
+
const leftText = buildMultilineHandler(n.left);
|
|
4160
|
+
const rightText = buildMultilineHandler(n.right);
|
|
4161
|
+
|
|
4162
|
+
return `${leftText}\n${conditionIndent}${n.operator} ${rightText}`;
|
|
4163
|
+
}
|
|
4164
|
+
|
|
4165
|
+
return getSourceTextWithGroupsHandler(n);
|
|
4166
|
+
};
|
|
4167
|
+
|
|
4168
|
+
const consequentText = sourceCode.getText(node.consequent);
|
|
4169
|
+
const alternateText = sourceCode.getText(node.alternate);
|
|
4170
|
+
|
|
4171
|
+
const newText = `\n${conditionIndent}${buildMultilineHandler(test)}\n${branchIndent}? ${consequentText}\n${branchIndent}: ${alternateText}`;
|
|
4172
|
+
|
|
4173
|
+
return fixer.replaceText(node, newText);
|
|
4174
|
+
},
|
|
4175
|
+
message: `Ternary conditions with more than ${maxOperands} operands should be multiline, with each operand on its own line`,
|
|
4176
|
+
node: test,
|
|
4177
|
+
});
|
|
4178
|
+
}
|
|
4179
|
+
};
|
|
4180
|
+
|
|
4181
|
+
return {
|
|
4182
|
+
ConditionalExpression(node) {
|
|
4183
|
+
const { test } = node;
|
|
4184
|
+
|
|
4185
|
+
// First, try to collapse simple ternaries to single line
|
|
4186
|
+
if (isSimpleConditionHandler(test)) {
|
|
4187
|
+
if (handleSimpleTernaryHandler(node)) return;
|
|
4188
|
+
}
|
|
4189
|
+
|
|
4190
|
+
// For complex logical expressions, handle multiline formatting
|
|
4191
|
+
if (test.type === "LogicalExpression") {
|
|
4192
|
+
handleComplexLogicalTernaryHandler(node);
|
|
4193
|
+
}
|
|
4086
4194
|
},
|
|
4087
4195
|
};
|
|
4088
4196
|
},
|
|
4089
4197
|
meta: {
|
|
4090
|
-
docs: { description: "Enforce
|
|
4198
|
+
docs: { description: "Enforce consistent ternary formatting: collapse simple ternaries to single line, expand complex conditions to multiline" },
|
|
4091
4199
|
fixable: "code",
|
|
4092
4200
|
schema: [
|
|
4093
4201
|
{
|
|
4094
4202
|
additionalProperties: false,
|
|
4095
4203
|
properties: {
|
|
4204
|
+
maxLineLength: {
|
|
4205
|
+
default: 120,
|
|
4206
|
+
description: "Maximum line length for single-line ternaries (default: 120)",
|
|
4207
|
+
minimum: 80,
|
|
4208
|
+
type: "integer",
|
|
4209
|
+
},
|
|
4096
4210
|
maxOperands: {
|
|
4097
4211
|
default: 3,
|
|
4098
4212
|
description: "Maximum operands to keep on single line (default: 3)",
|
|
@@ -4107,6 +4221,372 @@ const ternaryConditionMultiline = {
|
|
|
4107
4221
|
},
|
|
4108
4222
|
};
|
|
4109
4223
|
|
|
4224
|
+
/**
|
|
4225
|
+
* ───────────────────────────────────────────────────────────────
|
|
4226
|
+
* Rule: Empty Line After Block
|
|
4227
|
+
* ───────────────────────────────────────────────────────────────
|
|
4228
|
+
*
|
|
4229
|
+
* Description:
|
|
4230
|
+
* Require an empty line between a closing brace `}` of a block
|
|
4231
|
+
* statement (if, try, for, while, etc.) and the next statement,
|
|
4232
|
+
* unless the next statement is part of the same construct (else, catch, finally).
|
|
4233
|
+
*
|
|
4234
|
+
* ✓ Good:
|
|
4235
|
+
* if (condition) {
|
|
4236
|
+
* doSomething();
|
|
4237
|
+
* }
|
|
4238
|
+
*
|
|
4239
|
+
* const x = 1;
|
|
4240
|
+
*
|
|
4241
|
+
* ✗ Bad:
|
|
4242
|
+
* if (condition) {
|
|
4243
|
+
* doSomething();
|
|
4244
|
+
* }
|
|
4245
|
+
* const x = 1;
|
|
4246
|
+
*/
|
|
4247
|
+
const emptyLineAfterBlock = {
|
|
4248
|
+
create(context) {
|
|
4249
|
+
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
4250
|
+
|
|
4251
|
+
// Check if a node is a block-containing statement
|
|
4252
|
+
const isBlockStatementHandler = (node) => {
|
|
4253
|
+
const blockTypes = [
|
|
4254
|
+
"IfStatement",
|
|
4255
|
+
"ForStatement",
|
|
4256
|
+
"ForInStatement",
|
|
4257
|
+
"ForOfStatement",
|
|
4258
|
+
"WhileStatement",
|
|
4259
|
+
"DoWhileStatement",
|
|
4260
|
+
"TryStatement",
|
|
4261
|
+
"SwitchStatement",
|
|
4262
|
+
"WithStatement",
|
|
4263
|
+
];
|
|
4264
|
+
|
|
4265
|
+
return blockTypes.includes(node.type);
|
|
4266
|
+
};
|
|
4267
|
+
|
|
4268
|
+
// Get the actual end line of a statement (including else, catch, finally)
|
|
4269
|
+
const getStatementEndLineHandler = (node) => {
|
|
4270
|
+
if (node.type === "IfStatement" && node.alternate) {
|
|
4271
|
+
return getStatementEndLineHandler(node.alternate);
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4274
|
+
if (node.type === "TryStatement") {
|
|
4275
|
+
if (node.finalizer) return node.finalizer.loc.end.line;
|
|
4276
|
+
|
|
4277
|
+
if (node.handler) return node.handler.loc.end.line;
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4280
|
+
return node.loc.end.line;
|
|
4281
|
+
};
|
|
4282
|
+
|
|
4283
|
+
return {
|
|
4284
|
+
"BlockStatement:exit"(node) {
|
|
4285
|
+
const parent = node.parent;
|
|
4286
|
+
|
|
4287
|
+
// Only check for block-containing statements
|
|
4288
|
+
if (!parent || !isBlockStatementHandler(parent)) return;
|
|
4289
|
+
|
|
4290
|
+
// Skip if this block is followed by else, catch, or finally
|
|
4291
|
+
if (parent.type === "IfStatement" && parent.consequent === node && parent.alternate) {
|
|
4292
|
+
return;
|
|
4293
|
+
}
|
|
4294
|
+
|
|
4295
|
+
if (parent.type === "TryStatement" && (parent.block === node || parent.handler?.body === node) && (parent.handler || parent.finalizer)) {
|
|
4296
|
+
if (parent.block === node && (parent.handler || parent.finalizer)) return;
|
|
4297
|
+
|
|
4298
|
+
if (parent.handler?.body === node && parent.finalizer) return;
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4301
|
+
// Get the parent's container (the block that contains the parent statement)
|
|
4302
|
+
const grandparent = parent.parent;
|
|
4303
|
+
|
|
4304
|
+
if (!grandparent || grandparent.type !== "BlockStatement") return;
|
|
4305
|
+
|
|
4306
|
+
// Find the index of the parent statement in the grandparent's body
|
|
4307
|
+
const stmtIndex = grandparent.body.indexOf(parent);
|
|
4308
|
+
|
|
4309
|
+
if (stmtIndex === -1 || stmtIndex === grandparent.body.length - 1) return;
|
|
4310
|
+
|
|
4311
|
+
// Get the next statement
|
|
4312
|
+
const nextStmt = grandparent.body[stmtIndex + 1];
|
|
4313
|
+
|
|
4314
|
+
// Get the actual end of the current statement
|
|
4315
|
+
const currentEndLine = getStatementEndLineHandler(parent);
|
|
4316
|
+
const nextStartLine = nextStmt.loc.start.line;
|
|
4317
|
+
|
|
4318
|
+
// Check if there's an empty line between them
|
|
4319
|
+
if (nextStartLine - currentEndLine < 2) {
|
|
4320
|
+
context.report({
|
|
4321
|
+
fix: (fixer) => {
|
|
4322
|
+
const endToken = sourceCode.getLastToken(parent);
|
|
4323
|
+
|
|
4324
|
+
return fixer.insertTextAfter(endToken, "\n");
|
|
4325
|
+
},
|
|
4326
|
+
message: "Expected empty line after block statement",
|
|
4327
|
+
node: nextStmt,
|
|
4328
|
+
});
|
|
4329
|
+
}
|
|
4330
|
+
},
|
|
4331
|
+
};
|
|
4332
|
+
},
|
|
4333
|
+
meta: {
|
|
4334
|
+
docs: { description: "Require empty line between block statement closing brace and next statement" },
|
|
4335
|
+
fixable: "whitespace",
|
|
4336
|
+
schema: [],
|
|
4337
|
+
type: "layout",
|
|
4338
|
+
},
|
|
4339
|
+
};
|
|
4340
|
+
|
|
4341
|
+
/**
|
|
4342
|
+
* ───────────────────────────────────────────────────────────────
|
|
4343
|
+
* Rule: Class Naming Convention
|
|
4344
|
+
* ───────────────────────────────────────────────────────────────
|
|
4345
|
+
*
|
|
4346
|
+
* Description:
|
|
4347
|
+
* Enforce that class declarations must end with "Class" suffix.
|
|
4348
|
+
* This distinguishes class definitions from other PascalCase names
|
|
4349
|
+
* like React components or type definitions.
|
|
4350
|
+
*
|
|
4351
|
+
* ✓ Good:
|
|
4352
|
+
* class ApiServiceClass { ... }
|
|
4353
|
+
* class UserRepositoryClass { ... }
|
|
4354
|
+
*
|
|
4355
|
+
* ✗ Bad:
|
|
4356
|
+
* class ApiService { ... }
|
|
4357
|
+
* class UserRepository { ... }
|
|
4358
|
+
*/
|
|
4359
|
+
const classNamingConvention = {
|
|
4360
|
+
create(context) {
|
|
4361
|
+
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
4362
|
+
|
|
4363
|
+
return {
|
|
4364
|
+
ClassDeclaration(node) {
|
|
4365
|
+
if (!node.id || !node.id.name) return;
|
|
4366
|
+
|
|
4367
|
+
const className = node.id.name;
|
|
4368
|
+
|
|
4369
|
+
if (!className.endsWith("Class")) {
|
|
4370
|
+
context.report({
|
|
4371
|
+
fix: (fixer) => {
|
|
4372
|
+
const newName = `${className}Class`;
|
|
4373
|
+
|
|
4374
|
+
// Find all references to this class and rename them
|
|
4375
|
+
const scope = context.sourceCode.getScope
|
|
4376
|
+
? context.sourceCode.getScope(node)
|
|
4377
|
+
: context.getScope();
|
|
4378
|
+
const variable = scope.set.get(className);
|
|
4379
|
+
const fixes = [fixer.replaceText(node.id, newName)];
|
|
4380
|
+
|
|
4381
|
+
if (variable && variable.references) {
|
|
4382
|
+
variable.references.forEach((ref) => {
|
|
4383
|
+
if (ref.identifier !== node.id) {
|
|
4384
|
+
fixes.push(fixer.replaceText(ref.identifier, newName));
|
|
4385
|
+
}
|
|
4386
|
+
});
|
|
4387
|
+
}
|
|
4388
|
+
|
|
4389
|
+
return fixes;
|
|
4390
|
+
},
|
|
4391
|
+
message: `Class name "${className}" should end with "Class" suffix`,
|
|
4392
|
+
node: node.id,
|
|
4393
|
+
});
|
|
4394
|
+
}
|
|
4395
|
+
},
|
|
4396
|
+
};
|
|
4397
|
+
},
|
|
4398
|
+
meta: {
|
|
4399
|
+
docs: { description: "Enforce class names end with 'Class' suffix" },
|
|
4400
|
+
fixable: "code",
|
|
4401
|
+
schema: [],
|
|
4402
|
+
type: "suggestion",
|
|
4403
|
+
},
|
|
4404
|
+
};
|
|
4405
|
+
|
|
4406
|
+
/**
|
|
4407
|
+
* ───────────────────────────────────────────────────────────────
|
|
4408
|
+
* Rule: Enum Type Enforcement
|
|
4409
|
+
* ───────────────────────────────────────────────────────────────
|
|
4410
|
+
*
|
|
4411
|
+
* Description:
|
|
4412
|
+
* When a variable/parameter has a type like "ButtonVariantType",
|
|
4413
|
+
* enforce using the corresponding enum "ButtonVariantEnum.VALUE"
|
|
4414
|
+
* instead of string literals like "primary" or "ghost".
|
|
4415
|
+
*
|
|
4416
|
+
* The rule detects:
|
|
4417
|
+
* - Default values in destructuring: `variant = "primary"` → `variant = ButtonVariantEnum.PRIMARY`
|
|
4418
|
+
* - Comparisons: `variant === "ghost"` → `variant === ButtonVariantEnum.GHOST`
|
|
4419
|
+
* - Object property values matching the type
|
|
4420
|
+
*
|
|
4421
|
+
* ✓ Good:
|
|
4422
|
+
* const Button = ({ variant = ButtonVariantEnum.PRIMARY }: { variant?: ButtonVariantType }) => ...
|
|
4423
|
+
* if (variant === ButtonVariantEnum.GHOST) { ... }
|
|
4424
|
+
*
|
|
4425
|
+
* ✗ Bad:
|
|
4426
|
+
* const Button = ({ variant = "primary" }: { variant?: ButtonVariantType }) => ...
|
|
4427
|
+
* if (variant === "ghost") { ... }
|
|
4428
|
+
*/
|
|
4429
|
+
const enumTypeEnforcement = {
|
|
4430
|
+
create(context) {
|
|
4431
|
+
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
4432
|
+
|
|
4433
|
+
// Map to track variables with Type annotations and their corresponding Enum
|
|
4434
|
+
// e.g., "variant" -> { typeName: "ButtonVariantType", enumName: "ButtonVariantEnum" }
|
|
4435
|
+
const typeAnnotatedVars = new Map();
|
|
4436
|
+
|
|
4437
|
+
// Convert type name to enum name: ButtonVariantType -> ButtonVariantEnum
|
|
4438
|
+
const getEnumNameFromTypeHandler = (typeName) => {
|
|
4439
|
+
if (typeName.endsWith("Type")) {
|
|
4440
|
+
return typeName.slice(0, -4) + "Enum";
|
|
4441
|
+
}
|
|
4442
|
+
|
|
4443
|
+
return null;
|
|
4444
|
+
};
|
|
4445
|
+
|
|
4446
|
+
// Convert string literal to enum member: "primary" -> "PRIMARY", "ghost-danger" -> "GHOST_DANGER"
|
|
4447
|
+
const toEnumMemberHandler = (str) => str.toUpperCase().replace(/-/g, "_");
|
|
4448
|
+
|
|
4449
|
+
// Check if a type annotation references a Type that has a corresponding Enum
|
|
4450
|
+
const extractTypeInfoHandler = (typeAnnotation) => {
|
|
4451
|
+
if (!typeAnnotation) return null;
|
|
4452
|
+
|
|
4453
|
+
const annotation = typeAnnotation.typeAnnotation;
|
|
4454
|
+
|
|
4455
|
+
if (!annotation) return null;
|
|
4456
|
+
|
|
4457
|
+
// Handle direct type reference: : ButtonVariantType
|
|
4458
|
+
if (annotation.type === "TSTypeReference" && annotation.typeName?.type === "Identifier") {
|
|
4459
|
+
const typeName = annotation.typeName.name;
|
|
4460
|
+
|
|
4461
|
+
if (typeName.endsWith("Type")) {
|
|
4462
|
+
return {
|
|
4463
|
+
enumName: getEnumNameFromTypeHandler(typeName),
|
|
4464
|
+
typeName,
|
|
4465
|
+
};
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
|
|
4469
|
+
return null;
|
|
4470
|
+
};
|
|
4471
|
+
|
|
4472
|
+
// Track type-annotated parameters in function/component definitions
|
|
4473
|
+
const trackTypedParamsHandler = (params) => {
|
|
4474
|
+
params.forEach((param) => {
|
|
4475
|
+
// Handle destructured params: ({ variant }: { variant?: ButtonVariantType })
|
|
4476
|
+
if (param.type === "ObjectPattern" && param.typeAnnotation) {
|
|
4477
|
+
const annotation = param.typeAnnotation.typeAnnotation;
|
|
4478
|
+
|
|
4479
|
+
if (annotation && annotation.type === "TSTypeLiteral") {
|
|
4480
|
+
annotation.members.forEach((member) => {
|
|
4481
|
+
if (member.type === "TSPropertySignature" && member.key?.type === "Identifier") {
|
|
4482
|
+
const propName = member.key.name;
|
|
4483
|
+
const typeInfo = extractTypeInfoHandler(member);
|
|
4484
|
+
|
|
4485
|
+
if (typeInfo) {
|
|
4486
|
+
typeAnnotatedVars.set(propName, typeInfo);
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
});
|
|
4490
|
+
}
|
|
4491
|
+
}
|
|
4492
|
+
|
|
4493
|
+
// Handle simple typed param: (variant: ButtonVariantType)
|
|
4494
|
+
if (param.type === "Identifier" && param.typeAnnotation) {
|
|
4495
|
+
const typeInfo = extractTypeInfoHandler(param);
|
|
4496
|
+
|
|
4497
|
+
if (typeInfo) {
|
|
4498
|
+
typeAnnotatedVars.set(param.name, typeInfo);
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
});
|
|
4502
|
+
};
|
|
4503
|
+
|
|
4504
|
+
return {
|
|
4505
|
+
// Track function parameters
|
|
4506
|
+
"ArrowFunctionExpression, FunctionDeclaration, FunctionExpression"(node) {
|
|
4507
|
+
trackTypedParamsHandler(node.params);
|
|
4508
|
+
},
|
|
4509
|
+
|
|
4510
|
+
// Check default values in destructuring patterns
|
|
4511
|
+
AssignmentPattern(node) {
|
|
4512
|
+
// Pattern like: variant = "primary"
|
|
4513
|
+
if (node.left.type !== "Identifier") return;
|
|
4514
|
+
|
|
4515
|
+
const varName = node.left.name;
|
|
4516
|
+
const typeInfo = typeAnnotatedVars.get(varName);
|
|
4517
|
+
|
|
4518
|
+
if (!typeInfo) return;
|
|
4519
|
+
|
|
4520
|
+
// Check if the default is a string literal
|
|
4521
|
+
if (node.right.type === "Literal" && typeof node.right.value === "string") {
|
|
4522
|
+
const stringValue = node.right.value;
|
|
4523
|
+
const enumMember = toEnumMemberHandler(stringValue);
|
|
4524
|
+
const replacement = `${typeInfo.enumName}.${enumMember}`;
|
|
4525
|
+
|
|
4526
|
+
context.report({
|
|
4527
|
+
fix: (fixer) => fixer.replaceText(node.right, replacement),
|
|
4528
|
+
message: `Use "${replacement}" instead of string literal "${stringValue}"`,
|
|
4529
|
+
node: node.right,
|
|
4530
|
+
});
|
|
4531
|
+
}
|
|
4532
|
+
},
|
|
4533
|
+
|
|
4534
|
+
// Check comparisons: variant === "ghost"
|
|
4535
|
+
BinaryExpression(node) {
|
|
4536
|
+
if (node.operator !== "===" && node.operator !== "!==") return;
|
|
4537
|
+
|
|
4538
|
+
let varNode = null;
|
|
4539
|
+
let literalNode = null;
|
|
4540
|
+
|
|
4541
|
+
if (node.left.type === "Identifier" && node.right.type === "Literal") {
|
|
4542
|
+
varNode = node.left;
|
|
4543
|
+
literalNode = node.right;
|
|
4544
|
+
} else if (node.right.type === "Identifier" && node.left.type === "Literal") {
|
|
4545
|
+
varNode = node.right;
|
|
4546
|
+
literalNode = node.left;
|
|
4547
|
+
}
|
|
4548
|
+
|
|
4549
|
+
if (!varNode || !literalNode) return;
|
|
4550
|
+
|
|
4551
|
+
if (typeof literalNode.value !== "string") return;
|
|
4552
|
+
|
|
4553
|
+
const typeInfo = typeAnnotatedVars.get(varNode.name);
|
|
4554
|
+
|
|
4555
|
+
if (!typeInfo) return;
|
|
4556
|
+
|
|
4557
|
+
const stringValue = literalNode.value;
|
|
4558
|
+
const enumMember = toEnumMemberHandler(stringValue);
|
|
4559
|
+
const replacement = `${typeInfo.enumName}.${enumMember}`;
|
|
4560
|
+
|
|
4561
|
+
context.report({
|
|
4562
|
+
fix: (fixer) => fixer.replaceText(literalNode, replacement),
|
|
4563
|
+
message: `Use "${replacement}" instead of string literal "${stringValue}"`,
|
|
4564
|
+
node: literalNode,
|
|
4565
|
+
});
|
|
4566
|
+
},
|
|
4567
|
+
|
|
4568
|
+
// Clear tracked vars when exiting function scope
|
|
4569
|
+
"ArrowFunctionExpression:exit"() {
|
|
4570
|
+
typeAnnotatedVars.clear();
|
|
4571
|
+
},
|
|
4572
|
+
|
|
4573
|
+
"FunctionDeclaration:exit"() {
|
|
4574
|
+
typeAnnotatedVars.clear();
|
|
4575
|
+
},
|
|
4576
|
+
|
|
4577
|
+
"FunctionExpression:exit"() {
|
|
4578
|
+
typeAnnotatedVars.clear();
|
|
4579
|
+
},
|
|
4580
|
+
};
|
|
4581
|
+
},
|
|
4582
|
+
meta: {
|
|
4583
|
+
docs: { description: "Enforce using enum values instead of string literals for typed variables" },
|
|
4584
|
+
fixable: "code",
|
|
4585
|
+
schema: [],
|
|
4586
|
+
type: "suggestion",
|
|
4587
|
+
},
|
|
4588
|
+
};
|
|
4589
|
+
|
|
4110
4590
|
/**
|
|
4111
4591
|
* ───────────────────────────────────────────────────────────────
|
|
4112
4592
|
* Rule: Absolute Imports Only
|
|
@@ -16694,12 +17174,16 @@ export default {
|
|
|
16694
17174
|
|
|
16695
17175
|
// Control flow rules
|
|
16696
17176
|
"block-statement-newlines": blockStatementNewlines,
|
|
17177
|
+
"empty-line-after-block": emptyLineAfterBlock,
|
|
16697
17178
|
"if-else-spacing": ifElseSpacing,
|
|
16698
17179
|
"if-statement-format": ifStatementFormat,
|
|
16699
17180
|
"multiline-if-conditions": multilineIfConditions,
|
|
16700
17181
|
"no-empty-lines-in-switch-cases": noEmptyLinesInSwitchCases,
|
|
16701
17182
|
"ternary-condition-multiline": ternaryConditionMultiline,
|
|
16702
17183
|
|
|
17184
|
+
// Class rules
|
|
17185
|
+
"class-naming-convention": classNamingConvention,
|
|
17186
|
+
|
|
16703
17187
|
// Function rules
|
|
16704
17188
|
"function-call-spacing": functionCallSpacing,
|
|
16705
17189
|
"function-declaration-style": functionDeclarationStyle,
|
|
@@ -16756,6 +17240,9 @@ export default {
|
|
|
16756
17240
|
"type-format": typeFormat,
|
|
16757
17241
|
"typescript-definition-location": typescriptDefinitionLocation,
|
|
16758
17242
|
|
|
17243
|
+
// Type/Enum rules
|
|
17244
|
+
"enum-type-enforcement": enumTypeEnforcement,
|
|
17245
|
+
|
|
16759
17246
|
// Variable rules
|
|
16760
17247
|
"variable-naming-convention": variableNamingConvention,
|
|
16761
17248
|
},
|
package/package.json
CHANGED