@blumintinc/eslint-plugin-blumint 1.14.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 (49) hide show
  1. package/README.md +10 -11
  2. package/lib/index.js +14 -12
  3. package/lib/rules/avoid-utils-directory.js +0 -4
  4. package/lib/rules/consistent-callback-naming.js +42 -3
  5. package/lib/rules/dynamic-https-errors.d.ts +1 -1
  6. package/lib/rules/dynamic-https-errors.js +132 -41
  7. package/lib/rules/enforce-boolean-naming-prefixes.js +0 -212
  8. package/lib/rules/enforce-date-ttime.d.ts +1 -0
  9. package/lib/rules/enforce-date-ttime.js +156 -0
  10. package/lib/rules/enforce-dynamic-firebase-imports.d.ts +2 -1
  11. package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
  12. package/lib/rules/enforce-f-extension-for-entry-points.d.ts +8 -0
  13. package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
  14. package/lib/rules/enforce-global-constants.js +3 -3
  15. package/lib/rules/enforce-id-capitalization.js +1 -1
  16. package/lib/rules/enforce-memoize-async.js +69 -15
  17. package/lib/rules/enforce-props-argument-name.js +42 -16
  18. package/lib/rules/enforce-stable-hash-spread-props.js +3 -3
  19. package/lib/rules/enforce-transform-memoization.js +1 -1
  20. package/lib/rules/enforce-verb-noun-naming.js +3814 -4641
  21. package/lib/rules/global-const-style.js +16 -4
  22. package/lib/rules/memo-compare-deeply-complex-props.js +16 -3
  23. package/lib/rules/memo-nested-react-components.js +108 -103
  24. package/lib/rules/no-async-foreach.js +7 -2
  25. package/lib/rules/no-circular-references.d.ts +2 -1
  26. package/lib/rules/no-circular-references.js +13 -15
  27. package/lib/rules/no-console-error.js +12 -10
  28. package/lib/rules/no-empty-dependency-use-callbacks.js +1 -1
  29. package/lib/rules/no-entire-object-hook-deps.js +48 -1
  30. package/lib/rules/no-excessive-parent-chain.js +3 -0
  31. package/lib/rules/no-inline-component-prop.js +16 -7
  32. package/lib/rules/no-passthrough-getters.d.ts +2 -2
  33. package/lib/rules/no-passthrough-getters.js +83 -1
  34. package/lib/rules/no-redundant-this-params.js +50 -1
  35. package/lib/rules/no-unmemoized-memo-without-props.js +1 -1
  36. package/lib/rules/no-unnecessary-destructuring-rename.js +2 -5
  37. package/lib/rules/parallelize-async-operations.js +119 -54
  38. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +109 -4
  39. package/lib/rules/prefer-params-over-parent-id.js +1 -1
  40. package/lib/rules/prefer-settings-object.js +27 -10
  41. package/lib/rules/prefer-type-alias-over-typeof-constant.js +9 -0
  42. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +1 -1
  43. package/lib/rules/prevent-children-clobber.js +9 -5
  44. package/lib/rules/react-memoize-literals.js +131 -12
  45. package/lib/rules/require-https-error-cause.js +30 -11
  46. package/lib/rules/require-memo.js +17 -9
  47. package/lib/utils/ASTHelpers.d.ts +34 -1
  48. package/lib/utils/ASTHelpers.js +346 -112
  49. package/package.json +1 -1
@@ -1,9 +1,33 @@
1
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
+ };
2
25
  Object.defineProperty(exports, "__esModule", { value: true });
3
26
  exports.noPassthroughGetters = void 0;
4
27
  const createRule_1 = require("../utils/createRule");
5
28
  const utils_1 = require("@typescript-eslint/utils");
6
29
  const getMethodName_1 = require("../utils/getMethodName");
