eslint-plugin-code-style 1.7.2 → 1.7.3

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 (4) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +236 -0
  3. package/index.js +184 -36
  4. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.7.3] - 2026-02-02
11
+
12
+ ### Fixed
13
+
14
+ - **`ternary-condition-multiline`** - Fix `?`/`:` on own line without value; collapse simple ternaries to single line when they fit
15
+ - **`no-empty-lines-in-function-params`** - Detect empty lines after opening `{` and before closing `}` in ObjectPattern params
16
+ - **`empty-line-after-block`** - Skip consecutive if statements (already handled by `if-else-spacing`)
17
+ - **`classname-multiline`** - Fix closing backtick alignment for return statements
18
+
19
+ ### Documentation
20
+
21
+ - Add 6 missing rules to README detailed documentation
22
+ - Add 7 missing rules to README Quick Start example
23
+ - Update rule counts from 66 to 69 across all documentation files
24
+ - Update AGENTS.md Tailwind section with actual rules and comparison with `tailwindcss/classnames-order`
25
+ - Add README multi-section update warnings to AGENTS.md rule modification checklists
26
+
27
+ ### Added
28
+
29
+ - **`manage-rule` skill** - New skill for adding, editing, or removing ESLint rules with complete workflow
30
+
31
+ ---
32
+
10
33
  ## [1.7.2] - 2026-02-02
11
34
 
12
35
  ### Fixed
