eslint-plugin-code-style 1.6.4 → 1.6.6

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 +18 -0
  2. package/index.js +252 -14
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.6.6] - 2026-02-01
11
+
12
+ ### Fixed
13
+
14
+ - **`component-props-destructure`** - Detect body destructuring patterns (e.g., `const { name } = data;`) even without type annotations
15
+ - **`component-props-destructure`** - Add auto-fix for body destructuring: moves props to parameter and removes body declaration
16
+
17
+ ---
18
+
19
+ ## [1.6.5] - 2026-02-01
20
+
21
+ ### Added
22
+
23
+ - **`function-object-destructure`** - Add auto-fix: replaces destructured usages with dot notation and removes declaration
24
+
25
+ ---
26
+
10
27
  ## [1.6.4] - 2026-02-01
11
28
 
12
29
  ### Enhanced
@@ -1011,6 +1028,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1011
1028
 
1012
1029
  ---
1013
1030
 
1031
+ [1.6.5]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.4...v1.6.5
1014
1032
  [1.6.4]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.3...v1.6.4
1015
1033
  [1.6.3]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.2...v1.6.3
1016
1034
  [1.6.2]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.6.1...v1.6.2
package/index.js CHANGED
@@ -12459,19 +12459,60 @@ const functionObjectDestructure = {
12459
12459
  }
12460
12460
  };
12461
12461
 
