@blumintinc/eslint-plugin-blumint 1.16.2 → 1.17.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.
Files changed (49) hide show
  1. package/README.md +21 -0
  2. package/lib/index.js +64 -1
  3. package/lib/rules/enforce-boolean-naming-prefixes.js +30 -14
  4. package/lib/rules/enforce-cloud-function-id-length.d.ts +5 -0
  5. package/lib/rules/enforce-cloud-function-id-length.js +104 -0
  6. package/lib/rules/enforce-is-prefix-validators.d.ts +13 -0
  7. package/lib/rules/enforce-is-prefix-validators.js +304 -0
  8. package/lib/rules/enforce-m3-sentence-case.d.ts +12 -0
  9. package/lib/rules/enforce-m3-sentence-case.js +430 -0
  10. package/lib/rules/enforce-snapshot-state-narrowing.d.ts +10 -0
  11. package/lib/rules/enforce-snapshot-state-narrowing.js +281 -0
  12. package/lib/rules/enforce-types-directory-placement.d.ts +9 -0
  13. package/lib/rules/enforce-types-directory-placement.js +276 -0
  14. package/lib/rules/no-direct-function-state.d.ts +8 -0
  15. package/lib/rules/no-direct-function-state.js +285 -0
  16. package/lib/rules/no-fill-template-mutation.d.ts +1 -0
  17. package/lib/rules/no-fill-template-mutation.js +324 -0
  18. package/lib/rules/no-portal-inside-tooltip.d.ts +10 -0
  19. package/lib/rules/no-portal-inside-tooltip.js +219 -0
  20. package/lib/rules/no-redundant-boolean-callback-props.d.ts +11 -0
  21. package/lib/rules/no-redundant-boolean-callback-props.js +355 -0
  22. package/lib/rules/no-satisfies-in-frontend-bundle.d.ts +8 -0
  23. package/lib/rules/no-satisfies-in-frontend-bundle.js +126 -0
  24. package/lib/rules/no-single-dismiss-dialog-button.d.ts +7 -0
  25. package/lib/rules/no-single-dismiss-dialog-button.js +127 -0
  26. package/lib/rules/no-stablehash-react-nodes.d.ts +1 -0
  27. package/lib/rules/no-stablehash-react-nodes.js +325 -0
  28. package/lib/rules/parallelize-loop-awaits.d.ts +8 -0
  29. package/lib/rules/parallelize-loop-awaits.js +582 -0
  30. package/lib/rules/prefer-flat-transform-each-keys.d.ts +1 -0
  31. package/lib/rules/prefer-flat-transform-each-keys.js +228 -0
  32. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +1 -0
  33. package/lib/rules/prefer-getter-over-parameterless-method.js +44 -0
  34. package/lib/rules/prefer-spread-over-reassembly.d.ts +5 -0
  35. package/lib/rules/prefer-spread-over-reassembly.js +401 -0
  36. package/lib/rules/prefer-sx-prop-over-system-props.d.ts +9 -0
  37. package/lib/rules/prefer-sx-prop-over-system-props.js +401 -0
  38. package/lib/rules/prefer-use-base62-id.d.ts +8 -0
  39. package/lib/rules/prefer-use-base62-id.js +483 -0
  40. package/lib/rules/prefer-use-theme.d.ts +1 -0
  41. package/lib/rules/prefer-use-theme.js +206 -0
  42. package/lib/rules/prefer-utility-function-own-file.d.ts +9 -0
  43. package/lib/rules/prefer-utility-function-own-file.js +505 -0
  44. package/lib/rules/require-props-composition.d.ts +10 -0
  45. package/lib/rules/require-props-composition.js +433 -0
  46. package/lib/rules/require-server-timestamp-for-firestore-dates.d.ts +9 -0
  47. package/lib/rules/require-server-timestamp-for-firestore-dates.js +313 -0
  48. package/package.json +1 -1
  49. package/release-manifest.json +190 -0
