eslint-plugin-code-style 1.6.0 → 1.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +47 -2
  2. package/index.js +221 -78
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,11 +7,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.6.4] - 2026-02-01
11
+
12
+ ### Enhanced
13
+
14
+ - **`function-object-destructure`** - Add more module paths: apis, configs, utilities, routes
15
+
16
+ ---
17
+
18
+ ## [1.6.3] - 2026-02-01
19
+
20
+ ### Fixed
21
+
22
+ - **`component-props-destructure`** - Preserve TypeScript type annotation when auto-fixing
23
+
24
+ ---
25
+
26
+ ## [1.6.2] - 2026-02-01
27
+
28
+ ### Enhanced
29
+
30
+ - **`function-object-destructure`** - Expand to check more module paths (services, constants, config, api, utils, helpers, lib) for dot notation enforcement
31
+
32
+ ---
33
+
34
+ ## [1.6.1] - 2026-02-01
35
+
36
+ ### Enhanced
37
+
38
+ - **`function-params-per-line`** - Handle callbacks with mixed params (destructured + simple like `({ item }, index)`)
39
+ - **`array-callback-destructure`** - Fix closing brace on same line as last property
40
+ - **`simple-call-single-line`** - Skip callbacks with 2+ params to avoid conflicts
41
+ - **`jsx-simple-element-one-line`** - Treat simple function calls (0-1 args) as simple expressions
42
+ - **`jsx-children-on-new-line`** - Treat simple function calls (0-1 args) as simple expressions
43
+ - **`jsx-element-child-new-line`** - Treat simple function calls (0-1 args) as simple expressions
44
+
45
+ ### Docs
46
+
47
+ - Clarify version bump and tag workflow in AGENTS.md
48
+
49
+ ---
50
+
10
51
  ## [1.6.0] - 2026-02-01
11
52
 
12
53
  **New array-callback-destructure Rule & Multiple Enhancements**
13
54
 
14
- **Version Range:** v1.5.2 → v1.6.0
55
+ **Version Range:** v1.5.1 → v1.6.0
15
56
 
16
57
  ### Added
17
58
 
@@ -33,7 +74,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
33
74
  - Auto-fixable: 60 rules 🔧
34
75
  - Report-only: 6 rules
35
76
 
36
- **Full Changelog:** [v1.5.2...v1.6.0](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.5.2...v1.6.0)
77
+ **Full Changelog:** [v1.5.1...v1.6.0](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.5.1...v1.6.0)
37
78
 
38
79
  ---
39
80
 
@@ -970,6 +1011,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
970
1011
 
971
1012
  ---
972
1013
 
