@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
|
@@ -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: [],
|