@@ -0,0 +1,9 @@
1
+ type Options = [
2
+ {
3
+ minStatements?: number;
4
+ minLines?: number;
5
+ ignoreClosures?: boolean;
6
+ }
7
+ ];
8
+ export declare const preferUtilityFunctionOwnFile: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"extractUtility", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
9
+ export {};
@@ -0,0 +1,505 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.preferUtilityFunctionOwnFile = void 0;
27
+ const path = __importStar(require("path"));
28
+ const utils_1 = require("@typescript-eslint/utils");
29
+ const createRule_1 = require("../utils/createRule");
30
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
31
+ const DEFAULT_MIN_STATEMENTS = 8;
32
+ const DEFAULT_MIN_LINES = 12;
33
+ const DEFAULT_IGNORE_CLOSURES = true;
34
+ /**
35
+ * Collects all identifiers referenced in a node body (for closure detection).
36
+ * Returns the set of identifier names referenced anywhere inside the node,
37
+ * not counting parameter names or locally-declared names.
38
+ */
39
+ function collectReferencedIdentifiers(node) {
40
+ const refs = new Set();
41
+ const locals = new Set();
42
+ function walk(n) {
43
+ if (!n)
44
+ return;
45
+ switch (n.type) {
46
+ case utils_1.AST_NODE_TYPES.Identifier:
47
+ refs.add(n.name);
48
+ break;
49
+ case utils_1.AST_NODE_TYPES.FunctionDeclaration:
50
+ case utils_1.AST_NODE_TYPES.FunctionExpression:
51
+ case utils_1.AST_NODE_TYPES.ArrowFunctionExpression: {
52
+ const fn = n;
53
+ // Record parameter names as locals
54
+ for (const param of fn.params) {
55
+ collectPatternNames(param, locals);
56
+ }
57
+ walk(fn.body);
58
+ break;
59
+ }
60
+ case utils_1.AST_NODE_TYPES.VariableDeclarator: {
61
+ const decl = n;
62
+ collectPatternNames(decl.id, locals);
63
+ walk(decl.init);
64
+ break;
65
+ }
66
+ default: {
67
+ // Walk all child nodes
68
+ for (const key of Object.keys(n)) {
69
+ if (key === 'parent')
70
+ continue;
71
+ const child = n[key];
72
+ if (Array.isArray(child)) {
73
+ for (const item of child) {
74
+ if (item && typeof item === 'object' && 'type' in item) {
75
+ walk(item);
76
+ }
77
+ }
78
+ }
79
+ else if (child && typeof child === 'object' && 'type' in child) {
80
+ walk(child);
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ walk(node);
87
+ // Remove purely local names from the reference set
88
+ for (const local of locals) {
89
+ refs.delete(local);
90
+ }
91
+ return refs;
92
+ }
93
+ /**
94
+ * Collects all binding names from a pattern into the given Set.
95
+ */
96
+ function collectPatternNames(node, names) {
97
+ if (!node)
98
+ return;
99
+ switch (node.type) {
100
+ case utils_1.AST_NODE_TYPES.Identifier:
101
+ names.add(node.name);
102
+ break;
103
+ case utils_1.AST_NODE_TYPES.ArrayPattern:
104
+ for (const el of node.elements) {
105
+ if (el)
106
+ collectPatternNames(el, names);
107
+ }
108
+ break;
109
+ case utils_1.AST_NODE_TYPES.ObjectPattern:
110
+ for (const prop of node.properties) {
111
+ if (prop.type === utils_1.AST_NODE_TYPES.RestElement) {
112
+ collectPatternNames(prop.argument, names);
113
+ }
114
+ else {
115
+ collectPatternNames(prop.value, names);
116
+ }
117
+ }
118
+ break;
119
+ case utils_1.AST_NODE_TYPES.AssignmentPattern:
120
+ collectPatternNames(node.left, names);
121
+ break;
122
+ case utils_1.AST_NODE_TYPES.RestElement:
123
+ collectPatternNames(node.argument, names);
124
+ break;
125
+ default:
126
+ break;
127
+ }
128
+ }
129
+ /**
130
+ * Returns the body node (BlockStatement or expression) of a function.
131
+ */
132
+ function getFunctionBody(fn) {
133
+ return fn.body ?? null;
134
+ }
135
+ /**
136
+ * Counts statements inside a BlockStatement body (top level only).
137
+ */
138
+ function countStatements(fn) {
139
+ const body = getFunctionBody(fn);
140
+ if (!body)
141
+ return 0;
142
+ if (body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
143
+ return body.body.length;
144
+ }
145
+ // Arrow function with expression body — count as 1 statement
146
+ return 1;
147
+ }
148
+ /**
149
+ * Counts the number of source lines spanned by a function node.
150
+ */
151
+ function countLines(fn) {
152
+ if (!fn.loc)
153
+ return 0;
154
+ return fn.loc.end.line - fn.loc.start.line + 1;
155
+ }
156
+ /**
157
+ * Returns the function expression node if the VariableDeclarator initializer
158
+ * is (unwrapped) an arrow or function expression.
159
+ */
160
+ function extractFunctionInit(init) {
161
+ if (!init)
162
+ return null;
163
+ let node = init;
164
+ // Unwrap TS wrappers
165
+ while (node &&
166
+ (node.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
167
+ node.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
168
+ node.type === utils_1.AST_NODE_TYPES.TSNonNullExpression ||
169
+ node.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
170
+ node.type === 'ParenthesizedExpression')) {
171
+ node = node.expression ?? null;
172
+ }
173
+ if (!node)
174
+ return null;
175
+ if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
176
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
177
+ return node;
178
+ }
179
+ return null;
180
+ }
181
+ /**
182
+ * Returns true if the function returns JSX (is a React component).
183
+ * We check the body for any JSXElement or JSXFragment return.
184
+ */
185
+ function functionReturnsJSX(fn) {
186
+ return ASTHelpers_1.ASTHelpers.returnsJSX(fn);
187
+ }
188
+ /**
189
+ * Returns true if the name matches the React hook naming convention.
190
+ */
191
+ function isHookName(name) {
192
+ return /^use[A-Z]/.test(name);
193
+ }
194
+ /**
195
+ * Extracts the basename (without extension) from a filename.
196
+ * E.g. "modifyRoleMembers.f.ts" → "modifyRoleMembers"
197
+ */
198
+ function fileBasename(filename) {
199
+ const base = path.basename(filename);
200
+ // Strip all extensions (e.g. .f.ts, .test.ts)
201
+ return base.replace(/(\.\w+)+$/, '');
202
+ }
203
+ /**
204
+ * Returns true if the file should be exempt from this rule.
205
+ * Exempt: test/spec files, __mocks__ directories, type-only files under types/**
206
+ */
207
+ function isExemptFile(filename) {
208
+ if (!filename)
209
+ return false;
210
+ const normalized = filename.replace(/\\/g, '/');
211
+ // Test and spec files
212
+ if (/\.(test|spec)\.(tsx?|jsx?)$/.test(normalized))
213
+ return true;
214
+ // Files inside __mocks__ directories
215
+ if (/\/__mocks__\//.test(normalized))
216
+ return true;
217
+ // Type files (functions/src/types/**)
218
+ if (/\/types\//.test(normalized))
219
+ return true;
220
+ return false;
221
+ }
222
+ exports.preferUtilityFunctionOwnFile = (0, createRule_1.createRule)({
223
+ name: 'prefer-utility-function-own-file',
224
+ meta: {
225
+ type: 'suggestion',
226
+ docs: {
227
+ description: 'Enforce that sizable utility functions live in their own file rather than being co-located inside an entry-point or consumer file',
228
+ recommended: 'warn',
229
+ },
230
+ schema: [
231
+ {
232
+ type: 'object',
233
+ properties: {
234
+ minStatements: {
235
+ type: 'number',
236
+ minimum: 1,
237
+ default: DEFAULT_MIN_STATEMENTS,
238
+ },
239
+ minLines: {
240
+ type: 'number',
241
+ minimum: 1,
242
+ default: DEFAULT_MIN_LINES,
243
+ },
244
+ ignoreClosures: {
245
+ type: 'boolean',
246
+ default: DEFAULT_IGNORE_CLOSURES,
247
+ },
248
+ },
249
+ additionalProperties: false,
250
+ },
251
+ ],
252
+ messages: {
253
+ extractUtility: '"{{name}}" is a sizable utility function co-located in a file whose primary purpose is something else. Move it to its own file (e.g. under util/) so it is discoverable and reusable.',
254
+ },
255
+ },
256
+ defaultOptions: [{}],
257
+ create(context, [options]) {
258
+ const minStatements = options.minStatements ?? DEFAULT_MIN_STATEMENTS;
259
+ const minLines = options.minLines ?? DEFAULT_MIN_LINES;
260
+ const ignoreClosures = options.ignoreClosures ?? DEFAULT_IGNORE_CLOSURES;
261
+ const filename = context.getFilename();
262
+ // Exempt test/mock/type files entirely
263
+ if (isExemptFile(filename))
264
+ return {};
265
+ const basename = fileBasename(filename);
266
+ const topLevelFunctions = [];
267
+ let hasExportDefault = false;
268
+ // Names that appear as the handler inside `export default someWrapper(name)`
269
+ const wrappedDefaultHandlerNames = new Set();
270
+ // Names directly exported as default (export default myFunc style via ExportDefaultDeclaration referencing an identifier)
271
+ const defaultExportedIdentifiers = new Set();
272
+ // Names exported via `export { foo, bar }` specifiers
273
+ const specifierExportedNames = new Set();
274
+ return {
275
+ // Collect export default declarations
276
+ ExportDefaultDeclaration(node) {
277
+ hasExportDefault = true;
278
+ const decl = node.declaration;
279
+ // export default myFunc
280
+ if (decl.type === utils_1.AST_NODE_TYPES.Identifier) {
281
+ defaultExportedIdentifiers.add(decl.name);
282
+ }
283
+ // export default onCall(authenticatedOnly(myFunc))
284
+ if (decl.type === utils_1.AST_NODE_TYPES.CallExpression) {
285
+ // walk the call expression tree for identifier references
286
+ const findWrappedIds = (expr) => {
287
+ let e = expr;
288
+ while (e &&
289
+ (e.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
290
+ e.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
291
+ e.type === utils_1.AST_NODE_TYPES.TSNonNullExpression ||
292
+ e.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
293
+ e.type === 'ParenthesizedExpression')) {
294
+ e = e.expression;
295
+ }
296
+ if (!e)
297
+ return;
298
+ if (e.type === utils_1.AST_NODE_TYPES.Identifier) {
299
+ wrappedDefaultHandlerNames.add(e.name);
300
+ }
301
+ if (e.type === utils_1.AST_NODE_TYPES.CallExpression) {
302
+ for (const arg of e.arguments) {
303
+ findWrappedIds(arg);
304
+ }
305
+ }
306
+ };
307
+ findWrappedIds(decl);
308
+ }
309
+ },
310
+ // Collect names exported via specifiers: `export { foo, bar }`
311
+ ExportNamedDeclaration(node) {
312
+ // Only handle specifier-style exports (not `export const foo = ...` which
313
+ // is handled by VariableDeclaration with ExportNamedDeclaration parent)
314
+ if (node.declaration)
315
+ return;
316
+ for (const specifier of node.specifiers) {
317
+ if (specifier.local.type === utils_1.AST_NODE_TYPES.Identifier) {
318
+ specifierExportedNames.add(specifier.local.name);
319
+ }
320
+ }
321
+ },
322
+ // Collect top-level FunctionDeclarations
323
+ FunctionDeclaration(node) {
324
+ // Only top-level: parent must be Program or ExportNamedDeclaration or ExportDefaultDeclaration
325
+ const parent = node.parent;
326
+ const isTopLevel = parent?.type === utils_1.AST_NODE_TYPES.Program ||
327
+ parent?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration ||
328
+ parent?.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration;
329
+ if (!isTopLevel)
330
+ return;
331
+ if (!node.id)
332
+ return;
333
+ const name = node.id.name;
334
+ const isNamedExport = parent?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration;
335
+ const isDefaultExport = parent?.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration;
336
+ topLevelFunctions.push({
337
+ node,
338
+ name,
339
+ fn: node,
340
+ isDefaultExport,
341
+ isNamedExport,
342
+ });
343
+ },
344
+ // Collect top-level arrow/function expression VariableDeclarators
345
+ VariableDeclaration(node) {
346
+ // Only top-level
347
+ const parent = node.parent;
348
+ const isTopLevel = parent?.type === utils_1.AST_NODE_TYPES.Program ||
349
+ parent?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration;
350
+ if (!isTopLevel)
351
+ return;
352
+ const isNamedExport = parent?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration;
353
+ for (const declarator of node.declarations) {
354
+ if (declarator.id.type !== utils_1.AST_NODE_TYPES.Identifier)
355
+ continue;
356
+ const name = declarator.id.name;
357
+ const fn = extractFunctionInit(declarator.init);
358
+ if (!fn)
359
+ continue;
360
+ topLevelFunctions.push({
361
+ node: declarator,
362
+ name,
363
+ fn,
364
+ isDefaultExport: false,
365
+ isNamedExport,
366
+ });
367
+ }
368
+ },
369
+ 'Program:exit'() {
370
+ // Determine the file's primary export name (basename heuristic)
371
+ // e.g. "modifyRoleMembers" in "modifyRoleMembers.f.ts"
372
+ const primaryName = basename;
373
+ // Reconcile isNamedExport for functions exported via specifiers
374
+ // (e.g. `export { foo, bar }` when foo/bar were declared without export keyword)
375
+ for (const info of topLevelFunctions) {
376
+ if (!info.isNamedExport && specifierExportedNames.has(info.name)) {
377
+ info.isNamedExport = true;
378
+ }
379
+ }
380
+ // Determine if the file has a distinct primary export (co-location gate).
381
+ // A file must have SOME other purpose for us to flag anything.
382
+ // Indicators of a distinct primary purpose:
383
+ // 1. Has `export default` (callable entry-point, page, etc.)
384
+ // 2. Has a React component (PascalCase, returning JSX)
385
+ // 3. Has another named exported util that is NOT the function being evaluated
386
+ // We'll compute this lazily per-candidate below.
387
+ // If the file has no export default AND only one top-level function, the
388
+ // file's sole purpose IS that function — it's already in its own file.
389
+ // Never flag in that case.
390
+ const totalFunctions = topLevelFunctions.length;
391
+ if (!hasExportDefault && totalFunctions <= 1)
392
+ return;
393
+ // For each candidate, determine whether to flag
394
+ for (const info of topLevelFunctions) {
395
+ const { node, name, fn, isDefaultExport } = info;
396
+ // --- Exclusion: is a hook ---
397
+ if (isHookName(name))
398
+ continue;
399
+ // --- Exclusion: returns JSX (React component) ---
400
+ if (functionReturnsJSX(fn))
401
+ continue;
402
+ // --- Exclusion: is the default export directly ---
403
+ if (isDefaultExport)
404
+ continue;
405
+ // --- Exclusion: name is referenced in export default (e.g. wrapped handler) ---
406
+ if (wrappedDefaultHandlerNames.has(name) ||
407
+ defaultExportedIdentifiers.has(name))
408
+ continue;
409
+ // --- Exclusion: name matches file basename (primary export heuristic) ---
410
+ if (name === primaryName)
411
+ continue;
412
+ // --- Exclusion: it IS a named exported util but name matches basename ---
413
+ // (already covered above, but be explicit)
414
+ // --- Size check: must be sizable ---
415
+ const stmts = countStatements(fn);
416
+ const lines = countLines(fn);
417
+ const isSizable = stmts >= minStatements || lines >= minLines;
418
+ if (!isSizable)
419
+ continue;
420
+ // --- ignoreClosures: skip if the function body references module-scoped identifiers not passed as params ---
421
+ if (ignoreClosures) {
422
+ const closesOverModuleScope = functionClosesOverModuleScope(fn, topLevelFunctions);
423
+ if (closesOverModuleScope)
424
+ continue;
425
+ }
426
+ // --- Co-location gate: file must have a distinct primary export ---
427
+ // The file has a distinct primary purpose if:
428
+ // - It has export default (and it's not THIS function)
429
+ // - Another top-level function returns JSX (a React component lives here)
430
+ // - Another named exported function exists that is not this one
431
+ const hasDistinctPrimary = determineHasDistinctPrimary(info, topLevelFunctions, hasExportDefault);
432
+ if (!hasDistinctPrimary)
433
+ continue;
434
+ context.report({
435
+ node,
436
+ messageId: 'extractUtility',
437
+ data: { name },
438
+ });
439
+ }
440
+ },
441
+ };
442
+ },
443
+ });
444
+ /**
445
+ * Returns true if the function closes over module-scoped bindings
446
+ * (identifiers defined at the top level of the module) that are NOT
447
+ * passed as parameters, making it non-trivially extractable.
448
+ *
449
+ * We identify "module-scope" identifiers as names of other top-level
450
+ * declarations in the file (functions, variables) that are NOT imported.
451
+ * Imported names and standard globals are considered "extractable" (you'd
452
+ * just re-import them).
453
+ *
454
+ * Strategy: collect referenced identifiers in the body, subtract param names,
455
+ * then check if any remain that are names of other top-level declarations.
456
+ */
457
+ function functionClosesOverModuleScope(fn, topLevelFunctions) {
458
+ const body = getFunctionBody(fn);
459
+ if (!body)
460
+ return false;
461
+ // Collect names that are parameters of this function
462
+ const paramNames = new Set();
463
+ for (const param of fn.params) {
464
+ collectPatternNames(param, paramNames);
465
+ }
466
+ // Collect all identifiers referenced inside the function body
467
+ const referencedIds = collectReferencedIdentifiers(body);
468
+ // Remove params (they're passed in, fine)
469
+ for (const param of paramNames) {
470
+ referencedIds.delete(param);
471
+ }
472
+ // Get the set of top-level sibling function names
473
+ const topLevelNames = new Set(topLevelFunctions.map((f) => f.name));
474
+ // If ANY referenced identifier is a top-level sibling function name,
475
+ // then this function closes over a module-scope binding.
476
+ for (const ref of referencedIds) {
477
+ if (topLevelNames.has(ref)) {
478
+ return true;
479
+ }
480
+ }
481
+ return false;
482
+ }
483
+ /**
484
+ * Determines whether the file has a distinct primary purpose (co-location gate).
485
+ * A file has a distinct primary purpose if (excluding the function under test):
486
+ * - It has an `export default` declaration, OR
487
+ * - Another top-level function returns JSX (a component lives here), OR
488
+ * - Another top-level function is a named export (a different util lives here)
489
+ */
490
+ function determineHasDistinctPrimary(candidate, allFunctions, hasExportDefault) {
491
+ // If there's an export default in the file, that signals a distinct primary
492
+ if (hasExportDefault)
493
+ return true;
494
+ // Check siblings for React components or other named exported utils
495
+ for (const other of allFunctions) {
496
+ if (other.name === candidate.name)
497
+ continue;
498
+ if (functionReturnsJSX(other.fn))
499
+ return true;
500
+ if (other.isNamedExport || other.isDefaultExport)
501
+ return true;
502
+ }
503
+ return false;
504
+ }
505
+ //# sourceMappingURL=prefer-utility-function-own-file.js.map
@@ -0,0 +1,10 @@
1
+ type Options = [
2
+ {
3
+ targetPaths?: string[];
4
+ excludeComponents?: string[];
5
+ minDependencyCount?: number;
6
+ requireAllDependencies?: boolean;
7
+ }
8
+ ];
9
+ export declare const requirePropsComposition: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"missingPropsComposition", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
10
+ export {};