@blumintinc/eslint-plugin-blumint 1.12.5 → 1.13.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 (142) hide show
  1. package/README.md +140 -99
  2. package/lib/index.js +111 -20
  3. package/lib/rules/array-methods-this-context.js +20 -2
  4. package/lib/rules/avoid-utils-directory.js +11 -2
  5. package/lib/rules/dynamic-https-errors.d.ts +3 -1
  6. package/lib/rules/dynamic-https-errors.js +48 -10
  7. package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
  8. package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +51 -95
  9. package/lib/rules/enforce-assert-throws.d.ts +3 -1
  10. package/lib/rules/enforce-assert-throws.js +493 -51
  11. package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
  12. package/lib/rules/enforce-boolean-naming-prefixes.js +832 -39
  13. package/lib/rules/enforce-callable-types.js +4 -4
  14. package/lib/rules/enforce-callback-memo.js +143 -18
  15. package/lib/rules/enforce-centralized-mock-firestore.js +4 -1
  16. package/lib/rules/enforce-console-error.d.ts +3 -0
  17. package/lib/rules/enforce-console-error.js +250 -0
  18. package/lib/rules/enforce-css-media-queries.js +24 -46
  19. package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
  20. package/lib/rules/enforce-dynamic-imports.js +1 -1
  21. package/lib/rules/enforce-exported-function-types.js +3 -3
  22. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
  23. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +212 -0
  24. package/lib/rules/enforce-firestore-doc-ref-generic.js +335 -0
  25. package/lib/rules/enforce-firestore-facade.js +347 -157
  26. package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
  27. package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
  28. package/lib/rules/enforce-global-constants.d.ts +4 -1
  29. package/lib/rules/enforce-global-constants.js +240 -12
  30. package/lib/rules/enforce-memoize-async.d.ts +2 -1
  31. package/lib/rules/enforce-memoize-async.js +96 -27
  32. package/lib/rules/enforce-memoize-getters.d.ts +3 -0
  33. package/lib/rules/enforce-memoize-getters.js +123 -0
  34. package/lib/rules/enforce-microdiff.js +2 -1
  35. package/lib/rules/enforce-positive-naming.js +5 -7
  36. package/lib/rules/enforce-props-argument-name.d.ts +2 -7
  37. package/lib/rules/enforce-props-argument-name.js +165 -151
  38. package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
  39. package/lib/rules/enforce-props-naming-consistency.js +145 -0
  40. package/lib/rules/enforce-querykey-ts.d.ts +9 -0
  41. package/lib/rules/enforce-querykey-ts.js +274 -0
  42. package/lib/rules/enforce-singular-type-names.js +9 -5
  43. package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
  44. package/lib/rules/enforce-typescript-markdown-code-blocks.js +96 -0
  45. package/lib/rules/enforce-verb-noun-naming.js +85 -10
  46. package/lib/rules/extract-global-constants.js +57 -45
  47. package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
  48. package/lib/rules/fast-deep-equal-over-microdiff.js +356 -78
  49. package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
  50. package/lib/rules/firestore-transaction-reads-before-writes.js +231 -0
  51. package/lib/rules/generic-starts-with-t.js +8 -2
  52. package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
  53. package/lib/rules/memoize-root-level-hocs.js +242 -0
  54. package/lib/rules/no-array-length-in-deps.d.ts +12 -0
  55. package/lib/rules/no-array-length-in-deps.js +308 -0
  56. package/lib/rules/no-async-array-filter.js +5 -2
  57. package/lib/rules/no-complex-cloud-params.js +15 -2
  58. package/lib/rules/no-compositing-layer-props.js +4 -2
  59. package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
  60. package/lib/rules/no-entire-object-hook-deps.js +317 -63
  61. package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
  62. package/lib/rules/no-excessive-parent-chain.js +273 -0
  63. package/lib/rules/no-firestore-object-arrays.js +345 -30
  64. package/lib/rules/no-hungarian.js +13 -13
  65. package/lib/rules/no-margin-properties.d.ts +7 -1
  66. package/lib/rules/no-margin-properties.js +16 -3
  67. package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
  68. package/lib/rules/no-misleading-boolean-prefixes.js +327 -0
  69. package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
  70. package/lib/rules/no-overridable-method-calls-in-constructor.js +260 -0
  71. package/lib/rules/no-passthrough-getters.d.ts +4 -0
  72. package/lib/rules/no-passthrough-getters.js +170 -0
  73. package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
  74. package/lib/rules/no-redundant-usecallback-wrapper.js +315 -0
  75. package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
  76. package/lib/rules/no-restricted-properties-fix.js +172 -0
  77. package/lib/rules/no-separate-loading-state.d.ts +8 -0
  78. package/lib/rules/no-separate-loading-state.js +142 -0
  79. package/lib/rules/no-stale-state-across-await.d.ts +1 -0
  80. package/lib/rules/no-stale-state-across-await.js +185 -0
  81. package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
  82. package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
  83. package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
  84. package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
  85. package/lib/rules/no-undefined-null-passthrough.js +166 -19
  86. package/lib/rules/no-unnecessary-verb-suffix.js +0 -2
  87. package/lib/rules/no-unsafe-firestore-spread.js +8 -5
  88. package/lib/rules/no-unused-props.d.ts +3 -0
  89. package/lib/rules/no-unused-props.js +213 -78
  90. package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
  91. package/lib/rules/optimize-object-boolean-conditions.js +234 -0
  92. package/lib/rules/parallelize-async-operations.d.ts +8 -0
  93. package/lib/rules/parallelize-async-operations.js +375 -0
  94. package/lib/rules/prefer-batch-operations.js +199 -33
  95. package/lib/rules/prefer-block-comments-for-declarations.js +13 -3
  96. package/lib/rules/prefer-document-flattening.d.ts +4 -0
  97. package/lib/rules/prefer-document-flattening.js +239 -0
  98. package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
  99. package/lib/rules/prefer-field-paths-in-transforms.js +250 -0
  100. package/lib/rules/prefer-fragment-component.js +3 -3
  101. package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
  102. package/lib/rules/prefer-global-router-state-key.js +230 -31
  103. package/lib/rules/prefer-next-dynamic.d.ts +9 -0
  104. package/lib/rules/prefer-next-dynamic.js +366 -0
  105. package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
  106. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +430 -0
  107. package/lib/rules/prefer-nullish-coalescing-override.d.ts +6 -0
  108. package/lib/rules/prefer-nullish-coalescing-override.js +182 -0
  109. package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
  110. package/lib/rules/prefer-params-over-parent-id.js +228 -0
  111. package/lib/rules/prefer-settings-object.js +23 -15
  112. package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
  113. package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
  114. package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
  115. package/lib/rules/prefer-url-tostring-over-tojson.js +166 -0
  116. package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
  117. package/lib/rules/prefer-use-deep-compare-memo.js +428 -0
  118. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +24 -1
  119. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
  120. package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
  121. package/lib/rules/react-usememo-should-be-component.js +144 -78
  122. package/lib/rules/require-dynamic-firebase-imports.js +2 -1
  123. package/lib/rules/require-image-optimized.js +1 -1
  124. package/lib/rules/require-memo.js +11 -1
  125. package/lib/rules/require-usememo-object-literals.js +7 -1
  126. package/lib/rules/semantic-function-prefixes.js +38 -18
  127. package/lib/rules/sync-onwrite-name-func.js +5 -1
  128. package/lib/rules/test-file-location-enforcement.d.ts +1 -0
  129. package/lib/rules/test-file-location-enforcement.js +57 -0
  130. package/lib/rules/use-custom-link.js +6 -1
  131. package/lib/rules/use-custom-router.js +6 -1
  132. package/lib/rules/use-latest-callback.d.ts +3 -0
  133. package/lib/rules/use-latest-callback.js +270 -0
  134. package/lib/utils/ASTHelpers.js +10 -0
  135. package/lib/utils/createRule.js +1 -1
  136. package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
  137. package/lib/utils/ruleTester.d.ts +1 -0
  138. package/lib/utils/ruleTester.js +4 -1
  139. package/package.json +21 -14
  140. package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
  141. package/lib/rules/no-restricted-imports-dynamic.d.ts +0 -25
  142. package/lib/rules/no-restricted-imports-dynamic.js +0 -213
