@blumintinc/eslint-plugin-blumint 1.9.1 → 1.11.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 (72) hide show
  1. package/README.md +99 -83
  2. package/lib/index.js +16 -1
  3. package/lib/rules/class-methods-read-top-to-bottom.js +10 -0
  4. package/lib/rules/consistent-callback-naming.js +20 -16
  5. package/lib/rules/enforce-assert-throws.js +10 -5
  6. package/lib/rules/enforce-boolean-naming-prefixes.d.ts +7 -0
  7. package/lib/rules/enforce-boolean-naming-prefixes.js +462 -0
  8. package/lib/rules/enforce-callable-types.d.ts +1 -1
  9. package/lib/rules/enforce-callable-types.js +13 -13
  10. package/lib/rules/enforce-callback-memo.js +6 -3
  11. package/lib/rules/enforce-centralized-mock-firestore.js +8 -6
  12. package/lib/rules/enforce-css-media-queries.js +3 -3
  13. package/lib/rules/enforce-dynamic-file-naming.d.ts +3 -0
  14. package/lib/rules/enforce-dynamic-file-naming.js +82 -0
  15. package/lib/rules/enforce-dynamic-imports.js +1 -1
  16. package/lib/rules/enforce-firestore-doc-ref-generic.js +16 -12
  17. package/lib/rules/enforce-firestore-facade.js +7 -3
  18. package/lib/rules/enforce-firestore-path-utils.js +5 -2
  19. package/lib/rules/enforce-id-capitalization.js +59 -0
  20. package/lib/rules/enforce-identifiable-firestore-type.js +17 -10
  21. package/lib/rules/enforce-memoize-async.js +2 -2
  22. package/lib/rules/enforce-positive-naming.js +71 -27
  23. package/lib/rules/enforce-props-argument-name.js +31 -9
  24. package/lib/rules/enforce-realtimedb-path-utils.js +2 -1
  25. package/lib/rules/enforce-singular-type-names.js +4 -1
  26. package/lib/rules/enforce-timestamp-now.js +3 -4
  27. package/lib/rules/enforce-verb-noun-naming.js +18 -8
  28. package/lib/rules/ensure-pointer-events-none.js +20 -11
  29. package/lib/rules/extract-global-constants.js +25 -10
  30. package/lib/rules/fast-deep-equal-over-microdiff.d.ts +3 -0
  31. package/lib/rules/fast-deep-equal-over-microdiff.js +182 -0
  32. package/lib/rules/key-only-outermost-element.js +13 -7
  33. package/lib/rules/no-always-true-false-conditions.js +189 -5
  34. package/lib/rules/no-async-array-filter.js +1 -1
  35. package/lib/rules/no-circular-references.js +121 -59
  36. package/lib/rules/no-complex-cloud-params.js +4 -2
  37. package/lib/rules/no-entire-object-hook-deps.js +113 -29
  38. package/lib/rules/no-explicit-return-type.js +3 -2
  39. package/lib/rules/no-firestore-jest-mock.js +1 -1
  40. package/lib/rules/no-firestore-object-arrays.js +2 -1
  41. package/lib/rules/no-hungarian.js +18 -1
  42. package/lib/rules/no-jsx-in-hooks.js +4 -3
  43. package/lib/rules/no-margin-properties.d.ts +1 -0
  44. package/lib/rules/no-margin-properties.js +314 -0
  45. package/lib/rules/no-mixed-firestore-transactions.js +8 -4
  46. package/lib/rules/no-mock-firebase-admin.js +6 -1
  47. package/lib/rules/no-object-values-on-strings.js +28 -10
  48. package/lib/rules/no-type-assertion-returns.js +47 -6
  49. package/lib/rules/no-unnecessary-destructuring.js +1 -1
  50. package/lib/rules/no-unnecessary-verb-suffix.js +0 -31
  51. package/lib/rules/no-unused-props.js +10 -7
  52. package/lib/rules/no-unused-usestate.js +19 -6
  53. package/lib/rules/omit-index-html.js +1 -1
  54. package/lib/rules/prefer-batch-operations.js +19 -6
  55. package/lib/rules/prefer-clone-deep.js +16 -11
  56. package/lib/rules/prefer-destructuring-no-class.js +19 -0
  57. package/lib/rules/prefer-fragment-component.js +19 -16
  58. package/lib/rules/prefer-global-router-state-key.js +6 -4
  59. package/lib/rules/prefer-settings-object.js +1 -1
  60. package/lib/rules/prefer-usecallback-over-usememo-for-functions.d.ts +8 -0
  61. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +180 -0
  62. package/lib/rules/react-usememo-should-be-component.js +139 -17
  63. package/lib/rules/require-hooks-default-params.js +25 -15
  64. package/lib/rules/require-usememo-object-literals.js +3 -2
  65. package/lib/rules/semantic-function-prefixes.js +15 -3
  66. package/lib/rules/sync-onwrite-name-func.js +5 -3
  67. package/lib/utils/ASTHelpers.js +6 -2
  68. package/lib/utils/graph/ClassGraphBuilder.js +4 -1
  69. package/lib/utils/graph/ClassGraphSorterReadability.js +3 -1
  70. package/package.json +3 -3
  71. package/lib/rules/require-image-overlayed.d.ts +0 -7
  72. package/lib/rules/require-image-overlayed.js +0 -82