12462
- // Check for destructuring of data imports (not allowed)
12462
+ // Find all references to a variable name in a scope (with parent tracking)
12463
+ const findAllReferencesHandler = (scope, varName, declNode) => {
12464
+ const references = [];
12465
+
12466
+ const visitNode = (n, parent) => {
12467
+ if (!n || typeof n !== "object") return;
12468
+
12469
+ // Skip the declaration itself
12470
+ if (n === declNode) return;
12471
+
12472
+ // Found a reference
12473
+ if (n.type === "Identifier" && n.name === varName) {
12474
+ // Make sure it's not a property key or part of a member expression property
12475
+ const isMemberProp = parent && parent.type === "MemberExpression" && parent.property === n && !parent.computed;
12476
+ const isObjectKey = parent && parent.type === "Property" && parent.key === n && !parent.computed;
12477
+ const isShorthandValue = parent && parent.type === "Property" && parent.shorthand && parent.value === n;
12478
+
12479
+ // Include shorthand properties as references (they use the variable)
12480
+ if (!isMemberProp && !isObjectKey) {
12481
+ references.push(n);
12482
+ }
12483
+ }
12484
+
12485
+ for (const key of Object.keys(n)) {
12486
+ if (key === "parent" || key === "range" || key === "loc") continue;
12487
+
12488
+ const child = n[key];
12489
+
12490
+ if (Array.isArray(child)) {
12491
+ child.forEach((c) => visitNode(c, n));
12492
+ } else if (child && typeof child === "object" && child.type) {
12493
+ visitNode(child, n);
12494
+ }
12495
+ }
12496
+ };
12497
+
12498
+ visitNode(scope, null);
12499
+
12500
+ return references;
12501
+ };
12502
+
12503
+ // Check for destructuring of module imports (not allowed)
12463
12504
  const checkVariableDeclarationHandler = (node) => {
12464
12505
  for (const decl of node.declarations) {
12465
12506
  // Check for ObjectPattern destructuring
12466
12507
  if (decl.id.type === "ObjectPattern" && decl.init) {
12467
12508
  let sourceVarName = null;
12468
12509
 
12469
- // Direct destructuring: const { x } = dataImport
12510
+ // Direct destructuring: const { x } = moduleImport
12470
12511
  if (decl.init.type === "Identifier") {
12471
12512
  sourceVarName = decl.init.name;
12472
12513
  }
12473
12514
 
12474
- // Nested destructuring: const { x } = dataImport.nested
12515
+ // Nested destructuring: const { x } = moduleImport.nested
12475
12516
  if (decl.init.type === "MemberExpression") {
12476
12517
  let obj = decl.init;
12477
12518
 
@@ -12485,14 +12526,76 @@ const functionObjectDestructure = {
12485
12526
  }
12486
12527
 
12487
12528
  if (sourceVarName && moduleImports.has(sourceVarName)) {
12529
+ // Get destructured properties with their local names
12488
12530
  const destructuredProps = decl.id.properties
12489
12531
  .filter((p) => p.type === "Property" && p.key && p.key.name)
12490
- .map((p) => p.key.name);
12532
+ .map((p) => ({
12533
+ key: p.key.name,
12534
+ local: p.value && p.value.type === "Identifier" ? p.value.name : p.key.name,
12535
+ }));
12491
12536
 
12492
12537
  const sourceText = sourceCode.getText(decl.init);
12493
12538
 
12539
+ // Find the containing function/program to search for references
12540
+ let scope = node.parent;
12541
+
12542
+ while (scope && scope.type !== "BlockStatement" && scope.type !== "Program") {
12543
+ scope = scope.parent;
12544
+ }
12545
+
12494
12546
  context.report({
12495
- message: `Do not destructure module imports. Use dot notation for searchability: "${sourceText}.${destructuredProps[0]}" instead of destructuring`,
12547
+ fix: scope
12548
+ ? (fixer) => {
12549
+ const fixes = [];
12550
+
12551
+ // Replace all references with dot notation
12552
+ destructuredProps.forEach(({ key, local }) => {
12553
+ const refs = findAllReferencesHandler(scope, local, decl);
12554
+
12555
+ refs.forEach((ref) => {
12556
+ fixes.push(fixer.replaceText(ref, `${sourceText}.${key}`));
12557
+ });
12558
+ });
12559
+
12560
+ // Remove the entire declaration statement
12561
+ // If it's the only declaration in the statement, remove the whole statement
12562
+ if (node.declarations.length === 1) {
12563
+ // Find the full statement including newline
12564
+ const tokenBefore = sourceCode.getTokenBefore(node);
12565
+ const tokenAfter = sourceCode.getTokenAfter(node);
12566
+ let start = node.range[0];
12567
+ let end = node.range[1];
12568
+
12569
+ // Include leading whitespace/newline
12570
+ if (tokenBefore) {
12571
+ const textBetween = sourceCode.text.slice(tokenBefore.range[1], node.range[0]);
12572
+ const newlineIndex = textBetween.lastIndexOf("\n");
12573
+
12574
+ if (newlineIndex !== -1) {
12575
+ start = tokenBefore.range[1] + newlineIndex;
12576
+ }
12577
+ }
12578
+
12579
+ // Include trailing newline
12580
+ if (tokenAfter) {
12581
+ const textBetween = sourceCode.text.slice(node.range[1], tokenAfter.range[0]);
12582
+ const newlineIndex = textBetween.indexOf("\n");
12583
+
12584
+ if (newlineIndex !== -1) {
12585
+ end = node.range[1] + newlineIndex + 1;
12586
+ }
12587
+ }
12588
+
12589
+ fixes.push(fixer.removeRange([start, end]));
12590
+ } else {
12591
+ // Remove just this declarator
12592
+ fixes.push(fixer.remove(decl));
12593
+ }
12594
+
12595
+ return fixes;
12596
+ }
12597
+ : undefined,
12598
+ message: `Do not destructure module imports. Use dot notation for searchability: "${sourceText}.${destructuredProps[0].key}" instead of destructuring`,
12496
12599
  node: decl.id,
12497
12600
  });
12498
12601
  }
@@ -13275,6 +13378,66 @@ const componentPropsDestructure = {
13275
13378
  return accesses;
13276
13379
  };
13277
13380
 
13381
+ // Find body destructuring like: const { name } = data; or const { name, age } = data;
13382
+ const findBodyDestructuringHandler = (body, paramName) => {
13383
+ const results = [];
13384
+
13385
+ if (body.type !== "BlockStatement") return results;
13386
+
13387
+ for (const statement of body.body) {
13388
+ if (statement.type === "VariableDeclaration") {
13389
+ for (const declarator of statement.declarations) {
13390
+ // Check if it's destructuring from the param: const { x } = paramName
13391
+ if (
13392
+ declarator.id.type === "ObjectPattern" &&
13393
+ declarator.init &&
13394
+ declarator.init.type === "Identifier" &&
13395
+ declarator.init.name === paramName
13396
+ ) {
13397
+ const props = [];
13398
+
13399
+ for (const prop of declarator.id.properties) {
13400
+ if (prop.type === "Property" && prop.key.type === "Identifier") {
13401
+ // Handle both { name } and { name: alias }
13402
+ const keyName = prop.key.name;
13403
+ const valueName = prop.value.type === "Identifier" ? prop.value.name : null;
13404
+ const hasDefault = prop.value.type === "AssignmentPattern";
13405
+ let defaultValue = null;
13406
+
13407
+ if (hasDefault && prop.value.right) {
13408
+ defaultValue = sourceCode.getText(prop.value.right);
13409
+ }
13410
+
13411
+ props.push({
13412
+ default: defaultValue,
13413
+ hasAlias: keyName !== valueName && !hasDefault,
13414
+ key: keyName,
13415
+ value: hasDefault ? prop.value.left.name : valueName,
13416
+ });
13417
+ } else if (prop.type === "RestElement" && prop.argument.type === "Identifier") {
13418
+ props.push({
13419
+ isRest: true,
13420
+ key: prop.argument.name,
13421
+ value: prop.argument.name,
13422
+ });
13423
+ }
13424
+ }
13425
+
13426
+ results.push({
13427
+ declarator,
13428
+ props,
13429
+ statement,
13430
+ // Track if this is the only declarator in the statement
13431
+ statementHasOnlyThisDeclarator: statement.declarations.length === 1,
13432
+ });
13433
+ }
13434
+ }
13435
+ }
13436
+ }
13437
+
13438
+ return results;
13439
+ };
13440
+
13278
13441
  const checkComponentPropsHandler = (node) => {
13279
13442
  if (!isReactComponentHandler(node)) return;
13280
13443
 
@@ -13288,14 +13451,33 @@ const componentPropsDestructure = {
13288
13451
 
13289
13452
  if (firstParam.type === "Identifier") {
13290
13453
  const paramName = firstParam.name;
13454
+
13455
+ // Find dot notation accesses: props.name
13291
13456
  const accesses = findPropAccessesHandler(node.body, paramName);
13292
- const accessedProps = [...new Set(accesses.map((a) => a.property))];
13293
13457
 
13294
- // Check if param is used directly (not just via dot notation)
13458
+ // Find body destructuring: const { name } = props
13459
+ const bodyDestructures = findBodyDestructuringHandler(node.body, paramName);
13460
+
13461
+ // Collect all accessed props from dot notation
13462
+ const dotNotationProps = [...new Set(accesses.map((a) => a.property))];
13463
+
13464
+ // Collect all props from body destructuring
13465
+ const bodyDestructuredProps = [];
13466
+
13467
+ bodyDestructures.forEach((bd) => {
13468
+ bd.props.forEach((p) => {
13469
+ bodyDestructuredProps.push(p);
13470
+ });
13471
+ });
13472
+
13473
+ // Check if param is used anywhere that we can't handle
13295
13474
  const allRefs = [];
13296
- const countRefs = (n) => {
13475
+ const countRefs = (n, skipNodes = []) => {
13297
13476
  if (!n || typeof n !== "object") return;
13298
13477
 
13478
+ // Skip nodes we're already accounting for
13479
+ if (skipNodes.includes(n)) return;
13480
+
13299
13481
  if (n.type === "Identifier" && n.name === paramName) allRefs.push(n);
13300
13482
 
13301
13483
  for (const key of Object.keys(n)) {
@@ -13303,23 +13485,58 @@ const componentPropsDestructure = {
13303
13485
 
13304
13486
  const child = n[key];
13305
13487
 
13306
- if (Array.isArray(child)) child.forEach(countRefs);
13307
- else if (child && typeof child === "object" && child.type) countRefs(child);
13488
+ if (Array.isArray(child)) child.forEach((c) => countRefs(c, skipNodes));
13489
+ else if (child && typeof child === "object" && child.type) countRefs(child, skipNodes);
13308
13490
  }
13309
13491
  };
13310
13492
 
13311
13493
  countRefs(node.body);
13312
13494
 
13313
- // Can only auto-fix if all references are covered by dot notation accesses
13314
- const canAutoFix = accessedProps.length > 0 && allRefs.length === accesses.length;
13495
+ // Count expected refs: dot notation accesses + body destructuring init nodes
13496
+ const expectedRefCount = accesses.length + bodyDestructures.length;
13497
+
13498
+ // Can auto-fix if:
13499
+ // 1. We have either dot notation props OR body destructured props
13500
+ // 2. All references to the param are accounted for
13501
+ const hasSomeProps = dotNotationProps.length > 0 || bodyDestructuredProps.length > 0;
13502
+ const canAutoFix = hasSomeProps && allRefs.length === expectedRefCount;
13315
13503
 
13316
13504
  context.report({
13317
13505
  fix: canAutoFix
13318
13506
  ? (fixer) => {
13319
13507
  const fixes = [];
13320
13508
 
13321
- // Build destructured pattern, preserving type annotation if present
13322
- const destructuredPattern = `{ ${accessedProps.join(", ")} }`;
13509
+ // Build the destructured props list for the parameter
13510
+ const allProps = [];
13511
+
13512
+ // Add dot notation props (simple names)
13513
+ dotNotationProps.forEach((p) => {
13514
+ if (!allProps.some((ap) => ap.key === p)) {
13515
+ allProps.push({ key: p, simple: true });
13516
+ }
13517
+ });
13518
+
13519
+ // Add body destructured props (may have aliases, defaults, rest)
13520
+ bodyDestructuredProps.forEach((p) => {
13521
+ // Don't duplicate if already in dot notation
13522
+ if (!allProps.some((ap) => ap.key === p.key)) {
13523
+ allProps.push(p);
13524
+ }
13525
+ });
13526
+
13527
+ // Build the destructured pattern string
13528
+ const propStrings = allProps.map((p) => {
13529
+ if (p.isRest) return `...${p.key}`;
13530
+
13531
+ if (p.simple) return p.key;
13532
+
13533
+ if (p.default) return `${p.key} = ${p.default}`;
13534
+
13535
+ if (p.hasAlias) return `${p.key}: ${p.value}`;
13536
+
13537
+ return p.key;
13538
+ });
13539
+ const destructuredPattern = `{ ${propStrings.join(", ")} }`;
13323
13540
  let replacement = destructuredPattern;
13324
13541
 
13325
13542
  // Preserve TypeScript type annotation if present
@@ -13337,6 +13554,27 @@ const componentPropsDestructure = {
13337
13554
  fixes.push(fixer.replaceText(access.node, access.property));
13338
13555
  });
13339
13556
 
13557
+ // Remove body destructuring statements
13558
+ bodyDestructures.forEach((bd) => {
13559
+ if (bd.statementHasOnlyThisDeclarator) {
13560
+ // Remove the entire statement including newline
13561
+ const statementStart = bd.statement.range[0];
13562
+ let statementEnd = bd.statement.range[1];
13563
+
13564
+ // Try to also remove trailing newline/whitespace
13565
+ const textAfter = sourceCode.getText().slice(statementEnd, statementEnd + 2);
13566
+
13567
+ if (textAfter.startsWith("\n")) statementEnd += 1;
13568
+ else if (textAfter.startsWith("\r\n")) statementEnd += 2;
13569
+
13570
+ fixes.push(fixer.removeRange([statementStart, statementEnd]));
13571
+ } else {
13572
+ // Only remove this declarator from a multi-declarator statement
13573
+ // This is more complex - for now just remove the declarator text
13574
+ fixes.push(fixer.remove(bd.declarator));
13575
+ }
13576
+ });
13577
+
13340
13578
  return fixes;
13341
13579
  }
13342
13580
  : undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-code-style",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
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",