@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
@@ -12,6 +12,47 @@ const DEFAULT_USE_DYNAMIC_SOURCES = [
12
12
  function matchesAllowedSource(source, allowedSources) {
13
13
  return allowedSources.some((pattern) => source === pattern || source.endsWith(`/${pattern}`));
14
14
  }
15
+ function hasNodeStructure(value) {
16
+ return (!!value &&
17
+ typeof value === 'object' &&
18
+ 'type' in value &&
19
+ typeof value.type === 'string');
20
+ }
21
+ /**
22
+ * Walks the AST and invokes the visitor on each node.
23
+ * Uses WeakSet to prevent infinite cycles.
24
+ *
25
+ * @param visitor - Callback invoked on each node. Return `false` to stop
26
+ * traversal; return `undefined` (or nothing) to continue walking.
27
+ * @returns `false` if traversal stopped early; `true` when traversal completes.
28
+ */
29
+ function walkAst(node, visitor, visited = new WeakSet()) {
30
+ if (visited.has(node))
31
+ return true;
32
+ visited.add(node);
33
+ const shouldContinue = visitor(node);
34
+ if (shouldContinue === false)
35
+ return false;
36
+ const anyNode = node;
37
+ for (const key of Object.keys(anyNode)) {
38
+ if (key === 'parent')
39
+ continue;
40
+ const child = anyNode[key];
41
+ if (Array.isArray(child)) {
42
+ for (const c of child) {
43
+ if (hasNodeStructure(c)) {
44
+ if (!walkAst(c, visitor, visited))
45
+ return false;
46
+ }
47
+ }
48
+ }
49
+ else if (hasNodeStructure(child)) {
50
+ if (!walkAst(child, visitor, visited))
51
+ return false;
52
+ }
53
+ }
54
+ return true;
55
+ }
15
56
  function isUseDynamicCall(node) {
16
57
  const { callee, arguments: args } = node;
17
58
  if (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
@@ -126,7 +167,7 @@ exports.preferNextDynamic = (0, createRule_1.createRule)({
126
167
  docs: {
127
168
  description: 'Prefer Next.js dynamic() over custom useDynamic() for component imports',
128
169
  recommended: 'error',
129
- requiresTypeChecking: true,
170
+ requiresTypeChecking: false,
130
171
  },
131
172
  fixable: 'code',
132
173
  schema: [
@@ -143,9 +184,9 @@ exports.preferNextDynamic = (0, createRule_1.createRule)({
143
184
  },
144
185
  ],
145
186
  messages: {
146
- preferNextDynamic: 'Use Next.js dynamic() instead of useDynamic(import(...)) for component imports',
147
- addNextDynamicImport: "Add default import: import dynamic from 'next/dynamic'",
148
- removeUseDynamicImport: 'Remove unused useDynamic import',
187
+ preferNextDynamic: 'Component "{{componentName}}" is created with useDynamic(import(...)), which bypasses Next.js dynamic() handling for client-only components and leaves SSR control to a custom wrapper. Wrap the import in dynamic(() => import(...), { ssr: false }) so Next.js manages code-splitting and disables server rendering safely.',
188
+ addNextDynamicImport: "The auto-fix will replace useDynamic(import(...)) with dynamic(() => import(...), { ssr: false }), which references Next.js's dynamic function. Without importing dynamic from 'next/dynamic', the fixed code will throw a ReferenceError at runtime when the module loads. Add `import dynamic from 'next/dynamic'` at the top of the file to make the dynamic identifier available.",
189
+ removeUseDynamicImport: 'Remove the unused useDynamic import after migrating to dynamic(); leaving the custom hook imported invites accidental reuse and keeps dead code in the bundle.',
149
190
  },
150
191
  },
151
192
  defaultOptions: [{}],
@@ -154,7 +195,7 @@ exports.preferNextDynamic = (0, createRule_1.createRule)({
154
195
  const allowedUseDynamicSources = options.useDynamicSources && options.useDynamicSources.length > 0
155
196
  ? options.useDynamicSources
156
197
  : DEFAULT_USE_DYNAMIC_SOURCES;
157
- const sourceCode = context.getSourceCode();
198
+ const sourceCode = context.sourceCode;
158
199
  return {
159
200
  VariableDeclarator(node) {
160
201
  const info = inferVariableInfo(node);
@@ -169,50 +210,18 @@ exports.preferNextDynamic = (0, createRule_1.createRule)({
169
210
  if (!useDynamicImportInfo)
170
211
  return;
171
212
  const identifierName = info.idText;
172
- let usedInJsx = false;
173
- const scopeBody = program.body;
174
- // Walk AST to find JSX usage; track visited nodes to avoid cycles
175
- // Note: We intentionally avoid relying on tokens here.
176
- // Fallback AST traversal for JSX usage
177
- const visited = new WeakSet();
178
- const checkJsxUsage = (n) => {
179
- if (visited.has(n))
180
- return;
181
- visited.add(n);
182
- if (usedInJsx)
183
- return;
213
+ // walkAst returns false when visitor returns false (early exit on match)
214
+ const usedInJsx = program.body.some((b) => walkAst(b, (n) => {
184
215
  if (n.type === utils_1.AST_NODE_TYPES.JSXOpeningElement) {
185
216
  const name = n.name;
186
217
  if (name.type === utils_1.AST_NODE_TYPES.JSXIdentifier) {
187
- if (name.name === identifierName)
188
- usedInJsx = true;
189
- }
190
- else if (name.type === utils_1.AST_NODE_TYPES.JSXMemberExpression) {
191
- // Not matching identifier simple usage
192
- }
193
- }
194
- // recurse
195
- const anyNode = n;
196
- for (const key of Object.keys(anyNode)) {
197
- if (key === 'parent')
198
- continue;
199
- const child = anyNode[key];
200
- if (Array.isArray(child)) {
201
- for (const c of child) {
202
- if (c && typeof c.type === 'string') {
203
- checkJsxUsage(c);
204
- }
205
- }
206
- }
207
- else if (child && typeof child === 'object') {
208
- if (child.type &&
209
- typeof child.type === 'string') {
210
- checkJsxUsage(child);
218
+ if (name.name === identifierName) {
219
+ return false;
211
220
  }
212
221
  }
213
222
  }
214
- };
215
- scopeBody.forEach((b) => checkJsxUsage(b));
223
+ return undefined;
224
+ }) === false);
216
225
  if (!usedInJsx) {
217
226
  // Skip to avoid flagging non-component dynamic imports
218
227
  return;
@@ -223,10 +232,11 @@ exports.preferNextDynamic = (0, createRule_1.createRule)({
223
232
  context.report({
224
233
  node: init,
225
234
  messageId: 'preferNextDynamic',
235
+ data: { componentName: identifierName },
226
236
  fix(fixer) {
227
237
  const fixes = [];
228
238
  // ensure dynamic import is present
229
- const programNode = findProgramNode(node);
239
+ const programNode = program;
230
240
  let dynamicLocal = getNextDynamicLocalName(programNode);
231
241
  const hasDynamic = !!dynamicLocal;
232
242
  if (!hasDynamic) {
@@ -266,33 +276,14 @@ exports.preferNextDynamic = (0, createRule_1.createRule)({
266
276
  const latestUseDynamicImport = findUseDynamicImport(programNode, allowedUseDynamicSources);
267
277
  if (latestUseDynamicImport) {
268
278
  // Abort removal if there are other useDynamic(import(...)) calls in the file
269
- let otherUseDynamicCalls = false;
270
- const visit = (n) => {
271
- if (otherUseDynamicCalls)
272
- return;
279
+ const otherUseDynamicCalls = programNode.body.some((b) => walkAst(b, (n) => {
273
280
  if (n.type === utils_1.AST_NODE_TYPES.CallExpression &&
274
- isUseDynamicCall(n)) {
275
- if (n !== init)
276
- otherUseDynamicCalls = true;
277
- }
278
- const anyNode = n;
279
- for (const key of Object.keys(anyNode)) {
280
- if (key === 'parent')
281
- continue;
282
- const child = anyNode[key];
283
- if (Array.isArray(child)) {
284
- for (const c of child)
285
- if (c && typeof c.type === 'string')
286
- visit(c);
287
- }
288
- else if (child &&
289
- typeof child === 'object' &&
290
- child.type) {
291
- visit(child);
292
- }
281
+ isUseDynamicCall(n) &&
282
+ n !== init) {
283
+ return false;
293
284
  }
294
- };
295
- programNode.body.forEach((b) => visit(b));
285
+ return undefined;
286
+ }) === false);
296
287
  if (otherUseDynamicCalls) {
297
288
  return fixes; // keep the import; other occurrences still rely on it
298
289
  }
@@ -396,7 +396,7 @@ exports.preferNullishCoalescingBooleanProps = (0, createRule_1.createRule)({
396
396
  },
397
397
  fixable: 'code',
398
398
  messages: {
399
- preferNullishCoalescing: 'Prefer using nullish coalescing operator (??) instead of logical OR operator (||) when checking for null/undefined',
399
+ preferNullishCoalescing: 'Logical OR between "{{left}}" and "{{right}}" treats every falsy value (false, 0, "", NaN) as missing and will override intentional boolean or empty states. Use the nullish coalescing operator (??) so "{{right}}" only applies when "{{left}}" is null or undefined, preserving explicit false/0/"" values.',
400
400
  },
401
401
  schema: [],
402
402
  },
@@ -412,13 +412,18 @@ exports.preferNullishCoalescingBooleanProps = (0, createRule_1.createRule)({
412
412
  // Check if this could benefit from nullish coalescing
413
413
  // We only suggest nullish coalescing when the left operand could be nullish
414
414
  if (couldBeNullish(node.left)) {
415
+ const sourceCode = context.getSourceCode();
416
+ const leftText = sourceCode.getText(node.left);
417
+ const rightText = sourceCode.getText(node.right);
415
418
  context.report({
416
419
  node,
417
420
  messageId: 'preferNullishCoalescing',
421
+ data: {
422
+ left: leftText,
423
+ right: rightText,
424
+ },
418
425
  fix(fixer) {
419
- return fixer.replaceText(node, `${context.getSourceCode().getText(node.left)} ?? ${context
420
- .getSourceCode()
421
- .getText(node.right)}`);
426
+ return fixer.replaceText(node, `${leftText} ?? ${rightText}`);
422
427
  },
423
428
  });
424
429
  }
@@ -1,6 +1,7 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
1
2
  /**
2
3
  * This rule overrides the behavior of @typescript-eslint/prefer-nullish-coalescing
3
4
  * to only suggest using the nullish coalescing operator when checking for null/undefined,
4
5
  * not when intentionally checking for all falsy values.
5
6
  */
6
- export declare const preferNullishCoalescingOverride: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"preferNullishCoalescing", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
7
+ export declare const preferNullishCoalescingOverride: TSESLint.RuleModule<"preferNullishCoalescing", [], TSESLint.RuleListener>;
@@ -19,11 +19,13 @@ exports.preferNullishCoalescingOverride = (0, createRule_1.createRule)({
19
19
  fixable: 'code',
20
20
  schema: [],
21
21
  messages: {
22
- preferNullishCoalescing: 'Prefer using nullish coalescing operator (`??`) instead of logical OR operator (`||`) when only checking for null/undefined.',
22
+ preferNullishCoalescing: 'Replace "{{left}} || {{right}}" with nullish coalescing when you only want a fallback for null or undefined. The logical OR operator treats "", 0, and false as missing values and silently swaps in the fallback, which hides valid data. Use "{{left}} ?? {{right}}" so only nullish inputs trigger the fallback.',
23
23
  },
24
24
  },
25
25
  defaultOptions: [],
26
26
  create(context) {
27
+ const contextWithSource = context;
28
+ const sourceCode = contextWithSource.sourceCode ?? contextWithSource.getSourceCode();
27
29
  /**
28
30
  * Checks if a node is in a boolean context where truthiness matters
29
31
  */
@@ -154,7 +156,8 @@ exports.preferNullishCoalescingOverride = (0, createRule_1.createRule)({
154
156
  /**
155
157
  * Checks if this logical OR should be converted to nullish coalescing
156
158
  */
157
- function shouldConvertToNullishCoalescing(_node) {
159
+ function shouldConvertToNullishCoalescing(node) {
160
+ void node;
158
161
  return false;
159
162
  }
160
163
  void isInBooleanContext;
@@ -164,12 +167,15 @@ exports.preferNullishCoalescingOverride = (0, createRule_1.createRule)({
164
167
  return {
165
168
  LogicalExpression(node) {
166
169
  if (shouldConvertToNullishCoalescing(node)) {
167
- const sc = context.getSourceCode();
168
- const leftText = sc.getText(node.left);
169
- const rightText = sc.getText(node.right);
170
+ const leftText = sourceCode.getText(node.left);
171
+ const rightText = sourceCode.getText(node.right);
170
172
  context.report({
171
173
  node,
172
174
  messageId: 'preferNullishCoalescing',
175
+ data: {
176
+ left: leftText,
177
+ right: rightText,
178
+ },
173
179
  fix(fixer) {
174
180
  return fixer.replaceText(node, `${leftText} ?? ${rightText}`);
175
181
  },