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.
- package/CHANGELOG.md +18 -0
- package/index.js +252 -14
- 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
|
-
//
|
|
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 } =
|
|
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 } =
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
13314
|
-
const
|
|
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
|
|
13322
|
-
const
|
|
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