30
+ const ts = __importStar(require("typescript"));
7
31
  const COMPLEX_EXPRESSION_TYPES = new Set([
8
32
  utils_1.AST_NODE_TYPES.CallExpression,
9
33
  utils_1.AST_NODE_TYPES.TemplateLiteral,
@@ -14,7 +38,7 @@ const COMPLEX_EXPRESSION_TYPES = new Set([
14
38
  ]);
15
39
  exports.noPassthroughGetters = (0, createRule_1.createRule)({
16
40
  create(context) {
17
- const sourceCode = context.getSourceCode();
41
+ const sourceCode = context.sourceCode;
18
42
  return {
19
43
  // Target getter methods in classes
20
44
  MethodDefinition(node) {
@@ -26,6 +50,10 @@ exports.noPassthroughGetters = (0, createRule_1.createRule)({
26
50
  if (node.decorators && node.decorators.length > 0) {
27
51
  return;
28
52
  }
53
+ // Check if this getter satisfies an interface or overrides a base class member
54
+ if (isRequiredByInterfaceOrBaseClass(node, sourceCode)) {
55
+ return;
56
+ }
29
57
  const methodBody = node.value.body;
30
58
  if (!methodBody) {
31
59
  return;
@@ -74,6 +102,60 @@ exports.noPassthroughGetters = (0, createRule_1.createRule)({
74
102
  }
75
103
  },
76
104
  };
105
+ /**
106
+ * Check if the getter is required by an implemented interface or overrides a base class member
107
+ */
108
+ function isRequiredByInterfaceOrBaseClass(node, sourceCode) {
109
+ const parserServices = sourceCode.parserServices;
110
+ if (!parserServices ||
111
+ !parserServices.program ||
112
+ !parserServices.esTreeNodeToTSNodeMap) {
113
+ return false;
114
+ }
115
+ const checker = parserServices.program.getTypeChecker();
116
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
117
+ if (!tsNode) {
118
+ return false;
119
+ }
120
+ const symbol = checker.getSymbolAtLocation(tsNode.name);
121
+ if (!symbol ||
122
+ !tsNode.parent ||
123
+ !(ts.isClassDeclaration(tsNode.parent) ||
124
+ ts.isClassExpression(tsNode.parent))) {
125
+ return false;
126
+ }
127
+ const classNode = tsNode.parent;
128
+ const classType = checker.getTypeAtLocation(classNode);
129
+ const classSymbol = classType.getSymbol();
130
+ if (!classSymbol) {
131
+ return false;
132
+ }
133
+ const instanceType = checker.getDeclaredTypeOfSymbol(classSymbol);
134
+ const name = symbol.getName();
135
+ // Check base classes
136
+ const baseTypes = instanceType.getBaseTypes() || [];
137
+ for (const baseType of baseTypes) {
138
+ if (baseType.getProperty(name)) {
139
+ return true;
140
+ }
141
+ }
142
+ // Check interfaces
143
+ if (classNode.heritageClauses) {
144
+ for (const clause of classNode.heritageClauses) {
145
+ // Only check implemented interfaces as base classes are already checked via getBaseTypes()
146
+ if (clause.token !== ts.SyntaxKind.ImplementsKeyword) {
147
+ continue;
148
+ }
149
+ for (const typeNode of clause.types) {
150
+ const type = checker.getTypeAtLocation(typeNode);
151
+ if (type.getProperty(name)) {
152
+ return true;
153
+ }
154
+ }
155
+ }
156
+ }
157
+ return false;
158
+ }
77
159
  /**
78
160
  * Check if the node is a simple property access from a constructor parameter
79
161
  * like this.settings.property or this.settings['property'] or this.settings.nested.deep.property
@@ -21,6 +21,7 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
21
21
  defaultOptions: [],
22
22
  create(context) {
23
23
  const classInfoMap = new WeakMap();
24
+ const classStatsMap = new WeakMap();
24
25
  function isFunctionLike(node) {
25
26
  return (node?.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
26
27
  node?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
@@ -412,6 +413,24 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
412
413
  return {
413
414
  'ClassDeclaration, ClassExpression'(node) {
414
415
  collectClassInfo(node);
416
+ classStatsMap.set(node, { methods: new Map() });
417
+ },
418
+ 'ClassDeclaration, ClassExpression:exit'(node) {
419
+ const stats = classStatsMap.get(node);
420
+ if (!stats) {
421
+ return;
422
+ }
423
+ for (const [methodName, methodStats] of stats.methods) {
424
+ for (const [argIndex, argProperties] of methodStats.argStats) {
425
+ for (const [, propStats] of argProperties) {
426
+ if (propStats.callsWithProperty === methodStats.totalCalls) {
427
+ for (const violation of propStats.violations) {
428
+ reportAccess(methodName, violation.methodMeta, argIndex, violation.access);
429
+ }
430
+ }
431
+ }
432
+ }
433
+ }
415
434
  },
416
435
  CallExpression(node) {
417
436
  const methodName = getMethodNameFromCallee(node.callee);
@@ -442,6 +461,18 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
442
461
  return;
443
462
  }
444
463
  }
464
+ const stats = classStatsMap.get(classNode);
465
+ if (!stats) {
466
+ return;
467
+ }
468
+ let methodStats = stats.methods.get(methodName);
469
+ if (!methodStats) {
470
+ methodStats = { totalCalls: 0, argStats: new Map() };
471
+ stats.methods.set(methodName, methodStats);
472
+ }
473
+ const currentMethodStats = methodStats;
474
+ currentMethodStats.totalCalls++;
475
+ const seenInThisCall = new Set();
445
476
  node.arguments.forEach((arg, index) => {
446
477
  if (!arg) {
447
478
  return;
@@ -449,7 +480,25 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
449
480
  const targetNode = arg.type === utils_1.AST_NODE_TYPES.SpreadElement ? arg.argument : arg;
450
481
  const accesses = collectThisAccesses(targetNode);
451
482
  for (const access of accesses) {
452
- reportAccess(methodName, methodMeta, index, access);
483
+ const key = `${index}:${access.propertyName}`;
484
+ let argMap = currentMethodStats.argStats.get(index);
485
+ if (!argMap) {
486
+ argMap = new Map();
487
+ currentMethodStats.argStats.set(index, argMap);
488
+ }
489
+ let propStats = argMap.get(access.propertyName);
490
+ if (!propStats) {
491
+ propStats = { callsWithProperty: 0, violations: [] };
492
+ argMap.set(access.propertyName, propStats);
493
+ }
494
+ propStats.violations.push({
495
+ methodMeta: methodMeta,
496
+ access,
497
+ });
498
+ if (!seenInThisCall.has(key)) {
499
+ propStats.callsWithProperty++;
500
+ seenInThisCall.add(key);
501
+ }
453
502
  }
454
503
  });
455
504
  },
@@ -274,7 +274,7 @@ exports.noUnmemoizedMemoWithoutProps = (0, createRule_1.createRule)({
274
274
  if (!node.body) {
275
275
  return;
276
276
  }
277
- const returnsJsx = ASTHelpers_1.ASTHelpers.returnsJSX(node.body);
277
+ const returnsJsx = ASTHelpers_1.ASTHelpers.returnsJSX(node.body, context);
278
278
  if (!returnsJsx) {
279
279
  return;
280
280
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noUnnecessaryDestructuringRename = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
6
7
  const BINDABLE_IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
7
8
  const RESERVED_BINDINGS = new Set([
8
9
  'break',
@@ -113,11 +114,7 @@ exports.noUnnecessaryDestructuringRename = (0, createRule_1.createRule)({
113
114
  const sourceCode = context.getSourceCode();
114
115
  const candidates = [];
115
116
  const getDeclaredVariables = (node) => {
116
- const sourceCodeWithDeclarations = sourceCode;
117
- if (typeof sourceCodeWithDeclarations.getDeclaredVariables === 'function') {
118
- return sourceCodeWithDeclarations.getDeclaredVariables(node);
119
- }
120
- return context.getDeclaredVariables(node);
117
+ return ASTHelpers_1.ASTHelpers.getDeclaredVariables(context, node);
121
118
  };
122
119
  function collectDeclaredVariablesUpTree(node) {
123
120
  const variables = [];
@@ -13,6 +13,9 @@ const defaultOptions = [
13
13
  'updatethreshold',
14
14
  'setthreshold',
15
15
  'checkthreshold',
16
+ 'commit',
17
+ 'flush',
18
+ 'saveall',
16
19
  ],
17
20
  },
18
21
  ];
@@ -95,81 +98,149 @@ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
95
98
  return null;
96
99
  }
97
100
  /**
98
- * Checks if an identifier is used in a node
101
+ * Generic AST visitor for identifier collection
99
102
  */
100
- function isIdentifierUsedInNode(identifier, node) {
101
- let isUsed = false;
102
- function visit(node) {
103
- if (node.type === utils_1.AST_NODE_TYPES.Identifier &&
104
- node.name === identifier) {
105
- isUsed = true;
106
- return;
103
+ function visitIdentifiers(node, callback, options = {}) {
104
+ if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
105
+ const parent = node.parent;
106
+ // Skip non-computed properties in MemberExpressions as they are not value uses
107
+ if (parent && parent.type === utils_1.AST_NODE_TYPES.MemberExpression) {
108
+ if (parent.property === node &&
109
+ !parent.computed &&
110
+ !options.includeMemberProperties) {
111
+ return false;
112
+ }
113
+ }
114
+ // Skip non-shorthand keys in object literals as they are not value uses
115
+ if (parent && parent.type === utils_1.AST_NODE_TYPES.Property) {
116
+ if (parent.key === node &&
117
+ !parent.computed &&
118
+ !parent.shorthand) {
119
+ return false;
120
+ }
107
121
  }
108
- // Recursively visit all child nodes
109
- for (const key in node) {
110
- if (key === 'parent' || key === 'range' || key === 'loc')
111
- continue;
112
- const child = node[key];
113
- if (child && typeof child === 'object') {
114
- if (Array.isArray(child)) {
115
- for (const item of child) {
116
- if (item && typeof item === 'object' && 'type' in item) {
117
- visit(item);
122
+ if (callback(node.name) === true)
123
+ return true;
124
+ }
125
+ /**
126
+ * Recursively traverses child nodes while skipping 'parent' to avoid
127
+ * circular back-references, and 'range'/'loc' which are metadata.
128
+ */
129
+ for (const key in node) {
130
+ if (key === 'parent' || key === 'range' || key === 'loc')
131
+ continue;
132
+ const child = node[key];
133
+ if (child && typeof child === 'object') {
134
+ if (Array.isArray(child)) {
135
+ for (const item of child) {
136
+ if (item && typeof item === 'object' && 'type' in item) {
137
+ if (visitIdentifiers(item, callback, options)) {
138
+ return true;
118
139
  }
119
140
  }
120
141
  }
121
- else if ('type' in child) {
122
- visit(child);
142
+ }
143
+ else if ('type' in child) {
144
+ if (visitIdentifiers(child, callback, options)) {
145
+ return true;
123
146
  }
124
147
  }
125
148
  }
126
149
  }
127
- visit(node);
128
- return isUsed;
150
+ return false;
151
+ }
152
+ /**
153
+ * Checks if an identifier is used in a node
154
+ */
155
+ function isIdentifierUsedInNode(identifier, node) {
156
+ return visitIdentifiers(node, (name) => name === identifier);
129
157
  }
158
+ /**
159
+ * Extracts all identifiers used in a node
160
+ */
161
+ function getAllIdentifiers(node, options) {
162
+ const identifiers = new Set();
163
+ visitIdentifiers(node, (name) => {
164
+ identifiers.add(name);
165
+ }, options);
166
+ return identifiers;
167
+ }
168
+ /**
169
+ * Matches coordinator-like identifiers. Intentionally uses substring
170
+ * matching (no word boundaries) to catch patterns like "batchManager",
171
+ * "transactionCollector", etc. This may produce false positives for
172
+ * unrelated managers, but errs on the side of safety.
173
+ */
174
+ const COORDINATOR_PATTERN = /batch|manager|collector|transaction|tx|unitofwork|accumulator/i;
130
175
  /**
131
176
  * Checks if there are dependencies between await expressions
132
177
  */
133
178
  function hasDependencies(awaitNodes, variableNames, sideEffectPatterns) {
134
- // If we have fewer than 2 nodes, there are no dependencies to check
135
179
  if (awaitNodes.length < 2) {
136
180
  return false;
137
181
  }
138
- // For each await node (except the first), check if it depends on previous variables
139
- for (let i = 1; i < awaitNodes.length; i++) {
182
+ const allIdentifiers = awaitNodes.map((node) => {
183
+ const awaitExpr = getAwaitExpression(node);
184
+ return awaitExpr
185
+ ? getAllIdentifiers(awaitExpr.argument, {
186
+ includeMemberProperties: true,
187
+ })
188
+ : new Set();
189
+ });
190
+ // Check all nodes for side effects and shared coordinators
191
+ for (let i = 0; i < awaitNodes.length; i++) {
140
192
  const currentNode = awaitNodes[i];
141
- // Check if any previous variable is used in the current await expression
142
- for (const varName of variableNames) {
143
- const awaitExpr = getAwaitExpression(currentNode);
144
- if (awaitExpr &&
145
- isIdentifierUsedInNode(varName, awaitExpr.argument)) {
146
- return true;
147
- }
148
- }
149
- // Check for operations that might have side effects that affect subsequent operations
150
- // This is a conservative heuristic - we only flag very specific patterns
151
193
  const awaitExpr = getAwaitExpression(currentNode);
152
- if (awaitExpr &&
153
- awaitExpr.argument.type === utils_1.AST_NODE_TYPES.CallExpression) {
154
- const callee = awaitExpr.argument.callee;
155
- // Check for method calls that might indicate side effects
156
- if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
157
- callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
158
- const methodName = callee.property.name;
159
- if (sideEffectPatterns.some((pattern) => pattern.test(methodName))) {
194
+ if (!awaitExpr)
195
+ continue;
196
+ // 1. Check if current node depends on variables DECLARED in previous awaits
197
+ if (i > 0) {
198
+ for (const varName of variableNames) {
199
+ if (isIdentifierUsedInNode(varName, awaitExpr.argument)) {
160
200
  return true;
161
201
  }
162
202
  }
163
- if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
164
- const functionName = callee.name;
165
- if (sideEffectPatterns.some((pattern) => pattern.test(functionName))) {
166
- return true;
203
+ }
204
+ // 2. Check for shared coordinators between this node and ANY previous node
205
+ if (i > 0) {
206
+ const currentIds = allIdentifiers[i];
207
+ for (const id of currentIds) {
208
+ if (COORDINATOR_PATTERN.test(id)) {
209
+ for (let j = 0; j < i; j++) {
210
+ if (allIdentifiers[j].has(id)) {
211
+ return true;
212
+ }
213
+ }
167
214
  }
168
215
  }
169
216
  }
217
+ // 3. Check for operations that might have side effects
218
+ // If any node has a side effect, we should not parallelize the sequence
219
+ let callExpr = null;
220
+ if (awaitExpr.argument.type === utils_1.AST_NODE_TYPES.CallExpression) {
221
+ callExpr = awaitExpr.argument;
222
+ }
223
+ else if (awaitExpr.argument.type === utils_1.AST_NODE_TYPES.ChainExpression &&
224
+ awaitExpr.argument.expression.type === utils_1.AST_NODE_TYPES.CallExpression) {
225
+ callExpr = awaitExpr.argument.expression;
226
+ }
227
+ if (callExpr) {
228
+ const callee = callExpr.callee;
229
+ let methodName = null;
230
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
231
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
232
+ methodName = callee.property.name;
233
+ }
234
+ else if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
235
+ methodName = callee.name;
236
+ }
237
+ if (methodName &&
238
+ sideEffectPatterns.some((pattern) => pattern.test(methodName))) {
239
+ return true;
240
+ }
241
+ }
170
242
  }
171
243
  // If any node is a variable declaration with destructuring, consider it as having dependencies
172
- // This is because destructuring often creates variables that are used later
173
244
  for (const node of awaitNodes) {
174
245
  if (node.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
175
246
  for (const declaration of node.declarations) {
@@ -234,7 +305,6 @@ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
234
305
  function areInTryCatchBlocks(nodes) {
235
306
  for (const node of nodes) {
236
307
  let current = node;
237
- // Traverse up to find if the node is in a try block
238
308
  while (current && current.parent) {
239
309
  if (current.parent.type === utils_1.AST_NODE_TYPES.TryStatement &&
240
310
  current.parent.block === current) {
@@ -253,7 +323,6 @@ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
253
323
  function areInLoop(nodes) {
254
324
  for (const node of nodes) {
255
325
  let current = node;
256
- // Traverse up to find if the node is in a loop
257
326
  while (current && current.parent) {
258
327
  if (current.parent.type === utils_1.AST_NODE_TYPES.ForStatement ||
259
328
  current.parent.type === utils_1.AST_NODE_TYPES.ForInStatement ||
@@ -276,14 +345,12 @@ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
276
345
  if (awaitNodes.length < 2) {
277
346
  return null;
278
347
  }
279
- // Extract the await expressions
280
348
  const awaitExpressions = awaitNodes
281
349
  .map((node) => getAwaitExpression(node))
282
350
  .filter((node) => node !== null);
283
351
  if (awaitExpressions.length < 2) {
284
352
  return null;
285
353
  }
286
- // Get the text of each await argument
287
354
  const awaitArguments = awaitExpressions.map((expr) => sourceCode.getText(expr.argument));
288
355
  const idsText = [];
289
356
  const declKinds = new Set();
@@ -316,9 +383,7 @@ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
316
383
  // Simple Promise.all without variable assignments
317
384
  promiseAllText = `await Promise.all([\n ${awaitArguments.join(',\n ')}\n]);`;
318
385
  }
319
- // Find the start position, accounting for leading comments
320
386
  const startPos = awaitNodes[0].range[0];
321
- // Replace the range from the start of the first await to the end of the last await
322
387
  const endPos = awaitNodes[awaitNodes.length - 1].range[1];
323
388
  return fixer.replaceTextRange([startPos, endPos], promiseAllText);
324
389
  }
@@ -1,9 +1,73 @@
1
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
+ };
2
25
  Object.defineProperty(exports, "__esModule", { value: true });
3
26
  exports.preferNullishCoalescingBooleanProps = void 0;
4
27
  const utils_1 = require("@typescript-eslint/utils");
28
+ const ts = __importStar(require("typescript"));
5
29
  const createRule_1 = require("../utils/createRule");
6
30
  const BOOLEAN_PROP_REGEX = /^(is|has|should|can|will|do|does|did|was|were|enable|disable)/;
31
+ function isBooleanType(type, checker) {
32
+ if (type.isUnion()) {
33
+ return type.types.every((t) => isBooleanType(t, checker));
34
+ }
35
+ // For type parameters, check if the constraint is a boolean type
36
+ if (type.getFlags() & ts.TypeFlags.TypeParameter) {
37
+ if (checker) {
38
+ const constraint = checker.getBaseConstraintOfType(type);
39
+ if (constraint && constraint !== type) {
40
+ return isBooleanType(constraint, checker);
41
+ }
42
+ }
43
+ return false;
44
+ }
45
+ return ((type.getFlags() & (ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral)) !==
46
+ 0);
47
+ }
48
+ function isPossiblyNullish(type, checker) {
49
+ if (type.isUnion()) {
50
+ return type.types.some((t) => isPossiblyNullish(t, checker));
51
+ }
52
+ // For type parameters, check if the constraint excludes nullish
53
+ if (type.getFlags() & ts.TypeFlags.TypeParameter) {
54
+ if (checker) {
55
+ const constraint = checker.getBaseConstraintOfType(type);
56
+ if (constraint && constraint !== type) {
57
+ return isPossiblyNullish(constraint, checker);
58
+ }
59
+ }
60
+ // If we can't determine the constraint, assume it could be nullish
61
+ return true;
62
+ }
63
+ return ((type.getFlags() &
64
+ (ts.TypeFlags.Null |
65
+ ts.TypeFlags.Undefined |
66
+ ts.TypeFlags.Void |
67
+ ts.TypeFlags.Any |
68
+ ts.TypeFlags.Unknown)) !==
69
+ 0);
70
+ }
7
71
  function isInJSXBooleanAttribute(node) {
8
72
  const parent = node.parent;
9
73
  if (parent?.type !== utils_1.AST_NODE_TYPES.JSXAttribute)
@@ -57,7 +121,34 @@ function isInConditionalContext(node) {
57
121
  /**
58
122
  * Determines if a node is within a boolean context in JSX props or other boolean contexts
59
123
  */
60
- function isInBooleanContext(node) {
124
+ function isInBooleanContext(node, checker, parserServices) {
125
+ if (checker && parserServices) {
126
+ try {
127
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
128
+ const contextualType = checker.getContextualType(tsNode);
129
+ if (contextualType && isBooleanType(contextualType, checker)) {
130
+ return true;
131
+ }
132
+ // Also check if the expression itself is a boolean type
133
+ const actualType = checker.getTypeAtLocation(tsNode);
134
+ if (actualType && isBooleanType(actualType, checker)) {
135
+ return true;
136
+ }
137
+ // If this is a logical expression, also check if the left operand is a boolean
138
+ if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
139
+ const leftTSNode = parserServices.esTreeNodeToTSNodeMap.get(node.left);
140
+ const leftType = checker.getTypeAtLocation(leftTSNode);
141
+ if (leftType && isBooleanType(leftType, checker)) {
142
+ return true;
143
+ }
144
+ }
145
+ }
146
+ catch {
147
+ // esTreeNodeToTSNodeMap may fail for synthetic nodes or nodes without
148
+ // source positions; getContextualType may return undefined for non-expression
149
+ // contexts. Fall back to AST-based heuristics in these cases.
150
+ }
151
+ }
61
152
  let current = node;
62
153
  // Traverse up the AST to find if we're in a boolean context
63
154
  while (current && current.parent) {
@@ -365,7 +456,19 @@ function isInBooleanContext(node) {
365
456
  /**
366
457
  * Checks if the left operand could be nullish (null or undefined)
367
458
  */
368
- function couldBeNullish(node) {
459
+ function couldBeNullish(node, checker, parserServices) {
460
+ if (checker && parserServices) {
461
+ try {
462
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
463
+ const type = checker.getTypeAtLocation(tsNode);
464
+ return isPossiblyNullish(type, checker);
465
+ }
466
+ catch {
467
+ // esTreeNodeToTSNodeMap may fail for synthetic nodes or nodes without
468
+ // source positions; getTypeAtLocation may throw for nodes without
469
+ // type information. Fall back to manual check.
470
+ }
471
+ }
369
472
  // For literals, check the actual value
370
473
  if (node.type === utils_1.AST_NODE_TYPES.Literal) {
371
474
  return node.value === null || node.value === undefined;
@@ -402,16 +505,18 @@ exports.preferNullishCoalescingBooleanProps = (0, createRule_1.createRule)({
402
505
  },
403
506
  defaultOptions: [],
404
507
  create(context) {
508
+ const parserServices = utils_1.ESLintUtils.getParserServices(context, true);
509
+ const checker = parserServices?.program?.getTypeChecker();
405
510
  return {
406
511
  LogicalExpression(node) {
407
512
  if (node.operator === '||') {
408
513
  // If the node is in a boolean context, we allow logical OR
409
- if (isInBooleanContext(node)) {
514
+ if (isInBooleanContext(node, checker, parserServices)) {
410
515
  return;
411
516
  }
412
517
  // Check if this could benefit from nullish coalescing
413
518
  // We only suggest nullish coalescing when the left operand could be nullish
414
- if (couldBeNullish(node.left)) {
519
+ if (couldBeNullish(node.left, checker, parserServices)) {
415
520
  const sourceCode = context.getSourceCode();
416
521
  const leftText = sourceCode.getText(node.left);
417
522
  const rightText = sourceCode.getText(node.right);
@@ -202,7 +202,7 @@ exports.preferParamsOverParentId = (0, createRule_1.createRule)({
202
202
  meta: {
203
203
  type: 'suggestion',
204
204
  docs: {
205
- description: 'Prefer handler params for parent IDs instead of traversing ref.parent.id so Firebase triggers stay aligned with path templates and type-safe.',
205
+ description: 'Prefer event.params over ref.parent.id for type-safe Firebase trigger paths.',
206
206
  recommended: 'error',
207
207
  },
208
208
  fixable: 'code',