@blumintinc/eslint-plugin-blumint 1.13.0 → 1.14.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/README.md +170 -139
- package/lib/index.js +190 -1
- package/lib/rules/array-methods-this-context.js +1 -1
- package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
- package/lib/rules/consistent-callback-naming.js +1 -1
- package/lib/rules/dynamic-https-errors.js +3 -4
- package/lib/rules/enforce-assert-safe-object-key.js +11 -11
- package/lib/rules/enforce-boolean-naming-prefixes.js +11 -23
- package/lib/rules/enforce-callback-memo.js +1 -1
- package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
- package/lib/rules/enforce-console-error.js +35 -7
- package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
- package/lib/rules/enforce-dynamic-file-naming.js +53 -28
- package/lib/rules/enforce-early-destructuring.d.ts +2 -0
- package/lib/rules/enforce-early-destructuring.js +980 -0
- package/lib/rules/enforce-empty-object-check.d.ts +11 -0
- package/lib/rules/enforce-empty-object-check.js +502 -0
- package/lib/rules/enforce-exported-function-types.js +1 -1
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
- package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
- package/lib/rules/enforce-firestore-facade.js +4 -2
- package/lib/rules/enforce-firestore-set-merge.js +3 -4
- package/lib/rules/enforce-global-constants.js +1 -1
- package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
- package/lib/rules/enforce-memoize-async.js +2 -6
- package/lib/rules/enforce-memoize-getters.js +39 -15
- package/lib/rules/enforce-microdiff.js +1 -1
- package/lib/rules/enforce-object-literal-as-const.js +4 -4
- package/lib/rules/enforce-positive-naming.js +4 -0
- package/lib/rules/enforce-props-argument-name.js +2 -2
- package/lib/rules/enforce-safe-stringify.js +1 -1
- package/lib/rules/enforce-serializable-params.js +3 -3
- package/lib/rules/enforce-singular-type-names.js +1 -1
- package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
- package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
- package/lib/rules/enforce-storage-context.d.ts +9 -0
- package/lib/rules/enforce-storage-context.js +646 -0
- package/lib/rules/enforce-timestamp-now.js +23 -7
- package/lib/rules/enforce-transform-memoization.d.ts +4 -0
- package/lib/rules/enforce-transform-memoization.js +416 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
- package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
- package/lib/rules/enforce-unique-cursor-headers.js +365 -0
- package/lib/rules/enforce-verb-noun-naming.js +5 -4
- package/lib/rules/ensure-pointer-events-none.js +28 -2
- package/lib/rules/export-if-in-doubt.js +27 -4
- package/lib/rules/extract-global-constants.js +40 -13
- package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
- package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
- package/lib/rules/flatten-push-calls.d.ts +2 -0
- package/lib/rules/flatten-push-calls.js +428 -0
- package/lib/rules/global-const-style.js +50 -19
- package/lib/rules/jsdoc-above-field.d.ts +11 -0
- package/lib/rules/jsdoc-above-field.js +208 -0
- package/lib/rules/key-only-outermost-element.js +21 -3
- package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
- package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
- package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
- package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
- package/lib/rules/memo-nested-react-components.d.ts +8 -0
- package/lib/rules/memo-nested-react-components.js +410 -0
- package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
- package/lib/rules/no-always-true-false-conditions.js +68 -8
- package/lib/rules/no-array-length-in-deps.js +9 -3
- package/lib/rules/no-async-foreach.js +210 -12
- package/lib/rules/no-circular-references.js +18 -17
- package/lib/rules/no-class-instance-destructuring.js +55 -12
- package/lib/rules/no-complex-cloud-params.js +7 -3
- package/lib/rules/no-compositing-layer-props.js +2 -2
- package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
- package/lib/rules/no-console-error.d.ts +9 -0
- package/lib/rules/no-console-error.js +525 -0
- package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
- package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
- package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
- package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
- package/lib/rules/no-entire-object-hook-deps.js +4 -6
- package/lib/rules/no-excessive-parent-chain.js +1 -1
- package/lib/rules/no-explicit-return-type.d.ts +2 -1
- package/lib/rules/no-explicit-return-type.js +215 -26
- package/lib/rules/no-filter-without-return.js +5 -1
- package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
- package/lib/rules/no-firestore-jest-mock.js +126 -8
- package/lib/rules/no-firestore-object-arrays.js +67 -12
- package/lib/rules/no-handler-suffix.d.ts +12 -0
- package/lib/rules/no-handler-suffix.js +305 -0
- package/lib/rules/no-hungarian.js +1 -1
- package/lib/rules/no-inline-component-prop.d.ts +10 -0
- package/lib/rules/no-inline-component-prop.js +456 -0
- package/lib/rules/no-jsx-in-hooks.js +6 -1
- package/lib/rules/no-jsx-whitespace-literal.js +8 -2
- package/lib/rules/no-margin-properties.js +6 -6
- package/lib/rules/no-memoize-on-static.js +9 -1
- package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
- package/lib/rules/no-misused-switch-case.js +22 -1
- package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
- package/lib/rules/no-mixed-firestore-transactions.js +296 -34
- package/lib/rules/no-mock-firebase-admin.js +5 -2
- package/lib/rules/no-object-values-on-strings.js +10 -2
- package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
- package/lib/rules/no-passthrough-getters.js +38 -2
- package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
- package/lib/rules/no-redundant-annotation-assertion.js +402 -0
- package/lib/rules/no-redundant-param-types.js +16 -6
- package/lib/rules/no-redundant-this-params.d.ts +3 -0
- package/lib/rules/no-redundant-this-params.js +459 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
- package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
- package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
- package/lib/rules/no-restricted-properties-fix.js +11 -10
- package/lib/rules/no-separate-loading-state.js +7 -16
- package/lib/rules/no-stale-state-across-await.js +1 -1
- package/lib/rules/no-type-assertion-returns.js +43 -49
- package/lib/rules/no-undefined-null-passthrough.js +61 -31
- package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
- package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
- package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
- package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
- package/lib/rules/no-unnecessary-destructuring.js +14 -4
- package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
- package/lib/rules/no-unpinned-dependencies.js +36 -5
- package/lib/rules/no-unused-props.d.ts +2 -2
- package/lib/rules/no-unused-props.js +295 -91
- package/lib/rules/no-unused-usestate.js +6 -2
- package/lib/rules/no-useless-fragment.js +28 -2
- package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
- package/lib/rules/no-useless-usememo-primitives.js +393 -0
- package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
- package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
- package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
- package/lib/rules/omit-index-html.d.ts +2 -1
- package/lib/rules/omit-index-html.js +62 -7
- package/lib/rules/optimize-object-boolean-conditions.js +6 -4
- package/lib/rules/parallelize-async-operations.js +24 -5
- package/lib/rules/prefer-batch-operations.d.ts +1 -3
- package/lib/rules/prefer-batch-operations.js +32 -5
- package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
- package/lib/rules/prefer-clone-deep.js +3 -3
- package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
- package/lib/rules/prefer-destructuring-no-class.js +244 -59
- package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
- package/lib/rules/prefer-docsetter-setall.js +243 -0
- package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
- package/lib/rules/prefer-fragment-component.js +1 -1
- package/lib/rules/prefer-fragment-shorthand.js +2 -1
- package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
- package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
- package/lib/rules/prefer-global-router-state-key.js +88 -13
- package/lib/rules/prefer-memoized-props.d.ts +3 -0
- package/lib/rules/prefer-memoized-props.js +445 -0
- package/lib/rules/prefer-next-dynamic.js +60 -69
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +9 -4
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
- package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
- package/lib/rules/prefer-params-over-parent-id.js +220 -171
- package/lib/rules/prefer-settings-object.js +2 -2
- package/lib/rules/prefer-type-over-interface.js +7 -2
- package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
- package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
- package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
- package/lib/rules/prevent-children-clobber.d.ts +2 -0
- package/lib/rules/prevent-children-clobber.js +536 -0
- package/lib/rules/react-memoize-literals.d.ts +4 -0
- package/lib/rules/react-memoize-literals.js +495 -0
- package/lib/rules/react-usememo-should-be-component.js +2 -2
- package/lib/rules/require-hooks-default-params.js +17 -16
- package/lib/rules/require-https-error-cause.d.ts +4 -0
- package/lib/rules/require-https-error-cause.js +136 -0
- package/lib/rules/require-https-error.js +43 -21
- package/lib/rules/require-image-optimized.js +1 -1
- package/lib/rules/require-memo.js +1 -1
- package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
- package/lib/rules/require-memoize-jsx-returners.js +485 -0
- package/lib/rules/require-usememo-object-literals.js +2 -3
- package/lib/rules/test-file-location-enforcement.js +1 -1
- package/lib/rules/use-latest-callback.js +6 -5
- package/lib/rules/vertically-group-related-functions.d.ts +16 -0
- package/lib/rules/vertically-group-related-functions.js +480 -0
- package/lib/utils/ASTHelpers.d.ts +13 -1
- package/lib/utils/ASTHelpers.js +24 -0
- package/lib/utils/getMethodName.d.ts +27 -0
- package/lib/utils/getMethodName.js +35 -0
- package/lib/utils/tsTypeClassifier.d.ts +30 -0
- package/lib/utils/tsTypeClassifier.js +149 -0
- package/package.json +3 -1
|
@@ -0,0 +1,980 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforceEarlyDestructuring = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const HOOK_NAMES = new Set([
|
|
7
|
+
'useEffect',
|
|
8
|
+
'useMemo',
|
|
9
|
+
'useCallback',
|
|
10
|
+
'useLayoutEffect',
|
|
11
|
+
]);
|
|
12
|
+
function isParenthesizedExpression(expression) {
|
|
13
|
+
return expression.type === 'ParenthesizedExpression';
|
|
14
|
+
}
|
|
15
|
+
function unwrapTsExpression(expression) {
|
|
16
|
+
let current = expression;
|
|
17
|
+
// Loop to peel off TS/paren wrappers that do not change the underlying value.
|
|
18
|
+
// The explicit loop keeps TypeScript aware that `current` always has an
|
|
19
|
+
// `.expression` property inside the branch.
|
|
20
|
+
// eslint-disable-next-line no-constant-condition -- Loop intentionally runs until wrapper nodes are fully unwrapped.
|
|
21
|
+
while (true) {
|
|
22
|
+
if (current.type === utils_1.AST_NODE_TYPES.TSNonNullExpression ||
|
|
23
|
+
current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
24
|
+
current.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
25
|
+
current.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
|
|
26
|
+
isParenthesizedExpression(current)) {
|
|
27
|
+
const nodeWithExpression = current;
|
|
28
|
+
current = nodeWithExpression.expression;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
return current;
|
|
34
|
+
}
|
|
35
|
+
function isFunctionNode(node) {
|
|
36
|
+
return (node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
37
|
+
node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression);
|
|
38
|
+
}
|
|
39
|
+
function isHookCall(node) {
|
|
40
|
+
const callee = node.callee;
|
|
41
|
+
if (callee.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
42
|
+
return null;
|
|
43
|
+
return HOOK_NAMES.has(callee.name) ? callee.name : null;
|
|
44
|
+
}
|
|
45
|
+
function isAllowedInit(init) {
|
|
46
|
+
const unwrapped = unwrapTsExpression(init);
|
|
47
|
+
if (unwrapped.type === utils_1.AST_NODE_TYPES.Identifier)
|
|
48
|
+
return true;
|
|
49
|
+
if (unwrapped.type === utils_1.AST_NODE_TYPES.MemberExpression)
|
|
50
|
+
return true;
|
|
51
|
+
if (unwrapped.type === utils_1.AST_NODE_TYPES.ChainExpression) {
|
|
52
|
+
const inner = unwrapTsExpression(unwrapped.expression);
|
|
53
|
+
return inner.type === utils_1.AST_NODE_TYPES.MemberExpression;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
function getBaseIdentifier(init) {
|
|
58
|
+
const unwrapped = unwrapTsExpression(init);
|
|
59
|
+
if (unwrapped.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
60
|
+
return unwrapped.name;
|
|
61
|
+
}
|
|
62
|
+
if (unwrapped.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
63
|
+
let current = unwrapTsExpression(unwrapped.object);
|
|
64
|
+
while (current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
65
|
+
current = unwrapTsExpression(current.object);
|
|
66
|
+
}
|
|
67
|
+
if (current.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
68
|
+
return current.name;
|
|
69
|
+
}
|
|
70
|
+
if (current.type === utils_1.AST_NODE_TYPES.ChainExpression) {
|
|
71
|
+
return getBaseIdentifier(current.expression);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (unwrapped.type === utils_1.AST_NODE_TYPES.ChainExpression) {
|
|
75
|
+
return getBaseIdentifier(unwrapped.expression);
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function addNameIfAbsent(name, names, orderedNames) {
|
|
80
|
+
if (names.has(name))
|
|
81
|
+
return;
|
|
82
|
+
names.add(name);
|
|
83
|
+
orderedNames.push(name);
|
|
84
|
+
}
|
|
85
|
+
function handleAssignmentPatternNames(node, names, orderedNames) {
|
|
86
|
+
const left = node.left;
|
|
87
|
+
if (left.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
88
|
+
addNameIfAbsent(left.name, names, orderedNames);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (left.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
92
|
+
collectNamesFromPattern(left, names, orderedNames);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (left.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
|
|
96
|
+
collectNamesFromArrayPattern(left, names, orderedNames);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function handlePropertyNodeNames(property, names, orderedNames) {
|
|
100
|
+
const value = property.value;
|
|
101
|
+
if (value.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
102
|
+
addNameIfAbsent(value.name, names, orderedNames);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (value.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
|
|
106
|
+
handleAssignmentPatternNames(value, names, orderedNames);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (value.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
110
|
+
collectNamesFromPattern(value, names, orderedNames);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (value.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
|
|
114
|
+
collectNamesFromArrayPattern(value, names, orderedNames);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function handleRestElementNodeNames(rest, names, orderedNames) {
|
|
118
|
+
const argument = rest.argument;
|
|
119
|
+
if (argument.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
120
|
+
addNameIfAbsent(argument.name, names, orderedNames);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (argument.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
124
|
+
collectNamesFromPattern(argument, names, orderedNames);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (argument.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
|
|
128
|
+
collectNamesFromArrayPattern(argument, names, orderedNames);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function collectNamesFromPattern(pattern, names, orderedNames) {
|
|
132
|
+
for (const property of pattern.properties) {
|
|
133
|
+
if (property.type === utils_1.AST_NODE_TYPES.Property) {
|
|
134
|
+
handlePropertyNodeNames(property, names, orderedNames);
|
|
135
|
+
}
|
|
136
|
+
else if (property.type === utils_1.AST_NODE_TYPES.RestElement) {
|
|
137
|
+
handleRestElementNodeNames(property, names, orderedNames);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function collectNamesFromArrayPattern(pattern, names, orderedNames) {
|
|
142
|
+
for (const element of pattern.elements) {
|
|
143
|
+
if (!element)
|
|
144
|
+
continue;
|
|
145
|
+
if (element.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
146
|
+
addNameIfAbsent(element.name, names, orderedNames);
|
|
147
|
+
}
|
|
148
|
+
else if (element.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
|
|
149
|
+
handleAssignmentPatternNames(element, names, orderedNames);
|
|
150
|
+
}
|
|
151
|
+
else if (element.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
152
|
+
collectNamesFromPattern(element, names, orderedNames);
|
|
153
|
+
}
|
|
154
|
+
else if (element.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
|
|
155
|
+
collectNamesFromArrayPattern(element, names, orderedNames);
|
|
156
|
+
}
|
|
157
|
+
else if (element.type === utils_1.AST_NODE_TYPES.RestElement) {
|
|
158
|
+
handleRestElementNodeNames(element, names, orderedNames);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function collectBindingNamesFromBindingName(binding, names) {
|
|
163
|
+
if (binding.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
164
|
+
names.add(binding.name);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (binding.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
168
|
+
collectNamesFromPattern(binding, names, []);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
collectNamesFromArrayPattern(binding, names, []);
|
|
172
|
+
}
|
|
173
|
+
function collectBindingNamesFromParamLike(node, names) {
|
|
174
|
+
if (node.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
|
|
175
|
+
collectBindingNamesFromParamLike(node.left, names);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (node.type === utils_1.AST_NODE_TYPES.RestElement) {
|
|
179
|
+
collectBindingNamesFromParamLike(node.argument, names);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
collectBindingNamesFromBindingName(node, names);
|
|
183
|
+
}
|
|
184
|
+
function collectExistingBindings(callback, declarationsToRemove) {
|
|
185
|
+
const names = new Set();
|
|
186
|
+
for (const param of callback.params) {
|
|
187
|
+
if (param) {
|
|
188
|
+
collectBindingNamesFromParamLike(param, names);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (callback.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
192
|
+
return names;
|
|
193
|
+
}
|
|
194
|
+
for (const statement of callback.body.body) {
|
|
195
|
+
if (statement.type === utils_1.AST_NODE_TYPES.FunctionDeclaration && statement.id) {
|
|
196
|
+
names.add(statement.id.name);
|
|
197
|
+
}
|
|
198
|
+
if (statement.type === utils_1.AST_NODE_TYPES.VariableDeclaration &&
|
|
199
|
+
!declarationsToRemove.has(statement)) {
|
|
200
|
+
for (const declarator of statement.declarations) {
|
|
201
|
+
collectBindingNamesFromParamLike(declarator.id, names);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return names;
|
|
206
|
+
}
|
|
207
|
+
function collectCallbackLocalBindings(callback, declarationsToRemove, visitorKeys) {
|
|
208
|
+
const names = new Set();
|
|
209
|
+
const stack = [callback.body];
|
|
210
|
+
while (stack.length) {
|
|
211
|
+
const current = stack.pop();
|
|
212
|
+
if (!current)
|
|
213
|
+
continue;
|
|
214
|
+
if (declarationsToRemove.has(current)) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
|
|
218
|
+
if (current.id) {
|
|
219
|
+
names.add(current.id.name);
|
|
220
|
+
}
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (current.type === utils_1.AST_NODE_TYPES.ClassDeclaration) {
|
|
224
|
+
if (current.id) {
|
|
225
|
+
names.add(current.id.name);
|
|
226
|
+
}
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
230
|
+
current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (current.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
234
|
+
collectBindingNamesFromBindingName(current.id, names);
|
|
235
|
+
}
|
|
236
|
+
if (current.type === utils_1.AST_NODE_TYPES.CatchClause && current.param) {
|
|
237
|
+
collectBindingNamesFromBindingName(current.param, names);
|
|
238
|
+
}
|
|
239
|
+
const keys = visitorKeys[current.type] ?? [];
|
|
240
|
+
for (const key of keys) {
|
|
241
|
+
const value = current[key];
|
|
242
|
+
if (Array.isArray(value)) {
|
|
243
|
+
for (const child of value) {
|
|
244
|
+
if (child && typeof child === 'object') {
|
|
245
|
+
stack.push(child);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else if (value && typeof value === 'object') {
|
|
250
|
+
stack.push(value);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return names;
|
|
255
|
+
}
|
|
256
|
+
function collectBindingsInScope(scope) {
|
|
257
|
+
const names = new Set();
|
|
258
|
+
let current = scope;
|
|
259
|
+
while (current) {
|
|
260
|
+
for (const variable of current.variables) {
|
|
261
|
+
if (variable.identifiers.length === 0) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
names.add(variable.name);
|
|
265
|
+
}
|
|
266
|
+
current = current.upper ?? null;
|
|
267
|
+
}
|
|
268
|
+
return names;
|
|
269
|
+
}
|
|
270
|
+
function findInsertionStatement(node) {
|
|
271
|
+
let current = node;
|
|
272
|
+
while (current) {
|
|
273
|
+
const parent = current.parent;
|
|
274
|
+
if (!parent)
|
|
275
|
+
return null;
|
|
276
|
+
if (parent.type === utils_1.AST_NODE_TYPES.BlockStatement &&
|
|
277
|
+
parent.body.includes(current)) {
|
|
278
|
+
return current;
|
|
279
|
+
}
|
|
280
|
+
current = parent;
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
function bindingNamesOfDestructuringProperty(property) {
|
|
285
|
+
const names = new Set();
|
|
286
|
+
if (property.type === utils_1.AST_NODE_TYPES.Property) {
|
|
287
|
+
collectBindingNamesFromParamLike(property.value, names);
|
|
288
|
+
return names;
|
|
289
|
+
}
|
|
290
|
+
collectBindingNamesFromParamLike(property, names);
|
|
291
|
+
return names;
|
|
292
|
+
}
|
|
293
|
+
function collectRuntimeIdentifierReferences(root, visitorKeys, names) {
|
|
294
|
+
const stack = [root];
|
|
295
|
+
while (stack.length) {
|
|
296
|
+
const current = stack.pop();
|
|
297
|
+
if (!current)
|
|
298
|
+
continue;
|
|
299
|
+
if (current.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
300
|
+
isIdentifierReference(current)) {
|
|
301
|
+
names.add(current.name);
|
|
302
|
+
}
|
|
303
|
+
if (current.type === utils_1.AST_NODE_TYPES.TSNonNullExpression ||
|
|
304
|
+
current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
305
|
+
current.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
306
|
+
current.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
|
|
307
|
+
isParenthesizedExpression(current)) {
|
|
308
|
+
const nodeWithExpression = current;
|
|
309
|
+
stack.push(nodeWithExpression.expression);
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
const keys = visitorKeys[current.type] ?? [];
|
|
313
|
+
for (const key of keys) {
|
|
314
|
+
const value = current[key];
|
|
315
|
+
if (Array.isArray(value)) {
|
|
316
|
+
for (const child of value) {
|
|
317
|
+
if (child && typeof child === 'object') {
|
|
318
|
+
stack.push(child);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else if (value && typeof value === 'object') {
|
|
323
|
+
stack.push(value);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function collectPatternReferenceNames(node, visitorKeys, names) {
|
|
329
|
+
if (node.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
|
|
330
|
+
collectRuntimeIdentifierReferences(node.right, visitorKeys, names);
|
|
331
|
+
collectPatternReferenceNames(node.left, visitorKeys, names);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (node.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
335
|
+
for (const property of node.properties) {
|
|
336
|
+
if (property.type === utils_1.AST_NODE_TYPES.Property) {
|
|
337
|
+
if (property.computed) {
|
|
338
|
+
collectRuntimeIdentifierReferences(property.key, visitorKeys, names);
|
|
339
|
+
}
|
|
340
|
+
collectPatternReferenceNames(property.value, visitorKeys, names);
|
|
341
|
+
}
|
|
342
|
+
else if (property.type === utils_1.AST_NODE_TYPES.RestElement) {
|
|
343
|
+
collectPatternReferenceNames(property.argument, visitorKeys, names);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (node.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
|
|
349
|
+
for (const element of node.elements) {
|
|
350
|
+
if (!element)
|
|
351
|
+
continue;
|
|
352
|
+
collectPatternReferenceNames(element, visitorKeys, names);
|
|
353
|
+
}
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (node.type === utils_1.AST_NODE_TYPES.RestElement) {
|
|
357
|
+
collectPatternReferenceNames(node.argument, visitorKeys, names);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function referenceNamesOfDestructuringProperty(property, visitorKeys) {
|
|
361
|
+
const names = new Set();
|
|
362
|
+
if (property.type === utils_1.AST_NODE_TYPES.Property) {
|
|
363
|
+
if (property.computed) {
|
|
364
|
+
collectRuntimeIdentifierReferences(property.key, visitorKeys, names);
|
|
365
|
+
}
|
|
366
|
+
collectPatternReferenceNames(property.value, visitorKeys, names);
|
|
367
|
+
return names;
|
|
368
|
+
}
|
|
369
|
+
collectPatternReferenceNames(property, visitorKeys, names);
|
|
370
|
+
return names;
|
|
371
|
+
}
|
|
372
|
+
function collectProperties(pattern, sourceCode, visitorKeys, acc) {
|
|
373
|
+
for (const property of pattern.properties) {
|
|
374
|
+
const text = getSafePropertyText(property, sourceCode);
|
|
375
|
+
if (acc.has(text))
|
|
376
|
+
continue;
|
|
377
|
+
const keyText = property.type === utils_1.AST_NODE_TYPES.Property
|
|
378
|
+
? property.key.type === utils_1.AST_NODE_TYPES.Literal
|
|
379
|
+
? String(property.key.value)
|
|
380
|
+
: property.key.type === utils_1.AST_NODE_TYPES.Identifier
|
|
381
|
+
? property.key.name
|
|
382
|
+
: sourceCode.getText(property.key)
|
|
383
|
+
: `...${sourceCode.getText(property.argument)}`;
|
|
384
|
+
acc.set(text, {
|
|
385
|
+
key: keyText,
|
|
386
|
+
text,
|
|
387
|
+
order: property.range ? property.range[0] : acc.size,
|
|
388
|
+
bindingNames: bindingNamesOfDestructuringProperty(property),
|
|
389
|
+
referenceNames: referenceNamesOfDestructuringProperty(property, visitorKeys),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function renderArrayPatternWithDefaults(pattern, sourceCode) {
|
|
394
|
+
const elements = pattern.elements.map((element) => {
|
|
395
|
+
if (!element)
|
|
396
|
+
return '';
|
|
397
|
+
if (element.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
398
|
+
return sourceCode.getText(element);
|
|
399
|
+
}
|
|
400
|
+
if (element.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
|
|
401
|
+
const left = element.left;
|
|
402
|
+
const leftText = left.type === utils_1.AST_NODE_TYPES.ObjectPattern
|
|
403
|
+
? renderObjectPatternWithDefaults(left, sourceCode)
|
|
404
|
+
: left.type === utils_1.AST_NODE_TYPES.ArrayPattern
|
|
405
|
+
? renderArrayPatternWithDefaults(left, sourceCode)
|
|
406
|
+
: sourceCode.getText(left);
|
|
407
|
+
return `${leftText} = ${sourceCode.getText(element.right)}`;
|
|
408
|
+
}
|
|
409
|
+
if (element.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
410
|
+
const nested = renderObjectPatternWithDefaults(element, sourceCode);
|
|
411
|
+
return `${nested} = {}`;
|
|
412
|
+
}
|
|
413
|
+
if (element.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
|
|
414
|
+
const nested = renderArrayPatternWithDefaults(element, sourceCode);
|
|
415
|
+
return `${nested} = []`;
|
|
416
|
+
}
|
|
417
|
+
if (element.type === utils_1.AST_NODE_TYPES.RestElement) {
|
|
418
|
+
const argument = element.argument;
|
|
419
|
+
if (argument.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
420
|
+
return `...${renderObjectPatternWithDefaults(argument, sourceCode)}`;
|
|
421
|
+
}
|
|
422
|
+
if (argument.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
|
|
423
|
+
return `...${renderArrayPatternWithDefaults(argument, sourceCode)}`;
|
|
424
|
+
}
|
|
425
|
+
return `...${sourceCode.getText(argument)}`;
|
|
426
|
+
}
|
|
427
|
+
return sourceCode.getText(element);
|
|
428
|
+
});
|
|
429
|
+
return `[${elements.join(', ')}]`;
|
|
430
|
+
}
|
|
431
|
+
function formatPropertyText(property, sourceCode) {
|
|
432
|
+
if (property.type === utils_1.AST_NODE_TYPES.RestElement) {
|
|
433
|
+
return renderRestElementProperty(property, sourceCode);
|
|
434
|
+
}
|
|
435
|
+
if (property.shorthand) {
|
|
436
|
+
return sourceCode.getText(property);
|
|
437
|
+
}
|
|
438
|
+
const keyText = renderPropertyKey(property, sourceCode);
|
|
439
|
+
const value = property.value;
|
|
440
|
+
if (value.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
|
|
441
|
+
return renderPropertyWithAssignment(property, value, keyText, sourceCode);
|
|
442
|
+
}
|
|
443
|
+
if (value.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
444
|
+
const nested = renderObjectPatternWithDefaults(value, sourceCode);
|
|
445
|
+
return `${keyText}: ${nested} = {}`;
|
|
446
|
+
}
|
|
447
|
+
if (value.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
|
|
448
|
+
const nested = renderArrayPatternWithDefaults(value, sourceCode);
|
|
449
|
+
return `${keyText}: ${nested} = []`;
|
|
450
|
+
}
|
|
451
|
+
return `${keyText}: ${sourceCode.getText(value)}`;
|
|
452
|
+
}
|
|
453
|
+
function renderObjectProperty(property, sourceCode) {
|
|
454
|
+
return formatPropertyText(property, sourceCode);
|
|
455
|
+
}
|
|
456
|
+
function renderPropertyKey(property, sourceCode) {
|
|
457
|
+
return property.computed
|
|
458
|
+
? `[${sourceCode.getText(property.key)}]`
|
|
459
|
+
: sourceCode.getText(property.key);
|
|
460
|
+
}
|
|
461
|
+
function renderPropertyWithAssignment(property, value, keyText, sourceCode) {
|
|
462
|
+
const left = value.left;
|
|
463
|
+
if (!property.computed &&
|
|
464
|
+
property.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
465
|
+
left.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
466
|
+
property.key.name === left.name) {
|
|
467
|
+
return `${sourceCode.getText(property.key)} = ${sourceCode.getText(value.right)}`;
|
|
468
|
+
}
|
|
469
|
+
const leftText = renderPatternLeft(left, sourceCode);
|
|
470
|
+
return `${keyText}: ${leftText} = ${sourceCode.getText(value.right)}`;
|
|
471
|
+
}
|
|
472
|
+
function renderPatternLeft(node, sourceCode) {
|
|
473
|
+
if (node.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
474
|
+
return renderObjectPatternWithDefaults(node, sourceCode);
|
|
475
|
+
}
|
|
476
|
+
if (node.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
|
|
477
|
+
return renderArrayPatternWithDefaults(node, sourceCode);
|
|
478
|
+
}
|
|
479
|
+
return sourceCode.getText(node);
|
|
480
|
+
}
|
|
481
|
+
function renderRestElementProperty(property, sourceCode) {
|
|
482
|
+
const argument = property.argument;
|
|
483
|
+
if (argument.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
484
|
+
return `...${renderObjectPatternWithDefaults(argument, sourceCode)}`;
|
|
485
|
+
}
|
|
486
|
+
if (argument.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
|
|
487
|
+
return `...${renderArrayPatternWithDefaults(argument, sourceCode)}`;
|
|
488
|
+
}
|
|
489
|
+
return `...${sourceCode.getText(argument)}`;
|
|
490
|
+
}
|
|
491
|
+
function renderObjectPatternWithDefaults(pattern, sourceCode) {
|
|
492
|
+
const properties = pattern.properties.map((property) => {
|
|
493
|
+
if (property.type === utils_1.AST_NODE_TYPES.Property) {
|
|
494
|
+
return renderObjectProperty(property, sourceCode);
|
|
495
|
+
}
|
|
496
|
+
if (property.type === utils_1.AST_NODE_TYPES.RestElement) {
|
|
497
|
+
return renderRestElementProperty(property, sourceCode);
|
|
498
|
+
}
|
|
499
|
+
return sourceCode.getText(property);
|
|
500
|
+
});
|
|
501
|
+
return `{ ${properties.join(', ')} }`;
|
|
502
|
+
}
|
|
503
|
+
function getSafePropertyText(property, sourceCode) {
|
|
504
|
+
return formatPropertyText(property, sourceCode);
|
|
505
|
+
}
|
|
506
|
+
function dependencyElements(depsArray, sourceCode) {
|
|
507
|
+
return depsArray.elements
|
|
508
|
+
.filter((element) => Boolean(element))
|
|
509
|
+
.map((element) => sourceCode.getText(element));
|
|
510
|
+
}
|
|
511
|
+
function callbackUsesBaseIdentifier(callback, baseName, excludedDeclarations, excludedInits, visitorKeys) {
|
|
512
|
+
const stack = [callback.body];
|
|
513
|
+
while (stack.length) {
|
|
514
|
+
const current = stack.pop();
|
|
515
|
+
if (!current)
|
|
516
|
+
continue;
|
|
517
|
+
if (excludedDeclarations.has(current)) {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
if (excludedInits.has(current)) {
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (current.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
524
|
+
current.name === baseName &&
|
|
525
|
+
isIdentifierReference(current)) {
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
const keys = visitorKeys[current.type] ?? [];
|
|
529
|
+
for (const key of keys) {
|
|
530
|
+
const value = current[key];
|
|
531
|
+
if (Array.isArray(value)) {
|
|
532
|
+
for (const child of value) {
|
|
533
|
+
if (child && typeof child === 'object') {
|
|
534
|
+
stack.push(child);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
else if (value && typeof value === 'object') {
|
|
539
|
+
stack.push(value);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
function testContainsObjectMember(testNode, objectName, visitorKeys) {
|
|
546
|
+
let found = false;
|
|
547
|
+
const stack = [testNode];
|
|
548
|
+
while (stack.length && !found) {
|
|
549
|
+
const current = stack.pop();
|
|
550
|
+
if (!current)
|
|
551
|
+
continue;
|
|
552
|
+
if (current.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
553
|
+
current.object &&
|
|
554
|
+
(() => {
|
|
555
|
+
let base = current.object;
|
|
556
|
+
while (base.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
557
|
+
base = base.object;
|
|
558
|
+
}
|
|
559
|
+
return (base.type === utils_1.AST_NODE_TYPES.Identifier && base.name === objectName);
|
|
560
|
+
})()) {
|
|
561
|
+
found = true;
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
if (current.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
565
|
+
current.name === objectName &&
|
|
566
|
+
isIdentifierReference(current)) {
|
|
567
|
+
found = true;
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
const keys = visitorKeys[current.type] ?? [];
|
|
571
|
+
for (const key of keys) {
|
|
572
|
+
const value = current[key];
|
|
573
|
+
if (Array.isArray(value)) {
|
|
574
|
+
for (const child of value) {
|
|
575
|
+
if (child && typeof child === 'object') {
|
|
576
|
+
stack.push(child);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
else if (value && typeof value === 'object') {
|
|
581
|
+
stack.push(value);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return found;
|
|
586
|
+
}
|
|
587
|
+
function isTypeNarrowingContext(node, baseName, visitorKeys) {
|
|
588
|
+
if (!baseName)
|
|
589
|
+
return false;
|
|
590
|
+
let current = node.parent;
|
|
591
|
+
while (current && current.type !== utils_1.AST_NODE_TYPES.Program) {
|
|
592
|
+
if (current.type === utils_1.AST_NODE_TYPES.IfStatement && current.test) {
|
|
593
|
+
if (testContainsObjectMember(current.test, baseName, visitorKeys)) {
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
current = current.parent;
|
|
598
|
+
}
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
function getIndentation(node, sourceCode) {
|
|
602
|
+
const text = sourceCode.getText();
|
|
603
|
+
const lineStart = text.lastIndexOf('\n', node.range[0]) + 1;
|
|
604
|
+
const prefix = text.slice(lineStart, node.range[0]);
|
|
605
|
+
const match = prefix.match(/^[\t ]*/);
|
|
606
|
+
return match ? match[0] : '';
|
|
607
|
+
}
|
|
608
|
+
function removalRange(node, sourceCode) {
|
|
609
|
+
const text = sourceCode.getText();
|
|
610
|
+
const range = node.range;
|
|
611
|
+
const lineStart = text.lastIndexOf('\n', range[0] - 1) + 1;
|
|
612
|
+
const lineEnd = text.indexOf('\n', range[1]);
|
|
613
|
+
const endOfLine = lineEnd === -1 ? text.length : lineEnd;
|
|
614
|
+
const leading = text.slice(lineStart, range[0]);
|
|
615
|
+
const trailing = text.slice(range[1], endOfLine);
|
|
616
|
+
if (/^[\t ]*$/.test(leading) && /^[\t ;]*$/.test(trailing)) {
|
|
617
|
+
return [lineStart, lineEnd === -1 ? text.length : lineEnd + 1];
|
|
618
|
+
}
|
|
619
|
+
if (/^[\t ;]*$/.test(trailing)) {
|
|
620
|
+
return [range[0], lineEnd === -1 ? text.length : lineEnd + 1];
|
|
621
|
+
}
|
|
622
|
+
if (text[range[1]] === ' ') {
|
|
623
|
+
return [range[0], range[1] + 1];
|
|
624
|
+
}
|
|
625
|
+
return [range[0], range[1]];
|
|
626
|
+
}
|
|
627
|
+
function isAnyFunctionLikeNode(node) {
|
|
628
|
+
return (node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
629
|
+
node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
630
|
+
node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
631
|
+
node.type === utils_1.AST_NODE_TYPES.TSDeclareFunction);
|
|
632
|
+
}
|
|
633
|
+
function shouldSkipNestedFunction(candidateNode, callback) {
|
|
634
|
+
return isAnyFunctionLikeNode(candidateNode) && candidateNode !== callback;
|
|
635
|
+
}
|
|
636
|
+
function hasCrossGroupNameCollision(groups) {
|
|
637
|
+
const nameToObjects = new Map();
|
|
638
|
+
for (const group of groups.values()) {
|
|
639
|
+
for (const name of group.names) {
|
|
640
|
+
const seen = nameToObjects.get(name) ?? new Set();
|
|
641
|
+
seen.add(group.objectText);
|
|
642
|
+
nameToObjects.set(name, seen);
|
|
643
|
+
if (seen.size > 1) {
|
|
644
|
+
return true;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
function hasPriorConditionalGuard(node, baseName, visitorKeys) {
|
|
651
|
+
if (!baseName)
|
|
652
|
+
return false;
|
|
653
|
+
const parent = node.parent;
|
|
654
|
+
if (!parent || parent.type !== utils_1.AST_NODE_TYPES.BlockStatement)
|
|
655
|
+
return false;
|
|
656
|
+
const index = parent.body.indexOf(node);
|
|
657
|
+
if (index <= 0)
|
|
658
|
+
return false;
|
|
659
|
+
return parent.body
|
|
660
|
+
.slice(0, index)
|
|
661
|
+
.some((statement) => statement.type === utils_1.AST_NODE_TYPES.IfStatement &&
|
|
662
|
+
Boolean(statement.test) &&
|
|
663
|
+
testContainsObjectMember(statement.test, baseName, visitorKeys));
|
|
664
|
+
}
|
|
665
|
+
function isIdentifierReference(node) {
|
|
666
|
+
const parent = node.parent;
|
|
667
|
+
if (!parent)
|
|
668
|
+
return true;
|
|
669
|
+
if (parent.type === utils_1.AST_NODE_TYPES.Property &&
|
|
670
|
+
parent.key === node &&
|
|
671
|
+
!parent.computed &&
|
|
672
|
+
!parent.shorthand) {
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
675
|
+
if (parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
676
|
+
parent.property === node &&
|
|
677
|
+
!parent.computed) {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
if (parent.type === utils_1.AST_NODE_TYPES.MethodDefinition &&
|
|
681
|
+
parent.key === node &&
|
|
682
|
+
!parent.computed) {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
if (parent.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
|
|
686
|
+
parent.key === node &&
|
|
687
|
+
!parent.computed) {
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
return true;
|
|
691
|
+
}
|
|
692
|
+
function buildDestructuringGroups(callback, depTextSet, visitorKeys, sourceCode) {
|
|
693
|
+
const groups = new Map();
|
|
694
|
+
const stack = [...callback.body.body].reverse();
|
|
695
|
+
while (stack.length) {
|
|
696
|
+
const current = stack.pop();
|
|
697
|
+
if (!current)
|
|
698
|
+
continue;
|
|
699
|
+
if (shouldSkipNestedFunction(current, callback)) {
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
if (current.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
703
|
+
for (const declarator of current.declarations) {
|
|
704
|
+
if (declarator.id.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
|
|
705
|
+
declarator.init &&
|
|
706
|
+
isAllowedInit(declarator.init) &&
|
|
707
|
+
current.declarations.length === 1 &&
|
|
708
|
+
!declarator.id.properties.some((prop) => prop.type === utils_1.AST_NODE_TYPES.RestElement)) {
|
|
709
|
+
const initText = sourceCode.getText(declarator.init);
|
|
710
|
+
const normalizedInit = unwrapTsExpression(declarator.init);
|
|
711
|
+
const normalizedText = sourceCode.getText(normalizedInit);
|
|
712
|
+
const depKey = depTextSet.has(initText)
|
|
713
|
+
? initText
|
|
714
|
+
: depTextSet.has(normalizedText)
|
|
715
|
+
? normalizedText
|
|
716
|
+
: null;
|
|
717
|
+
if (!depKey)
|
|
718
|
+
continue;
|
|
719
|
+
const baseName = getBaseIdentifier(declarator.init);
|
|
720
|
+
if (hasPriorConditionalGuard(current, baseName, visitorKeys) ||
|
|
721
|
+
isTypeNarrowingContext(current, baseName, visitorKeys)) {
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
const existingGroup = groups.get(depKey);
|
|
725
|
+
const properties = existingGroup?.properties ?? new Map();
|
|
726
|
+
collectProperties(declarator.id, sourceCode, visitorKeys, properties);
|
|
727
|
+
const names = existingGroup?.names ?? new Set();
|
|
728
|
+
const orderedNames = existingGroup?.orderedNames ?? [];
|
|
729
|
+
collectNamesFromPattern(declarator.id, names, orderedNames);
|
|
730
|
+
const declarations = existingGroup?.declarations ?? [];
|
|
731
|
+
declarations.push(current);
|
|
732
|
+
const inits = existingGroup?.inits ?? [];
|
|
733
|
+
inits.push(declarator.init);
|
|
734
|
+
groups.set(depKey, {
|
|
735
|
+
objectText: initText,
|
|
736
|
+
properties,
|
|
737
|
+
names,
|
|
738
|
+
orderedNames,
|
|
739
|
+
declarations,
|
|
740
|
+
inits,
|
|
741
|
+
baseName: existingGroup?.baseName ?? baseName ?? null,
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
const keys = visitorKeys[current.type] ?? [];
|
|
747
|
+
for (const key of keys) {
|
|
748
|
+
const value = current[key];
|
|
749
|
+
if (Array.isArray(value)) {
|
|
750
|
+
for (const child of value) {
|
|
751
|
+
if (child && typeof child === 'object') {
|
|
752
|
+
stack.push(child);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
else if (value && typeof value === 'object') {
|
|
757
|
+
stack.push(value);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return groups;
|
|
762
|
+
}
|
|
763
|
+
function validateGroupsForHoisting(groups, callback, scope, visitorKeys, reservedNamesByScope) {
|
|
764
|
+
if (hasCrossGroupNameCollision(groups)) {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
const declarationsToRemove = new Set();
|
|
768
|
+
const initsToIgnore = new Set();
|
|
769
|
+
for (const group of groups.values()) {
|
|
770
|
+
group.declarations.forEach((decl) => declarationsToRemove.add(decl));
|
|
771
|
+
group.inits.forEach((init) => initsToIgnore.add(init));
|
|
772
|
+
}
|
|
773
|
+
const existingBindings = collectExistingBindings(callback, declarationsToRemove);
|
|
774
|
+
const scopeDeclaredNames = collectBindingsInScope(scope);
|
|
775
|
+
const reservedNames = reservedNamesByScope.get(scope) ?? new Set();
|
|
776
|
+
const scopeNameCollisions = new Set([
|
|
777
|
+
...scopeDeclaredNames,
|
|
778
|
+
...reservedNames,
|
|
779
|
+
]);
|
|
780
|
+
const callbackLocalBindings = collectCallbackLocalBindings(callback, declarationsToRemove, visitorKeys);
|
|
781
|
+
for (const group of groups.values()) {
|
|
782
|
+
for (const property of group.properties.values()) {
|
|
783
|
+
for (const name of property.referenceNames) {
|
|
784
|
+
if (callbackLocalBindings.has(name)) {
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
for (const group of groups.values()) {
|
|
791
|
+
for (const name of group.names) {
|
|
792
|
+
if (existingBindings.has(name)) {
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
if (scopeNameCollisions.has(name)) {
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
for (const group of groups.values()) {
|
|
801
|
+
const sortedProps = Array.from(group.properties.values()).sort((a, b) => a.order - b.order);
|
|
802
|
+
const bindingNamesInHoistedPattern = new Set();
|
|
803
|
+
for (const property of sortedProps) {
|
|
804
|
+
for (const name of property.bindingNames) {
|
|
805
|
+
if (bindingNamesInHoistedPattern.has(name)) {
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
bindingNamesInHoistedPattern.add(name);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
for (const name of group.names) {
|
|
812
|
+
if (!bindingNamesInHoistedPattern.has(name)) {
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return {
|
|
818
|
+
declarationsToRemove,
|
|
819
|
+
initsToIgnore,
|
|
820
|
+
existingBindings,
|
|
821
|
+
scopeNameCollisions,
|
|
822
|
+
callbackLocalBindings,
|
|
823
|
+
reservedNames,
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
function generateHoistingFixes(groups, callback, depsArray, depTexts, insertionStatement, sourceCode, fixer, visitorKeys, validation, orderedDependencies, reservedNamesByScope, scope) {
|
|
827
|
+
const { declarationsToRemove, initsToIgnore, reservedNames } = validation;
|
|
828
|
+
const indent = getIndentation(insertionStatement, sourceCode);
|
|
829
|
+
const hoistedLines = [];
|
|
830
|
+
const baseUsageByObject = new Map();
|
|
831
|
+
for (const [depKey, group] of groups.entries()) {
|
|
832
|
+
if (!group.baseName) {
|
|
833
|
+
baseUsageByObject.set(depKey, true);
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
const usesBase = callbackUsesBaseIdentifier(callback, group.baseName, declarationsToRemove, initsToIgnore, visitorKeys);
|
|
837
|
+
baseUsageByObject.set(depKey, usesBase);
|
|
838
|
+
}
|
|
839
|
+
const newDepTexts = depTexts.filter((text) => {
|
|
840
|
+
const group = groups.get(text);
|
|
841
|
+
if (!group)
|
|
842
|
+
return true;
|
|
843
|
+
return baseUsageByObject.get(text) ?? true;
|
|
844
|
+
});
|
|
845
|
+
const updatedReservedNames = new Set(reservedNames);
|
|
846
|
+
for (const group of groups.values()) {
|
|
847
|
+
for (const name of group.names) {
|
|
848
|
+
updatedReservedNames.add(name);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
for (const group of groups.values()) {
|
|
852
|
+
const sortedProps = Array.from(group.properties.values()).sort((a, b) => a.order - b.order);
|
|
853
|
+
const pattern = `{ ${sortedProps.map((p) => p.text).join(', ')} }`;
|
|
854
|
+
hoistedLines.push(`${indent}const ${pattern} = (${group.objectText}) ?? {};`);
|
|
855
|
+
}
|
|
856
|
+
reservedNamesByScope.set(scope, updatedReservedNames);
|
|
857
|
+
const newDepSet = new Set(newDepTexts);
|
|
858
|
+
for (const name of orderedDependencies) {
|
|
859
|
+
if (!newDepSet.has(name)) {
|
|
860
|
+
newDepTexts.push(name);
|
|
861
|
+
newDepSet.add(name);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
const insertAt = sourceCode.getText().lastIndexOf('\n', insertionStatement.range[0]) + 1;
|
|
865
|
+
const fixes = [
|
|
866
|
+
fixer.insertTextBeforeRange([insertAt, insertAt], `${hoistedLines.join('\n')}\n`),
|
|
867
|
+
fixer.replaceText(depsArray, `[${newDepTexts.join(', ')}]`),
|
|
868
|
+
];
|
|
869
|
+
for (const decl of declarationsToRemove) {
|
|
870
|
+
fixes.push(fixer.removeRange(removalRange(decl, sourceCode)));
|
|
871
|
+
}
|
|
872
|
+
return fixes;
|
|
873
|
+
}
|
|
874
|
+
function validateHookForTransform(node, context) {
|
|
875
|
+
const hookName = isHookCall(node);
|
|
876
|
+
if (!hookName)
|
|
877
|
+
return null;
|
|
878
|
+
const callback = node.arguments[0];
|
|
879
|
+
if (!callback ||
|
|
880
|
+
!isFunctionNode(callback) ||
|
|
881
|
+
callback.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
if (callback.async)
|
|
885
|
+
return null;
|
|
886
|
+
const depsArray = node.arguments.length > 1 &&
|
|
887
|
+
node.arguments[1] &&
|
|
888
|
+
node.arguments[1].type === utils_1.AST_NODE_TYPES.ArrayExpression
|
|
889
|
+
? node.arguments[1]
|
|
890
|
+
: null;
|
|
891
|
+
if (!depsArray)
|
|
892
|
+
return null;
|
|
893
|
+
const sourceCode = context.sourceCode ??
|
|
894
|
+
context.getSourceCode();
|
|
895
|
+
const depTexts = dependencyElements(depsArray, sourceCode);
|
|
896
|
+
const depTextSet = new Set(depTexts);
|
|
897
|
+
const scope = context.getScope();
|
|
898
|
+
return {
|
|
899
|
+
hookName,
|
|
900
|
+
callback: callback,
|
|
901
|
+
depsArray,
|
|
902
|
+
depTextSet,
|
|
903
|
+
depTexts,
|
|
904
|
+
scope,
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
exports.enforceEarlyDestructuring = (0, createRule_1.createRule)({
|
|
908
|
+
name: 'enforce-early-destructuring',
|
|
909
|
+
meta: {
|
|
910
|
+
type: 'suggestion',
|
|
911
|
+
docs: {
|
|
912
|
+
description: 'Hoist object destructuring out of React hooks so dependency arrays track the fields in use instead of the entire object.',
|
|
913
|
+
recommended: 'error',
|
|
914
|
+
},
|
|
915
|
+
fixable: 'code',
|
|
916
|
+
schema: [],
|
|
917
|
+
messages: {
|
|
918
|
+
hoistDestructuring: 'What\'s wrong: "{{objectName}}" is destructured inside the {{hookName}} callback -> ' +
|
|
919
|
+
'Why it matters: the deps array then tracks the whole object, so the hook can re-run for unrelated field changes and can hide stale closures -> ' +
|
|
920
|
+
'How to fix: hoist the destructuring before {{hookName}} (or memoize/guard the object) and depend on the specific fields: {{dependencies}}.',
|
|
921
|
+
},
|
|
922
|
+
},
|
|
923
|
+
defaultOptions: [],
|
|
924
|
+
create(context) {
|
|
925
|
+
const sourceCode = context.sourceCode ??
|
|
926
|
+
context.getSourceCode();
|
|
927
|
+
const visitorKeys = sourceCode
|
|
928
|
+
.visitorKeys ??
|
|
929
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
930
|
+
context.visitorKeys ??
|
|
931
|
+
{};
|
|
932
|
+
const reservedNamesByScope = new WeakMap();
|
|
933
|
+
return {
|
|
934
|
+
CallExpression(node) {
|
|
935
|
+
const validation = validateHookForTransform(node, context);
|
|
936
|
+
if (!validation)
|
|
937
|
+
return;
|
|
938
|
+
const { hookName, callback, depsArray, depTextSet, depTexts, scope } = validation;
|
|
939
|
+
const groups = buildDestructuringGroups(callback, depTextSet, visitorKeys, sourceCode);
|
|
940
|
+
if (!groups.size)
|
|
941
|
+
return;
|
|
942
|
+
const allNames = new Set();
|
|
943
|
+
const orderedDependencies = [];
|
|
944
|
+
for (const group of groups.values()) {
|
|
945
|
+
for (const name of group.orderedNames) {
|
|
946
|
+
if (!allNames.has(name)) {
|
|
947
|
+
allNames.add(name);
|
|
948
|
+
orderedDependencies.push(name);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
const dependencyList = orderedDependencies.length > 0
|
|
953
|
+
? orderedDependencies.join(', ')
|
|
954
|
+
: 'the fields you use';
|
|
955
|
+
const firstGroup = Array.from(groups.values())[0];
|
|
956
|
+
context.report({
|
|
957
|
+
node: firstGroup.declarations[0],
|
|
958
|
+
messageId: 'hoistDestructuring',
|
|
959
|
+
data: {
|
|
960
|
+
objectName: firstGroup.objectText,
|
|
961
|
+
hookName,
|
|
962
|
+
dependencies: dependencyList,
|
|
963
|
+
},
|
|
964
|
+
fix(fixer) {
|
|
965
|
+
const insertionStatement = findInsertionStatement(node);
|
|
966
|
+
if (!insertionStatement) {
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
const validationResult = validateGroupsForHoisting(groups, callback, scope, visitorKeys, reservedNamesByScope);
|
|
970
|
+
if (!validationResult) {
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
return generateHoistingFixes(groups, callback, depsArray, depTexts, insertionStatement, sourceCode, fixer, visitorKeys, validationResult, orderedDependencies, reservedNamesByScope, scope);
|
|
974
|
+
},
|
|
975
|
+
});
|
|
976
|
+
},
|
|
977
|
+
};
|
|
978
|
+
},
|
|
979
|
+
});
|
|
980
|
+
//# sourceMappingURL=enforce-early-destructuring.js.map
|