@blumintinc/eslint-plugin-blumint 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +170 -139
  2. package/lib/index.js +190 -1
  3. package/lib/rules/array-methods-this-context.js +1 -1
  4. package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
  5. package/lib/rules/consistent-callback-naming.js +1 -1
  6. package/lib/rules/dynamic-https-errors.js +3 -4
  7. package/lib/rules/enforce-assert-safe-object-key.js +11 -11
  8. package/lib/rules/enforce-boolean-naming-prefixes.js +11 -23
  9. package/lib/rules/enforce-callback-memo.js +1 -1
  10. package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
  11. package/lib/rules/enforce-console-error.js +35 -7
  12. package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
  13. package/lib/rules/enforce-dynamic-file-naming.js +53 -28
  14. package/lib/rules/enforce-early-destructuring.d.ts +2 -0
  15. package/lib/rules/enforce-early-destructuring.js +980 -0
  16. package/lib/rules/enforce-empty-object-check.d.ts +11 -0
  17. package/lib/rules/enforce-empty-object-check.js +502 -0
  18. package/lib/rules/enforce-exported-function-types.js +1 -1
  19. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
  20. package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
  21. package/lib/rules/enforce-firestore-facade.js +4 -2
  22. package/lib/rules/enforce-firestore-set-merge.js +3 -4
  23. package/lib/rules/enforce-global-constants.js +1 -1
  24. package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
  25. package/lib/rules/enforce-memoize-async.js +2 -6
  26. package/lib/rules/enforce-memoize-getters.js +39 -15
  27. package/lib/rules/enforce-microdiff.js +1 -1
  28. package/lib/rules/enforce-object-literal-as-const.js +4 -4
  29. package/lib/rules/enforce-positive-naming.js +4 -0
  30. package/lib/rules/enforce-props-argument-name.js +2 -2
  31. package/lib/rules/enforce-safe-stringify.js +1 -1
  32. package/lib/rules/enforce-serializable-params.js +3 -3
  33. package/lib/rules/enforce-singular-type-names.js +1 -1
  34. package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
  35. package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
  36. package/lib/rules/enforce-storage-context.d.ts +9 -0
  37. package/lib/rules/enforce-storage-context.js +646 -0
  38. package/lib/rules/enforce-timestamp-now.js +23 -7
  39. package/lib/rules/enforce-transform-memoization.d.ts +4 -0
  40. package/lib/rules/enforce-transform-memoization.js +416 -0
  41. package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
  42. package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
  43. package/lib/rules/enforce-unique-cursor-headers.js +365 -0
  44. package/lib/rules/enforce-verb-noun-naming.js +5 -4
  45. package/lib/rules/ensure-pointer-events-none.js +28 -2
  46. package/lib/rules/export-if-in-doubt.js +27 -4
  47. package/lib/rules/extract-global-constants.js +40 -13
  48. package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
  49. package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
  50. package/lib/rules/flatten-push-calls.d.ts +2 -0
  51. package/lib/rules/flatten-push-calls.js +428 -0
  52. package/lib/rules/global-const-style.js +50 -19
  53. package/lib/rules/jsdoc-above-field.d.ts +11 -0
  54. package/lib/rules/jsdoc-above-field.js +208 -0
  55. package/lib/rules/key-only-outermost-element.js +21 -3
  56. package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
  57. package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
  58. package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
  59. package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
  60. package/lib/rules/memo-nested-react-components.d.ts +8 -0
  61. package/lib/rules/memo-nested-react-components.js +410 -0
  62. package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
  63. package/lib/rules/no-always-true-false-conditions.js +68 -8
  64. package/lib/rules/no-array-length-in-deps.js +9 -3
  65. package/lib/rules/no-async-foreach.js +210 -12
  66. package/lib/rules/no-circular-references.js +18 -17
  67. package/lib/rules/no-class-instance-destructuring.js +55 -12
  68. package/lib/rules/no-complex-cloud-params.js +7 -3
  69. package/lib/rules/no-compositing-layer-props.js +2 -2
  70. package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
  71. package/lib/rules/no-console-error.d.ts +9 -0
  72. package/lib/rules/no-console-error.js +525 -0
  73. package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
  74. package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
  75. package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
  76. package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
  77. package/lib/rules/no-entire-object-hook-deps.js +4 -6
  78. package/lib/rules/no-excessive-parent-chain.js +1 -1
  79. package/lib/rules/no-explicit-return-type.d.ts +2 -1
  80. package/lib/rules/no-explicit-return-type.js +215 -26
  81. package/lib/rules/no-filter-without-return.js +5 -1
  82. package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
  83. package/lib/rules/no-firestore-jest-mock.js +126 -8
  84. package/lib/rules/no-firestore-object-arrays.js +67 -12
  85. package/lib/rules/no-handler-suffix.d.ts +12 -0
  86. package/lib/rules/no-handler-suffix.js +305 -0
  87. package/lib/rules/no-hungarian.js +1 -1
  88. package/lib/rules/no-inline-component-prop.d.ts +10 -0
  89. package/lib/rules/no-inline-component-prop.js +456 -0
  90. package/lib/rules/no-jsx-in-hooks.js +6 -1
  91. package/lib/rules/no-jsx-whitespace-literal.js +8 -2
  92. package/lib/rules/no-margin-properties.js +6 -6
  93. package/lib/rules/no-memoize-on-static.js +9 -1
  94. package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
  95. package/lib/rules/no-misused-switch-case.js +22 -1
  96. package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
  97. package/lib/rules/no-mixed-firestore-transactions.js +296 -34
  98. package/lib/rules/no-mock-firebase-admin.js +5 -2
  99. package/lib/rules/no-object-values-on-strings.js +10 -2
  100. package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
  101. package/lib/rules/no-passthrough-getters.js +38 -2
  102. package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
  103. package/lib/rules/no-redundant-annotation-assertion.js +402 -0
  104. package/lib/rules/no-redundant-param-types.js +16 -6
  105. package/lib/rules/no-redundant-this-params.d.ts +3 -0
  106. package/lib/rules/no-redundant-this-params.js +459 -0
  107. package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
  108. package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
  109. package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
  110. package/lib/rules/no-restricted-properties-fix.js +11 -10
  111. package/lib/rules/no-separate-loading-state.js +7 -16
  112. package/lib/rules/no-stale-state-across-await.js +1 -1
  113. package/lib/rules/no-type-assertion-returns.js +43 -49
  114. package/lib/rules/no-undefined-null-passthrough.js +61 -31
  115. package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
  116. package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
  117. package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
  118. package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
  119. package/lib/rules/no-unnecessary-destructuring.js +14 -4
  120. package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
  121. package/lib/rules/no-unpinned-dependencies.js +36 -5
  122. package/lib/rules/no-unused-props.d.ts +2 -2
  123. package/lib/rules/no-unused-props.js +295 -91
  124. package/lib/rules/no-unused-usestate.js +6 -2
  125. package/lib/rules/no-useless-fragment.js +28 -2
  126. package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
  127. package/lib/rules/no-useless-usememo-primitives.js +393 -0
  128. package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
  129. package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
  130. package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
  131. package/lib/rules/omit-index-html.d.ts +2 -1
  132. package/lib/rules/omit-index-html.js +62 -7
  133. package/lib/rules/optimize-object-boolean-conditions.js +6 -4
  134. package/lib/rules/parallelize-async-operations.js +24 -5
  135. package/lib/rules/prefer-batch-operations.d.ts +1 -3
  136. package/lib/rules/prefer-batch-operations.js +32 -5
  137. package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
  138. package/lib/rules/prefer-clone-deep.js +3 -3
  139. package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
  140. package/lib/rules/prefer-destructuring-no-class.js +244 -59
  141. package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
  142. package/lib/rules/prefer-docsetter-setall.js +243 -0
  143. package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
  144. package/lib/rules/prefer-fragment-component.js +1 -1
  145. package/lib/rules/prefer-fragment-shorthand.js +2 -1
  146. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
  147. package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
  148. package/lib/rules/prefer-global-router-state-key.js +88 -13
  149. package/lib/rules/prefer-memoized-props.d.ts +3 -0
  150. package/lib/rules/prefer-memoized-props.js +445 -0
  151. package/lib/rules/prefer-next-dynamic.js +60 -69
  152. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +9 -4
  153. package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
  154. package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
  155. package/lib/rules/prefer-params-over-parent-id.js +220 -171
  156. package/lib/rules/prefer-settings-object.js +2 -2
  157. package/lib/rules/prefer-type-over-interface.js +7 -2
  158. package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
  159. package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
  160. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
  161. package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
  162. package/lib/rules/prevent-children-clobber.d.ts +2 -0
  163. package/lib/rules/prevent-children-clobber.js +536 -0
  164. package/lib/rules/react-memoize-literals.d.ts +4 -0
  165. package/lib/rules/react-memoize-literals.js +495 -0
  166. package/lib/rules/react-usememo-should-be-component.js +2 -2
  167. package/lib/rules/require-hooks-default-params.js +17 -16
  168. package/lib/rules/require-https-error-cause.d.ts +4 -0
  169. package/lib/rules/require-https-error-cause.js +136 -0
  170. package/lib/rules/require-https-error.js +43 -21
  171. package/lib/rules/require-image-optimized.js +1 -1
  172. package/lib/rules/require-memo.js +1 -1
  173. package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
  174. package/lib/rules/require-memoize-jsx-returners.js +485 -0
  175. package/lib/rules/require-usememo-object-literals.js +2 -3
  176. package/lib/rules/test-file-location-enforcement.js +1 -1
  177. package/lib/rules/use-latest-callback.js +6 -5
  178. package/lib/rules/vertically-group-related-functions.d.ts +16 -0
  179. package/lib/rules/vertically-group-related-functions.js +480 -0
  180. package/lib/utils/ASTHelpers.d.ts +13 -1
  181. package/lib/utils/ASTHelpers.js +24 -0
  182. package/lib/utils/getMethodName.d.ts +27 -0
  183. package/lib/utils/getMethodName.js +35 -0
  184. package/lib/utils/tsTypeClassifier.d.ts +30 -0
  185. package/lib/utils/tsTypeClassifier.js +149 -0
  186. package/package.json +3 -1
