@blumintinc/eslint-plugin-blumint 1.13.0 → 1.15.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 (199) hide show
  1. package/README.md +169 -139
  2. package/lib/index.js +200 -9
  3. package/lib/rules/array-methods-this-context.js +1 -1
  4. package/lib/rules/avoid-utils-directory.js +0 -4
  5. package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
  6. package/lib/rules/consistent-callback-naming.js +43 -4
  7. package/lib/rules/dynamic-https-errors.d.ts +1 -1
  8. package/lib/rules/dynamic-https-errors.js +134 -44
  9. package/lib/rules/enforce-assert-safe-object-key.js +11 -11
  10. package/lib/rules/enforce-boolean-naming-prefixes.js +11 -235
  11. package/lib/rules/enforce-callback-memo.js +1 -1
  12. package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
  13. package/lib/rules/enforce-console-error.js +35 -7
  14. package/lib/rules/enforce-date-ttime.d.ts +1 -0
  15. package/lib/rules/enforce-date-ttime.js +156 -0
  16. package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
  17. package/lib/rules/enforce-dynamic-file-naming.js +53 -28
  18. package/lib/rules/enforce-dynamic-firebase-imports.d.ts +2 -1
  19. package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
  20. package/lib/rules/enforce-early-destructuring.d.ts +2 -0
  21. package/lib/rules/enforce-early-destructuring.js +980 -0
  22. package/lib/rules/enforce-empty-object-check.d.ts +11 -0
  23. package/lib/rules/enforce-empty-object-check.js +502 -0
  24. package/lib/rules/enforce-exported-function-types.js +1 -1
  25. package/lib/rules/enforce-f-extension-for-entry-points.d.ts +8 -0
  26. package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
  27. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
  28. package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
  29. package/lib/rules/enforce-firestore-facade.js +4 -2
  30. package/lib/rules/enforce-firestore-set-merge.js +3 -4
  31. package/lib/rules/enforce-global-constants.js +4 -4
  32. package/lib/rules/enforce-id-capitalization.js +1 -1
  33. package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
  34. package/lib/rules/enforce-memoize-async.js +71 -21
  35. package/lib/rules/enforce-memoize-getters.js +39 -15
  36. package/lib/rules/enforce-microdiff.js +1 -1
  37. package/lib/rules/enforce-object-literal-as-const.js +4 -4
  38. package/lib/rules/enforce-positive-naming.js +4 -0
  39. package/lib/rules/enforce-props-argument-name.js +44 -18
  40. package/lib/rules/enforce-safe-stringify.js +1 -1
  41. package/lib/rules/enforce-serializable-params.js +3 -3
  42. package/lib/rules/enforce-singular-type-names.js +1 -1
  43. package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
  44. package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
  45. package/lib/rules/enforce-storage-context.d.ts +9 -0
  46. package/lib/rules/enforce-storage-context.js +646 -0
  47. package/lib/rules/enforce-timestamp-now.js +23 -7
  48. package/lib/rules/enforce-transform-memoization.d.ts +4 -0
  49. package/lib/rules/enforce-transform-memoization.js +416 -0
  50. package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
  51. package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
  52. package/lib/rules/enforce-unique-cursor-headers.js +365 -0
  53. package/lib/rules/enforce-verb-noun-naming.js +3818 -4644
  54. package/lib/rules/ensure-pointer-events-none.js +28 -2
  55. package/lib/rules/export-if-in-doubt.js +27 -4
  56. package/lib/rules/extract-global-constants.js +40 -13
  57. package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
  58. package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
  59. package/lib/rules/flatten-push-calls.d.ts +2 -0
  60. package/lib/rules/flatten-push-calls.js +428 -0
  61. package/lib/rules/global-const-style.js +66 -23
  62. package/lib/rules/jsdoc-above-field.d.ts +11 -0
  63. package/lib/rules/jsdoc-above-field.js +208 -0
  64. package/lib/rules/key-only-outermost-element.js +21 -3
  65. package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
  66. package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
  67. package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
  68. package/lib/rules/memo-compare-deeply-complex-props.js +801 -0
  69. package/lib/rules/memo-nested-react-components.d.ts +8 -0
  70. package/lib/rules/memo-nested-react-components.js +415 -0
  71. package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
  72. package/lib/rules/no-always-true-false-conditions.js +68 -8
  73. package/lib/rules/no-array-length-in-deps.js +9 -3
  74. package/lib/rules/no-async-foreach.js +215 -12
  75. package/lib/rules/no-circular-references.d.ts +2 -1
  76. package/lib/rules/no-circular-references.js +31 -32
  77. package/lib/rules/no-class-instance-destructuring.js +55 -12
  78. package/lib/rules/no-complex-cloud-params.js +7 -3
  79. package/lib/rules/no-compositing-layer-props.js +2 -2
  80. package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
  81. package/lib/rules/no-console-error.d.ts +9 -0
  82. package/lib/rules/no-console-error.js +527 -0
  83. package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
  84. package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
  85. package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
  86. package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
  87. package/lib/rules/no-entire-object-hook-deps.js +52 -7
  88. package/lib/rules/no-excessive-parent-chain.js +4 -1
  89. package/lib/rules/no-explicit-return-type.d.ts +2 -1
  90. package/lib/rules/no-explicit-return-type.js +215 -26
  91. package/lib/rules/no-filter-without-return.js +5 -1
  92. package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
  93. package/lib/rules/no-firestore-jest-mock.js +126 -8
  94. package/lib/rules/no-firestore-object-arrays.js +67 -12
  95. package/lib/rules/no-handler-suffix.d.ts +12 -0
  96. package/lib/rules/no-handler-suffix.js +305 -0
  97. package/lib/rules/no-hungarian.js +1 -1
  98. package/lib/rules/no-inline-component-prop.d.ts +10 -0
  99. package/lib/rules/no-inline-component-prop.js +465 -0
  100. package/lib/rules/no-jsx-in-hooks.js +6 -1
  101. package/lib/rules/no-jsx-whitespace-literal.js +8 -2
  102. package/lib/rules/no-margin-properties.js +6 -6
  103. package/lib/rules/no-memoize-on-static.js +9 -1
  104. package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
  105. package/lib/rules/no-misused-switch-case.js +22 -1
  106. package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
  107. package/lib/rules/no-mixed-firestore-transactions.js +296 -34
  108. package/lib/rules/no-mock-firebase-admin.js +5 -2
  109. package/lib/rules/no-object-values-on-strings.js +10 -2
  110. package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
  111. package/lib/rules/no-passthrough-getters.d.ts +2 -2
  112. package/lib/rules/no-passthrough-getters.js +120 -2
  113. package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
  114. package/lib/rules/no-redundant-annotation-assertion.js +402 -0
  115. package/lib/rules/no-redundant-param-types.js +16 -6
  116. package/lib/rules/no-redundant-this-params.d.ts +3 -0
  117. package/lib/rules/no-redundant-this-params.js +508 -0
  118. package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
  119. package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
  120. package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
  121. package/lib/rules/no-restricted-properties-fix.js +11 -10
  122. package/lib/rules/no-separate-loading-state.js +7 -16
  123. package/lib/rules/no-stale-state-across-await.js +1 -1
  124. package/lib/rules/no-type-assertion-returns.js +43 -49
  125. package/lib/rules/no-undefined-null-passthrough.js +61 -31
  126. package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
  127. package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
  128. package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
  129. package/lib/rules/no-unnecessary-destructuring-rename.js +344 -0
  130. package/lib/rules/no-unnecessary-destructuring.js +14 -4
  131. package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
  132. package/lib/rules/no-unpinned-dependencies.js +36 -5
  133. package/lib/rules/no-unused-props.d.ts +2 -2
  134. package/lib/rules/no-unused-props.js +295 -91
  135. package/lib/rules/no-unused-usestate.js +6 -2
  136. package/lib/rules/no-useless-fragment.js +28 -2
  137. package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
  138. package/lib/rules/no-useless-usememo-primitives.js +393 -0
  139. package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
  140. package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
  141. package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
  142. package/lib/rules/omit-index-html.d.ts +2 -1
  143. package/lib/rules/omit-index-html.js +62 -7
  144. package/lib/rules/optimize-object-boolean-conditions.js +6 -4
  145. package/lib/rules/parallelize-async-operations.js +143 -59
  146. package/lib/rules/prefer-batch-operations.d.ts +1 -3
  147. package/lib/rules/prefer-batch-operations.js +32 -5
  148. package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
  149. package/lib/rules/prefer-clone-deep.js +3 -3
  150. package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
  151. package/lib/rules/prefer-destructuring-no-class.js +244 -59
  152. package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
  153. package/lib/rules/prefer-docsetter-setall.js +243 -0
  154. package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
  155. package/lib/rules/prefer-fragment-component.js +1 -1
  156. package/lib/rules/prefer-fragment-shorthand.js +2 -1
  157. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
  158. package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
  159. package/lib/rules/prefer-global-router-state-key.js +88 -13
  160. package/lib/rules/prefer-memoized-props.d.ts +3 -0
  161. package/lib/rules/prefer-memoized-props.js +445 -0
  162. package/lib/rules/prefer-next-dynamic.js +60 -69
  163. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +118 -8
  164. package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
  165. package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
  166. package/lib/rules/prefer-params-over-parent-id.js +221 -172
  167. package/lib/rules/prefer-settings-object.js +27 -10
  168. package/lib/rules/prefer-type-alias-over-typeof-constant.js +9 -0
  169. package/lib/rules/prefer-type-over-interface.js +7 -2
  170. package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
  171. package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
  172. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
  173. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +1 -1
  174. package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
  175. package/lib/rules/prevent-children-clobber.d.ts +2 -0
  176. package/lib/rules/prevent-children-clobber.js +540 -0
  177. package/lib/rules/react-memoize-literals.d.ts +4 -0
  178. package/lib/rules/react-memoize-literals.js +614 -0
  179. package/lib/rules/react-usememo-should-be-component.js +2 -2
  180. package/lib/rules/require-hooks-default-params.js +17 -16
  181. package/lib/rules/require-https-error-cause.d.ts +4 -0
  182. package/lib/rules/require-https-error-cause.js +155 -0
  183. package/lib/rules/require-https-error.js +43 -21
  184. package/lib/rules/require-image-optimized.js +1 -1
  185. package/lib/rules/require-memo.js +18 -10
  186. package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
  187. package/lib/rules/require-memoize-jsx-returners.js +485 -0
  188. package/lib/rules/require-usememo-object-literals.js +2 -3
  189. package/lib/rules/test-file-location-enforcement.js +1 -1
  190. package/lib/rules/use-latest-callback.js +6 -5
  191. package/lib/rules/vertically-group-related-functions.d.ts +16 -0
  192. package/lib/rules/vertically-group-related-functions.js +480 -0
  193. package/lib/utils/ASTHelpers.d.ts +47 -2
  194. package/lib/utils/ASTHelpers.js +370 -112
  195. package/lib/utils/getMethodName.d.ts +27 -0
  196. package/lib/utils/getMethodName.js +35 -0
  197. package/lib/utils/tsTypeClassifier.d.ts +30 -0
  198. package/lib/utils/tsTypeClassifier.js +149 -0
  199. package/package.json +3 -1
