@blumintinc/eslint-plugin-blumint 1.3.1 → 1.3.2

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.
package/lib/index.js CHANGED
@@ -43,7 +43,7 @@ const enforce_mock_firestore_1 = require("./rules/enforce-mock-firestore");
43
43
  module.exports = {
44
44
  meta: {
45
45
  name: '@blumintinc/eslint-plugin-blumint',
46
- version: '1.3.1',
46
+ version: '1.3.2',
47
47
  },
48
48
  parseOptions: {
49
49
  ecmaVersion: 2020,
@@ -31,6 +31,7 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
31
31
  defaultOptions: [],
32
32
  create(context) {
33
33
  const typeCache = new Map();
34
+ const nodeCache = new WeakMap();
34
35
  function hasInvalidType(node) {
35
36
  if (!node)
36
37
  return false;
@@ -106,6 +107,84 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
106
107
  return false;
107
108
  }
108
109
  }
110
+ function hasTypeAnnotation(node) {
111
+ if (nodeCache.has(node)) {
112
+ return nodeCache.get(node);
113
+ }
114
+ let current = node;
115
+ while (current) {
116
+ // Variable declarations with type annotations
117
+ if (current.type === utils_1.AST_NODE_TYPES.VariableDeclarator && current.id.typeAnnotation) {
118
+ nodeCache.set(node, true);
119
+ return true;
120
+ }
121
+ // Class property definitions with type annotations
122
+ if (current.type === utils_1.AST_NODE_TYPES.PropertyDefinition && current.typeAnnotation) {
123
+ nodeCache.set(node, true);
124
+ return true;
125
+ }
126
+ // Return statements in functions with return type annotations
127
+ if (current.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
128
+ const func = current.parent?.parent;
129
+ if (func?.type === utils_1.AST_NODE_TYPES.FunctionDeclaration && func.returnType) {
130
+ nodeCache.set(node, true);
131
+ return true;
132
+ }
133
+ }
134
+ // Assignment expressions to class properties
135
+ if (current.type === utils_1.AST_NODE_TYPES.AssignmentExpression) {
136
+ const left = current.left;
137
+ if (left.type === utils_1.AST_NODE_TYPES.MemberExpression) {
138
+ const obj = left.object;
139
+ if (obj.type === utils_1.AST_NODE_TYPES.ThisExpression) {
140
+ const classNode = findParentClass(current);
141
+ if (classNode) {
142
+ const property = classNode.body.body.find((member) => member.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
143
+ member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
144
+ member.key.name === left.property.name);
145
+ if (property?.typeAnnotation) {
146
+ nodeCache.set(node, true);
147
+ return true;
148
+ }
149
+ }
150
+ }
151
+ }
152
+ }
153
+ current = current.parent;
154
+ }
155
+ nodeCache.set(node, false);
156
+ return false;
157
+ }
158
+ function findParentClass(node) {
159
+ let current = node;
160
+ while (current) {
161
+ if (current.type === utils_1.AST_NODE_TYPES.ClassDeclaration) {
162
+ return current;
163
+ }
164
+ current = current.parent;
165
+ }
166
+ return undefined;
167
+ }
168
+ function isPartOfMethodChain(node) {
169
+ if (node.callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
170
+ return false;
171
+ }
172
+ // Check if this node is part of a method chain as the object
173
+ const obj = node.callee.object;
174
+ if (obj.type === utils_1.AST_NODE_TYPES.CallExpression) {
175
+ return true;
176
+ }
177
+ // Check if this node is part of a method chain as the callee
178
+ let current = node;
179
+ while (current) {
180
+ if (current.parent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
181
+ current.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
182
+ return true;
183
+ }
184
+ current = current.parent;
185
+ }
186
+ return false;
187
+ }
109
188
  return {
110
189
  TSTypeReference(node) {
111
190
  if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
@@ -133,6 +212,73 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
133
212
  }
134
213
  }
135
214
  },
215
+ CallExpression(node) {
216
+ // Only check method calls if there's no type annotation
217
+ if (hasTypeAnnotation(node)) {
218
+ return;
219
+ }
220
+ // Check for .doc() calls
221
+ if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
222
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
223
+ node.callee.property.name === 'doc') {
224
+ const typeAnnotation = node.typeParameters;
225
+ if (!typeAnnotation) {
226
+ context.report({
227
+ node,
228
+ messageId: 'missingGeneric',
229
+ data: { type: 'DocumentReference' }
230
+ });
231
+ }
232
+ else if (hasInvalidType(typeAnnotation.params[0])) {
233
+ context.report({
234
+ node,
235
+ messageId: 'invalidGeneric',
236
+ data: { type: 'DocumentReference' }
237
+ });
238
+ }
239
+ }
240
+ // Check for .collection() calls
241
+ else if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
242
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
243
+ node.callee.property.name === 'collection' &&
244
+ !isPartOfMethodChain(node)) {
245
+ const typeAnnotation = node.typeParameters;
246
+ if (!typeAnnotation) {
247
+ context.report({
248
+ node,
249
+ messageId: 'missingGeneric',
250
+ data: { type: 'CollectionReference' }
251
+ });
252
+ }
253
+ else if (hasInvalidType(typeAnnotation.params[0])) {
254
+ context.report({
255
+ node,
256
+ messageId: 'invalidGeneric',
257
+ data: { type: 'CollectionReference' }
258
+ });
259
+ }
260
+ }
261
+ // Check for .collectionGroup() calls
262
+ else if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
263
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
264
+ node.callee.property.name === 'collectionGroup') {
265
+ const typeAnnotation = node.typeParameters;
266
+ if (!typeAnnotation) {
267
+ context.report({
268
+ node,
269
+ messageId: 'missingGeneric',
270
+ data: { type: 'CollectionGroup' }
271
+ });
272
+ }
273
+ else if (hasInvalidType(typeAnnotation.params[0])) {
274
+ context.report({
275
+ node,
276
+ messageId: 'invalidGeneric',
277
+ data: { type: 'CollectionGroup' }
278
+ });
279
+ }
280
+ }
281
+ },
136
282
  };
