@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,1258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logicalTopToBottomGrouping = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
6
|
+
const createRule_1 = require("../utils/createRule");
|
|
7
|
+
const TYPE_EXPRESSION_WRAPPERS = new Set([
|
|
8
|
+
utils_1.AST_NODE_TYPES.TSAsExpression,
|
|
9
|
+
utils_1.AST_NODE_TYPES.TSTypeAssertion,
|
|
10
|
+
utils_1.AST_NODE_TYPES.TSNonNullExpression,
|
|
11
|
+
utils_1.AST_NODE_TYPES.TSSatisfiesExpression,
|
|
12
|
+
utils_1.AST_NODE_TYPES.TSInstantiationExpression,
|
|
13
|
+
]);
|
|
14
|
+
function isHookLikeName(name) {
|
|
15
|
+
return /^use[A-Z0-9]/.test(name);
|
|
16
|
+
}
|
|
17
|
+
function isHookCallee(callee) {
|
|
18
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
19
|
+
return isHookLikeName(callee.name);
|
|
20
|
+
}
|
|
21
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
22
|
+
!callee.computed &&
|
|
23
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
24
|
+
return isHookLikeName(callee.property.name);
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
function isTypeNode(node) {
|
|
29
|
+
if (!node) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (TYPE_EXPRESSION_WRAPPERS.has(node.type)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return node.type.startsWith('TS');
|
|
36
|
+
}
|
|
37
|
+
function unwrapTypeExpression(expression) {
|
|
38
|
+
switch (expression.type) {
|
|
39
|
+
case utils_1.AST_NODE_TYPES.TSAsExpression:
|
|
40
|
+
case utils_1.AST_NODE_TYPES.TSTypeAssertion:
|
|
41
|
+
case utils_1.AST_NODE_TYPES.TSNonNullExpression:
|
|
42
|
+
case utils_1.AST_NODE_TYPES.TSSatisfiesExpression:
|
|
43
|
+
case utils_1.AST_NODE_TYPES.TSInstantiationExpression:
|
|
44
|
+
return expression.expression;
|
|
45
|
+
default:
|
|
46
|
+
return expression;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function isDeclarationIdentifier(node, parent) {
|
|
50
|
+
if (!parent) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator && parent.id === node) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if ((parent.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
57
|
+
parent.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
|
|
58
|
+
parent.id === node) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
if ((parent.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
62
|
+
parent.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
63
|
+
parent.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) &&
|
|
64
|
+
parent.params.includes(node)) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
if ((parent.type === utils_1.AST_NODE_TYPES.ClassDeclaration ||
|
|
68
|
+
parent.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration ||
|
|
69
|
+
parent.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) &&
|
|
70
|
+
parent.id === node) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
function collectPatternDependencies(pattern, names) {
|
|
76
|
+
switch (pattern.type) {
|
|
77
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
78
|
+
return;
|
|
79
|
+
case utils_1.AST_NODE_TYPES.RestElement:
|
|
80
|
+
collectPatternDependencies(pattern.argument, names);
|
|
81
|
+
return;
|
|
82
|
+
case utils_1.AST_NODE_TYPES.AssignmentPattern:
|
|
83
|
+
collectUsedIdentifiers(pattern.right, names, {
|
|
84
|
+
skipFunctions: true,
|
|
85
|
+
includeFunctionCaptures: true,
|
|
86
|
+
});
|
|
87
|
+
collectPatternDependencies(pattern.left, names);
|
|
88
|
+
return;
|
|
89
|
+
case utils_1.AST_NODE_TYPES.ArrayPattern:
|
|
90
|
+
pattern.elements.forEach((element) => {
|
|
91
|
+
if (element) {
|
|
92
|
+
collectPatternDependencies(element, names);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
case utils_1.AST_NODE_TYPES.ObjectPattern:
|
|
97
|
+
pattern.properties.forEach((prop) => {
|
|
98
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
99
|
+
if (prop.computed && ASTHelpers_1.ASTHelpers.isNode(prop.key)) {
|
|
100
|
+
collectUsedIdentifiers(prop.key, names, {
|
|
101
|
+
skipFunctions: true,
|
|
102
|
+
includeFunctionCaptures: true,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
collectPatternDependencies(prop.value, names);
|
|
106
|
+
}
|
|
107
|
+
else if (prop.type === utils_1.AST_NODE_TYPES.RestElement) {
|
|
108
|
+
collectPatternDependencies(prop.argument, names);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return;
|
|
112
|
+
default:
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function processIdentifier(identifier, names) {
|
|
117
|
+
const parent = identifier.parent;
|
|
118
|
+
if (shouldSkipIdentifier(identifier, parent)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
names.add(identifier.name);
|
|
122
|
+
}
|
|
123
|
+
function shouldSkipIdentifier(identifier, parent) {
|
|
124
|
+
if (isTypeNode(parent) || isDeclarationIdentifier(identifier, parent)) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
if (parent &&
|
|
128
|
+
parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
129
|
+
parent.property === identifier &&
|
|
130
|
+
!parent.computed) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
if (parent &&
|
|
134
|
+
parent.type === utils_1.AST_NODE_TYPES.Property &&
|
|
135
|
+
parent.key === identifier &&
|
|
136
|
+
!parent.computed &&
|
|
137
|
+
!parent.shorthand) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
function shouldSkipFunction(node, skipFunctions) {
|
|
143
|
+
return (skipFunctions &&
|
|
144
|
+
(node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
145
|
+
node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
146
|
+
node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression));
|
|
147
|
+
}
|
|
148
|
+
function addChildNodesToStack(node, stack) {
|
|
149
|
+
for (const key of Object.keys(node)) {
|
|
150
|
+
if (key === 'parent') {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const value = node[key];
|
|
154
|
+
if (Array.isArray(value)) {
|
|
155
|
+
for (const element of value) {
|
|
156
|
+
if (ASTHelpers_1.ASTHelpers.isNode(element)) {
|
|
157
|
+
stack.push(element);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else if (ASTHelpers_1.ASTHelpers.isNode(value)) {
|
|
162
|
+
stack.push(value);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function traverseAst(node, { skipFunctions, visit, onSkipFunction, }) {
|
|
167
|
+
const stack = [node];
|
|
168
|
+
while (stack.length > 0) {
|
|
169
|
+
const current = stack.pop();
|
|
170
|
+
if (shouldSkipFunction(current, skipFunctions)) {
|
|
171
|
+
if (onSkipFunction) {
|
|
172
|
+
onSkipFunction(current);
|
|
173
|
+
}
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const result = visit(current) ?? {};
|
|
177
|
+
if (result.push) {
|
|
178
|
+
result.push.forEach((child) => {
|
|
179
|
+
if (ASTHelpers_1.ASTHelpers.isNode(child)) {
|
|
180
|
+
stack.push(child);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (result.skipChildren) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
addChildNodesToStack(current, stack);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function unwrapIifeCallee(callee) {
|
|
191
|
+
const node = callee;
|
|
192
|
+
if (node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
193
|
+
node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
194
|
+
return node;
|
|
195
|
+
}
|
|
196
|
+
if (TYPE_EXPRESSION_WRAPPERS.has(node.type) && 'expression' in node) {
|
|
197
|
+
return unwrapIifeCallee(node
|
|
198
|
+
.expression);
|
|
199
|
+
}
|
|
200
|
+
if (node.type === utils_1.AST_NODE_TYPES.ChainExpression && 'expression' in node) {
|
|
201
|
+
return unwrapIifeCallee(node
|
|
202
|
+
.expression);
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
function collectIifeDependencies(fn, names) {
|
|
207
|
+
collectFunctionCaptures(fn, names, {
|
|
208
|
+
skipFunctions: true,
|
|
209
|
+
includeFunctionCaptures: true,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
function collectFunctionScopedDeclarations(node, declared) {
|
|
213
|
+
traverseAst(node, {
|
|
214
|
+
skipFunctions: false,
|
|
215
|
+
visit(current) {
|
|
216
|
+
if (current !== node &&
|
|
217
|
+
(current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
218
|
+
current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
|
|
219
|
+
return { skipChildren: true };
|
|
220
|
+
}
|
|
221
|
+
if (current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration && current.id) {
|
|
222
|
+
declared.add(current.id.name);
|
|
223
|
+
return { skipChildren: true };
|
|
224
|
+
}
|
|
225
|
+
if (current.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
226
|
+
current.declarations.forEach((declarator) => collectDeclaredNamesFromPattern(declarator.id, declared));
|
|
227
|
+
}
|
|
228
|
+
if (current.type === utils_1.AST_NODE_TYPES.ClassDeclaration && current.id) {
|
|
229
|
+
declared.add(current.id.name);
|
|
230
|
+
return { skipChildren: true };
|
|
231
|
+
}
|
|
232
|
+
if (current.type === utils_1.AST_NODE_TYPES.CatchClause && current.param) {
|
|
233
|
+
collectDeclaredNamesFromPattern(current.param, declared);
|
|
234
|
+
}
|
|
235
|
+
return undefined;
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function collectFunctionCaptures(fn, names, options) {
|
|
240
|
+
const declared = new Set();
|
|
241
|
+
if (fn.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression && fn.id) {
|
|
242
|
+
declared.add(fn.id.name);
|
|
243
|
+
}
|
|
244
|
+
fn.params.forEach((param) => collectDeclaredNamesFromPattern(param, declared));
|
|
245
|
+
if (fn.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
246
|
+
collectFunctionScopedDeclarations(fn.body, declared);
|
|
247
|
+
}
|
|
248
|
+
const used = new Set();
|
|
249
|
+
fn.params.forEach((param) => {
|
|
250
|
+
collectUsedIdentifiers(param, used, {
|
|
251
|
+
skipFunctions: options.skipFunctions,
|
|
252
|
+
includeFunctionCaptures: options.includeFunctionCaptures,
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
collectUsedIdentifiers(fn.body, used, {
|
|
256
|
+
skipFunctions: options.skipFunctions,
|
|
257
|
+
includeFunctionCaptures: options.includeFunctionCaptures,
|
|
258
|
+
});
|
|
259
|
+
used.forEach((name) => {
|
|
260
|
+
if (!declared.has(name)) {
|
|
261
|
+
names.add(name);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function collectUsedIdentifiers(node, names, { skipFunctions, includeFunctionCaptures = false }) {
|
|
266
|
+
traverseAst(node, {
|
|
267
|
+
skipFunctions,
|
|
268
|
+
onSkipFunction: includeFunctionCaptures
|
|
269
|
+
? createFunctionCaptureHandler(names, {
|
|
270
|
+
skipFunctions,
|
|
271
|
+
includeFunctionCaptures,
|
|
272
|
+
})
|
|
273
|
+
: undefined,
|
|
274
|
+
visit(current) {
|
|
275
|
+
if (current.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
276
|
+
processIdentifier(current, names);
|
|
277
|
+
return { skipChildren: true };
|
|
278
|
+
}
|
|
279
|
+
if (skipFunctions && current.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
280
|
+
processCallExpression(current, names);
|
|
281
|
+
}
|
|
282
|
+
return undefined;
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
function createFunctionCaptureHandler(names, options) {
|
|
287
|
+
return (fnNode) => {
|
|
288
|
+
if (fnNode.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
289
|
+
fnNode.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
290
|
+
fnNode.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
291
|
+
collectFunctionCaptures(fnNode, names, {
|
|
292
|
+
skipFunctions: true,
|
|
293
|
+
includeFunctionCaptures: options.includeFunctionCaptures,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function processCallExpression(node, names) {
|
|
299
|
+
const iife = unwrapIifeCallee(node.callee);
|
|
300
|
+
if (iife) {
|
|
301
|
+
collectIifeDependencies(iife, names);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function collectDeclaredNamesFromPattern(pattern, names) {
|
|
305
|
+
switch (pattern.type) {
|
|
306
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
307
|
+
names.add(pattern.name);
|
|
308
|
+
return;
|
|
309
|
+
case utils_1.AST_NODE_TYPES.RestElement:
|
|
310
|
+
collectDeclaredNamesFromPattern(pattern.argument, names);
|
|
311
|
+
return;
|
|
312
|
+
case utils_1.AST_NODE_TYPES.AssignmentPattern:
|
|
313
|
+
collectDeclaredNamesFromPattern(pattern.left, names);
|
|
314
|
+
return;
|
|
315
|
+
case utils_1.AST_NODE_TYPES.ArrayPattern:
|
|
316
|
+
pattern.elements.forEach((element) => {
|
|
317
|
+
if (element) {
|
|
318
|
+
collectDeclaredNamesFromPattern(element, names);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
return;
|
|
322
|
+
case utils_1.AST_NODE_TYPES.ObjectPattern:
|
|
323
|
+
pattern.properties.forEach((prop) => {
|
|
324
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
325
|
+
if (prop.value.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
326
|
+
names.add(prop.value.name);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
collectDeclaredNamesFromPattern(prop.value, names);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
else if (prop.type === utils_1.AST_NODE_TYPES.RestElement) {
|
|
333
|
+
collectDeclaredNamesFromPattern(prop.argument, names);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
return;
|
|
337
|
+
default:
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function getDeclaredNames(statement) {
|
|
342
|
+
const names = new Set();
|
|
343
|
+
if (statement.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
344
|
+
statement.declarations.forEach((declarator) => {
|
|
345
|
+
collectDeclaredNamesFromPattern(declarator.id, names);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
if (statement.type === utils_1.AST_NODE_TYPES.FunctionDeclaration && statement.id) {
|
|
349
|
+
names.add(statement.id.name);
|
|
350
|
+
}
|
|
351
|
+
if (statement.type === utils_1.AST_NODE_TYPES.ClassDeclaration &&
|
|
352
|
+
statement.id &&
|
|
353
|
+
statement.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
354
|
+
names.add(statement.id.name);
|
|
355
|
+
}
|
|
356
|
+
return names;
|
|
357
|
+
}
|
|
358
|
+
function statementReferencesAny(statement, names) {
|
|
359
|
+
if (names.size === 0) {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
const found = new Set();
|
|
363
|
+
collectUsedIdentifiers(statement, found, { skipFunctions: false });
|
|
364
|
+
for (const name of names) {
|
|
365
|
+
if (found.has(name)) {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
function collectAssignedNamesFromPattern(target, names) {
|
|
372
|
+
if (TYPE_EXPRESSION_WRAPPERS.has(target.type) &&
|
|
373
|
+
'expression' in target) {
|
|
374
|
+
collectAssignedNamesFromPattern(target.expression, names);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
switch (target.type) {
|
|
378
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
379
|
+
names.add(target.name);
|
|
380
|
+
return;
|
|
381
|
+
case utils_1.AST_NODE_TYPES.MemberExpression: {
|
|
382
|
+
let cursor = target
|
|
383
|
+
.object;
|
|
384
|
+
while (true) {
|
|
385
|
+
if (TYPE_EXPRESSION_WRAPPERS.has(cursor.type) &&
|
|
386
|
+
'expression' in cursor) {
|
|
387
|
+
cursor = cursor
|
|
388
|
+
.expression;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (cursor.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
392
|
+
cursor = cursor
|
|
393
|
+
.object;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
if (cursor.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
399
|
+
names.add(cursor.name);
|
|
400
|
+
}
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
case utils_1.AST_NODE_TYPES.AssignmentPattern:
|
|
404
|
+
collectAssignedNamesFromPattern(target.left, names);
|
|
405
|
+
return;
|
|
406
|
+
case utils_1.AST_NODE_TYPES.RestElement:
|
|
407
|
+
collectAssignedNamesFromPattern(target.argument, names);
|
|
408
|
+
return;
|
|
409
|
+
case utils_1.AST_NODE_TYPES.ArrayPattern:
|
|
410
|
+
target.elements.forEach((element) => {
|
|
411
|
+
if (element) {
|
|
412
|
+
collectAssignedNamesFromPattern(element, names);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
return;
|
|
416
|
+
case utils_1.AST_NODE_TYPES.ObjectPattern:
|
|
417
|
+
target.properties.forEach((prop) => {
|
|
418
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
419
|
+
collectAssignedNamesFromPattern(prop.value, names);
|
|
420
|
+
}
|
|
421
|
+
else if (prop.type === utils_1.AST_NODE_TYPES.RestElement) {
|
|
422
|
+
collectAssignedNamesFromPattern(prop.argument, names);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
return;
|
|
426
|
+
default:
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function collectMutatedIdentifiers(node, names, { skipFunctions }) {
|
|
431
|
+
traverseAst(node, {
|
|
432
|
+
skipFunctions,
|
|
433
|
+
visit(current) {
|
|
434
|
+
if (current.type === utils_1.AST_NODE_TYPES.AssignmentExpression) {
|
|
435
|
+
const push = ASTHelpers_1.ASTHelpers.isNode(current.right)
|
|
436
|
+
? [current.right]
|
|
437
|
+
: undefined;
|
|
438
|
+
collectAssignedNamesFromPattern(current.left, names);
|
|
439
|
+
return { skipChildren: true, push };
|
|
440
|
+
}
|
|
441
|
+
if (current.type === utils_1.AST_NODE_TYPES.UpdateExpression) {
|
|
442
|
+
collectAssignedNamesFromPattern(current.argument, names);
|
|
443
|
+
return { skipChildren: true };
|
|
444
|
+
}
|
|
445
|
+
return undefined;
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
function statementMutatesAny(statement, names) {
|
|
450
|
+
if (names.size === 0) {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
const mutated = new Set();
|
|
454
|
+
collectMutatedIdentifiers(statement, mutated, { skipFunctions: true });
|
|
455
|
+
for (const name of names) {
|
|
456
|
+
if (mutated.has(name)) {
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Mutations create ordering barriers: once a name is reassigned, moving statements
|
|
464
|
+
* across that mutation can change observable state. Guard moves stop before the
|
|
465
|
+
* first mutation to keep evaluation order stable.
|
|
466
|
+
*/
|
|
467
|
+
function isIdentifierMutated(body, name, beforeIndex) {
|
|
468
|
+
const target = new Set([name]);
|
|
469
|
+
let seenDeclaration = false;
|
|
470
|
+
for (let index = 0; index < beforeIndex; index += 1) {
|
|
471
|
+
const statement = body[index];
|
|
472
|
+
if (statementMutatesAny(statement, target)) {
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
if (statement.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
476
|
+
for (const declarator of statement.declarations) {
|
|
477
|
+
const declaredNames = new Set();
|
|
478
|
+
collectDeclaredNamesFromPattern(declarator.id, declaredNames);
|
|
479
|
+
if (!declaredNames.has(name)) {
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (seenDeclaration && declarator.init) {
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
seenDeclaration = true;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
function initializerIsSafe(expression, { allowHooks }) {
|
|
492
|
+
// Hook calls are treated as impure so we never reorder React hook execution unless a callsite explicitly opts in.
|
|
493
|
+
const unwrapped = unwrapTypeExpression(expression);
|
|
494
|
+
if (unwrapped !== expression) {
|
|
495
|
+
return initializerIsSafe(unwrapped, { allowHooks });
|
|
496
|
+
}
|
|
497
|
+
switch (expression.type) {
|
|
498
|
+
case utils_1.AST_NODE_TYPES.Literal:
|
|
499
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
500
|
+
case utils_1.AST_NODE_TYPES.Super:
|
|
501
|
+
case utils_1.AST_NODE_TYPES.ThisExpression:
|
|
502
|
+
return true;
|
|
503
|
+
case utils_1.AST_NODE_TYPES.TemplateLiteral:
|
|
504
|
+
return expression.expressions.every((exp) => initializerIsSafe(exp, { allowHooks }));
|
|
505
|
+
case utils_1.AST_NODE_TYPES.MemberExpression:
|
|
506
|
+
if (expression.computed) {
|
|
507
|
+
return (initializerIsSafe(expression.property, {
|
|
508
|
+
allowHooks,
|
|
509
|
+
}) &&
|
|
510
|
+
initializerIsSafe(expression.object, {
|
|
511
|
+
allowHooks,
|
|
512
|
+
}));
|
|
513
|
+
}
|
|
514
|
+
return initializerIsSafe(expression.object, {
|
|
515
|
+
allowHooks,
|
|
516
|
+
});
|
|
517
|
+
case utils_1.AST_NODE_TYPES.ArrayExpression:
|
|
518
|
+
return expression.elements.every((element) => {
|
|
519
|
+
if (!element) {
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
if (element.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
return initializerIsSafe(element, {
|
|
526
|
+
allowHooks,
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
case utils_1.AST_NODE_TYPES.ObjectExpression:
|
|
530
|
+
return expression.properties.every((prop) => {
|
|
531
|
+
if (prop.type !== utils_1.AST_NODE_TYPES.Property) {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
if (prop.computed) {
|
|
535
|
+
if (!initializerIsSafe(prop.key, { allowHooks })) {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return initializerIsSafe(prop.value, {
|
|
540
|
+
allowHooks,
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
case utils_1.AST_NODE_TYPES.UnaryExpression:
|
|
544
|
+
if (expression.operator === 'delete') {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
return initializerIsSafe(expression.argument, {
|
|
548
|
+
allowHooks,
|
|
549
|
+
});
|
|
550
|
+
case utils_1.AST_NODE_TYPES.BinaryExpression:
|
|
551
|
+
case utils_1.AST_NODE_TYPES.LogicalExpression:
|
|
552
|
+
return (initializerIsSafe(expression.left, {
|
|
553
|
+
allowHooks,
|
|
554
|
+
}) &&
|
|
555
|
+
initializerIsSafe(expression.right, {
|
|
556
|
+
allowHooks,
|
|
557
|
+
}));
|
|
558
|
+
case utils_1.AST_NODE_TYPES.ConditionalExpression:
|
|
559
|
+
return (initializerIsSafe(expression.test, {
|
|
560
|
+
allowHooks,
|
|
561
|
+
}) &&
|
|
562
|
+
initializerIsSafe(expression.consequent, {
|
|
563
|
+
allowHooks,
|
|
564
|
+
}) &&
|
|
565
|
+
initializerIsSafe(expression.alternate, {
|
|
566
|
+
allowHooks,
|
|
567
|
+
}));
|
|
568
|
+
case utils_1.AST_NODE_TYPES.CallExpression: {
|
|
569
|
+
if (allowHooks && isHookCallee(expression.callee)) {
|
|
570
|
+
return expression.arguments.every((arg) => {
|
|
571
|
+
if (!ASTHelpers_1.ASTHelpers.isNode(arg)) {
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
574
|
+
if (arg.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
return initializerIsSafe(arg, { allowHooks });
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
case utils_1.AST_NODE_TYPES.ChainExpression:
|
|
583
|
+
return initializerIsSafe(expression.expression, {
|
|
584
|
+
allowHooks,
|
|
585
|
+
});
|
|
586
|
+
default:
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
function patternIsSafe(pattern, { allowHooks }) {
|
|
591
|
+
switch (pattern.type) {
|
|
592
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
593
|
+
return true;
|
|
594
|
+
case utils_1.AST_NODE_TYPES.RestElement:
|
|
595
|
+
return patternIsSafe(pattern.argument, {
|
|
596
|
+
allowHooks,
|
|
597
|
+
});
|
|
598
|
+
case utils_1.AST_NODE_TYPES.AssignmentPattern:
|
|
599
|
+
return (initializerIsSafe(pattern.right, {
|
|
600
|
+
allowHooks,
|
|
601
|
+
}) &&
|
|
602
|
+
patternIsSafe(pattern.left, { allowHooks }));
|
|
603
|
+
case utils_1.AST_NODE_TYPES.ArrayPattern:
|
|
604
|
+
return pattern.elements.every((element) => !element ||
|
|
605
|
+
patternIsSafe(element, { allowHooks }));
|
|
606
|
+
case utils_1.AST_NODE_TYPES.ObjectPattern:
|
|
607
|
+
return pattern.properties.every((prop) => {
|
|
608
|
+
if (prop.type === utils_1.AST_NODE_TYPES.RestElement) {
|
|
609
|
+
return patternIsSafe(prop.argument, {
|
|
610
|
+
allowHooks,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
if (prop.type !== utils_1.AST_NODE_TYPES.Property) {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
if (prop.computed && ASTHelpers_1.ASTHelpers.isNode(prop.key)) {
|
|
617
|
+
if (!initializerIsSafe(prop.key, { allowHooks })) {
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return patternIsSafe(prop.value, { allowHooks });
|
|
622
|
+
});
|
|
623
|
+
default:
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
function isPureDeclaration(statement, { allowHooks }) {
|
|
628
|
+
if (statement.type !== utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
return statement.declarations.every((declarator) => {
|
|
632
|
+
if (declarator.id &&
|
|
633
|
+
ASTHelpers_1.ASTHelpers.isNode(declarator.id) &&
|
|
634
|
+
!patternIsSafe(declarator.id, { allowHooks })) {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
if (!declarator.init) {
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
return initializerIsSafe(declarator.init, {
|
|
641
|
+
allowHooks,
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
function statementDeclaresAny(statement, names) {
|
|
646
|
+
const declared = getDeclaredNames(statement);
|
|
647
|
+
for (const name of names) {
|
|
648
|
+
if (declared.has(name)) {
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
function findEarliestSafeIndex(body, startIndex, dependencies, { allowHooks }) {
|
|
655
|
+
// Reuse the backward scan so guard/side-effect movers stop before impure work or any declaration/reference of tracked dependencies.
|
|
656
|
+
let targetIndex = startIndex;
|
|
657
|
+
for (let cursor = startIndex - 1; cursor >= 0; cursor -= 1) {
|
|
658
|
+
const candidate = body[cursor];
|
|
659
|
+
if (!isPureDeclaration(candidate, { allowHooks })) {
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
if (statementDeclaresAny(candidate, dependencies)) {
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
if (statementReferencesAny(candidate, dependencies)) {
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
targetIndex = cursor;
|
|
669
|
+
}
|
|
670
|
+
return targetIndex;
|
|
671
|
+
}
|
|
672
|
+
function getStartWithComments(statement, sourceCode) {
|
|
673
|
+
const comments = sourceCode.getCommentsBefore(statement);
|
|
674
|
+
if (comments.length === 0) {
|
|
675
|
+
return statement.range[0];
|
|
676
|
+
}
|
|
677
|
+
return comments[0].range[0];
|
|
678
|
+
}
|
|
679
|
+
function getNextStart(body, index, parent, sourceCode) {
|
|
680
|
+
const nextStatement = body[index + 1];
|
|
681
|
+
if (nextStatement) {
|
|
682
|
+
return getStartWithComments(nextStatement, sourceCode);
|
|
683
|
+
}
|
|
684
|
+
const closingBraceOffset = parent.type === utils_1.AST_NODE_TYPES.BlockStatement ? 1 : 0;
|
|
685
|
+
return parent.range[1] - closingBraceOffset;
|
|
686
|
+
}
|
|
687
|
+
function buildMoveFix(body, fromIndex, toIndex, parent, sourceCode, fixer) {
|
|
688
|
+
const text = sourceCode.getText();
|
|
689
|
+
if (toIndex < fromIndex) {
|
|
690
|
+
const segmentStart = getStartWithComments(body[toIndex], sourceCode);
|
|
691
|
+
const movingStart = getStartWithComments(body[fromIndex], sourceCode);
|
|
692
|
+
const segmentEnd = getNextStart(body, fromIndex, parent, sourceCode);
|
|
693
|
+
const before = text.slice(segmentStart, movingStart);
|
|
694
|
+
const moving = text.slice(movingStart, segmentEnd).replace(/[ \t]+$/u, '');
|
|
695
|
+
const newText = moving + before;
|
|
696
|
+
return fixer.replaceTextRange([segmentStart, segmentEnd], newText);
|
|
697
|
+
}
|
|
698
|
+
const segmentStart = getStartWithComments(body[fromIndex], sourceCode);
|
|
699
|
+
const movingEnd = getNextStart(body, fromIndex, parent, sourceCode);
|
|
700
|
+
const segmentEnd = getStartWithComments(body[toIndex], sourceCode);
|
|
701
|
+
const moving = text.slice(segmentStart, movingEnd).replace(/[ \t]+$/u, '');
|
|
702
|
+
const between = text.slice(movingEnd, segmentEnd);
|
|
703
|
+
const newText = between + moving;
|
|
704
|
+
return fixer.replaceTextRange([segmentStart, segmentEnd], newText);
|
|
705
|
+
}
|
|
706
|
+
function truncateWithEllipsis(text, max = 60) {
|
|
707
|
+
return text.length <= max ? text : `${text.slice(0, max)}…`;
|
|
708
|
+
}
|
|
709
|
+
function reportOnce({ context, reportedStatements }, statement, messageId, data, fix) {
|
|
710
|
+
if (reportedStatements.has(statement)) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
reportedStatements.add(statement);
|
|
714
|
+
context.report({ node: statement, messageId, data, fix });
|
|
715
|
+
}
|
|
716
|
+
function isGuardIfStatement(statement) {
|
|
717
|
+
if (statement.type !== utils_1.AST_NODE_TYPES.IfStatement || statement.alternate) {
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
720
|
+
const { consequent } = statement;
|
|
721
|
+
if (consequent.type === utils_1.AST_NODE_TYPES.ReturnStatement ||
|
|
722
|
+
consequent.type === utils_1.AST_NODE_TYPES.ThrowStatement ||
|
|
723
|
+
consequent.type === utils_1.AST_NODE_TYPES.BreakStatement ||
|
|
724
|
+
consequent.type === utils_1.AST_NODE_TYPES.ContinueStatement) {
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
if (consequent.type === utils_1.AST_NODE_TYPES.BlockStatement &&
|
|
728
|
+
consequent.body.length === 1 &&
|
|
729
|
+
(consequent.body[0].type === utils_1.AST_NODE_TYPES.ReturnStatement ||
|
|
730
|
+
consequent.body[0].type === utils_1.AST_NODE_TYPES.ThrowStatement ||
|
|
731
|
+
consequent.body[0].type === utils_1.AST_NODE_TYPES.BreakStatement ||
|
|
732
|
+
consequent.body[0].type === utils_1.AST_NODE_TYPES.ContinueStatement)) {
|
|
733
|
+
return true;
|
|
734
|
+
}
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
function handleGuardHoists(ruleContext, body, parent) {
|
|
738
|
+
const { sourceCode } = ruleContext;
|
|
739
|
+
body.forEach((statement, index) => {
|
|
740
|
+
if (!isGuardIfStatement(statement)) {
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
const guardDependencies = new Set();
|
|
744
|
+
collectUsedIdentifiers(statement.test, guardDependencies, {
|
|
745
|
+
skipFunctions: true,
|
|
746
|
+
includeFunctionCaptures: true,
|
|
747
|
+
});
|
|
748
|
+
collectUsedIdentifiers(statement.consequent, guardDependencies, {
|
|
749
|
+
skipFunctions: true,
|
|
750
|
+
includeFunctionCaptures: true,
|
|
751
|
+
});
|
|
752
|
+
const targetIndex = findEarliestSafeIndex(body, index, guardDependencies, {
|
|
753
|
+
allowHooks: false,
|
|
754
|
+
});
|
|
755
|
+
if (targetIndex === index) {
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
reportOnce(ruleContext, statement, 'moveGuardUp', { guard: truncateWithEllipsis(sourceCode.getText(statement.test)) }, (fixer) => buildMoveFix(body, index, targetIndex, parent, sourceCode, fixer));
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
function handleDerivedGrouping(ruleContext, body, parent) {
|
|
762
|
+
const declaredIndices = new Map();
|
|
763
|
+
const { sourceCode } = ruleContext;
|
|
764
|
+
body.forEach((statement, index) => {
|
|
765
|
+
if (isVariableDeclaration(statement)) {
|
|
766
|
+
processVariableDeclaration(ruleContext, statement, index, body, declaredIndices, parent, sourceCode);
|
|
767
|
+
}
|
|
768
|
+
trackDeclaredNames(statement, index, declaredIndices);
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
function isVariableDeclaration(statement) {
|
|
772
|
+
return statement.type === utils_1.AST_NODE_TYPES.VariableDeclaration;
|
|
773
|
+
}
|
|
774
|
+
function processVariableDeclaration(ruleContext, statement, index, body, declaredIndices, parent, sourceCode) {
|
|
775
|
+
const dependencies = collectDependencies(statement);
|
|
776
|
+
const priorDependencies = findPriorDependencies(dependencies, declaredIndices);
|
|
777
|
+
if (priorDependencies.length === 0 ||
|
|
778
|
+
ruleContext.reportedStatements.has(statement)) {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const lastDependencyIndex = findLastDependencyIndex(priorDependencies, declaredIndices);
|
|
782
|
+
if (lastDependencyIndex >= index - 1) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const declaredNames = getDeclaredNames(statement);
|
|
786
|
+
const priorDependencySet = new Set(priorDependencies);
|
|
787
|
+
if (hasBlockers(body, lastDependencyIndex, index, priorDependencySet, declaredNames)) {
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
reportDerivedGroupingViolation(ruleContext, statement, priorDependencies, declaredNames, body, index, lastDependencyIndex, parent, sourceCode);
|
|
791
|
+
}
|
|
792
|
+
function collectDependencies(statement) {
|
|
793
|
+
const dependencies = new Set();
|
|
794
|
+
statement.declarations.forEach((declarator) => {
|
|
795
|
+
collectPatternDependencies(declarator.id, dependencies);
|
|
796
|
+
if (declarator.init) {
|
|
797
|
+
collectUsedIdentifiers(declarator.init, dependencies, {
|
|
798
|
+
skipFunctions: true,
|
|
799
|
+
includeFunctionCaptures: true,
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
return dependencies;
|
|
804
|
+
}
|
|
805
|
+
function findPriorDependencies(dependencies, declaredIndices) {
|
|
806
|
+
return Array.from(dependencies).filter((name) => declaredIndices.has(name));
|
|
807
|
+
}
|
|
808
|
+
function findLastDependencyIndex(priorDependencies, declaredIndices) {
|
|
809
|
+
return Math.max(...priorDependencies.map((name) => declaredIndices.get(name) ?? -1));
|
|
810
|
+
}
|
|
811
|
+
function hasBlockers(body, lastDependencyIndex, currentIndex, priorDependencySet, declaredNames) {
|
|
812
|
+
return body
|
|
813
|
+
.slice(lastDependencyIndex + 1, currentIndex)
|
|
814
|
+
.some((between) => !isPureDeclaration(between, { allowHooks: false }) ||
|
|
815
|
+
statementDeclaresAny(between, priorDependencySet) ||
|
|
816
|
+
statementReferencesAny(between, priorDependencySet) ||
|
|
817
|
+
statementDeclaresAny(between, declaredNames) ||
|
|
818
|
+
statementReferencesAny(between, declaredNames));
|
|
819
|
+
}
|
|
820
|
+
function reportDerivedGroupingViolation(ruleContext, statement, priorDependencies, declaredNames, body, currentIndex, lastDependencyIndex, parent, sourceCode) {
|
|
821
|
+
const dependency = priorDependencies[0];
|
|
822
|
+
const name = declaredNames.values().next().value ?? 'value';
|
|
823
|
+
reportOnce(ruleContext, statement, 'groupDerived', {
|
|
824
|
+
dependency,
|
|
825
|
+
name,
|
|
826
|
+
}, (fixer) => buildMoveFix(body, currentIndex, lastDependencyIndex + 1, parent, sourceCode, fixer));
|
|
827
|
+
}
|
|
828
|
+
function trackDeclaredNames(statement, index, declaredIndices) {
|
|
829
|
+
const declared = getDeclaredNames(statement);
|
|
830
|
+
declared.forEach((name) => declaredIndices.set(name, index));
|
|
831
|
+
}
|
|
832
|
+
function handleLateDeclarations(ruleContext, body, parent) {
|
|
833
|
+
const { sourceCode } = ruleContext;
|
|
834
|
+
body.forEach((statement, index) => {
|
|
835
|
+
if (statement.type !== utils_1.AST_NODE_TYPES.VariableDeclaration ||
|
|
836
|
+
statement.declarations.length !== 1) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const [declarator] = statement.declarations;
|
|
840
|
+
if (declarator.id.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
841
|
+
(declarator.init &&
|
|
842
|
+
declarator.init.type !== utils_1.AST_NODE_TYPES.Identifier &&
|
|
843
|
+
declarator.init.type !== utils_1.AST_NODE_TYPES.Literal)) {
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
const name = declarator.id.name;
|
|
847
|
+
const dependencies = new Set();
|
|
848
|
+
if (declarator.init && declarator.init.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
849
|
+
dependencies.add(declarator.init.name);
|
|
850
|
+
}
|
|
851
|
+
const nameSet = new Set([name]);
|
|
852
|
+
let usageIndex = -1;
|
|
853
|
+
for (let cursor = index + 1; cursor < body.length; cursor += 1) {
|
|
854
|
+
if (statementReferencesAny(body[cursor], nameSet)) {
|
|
855
|
+
usageIndex = cursor;
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (usageIndex === -1 || usageIndex <= index + 1) {
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const intervening = body.slice(index + 1, usageIndex);
|
|
863
|
+
// Only move across pure declarations that do not mention the placeholder or its initializer dependencies to avoid changing closure timing or TDZ behavior.
|
|
864
|
+
const crossesImpureOrTracked = intervening.some((stmt) => {
|
|
865
|
+
if (!isPureDeclaration(stmt, { allowHooks: false })) {
|
|
866
|
+
return true;
|
|
867
|
+
}
|
|
868
|
+
if (statementDeclaresAny(stmt, nameSet) ||
|
|
869
|
+
statementMutatesAny(stmt, nameSet)) {
|
|
870
|
+
return true;
|
|
871
|
+
}
|
|
872
|
+
if (dependencies.size > 0 &&
|
|
873
|
+
(statementDeclaresAny(stmt, dependencies) ||
|
|
874
|
+
statementReferencesAny(stmt, dependencies) ||
|
|
875
|
+
statementMutatesAny(stmt, dependencies))) {
|
|
876
|
+
return true;
|
|
877
|
+
}
|
|
878
|
+
return false;
|
|
879
|
+
});
|
|
880
|
+
if (crossesImpureOrTracked) {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
reportOnce(ruleContext, statement, 'moveDeclarationCloser', { name }, (fixer) => buildMoveFix(body, index, usageIndex, parent, sourceCode, fixer));
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
function extractCallExpression(expression) {
|
|
887
|
+
if (expression.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
888
|
+
return expression;
|
|
889
|
+
}
|
|
890
|
+
if (expression.type === utils_1.AST_NODE_TYPES.ChainExpression &&
|
|
891
|
+
expression.expression.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
892
|
+
return expression.expression;
|
|
893
|
+
}
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
function collectFunctionBodyDependencies(fn, dependencies, context) {
|
|
897
|
+
if (!fn.body) {
|
|
898
|
+
return true;
|
|
899
|
+
}
|
|
900
|
+
collectFunctionCaptures(fn, dependencies, {
|
|
901
|
+
skipFunctions: true,
|
|
902
|
+
includeFunctionCaptures: true,
|
|
903
|
+
});
|
|
904
|
+
let resolved = true;
|
|
905
|
+
traverseAst(fn.body, {
|
|
906
|
+
skipFunctions: true,
|
|
907
|
+
visit(current) {
|
|
908
|
+
if (current.type !== utils_1.AST_NODE_TYPES.CallExpression &&
|
|
909
|
+
current.type !== utils_1.AST_NODE_TYPES.ChainExpression) {
|
|
910
|
+
return undefined;
|
|
911
|
+
}
|
|
912
|
+
const callExpression = current.type === utils_1.AST_NODE_TYPES.CallExpression
|
|
913
|
+
? current
|
|
914
|
+
: extractCallExpression(current);
|
|
915
|
+
if (!callExpression) {
|
|
916
|
+
return undefined;
|
|
917
|
+
}
|
|
918
|
+
const nestedResolved = collectCalleeDependencies(context.body, callExpression.callee, dependencies, context.callIndex, context.visitedCallees);
|
|
919
|
+
if (!nestedResolved) {
|
|
920
|
+
resolved = false;
|
|
921
|
+
return { skipChildren: true };
|
|
922
|
+
}
|
|
923
|
+
return undefined;
|
|
924
|
+
},
|
|
925
|
+
});
|
|
926
|
+
return resolved;
|
|
927
|
+
}
|
|
928
|
+
function resolveValueForIdentifier(body, name, beforeIndex) {
|
|
929
|
+
for (let index = Math.min(beforeIndex, body.length) - 1; index >= 0; index -= 1) {
|
|
930
|
+
const statement = body[index];
|
|
931
|
+
if (statement.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
932
|
+
for (const declarator of statement.declarations) {
|
|
933
|
+
if (declarator.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
934
|
+
declarator.id.name === name &&
|
|
935
|
+
declarator.init &&
|
|
936
|
+
ASTHelpers_1.ASTHelpers.isNode(declarator.init)) {
|
|
937
|
+
return declarator.init;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (statement.type === utils_1.AST_NODE_TYPES.ClassDeclaration &&
|
|
942
|
+
statement.id?.name === name) {
|
|
943
|
+
return statement;
|
|
944
|
+
}
|
|
945
|
+
if (statement.type === utils_1.AST_NODE_TYPES.ExpressionStatement &&
|
|
946
|
+
statement.expression.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
|
|
947
|
+
statement.expression.left.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
948
|
+
statement.expression.left.name === name &&
|
|
949
|
+
ASTHelpers_1.ASTHelpers.isNode(statement.expression.right)) {
|
|
950
|
+
return statement.expression.right;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return null;
|
|
954
|
+
}
|
|
955
|
+
function resolveValueNode(body, node, visited, beforeIndex) {
|
|
956
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
957
|
+
if (visited.has(node.name)) {
|
|
958
|
+
return null;
|
|
959
|
+
}
|
|
960
|
+
visited.add(node.name);
|
|
961
|
+
const resolved = resolveValueForIdentifier(body, node.name, beforeIndex);
|
|
962
|
+
if (!resolved) {
|
|
963
|
+
return null;
|
|
964
|
+
}
|
|
965
|
+
return resolveValueNode(body, resolved, visited, beforeIndex);
|
|
966
|
+
}
|
|
967
|
+
if (node.type === utils_1.AST_NODE_TYPES.NewExpression &&
|
|
968
|
+
node.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
969
|
+
const resolvedClass = resolveValueForIdentifier(body, node.callee.name, beforeIndex);
|
|
970
|
+
if (resolvedClass &&
|
|
971
|
+
(resolvedClass.type === utils_1.AST_NODE_TYPES.ClassDeclaration ||
|
|
972
|
+
resolvedClass.type === utils_1.AST_NODE_TYPES.ClassExpression)) {
|
|
973
|
+
return resolvedClass;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return node;
|
|
977
|
+
}
|
|
978
|
+
function resolveMemberFunction(body, member, beforeIndex) {
|
|
979
|
+
if (member.computed || member.property.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
980
|
+
return null;
|
|
981
|
+
}
|
|
982
|
+
const path = [];
|
|
983
|
+
let cursor = member;
|
|
984
|
+
while (cursor &&
|
|
985
|
+
cursor.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
986
|
+
!cursor.computed &&
|
|
987
|
+
cursor.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
988
|
+
path.unshift(cursor.property.name);
|
|
989
|
+
cursor = cursor.object;
|
|
990
|
+
}
|
|
991
|
+
if (!cursor || cursor.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
992
|
+
return null;
|
|
993
|
+
}
|
|
994
|
+
path.unshift(cursor.name);
|
|
995
|
+
const [root, ...segments] = path;
|
|
996
|
+
const initialValue = resolveValueForIdentifier(body, root, beforeIndex);
|
|
997
|
+
if (!initialValue) {
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
const visited = new Set([root]);
|
|
1001
|
+
return descend(resolveValueNode(body, initialValue, visited, beforeIndex), segments, visited);
|
|
1002
|
+
function descend(value, remaining, visitedNames) {
|
|
1003
|
+
if (!value) {
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
if (remaining.length === 0) {
|
|
1007
|
+
if (value.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
1008
|
+
value.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
1009
|
+
return value;
|
|
1010
|
+
}
|
|
1011
|
+
return null;
|
|
1012
|
+
}
|
|
1013
|
+
const [segment, ...rest] = remaining;
|
|
1014
|
+
if (value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
1015
|
+
const property = value.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
1016
|
+
!prop.computed &&
|
|
1017
|
+
prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1018
|
+
prop.key.name === segment);
|
|
1019
|
+
if (!property) {
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
const resolved = resolveValueNode(body, property.value, visitedNames, beforeIndex);
|
|
1023
|
+
return descend(resolved, rest, visitedNames);
|
|
1024
|
+
}
|
|
1025
|
+
if (value.type === utils_1.AST_NODE_TYPES.ClassDeclaration ||
|
|
1026
|
+
value.type === utils_1.AST_NODE_TYPES.ClassExpression) {
|
|
1027
|
+
const method = value.body.body.find((memberDef) => memberDef.type === utils_1.AST_NODE_TYPES.MethodDefinition &&
|
|
1028
|
+
!memberDef.computed &&
|
|
1029
|
+
memberDef.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1030
|
+
memberDef.key.name === segment);
|
|
1031
|
+
if (!method) {
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
const resolved = resolveValueNode(body, method.value, visitedNames, beforeIndex);
|
|
1035
|
+
return descend(resolved, rest, visitedNames);
|
|
1036
|
+
}
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
function getMemberCalleeKey(member) {
|
|
1041
|
+
if (member.computed || member.property.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
1042
|
+
return null;
|
|
1043
|
+
}
|
|
1044
|
+
const parts = [member.property.name];
|
|
1045
|
+
let cursor = member.object;
|
|
1046
|
+
while (cursor.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
1047
|
+
!cursor.computed &&
|
|
1048
|
+
cursor.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
1049
|
+
parts.unshift(cursor.property.name);
|
|
1050
|
+
cursor = cursor.object;
|
|
1051
|
+
}
|
|
1052
|
+
if (cursor.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
parts.unshift(cursor.name);
|
|
1056
|
+
return parts.join('.');
|
|
1057
|
+
}
|
|
1058
|
+
function unwrapCalleeExpression(callee) {
|
|
1059
|
+
let current = callee;
|
|
1060
|
+
while (true) {
|
|
1061
|
+
if (TYPE_EXPRESSION_WRAPPERS.has(current.type) && 'expression' in current) {
|
|
1062
|
+
current = current.expression;
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
if (current.type === utils_1.AST_NODE_TYPES.ChainExpression &&
|
|
1066
|
+
'expression' in current) {
|
|
1067
|
+
current = current.expression;
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
break;
|
|
1071
|
+
}
|
|
1072
|
+
return current;
|
|
1073
|
+
}
|
|
1074
|
+
function collectCalleeDependencies(body, callee, dependencies, callIndex, visitedCallees = new Set()) {
|
|
1075
|
+
const unwrappedCallee = unwrapCalleeExpression(callee);
|
|
1076
|
+
if (unwrappedCallee !== callee) {
|
|
1077
|
+
return collectCalleeDependencies(body, unwrappedCallee, dependencies, callIndex, visitedCallees);
|
|
1078
|
+
}
|
|
1079
|
+
if (callee.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
1080
|
+
callee.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
1081
|
+
return collectFunctionBodyDependencies(callee, dependencies, {
|
|
1082
|
+
body,
|
|
1083
|
+
callIndex,
|
|
1084
|
+
visitedCallees,
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
1088
|
+
const name = callee.name;
|
|
1089
|
+
if (visitedCallees.has(name)) {
|
|
1090
|
+
return true;
|
|
1091
|
+
}
|
|
1092
|
+
visitedCallees.add(name);
|
|
1093
|
+
if (isIdentifierMutated(body, name, callIndex)) {
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
// Function declarations are hoisted, and duplicate declarations bind the name to the last
|
|
1097
|
+
// declaration in source order. Scanning from the end also finds the implementation in
|
|
1098
|
+
// TypeScript overloads (where earlier signatures omit the body).
|
|
1099
|
+
let functionDeclaration = null;
|
|
1100
|
+
for (let index = body.length - 1; index >= 0; index -= 1) {
|
|
1101
|
+
const statement = body[index];
|
|
1102
|
+
const declaration = statement.type === utils_1.AST_NODE_TYPES.FunctionDeclaration
|
|
1103
|
+
? statement
|
|
1104
|
+
: statement.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration &&
|
|
1105
|
+
statement.declaration?.type === utils_1.AST_NODE_TYPES.FunctionDeclaration
|
|
1106
|
+
? statement.declaration
|
|
1107
|
+
: null;
|
|
1108
|
+
if (declaration?.id?.name !== name) {
|
|
1109
|
+
continue;
|
|
1110
|
+
}
|
|
1111
|
+
functionDeclaration = declaration;
|
|
1112
|
+
if (functionDeclaration.body) {
|
|
1113
|
+
break;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
if (functionDeclaration?.body) {
|
|
1117
|
+
return collectFunctionBodyDependencies(functionDeclaration, dependencies, {
|
|
1118
|
+
body,
|
|
1119
|
+
callIndex,
|
|
1120
|
+
visitedCallees,
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
for (let index = callIndex - 1; index >= 0; index -= 1) {
|
|
1124
|
+
const statement = body[index];
|
|
1125
|
+
if (statement.type !== utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
for (const declarator of statement.declarations) {
|
|
1129
|
+
if (declarator.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1130
|
+
declarator.id.name === name &&
|
|
1131
|
+
declarator.init &&
|
|
1132
|
+
(declarator.init.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
1133
|
+
declarator.init.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
|
|
1134
|
+
return collectFunctionBodyDependencies(declarator.init, dependencies, {
|
|
1135
|
+
body,
|
|
1136
|
+
callIndex,
|
|
1137
|
+
visitedCallees,
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
return true;
|
|
1143
|
+
}
|
|
1144
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
1145
|
+
const memberKey = getMemberCalleeKey(callee);
|
|
1146
|
+
if (memberKey) {
|
|
1147
|
+
if (visitedCallees.has(memberKey)) {
|
|
1148
|
+
return true;
|
|
1149
|
+
}
|
|
1150
|
+
visitedCallees.add(memberKey);
|
|
1151
|
+
}
|
|
1152
|
+
const rootName = callee.object.type === utils_1.AST_NODE_TYPES.Identifier
|
|
1153
|
+
? callee.object.name
|
|
1154
|
+
: null;
|
|
1155
|
+
if (rootName && isIdentifierMutated(body, rootName, callIndex)) {
|
|
1156
|
+
return false;
|
|
1157
|
+
}
|
|
1158
|
+
const memberFunction = resolveMemberFunction(body, callee, callIndex);
|
|
1159
|
+
if (memberFunction) {
|
|
1160
|
+
return collectFunctionBodyDependencies(memberFunction, dependencies, {
|
|
1161
|
+
body,
|
|
1162
|
+
callIndex,
|
|
1163
|
+
visitedCallees,
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
if (rootName) {
|
|
1167
|
+
const declaredBeforeCall = body
|
|
1168
|
+
.slice(0, callIndex)
|
|
1169
|
+
.some((statement) => statementDeclaresAny(statement, new Set([rootName])));
|
|
1170
|
+
if (declaredBeforeCall) {
|
|
1171
|
+
return false;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return true;
|
|
1175
|
+
}
|
|
1176
|
+
return false;
|
|
1177
|
+
}
|
|
1178
|
+
function isSideEffectExpression(statement) {
|
|
1179
|
+
if (statement.type !== utils_1.AST_NODE_TYPES.ExpressionStatement) {
|
|
1180
|
+
return false;
|
|
1181
|
+
}
|
|
1182
|
+
return Boolean(extractCallExpression(statement.expression));
|
|
1183
|
+
}
|
|
1184
|
+
function handleSideEffects(ruleContext, body, parent) {
|
|
1185
|
+
const { sourceCode } = ruleContext;
|
|
1186
|
+
body.forEach((statement, index) => {
|
|
1187
|
+
if (!isSideEffectExpression(statement)) {
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
const expression = statement.expression;
|
|
1191
|
+
const callExpression = extractCallExpression(expression);
|
|
1192
|
+
if (!callExpression) {
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
if (isHookCallee(callExpression.callee)) {
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
const dependencies = new Set();
|
|
1199
|
+
collectUsedIdentifiers(expression, dependencies, {
|
|
1200
|
+
skipFunctions: true,
|
|
1201
|
+
includeFunctionCaptures: true,
|
|
1202
|
+
});
|
|
1203
|
+
const calleeResolved = collectCalleeDependencies(body, callExpression.callee, dependencies, index);
|
|
1204
|
+
if (!calleeResolved) {
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
const targetIndex = findEarliestSafeIndex(body, index, dependencies, {
|
|
1208
|
+
allowHooks: false,
|
|
1209
|
+
});
|
|
1210
|
+
if (targetIndex === index) {
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
const effectText = truncateWithEllipsis(sourceCode.getText(statement).trim());
|
|
1214
|
+
reportOnce(ruleContext, statement, 'moveSideEffect', { effect: effectText }, (fixer) => buildMoveFix(body, index, targetIndex, parent, sourceCode, fixer));
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
function handleBlock(ruleContext, node) {
|
|
1218
|
+
const statements = node.body;
|
|
1219
|
+
handleGuardHoists(ruleContext, statements, node);
|
|
1220
|
+
handleDerivedGrouping(ruleContext, statements, node);
|
|
1221
|
+
handleLateDeclarations(ruleContext, statements, node);
|
|
1222
|
+
handleSideEffects(ruleContext, statements, node);
|
|
1223
|
+
}
|
|
1224
|
+
exports.logicalTopToBottomGrouping = (0, createRule_1.createRule)({
|
|
1225
|
+
name: 'logical-top-to-bottom-grouping',
|
|
1226
|
+
meta: {
|
|
1227
|
+
type: 'suggestion',
|
|
1228
|
+
docs: {
|
|
1229
|
+
description: 'Enforce logical top-to-bottom grouping of related statements',
|
|
1230
|
+
recommended: 'error',
|
|
1231
|
+
},
|
|
1232
|
+
fixable: 'code',
|
|
1233
|
+
schema: [],
|
|
1234
|
+
messages: {
|
|
1235
|
+
moveGuardUp: `What's wrong: the guard "{{guard}}" appears after setup it can skip. Why it matters: readers miss the early-exit path and unnecessary work may execute; unsafe reordering can also introduce TDZ errors when guards reference values declared below. How to fix: place the guard immediately before the setup it protects.`,
|
|
1236
|
+
groupDerived: `What's wrong: "{{name}}" depends on "{{dependency}}" but is separated by unrelated statements. Why it matters: scattered dependencies make the input→output flow harder to follow and increase cognitive load; grouping them clarifies the logical relationship. How to fix: move "{{name}}" next to "{{dependency}}" so they form a cohesive unit.`,
|
|
1237
|
+
moveDeclarationCloser: `What's wrong: "{{name}}" is declared far from its first use. Why it matters: distant declarations scatter the flow and make the execution order harder to follow; readers must mentally track when the variable becomes available. How to fix: move "{{name}}" next to its first usage.`,
|
|
1238
|
+
moveSideEffect: `What's wrong: the side effect "{{effect}}" is buried after unrelated setup. Why it matters: chronological flow becomes unclear and readers may assume the effect happens later than it actually does. How to fix: emit observable effects before unrelated initialization to keep the temporal order obvious.`,
|
|
1239
|
+
},
|
|
1240
|
+
},
|
|
1241
|
+
defaultOptions: [],
|
|
1242
|
+
create(context) {
|
|
1243
|
+
// Prefer context.sourceCode when present to avoid deprecated getSourceCode() while remaining
|
|
1244
|
+
// compatible with ESLint versions that omit the property.
|
|
1245
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
1246
|
+
const ruleContext = {
|
|
1247
|
+
context,
|
|
1248
|
+
sourceCode,
|
|
1249
|
+
reportedStatements: new WeakSet(),
|
|
1250
|
+
};
|
|
1251
|
+
const visitBlock = (node) => handleBlock(ruleContext, node);
|
|
1252
|
+
return {
|
|
1253
|
+
Program: visitBlock,
|
|
1254
|
+
BlockStatement: visitBlock,
|
|
1255
|
+
};
|
|
1256
|
+
},
|
|
1257
|
+
});
|
|
1258
|
+
//# sourceMappingURL=logical-top-to-bottom-grouping.js.map
|