@@ -106,7 +106,7 @@ function isNonPrimitiveWithTypes(context, expr) {
106
106
  }
107
107
  function collectMemoizedIdentifiers(context) {
108
108
  const memoized = new Set();
109
- const sourceCode = context.getSourceCode();
109
+ const sourceCode = context.sourceCode;
110
110
  const program = sourceCode.ast;
111
111
  function visit(node) {
112
112
  if (node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
@@ -156,7 +156,7 @@ function collectMemoizedIdentifiers(context) {
156
156
  }
157
157
  function ensureDeepCompareImportFixes(context, fixer) {
158
158
  const fixes = [];
159
- const sourceCode = context.getSourceCode();
159
+ const sourceCode = context.sourceCode;
160
160
  const program = sourceCode.ast;
161
161
  // If already imported anywhere, skip adding
162
162
  const hasImport = program.body.some((n) => n.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
@@ -227,7 +227,7 @@ function removeImportSpecifierFixes(sourceCode, fixer, importDecl, specifier) {
227
227
  return fixes;
228
228
  }
229
229
  function isImportedIdentifier(context, name) {
230
- const sourceCode = context.getSourceCode();
230
+ const sourceCode = context.sourceCode;
231
231
  const program = sourceCode.ast;
232
232
  for (const node of program.body) {
233
233
  if (node.type === utils_1.AST_NODE_TYPES.ImportDeclaration) {
@@ -319,7 +319,7 @@ exports.preferUseDeepCompareMemo = (0, createRule_1.createRule)({
319
319
  fixable: 'code',
320
320
  schema: [],
321
321
  messages: {
322
- preferUseDeepCompareMemo: 'Use useDeepCompareMemo instead of useMemo because dependency array contains unmemoized non-primitive values.',
322
+ preferUseDeepCompareMemo: 'Dependency array for "{{hook}}" includes objects/arrays/functions that change identity each render, so React treats them as changed and reruns the memoized computation, triggering avoidable renders. Use useDeepCompareMemo (or memoize those dependencies first) so comparisons use deep equality and the memo stays stable.',
323
323
  },
324
324
  },
325
325
  defaultOptions: [],
@@ -386,6 +386,9 @@ exports.preferUseDeepCompareMemo = (0, createRule_1.createRule)({
386
386
  context.report({
387
387
  node,
388
388
  messageId: 'preferUseDeepCompareMemo',
389
+ data: {
390
+ hook: 'useMemo',
391
+ },
389
392
  fix(fixer) {
390
393
  const fixes = [];
391
394
  // Replace callee
@@ -398,7 +401,7 @@ exports.preferUseDeepCompareMemo = (0, createRule_1.createRule)({
398
401
  // Ensure import exists
399
402
  fixes.push(...ensureDeepCompareImportFixes(context, fixer));
400
403
  // Clean up now-unused React/useMemo imports if safe
401
- const sourceCode = context.getSourceCode();
404
+ const sourceCode = context.sourceCode;
402
405
  const program = sourceCode.ast;
403
406
  const reactImport = program.body.find((n) => n.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
404
407
  n.source.value === 'react');
@@ -166,7 +166,7 @@ function getMemoizedFunctionDescription(node) {
166
166
  return 'this callback value';
167
167
  }
168
168
  function reportAndFix(node, context) {
169
- const sourceCode = context.getSourceCode();
169
+ const sourceCode = context.sourceCode;
170
170
  const useMemoCallback = node.arguments[0];
171
171
  const dependencyArray = node.arguments[1]
172
172
  ? sourceCode.getText(node.arguments[1])
@@ -11,7 +11,7 @@ exports.preferUseMemoOverUseEffectUseState = (0, createRule_1.createRule)({
11
11
  recommended: 'error',
12
12
  },
13
13
  messages: {
14
- preferUseMemo: 'Derived state "{{stateName}}" is computed inside useEffect and copied into React state even though the value comes from a pure calculation. That extra render cycle and state indirection make components re-render more and risk stale snapshots when dependencies change. Compute the value with useMemo (or inline in render) and read it directly instead of mirroring it into state.',
14
+ preferUseMemo: 'Use useMemo to compute derived state "{{stateName}}" instead of useEffect + useState to avoid extra render cycles and stale snapshots.',
15
15
  },
16
16
  schema: [],
17
17
  },
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.preferUtilityFunctionOverPrivateStatic = void 0;
4
4
  const createRule_1 = require("../utils/createRule");
5
+ const getMethodName_1 = require("../utils/getMethodName");
5
6
  exports.preferUtilityFunctionOverPrivateStatic = (0, createRule_1.createRule)({
6
7
  name: 'prefer-utility-function-over-private-static',
7
8
  meta: {
@@ -12,7 +13,7 @@ exports.preferUtilityFunctionOverPrivateStatic = (0, createRule_1.createRule)({
12
13
  },
13
14
  schema: [],
14
15
  messages: {
15
- preferUtilityFunctionOverPrivateStatic: 'Private static methods should be abstracted into standalone utility functions for better reusability and testability',
16
+ preferUtilityFunctionOverPrivateStatic: 'Private static method "{{methodName}}" in class "{{className}}" does not use class state. Keeping class-agnostic helpers as private statics hides reusable logic and signals coupling that is not present. Extract this logic into a standalone utility function (module-level or shared) and import it where needed so it can be reused and unit tested independently.',
16
17
  },
17
18
  },
18
19
  defaultOptions: [],
@@ -47,9 +48,32 @@ exports.preferUtilityFunctionOverPrivateStatic = (0, createRule_1.createRule)({
47
48
  }
48
49
  return false;
49
50
  };
51
+ const getClassName = (method) => {
52
+ const classNode = method.parent?.parent;
53
+ if (classNode &&
54
+ (classNode.type === 'ClassDeclaration' ||
55
+ classNode.type === 'ClassExpression')) {
56
+ if (classNode.id && classNode.id.type === 'Identifier') {
57
+ return classNode.id.name;
58
+ }
59
+ if (classNode.type === 'ClassExpression' &&
60
+ classNode.parent &&
61
+ classNode.parent.type === 'VariableDeclarator' &&
62
+ classNode.parent.id.type === 'Identifier') {
63
+ return classNode.parent.id.name;
64
+ }
65
+ if (classNode.type === 'ClassExpression' &&
66
+ classNode.parent &&
67
+ classNode.parent.type === 'AssignmentExpression' &&
68
+ classNode.parent.left.type === 'Identifier') {
69
+ return classNode.parent.left.name;
70
+ }
71
+ }
72
+ return 'this class';
73
+ };
50
74
  return {
51
75
  'MethodDefinition[static=true][accessibility="private"]'(node) {
52
- const sourceCode = context.getSourceCode();
76
+ const sourceCode = context.sourceCode;
53
77
  const methodBody = node.value.body;
54
78
  if (!methodBody) {
55
79
  return;
@@ -65,9 +89,17 @@ exports.preferUtilityFunctionOverPrivateStatic = (0, createRule_1.createRule)({
65
89
  const usesThis = hasThisReference(methodBody);
66
90
  // If the method doesn't use 'this', it's a good candidate for extraction
67
91
  if (!usesThis) {
92
+ const methodName = (0, getMethodName_1.getMethodName)(node, sourceCode, {
93
+ computedFallbackToText: false,
94
+ }) || '<unknown>';
95
+ const className = getClassName(node);
68
96
  context.report({
69
97
  node,
70
98
  messageId: 'preferUtilityFunctionOverPrivateStatic',
99
+ data: {
100
+ methodName,
101
+ className,
102
+ },
71
103
  });
72
104
  }
73
105
  },
@@ -0,0 +1,2 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const preventChildrenClobber: TSESLint.RuleModule<"childrenClobbered", [], TSESLint.RuleListener>;
@@ -0,0 +1,540 @@
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.preventChildrenClobber = void 0;
27
+ const utils_1 = require("@typescript-eslint/utils");
28
+ const ts = __importStar(require("typescript"));
29
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
30
+ const createRule_1 = require("../utils/createRule");
31
+ function resolveFunctionName(node) {
32
+ if ('id' in node && node.id?.name) {
33
+ return node.id.name;
34
+ }
35
+ if (node.type === utils_1.AST_NODE_TYPES.FunctionExpression &&
36
+ node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
37
+ node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
38
+ return node.parent.id.name;
39
+ }
40
+ if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
41
+ node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
42
+ node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
43
+ return node.parent.id.name;
44
+ }
45
+ return null;
46
+ }
47
+ function isComponentLike(node, context) {
48
+ const name = resolveFunctionName(node);
49
+ if (name && /^[A-Z]/.test(name)) {
50
+ return true;
51
+ }
52
+ return ASTHelpers_1.ASTHelpers.returnsJSX(node.body, context);
53
+ }
54
+ function patternHasChildrenProperty(pattern) {
55
+ return pattern.properties.some((prop) => {
56
+ if (prop.type !== utils_1.AST_NODE_TYPES.Property)
57
+ return false;
58
+ if (prop.computed)
59
+ return false;
60
+ const key = prop.key;
61
+ if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
62
+ return key.name === 'children';
63
+ }
64
+ if (key.type === utils_1.AST_NODE_TYPES.Literal) {
65
+ return key.value === 'children';
66
+ }
67
+ return false;
68
+ });
69
+ }
70
+ function typeNodeContainsLiteral(node, literalValue) {
71
+ if (node.type === utils_1.AST_NODE_TYPES.TSLiteralType) {
72
+ return node.literal.type === utils_1.AST_NODE_TYPES.Literal
73
+ ? node.literal.value === literalValue
74
+ : false;
75
+ }
76
+ if (node.type === utils_1.AST_NODE_TYPES.TSUnionType) {
77
+ return node.types.some((t) => typeNodeContainsLiteral(t, literalValue));
78
+ }
79
+ if (node.type === utils_1.AST_NODE_TYPES.TSTupleType) {
80
+ return node.elementTypes.some((t) => typeNodeContainsLiteral(t, literalValue));
81
+ }
82
+ if (node.type === utils_1.AST_NODE_TYPES.TSArrayType) {
83
+ return typeNodeContainsLiteral(node.elementType, literalValue);
84
+ }
85
+ return false;
86
+ }
87
+ function typeNodeExcludesProperty(node, propertyName, aliasMap, seen = new Set()) {
88
+ if (node.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
89
+ const typeName = node.typeName.type === utils_1.AST_NODE_TYPES.Identifier
90
+ ? node.typeName.name
91
+ : null;
92
+ if (typeName === 'Omit' && node.typeParameters?.params?.[1]) {
93
+ const excluded = node.typeParameters.params[1];
94
+ if (typeNodeContainsLiteral(excluded, propertyName) ||
95
+ typeNodeExcludesProperty(excluded, propertyName, aliasMap, seen)) {
96
+ return true;
97
+ }
98
+ }
99
+ if (node.typeParameters?.params) {
100
+ return node.typeParameters.params.some((param) => typeNodeExcludesProperty(param, propertyName, aliasMap, seen));
101
+ }
102
+ if (typeName && aliasMap?.has(typeName) && !seen.has(typeName)) {
103
+ seen.add(typeName);
104
+ const alias = aliasMap.get(typeName);
105
+ if (alias &&
106
+ typeNodeExcludesProperty(alias, propertyName, aliasMap, seen)) {
107
+ return true;
108
+ }
109
+ }
110
+ }
111
+ if (node.type === utils_1.AST_NODE_TYPES.TSUnionType) {
112
+ return node.types.every((typeNode) => typeNodeExcludesProperty(typeNode, propertyName, aliasMap, seen));
113
+ }
114
+ if (node.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
115
+ return node.types.every((typeNode) => typeNodeExcludesProperty(typeNode, propertyName, aliasMap, seen));
116
+ }
117
+ return false;
118
+ }
119
+ function typeAnnotationExcludesProperty(annotation, propertyName, aliasMap) {
120
+ if (!annotation)
121
+ return false;
122
+ return typeNodeExcludesProperty(annotation.typeAnnotation, propertyName, aliasMap);
123
+ }
124
+ function collectRestBindingsFromPattern(pattern, ctx, annotation, aliasMap, sourceChildrenSourceId) {
125
+ const childrenPresent = patternHasChildrenProperty(pattern);
126
+ for (const prop of pattern.properties) {
127
+ if (prop.type === utils_1.AST_NODE_TYPES.RestElement &&
128
+ prop.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
129
+ ctx.propsLikeIdentifiers.add(prop.argument.name);
130
+ ctx.bindings.set(prop.argument.name, {
131
+ identifier: prop.argument,
132
+ childrenExcluded: childrenPresent,
133
+ typeAnnotationExcludesProperty: typeAnnotationExcludesProperty(annotation, 'children', aliasMap),
134
+ childrenSourceId: sourceChildrenSourceId ?? prop.argument.name,
135
+ });
136
+ }
137
+ else if (prop.type === utils_1.AST_NODE_TYPES.Property &&
138
+ prop.value.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
139
+ collectRestBindingsFromPattern(prop.value, ctx, null, aliasMap);
140
+ }
141
+ }
142
+ }
143
+ function recordChildrenValueBindingsFromPattern(pattern, ctx, sourceChildrenSourceId) {
144
+ for (const prop of pattern.properties) {
145
+ if (prop.type !== utils_1.AST_NODE_TYPES.Property)
146
+ continue;
147
+ if (prop.computed)
148
+ continue;
149
+ const key = prop.key;
150
+ const keyName = key.type === utils_1.AST_NODE_TYPES.Identifier
151
+ ? key.name
152
+ : key.type === utils_1.AST_NODE_TYPES.Literal && typeof key.value === 'string'
153
+ ? key.value
154
+ : null;
155
+ if (keyName !== 'children')
156
+ continue;
157
+ const value = prop.value;
158
+ if (value.type === utils_1.AST_NODE_TYPES.Identifier) {
159
+ ctx.childrenValueSourceIds.set(value.name, sourceChildrenSourceId);
160
+ continue;
161
+ }
162
+ if (value.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
163
+ value.left.type === utils_1.AST_NODE_TYPES.Identifier) {
164
+ ctx.childrenValueSourceIds.set(value.left.name, sourceChildrenSourceId);
165
+ }
166
+ }
167
+ }
168
+ function recordParamBindings(param, ctx, aliasMap) {
169
+ if (param.type === utils_1.AST_NODE_TYPES.Identifier) {
170
+ ctx.propsLikeIdentifiers.add(param.name);
171
+ ctx.bindings.set(param.name, {
172
+ identifier: param,
173
+ childrenExcluded: false,
174
+ typeAnnotationExcludesProperty: typeAnnotationExcludesProperty(param.typeAnnotation, 'children', aliasMap),
175
+ childrenSourceId: param.name,
176
+ });
177
+ return;
178
+ }
179
+ if (param.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
180
+ param.left.type === utils_1.AST_NODE_TYPES.Identifier) {
181
+ ctx.propsLikeIdentifiers.add(param.left.name);
182
+ ctx.bindings.set(param.left.name, {
183
+ identifier: param.left,
184
+ childrenExcluded: false,
185
+ typeAnnotationExcludesProperty: typeAnnotationExcludesProperty(param.typeAnnotation, 'children', aliasMap),
186
+ childrenSourceId: param.left.name,
187
+ });
188
+ return;
189
+ }
190
+ if (param.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
191
+ param.left.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
192
+ collectRestBindingsFromPattern(param.left, ctx, param.typeAnnotation, aliasMap);
193
+ return;
194
+ }
195
+ if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
196
+ collectRestBindingsFromPattern(param, ctx, param.typeAnnotation, aliasMap);
197
+ }
198
+ }
199
+ function findNearestComponentContext(stack) {
200
+ for (let i = stack.length - 1; i >= 0; i -= 1) {
201
+ if (stack[i].isComponent)
202
+ return stack[i];
203
+ }
204
+ return undefined;
205
+ }
206
+ function findBinding(name, stack) {
207
+ for (let i = stack.length - 1; i >= 0; i -= 1) {
208
+ const binding = stack[i].bindings.get(name);
209
+ if (binding)
210
+ return binding;
211
+ }
212
+ return undefined;
213
+ }
214
+ function findChildrenValueSourceId(name, stack) {
215
+ for (let i = stack.length - 1; i >= 0; i -= 1) {
216
+ const sourceId = stack[i].childrenValueSourceIds.get(name);
217
+ if (sourceId)
218
+ return sourceId;
219
+ }
220
+ return undefined;
221
+ }
222
+ function isPropsLike(name, stack) {
223
+ for (let i = stack.length - 1; i >= 0; i -= 1) {
224
+ if (stack[i].propsLikeIdentifiers.has(name))
225
+ return true;
226
+ }
227
+ return false;
228
+ }
229
+ function typeHasChildrenProperty(checker, type) {
230
+ if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
231
+ return null;
232
+ }
233
+ const apparent = checker.getApparentType(type);
234
+ const directProp = type.getProperty?.('children') ??
235
+ checker.getPropertyOfType(type, 'children') ??
236
+ checker.getPropertyOfType(apparent, 'children');
237
+ if (directProp) {
238
+ return true;
239
+ }
240
+ if (type.isUnion?.()) {
241
+ let sawUnknown = false;
242
+ for (const member of type.types) {
243
+ const result = typeHasChildrenProperty(checker, member);
244
+ if (result)
245
+ return true;
246
+ if (result === null)
247
+ sawUnknown = true;
248
+ }
249
+ return sawUnknown ? null : false;
250
+ }
251
+ if (type.isIntersection?.()) {
252
+ let sawUnknown = false;
253
+ for (const member of type.types) {
254
+ const result = typeHasChildrenProperty(checker, member);
255
+ if (result)
256
+ return true;
257
+ if (result === null)
258
+ sawUnknown = true;
259
+ }
260
+ return sawUnknown ? null : false;
261
+ }
262
+ return false;
263
+ }
264
+ function bindingMayContainChildren(binding, context) {
265
+ if (binding.childrenExcluded)
266
+ return false;
267
+ if (binding.typeAnnotationExcludesProperty)
268
+ return false;
269
+ const services = context.sourceCode?.parserServices ??
270
+ context.parserServices;
271
+ if (!services?.program || !services?.esTreeNodeToTSNodeMap) {
272
+ return true;
273
+ }
274
+ try {
275
+ const checker = services.program.getTypeChecker();
276
+ const tsNode = services.esTreeNodeToTSNodeMap.get(binding.identifier);
277
+ if (!tsNode)
278
+ return true;
279
+ const type = checker.getTypeAtLocation(tsNode);
280
+ const hasChildren = typeHasChildrenProperty(checker, type);
281
+ if (hasChildren === false)
282
+ return false;
283
+ return true;
284
+ }
285
+ catch {
286
+ return true;
287
+ }
288
+ }
289
+ function hasExplicitChildren(element) {
290
+ if (element.openingElement.selfClosing)
291
+ return false;
292
+ return element.children.some((child) => {
293
+ if (child.type === utils_1.AST_NODE_TYPES.JSXText) {
294
+ return child.value.trim().length > 0;
295
+ }
296
+ if (child.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
297
+ return child.expression.type !== utils_1.AST_NODE_TYPES.JSXEmptyExpression;
298
+ }
299
+ return true; // JSXElement, JSXFragment, JSXSpreadChild, etc.
300
+ });
301
+ }
302
+ function nodeReferencesChildren(node, propsObjectNames, childrenValueNames) {
303
+ const stack = [node];
304
+ while (stack.length) {
305
+ const current = stack.pop();
306
+ if (current.type === utils_1.AST_NODE_TYPES.Identifier) {
307
+ if (childrenValueNames.has(current.name))
308
+ return true;
309
+ }
310
+ else if (current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
311
+ if (!current.computed &&
312
+ current.property.type === utils_1.AST_NODE_TYPES.Identifier &&
313
+ current.property.name === 'children' &&
314
+ current.object.type === utils_1.AST_NODE_TYPES.Identifier &&
315
+ propsObjectNames.has(current.object.name)) {
316
+ return true;
317
+ }
318
+ }
319
+ else if (current.type === utils_1.AST_NODE_TYPES.ChainExpression) {
320
+ stack.push(current.expression);
321
+ }
322
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
323
+ for (const key of Object.keys(current)) {
324
+ if (key === 'parent')
325
+ continue;
326
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
327
+ const value = current[key];
328
+ if (!value)
329
+ continue;
330
+ if (Array.isArray(value)) {
331
+ for (const child of value) {
332
+ if (child && typeof child === 'object' && 'type' in child) {
333
+ stack.push(child);
334
+ }
335
+ }
336
+ }
337
+ else if (typeof value === 'object' && 'type' in value) {
338
+ stack.push(value);
339
+ }
340
+ }
341
+ }
342
+ return false;
343
+ }
344
+ function childrenRenderSpreadChildren(children, propsObjectNames, childrenValueNames) {
345
+ for (const child of children) {
346
+ if (child.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
347
+ if (nodeReferencesChildren(child.expression, propsObjectNames, childrenValueNames)) {
348
+ return true;
349
+ }
350
+ }
351
+ else if (child.type === utils_1.AST_NODE_TYPES.JSXElement ||
352
+ child.type === utils_1.AST_NODE_TYPES.JSXFragment ||
353
+ child.type === utils_1.AST_NODE_TYPES.JSXSpreadChild) {
354
+ if (nodeReferencesChildren(child, propsObjectNames, childrenValueNames)) {
355
+ return true;
356
+ }
357
+ }
358
+ else if (child.type === utils_1.AST_NODE_TYPES.JSXText) {
359
+ continue;
360
+ }
361
+ }
362
+ return false;
363
+ }
364
+ function collectPropsObjectNamesForChildrenSourceIds(sourceIds, stack) {
365
+ const names = new Set();
366
+ for (const ctx of stack) {
367
+ for (const [name, binding] of ctx.bindings) {
368
+ if (sourceIds.has(binding.childrenSourceId)) {
369
+ names.add(name);
370
+ }
371
+ }
372
+ }
373
+ return names;
374
+ }
375
+ function collectChildrenValueNamesForChildrenSourceIds(sourceIds, stack) {
376
+ const names = new Set();
377
+ for (const ctx of stack) {
378
+ for (const [name, sourceId] of ctx.childrenValueSourceIds) {
379
+ if (sourceIds.has(sourceId)) {
380
+ names.add(name);
381
+ }
382
+ }
383
+ }
384
+ return names;
385
+ }
386
+ exports.preventChildrenClobber = (0, createRule_1.createRule)({
387
+ name: 'prevent-children-clobber',
388
+ meta: {
389
+ type: 'problem',
390
+ docs: {
391
+ description: 'Prevent JSX spreads from silently discarding props.children',
392
+ recommended: 'error',
393
+ requiresTypeChecking: false,
394
+ },
395
+ schema: [],
396
+ messages: {
397
+ childrenClobbered: "Children clobber detected: JSX spreads {{spreadNames}} which may already contain children, but the element also declares its own children. The spread children are discarded. Destructure and render children explicitly (e.g., `{ children, ...rest }` and include `{children}`) or add `'children'` to an `Omit<>` if this component should not accept children.",
398
+ },
399
+ },
400
+ defaultOptions: [],
401
+ create(context) {
402
+ const sourceCode = context.sourceCode ??
403
+ context.getSourceCode();
404
+ const aliasMap = new Map();
405
+ for (const node of sourceCode.ast.body) {
406
+ if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
407
+ aliasMap.set(node.id.name, node.typeAnnotation);
408
+ }
409
+ }
410
+ const functionStack = [];
411
+ return {
412
+ ':function'(node) {
413
+ const ctx = {
414
+ isComponent: isComponentLike(node, context),
415
+ bindings: new Map(),
416
+ propsLikeIdentifiers: new Set(),
417
+ childrenValueSourceIds: new Map(),
418
+ };
419
+ if (ctx.isComponent) {
420
+ for (const param of node.params) {
421
+ recordParamBindings(param, ctx, aliasMap);
422
+ }
423
+ }
424
+ functionStack.push(ctx);
425
+ },
426
+ ':function:exit'() {
427
+ functionStack.pop();
428
+ },
429
+ VariableDeclarator(node) {
430
+ const componentCtx = findNearestComponentContext(functionStack);
431
+ if (!componentCtx)
432
+ return;
433
+ const id = node.id;
434
+ const init = node.init;
435
+ if (id.type === utils_1.AST_NODE_TYPES.Identifier &&
436
+ init?.type === utils_1.AST_NODE_TYPES.Identifier) {
437
+ const sourceBinding = findBinding(init.name, functionStack);
438
+ const typeExcludes = typeAnnotationExcludesProperty(id.typeAnnotation, 'children', aliasMap);
439
+ if (sourceBinding) {
440
+ componentCtx.bindings.set(id.name, {
441
+ identifier: id,
442
+ childrenExcluded: sourceBinding.childrenExcluded,
443
+ typeAnnotationExcludesProperty: sourceBinding.typeAnnotationExcludesProperty || typeExcludes,
444
+ childrenSourceId: sourceBinding.childrenSourceId,
445
+ });
446
+ }
447
+ else if (isPropsLike(init.name, functionStack)) {
448
+ const propsLikeBinding = findBinding(init.name, functionStack);
449
+ componentCtx.bindings.set(id.name, {
450
+ identifier: id,
451
+ childrenExcluded: false,
452
+ typeAnnotationExcludesProperty: typeExcludes,
453
+ childrenSourceId: propsLikeBinding?.childrenSourceId ?? init.name,
454
+ });
455
+ }
456
+ if (isPropsLike(init.name, functionStack)) {
457
+ componentCtx.propsLikeIdentifiers.add(id.name);
458
+ }
459
+ const childSourceId = findChildrenValueSourceId(init.name, functionStack);
460
+ if (childSourceId) {
461
+ componentCtx.childrenValueSourceIds.set(id.name, childSourceId);
462
+ }
463
+ }
464
+ else if (id.type === utils_1.AST_NODE_TYPES.Identifier &&
465
+ init &&
466
+ (init.type === utils_1.AST_NODE_TYPES.MemberExpression ||
467
+ init.type === utils_1.AST_NODE_TYPES.ChainExpression)) {
468
+ const member = init.type === utils_1.AST_NODE_TYPES.ChainExpression
469
+ ? init.expression
470
+ : init;
471
+ if (member.type === utils_1.AST_NODE_TYPES.MemberExpression &&
472
+ !member.computed &&
473
+ member.property.type === utils_1.AST_NODE_TYPES.Identifier &&
474
+ member.property.name === 'children' &&
475
+ member.object.type === utils_1.AST_NODE_TYPES.Identifier) {
476
+ const sourceBinding = findBinding(member.object.name, functionStack);
477
+ if (sourceBinding) {
478
+ componentCtx.childrenValueSourceIds.set(id.name, sourceBinding.childrenSourceId);
479
+ }
480
+ }
481
+ }
482
+ else if (id.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
483
+ init?.type === utils_1.AST_NODE_TYPES.Identifier &&
484
+ isPropsLike(init.name, functionStack)) {
485
+ const initBinding = findBinding(init.name, functionStack);
486
+ const sourceChildrenSourceId = initBinding?.childrenSourceId ?? init.name;
487
+ recordChildrenValueBindingsFromPattern(id, componentCtx, sourceChildrenSourceId);
488
+ collectRestBindingsFromPattern(id, componentCtx, id.typeAnnotation ?? null, aliasMap, sourceChildrenSourceId);
489
+ }
490
+ },
491
+ JSXElement(node) {
492
+ const componentCtx = findNearestComponentContext(functionStack);
493
+ if (!componentCtx)
494
+ return;
495
+ if (!hasExplicitChildren(node))
496
+ return;
497
+ const spreadNamesInOrder = [];
498
+ for (const attr of node.openingElement.attributes) {
499
+ if (attr.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute &&
500
+ attr.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
501
+ spreadNamesInOrder.push(attr.argument.name);
502
+ }
503
+ }
504
+ if (spreadNamesInOrder.length === 0)
505
+ return;
506
+ const offendingSpreads = [];
507
+ for (const name of spreadNamesInOrder) {
508
+ const binding = findBinding(name, functionStack);
509
+ if (!binding)
510
+ continue;
511
+ if (!bindingMayContainChildren(binding, context)) {
512
+ continue;
513
+ }
514
+ offendingSpreads.push({
515
+ name,
516
+ childrenSourceId: binding.childrenSourceId,
517
+ });
518
+ }
519
+ if (offendingSpreads.length === 0)
520
+ return;
521
+ const lastOffendingChildrenSourceId = offendingSpreads[offendingSpreads.length - 1].childrenSourceId;
522
+ const lastSourceIds = new Set([lastOffendingChildrenSourceId]);
523
+ const propsObjectNames = collectPropsObjectNamesForChildrenSourceIds(lastSourceIds, functionStack);
524
+ const childrenValueNames = collectChildrenValueNamesForChildrenSourceIds(lastSourceIds, functionStack);
525
+ if (childrenRenderSpreadChildren(node.children, propsObjectNames, childrenValueNames)) {
526
+ return;
527
+ }
528
+ const clobberedNames = Array.from(new Set(offendingSpreads
529
+ .filter((spread) => spread.childrenSourceId === lastOffendingChildrenSourceId)
530
+ .map((spread) => spread.name)));
531
+ context.report({
532
+ node: node.openingElement,
533
+ messageId: 'childrenClobbered',
534
+ data: { spreadNames: clobberedNames.join(', ') },
535
+ });
536
+ },
537
+ };
538
+ },
539
+ });
540
+ //# sourceMappingURL=prevent-children-clobber.js.map