137
283
  },
138
284
  });
@@ -17,6 +17,8 @@ function isArrayOrPrimitive(checker, esTreeNode, nodeMap) {
17
17
  // Check if it's a primitive type
18
18
  if (type.flags &
19
19
  (typescript_1.TypeFlags.String |
20
+ typescript_1.TypeFlags.StringLike |
21
+ typescript_1.TypeFlags.StringLiteral |
20
22
  typescript_1.TypeFlags.Number |
21
23
  typescript_1.TypeFlags.Boolean |
22
24
  typescript_1.TypeFlags.Null |
@@ -36,6 +38,10 @@ function isArrayOrPrimitive(checker, esTreeNode, nodeMap) {
36
38
  (typeNode && ((0, typescript_1.isArrayTypeNode)(typeNode) || (0, typescript_1.isTupleTypeNode)(typeNode)))) {
37
39
  return true;
38
40
  }
41
+ // Check if it's a string type with methods (like String object)
42
+ if (type.symbol?.name === 'String' || type.symbol?.escapedName === 'String') {
43
+ return true;
44
+ }
39
45
  // If it's not a primitive or array, and has properties, it's an object
40
46
  return false;
41
47
  }
@@ -130,8 +136,9 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
130
136
  meta: {
131
137
  type: 'suggestion',
132
138
  docs: {
133
- description: 'Avoid using entire objects in React hook dependency arrays when only specific fields are used',
139
+ description: 'Avoid using entire objects in React hook dependency arrays when only specific fields are used. Requires TypeScript and `parserOptions.project` to be configured.',
134
140
  recommended: 'error',
141
+ requiresTypeChecking: true,
135
142
  },
136
143
  fixable: 'code',
137
144
  schema: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",