@@ -16,11 +16,12 @@ exports.enforceTimestampNow = (0, createRule_1.createRule)({
16
16
  fixable: 'code',
17
17
  schema: [],
18
18
  messages: {
19
- preferTimestampNow: 'Use Timestamp.now() instead of creating a Date object and converting it. This is more efficient and idiomatic for Firestore.',
19
+ preferTimestampNow: '{{expression}} builds a timestamp from the local JS clock. Firestore timestamps should come from the SDK so they stay server-synced and avoid drift between app servers and clients. Use {{timestampAlias}}.now() so the timestamp is generated by Firestore; replace this expression with {{timestampAlias}}.now().',
20
20
  },
21
21
  },
22
22
  defaultOptions: [],
23
23
  create(context) {
24
+ const sourceCode = context.getSourceCode();
24
25
  // Only apply this rule to backend code (functions/src/)
25
26
  const filename = context.getFilename();
26
27
  if (!filename.includes('functions/src/')) {
@@ -151,14 +152,19 @@ exports.enforceTimestampNow = (0, createRule_1.createRule)({
151
152
  },
152
153
  CallExpression(node) {
153
154
  if (isTimestampFromDateWithNewDate(node)) {
155
+ if (node.callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
156
+ return;
157
+ }
158
+ const timestampObj = sourceCode.getText(node.callee.object);
154
159
  context.report({
155
160
  node,
156
161
  messageId: 'preferTimestampNow',
162
+ data: {
163
+ expression: sourceCode.getText(node),
164
+ timestampAlias: timestampObj,
165
+ },
157
166
  fix(fixer) {
158
167
  if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
159
- const timestampObj = context
160
- .getSourceCode()
161
- .getText(node.callee.object);
162
168
  return fixer.replaceText(node, `${timestampObj}.now()`);
163
169
  }
164
170
  return null;
@@ -166,14 +172,19 @@ exports.enforceTimestampNow = (0, createRule_1.createRule)({
166
172
  });
167
173
  }
168
174
  else if (isTimestampFromMillisWithDateNow(node)) {
175
+ if (node.callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
176
+ return;
177
+ }
178
+ const timestampObj = sourceCode.getText(node.callee.object);
169
179
  context.report({
170
180
  node,
171
181
  messageId: 'preferTimestampNow',
182
+ data: {
183
+ expression: sourceCode.getText(node),
184
+ timestampAlias: timestampObj,
185
+ },
172
186
  fix(fixer) {
173
187
  if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
174
- const timestampObj = context
175
- .getSourceCode()
176
- .getText(node.callee.object);
177
188
  return fixer.replaceText(node, `${timestampObj}.now()`);
178
189
  }
179
190
  return null;
@@ -204,9 +215,14 @@ exports.enforceTimestampNow = (0, createRule_1.createRule)({
204
215
  // Check if we have a Timestamp import before suggesting
205
216
  if (timestampAliases.size > 0) {
206
217
  const timestampName = Array.from(timestampAliases)[0];
218
+ const expressionText = sourceCode.getText(node);
207
219
  context.report({
208
220
  node,
209
221
  messageId: 'preferTimestampNow',
222
+ data: {
223
+ expression: expressionText,
224
+ timestampAlias: timestampName,
225
+ },
210
226
  fix(fixer) {
211
227
  return fixer.replaceText(node, `${timestampName}.now()`);
212
228
  },
@@ -0,0 +1,4 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ type MessageIds = 'memoizeTransformValue' | 'memoizeTransformOnChange' | 'useCorrectHook' | 'missingDependencies';
3
+ export declare const enforceTransformMemoization: TSESLint.RuleModule<MessageIds, [], TSESLint.RuleListener>;
4
+ export {};
@@ -0,0 +1,416 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enforceTransformMemoization = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.enforceTransformMemoization = (0, createRule_1.createRule)({
7
+ name: 'enforce-transform-memoization',
8
+ meta: {
9
+ type: 'problem',
10
+ docs: {
11
+ description: 'Enforce memoization of adaptValue transformValue/transformOnChange so the adapted component receives stable handlers and avoids unnecessary re-renders.',
12
+ recommended: 'error',
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ memoizeTransformValue: 'transformValue is recreated on every render. Wrap it in useMemo (or reference a memoized helper) so adaptValue passes a stable transformer and avoids rerendering the adapted component.',
17
+ memoizeTransformOnChange: 'transformOnChange handler is recreated on every render. Wrap it in useCallback (or another memoized helper) so adaptValue passes a stable onChange and prevents extra renders and stale closures.',
18
+ useCorrectHook: '{{propName}} should be memoized with {{expectedHook}}. {{actualHook}} recreates a new transform on every render and hides intent; wrap the transform with {{expectedHook}} so React preserves its identity and avoids churn.',
19
+ missingDependencies: '{{hook}} for {{propName}} is missing {{deps}} in its dependency array. Include every external value the transform closes over so the memoized function stays in sync and does not capture stale data.',
20
+ },
21
+ },
22
+ defaultOptions: [],
23
+ create(context) {
24
+ const sourceCode = context.getSourceCode();
25
+ const scopeManager = sourceCode.scopeManager;
26
+ const adaptValueNames = new Set(['adaptValue']);
27
+ const memoizingHooks = new Set(['useMemo', 'useCallback']);
28
+ const stabilizingUtilities = new Set(['useEvent']);
29
+ const getPropertyName = (key) => {
30
+ if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
31
+ return key.name;
32
+ }
33
+ if (key.type === utils_1.AST_NODE_TYPES.Literal &&
34
+ typeof key.value === 'string') {
35
+ return key.value;
36
+ }
37
+ return null;
38
+ };
39
+ const isFunctionExpression = (node) => node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
40
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression;
41
+ const unwrapExpression = (node) => {
42
+ if (node.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
43
+ node.type === utils_1.AST_NODE_TYPES.TSTypeAssertion) {
44
+ return unwrapExpression(node.expression);
45
+ }
46
+ if (node.type === utils_1.AST_NODE_TYPES.ChainExpression) {
47
+ return unwrapExpression(node.expression);
48
+ }
49
+ return node;
50
+ };
51
+ const getCalleeName = (callee) => {
52
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
53
+ return callee.name;
54
+ }
55
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
56
+ !callee.computed &&
57
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
58
+ return callee.property.name;
59
+ }
60
+ return '';
61
+ };
62
+ const isTopLevelScope = (scope) => scope.type === 'global' || scope.type === 'module';
63
+ const findVariableInScopeChain = (identifier) => {
64
+ if (!scopeManager)
65
+ return null;
66
+ let currentNode = identifier;
67
+ let scope = null;
68
+ while (currentNode && !scope) {
69
+ scope = scopeManager.acquire(currentNode, true);
70
+ currentNode = currentNode.parent ?? undefined;
71
+ }
72
+ if (!scope) {
73
+ scope = scopeManager.globalScope;
74
+ }
75
+ if (!scope)
76
+ return null;
77
+ let currentScope = scope;
78
+ while (currentScope) {
79
+ const variable = currentScope.variables.find((v) => v.name === identifier.name);
80
+ if (variable)
81
+ return variable;
82
+ currentScope = currentScope.upper;
83
+ }
84
+ return null;
85
+ };
86
+ const isPropertyKeyIdentifier = (identifier) => identifier.parent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
87
+ identifier.parent.property === identifier &&
88
+ !identifier.parent.computed;
89
+ const isScopeAncestor = (maybeAncestor, scope) => {
90
+ let current = scope.upper;
91
+ while (current) {
92
+ if (current === maybeAncestor)
93
+ return true;
94
+ current = current.upper;
95
+ }
96
+ return false;
97
+ };
98
+ const collectExternalDependencies = (callback) => {
99
+ const deps = new Set();
100
+ if (!scopeManager)
101
+ return deps;
102
+ const rootScope = scopeManager.acquire(callback);
103
+ if (!rootScope)
104
+ return deps;
105
+ const walkScopes = (scope) => {
106
+ for (const ref of scope.references) {
107
+ const resolved = ref.resolved;
108
+ if (!resolved)
109
+ continue;
110
+ if (isPropertyKeyIdentifier(ref.identifier))
111
+ continue;
112
+ const resolvedScope = resolved.scope;
113
+ if (isScopeAncestor(resolvedScope, rootScope) &&
114
+ !isTopLevelScope(resolvedScope)) {
115
+ deps.add(ref.identifier.name);
116
+ }
117
+ }
118
+ for (const child of scope.childScopes) {
119
+ walkScopes(child);
120
+ }
121
+ };
122
+ walkScopes(rootScope);
123
+ return deps;
124
+ };
125
+ const extractDependencyNames = (depsArg) => {
126
+ const dependencyNames = new Set();
127
+ const addName = (name) => {
128
+ if (name)
129
+ dependencyNames.add(name);
130
+ };
131
+ const extractFromExpression = (expression) => {
132
+ if (expression.type === utils_1.AST_NODE_TYPES.Identifier) {
133
+ return expression.name;
134
+ }
135
+ if (expression.type === utils_1.AST_NODE_TYPES.MemberExpression &&
136
+ !expression.computed) {
137
+ const objectExpr = expression.object;
138
+ if (objectExpr.type === utils_1.AST_NODE_TYPES.Identifier) {
139
+ return objectExpr.name;
140
+ }
141
+ }
142
+ if (expression.type === utils_1.AST_NODE_TYPES.ChainExpression) {
143
+ return extractFromExpression(expression.expression);
144
+ }
145
+ return null;
146
+ };
147
+ for (const element of depsArg.elements) {
148
+ if (!element)
149
+ continue;
150
+ if (element.type === utils_1.AST_NODE_TYPES.SpreadElement)
151
+ continue;
152
+ addName(extractFromExpression(element));
153
+ }
154
+ return dependencyNames;
155
+ };
156
+ const formatMissingDeps = (missing, hasArray) => {
157
+ if (missing.size === 0 && !hasArray) {
158
+ return 'a dependency array (use [] when the transform has no external values)';
159
+ }
160
+ if (missing.size === 0) {
161
+ return 'all external values referenced by the transform';
162
+ }
163
+ return Array.from(missing).join(', ');
164
+ };
165
+ const getVariableInitializer = (variable) => {
166
+ for (const def of variable.defs) {
167
+ if (def.type === 'Variable' && def.node.init) {
168
+ return def.node.init;
169
+ }
170
+ if (def.type === 'FunctionName') {
171
+ return def.node;
172
+ }
173
+ }
174
+ return null;
175
+ };
176
+ const resolveValueForKey = (expression, key, depth = 0) => {
177
+ if (!expression || depth > 5)
178
+ return null;
179
+ const unwrapped = unwrapExpression(expression);
180
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
181
+ let found = null;
182
+ for (const prop of unwrapped.properties) {
183
+ if (prop.type === utils_1.AST_NODE_TYPES.Property) {
184
+ const keyName = getPropertyName(prop.key);
185
+ if (keyName === key) {
186
+ found = prop.value;
187
+ }
188
+ }
189
+ else if (prop.type === utils_1.AST_NODE_TYPES.SpreadElement) {
190
+ const nested = resolveValueForKey(prop.argument, key, depth + 1);
191
+ if (nested) {
192
+ found = nested;
193
+ }
194
+ }
195
+ }
196
+ if (found)
197
+ return found;
198
+ }
199
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.Identifier) {
200
+ const variable = findVariableInScopeChain(unwrapped);
201
+ const init = variable ? getVariableInitializer(variable) : null;
202
+ if (init) {
203
+ return resolveValueForKey(init, key, depth + 1);
204
+ }
205
+ }
206
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.MemberExpression &&
207
+ !unwrapped.computed) {
208
+ const propertyName = getPropertyName(unwrapped.property);
209
+ if (!propertyName)
210
+ return null;
211
+ if (unwrapped.object.type !== utils_1.AST_NODE_TYPES.Super) {
212
+ const resolvedMember = resolveValueForKey(unwrapped.object, propertyName, depth + 1);
213
+ if (!resolvedMember)
214
+ return null;
215
+ if (propertyName !== key &&
216
+ resolvedMember.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
217
+ return resolveValueForKey(resolvedMember, key, depth + 1);
218
+ }
219
+ return resolvedMember;
220
+ }
221
+ }
222
+ return null;
223
+ };
224
+ const resolveOptionsObject = (node) => {
225
+ if (!node)
226
+ return null;
227
+ const unwrapped = unwrapExpression(node);
228
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
229
+ return unwrapped;
230
+ }
231
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.Identifier) {
232
+ const variable = findVariableInScopeChain(unwrapped);
233
+ const init = variable ? getVariableInitializer(variable) : null;
234
+ if (init && init.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
235
+ return init;
236
+ }
237
+ }
238
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.MemberExpression &&
239
+ !unwrapped.computed) {
240
+ const objectExpression = unwrapped.object.type === utils_1.AST_NODE_TYPES.Super
241
+ ? null
242
+ : resolveOptionsObject(unwrapped.object);
243
+ if (objectExpression) {
244
+ const propertyName = getPropertyName(unwrapped.property);
245
+ if (propertyName) {
246
+ const nested = resolveValueForKey(objectExpression, propertyName);
247
+ if (nested && nested.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
248
+ return nested;
249
+ }
250
+ }
251
+ }
252
+ }
253
+ return null;
254
+ };
255
+ const analyzeMemoization = (node, propName, expectedHook, seenIdentifiers) => {
256
+ const unwrapped = unwrapExpression(node);
257
+ if (isFunctionExpression(unwrapped)) {
258
+ return {
259
+ ok: false,
260
+ messageId: propName === 'transformValue'
261
+ ? 'memoizeTransformValue'
262
+ : 'memoizeTransformOnChange',
263
+ };
264
+ }
265
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
266
+ return {
267
+ ok: false,
268
+ messageId: propName === 'transformValue'
269
+ ? 'memoizeTransformValue'
270
+ : 'memoizeTransformOnChange',
271
+ };
272
+ }
273
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.CallExpression) {
274
+ const calleeName = getCalleeName(unwrapped.callee);
275
+ if (memoizingHooks.has(calleeName)) {
276
+ if ((expectedHook === 'useMemo' && calleeName === 'useCallback') ||
277
+ (expectedHook === 'useCallback' && calleeName === 'useMemo')) {
278
+ return {
279
+ ok: false,
280
+ messageId: 'useCorrectHook',
281
+ data: {
282
+ propName,
283
+ expectedHook,
284
+ actualHook: calleeName,
285
+ },
286
+ };
287
+ }
288
+ const callback = unwrapped.arguments[0];
289
+ const depsArg = unwrapped.arguments[unwrapped.arguments.length - 1];
290
+ const externalDeps = callback && isFunctionExpression(callback)
291
+ ? collectExternalDependencies(callback)
292
+ : new Set();
293
+ if (!depsArg || depsArg.type !== utils_1.AST_NODE_TYPES.ArrayExpression) {
294
+ return {
295
+ ok: false,
296
+ messageId: 'missingDependencies',
297
+ data: {
298
+ hook: calleeName,
299
+ propName,
300
+ deps: formatMissingDeps(externalDeps, false),
301
+ },
302
+ };
303
+ }
304
+ if (externalDeps.size > 0) {
305
+ const declaredDeps = extractDependencyNames(depsArg);
306
+ const missingDeps = new Set();
307
+ for (const dep of externalDeps) {
308
+ if (!declaredDeps.has(dep)) {
309
+ missingDeps.add(dep);
310
+ }
311
+ }
312
+ if (missingDeps.size > 0) {
313
+ return {
314
+ ok: false,
315
+ messageId: 'missingDependencies',
316
+ data: {
317
+ hook: calleeName,
318
+ propName,
319
+ deps: formatMissingDeps(missingDeps, true),
320
+ },
321
+ };
322
+ }
323
+ }
324
+ return { ok: true };
325
+ }
326
+ if (stabilizingUtilities.has(calleeName)) {
327
+ return { ok: true };
328
+ }
329
+ return {
330
+ ok: false,
331
+ messageId: propName === 'transformValue'
332
+ ? 'memoizeTransformValue'
333
+ : 'memoizeTransformOnChange',
334
+ };
335
+ }
336
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.Identifier) {
337
+ if (seenIdentifiers.has(unwrapped.name)) {
338
+ return { ok: true };
339
+ }
340
+ seenIdentifiers.add(unwrapped.name);
341
+ const variable = findVariableInScopeChain(unwrapped);
342
+ if (!variable) {
343
+ return { ok: true };
344
+ }
345
+ if (variable.defs.some((def) => def.type === 'Parameter') ||
346
+ isTopLevelScope(variable.scope)) {
347
+ return { ok: true };
348
+ }
349
+ const init = getVariableInitializer(variable);
350
+ if (!init) {
351
+ return {
352
+ ok: false,
353
+ messageId: propName === 'transformValue'
354
+ ? 'memoizeTransformValue'
355
+ : 'memoizeTransformOnChange',
356
+ };
357
+ }
358
+ return analyzeMemoization(init, propName, expectedHook, seenIdentifiers);
359
+ }
360
+ return {
361
+ ok: false,
362
+ messageId: propName === 'transformValue'
363
+ ? 'memoizeTransformValue'
364
+ : 'memoizeTransformOnChange',
365
+ };
366
+ };
367
+ const checkTransformProperty = (node, propName, expectedHook) => {
368
+ const result = analyzeMemoization(node, propName, expectedHook, new Set());
369
+ if (result.ok)
370
+ return;
371
+ context.report({
372
+ node,
373
+ messageId: result.messageId,
374
+ data: result.data,
375
+ });
376
+ };
377
+ return {
378
+ ImportDeclaration(node) {
379
+ const sourceValue = typeof node.source.value === 'string' ? node.source.value : '';
380
+ for (const specifier of node.specifiers) {
381
+ if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
382
+ specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
383
+ specifier.imported.name === 'adaptValue') {
384
+ adaptValueNames.add(specifier.local.name);
385
+ }
386
+ if (specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier &&
387
+ (specifier.local.name === 'adaptValue' ||
388
+ sourceValue.includes('adaptValue'))) {
389
+ adaptValueNames.add(specifier.local.name);
390
+ }
391
+ }
392
+ },
393
+ CallExpression(node) {
394
+ if (node.callee.type !== utils_1.AST_NODE_TYPES.Identifier ||
395
+ !adaptValueNames.has(node.callee.name)) {
396
+ return;
397
+ }
398
+ const optionsArg = node.arguments[0];
399
+ if (!optionsArg)
400
+ return;
401
+ const optionsObject = resolveOptionsObject(optionsArg);
402
+ if (!optionsObject)
403
+ return;
404
+ const transformValueNode = resolveValueForKey(optionsObject, 'transformValue');
405
+ if (transformValueNode) {
406
+ checkTransformProperty(transformValueNode, 'transformValue', 'useMemo');
407
+ }
408
+ const transformOnChangeNode = resolveValueForKey(optionsObject, 'transformOnChange');
409
+ if (transformOnChangeNode) {
410
+ checkTransformProperty(transformOnChangeNode, 'transformOnChange', 'useCallback');
411
+ }
412
+ },
413
+ };
414
+ },
415
+ });
416
+ //# sourceMappingURL=enforce-transform-memoization.js.map
@@ -5,8 +5,12 @@ const createRule_1 = require("../utils/createRule");
5
5
  const FENCE = '```';
6
6
  function isFenceLine(text, fenceIndex, indent) {
7
7
  const lineEnd = text.indexOf('\n', fenceIndex);
8
- const afterFence = lineEnd === -1 ? text.slice(fenceIndex + FENCE.length) : text.slice(fenceIndex + FENCE.length, lineEnd);
9
- return afterFence.trim().length === 0 && text.slice(text.lastIndexOf('\n', fenceIndex - 1) + 1, fenceIndex) === indent;
8
+ const afterFence = lineEnd === -1
9
+ ? text.slice(fenceIndex + FENCE.length)
10
+ : text.slice(fenceIndex + FENCE.length, lineEnd);
11
+ return (afterFence.trim().length === 0 &&
12
+ text.slice(text.lastIndexOf('\n', fenceIndex - 1) + 1, fenceIndex) ===
13
+ indent);
10
14
  }
11
15
  function findClosingFence(text, startIndex, indent) {
12
16
  let searchIndex = startIndex;
@@ -47,7 +51,7 @@ exports.enforceTypescriptMarkdownCodeBlocks = (0, createRule_1.createRule)({
47
51
  }
48
52
  return {
49
53
  Program() {
50
- const sourceCode = context.getSourceCode();
54
+ const sourceCode = context.sourceCode;
51
55
  const text = sourceCode.getText();
52
56
  let index = 0;
53
57
  while (index < text.length) {
@@ -0,0 +1,16 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ type NormalizedOptions = {
3
+ requiredPatterns: string[];
4
+ excludedPatterns: string[];
5
+ requiredTags: string[];
6
+ allowSplitHeaders: boolean;
7
+ headerTemplate: string | null;
8
+ ignoreGeneratedFiles: boolean;
9
+ generatedMarkers: string[];
10
+ excludedAtDirectives: string[];
11
+ };
12
+ type Options = [Partial<NormalizedOptions>];
13
+ type MessageIds = 'missingHeader' | 'duplicateHeader' | 'splitHeaderFragment';
14
+ export declare const RULE_NAME = "enforce-unique-cursor-headers";
15
+ export declare const enforceUniqueCursorHeaders: TSESLint.RuleModule<MessageIds, Options, TSESLint.RuleListener>;
16
+ export {};