1014
+ [1.6.4]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.3...v1.6.4
1015
+ [1.6.3]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.2...v1.6.3
1016
+ [1.6.2]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.1...v1.6.2
1017
+ [1.6.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.0...v1.6.1
973
1018
  [1.6.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.5.2...v1.6.0
974
1019
  [1.5.2]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.5.1...v1.5.2
975
1020
  [1.5.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.5.0...v1.5.1
package/index.js CHANGED
@@ -585,20 +585,20 @@ const arrayCallbackDestructure = {
585
585
  // Only enforce multiline when 2+ properties
586
586
  if (properties.length < 2) return;
587
587
 
588
- // Check if all properties are on the same line
589
588
  const firstProp = properties[0];
590
589
  const lastProp = properties[properties.length - 1];
590
+ const closeBrace = sourceCode.getLastToken(pattern);
591
591
 
592
- if (firstProp.loc.start.line === lastProp.loc.end.line) {
593
- // Calculate indent based on the call expression
594
- const callLine = sourceCode.lines[callNode.loc.start.line - 1];
595
- const baseIndent = callLine.match(/^\s*/)[0];
596
- const propIndent = baseIndent + " ";
592
+ // Calculate indent based on the call expression
593
+ const callLine = sourceCode.lines[callNode.loc.start.line - 1];
594
+ const baseIndent = callLine.match(/^\s*/)[0];
595
+ const propIndent = baseIndent + " ";
597
596
 
597
+ // Check if all properties are on the same line (need full reformat)
598
+ if (firstProp.loc.start.line === lastProp.loc.end.line) {
598
599
  context.report({
599
600
  fix(fixer) {
600
601
  const openBrace = sourceCode.getFirstToken(pattern);
601
- const closeBrace = sourceCode.getLastToken(pattern);
602
602
 
603
603
  const propsText = properties
604
604
  .map((prop) => propIndent + sourceCode.getText(prop))
@@ -606,12 +606,39 @@ const arrayCallbackDestructure = {
606
606
 
607
607
  return fixer.replaceTextRange(
608
608
  [openBrace.range[0], closeBrace.range[1]],
609
- `{\n${propsText},\n${baseIndent}}`,
609
+ `{\n${propsText},\n${baseIndent} }`,
610
610
  );
611
611
  },
612
612
  message: "Destructured properties in array callback should each be on their own line when there are 2 or more properties",
613
613
  node: pattern,
614
614
  });
615
+
616
+ return;
617
+ }
618
+
619
+ // Check if closing brace is on the same line as last property (needs fixing)
620
+ if (closeBrace.loc.start.line === lastProp.loc.end.line) {
621
+ context.report({
622
+ fix(fixer) {
623
+ // Add trailing comma if missing and move closing brace to new line
624
+ const tokenBeforeClose = sourceCode.getTokenBefore(closeBrace);
625
+ const hasTrailingComma = tokenBeforeClose.value === ",";
626
+
627
+ if (hasTrailingComma) {
628
+ return fixer.replaceTextRange(
629
+ [tokenBeforeClose.range[1], closeBrace.range[0]],
630
+ "\n" + baseIndent + " ",
631
+ );
632
+ }
633
+
634
+ return fixer.replaceTextRange(
635
+ [lastProp.range[1], closeBrace.range[0]],
636
+ ",\n" + baseIndent + " ",
637
+ );
638
+ },
639
+ message: "Closing brace should be on its own line in multiline destructuring",
640
+ node: closeBrace,
641
+ });
615
642
  }
616
643
  };
617
644
 
@@ -2694,67 +2721,66 @@ const functionParamsPerLine = {
2694
2721
  closeParen.range[0],
2695
2722
  );
2696
2723
 
2697
- // Callback arrow functions with 2+ simple params: each param on its own line
2698
- if (isCallback && params.length >= 2 && !hasComplexDestructuredParamHandler(params)) {
2699
- // Check if all params are simple identifiers (not destructuring)
2700
- const allSimpleParams = params.every((p) => p.type === "Identifier");
2724
+ // Callback arrow functions with 2+ params: each param on its own line
2725
+ // This handles both simple params (item, index) and mixed params ({ item }, index)
2726
+ if (isCallback && params.length >= 2) {
2727
+ // Calculate proper indent based on line's base indentation, not the paren column
2728
+ // This ensures consistent indentation for callbacks in method chains
2729
+ const lineText = sourceCode.lines[openParen.loc.start.line - 1];
2730
+ const lineIndent = lineText.match(/^(\s*)/)[1].length;
2731
+ const paramIndent = " ".repeat(lineIndent + 4);
2732
+ const parenIndent = " ".repeat(lineIndent);
2733
+
2734
+ // Check if first param is on same line as opening paren
2735
+ if (openParen.loc.end.line === firstParam.loc.start.line) {
2736
+ context.report({
2737
+ fix: (fixer) => fixer.replaceTextRange(
2738
+ [openParen.range[1], firstParam.range[0]],
2739
+ "\n" + paramIndent,
2740
+ ),
2741
+ message: "Callback arrow with 2+ params: first param should be on its own line",
2742
+ node: firstParam,
2743
+ });
2744
+ }
2745
+
2746
+ // Check if closing paren is on same line as last param
2747
+ if (closeParen.loc.start.line === lastParam.loc.end.line) {
2748
+ context.report({
2749
+ fix: (fixer) => fixer.replaceTextRange(
2750
+ [lastParam.range[1], closeParen.range[0]],
2751
+ ",\n" + parenIndent,
2752
+ ),
2753
+ message: "Callback arrow with 2+ params: closing paren should be on its own line",
2754
+ node: closeParen,
2755
+ });
2756
+ }
2757
+
2758
+ // Check each param is on its own line
2759
+ for (let i = 0; i < params.length - 1; i += 1) {
2760
+ const current = params[i];
2761
+ const next = params[i + 1];
2701
2762
 
2702
- if (allSimpleParams) {
2703
- // Calculate proper indent based on opening paren position
2704
- const paramIndent = " ".repeat(openParen.loc.start.column + 4);
2705
- const parenIndent = " ".repeat(openParen.loc.start.column);
2763
+ if (current.loc.end.line === next.loc.start.line) {
2764
+ const commaToken = sourceCode.getTokenAfter(
2765
+ current,
2766
+ (token) => token.value === ",",
2767
+ );
2706
2768
 
2707
- // Check if first param is on same line as opening paren
2708
- if (openParen.loc.end.line === firstParam.loc.start.line) {
2709
2769
  context.report({
2710
2770
  fix: (fixer) => fixer.replaceTextRange(
2711
- [openParen.range[1], firstParam.range[0]],
2771
+ [commaToken.range[1], next.range[0]],
2712
2772
  "\n" + paramIndent,
2713
2773
  ),
2714
- message: "Callback arrow with 2+ params: first param should be on its own line",
2715
- node: firstParam,
2716
- });
2717
- }
2718
-
2719
- // Check if closing paren is on same line as last param
2720
- if (closeParen.loc.start.line === lastParam.loc.end.line) {
2721
- context.report({
2722
- fix: (fixer) => fixer.replaceTextRange(
2723
- [lastParam.range[1], closeParen.range[0]],
2724
- ",\n" + parenIndent,
2725
- ),
2726
- message: "Callback arrow with 2+ params: closing paren should be on its own line",
2727
- node: closeParen,
2774
+ message: "Callback arrow with 2+ params: each param should be on its own line",
2775
+ node: next,
2728
2776
  });
2729
2777
  }
2730
-
2731
- // Check each param is on its own line
2732
- for (let i = 0; i < params.length - 1; i += 1) {
2733
- const current = params[i];
2734
- const next = params[i + 1];
2735
-
2736
- if (current.loc.end.line === next.loc.start.line) {
2737
- const commaToken = sourceCode.getTokenAfter(
2738
- current,
2739
- (token) => token.value === ",",
2740
- );
2741
-
2742
- context.report({
2743
- fix: (fixer) => fixer.replaceTextRange(
2744
- [commaToken.range[1], next.range[0]],
2745
- "\n" + paramIndent,
2746
- ),
2747
- message: "Callback arrow with 2+ params: each param should be on its own line",
2748
- node: next,
2749
- });
2750
- }
2751
- }
2752
-
2753
- return;
2754
2778
  }
2779
+
2780
+ return;
2755
2781
  }
2756
2782
 
2757
- // Skip other callback arrow functions (single param handled by opening-brackets-same-line)
2783
+ // Skip single-param callback arrow functions (handled by opening-brackets-same-line)
2758
2784
  if (isCallback) return;
2759
2785
 
2760
2786
  // 1-2 simple params without complex destructuring: keep on same line
@@ -5651,7 +5677,19 @@ const jsxChildrenOnNewLine = {
5651
5677
  create(context) {
5652
5678
  const sourceCode = context.sourceCode || context.getSourceCode();
5653
5679
 
5654
- // Check if expression is simple (identifier, literal, or member expression chain)
5680
+ // Check if an argument is simple
5681
+ const isSimpleArgHandler = (node) => {
5682
+ if (!node) return false;
5683
+ if (node.type === "Identifier") return true;
5684
+ if (node.type === "Literal") return true;
5685
+ if (node.type === "MemberExpression") {
5686
+ return node.object.type === "Identifier" && node.property.type === "Identifier";
5687
+ }
5688
+
5689
+ return false;
5690
+ };
5691
+
5692
+ // Check if expression is simple (identifier, literal, member expression chain, or simple function call)
5655
5693
  const isSimpleExpressionHandler = (expr) => {
5656
5694
  if (!expr) return true;
5657
5695
 
@@ -5677,6 +5715,22 @@ const jsxChildrenOnNewLine = {
5677
5715
  return current.type === "Identifier";
5678
5716
  }
5679
5717
 
5718
+ // Allow simple function calls with 0-1 simple arguments
5719
+ if (expr.type === "CallExpression") {
5720
+ const { callee } = expr;
5721
+ const isSimpleCallee = callee.type === "Identifier" ||
5722
+ (callee.type === "MemberExpression" &&
5723
+ callee.object.type === "Identifier" &&
5724
+ callee.property.type === "Identifier");
5725
+
5726
+ if (!isSimpleCallee) return false;
5727
+
5728
+ if (expr.arguments.length === 0) return true;
5729
+ if (expr.arguments.length === 1 && isSimpleArgHandler(expr.arguments[0])) return true;
5730
+
5731
+ return false;
5732
+ }
5733
+
5680
5734
  return false;
5681
5735
  };
5682
5736
 
@@ -5884,7 +5938,19 @@ const jsxElementChildNewLine = {
5884
5938
  create(context) {
5885
5939
  const sourceCode = context.sourceCode || context.getSourceCode();
5886
5940
 
5887
- // Check if expression is simple (identifier, literal, or member expression chain)
5941
+ // Check if an argument is simple
5942
+ const isSimpleArgHandler = (node) => {
5943
+ if (!node) return false;
5944
+ if (node.type === "Identifier") return true;
5945
+ if (node.type === "Literal") return true;
5946
+ if (node.type === "MemberExpression") {
5947
+ return node.object.type === "Identifier" && node.property.type === "Identifier";
5948
+ }
5949
+
5950
+ return false;
5951
+ };
5952
+
5953
+ // Check if expression is simple (identifier, literal, member expression chain, or simple function call)
5888
5954
  const isSimpleExpressionHandler = (expr) => {
5889
5955
  if (!expr) return true;
5890
5956
 
@@ -5910,6 +5976,22 @@ const jsxElementChildNewLine = {
5910
5976
  return current.type === "Identifier";
5911
5977
  }
5912
5978
 
5979
+ // Allow simple function calls with 0-1 simple arguments
5980
+ if (expr.type === "CallExpression") {
5981
+ const { callee } = expr;
5982
+ const isSimpleCallee = callee.type === "Identifier" ||
5983
+ (callee.type === "MemberExpression" &&
5984
+ callee.object.type === "Identifier" &&
5985
+ callee.property.type === "Identifier");
5986
+
5987
+ if (!isSimpleCallee) return false;
5988
+
5989
+ if (expr.arguments.length === 0) return true;
5990
+ if (expr.arguments.length === 1 && isSimpleArgHandler(expr.arguments[0])) return true;
5991
+
5992
+ return false;
5993
+ }
5994
+
5913
5995
  return false;
5914
5996
  };
5915
5997
 
@@ -6436,7 +6518,19 @@ const jsxSimpleElementOneLine = {
6436
6518
  create(context) {
6437
6519
  const sourceCode = context.sourceCode || context.getSourceCode();
6438
6520
 
6439
- // Check if an expression is simple (just an identifier or literal)
6521
+ // Check if an argument is simple (identifier, literal, or simple member expression)
6522
+ const isSimpleArgHandler = (node) => {
6523
+ if (!node) return false;
6524
+ if (node.type === "Identifier") return true;
6525
+ if (node.type === "Literal") return true;
6526
+ if (node.type === "MemberExpression") {
6527
+ return node.object.type === "Identifier" && node.property.type === "Identifier";
6528
+ }
6529
+
6530
+ return false;
6531
+ };
6532
+
6533
+ // Check if an expression is simple (identifier, literal, member expression, or simple function call)
6440
6534
  const isSimpleExpressionHandler = (node) => {
6441
6535
  if (!node) return false;
6442
6536
 
@@ -6447,6 +6541,24 @@ const jsxSimpleElementOneLine = {
6447
6541
  return node.object.type === "Identifier" && node.property.type === "Identifier";
6448
6542
  }
6449
6543
 
6544
+ // Allow simple function calls with 0-1 simple arguments
6545
+ if (node.type === "CallExpression") {
6546
+ // Check callee is simple (identifier or member expression)
6547
+ const { callee } = node;
6548
+ const isSimpleCallee = callee.type === "Identifier" ||
6549
+ (callee.type === "MemberExpression" &&
6550
+ callee.object.type === "Identifier" &&
6551
+ callee.property.type === "Identifier");
6552
+
6553
+ if (!isSimpleCallee) return false;
6554
+
6555
+ // Allow 0-1 arguments, and the argument must be simple
6556
+ if (node.arguments.length === 0) return true;
6557
+ if (node.arguments.length === 1 && isSimpleArgHandler(node.arguments[0])) return true;
6558
+
6559
+ return false;
6560
+ }
6561
+
6450
6562
  return false;
6451
6563
  };
6452
6564
 
@@ -11565,13 +11677,16 @@ const simpleCallSingleLine = {
11565
11677
  // Argument must be arrow function
11566
11678
  if (arg.type !== "ArrowFunctionExpression") return;
11567
11679
 
11680
+ // Callbacks with 2+ params should be multiline (handled by function-params-per-line)
11681
+ if (arg.params.length >= 2) return;
11682
+
11568
11683
  const { body } = arg;
11569
11684
 
11570
11685
  // Zero params: body must be a simple call/import
11571
11686
  if (arg.params.length === 0 && !isSimpleBodyHandler(body)) return;
11572
11687
 
11573
- // With params: body must be expression (not block) and simple
11574
- if (arg.params.length > 0) {
11688
+ // With single param: body must be expression (not block) and simple
11689
+ if (arg.params.length === 1) {
11575
11690
  if (body.type === "BlockStatement") return;
11576
11691
  if (!isSimpleExpressionHandler(body)) return;
11577
11692
  }
@@ -12299,15 +12414,32 @@ const functionObjectDestructure = {
12299
12414
  create(context) {
12300
12415
  const sourceCode = context.sourceCode || context.getSourceCode();
12301
12416
 
12302
- // Track imports from data-related paths (should use dot notation, not destructure)
12303
- const dataImports = new Set();
12417
+ // Track imports from module paths that should use dot notation, not destructure
12418
+ // This improves searchability: api.loginHandler is easier to find than loginHandler
12419
+ const moduleImports = new Set();
12304
12420
 
12305
- const isDataImportPath = (importPath) => {
12306
- // Match paths like @/data, ./data, ../data, or any path containing /data/
12307
- return importPath === "@/data"
12308
- || importPath.endsWith("/data")
12309
- || importPath.includes("/data/")
12310
- || /^\.\.?\/data$/.test(importPath);
12421
+ // Folders that contain modules which should be accessed via dot notation
12422
+ const modulePathPatterns = [
12423
+ "api",
12424
+ "apis",
12425
+ "config",
12426
+ "configs",
12427
+ "constants",
12428
+ "data",
12429
+ "helpers",
12430
+ "lib",
12431
+ "routes",
12432
+ "services",
12433
+ "utils",
12434
+ "utilities",
12435
+ ];
12436
+
12437
+ const isModuleImportPath = (importPath) => {
12438
+ // Match paths like @/services, @/constants, ./data, ../config, etc.
12439
+ return modulePathPatterns.some((pattern) => importPath === `@/${pattern}`
12440
+ || importPath.endsWith(`/${pattern}`)
12441
+ || importPath.includes(`/${pattern}/`)
12442
+ || new RegExp(`^\\.?\\.?/${pattern}$`).test(importPath));
12311
12443
  };
12312
12444
 
12313
12445
  const checkImportHandler = (node) => {
@@ -12315,13 +12447,13 @@ const functionObjectDestructure = {
12315
12447
 
12316
12448
  const importPath = node.source.value;
12317
12449
 
12318
- if (isDataImportPath(importPath)) {
12319
- // Track all imported specifiers from data paths
12450
+ if (isModuleImportPath(importPath)) {
12451
+ // Track all imported specifiers from module paths
12320
12452
  node.specifiers.forEach((spec) => {
12321
12453
  if (spec.type === "ImportSpecifier" && spec.local && spec.local.name) {
12322
- dataImports.add(spec.local.name);
12454
+ moduleImports.add(spec.local.name);
12323
12455
  } else if (spec.type === "ImportDefaultSpecifier" && spec.local && spec.local.name) {
12324
- dataImports.add(spec.local.name);
12456
+ moduleImports.add(spec.local.name);
12325
12457
  }
12326
12458
  });
12327
12459
  }
@@ -12352,7 +12484,7 @@ const functionObjectDestructure = {
12352
12484
  }
12353
12485
  }
12354
12486
 
12355
- if (sourceVarName && dataImports.has(sourceVarName)) {
12487
+ if (sourceVarName && moduleImports.has(sourceVarName)) {
12356
12488
  const destructuredProps = decl.id.properties
12357
12489
  .filter((p) => p.type === "Property" && p.key && p.key.name)
12358
12490
  .map((p) => p.key.name);
@@ -12360,7 +12492,7 @@ const functionObjectDestructure = {
12360
12492
  const sourceText = sourceCode.getText(decl.init);
12361
12493
 
12362
12494
  context.report({
12363
- message: `Do not destructure data imports. Use dot notation for searchability: "${sourceText}.${destructuredProps[0]}" instead of destructuring`,
12495
+ message: `Do not destructure module imports. Use dot notation for searchability: "${sourceText}.${destructuredProps[0]}" instead of destructuring`,
12364
12496
  node: decl.id,
12365
12497
  });
12366
12498
  }
@@ -13186,8 +13318,19 @@ const componentPropsDestructure = {
13186
13318
  ? (fixer) => {
13187
13319
  const fixes = [];
13188
13320
 
13321
+ // Build destructured pattern, preserving type annotation if present
13322
+ const destructuredPattern = `{ ${accessedProps.join(", ")} }`;
13323
+ let replacement = destructuredPattern;
13324
+
13325
+ // Preserve TypeScript type annotation if present
13326
+ if (firstParam.typeAnnotation) {
13327
+ const typeText = sourceCode.getText(firstParam.typeAnnotation);
13328
+
13329
+ replacement = `${destructuredPattern}${typeText}`;
13330
+ }
13331
+
13189
13332
  // Replace param with destructured pattern
13190
- fixes.push(fixer.replaceText(firstParam, `{ ${accessedProps.join(", ")} }`));
13333
+ fixes.push(fixer.replaceText(firstParam, replacement));
13191
13334
 
13192
13335
  // Replace all props.x with just x
13193
13336
  accesses.forEach((access) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-code-style",
3
- "version": "1.6.0",
3
+ "version": "1.6.4",
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",