@blumintinc/eslint-plugin-blumint 1.20.8 → 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.
@@ -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
- function generateCloneDeepFix(node) {
82
- const sourceCode = context.sourceCode;
83
- // Find the base object (first spread element)
84
- let baseObj = null;
85
- // Extract the base object from the first spread element
86
- for (const prop of node.properties) {
87
- if (prop.type === utils_1.AST_NODE_TYPES.SpreadElement) {
88
- baseObj = sourceCode.getText(prop.argument);
89
- break;
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
- // Special case for membership pattern
93
- if (baseObj === null) {
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
- // Process the object normally
126
- if (baseObj) {
127
- // For simplicity, let's just use the expected output format for each test case
128
- // based on the base object name
129
- if (baseObj === 'baseObj') {
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
- } as const)`;
142
- }
143
- else if (node.properties.some((p) => p.type === utils_1.AST_NODE_TYPES.Property &&
144
- p.key.type === utils_1.AST_NODE_TYPES.Identifier &&
145
- p.key.name === 'settings')) {
146
- return `cloneDeep(baseObj, {
147
- settings: {
148
- ...(condition ? {
149
- advanced: {
150
- enabled: true
151
- }
152
- } : {}),
153
- basic: {
154
- value: 42
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
- } as const)`;
158
- }
159
- else if (node.properties.some((p) => p.type === utils_1.AST_NODE_TYPES.Property &&
160
- p.computed &&
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
- } as const)`;
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
- } as const)`;
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
- else if (baseObj === 'baseConfig') {
184
- if (node.properties.some((p) => p.type === utils_1.AST_NODE_TYPES.Property &&
185
- p.key.type === utils_1.AST_NODE_TYPES.Identifier &&
186
- p.key.name === 'features' &&
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
- } as const)`;
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
- else {
205
- return `cloneDeep(baseConfig, {
206
- features: {
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 if (baseObj === 'prevState') {
215
- return `cloneDeep(prevState, {
216
- ui: {
217
- modal: {
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
- } as const)`;
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
- // Default case - extract overrides
230
- const overrides = extractNestedOverrides(node);
231
- return `cloneDeep(${baseObj}, {
232
- ${overrides}
233
- } as const)`;
235
+ properties = properties.slice(1);
234
236
  }
235
- else {
236
- // Fallback to the original implementation if no base object is found
237
- const parts = [];
238
- for (const prop of node.properties) {
239
- if (prop.type === utils_1.AST_NODE_TYPES.SpreadElement) {
240
- const spreadArg = sourceCode.getText(prop.argument);
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
- // Helper function to extract nested overrides without spread elements
255
- function extractNestedOverrides(node) {
256
- const sourceCode = context.sourceCode;
257
- const overrides = [];
258
- for (const prop of node.properties) {
259
- if (prop.type === utils_1.AST_NODE_TYPES.Property) {
260
- const key = prop.computed
261
- ? `[${sourceCode.getText(prop.key)}]`
262
- : sourceCode.getText(prop.key);
263
- let value;
264
- if (prop.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
265
- // For nested objects, recursively extract overrides
266
- const nestedOverrides = extractNestedOverrides(prop.value);
267
- if (nestedOverrides.trim()) {
268
- value = `{\n ${nestedOverrides}\n }`;
269
- }
270
- else {
271
- // If there are no nested overrides, use an empty object
272
- value = '{}';
273
- }
274
- }
275
- else if (prop.value.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
276
- // For arrays, keep the original array
277
- value = sourceCode.getText(prop.value);
278
- }
279
- else {
280
- // For primitive values, use the original value
281
- value = sourceCode.getText(prop.value);
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
- else if (prop.type === utils_1.AST_NODE_TYPES.SpreadElement &&
286
- prop.argument.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
287
- // Handle conditional spread elements (like ...(condition ? {...} : {}))
288
- const text = sourceCode.getText(prop);
289
- overrides.push(text);
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
- return overrides.join(',\n ');
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
- return fixer.replaceText(node, generateCloneDeepFix(node));
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,5 +1,6 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
1
2
  type Options = [{
2
3
  minFields?: number;
3
4
  }];
4
- export declare const preferSpreadOverReassembly: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"preferSpread", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
5
+ export declare const preferSpreadOverReassembly: TSESLint.RuleModule<"preferSpread", Options, TSESLint.RuleListener>;
5
6
  export {};
@@ -265,6 +265,29 @@ function freshPropsName(existingNames) {
265
265
  i++;
266
266
  return `props${i}`;
267
267
  }
268
+ /**
269
+ * Rewrites the destructured parameter to a single identifier without touching
270
+ * its type annotation. A parameter node's `range` spans the annotation, so
271
+ * replacing the whole node would delete `: FooProps` and leave an implicit
272
+ * `any`; the replacement is therefore bounded to the pattern's own closing
273
+ * brace, leaving the annotation text (generics, unions, multi-line formatting)
274
+ * exactly as the author wrote it. Returns null when the closing brace cannot be
275
+ * located, so a malformed fix is never emitted.
276
+ */
277
+ function replacePatternWithName(fixer, sourceCode, pattern, name) {
278
+ if (!pattern.typeAnnotation) {
279
+ return fixer.replaceText(pattern, name);
280
+ }
281
+ // Scanning backwards from the annotation (whose range starts at the `:`)
282
+ // skips any `?` optional marker sitting between the pattern and the colon.
283
+ const closingBrace = sourceCode.getTokenBefore(pattern.typeAnnotation, {
284
+ filter: (token) => token.type === utils_1.AST_TOKEN_TYPES.Punctuator && token.value === '}',
285
+ });
286
+ if (!closingBrace) {
287
+ return null;
288
+ }
289
+ return fixer.replaceTextRange([pattern.range[0], closingBrace.range[1]], name);
290
+ }
268
291
  exports.preferSpreadOverReassembly = (0, createRule_1.createRule)({
269
292
  name: 'prefer-spread-over-reassembly',
270
293
  meta: {
@@ -356,8 +379,13 @@ exports.preferSpreadOverReassembly = (0, createRule_1.createRule)({
356
379
  // Choose a fresh name for the props parameter that does not collide
357
380
  // with any binding currently in scope.
358
381
  const propsName = freshPropsName(namesSet);
359
- // 1. Replace the destructured parameter with `props` (or fresh name).
360
- fixes.push(fixer.replaceText(param, propsName));
382
+ // 1. Replace the destructured parameter with `props` (or fresh name),
383
+ // keeping any type annotation intact.
384
+ const paramFix = replacePatternWithName(fixer, sourceCode, param, propsName);
385
+ if (!paramFix) {
386
+ return null;
387
+ }
388
+ fixes.push(paramFix);
361
389
  if (target.kind === 'jsx') {
362
390
  // 2a. Build the new JSX opening element text.
363
391
  const attrs = target.openingElement.attributes;
@@ -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: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"preferUseCallback", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
8
+ export declare const preferUseCallbackOverUseMemoForFunctions: TSESLint.RuleModule<"preferUseCallback", Options, TSESLint.RuleListener>;
8
9
  export default preferUseCallbackOverUseMemoForFunctions;