@blumintinc/eslint-plugin-blumint 1.20.9 → 1.20.10
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/lib/index.js +1 -1
- package/lib/rules/enforce-early-destructuring.js +14 -0
- package/lib/rules/enforce-m3-sentence-case.js +211 -45
- package/lib/rules/enforce-props-naming-consistency.js +127 -8
- package/lib/rules/enforce-querykey-ts.js +170 -16
- package/lib/rules/enforce-react-type-naming.d.ts +2 -1
- package/lib/rules/enforce-react-type-naming.js +46 -17
- package/lib/rules/enforce-snapshot-state-narrowing.d.ts +3 -1
- package/lib/rules/enforce-snapshot-state-narrowing.js +213 -53
- package/lib/rules/no-class-instance-destructuring.js +7 -0
- package/lib/rules/no-excessive-parent-chain.js +43 -10
- package/lib/rules/no-unnecessary-destructuring.js +8 -1
- package/lib/rules/prefer-clone-deep.d.ts +2 -1
- package/lib/rules/prefer-clone-deep.js +250 -192
- package/lib/rules/prefer-destructuring-no-class.js +8 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.d.ts +2 -1
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +192 -5
- package/lib/rules/use-custom-memo.d.ts +2 -1
- package/lib/rules/use-custom-memo.js +51 -36
- package/lib/rules/use-latest-callback.js +142 -92
- package/lib/utils/renameFixes.d.ts +69 -0
- package/lib/utils/renameFixes.js +202 -0
- package/package.json +1 -1
- package/release-manifest.json +118 -0
|
@@ -3,6 +3,28 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.preferCloneDeep = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const CLONE_DEEP_NAME = 'cloneDeep';
|
|
7
|
+
const CLONE_DEEP_MODULE = 'functions/src/util/cloneDeep';
|
|
8
|
+
const INDENT_STEP = ' ';
|
|
9
|
+
/**
|
|
10
|
+
* Only BluMint's own `cloneDeep` accepts an overrides argument, so an existing
|
|
11
|
+
* binding coming from anywhere else (notably `lodash`) must not be reused by the
|
|
12
|
+
* fix. Relative/aliased paths are accepted because the helper is imported by
|
|
13
|
+
* path, never as a package.
|
|
14
|
+
*/
|
|
15
|
+
function isCloneDeepModule(source) {
|
|
16
|
+
if (source === CLONE_DEEP_MODULE) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (!/(^|\/)cloneDeep$/.test(source)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return (source.startsWith('.') ||
|
|
23
|
+
source.startsWith('/') ||
|
|
24
|
+
source.startsWith('~') ||
|
|
25
|
+
source.startsWith('@/') ||
|
|
26
|
+
source.endsWith('util/cloneDeep'));
|
|
27
|
+
}
|
|
6
28
|
exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
7
29
|
name: 'prefer-clone-deep',
|
|
8
30
|
meta: {
|
|
@@ -78,218 +100,235 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
78
100
|
!hasFunction &&
|
|
79
101
|
!hasSymbol);
|
|
80
102
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
103
|
+
const sourceCode = context.sourceCode;
|
|
104
|
+
function normalizedTextOf(node) {
|
|
105
|
+
return sourceCode.getText(node).replace(/\s+/g, '');
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* `...(base.a ?? {})`, `...(base?.a)` and `...base.a!` are defensive
|
|
109
|
+
* spellings of `...base.a`; unwrapping them lets the mirror check below
|
|
110
|
+
* recognize the underlying member path.
|
|
111
|
+
*/
|
|
112
|
+
function unwrapSpreadArgument(node) {
|
|
113
|
+
if (node.type === utils_1.AST_NODE_TYPES.ChainExpression) {
|
|
114
|
+
return unwrapSpreadArgument(node.expression);
|
|
91
115
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// Check if this is a membership pattern (object with membership property)
|
|
95
|
-
const membershipProp = node.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
96
|
-
!prop.computed &&
|
|
97
|
-
prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
98
|
-
prop.key.name === 'membership');
|
|
99
|
-
if (membershipProp &&
|
|
100
|
-
membershipProp.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
101
|
-
// Find the first spread in the membership object
|
|
102
|
-
const membershipSpread = membershipProp.value.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.SpreadElement);
|
|
103
|
-
if (membershipSpread) {
|
|
104
|
-
// This is a special case for the membership pattern
|
|
105
|
-
// Just return a hardcoded string that matches the expected output
|
|
106
|
-
return `{
|
|
107
|
-
sender: 'unchanged',
|
|
108
|
-
receiver: 'unchanged',
|
|
109
|
-
membership: cloneDeep(membershipIncomplete, {
|
|
110
|
-
sender: {
|
|
111
|
-
request: {
|
|
112
|
-
status: 'accepted',
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
receiver: {
|
|
116
|
-
request: {
|
|
117
|
-
status: 'accepted',
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
} as const),
|
|
121
|
-
}`;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
116
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
|
|
117
|
+
return unwrapSpreadArgument(node.expression);
|
|
124
118
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// Check for template literal key
|
|
131
|
-
const hasTemplateLiteral = node.properties.some((p) => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
132
|
-
p.computed &&
|
|
133
|
-
p.key.type === utils_1.AST_NODE_TYPES.TemplateLiteral);
|
|
134
|
-
if (hasTemplateLiteral) {
|
|
135
|
-
return `cloneDeep(baseObj, {
|
|
136
|
-
[\`\${prefix}Config\`]: {
|
|
137
|
-
nested: {
|
|
138
|
-
value: 42
|
|
119
|
+
if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression &&
|
|
120
|
+
(node.operator === '??' || node.operator === '||') &&
|
|
121
|
+
node.right.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
122
|
+
node.right.properties.length === 0) {
|
|
123
|
+
return unwrapSpreadArgument(node.left);
|
|
139
124
|
}
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
125
|
+
return node;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Canonical, whitespace-insensitive spelling of a member path so that a
|
|
129
|
+
* nested spread can be compared against the path cloneDeep already copies.
|
|
130
|
+
*/
|
|
131
|
+
function accessPathOf(node) {
|
|
132
|
+
const unwrapped = unwrapSpreadArgument(node);
|
|
133
|
+
if (unwrapped.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
134
|
+
const objectPath = accessPathOf(unwrapped.object);
|
|
135
|
+
if (unwrapped.computed) {
|
|
136
|
+
return `${objectPath}[${normalizedTextOf(unwrapped.property)}]`;
|
|
137
|
+
}
|
|
138
|
+
if (unwrapped.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
139
|
+
return `${objectPath}.${unwrapped.property.name}`;
|
|
140
|
+
}
|
|
141
|
+
return `${objectPath}.${normalizedTextOf(unwrapped.property)}`;
|
|
155
142
|
}
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
p.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
162
|
-
p.key.name === 'key')) {
|
|
163
|
-
return `cloneDeep(baseObj, {
|
|
164
|
-
[key]: {
|
|
165
|
-
nested: {
|
|
166
|
-
['dynamic' + key]: {
|
|
167
|
-
value: 42
|
|
168
|
-
}
|
|
143
|
+
return normalizedTextOf(unwrapped);
|
|
144
|
+
}
|
|
145
|
+
function accessorForKey(prop) {
|
|
146
|
+
if (prop.computed) {
|
|
147
|
+
return `[${normalizedTextOf(prop.key)}]`;
|
|
169
148
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
return `cloneDeep(baseObj, {
|
|
175
|
-
data: {
|
|
176
|
-
nested: {
|
|
177
|
-
value: 42
|
|
149
|
+
if (prop.key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
150
|
+
return `.${prop.key.name}`;
|
|
178
151
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
152
|
+
if (prop.key.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
153
|
+
return `[${normalizedTextOf(prop.key)}]`;
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
function keyTextOf(prop) {
|
|
158
|
+
return prop.computed
|
|
159
|
+
? `[${sourceCode.getText(prop.key)}]`
|
|
160
|
+
: sourceCode.getText(prop.key);
|
|
161
|
+
}
|
|
162
|
+
function indentOf(node) {
|
|
163
|
+
const text = sourceCode.getText();
|
|
164
|
+
const lineStart = text.lastIndexOf('\n', node.range[0] - 1) + 1;
|
|
165
|
+
const prefix = text.slice(lineStart, node.range[0]);
|
|
166
|
+
return /^[ \t]*/.exec(prefix)?.[0] ?? '';
|
|
167
|
+
}
|
|
168
|
+
function startsWithSpread(node) {
|
|
169
|
+
return node.properties[0]?.type === utils_1.AST_NODE_TYPES.SpreadElement;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* `SourceCode#getScope` supersedes the deprecated `context.getScope`; the
|
|
173
|
+
* fallback keeps the rule working on ESLint versions that predate it.
|
|
174
|
+
*/
|
|
175
|
+
function scopeOf(node) {
|
|
176
|
+
const scoped = sourceCode;
|
|
177
|
+
return typeof scoped.getScope === 'function'
|
|
178
|
+
? scoped.getScope(node)
|
|
179
|
+
: context.getScope();
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Rebuilds the properties of one object as cloneDeep overrides. Returns null
|
|
183
|
+
* whenever a property cannot be reproduced without changing runtime
|
|
184
|
+
* behavior, which makes the caller decline the fix instead of emitting code
|
|
185
|
+
* that silently drops data (#1364).
|
|
186
|
+
*/
|
|
187
|
+
function buildOverrideEntries(properties, basePath, indent) {
|
|
188
|
+
const entries = [];
|
|
189
|
+
for (const prop of properties) {
|
|
190
|
+
// Any spread other than a leading copy of the path cloneDeep already
|
|
191
|
+
// clones would have to be dropped, deleting whatever it contributed.
|
|
192
|
+
if (prop.type !== utils_1.AST_NODE_TYPES.Property) {
|
|
193
|
+
return null;
|
|
182
194
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
p.value.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
188
|
-
p.value.properties.some((sp) => sp.type === utils_1.AST_NODE_TYPES.Property &&
|
|
189
|
-
sp.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
190
|
-
sp.key.name === 'items'))) {
|
|
191
|
-
return `cloneDeep(baseConfig, {
|
|
192
|
-
features: {
|
|
193
|
-
items: [
|
|
194
|
-
...baseConfig.features.items,
|
|
195
|
-
{
|
|
196
|
-
settings: {
|
|
197
|
-
enabled: true
|
|
195
|
+
// Getters, setters and shorthand methods degrade into plain data
|
|
196
|
+
// properties when reprinted as overrides.
|
|
197
|
+
if (prop.kind !== 'init' || prop.method) {
|
|
198
|
+
return null;
|
|
198
199
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
let valueText;
|
|
201
|
+
if (prop.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
202
|
+
const accessor = accessorForKey(prop);
|
|
203
|
+
if (accessor === null) {
|
|
204
|
+
return null;
|
|
203
205
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
advanced: {
|
|
208
|
-
enabled: true
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
} as const)`;
|
|
206
|
+
const nested = buildOverrideObject(prop.value, `${basePath}${accessor}`, `${indent}${INDENT_STEP}`);
|
|
207
|
+
if (nested === null) {
|
|
208
|
+
return null;
|
|
212
209
|
}
|
|
210
|
+
valueText = nested;
|
|
213
211
|
}
|
|
214
|
-
else
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
content: {
|
|
219
|
-
form: {
|
|
220
|
-
values: {
|
|
221
|
-
submitted: true
|
|
222
|
-
}
|
|
212
|
+
else {
|
|
213
|
+
// Arrays, calls, conditionals and primitives are copied verbatim, so
|
|
214
|
+
// whatever they contain survives the fix untouched.
|
|
215
|
+
valueText = sourceCode.getText(prop.value);
|
|
223
216
|
}
|
|
224
|
-
|
|
217
|
+
entries.push(`${indent}${keyTextOf(prop)}: ${valueText}`);
|
|
225
218
|
}
|
|
226
|
-
|
|
227
|
-
}
|
|
219
|
+
return entries.join(',\n');
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* `indent` is the indentation of the rebuilt object's entries; its closing
|
|
223
|
+
* brace lines up one step to the left.
|
|
224
|
+
*/
|
|
225
|
+
function buildOverrideObject(node, basePath, indent) {
|
|
226
|
+
let properties = [...node.properties];
|
|
227
|
+
const [first] = properties;
|
|
228
|
+
if (first && first.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
229
|
+
// A leading spread of this exact path is redundant: cloneDeep already
|
|
230
|
+
// copies it from the base. Any other spread carries data that the
|
|
231
|
+
// overrides object cannot express.
|
|
232
|
+
if (accessPathOf(first.argument) !== basePath) {
|
|
233
|
+
return null;
|
|
228
234
|
}
|
|
229
|
-
|
|
230
|
-
const overrides = extractNestedOverrides(node);
|
|
231
|
-
return `cloneDeep(${baseObj}, {
|
|
232
|
-
${overrides}
|
|
233
|
-
} as const)`;
|
|
235
|
+
properties = properties.slice(1);
|
|
234
236
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
parts.push(`...${spreadArg}`);
|
|
242
|
-
}
|
|
243
|
-
else if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
244
|
-
const key = prop.computed
|
|
245
|
-
? `[${sourceCode.getText(prop.key)}]`
|
|
246
|
-
: sourceCode.getText(prop.key);
|
|
247
|
-
const value = sourceCode.getText(prop.value);
|
|
248
|
-
parts.push(`${key}: ${value}`);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
return `cloneDeep({ ${parts.join(', ')} }, {} as const)`;
|
|
237
|
+
const body = buildOverrideEntries(properties, basePath, indent);
|
|
238
|
+
if (body === null) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
if (body === '') {
|
|
242
|
+
return '{}';
|
|
252
243
|
}
|
|
244
|
+
const closingIndent = indent.slice(0, indent.length - INDENT_STEP.length);
|
|
245
|
+
return `{\n${body}\n${closingIndent}}`;
|
|
253
246
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
247
|
+
function buildCloneDeepCall(node) {
|
|
248
|
+
const [first, ...rest] = node.properties;
|
|
249
|
+
if (!first || first.type !== utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
const baseText = sourceCode.getText(first.argument);
|
|
253
|
+
const basePath = accessPathOf(first.argument);
|
|
254
|
+
const baseIndent = indentOf(node);
|
|
255
|
+
const body = buildOverrideEntries(rest, basePath, `${baseIndent}${INDENT_STEP}`);
|
|
256
|
+
if (body === null) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
const overrides = body === '' ? '{}' : `{\n${body}\n${baseIndent}}`;
|
|
260
|
+
return `cloneDeep(${baseText}, ${overrides} as const)`;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* When the reported object has no spread of its own (the #365 membership
|
|
264
|
+
* shape), the object that actually shallow-copies a base lives one or more
|
|
265
|
+
* levels down; rewriting only those children leaves the rest of the literal
|
|
266
|
+
* untouched.
|
|
267
|
+
*/
|
|
268
|
+
function collectCloneTargets(node) {
|
|
269
|
+
const targets = [];
|
|
270
|
+
const visit = (current) => {
|
|
271
|
+
if (current !== node &&
|
|
272
|
+
startsWithSpread(current) &&
|
|
273
|
+
hasNestedSpread(current)) {
|
|
274
|
+
targets.push(current);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
for (const prop of current.properties) {
|
|
278
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
279
|
+
prop.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
280
|
+
visit(prop.value);
|
|
282
281
|
}
|
|
283
|
-
overrides.push(`${key}: ${value}`);
|
|
284
282
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
283
|
+
};
|
|
284
|
+
visit(node);
|
|
285
|
+
return targets;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Returns the fixes required for `cloneDeep` to resolve, an empty list when
|
|
289
|
+
* it already does, or null when a conflicting binding of that name exists —
|
|
290
|
+
* shadowing it would silently call something else.
|
|
291
|
+
*/
|
|
292
|
+
function buildImportFixes(fixer, scope) {
|
|
293
|
+
const existing = utils_1.ASTUtils.findVariable(scope, CLONE_DEEP_NAME);
|
|
294
|
+
if (existing) {
|
|
295
|
+
const [definition] = existing.defs;
|
|
296
|
+
if (!definition) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
const definitionNode = definition.node;
|
|
300
|
+
if (definitionNode.type !== utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
301
|
+
definitionNode.type !== utils_1.AST_NODE_TYPES.ImportDefaultSpecifier) {
|
|
302
|
+
return null;
|
|
290
303
|
}
|
|
304
|
+
if (definitionNode.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
305
|
+
definitionNode.importKind === 'type') {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
const declaration = definitionNode.parent;
|
|
309
|
+
if (!declaration ||
|
|
310
|
+
declaration.type !== utils_1.AST_NODE_TYPES.ImportDeclaration ||
|
|
311
|
+
declaration.importKind === 'type' ||
|
|
312
|
+
!isCloneDeepModule(String(declaration.source.value))) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
const importDeclarations = sourceCode.ast.body.filter((statement) => statement.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
|
|
318
|
+
const reusable = importDeclarations.find((declaration) => declaration.importKind !== 'type' &&
|
|
319
|
+
isCloneDeepModule(String(declaration.source.value)) &&
|
|
320
|
+
declaration.specifiers.some((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier));
|
|
321
|
+
if (reusable) {
|
|
322
|
+
const namedSpecifiers = reusable.specifiers.filter((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier);
|
|
323
|
+
const lastSpecifier = namedSpecifiers[namedSpecifiers.length - 1];
|
|
324
|
+
return [fixer.insertTextAfter(lastSpecifier, `, ${CLONE_DEEP_NAME}`)];
|
|
291
325
|
}
|
|
292
|
-
|
|
326
|
+
const importText = `import { ${CLONE_DEEP_NAME} } from '${CLONE_DEEP_MODULE}';\n`;
|
|
327
|
+
const [firstImport] = importDeclarations;
|
|
328
|
+
if (firstImport) {
|
|
329
|
+
return [fixer.insertTextBefore(firstImport, importText)];
|
|
330
|
+
}
|
|
331
|
+
return [fixer.insertTextBeforeRange([0, 0], importText)];
|
|
293
332
|
}
|
|
294
333
|
// Find the outermost object expression that needs cloneDeep
|
|
295
334
|
function findOutermostObjectWithNestedSpread(node) {
|
|
@@ -338,11 +377,30 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
338
377
|
markProcessed(outermostNode);
|
|
339
378
|
// Only report on the outermost node
|
|
340
379
|
if (outermostNode === node) {
|
|
380
|
+
const scope = scopeOf(node);
|
|
341
381
|
context.report({
|
|
342
382
|
node,
|
|
343
383
|
messageId: 'preferCloneDeep',
|
|
344
384
|
fix(fixer) {
|
|
345
|
-
|
|
385
|
+
const rewrites = [];
|
|
386
|
+
const targets = startsWithSpread(node)
|
|
387
|
+
? [node]
|
|
388
|
+
: collectCloneTargets(node);
|
|
389
|
+
if (targets.length === 0) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
for (const target of targets) {
|
|
393
|
+
const call = buildCloneDeepCall(target);
|
|
394
|
+
if (call === null) {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
rewrites.push(fixer.replaceText(target, call));
|
|
398
|
+
}
|
|
399
|
+
const importFixes = buildImportFixes(fixer, scope);
|
|
400
|
+
if (importFixes === null) {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
return [...importFixes, ...rewrites];
|
|
346
404
|
},
|
|
347
405
|
});
|
|
348
406
|
}
|
|
@@ -295,6 +295,14 @@ exports.preferDestructuringNoClass = (0, createRule_1.createRule)({
|
|
|
295
295
|
if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
296
296
|
return null;
|
|
297
297
|
}
|
|
298
|
+
// The fix re-emits the declaration as `kind { binding } = object`, which has
|
|
299
|
+
// nowhere to carry an explicit annotation on the binding. Relocating it as
|
|
300
|
+
// `const { alpha }: { alpha: string } = obj` would assert something different
|
|
301
|
+
// (a structural constraint on the source object rather than the variable's
|
|
302
|
+
// declared type) and can shift inference, so withhold the fix and report only.
|
|
303
|
+
if (node.id.typeAnnotation) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
298
306
|
const destructuringBinding = getDestructuringBindingText(memberExpr, propertyText, node.id.name);
|
|
299
307
|
if (!destructuringBinding) {
|
|
300
308
|
return null;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
1
2
|
type Options = [
|
|
2
3
|
{
|
|
3
4
|
allowComplexBodies?: boolean;
|
|
4
5
|
allowFunctionFactories?: boolean;
|
|
5
6
|
}
|
|
6
7
|
];
|
|
7
|
-
export declare const preferUseCallbackOverUseMemoForFunctions:
|
|
8
|
+
export declare const preferUseCallbackOverUseMemoForFunctions: TSESLint.RuleModule<"preferUseCallback", Options, TSESLint.RuleListener>;
|
|
8
9
|
export default preferUseCallbackOverUseMemoForFunctions;
|