@@ -14,43 +14,111 @@ const PRIMITIVE_TYPES = new Set([
14
14
  'GeoPoint',
15
15
  ]);
16
16
  const isInFirestoreTypesDirectory = (filename) => {
17
- return filename.includes('functions/src/types/firestore');
17
+ if (!filename || filename === '<input>')
18
+ return false;
19
+ const normalized = filename.replace(/\\/g, '/');
20
+ if (normalized.includes('/node_modules/') || normalized.includes('/dist/')) {
21
+ return false;
22
+ }
23
+ // Match typical mono/multi-repo layouts
24
+ return normalized.includes('/types/firestore/');
18
25
  };
19
- const isObjectType = (node) => {
20
- switch (node.type) {
21
- case utils_1.AST_NODE_TYPES.TSTypeLiteral:
22
- return true;
23
- case utils_1.AST_NODE_TYPES.TSTypeReference:
24
- if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
25
- const typeName = node.typeName.name;
26
- return !PRIMITIVE_TYPES.has(typeName);
27
- }
28
- else if (node.typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
29
- // Handle namespace.Type cases
30
- return true;
31
- }
32
- return true;
33
- case utils_1.AST_NODE_TYPES.TSIntersectionType:
34
- case utils_1.AST_NODE_TYPES.TSUnionType:
35
- return node.types.some(isObjectType);
36
- case utils_1.AST_NODE_TYPES.TSMappedType:
37
- case utils_1.AST_NODE_TYPES.TSIndexedAccessType:
38
- return true;
39
- default:
26
+ const getRightmostIdentifierName = (name) => {
27
+ let cur = name;
28
+ // Walk rightwards until we reach an Identifier
29
+ while (cur.type !== utils_1.AST_NODE_TYPES.Identifier) {
30
+ cur = cur.right;
31
+ }
32
+ return cur.name;
33
+ };
34
+ const isArrayGenericReference = (node) => {
35
+ return (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
36
+ (node.typeName.name === 'Array' || node.typeName.name === 'ReadonlyArray'));
37
+ };
38
+ const isParenthesizedType = (node) => {
39
+ if (!node || typeof node !== 'object')
40
+ return false;
41
+ const candidate = node;
42
+ return (candidate.type === 'TSParenthesizedType' &&
43
+ candidate.typeAnnotation !== undefined);
44
+ };
45
+ const unwrapArrayElementType = (node) => {
46
+ let current = node;
47
+ // Fixpoint loop: peel wrappers in any order until none remain
48
+ // Cap unwrap iterations to prevent accidental non-terminating edits if new cases are added.
49
+ // Wrappers are finite; 10 is generous but safe.
50
+ for (let i = 0; i < 10; i++) {
51
+ if (current.type === utils_1.AST_NODE_TYPES.TSTypeOperator &&
52
+ current.operator === 'readonly') {
53
+ current = current
54
+ .typeAnnotation;
55
+ continue;
56
+ }
57
+ if (isParenthesizedType(current)) {
58
+ const paren = current;
59
+ current = paren.typeAnnotation;
60
+ continue;
61
+ }
62
+ if (current.type === utils_1.AST_NODE_TYPES.TSArrayType) {
63
+ current = current.elementType;
64
+ continue;
65
+ }
66
+ if (current.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
67
+ isArrayGenericReference(current) &&
68
+ current.typeParameters &&
69
+ current.typeParameters.params.length > 0) {
70
+ current = current.typeParameters.params[0];
71
+ continue;
72
+ }
73
+ break;
74
+ }
75
+ return current;
76
+ };
77
+ // Determine whether this type node appears in a Firestore model type context
78
+ // i.e., within an interface or type alias declaration, and not within variable/function annotations
79
+ const isInModelTypeContext = (node) => {
80
+ let current = node.parent;
81
+ while (current) {
82
+ // Hard stop for non-type declaration contexts
83
+ if (current.type === utils_1.AST_NODE_TYPES.VariableDeclarator ||
84
+ current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
85
+ current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
86
+ current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
87
+ current.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
88
+ current.type === utils_1.AST_NODE_TYPES.TSMethodSignature ||
89
+ current.type === utils_1.AST_NODE_TYPES.ClassDeclaration ||
90
+ current.type === utils_1.AST_NODE_TYPES.PropertyDefinition ||
91
+ current.type === utils_1.AST_NODE_TYPES.TSParameterProperty) {
40
92
  return false;
93
+ }
94
+ if (current.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration ||
95
+ current.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
96
+ return true;
97
+ }
98
+ current = current.parent;
41
99
  }
100
+ return false;
42
101
  };
43
102
  exports.noFirestoreObjectArrays = (0, createRule_1.createRule)({
44
103
  name: 'no-firestore-object-arrays',
45
104
  meta: {
46
105
  type: 'problem',
47
106
  docs: {
48
- description: 'Disallow arrays of objects in Firestore type definitions to optimize performance and avoid unnecessary fetches',
107
+ description: 'Disallow arrays of object types in Firestore models. Prefer Record maps keyed by id with an index field, or subcollections/arrays of IDs.',
49
108
  recommended: 'warn',
109
+ requiresTypeChecking: false,
50
110
  },
51
111
  schema: [],
52
112
  messages: {
53
- noObjectArrays: 'Arrays of objects are not recommended in Firestore. Use subcollections, arrays of IDs, or structured maps (Record<string, T>) instead.',
113
+ noObjectArrays: `Arrays of objects are problematic in Firestore:
114
+ - Not queryable
115
+ - Destructive updates
116
+ - Concurrency risks
117
+
118
+ Prefer:
119
+ - Record<string, T> keyed by id with an index field for order (use toMap/toArr)
120
+ - Subcollections
121
+ - Arrays of IDs where appropriate`,
54
122
  },
55
123
  },
56
124
  defaultOptions: [],
@@ -58,9 +126,246 @@ exports.noFirestoreObjectArrays = (0, createRule_1.createRule)({
58
126
  if (!isInFirestoreTypesDirectory(context.getFilename())) {
59
127
  return {};
60
128
  }
129
+ const sourceCode = context.getSourceCode();
130
+ // Collect alias/interface/enum information within this file to refine object vs primitive classification
131
+ const aliasNameToType = new Map();
132
+ const interfaceNames = new Set();
133
+ const enumNames = new Set();
134
+ const visitNode = (n) => {
135
+ switch (n.type) {
136
+ case utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration: {
137
+ aliasNameToType.set(n.id.name, n.typeAnnotation);
138
+ break;
139
+ }
140
+ case utils_1.AST_NODE_TYPES.TSInterfaceDeclaration: {
141
+ interfaceNames.add(n.id.name);
142
+ break;
143
+ }
144
+ case utils_1.AST_NODE_TYPES.TSEnumDeclaration: {
145
+ enumNames.add(n.id.name);
146
+ break;
147
+ }
148
+ case utils_1.AST_NODE_TYPES.ExportNamedDeclaration: {
149
+ if (n.declaration)
150
+ visitNode(n.declaration);
151
+ break;
152
+ }
153
+ case utils_1.AST_NODE_TYPES.ExportDefaultDeclaration: {
154
+ if (n.declaration) {
155
+ visitNode(n.declaration);
156
+ }
157
+ break;
158
+ }
159
+ case utils_1.AST_NODE_TYPES.TSExportAssignment: {
160
+ if (n.expression) {
161
+ visitNode(n.expression);
162
+ }
163
+ break;
164
+ }
165
+ case utils_1.AST_NODE_TYPES.TSModuleDeclaration: {
166
+ const body = n.body || null;
167
+ if (body && body.type === utils_1.AST_NODE_TYPES.TSModuleBlock) {
168
+ for (const stmt of body.body) {
169
+ visitNode(stmt);
170
+ }
171
+ }
172
+ else if (body && body.type === utils_1.AST_NODE_TYPES.TSModuleDeclaration) {
173
+ visitNode(body);
174
+ }
175
+ break;
176
+ }
177
+ default: {
178
+ // Only traverse a shallow subset we care about
179
+ break;
180
+ }
181
+ }
182
+ };
183
+ for (const stmt of sourceCode.ast.body) {
184
+ visitNode(stmt);
185
+ }
186
+ const seenAlias = new Set();
187
+ const visitingAliases = new Set();
188
+ const isPrimitiveLikeAlias = (name, recursionDepth) => {
189
+ if (seenAlias.has(name))
190
+ return true;
191
+ if (recursionDepth > 50)
192
+ return false;
193
+ if (PRIMITIVE_TYPES.has(name))
194
+ return true;
195
+ if (enumNames.has(name))
196
+ return true; // enums are non-object primitives for our purposes
197
+ const aliased = aliasNameToType.get(name);
198
+ if (!aliased)
199
+ return false;
200
+ if (visitingAliases.has(name)) {
201
+ return false;
202
+ }
203
+ visitingAliases.add(name);
204
+ seenAlias.add(name);
205
+ // Determine if the alias ultimately resolves to only primitive-like/literal types
206
+ const node = aliased;
207
+ const result = isPrimitiveLikeTypeNode(node, recursionDepth + 1);
208
+ visitingAliases.delete(name);
209
+ if (result) {
210
+ seenAlias.add(name);
211
+ }
212
+ else {
213
+ // Remove optimistic marking when resolution fails
214
+ seenAlias.delete(name);
215
+ }
216
+ return result;
217
+ };
218
+ const isPrimitiveLikeTypeNode = (node, recursionDepth = 0) => {
219
+ if (recursionDepth > 25) {
220
+ return false;
221
+ }
222
+ if (isParenthesizedType(node)) {
223
+ const paren = node;
224
+ return isPrimitiveLikeTypeNode(paren.typeAnnotation, recursionDepth + 1);
225
+ }
226
+ switch (node.type) {
227
+ case utils_1.AST_NODE_TYPES.TSStringKeyword:
228
+ case utils_1.AST_NODE_TYPES.TSNumberKeyword:
229
+ case utils_1.AST_NODE_TYPES.TSBooleanKeyword:
230
+ case utils_1.AST_NODE_TYPES.TSNullKeyword:
231
+ case utils_1.AST_NODE_TYPES.TSUndefinedKeyword:
232
+ case utils_1.AST_NODE_TYPES.TSNeverKeyword:
233
+ return true;
234
+ case utils_1.AST_NODE_TYPES.TSAnyKeyword:
235
+ case utils_1.AST_NODE_TYPES.TSUnknownKeyword:
236
+ return false;
237
+ case utils_1.AST_NODE_TYPES.TSLiteralType:
238
+ return true; // string/number/boolean literals
239
+ case utils_1.AST_NODE_TYPES.TSTypeReference: {
240
+ // Allow known primitive-like references and enums or primitive-like aliases
241
+ const ref = node;
242
+ if (ref.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
243
+ const name = ref.typeName.name;
244
+ if (PRIMITIVE_TYPES.has(name) || enumNames.has(name))
245
+ return true;
246
+ if (seenAlias.has(name))
247
+ return true;
248
+ return isPrimitiveLikeAlias(name, recursionDepth + 1);
249
+ }
250
+ if (ref.typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
251
+ const name = getRightmostIdentifierName(ref.typeName);
252
+ if (PRIMITIVE_TYPES.has(name) || enumNames.has(name))
253
+ return true;
254
+ if (seenAlias.has(name))
255
+ return true;
256
+ return isPrimitiveLikeAlias(name, recursionDepth + 1);
257
+ }
258
+ return false;
259
+ }
260
+ case utils_1.AST_NODE_TYPES.TSUnionType: {
261
+ return node.types.every((t) => isPrimitiveLikeTypeNode(t, recursionDepth + 1));
262
+ }
263
+ case utils_1.AST_NODE_TYPES.TSTypeOperator: {
264
+ // Treat keyof/unique symbol/etc as primitive-like to avoid false positives
265
+ if (node.operator !== 'readonly')
266
+ return true;
267
+ return isPrimitiveLikeTypeNode(node
268
+ .typeAnnotation, recursionDepth + 1);
269
+ }
270
+ case utils_1.AST_NODE_TYPES.TSTemplateLiteralType: {
271
+ // Template literal types behave like strings
272
+ return true;
273
+ }
274
+ default:
275
+ return false;
276
+ }
277
+ };
278
+ const isObjectType = (node) => {
279
+ if (isParenthesizedType(node)) {
280
+ const paren = node;
281
+ return isObjectType(paren.typeAnnotation);
282
+ }
283
+ switch (node.type) {
284
+ case utils_1.AST_NODE_TYPES.TSTypeLiteral:
285
+ return true;
286
+ case utils_1.AST_NODE_TYPES.TSTypeReference: {
287
+ const tn = node.typeName;
288
+ if (tn.type !== utils_1.AST_NODE_TYPES.Identifier &&
289
+ tn.type !== utils_1.AST_NODE_TYPES.TSQualifiedName) {
290
+ // Unsupported reference (e.g., ThisType) — do not assume object to avoid false positives
291
+ return false;
292
+ }
293
+ const refName = tn.type === utils_1.AST_NODE_TYPES.Identifier
294
+ ? tn.name
295
+ : getRightmostIdentifierName(tn);
296
+ if (PRIMITIVE_TYPES.has(refName))
297
+ return false;
298
+ if (interfaceNames.has(refName))
299
+ return true;
300
+ if (enumNames.has(refName))
301
+ return false;
302
+ if (aliasNameToType.has(refName)) {
303
+ return !isPrimitiveLikeAlias(refName, 0);
304
+ }
305
+ // Unknown reference: do not assume object to avoid false positives
306
+ return false;
307
+ }
308
+ case utils_1.AST_NODE_TYPES.TSIntersectionType:
309
+ case utils_1.AST_NODE_TYPES.TSUnionType:
310
+ return node.types.some(isObjectType);
311
+ case utils_1.AST_NODE_TYPES.TSMappedType:
312
+ return true;
313
+ case utils_1.AST_NODE_TYPES.TSIndexedAccessType:
314
+ // Treat indexed access as object-like to align with existing tests
315
+ return true;
316
+ case utils_1.AST_NODE_TYPES.TSTypeOperator:
317
+ if (node.operator === 'readonly') {
318
+ return isObjectType(node
319
+ .typeAnnotation);
320
+ }
321
+ return false;
322
+ case utils_1.AST_NODE_TYPES.TSAnyKeyword:
323
+ case utils_1.AST_NODE_TYPES.TSUnknownKeyword:
324
+ return true;
325
+ default:
326
+ return false;
327
+ }
328
+ };
329
+ const skipReadonlyAndParens = (parent) => {
330
+ let currentParent = parent;
331
+ while (currentParent) {
332
+ if (currentParent.type === utils_1.AST_NODE_TYPES.TSTypeOperator &&
333
+ currentParent.operator === 'readonly') {
334
+ currentParent = currentParent
335
+ .parent;
336
+ continue;
337
+ }
338
+ if (isParenthesizedType(currentParent)) {
339
+ const paren = currentParent;
340
+ currentParent = paren.parent;
341
+ continue;
342
+ }
343
+ break;
344
+ }
345
+ return currentParent;
346
+ };
347
+ const isImmediatelyWrappedByArraySyntax = (node) => {
348
+ const parent = skipReadonlyAndParens(node.parent);
349
+ if (!parent)
350
+ return false;
351
+ if (parent.type === utils_1.AST_NODE_TYPES.TSArrayType)
352
+ return true;
353
+ if (parent.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
354
+ isArrayGenericReference(parent))
355
+ return true;
356
+ return false;
357
+ };
61
358
  return {
62
359
  TSArrayType(node) {
63
- if (isObjectType(node.elementType)) {
360
+ // Only consider arrays within model type/interface declarations
361
+ if (!isInModelTypeContext(node)) {
362
+ return;
363
+ }
364
+ if (isImmediatelyWrappedByArraySyntax(node)) {
365
+ return;
366
+ }
367
+ const base = unwrapArrayElementType(node.elementType);
368
+ if (isObjectType(base)) {
64
369
  context.report({
65
370
  node,
66
371
  messageId: 'noObjectArrays',
@@ -68,12 +373,22 @@ exports.noFirestoreObjectArrays = (0, createRule_1.createRule)({
68
373
  }
69
374
  },
70
375
  TSTypeReference(node) {
71
- // Handle Array<T> and ReadonlyArray<T> syntax
72
- const typeName = node.typeName.name;
73
- if ((typeName === 'Array' || typeName === 'ReadonlyArray') &&
376
+ if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
377
+ (node.typeName.name === 'Array' ||
378
+ node.typeName.name === 'ReadonlyArray') &&
74
379
  node.typeParameters) {
75
- const elementType = node.typeParameters.params[0];
76
- if (isObjectType(elementType)) {
380
+ // Only consider arrays within model type/interface declarations
381
+ if (!isInModelTypeContext(node)) {
382
+ return;
383
+ }
384
+ if (isImmediatelyWrappedByArraySyntax(node)) {
385
+ return;
386
+ }
387
+ const elementType = node.typeParameters.params[0] ?? null;
388
+ if (!elementType)
389
+ return;
390
+ const base = unwrapArrayElementType(elementType);
391
+ if (isObjectType(base)) {
77
392
  context.report({
78
393
  node,
79
394
  messageId: 'noObjectArrays',
@@ -46,11 +46,7 @@ const ALLOWED_SUFFIXES = [
46
46
  'Displayed',
47
47
  ];
48
48
  // Common compound nouns that should not be flagged as Hungarian notation
49
- const ALLOWED_COMPOUND_NOUNS = [
50
- 'PhoneNumber',
51
- 'EmailAddress',
52
- 'PostalCode',
53
- ];
49
+ const ALLOWED_COMPOUND_NOUNS = ['PhoneNumber', 'EmailAddress', 'PostalCode'];
54
50
  // Common built-in JavaScript prototype methods
55
51
  const BUILT_IN_METHODS = new Set([
56
52
  // String methods
@@ -225,11 +221,12 @@ exports.noHungarian = (0, createRule_1.createRule)({
225
221
  if (variableName.includes(compoundNoun)) {
226
222
  // Check if it's a prefix like "strPhoneNumber" (which should be flagged)
227
223
  const prefix = variableName.substring(0, variableName.indexOf(compoundNoun));
228
- if (TYPE_MARKERS.some(marker => prefix.toLowerCase() === marker.toLowerCase())) {
224
+ if (TYPE_MARKERS.some((marker) => prefix.toLowerCase() === marker.toLowerCase())) {
229
225
  // This is a type marker prefix + compound noun, so it should be flagged
230
226
  return true;
231
227
  }
232
- // Otherwise, it's a valid use of the compound noun
228
+ // Otherwise treat the compound noun as an allowed descriptive phrase
229
+ // (e.g., userPhoneNumber is acceptable even though it contains "Number")
233
230
  return false;
234
231
  }
235
232
  }
@@ -281,7 +278,8 @@ exports.noHungarian = (0, createRule_1.createRule)({
281
278
  // Check if it's a suffix with proper boundary (e.g., userString, itemArray)
282
279
  if (normalizedVarName.endsWith(normalizedMarker) &&
283
280
  normalizedVarName.length > normalizedMarker.length &&
284
- /[A-Z0-9]/.test(variableName[variableName.length - normalizedMarker.length - 1])) {
281
+ (/[A-Z0-9]/.test(variableName[variableName.length - normalizedMarker.length - 1]) ||
282
+ /[A-Z]/.test(variableName[variableName.length - normalizedMarker.length]))) {
285
283
  return true;
286
284
  }
287
285
  // Check for word boundaries to avoid matching substrings
@@ -290,17 +288,19 @@ exports.noHungarian = (0, createRule_1.createRule)({
290
288
  if (markerIndex === -1) {
291
289
  return false;
292
290
  }
293
- const markerPrefix = variableName.at(markerIndex);
294
- const preMarkerPrefix = variableName.at(markerIndex - 1);
295
- const suffix = variableName.at(markerIndex + normalizedMarker.length);
291
+ const preMarkerPrefix = markerIndex > 0 ? variableName[markerIndex - 1] : '';
292
+ const suffix = variableName[markerIndex + normalizedMarker.length] ?? '';
296
293
  // Ensure we have proper word boundaries
297
294
  // A word boundary is defined by:
298
295
  // 1. Start of string OR underscore OR capital letter before the marker
299
296
  // 2. End of string OR underscore OR capital letter after the marker
300
- const hasStartBoundary = markerIndex === 0 || preMarkerPrefix === '_' || /[A-Z]/.test(markerPrefix || '');
297
+ const hasStartBoundary = markerIndex === 0 ||
298
+ preMarkerPrefix === '_' ||
299
+ /[A-Z]/.test(preMarkerPrefix) ||
300
+ /[A-Z]/.test(variableName[markerIndex]);
301
301
  const hasEndBoundary = markerIndex + normalizedMarker.length === normalizedVarName.length ||
302
302
  suffix === '_' ||
303
- /[A-Z]/.test(suffix || '');
303
+ /[A-Z]/.test(suffix);
304
304
  return hasStartBoundary && hasEndBoundary;
305
305
  });
306
306
  }
@@ -1 +1,7 @@
1
- export declare const noMarginProperties: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noMarginProperties", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
1
+ type Options = [
2
+ {
3
+ autofix?: boolean;
4
+ }
5
+ ];
6
+ export declare const noMarginProperties: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noMarginProperties", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
7
+ export {};
@@ -40,16 +40,29 @@ exports.noMarginProperties = (0, createRule_1.createRule)({
40
40
  type: 'suggestion',
41
41
  docs: {
42
42
  description: 'Discourage using margin properties (margin, marginLeft, marginRight, marginTop, marginBottom, mx, my, etc.) for spacing in MUI components. Instead, prefer defining spacing with padding, gap, or the spacing prop for more predictable layouts.',
43
- recommended: 'error',
43
+ recommended: 'warn',
44
44
  },
45
- schema: [],
45
+ schema: [
46
+ {
47
+ type: 'object',
48
+ properties: {
49
+ autofix: {
50
+ type: 'boolean',
51
+ default: false,
52
+ },
53
+ },
54
+ additionalProperties: false,
55
+ },
56
+ ],
46
57
  messages: {
47
58
  noMarginProperties: 'Avoid using {{property}} for spacing in MUI components. Use padding, gap, or the spacing prop instead for more predictable layouts. See https://www.youtube.com/watch?v=KVQMoEFUee8 for more details.',
48
59
  },
49
60
  },
50
- defaultOptions: [],
61
+ defaultOptions: [{ autofix: false }],
51
62
  create(context) {
52
63
  const seenNodes = new WeakSet();
64
+ // Note: autofix option is available but not currently implemented
65
+ // Future implementation can use: const { autofix = false } = _options;
53
66
  function checkProperty(propertyName) {
54
67
  const normalizedName = normalizePropertyName(propertyName);
55
68
  return MARGIN_PROPERTIES.has(normalizedName);
@@ -0,0 +1,7 @@
1
+ type Options = [
2
+ {
3
+ prefixes?: string[];
4
+ }
5
+ ];
6
+ export declare const noMisleadingBooleanPrefixes: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"nonBooleanReturn", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
7
+ export {};