@@ -8,14 +8,14 @@ exports.enforceCallableTypes = (0, createRule_1.createRule)({
8
8
  meta: {
9
9
  type: 'problem',
10
10
  docs: {
11
- description: 'Enforce Params and Response type exports in callable functions',
11
+ description: 'Enforce Props and Response type exports in callable functions',
12
12
  recommended: 'error',
13
13
  },
14
14
  schema: [],
15
15
  messages: {
16
- missingParamsType: 'Missing Params type export in callable function file',
16
+ missingPropsType: 'Missing Props type export in callable function file',
17
17
  missingResponseType: 'Missing Response type export in callable function file',
18
- unusedParamsType: 'Params type is exported but not used in the callable function',
18
+ unusedPropsType: 'Props type is exported but not used in the callable function',
19
19
  unusedResponseType: 'Response type is exported but not used in the callable function',
20
20
  },
21
21
  },
@@ -28,18 +28,18 @@ exports.enforceCallableTypes = (0, createRule_1.createRule)({
28
28
  filename.includes('/callable/scripts/')) {
29
29
  return {};
30
30
  }
31
- let hasParamsExport = false;
31
+ let hasPropsExport = false;
32
32
  let hasResponseExport = false;
33
33
  let hasCallableFunction = false;
34
- let paramsTypeUsed = false;
34
+ let propsTypeUsed = false;
35
35
  let responseTypeUsed = false;
36
36
  return {
37
37
  // Check for type exports
38
38
  ExportNamedDeclaration(node) {
39
39
  if (node.declaration?.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
40
40
  const typeName = node.declaration.id.name;
41
- if (typeName === 'Params') {
42
- hasParamsExport = true;
41
+ if (typeName === 'Props') {
42
+ hasPropsExport = true;
43
43
  }
44
44
  else if (typeName === 'Response') {
45
45
  hasResponseExport = true;
@@ -56,8 +56,8 @@ exports.enforceCallableTypes = (0, createRule_1.createRule)({
56
56
  // Check for type usage in function parameters and return types
57
57
  TSTypeReference(node) {
58
58
  if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
59
- if (node.typeName.name === 'Params') {
60
- paramsTypeUsed = true;
59
+ if (node.typeName.name === 'Props') {
60
+ propsTypeUsed = true;
61
61
  }
62
62
  else if (node.typeName.name === 'Response') {
63
63
  responseTypeUsed = true;
@@ -68,16 +68,16 @@ exports.enforceCallableTypes = (0, createRule_1.createRule)({
68
68
  if (!hasCallableFunction) {
69
69
  return;
70
70
  }
71
- if (!hasParamsExport) {
71
+ if (!hasPropsExport) {
72
72
  context.report({
73
73
  loc: { line: 1, column: 0 },
74
- messageId: 'missingParamsType',
74
+ messageId: 'missingPropsType',
75
75
  });
76
76
  }
77
- else if (!paramsTypeUsed) {
77
+ else if (!propsTypeUsed) {
78
78
  context.report({
79
79
  loc: { line: 1, column: 0 },
80
- messageId: 'unusedParamsType',
80
+ messageId: 'unusedPropsType',
81
81
  });
82
82
  }
83
83
  if (!hasResponseExport) {
@@ -43,8 +43,10 @@ exports.default = (0, createRule_1.createRule)({
43
43
  function hasJSXWithFunction(node) {
44
44
  if (node.type === utils_1.TSESTree.AST_NODE_TYPES.JSXElement) {
45
45
  return node.openingElement.attributes.some((attr) => {
46
- if (attr.type === utils_1.TSESTree.AST_NODE_TYPES.JSXAttribute && attr.value) {
47
- if (attr.value.type === utils_1.TSESTree.AST_NODE_TYPES.JSXExpressionContainer) {
46
+ if (attr.type === utils_1.TSESTree.AST_NODE_TYPES.JSXAttribute &&
47
+ attr.value) {
48
+ if (attr.value.type ===
49
+ utils_1.TSESTree.AST_NODE_TYPES.JSXExpressionContainer) {
48
50
  return containsFunction(attr.value.expression);
49
51
  }
50
52
  }
@@ -80,7 +82,8 @@ exports.default = (0, createRule_1.createRule)({
80
82
  expression.type === utils_1.TSESTree.AST_NODE_TYPES.JSXElement) &&
81
83
  (containsFunction(expression) || hasJSXWithFunction(expression))) {
82
84
  // Skip reporting if this is a JSX element and we already reported an inline function
83
- if (expression.type === utils_1.TSESTree.AST_NODE_TYPES.JSXElement && hasJSXWithFunction(expression)) {
85
+ if (expression.type === utils_1.TSESTree.AST_NODE_TYPES.JSXElement &&
86
+ hasJSXWithFunction(expression)) {
84
87
  return;
85
88
  }
86
89
  context.report({
@@ -157,7 +157,7 @@ exports.enforceCentralizedMockFirestore = (0, createRule_1.createRule)({
157
157
  // Find all the lines that need to be removed
158
158
  const linesToRemove = new Set();
159
159
  // Process all nodes that need to be removed
160
- mockFirestoreNodes.forEach(node => {
160
+ mockFirestoreNodes.forEach((node) => {
161
161
  const startLine = sourceCode.getLocFromIndex(node.range[0]).line - 1;
162
162
  const endLine = sourceCode.getLocFromIndex(node.range[1]).line - 1;
163
163
  if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
@@ -186,21 +186,21 @@ exports.enforceCentralizedMockFirestore = (0, createRule_1.createRule)({
186
186
  // Replace custom mockFirestore references with the standard one
187
187
  const replacements = [];
188
188
  // Add replacements for custom mockFirestore names
189
- customMockFirestoreCallExpressions.forEach(node => {
189
+ customMockFirestoreCallExpressions.forEach((node) => {
190
190
  if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
191
191
  replacements.push([
192
192
  node.callee.range[0],
193
193
  node.callee.name,
194
- 'mockFirestore'
194
+ 'mockFirestore',
195
195
  ]);
196
196
  }
197
197
  });
198
198
  // Add replacements for this.mockFirestore
199
- thisExpressions.forEach(expr => {
199
+ thisExpressions.forEach((expr) => {
200
200
  replacements.push([
201
201
  expr.range[0],
202
202
  sourceCode.getText(expr),
203
- 'mockFirestore'
203
+ 'mockFirestore',
204
204
  ]);
205
205
  });
206
206
  // Sort replacements in reverse order to avoid range issues
@@ -214,7 +214,9 @@ exports.enforceCentralizedMockFirestore = (0, createRule_1.createRule)({
214
214
  fixedText.substring(pos + oldText.length);
215
215
  }
216
216
  // Filter out the lines to remove
217
- const fixedLines = fixedText.split('\n').filter((_, i) => !linesToRemove.has(i));
217
+ const fixedLines = fixedText
218
+ .split('\n')
219
+ .filter((_, i) => !linesToRemove.has(i));
218
220
  // Add the import statement at the beginning
219
221
  if (!hasCentralizedImport) {
220
222
  fixedLines.unshift(importLine);
@@ -59,9 +59,8 @@ exports.enforceCssMediaQueries = (0, createRule_1.createRule)({
59
59
  if (node.parent &&
60
60
  node.parent.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
61
61
  node.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
62
- (
63
62
  // Check for useMediaQuery from @mui/material
64
- (node.parent.source.value === '@mui/material' &&
63
+ ((node.parent.source.value === '@mui/material' &&
65
64
  node.imported.name === 'useMediaQuery') ||
66
65
  // Check for useMobile from any source
67
66
  node.imported.name === 'useMobile')) {
@@ -74,7 +73,8 @@ exports.enforceCssMediaQueries = (0, createRule_1.createRule)({
74
73
  // Check for useMediaQuery and useMobile calls
75
74
  CallExpression(node) {
76
75
  if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
77
- (node.callee.name === 'useMediaQuery' || node.callee.name === 'useMobile')) {
76
+ (node.callee.name === 'useMediaQuery' ||
77
+ node.callee.name === 'useMobile')) {
78
78
  context.report({
79
79
  node,
80
80
  messageId: 'enforceCssMediaQueries',
@@ -0,0 +1,3 @@
1
+ export declare const RULE_NAME = "enforce-dynamic-file-naming";
2
+ declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"requireDynamicExtension" | "requireDisableDirective", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
3
+ export default _default;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RULE_NAME = void 0;
7
+ const createRule_1 = require("../utils/createRule");
8
+ const path_1 = __importDefault(require("path"));
9
+ exports.RULE_NAME = 'enforce-dynamic-file-naming';
10
+ exports.default = (0, createRule_1.createRule)({
11
+ name: exports.RULE_NAME,
12
+ meta: {
13
+ type: 'suggestion',
14
+ docs: {
15
+ description: 'Enforce .dynamic.ts(x) file naming when @blumintinc/blumint/enforce-dynamic-imports or @blumintinc/blumint/require-dynamic-firebase-imports rule is disabled',
16
+ recommended: 'error',
17
+ },
18
+ schema: [],
19
+ messages: {
20
+ requireDynamicExtension: 'Files with disabled @blumintinc/blumint/enforce-dynamic-imports or @blumintinc/blumint/require-dynamic-firebase-imports rule must use .dynamic.ts(x) extension',
21
+ requireDisableDirective: 'Files with .dynamic.ts(x) extension must have at least one @blumintinc/blumint/enforce-dynamic-imports or @blumintinc/blumint/require-dynamic-firebase-imports disable directive',
22
+ },
23
+ },
24
+ defaultOptions: [],
25
+ create(context) {
26
+ // Get the file path and name
27
+ const filePath = context.getFilename();
28
+ const fileName = path_1.default.basename(filePath);
29
+ // Check if the file is a TypeScript file (ends with .ts or .tsx)
30
+ // Ignore files with other extensions like .test.ts, .deprecated.ts, etc.
31
+ const isTypeScriptFile = /^[^.]+\.tsx?$/.test(fileName);
32
+ // Check if the file has .dynamic.ts or .dynamic.tsx extension
33
+ const hasDynamicExtension = /\.dynamic\.tsx?$/.test(fileName);
34
+ // Skip if not a TypeScript file or has other extensions
35
+ if (!isTypeScriptFile && !hasDynamicExtension) {
36
+ return {};
37
+ }
38
+ // Track if we found a disable directive for enforce-dynamic-imports
39
+ let foundDisableDirective = false;
40
+ return {
41
+ Program() {
42
+ // Get the source code
43
+ const sourceCode = context.getSourceCode();
44
+ const comments = sourceCode.getAllComments();
45
+ // Check all comments for disable directives
46
+ for (const comment of comments) {
47
+ const commentText = comment.value.trim();
48
+ // Check for inline disable directive for either rule
49
+ if ((commentText.includes('eslint-disable-next-line') ||
50
+ commentText.includes('ednl')) &&
51
+ (commentText.includes('@blumintinc/blumint/enforce-dynamic-imports') ||
52
+ commentText.includes('@blumintinc/blumint/require-dynamic-firebase-imports'))) {
53
+ foundDisableDirective = true;
54
+ break;
55
+ }
56
+ // Check for block disable directive for either rule
57
+ if (commentText.includes('eslint-disable ') &&
58
+ (commentText.includes('@blumintinc/blumint/enforce-dynamic-imports') ||
59
+ commentText.includes('@blumintinc/blumint/require-dynamic-firebase-imports'))) {
60
+ foundDisableDirective = true;
61
+ break;
62
+ }
63
+ }
64
+ // If we found a disable directive but the file doesn't have .dynamic extension
65
+ if (foundDisableDirective && !hasDynamicExtension) {
66
+ context.report({
67
+ loc: { line: 1, column: 0 },
68
+ messageId: 'requireDynamicExtension',
69
+ });
70
+ }
71
+ // If the file has .dynamic extension but no disable directive
72
+ if (hasDynamicExtension && !foundDisableDirective) {
73
+ context.report({
74
+ loc: { line: 1, column: 0 },
75
+ messageId: 'requireDisableDirective',
76
+ });
77
+ }
78
+ },
79
+ };
80
+ },
81
+ });
82
+ //# sourceMappingURL=enforce-dynamic-file-naming.js.map
@@ -63,7 +63,7 @@ exports.default = (0, createRule_1.createRule)({
63
63
  }
64
64
  // Check if all specifiers are type imports
65
65
  if (node.specifiers.length > 0 &&
66
- node.specifiers.every(spec => (spec.type === 'ImportSpecifier' && spec.importKind === 'type'))) {
66
+ node.specifiers.every((spec) => spec.type === 'ImportSpecifier' && spec.importKind === 'type')) {
67
67
  return;
68
68
  }
69
69
  }
@@ -119,19 +119,22 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
119
119
  return true;
120
120
  }
121
121
  // Variable declarations with type annotations
122
- if (current.type === utils_1.AST_NODE_TYPES.VariableDeclarator && current.id.typeAnnotation) {
122
+ if (current.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
123
+ current.id.typeAnnotation) {
123
124
  nodeCache.set(node, true);
124
125
  return true;
125
126
  }
126
127
  // Class property definitions with type annotations
127
- if (current.type === utils_1.AST_NODE_TYPES.PropertyDefinition && current.typeAnnotation) {
128
+ if (current.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
129
+ current.typeAnnotation) {
128
130
  nodeCache.set(node, true);
129
131
  return true;
130
132
  }
131
133
  // Return statements in functions with return type annotations
132
134
  if (current.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
133
135
  const func = current.parent?.parent;
134
- if (func?.type === utils_1.AST_NODE_TYPES.FunctionDeclaration && func.returnType) {
136
+ if (func?.type === utils_1.AST_NODE_TYPES.FunctionDeclaration &&
137
+ func.returnType) {
135
138
  nodeCache.set(node, true);
136
139
  return true;
137
140
  }
@@ -146,7 +149,8 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
146
149
  if (classNode) {
147
150
  const property = classNode.body.body.find((member) => member.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
148
151
  member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
149
- member.key.name === left.property.name);
152
+ member.key.name ===
153
+ left.property.name);
150
154
  if (property?.typeAnnotation) {
151
155
  nodeCache.set(node, true);
152
156
  return true;
@@ -202,7 +206,7 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
202
206
  context.report({
203
207
  node,
204
208
  messageId: 'missingGeneric',
205
- data: { type: typeName }
209
+ data: { type: typeName },
206
210
  });
207
211
  return;
208
212
  }
@@ -212,7 +216,7 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
212
216
  context.report({
213
217
  node,
214
218
  messageId: 'invalidGeneric',
215
- data: { type: typeName }
219
+ data: { type: typeName },
216
220
  });
217
221
  }
218
222
  }
@@ -231,14 +235,14 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
231
235
  context.report({
232
236
  node,
233
237
  messageId: 'missingGeneric',
234
- data: { type: 'DocumentReference' }
238
+ data: { type: 'DocumentReference' },
235
239
  });
236
240
  }
237
241
  else if (hasInvalidType(typeAnnotation.params[0])) {
238
242
  context.report({
239
243
  node,
240
244
  messageId: 'invalidGeneric',
241
- data: { type: 'DocumentReference' }
245
+ data: { type: 'DocumentReference' },
242
246
  });
243
247
  }
244
248
  }
@@ -252,14 +256,14 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
252
256
  context.report({
253
257
  node,
254
258
  messageId: 'missingGeneric',
255
- data: { type: 'CollectionReference' }
259
+ data: { type: 'CollectionReference' },
256
260
  });
257
261
  }
258
262
  else if (hasInvalidType(typeAnnotation.params[0])) {
259
263
  context.report({
260
264
  node,
261
265
  messageId: 'invalidGeneric',
262
- data: { type: 'CollectionReference' }
266
+ data: { type: 'CollectionReference' },
263
267
  });
264
268
  }
265
269
  }
@@ -272,14 +276,14 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
272
276
  context.report({
273
277
  node,
274
278
  messageId: 'missingGeneric',
275
- data: { type: 'CollectionGroup' }
279
+ data: { type: 'CollectionGroup' },
276
280
  });
277
281
  }
278
282
  else if (hasInvalidType(typeAnnotation.params[0])) {
279
283
  context.report({
280
284
  node,
281
285
  messageId: 'invalidGeneric',
282
- data: { type: 'CollectionGroup' }
286
+ data: { type: 'CollectionGroup' },
283
287
  });
284
288
  }
285
289
  }
@@ -19,7 +19,9 @@ const isFirestoreMethodCall = (node) => {
19
19
  if (isIdentifier(object)) {
20
20
  const name = object.name;
21
21
  // Skip if it's a facade instance
22
- if (name.includes('Fetcher') || name.includes('Setter') || name.includes('Tx')) {
22
+ if (name.includes('Fetcher') ||
23
+ name.includes('Setter') ||
24
+ name.includes('Tx')) {
23
25
  return false;
24
26
  }
25
27
  // Check for batch or transaction
@@ -39,7 +41,8 @@ const isFirestoreMethodCall = (node) => {
39
41
  const callee = current.callee;
40
42
  if (isMemberExpression(callee)) {
41
43
  const property = callee.property;
42
- if (isIdentifier(property) && (property.name === 'doc' || property.name === 'collection')) {
44
+ if (isIdentifier(property) &&
45
+ (property.name === 'doc' || property.name === 'collection')) {
43
46
  foundDocOrCollection = true;
44
47
  break;
45
48
  }
@@ -60,7 +63,8 @@ const isFirestoreMethodCall = (node) => {
60
63
  if (!foundDocOrCollection && isIdentifier(object)) {
61
64
  const name = object.name;
62
65
  // If the variable name contains 'doc' or 'ref', it's likely a Firestore reference
63
- if (name.toLowerCase().includes('doc') || name.toLowerCase().includes('ref')) {
66
+ if (name.toLowerCase().includes('doc') ||
67
+ name.toLowerCase().includes('ref')) {
64
68
  return true;
65
69
  }
66
70
  }
@@ -30,7 +30,8 @@ exports.enforceFirestorePathUtils = (0, createRule_1.createRule)({
30
30
  return FIRESTORE_METHODS.has(property.name);
31
31
  }
32
32
  function isStringLiteralOrTemplate(node) {
33
- return (node.type === utils_1.AST_NODE_TYPES.Literal && typeof node.value === 'string' ||
33
+ return ((node.type === utils_1.AST_NODE_TYPES.Literal &&
34
+ typeof node.value === 'string') ||
34
35
  node.type === utils_1.AST_NODE_TYPES.TemplateLiteral);
35
36
  }
36
37
  function isUtilityFunction(node) {
@@ -64,7 +65,9 @@ exports.enforceFirestorePathUtils = (0, createRule_1.createRule)({
64
65
  }
65
66
  // Skip test files
66
67
  const filename = context.getFilename();
67
- if (filename.includes('__tests__') || filename.includes('.test.') || filename.includes('.spec.')) {
68
+ if (filename.includes('__tests__') ||
69
+ filename.includes('.test.') ||
70
+ filename.includes('.spec.')) {
68
71
  return;
69
72
  }
70
73
  context.report({
@@ -61,6 +61,55 @@ exports.enforceIdCapitalization = (0, createRule_1.createRule)({
61
61
  node.parent.id === node) {
62
62
  return true;
63
63
  }
64
+ // Check if the node is in an object property context
65
+ if (node.parent &&
66
+ node.parent.type === utils_1.AST_NODE_TYPES.Property &&
67
+ node.parent.value === node) {
68
+ // Check if this is a property in an object pattern (destructuring)
69
+ let currentNode = node.parent;
70
+ while (currentNode.parent) {
71
+ if (currentNode.parent.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
72
+ return true;
73
+ }
74
+ currentNode = currentNode.parent;
75
+ }
76
+ }
77
+ // Check if the node is in a property assignment context
78
+ if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
79
+ return true;
80
+ }
81
+ // Check if the node is in a property access context
82
+ if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.MemberExpression) {
83
+ return true;
84
+ }
85
+ // Check if the node is a string literal used for property access
86
+ // This handles cases like obj['id'] or OverwolfGame['id']
87
+ if (node.parent &&
88
+ node.parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
89
+ node.parent.computed === true &&
90
+ node.parent.property === node) {
91
+ return true;
92
+ }
93
+ // Check if the node is a string literal in a type definition context
94
+ // This handles cases like Pick<Type, 'id' | 'name'>
95
+ if (node.type === utils_1.AST_NODE_TYPES.Literal &&
96
+ typeof node.value === 'string') {
97
+ let currentNode = node;
98
+ while (currentNode.parent) {
99
+ // Check for TypeScript type contexts
100
+ if (currentNode.parent.type === utils_1.AST_NODE_TYPES.TSTypeReference ||
101
+ currentNode.parent.type ===
102
+ utils_1.AST_NODE_TYPES.TSTypeParameterInstantiation ||
103
+ currentNode.parent.type === utils_1.AST_NODE_TYPES.TSUnionType ||
104
+ currentNode.parent.type === utils_1.AST_NODE_TYPES.TSIntersectionType ||
105
+ currentNode.parent.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration ||
106
+ currentNode.parent.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration ||
107
+ currentNode.parent.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
108
+ return true;
109
+ }
110
+ currentNode = currentNode.parent;
111
+ }
112
+ }
64
113
  return false;
65
114
  }
66
115
  /**
@@ -72,6 +121,16 @@ exports.enforceIdCapitalization = (0, createRule_1.createRule)({
72
121
  // Skip checking if the node is in an excluded context
73
122
  if (isExcludedContext(node))
74
123
  return;
124
+ // Check if this is a variable declaration with a string literal containing 'id'
125
+ if (node.type === utils_1.AST_NODE_TYPES.Literal &&
126
+ node.parent &&
127
+ node.parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
128
+ node.parent.init === node &&
129
+ node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier &&
130
+ node.parent.id.name.toLowerCase().includes('id')) {
131
+ // Skip checking variable assignments where the variable name contains 'id'
132
+ return;
133
+ }
75
134
  // Reset the regex lastIndex to ensure consistent behavior
76
135
  idRegex.lastIndex = 0;
77
136
  // Check if the string contains "id" as a standalone word
@@ -67,7 +67,8 @@ exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
67
67
  const findIdentifiable = (type, checkedTypes = new Set()) => {
68
68
  if (!type)
69
69
  return false;
70
- if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference && type.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
70
+ if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
71
+ type.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
71
72
  const typeName = type.typeName.name;
72
73
  if (typeName === 'Identifiable') {
73
74
  return true;
@@ -76,19 +77,23 @@ exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
76
77
  checkedTypes.add(typeName);
77
78
  // Look for the type in all scopes
78
79
  const scope = context.getScope();
79
- const variable = scope.variables.find(v => v.name === typeName);
80
+ const variable = scope.variables.find((v) => v.name === typeName);
80
81
  if (variable) {
81
- const def = variable.defs.find(d => d.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
82
- if (def && 'typeAnnotation' in def.node && def.node.typeAnnotation) {
82
+ const def = variable.defs.find((d) => d.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
83
+ if (def &&
84
+ 'typeAnnotation' in def.node &&
85
+ def.node.typeAnnotation) {
83
86
  return findIdentifiable(def.node.typeAnnotation, checkedTypes);
84
87
  }
85
88
  }
86
89
  // Try looking in the parent scope
87
90
  if (scope.upper) {
88
- const parentVariable = scope.upper.variables.find(v => v.name === typeName);
91
+ const parentVariable = scope.upper.variables.find((v) => v.name === typeName);
89
92
  if (parentVariable) {
90
- const def = parentVariable.defs.find(d => d.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
91
- if (def && 'typeAnnotation' in def.node && def.node.typeAnnotation) {
93
+ const def = parentVariable.defs.find((d) => d.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
94
+ if (def &&
95
+ 'typeAnnotation' in def.node &&
96
+ def.node.typeAnnotation) {
92
97
  return findIdentifiable(def.node.typeAnnotation, checkedTypes);
93
98
  }
94
99
  }
@@ -97,7 +102,7 @@ exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
97
102
  }
98
103
  else if (type.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
99
104
  // For intersection types, check each part
100
- return type.types.some(part => findIdentifiable(part, checkedTypes));
105
+ return type.types.some((part) => findIdentifiable(part, checkedTypes));
101
106
  }
102
107
  return false;
103
108
  };
@@ -114,7 +119,8 @@ exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
114
119
  return type.members.some((member) => member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
115
120
  member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
116
121
  member.key.name === 'id' &&
117
- member.typeAnnotation?.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSStringKeyword);
122
+ member.typeAnnotation?.typeAnnotation.type ===
123
+ utils_1.AST_NODE_TYPES.TSStringKeyword);
118
124
  }
119
125
  // Check intersection types
120
126
  if (type.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
@@ -135,7 +141,8 @@ exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
135
141
  return true;
136
142
  }
137
143
  // Check if type has id: string field (only for utility types)
138
- if (isUtilityType(type) && checkIdField(type.typeParameters.params[0])) {
144
+ if (isUtilityType(type) &&
145
+ checkIdField(type.typeParameters.params[0])) {
139
146
  return true;
140
147
  }
141
148
  // Check if type is wrapped in a utility type
@@ -24,7 +24,7 @@ exports.enforceMemoizeAsync = (0, createRule_1.createRule)({
24
24
  return {
25
25
  ImportDeclaration(node) {
26
26
  if (node.source.value === 'typescript-memoize') {
27
- const memoizeSpecifier = node.specifiers.find(spec => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
27
+ const memoizeSpecifier = node.specifiers.find((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
28
28
  spec.imported.name === 'Memoize');
29
29
  if (memoizeSpecifier) {
30
30
  hasMemoizeImport = true;
@@ -46,7 +46,7 @@ exports.enforceMemoizeAsync = (0, createRule_1.createRule)({
46
46
  return;
47
47
  }
48
48
  // Check if method already has @Memoize decorator
49
- const hasDecorator = node.decorators?.some(decorator => {
49
+ const hasDecorator = node.decorators?.some((decorator) => {
50
50
  if (decorator.expression.type !== utils_1.AST_NODE_TYPES.CallExpression) {
51
51
  return false;
52
52
  }