package/README.md CHANGED
@@ -190,14 +190,18 @@ rules: {
190
190
  "code-style/arrow-function-simplify": "error",
191
191
  "code-style/assignment-value-same-line": "error",
192
192
  "code-style/block-statement-newlines": "error",
193
+ "code-style/class-naming-convention": "error",
193
194
  "code-style/classname-dynamic-at-end": "error",
194
195
  "code-style/classname-multiline": "error",
195
196
  "code-style/classname-no-extra-spaces": "error",
197
+ "code-style/classname-order": "error",
196
198
  "code-style/comment-format": "error",
197
199
  "code-style/component-props-destructure": "error",
198
200
  "code-style/component-props-inline-type": "error",
199
201
  "code-style/curried-arrow-same-line": "error",
202
+ "code-style/empty-line-after-block": "error",
200
203
  "code-style/enum-format": "error",
204
+ "code-style/enum-type-enforcement": "error",
201
205
  "code-style/export-format": "error",
202
206
  "code-style/function-arguments-format": "error",
203
207
  "code-style/function-call-spacing": "error",
@@ -212,6 +216,7 @@ rules: {
212
216
  "code-style/import-format": "error",
213
217
  "code-style/import-source-spacing": "error",
214
218
  "code-style/index-export-style": "error",
219
+ "code-style/index-exports-only": "error",
215
220
  "code-style/interface-format": "error",
216
221
  "code-style/jsx-children-on-new-line": "error",
217
222
  "code-style/jsx-closing-bracket-spacing": "error",
@@ -231,6 +236,7 @@ rules: {
231
236
  "code-style/no-empty-lines-in-jsx": "error",
232
237
  "code-style/no-empty-lines-in-objects": "error",
233
238
  "code-style/no-empty-lines-in-switch-cases": "error",
239
+ "code-style/no-inline-type-definitions": "error",
234
240
  "code-style/object-property-per-line": "error",
235
241
  "code-style/object-property-value-brace": "error",
236
242
  "code-style/object-property-value-format": "error",
@@ -239,6 +245,7 @@ rules: {
239
245
  "code-style/simple-call-single-line": "error",
240
246
  "code-style/single-argument-on-one-line": "error",
241
247
  "code-style/string-property-spacing": "error",
248
+ "code-style/ternary-condition-multiline": "error",
242
249
  "code-style/type-annotation-spacing": "error",
243
250
  "code-style/type-format": "error",
244
251
  "code-style/typescript-definition-location": "error",
@@ -881,6 +888,37 @@ dispatch(
881
888
 
882
889
  <br />
883
890
 
891
+ ## 🏛️ Class Rules
892
+
893
+ ### `class-naming-convention`
894
+
895
+ **What it does:** Enforces that class declarations must end with "Class" suffix. This distinguishes class definitions from other PascalCase names like React components or type definitions.
896
+
897
+ **Why use it:** Clear naming conventions prevent confusion between classes, components, and types. The "Class" suffix immediately identifies the construct.
898
+
899
+ ```javascript
900
+ // ✅ Good — class ends with "Class"
901
+ class ApiServiceClass {
902
+ constructor() {}
903
+ fetch() {}
904
+ }
905
+
906
+ class UserRepositoryClass {
907
+ save(user) {}
908
+ }
909
+
910
+ // ❌ Bad — missing "Class" suffix
911
+ class ApiService {
912
+ constructor() {}
913
+ }
914
+
915
+ class UserRepository {
916
+ save(user) {}
917
+ }
918
+ ```
919
+
920
+ <br />
921
+
884
922
  ## 🔀 Control Flow Rules
885
923
 
886
924
  ### `block-statement-newlines`
@@ -916,6 +954,38 @@ for (const item of items) { process(item);
916
954
 
917
955
  ---
918
956
 
957
+ ### `empty-line-after-block`
958
+
959
+ **What it does:** Requires an empty line between a closing brace `}` of a block statement (if, try, for, while, etc.) and the next statement, unless the next statement is part of the same construct (else, catch, finally).
960
+
961
+ **Why use it:** Visual separation between logical blocks improves code readability and makes the structure clearer.
962
+
963
+ > **Note:** Consecutive if statements are handled by `if-else-spacing` rule.
964
+
965
+ ```javascript
966
+ // ✅ Good — empty line after block
967
+ if (condition) {
968
+ doSomething();
969
+ }
970
+
971
+ const x = 1;
972
+
973
+ // ✅ Good — else is part of same construct (no empty line needed)
974
+ if (condition) {
975
+ doSomething();
976
+ } else {
977
+ doOther();
978
+ }
979
+
980
+ // ❌ Bad — no empty line after block
981
+ if (condition) {
982
+ doSomething();
983
+ }
984
+ const x = 1;
985
+ ```
986
+
987
+ ---
988
+
919
989
  ### `if-else-spacing`
920
990
 
921
991
  **What it does:** Enforces proper spacing between if statements and if-else chains:
@@ -1137,6 +1207,39 @@ switch (status) {
1137
1207
  }
1138
1208
  ```
1139
1209
 
1210
+ ---
1211
+
1212
+ ### `ternary-condition-multiline`
1213
+
1214
+ **What it does:** Enforces consistent ternary formatting:
1215
+ - Simple ternaries (≤3 operands in condition) collapse to single line if they fit
1216
+ - Complex ternaries (>3 operands) expand to multiline with each operand on its own line
1217
+
1218
+ **Why use it:** Long ternary conditions on a single line are hard to read. Breaking complex conditions into multiple lines makes them scannable.
1219
+
1220
+ **Options:**
1221
+
1222
+ | Option | Type | Default | Description |
1223
+ |--------|------|---------|-------------|
1224
+ | `maxOperands` | `integer` | `3` | Maximum operands to keep on single line |
1225
+ | `maxLineLength` | `integer` | `120` | Maximum line length for single-line ternaries |
1226
+
1227
+ ```javascript
1228
+ // ✅ Good — simple condition on single line
1229
+ const x = a && b && c ? "yes" : "no";
1230
+
1231
+ // ✅ Good — complex condition multiline (>3 operands)
1232
+ const style = variant === "ghost"
1233
+ || variant === "ghost-danger"
1234
+ || variant === "muted"
1235
+ || variant === "primary"
1236
+ ? "transparent"
1237
+ : "solid";
1238
+
1239
+ // ❌ Bad — complex condition crammed on one line
1240
+ const style = variant === "ghost" || variant === "ghost-danger" || variant === "muted" || variant === "primary" ? "transparent" : "solid";
1241
+ ```
1242
+
1140
1243
  <br />
1141
1244
 
1142
1245
  ## ⚡ Function Rules
@@ -1719,6 +1822,28 @@ export {
1719
1822
 
1720
1823
  ---
1721
1824
 
1825
+ ### `index-exports-only`
1826
+
1827
+ **What it does:** Index files (`index.ts`, `index.tsx`, `index.js`, `index.jsx`) should only contain imports and re-exports, not any code definitions. All definitions (types, interfaces, functions, variables, classes) should be moved to separate files.
1828
+
1829
+ **Why use it:** Index files should be "barrels" that aggregate exports from a module. Mixing definitions with re-exports makes the codebase harder to navigate and can cause circular dependency issues.
1830
+
1831
+ ```javascript
1832
+ // ✅ Good — index.ts with only imports and re-exports
1833
+ export { Button } from "./Button";
1834
+ export { helper } from "./utils";
1835
+ export type { ButtonProps } from "./types";
1836
+ export * from "./constants";
1837
+
1838
+ // ❌ Bad — index.ts with code definitions
1839
+ export type ButtonVariant = "primary" | "secondary"; // Move to types.ts
1840
+ export interface ButtonProps { ... } // Move to types.ts
1841
+ export const CONSTANT = "value"; // Move to constants.ts
1842
+ export function helper() { ... } // Move to utils.ts
1843
+ ```
1844
+
1845
+ ---
1846
+
1722
1847
  ### `module-index-exports`
1723
1848
 
1724
1849
  **What it does:** Ensures module folders have index files that export all their contents, creating a proper public API for each module.
@@ -1897,6 +2022,49 @@ const buttonClasses = ` flex items-center ${className} `;
1897
2022
 
1898
2023
  ---
1899
2024
 
2025
+ ### `classname-order`
2026
+
2027
+ **What it does:** Enforces Tailwind CSS class ordering in variables, object properties, and return statements. Uses smart detection to identify Tailwind class strings.
2028
+
2029
+ **Why use it:** This rule complements the official `tailwindcss/classnames-order` plugin by handling areas it doesn't cover:
2030
+ - **`tailwindcss/classnames-order`** — Handles JSX `className` attributes directly
2031
+ - **`classname-order`** — Handles class strings in variables, object properties, and return statements
2032
+
2033
+ Both rules should be enabled together for complete Tailwind class ordering coverage.
2034
+
2035
+ **Order enforced:** layout (flex, grid) → positioning → sizing (w, h) → spacing (p, m) → typography (text, font) → colors (bg, text) → effects (shadow, opacity) → transitions → states (hover, focus)
2036
+
2037
+ ```javascript
2038
+ // ✅ Good — classes in correct order (variable)
2039
+ const buttonClasses = "flex items-center px-4 py-2 text-white bg-blue-500 hover:bg-blue-600";
2040
+
2041
+ // ✅ Good — classes in correct order (object property)
2042
+ const variants = {
2043
+ primary: "flex items-center bg-blue-500 hover:bg-blue-600",
2044
+ secondary: "flex items-center bg-gray-500 hover:bg-gray-600",
2045
+ };
2046
+
2047
+ // ✅ Good — classes in correct order (return statement)
2048
+ const getInputStyles = () => {
2049
+ return "border-error text-error placeholder-error/50 focus:border-error";
2050
+ };
2051
+
2052
+ // ❌ Bad — hover state before base color (variable)
2053
+ const buttonClasses = "flex items-center hover:bg-blue-600 bg-blue-500";
2054
+
2055
+ // ❌ Bad — unordered classes (object property)
2056
+ const variants = {
2057
+ primary: "hover:bg-blue-600 bg-blue-500 flex items-center",
2058
+ };
2059
+
2060
+ // ❌ Bad — unordered classes (return statement)
2061
+ const getInputStyles = () => {
2062
+ return "focus:border-error text-error border-error";
2063
+ };
2064
+ ```
2065
+
2066
+ ---
2067
+
1900
2068
  ### `jsx-children-on-new-line`
1901
2069
 
1902
2070
  **What it does:** When a JSX element has multiple children, ensures each child is on its own line with proper indentation.
@@ -2696,6 +2864,38 @@ export enum UserStatusEnum {
2696
2864
 
2697
2865
  ---
2698
2866
 
2867
+ ### `enum-type-enforcement`
2868
+
2869
+ **What it does:** When a variable or parameter has a type ending in `Type` (like `ButtonVariantType`), enforces using the corresponding enum (`ButtonVariantEnum.VALUE`) instead of string literals.
2870
+
2871
+ **Why use it:** Using enum values instead of string literals provides type safety, autocompletion, and prevents typos. Changes to enum values automatically propagate.
2872
+
2873
+ ```javascript
2874
+ // ✅ Good — using enum values
2875
+ const Button = ({
2876
+ variant = ButtonVariantEnum.PRIMARY,
2877
+ }: {
2878
+ variant?: ButtonVariantType,
2879
+ }) => { ... };
2880
+
2881
+ if (variant === ButtonVariantEnum.GHOST) {
2882
+ // ...
2883
+ }
2884
+
2885
+ // ❌ Bad — using string literals
2886
+ const Button = ({
2887
+ variant = "primary", // Should use ButtonVariantEnum.PRIMARY
2888
+ }: {
2889
+ variant?: ButtonVariantType,
2890
+ }) => { ... };
2891
+
2892
+ if (variant === "ghost") { // Should use ButtonVariantEnum.GHOST
2893
+ // ...
2894
+ }
2895
+ ```
2896
+
2897
+ ---
2898
+
2699
2899
  ### `interface-format`
2700
2900
 
2701
2901
  **What it does:** Enforces consistent formatting for TypeScript interfaces:
@@ -2739,6 +2939,42 @@ export interface UserInterface {
2739
2939
 
2740
2940
  ---
2741
2941
 
2942
+ ### `no-inline-type-definitions`
2943
+
2944
+ **What it does:** Reports when function parameters have inline union types that are too complex (too many members or too long). These should be extracted to a named type in a types file.
2945
+
2946
+ **Why use it:** Complex inline types make function signatures hard to read. Named types are reusable, self-documenting, and easier to maintain.
2947
+
2948
+ **Options:**
2949
+
2950
+ | Option | Type | Default | Description |
2951
+ |--------|------|---------|-------------|
2952
+ | `maxUnionMembers` | `integer` | `2` | Maximum union members before requiring extraction |
2953
+ | `maxLength` | `integer` | `50` | Maximum character length before requiring extraction |
2954
+
2955
+ ```javascript
2956
+ // ✅ Good — type extracted to separate file
2957
+ // types.ts
2958
+ export type ButtonVariantType = "primary" | "muted" | "danger";
2959
+
2960
+ // Button.tsx
2961
+ import { ButtonVariantType } from "./types";
2962
+ export const Button = ({
2963
+ variant,
2964
+ }: {
2965
+ variant?: ButtonVariantType,
2966
+ }) => { ... };
2967
+
2968
+ // ❌ Bad — complex inline union type
2969
+ export const Button = ({
2970
+ variant,
2971
+ }: {
2972
+ variant?: "primary" | "muted" | "danger", // Extract to named type
2973
+ }) => { ... };
2974
+ ```
2975
+
2976
+ ---
2977
+
2742
2978
  ### `type-format`
2743
2979
 
2744
2980
  **What it does:** Enforces consistent formatting for TypeScript type aliases:
package/index.js CHANGED
@@ -4030,10 +4030,31 @@ const ternaryConditionMultiline = {
4030
4030
  return false;
4031
4031
  };
4032
4032
 
4033
+ // Check if ? or : is on its own line without its value
4034
+ const isOperatorOnOwnLineHandler = (node) => {
4035
+ const questionToken = sourceCode.getTokenAfter(node.test, (t) => t.value === "?");
4036
+ const colonToken = sourceCode.getTokenAfter(node.consequent, (t) => t.value === ":");
4037
+
4038
+ // Check if ? is on different line than consequent start
4039
+ if (questionToken && node.consequent.loc.start.line !== questionToken.loc.start.line) {
4040
+ return true;
4041
+ }
4042
+
4043
+ // Check if : is on different line than alternate start
4044
+ if (colonToken && node.alternate.loc.start.line !== colonToken.loc.start.line) {
4045
+ return true;
4046
+ }
4047
+
4048
+ return false;
4049
+ };
4050
+
4033
4051
  // Handle simple ternaries - collapse to single line if they fit
4034
4052
  const handleSimpleTernaryHandler = (node) => {
4035
- // Skip if already on single line
4036
- if (node.loc.start.line === node.loc.end.line) return false;
4053
+ const isOnSingleLine = node.loc.start.line === node.loc.end.line;
4054
+ const hasOperatorOnOwnLine = isOperatorOnOwnLineHandler(node);
4055
+
4056
+ // Skip if already on single line and no formatting issues
4057
+ if (isOnSingleLine && !hasOperatorOnOwnLine) return false;
4037
4058
 
4038
4059
  // Skip nested ternaries
4039
4060
  if (node.consequent.type === "ConditionalExpression" || node.alternate.type === "ConditionalExpression") {
@@ -4097,7 +4118,7 @@ const ternaryConditionMultiline = {
4097
4118
  const testEndLine = test.loc.end.line;
4098
4119
  const isMultiLine = testStartLine !== testEndLine;
4099
4120
 
4100
- // ≤maxOperands operands: keep condition on single line
4121
+ // ≤maxOperands operands: keep condition on single line, and try to collapse whole ternary
4101
4122
  if (operands.length <= maxOperands) {
4102
4123
  const firstOperandStartLine = operands[0].loc.start.line;
4103
4124
  const allOperandsStartOnSameLine = operands.every(
@@ -4108,29 +4129,82 @@ const ternaryConditionMultiline = {
4108
4129
  (op) => isBinaryExpressionSplitHandler(op),
4109
4130
  );
4110
4131
 
4111
- if (!allOperandsStartOnSameLine || hasSplitBinaryExpression) {
4112
- context.report({
4113
- fix: (fixer) => {
4114
- const buildSameLineHandler = (n) => {
4115
- if (n.type === "LogicalExpression" && !isParenthesizedHandler(n)) {
4116
- const leftText = buildSameLineHandler(n.left);
4117
- const rightText = buildSameLineHandler(n.right);
4132
+ // Check if ? or : is on its own line without its value
4133
+ const hasOperatorOnOwnLine = isOperatorOnOwnLineHandler(node);
4118
4134
 
4119
- return `${leftText} ${n.operator} ${rightText}`;
4120
- }
4135
+ // Check if ternary is multiline (could be collapsed)
4136
+ const isTernaryMultiline = node.loc.start.line !== node.loc.end.line;
4121
4137
 
4122
- if (n.type === "BinaryExpression" && isBinaryExpressionSplitHandler(n)) {
4123
- return buildBinaryExpressionSingleLineHandler(n);
4124
- }
4138
+ // Helper to build single line condition
4139
+ const buildSameLineHandler = (n) => {
4140
+ if (n.type === "LogicalExpression" && !isParenthesizedHandler(n)) {
4141
+ const leftText = buildSameLineHandler(n.left);
4142
+ const rightText = buildSameLineHandler(n.right);
4125
4143
 
4126
- return getSourceTextWithGroupsHandler(n);
4127
- };
4144
+ return `${leftText} ${n.operator} ${rightText}`;
4145
+ }
4128
4146
 
4129
- return fixer.replaceText(test, buildSameLineHandler(test));
4130
- },
4131
- message: `Ternary conditions with ≤${maxOperands} operands should be single line`,
4132
- node: test,
4133
- });
4147
+ if (n.type === "BinaryExpression" && isBinaryExpressionSplitHandler(n)) {
4148
+ return buildBinaryExpressionSingleLineHandler(n);
4149
+ }
4150
+
4151
+ return getSourceTextWithGroupsHandler(n);
4152
+ };
4153
+
4154
+ // Check if whole ternary can fit on one line
4155
+ const singleLineText = getTernarySingleLineHandler(node);
4156
+ const indent = getLineIndentHandler(node);
4157
+
4158
+ // Calculate prefix length for context
4159
+ let prefixLength = 0;
4160
+ const parent = node.parent;
4161
+
4162
+ if (parent && parent.type === "VariableDeclarator" && parent.init === node) {
4163
+ const varDecl = parent.parent;
4164
+ const declKeyword = varDecl ? sourceCode.getFirstToken(varDecl).value : "const";
4165
+ const varName = parent.id.name || sourceCode.getText(parent.id);
4166
+
4167
+ prefixLength = declKeyword.length + 1 + varName.length + 3;
4168
+ } else if (parent && parent.type === "AssignmentExpression" && parent.right === node) {
4169
+ const leftText = sourceCode.getText(parent.left);
4170
+
4171
+ prefixLength = leftText.length + 3;
4172
+ } else if (parent && parent.type === "Property" && parent.value === node) {
4173
+ const keyText = sourceCode.getText(parent.key);
4174
+
4175
+ prefixLength = keyText.length + 2;
4176
+ }
4177
+
4178
+ const totalLength = indent + prefixLength + singleLineText.length + 1;
4179
+ const canFitOnOneLine = totalLength <= maxLineLength;
4180
+
4181
+ // Skip if branches have complex objects
4182
+ const hasComplexBranches = hasComplexObjectHandler(node.consequent) || hasComplexObjectHandler(node.alternate);
4183
+
4184
+ // Skip nested ternaries
4185
+ const hasNestedTernary = node.consequent.type === "ConditionalExpression" || node.alternate.type === "ConditionalExpression";
4186
+
4187
+ // Determine if we need to fix anything
4188
+ const needsConditionFix = !allOperandsStartOnSameLine || hasSplitBinaryExpression;
4189
+ const needsTernaryCollapse = isTernaryMultiline && canFitOnOneLine && !hasComplexBranches && !hasNestedTernary;
4190
+ const needsOperatorFix = hasOperatorOnOwnLine;
4191
+
4192
+ if (needsConditionFix || needsTernaryCollapse || needsOperatorFix) {
4193
+ // If whole ternary can fit on one line, collapse it
4194
+ if (canFitOnOneLine && !hasComplexBranches && !hasNestedTernary) {
4195
+ context.report({
4196
+ fix: (fixer) => fixer.replaceText(node, singleLineText),
4197
+ message: `Ternary with ≤${maxOperands} operands should be on single line when it fits`,
4198
+ node,
4199
+ });
4200
+ } else if (needsConditionFix) {
4201
+ // Otherwise just fix the condition to be on single line
4202
+ context.report({
4203
+ fix: (fixer) => fixer.replaceText(test, buildSameLineHandler(test)),
4204
+ message: `Ternary conditions with ≤${maxOperands} operands should be single line`,
4205
+ node: test,
4206
+ });
4207
+ }
4134
4208
  }
4135
4209
 
4136
4210
  return;
@@ -4178,6 +4252,11 @@ const ternaryConditionMultiline = {
4178
4252
  isCorrectionNeeded = true;
4179
4253
  }
4180
4254
  }
4255
+
4256
+ // Check if ? or : is on its own line without its value
4257
+ if (!isCorrectionNeeded && isOperatorOnOwnLineHandler(node)) {
4258
+ isCorrectionNeeded = true;
4259
+ }
4181
4260
  }
4182
4261
 
4183
4262
  if (isCorrectionNeeded) {
@@ -4267,6 +4346,8 @@ const ternaryConditionMultiline = {
4267
4346
  * statement (if, try, for, while, etc.) and the next statement,
4268
4347
  * unless the next statement is part of the same construct (else, catch, finally).
4269
4348
  *
4349
+ * Note: Consecutive if statements are handled by if-else-spacing rule.
4350
+ *
4270
4351
  * ✓ Good:
4271
4352
  * if (condition) {
4272
4353
  * doSomething();
@@ -4347,6 +4428,11 @@ const emptyLineAfterBlock = {
4347
4428
  // Get the next statement
4348
4429
  const nextStmt = grandparent.body[stmtIndex + 1];
4349
4430
 
4431
+ // Skip consecutive if statements - handled by if-else-spacing rule
4432
+ if (parent.type === "IfStatement" && nextStmt.type === "IfStatement") {
4433
+ return;
4434
+ }
4435
+
4350
4436
  // Get the actual end of the current statement
4351
4437
  const currentEndLine = getStatementEndLineHandler(parent);
4352
4438
  const nextStartLine = nextStmt.loc.start.line;
@@ -7512,12 +7598,16 @@ const classNameNoExtraSpaces = {
7512
7598
  *
7513
7599
  * Description:
7514
7600
  * Enforce Tailwind CSS class ordering in class string variables,
7515
- * object properties, and return statements. Complements
7516
- * tailwindcss/classnames-order by handling cases it doesn't cover.
7601
+ * object properties, and return statements. This rule complements
7602
+ * tailwindcss/classnames-order by handling areas it doesn't cover.
7517
7603
  * Uses smart detection: checks if values look like Tailwind classes.
7518
7604
  *
7519
- * Note: This rule does NOT check JSX className attributes directly,
7520
- * as those should be handled by tailwindcss/classnames-order.
7605
+ * Coverage Division:
7606
+ * - tailwindcss/classnames-order: Handles JSX className attributes
7607
+ * - classname-order (this rule): Handles variables, object properties,
7608
+ * and return statements containing Tailwind class strings
7609
+ *
7610
+ * Both rules should be enabled together for complete coverage.
7521
7611
  *
7522
7612
  * ✓ Good:
7523
7613
  * const variants = { primary: "bg-blue-500 hover:bg-blue-600" };
@@ -7846,6 +7936,11 @@ const classNameMultiline = {
7846
7936
  return getLineIndent(current);
7847
7937
  }
7848
7938
 
7939
+ // For return statements, use the return keyword's indentation
7940
+ if (current.type === "ReturnStatement") {
7941
+ return getLineIndent(current);
7942
+ }
7943
+
7849
7944
  current = current.parent;
7850
7945
  }
7851
7946
 
@@ -9534,6 +9629,8 @@ const noEmptyLinesInFunctionCalls = {
9534
9629
  * Description:
9535
9630
  * Function parameter lists should not contain empty lines
9536
9631
  * between parameters or after opening/before closing parens.
9632
+ * Also checks inside ObjectPattern (destructuring) params for
9633
+ * empty lines after {, before }, or between properties.
9537
9634
  *
9538
9635
  * ✓ Good:
9539
9636
  * function test(
@@ -9541,12 +9638,23 @@ const noEmptyLinesInFunctionCalls = {
9541
9638
  * param2,
9542
9639
  * ) {}
9543
9640
  *
9641
+ * const Button = ({
9642
+ * children,
9643
+ * className,
9644
+ * }) => {};
9645
+ *
9544
9646
  * ✗ Bad:
9545
9647
  * function test(
9546
9648
  * param1,
9547
9649
  *
9548
9650
  * param2,
9549
9651
  * ) {}
9652
+ *
9653
+ * const Button = ({
9654
+ *
9655
+ * children,
9656
+ * className,
9657
+ * }) => {};
9550
9658
  */
9551
9659
  const noEmptyLinesInFunctionParams = {
9552
9660
  create(context) {
@@ -9618,24 +9726,64 @@ const noEmptyLinesInFunctionParams = {
9618
9726
 
9619
9727
  // Check inside ObjectPattern params for empty lines between destructured props
9620
9728
  params.forEach((param) => {
9621
- if (param.type === "ObjectPattern" && param.properties.length > 1) {
9622
- for (let i = 0; i < param.properties.length - 1; i += 1) {
9623
- const current = param.properties[i];
9624
- const next = param.properties[i + 1];
9729
+ if (param.type === "ObjectPattern" && param.properties.length > 0) {
9730
+ const firstProp = param.properties[0];
9731
+ const lastProp = param.properties[param.properties.length - 1];
9625
9732
 
9626
- if (next.loc.start.line - current.loc.end.line > 1) {
9627
- const commaToken = sourceCode.getTokenAfter(current, (t) => t.value === ",");
9733
+ // Find the opening brace of ObjectPattern
9734
+ const openBrace = sourceCode.getFirstToken(param);
9628
9735
 
9736
+ if (openBrace && openBrace.value === "{") {
9737
+ // Check for empty line after opening brace
9738
+ if (firstProp.loc.start.line - openBrace.loc.end.line > 1) {
9629
9739
  context.report({
9630
9740
  fix: (fixer) => fixer.replaceTextRange(
9631
- [commaToken.range[1], next.range[0]],
9632
- "\n" + " ".repeat(next.loc.start.column),
9741
+ [openBrace.range[1], firstProp.range[0]],
9742
+ "\n" + " ".repeat(firstProp.loc.start.column),
9633
9743
  ),
9634
- message: "No empty lines between destructured properties",
9635
- node: next,
9744
+ message: "No empty line after opening brace in destructuring",
9745
+ node: firstProp,
9746
+ });
9747
+ }
9748
+ }
9749
+
9750
+ // Find the closing brace of ObjectPattern
9751
+ const closeBrace = sourceCode.getLastToken(param);
9752
+
9753
+ if (closeBrace && closeBrace.value === "}") {
9754
+ // Check for empty line before closing brace
9755
+ if (closeBrace.loc.start.line - lastProp.loc.end.line > 1) {
9756
+ context.report({
9757
+ fix: (fixer) => fixer.replaceTextRange(
9758
+ [lastProp.range[1], closeBrace.range[0]],
9759
+ "\n" + " ".repeat(closeBrace.loc.start.column),
9760
+ ),
9761
+ message: "No empty line before closing brace in destructuring",
9762
+ node: lastProp,
9636
9763
  });
9637
9764
  }
9638
9765
  }
9766
+
9767
+ // Check for empty lines between properties
9768
+ if (param.properties.length > 1) {
9769
+ for (let i = 0; i < param.properties.length - 1; i += 1) {
9770
+ const current = param.properties[i];
9771
+ const next = param.properties[i + 1];
9772
+
9773
+ if (next.loc.start.line - current.loc.end.line > 1) {
9774
+ const commaToken = sourceCode.getTokenAfter(current, (t) => t.value === ",");
9775
+
9776
+ context.report({
9777
+ fix: (fixer) => fixer.replaceTextRange(
9778
+ [commaToken.range[1], next.range[0]],
9779
+ "\n" + " ".repeat(next.loc.start.column),
9780
+ ),
9781
+ message: "No empty lines between destructured properties",
9782
+ node: next,
9783
+ });
9784
+ }
9785
+ }
9786
+ }
9639
9787
  }
9640
9788
  });
9641
9789
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-code-style",
3
- "version": "1.7.2",
3
+ "version": "1.7.3",
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",