@blumintinc/eslint-plugin-blumint 1.5.5 → 1.7.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.
- package/README.md +62 -39
- package/lib/index.js +37 -1
- package/lib/rules/enforce-assert-throws.d.ts +1 -0
- package/lib/rules/enforce-assert-throws.js +132 -0
- package/lib/rules/enforce-centralized-mock-firestore.d.ts +1 -0
- package/lib/rules/enforce-centralized-mock-firestore.js +55 -0
- package/lib/rules/enforce-exported-function-types.js +3 -2
- package/lib/rules/enforce-firestore-facade.d.ts +3 -0
- package/lib/rules/enforce-firestore-facade.js +126 -0
- package/lib/rules/enforce-firestore-set-merge.js +83 -2
- package/lib/rules/enforce-verb-noun-naming.js +2 -0
- package/lib/rules/no-complex-cloud-params.d.ts +1 -0
- package/lib/rules/no-complex-cloud-params.js +363 -0
- package/lib/rules/no-explicit-return-type.js +11 -3
- package/lib/rules/no-firestore-jest-mock.d.ts +1 -0
- package/lib/rules/no-firestore-jest-mock.js +59 -0
- package/lib/rules/no-mixed-firestore-transactions.d.ts +1 -0
- package/lib/rules/no-mixed-firestore-transactions.js +115 -0
- package/lib/rules/no-mock-firebase-admin.d.ts +1 -0
- package/lib/rules/no-mock-firebase-admin.js +50 -0
- package/lib/rules/prefer-batch-operations.d.ts +3 -0
- package/lib/rules/prefer-batch-operations.js +176 -0
- package/lib/rules/prefer-clone-deep.d.ts +1 -0
- package/lib/rules/prefer-clone-deep.js +72 -0
- package/lib/rules/prefer-settings-object.js +164 -6
- package/lib/rules/semantic-function-prefixes.js +7 -0
- package/lib/rules/sync-onwrite-name-func.d.ts +1 -0
- package/lib/rules/sync-onwrite-name-func.js +79 -0
- package/package.json +2 -2
|
@@ -41,8 +41,80 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
// Only flag update() calls
|
|
45
|
-
|
|
44
|
+
// Only flag update() calls that are Firestore operations
|
|
45
|
+
if (property.name === 'update') {
|
|
46
|
+
const object = node.callee.object;
|
|
47
|
+
// Check for BatchManager update calls
|
|
48
|
+
if (object.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
49
|
+
object.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
50
|
+
object.property.name === 'batchManager') {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (object.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
54
|
+
// Check if it's a createHash().update() call
|
|
55
|
+
if (object.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
56
|
+
object.callee.name === 'createHash') {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Check if it's a Firestore document reference or transaction
|
|
61
|
+
let current = node;
|
|
62
|
+
while (current?.parent) {
|
|
63
|
+
current = current.parent;
|
|
64
|
+
if (current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
65
|
+
const obj = current.object;
|
|
66
|
+
if (obj.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
67
|
+
// Check for common Firestore variable names
|
|
68
|
+
if (obj.name === 'db' ||
|
|
69
|
+
obj.name === 'firestore' ||
|
|
70
|
+
obj.name === 'transaction' ||
|
|
71
|
+
obj.name === 'docRef' ||
|
|
72
|
+
obj.name === 'userRef' ||
|
|
73
|
+
obj.name.endsWith('Ref')) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Check if it's a Firestore document reference method chain
|
|
80
|
+
let currentObj = object;
|
|
81
|
+
while (currentObj.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
82
|
+
if (currentObj.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
83
|
+
const methodName = currentObj.property.name;
|
|
84
|
+
if (methodName === 'collection' || methodName === 'doc') {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
currentObj = currentObj.object;
|
|
89
|
+
}
|
|
90
|
+
// Check if it's a transaction.update() call
|
|
91
|
+
if (object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
92
|
+
object.name === 'transaction') {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
// Check if it's a Firestore document reference by looking at imports
|
|
96
|
+
const program = context
|
|
97
|
+
.getAncestors()
|
|
98
|
+
.find((node) => node.type === utils_1.AST_NODE_TYPES.Program);
|
|
99
|
+
if (program) {
|
|
100
|
+
for (const node of program.body) {
|
|
101
|
+
if (node.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
102
|
+
for (const decl of node.declarations) {
|
|
103
|
+
if (decl.init?.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
104
|
+
decl.init.callee.type ===
|
|
105
|
+
utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
106
|
+
decl.init.callee.property.type ===
|
|
107
|
+
utils_1.AST_NODE_TYPES.Identifier &&
|
|
108
|
+
decl.init.callee.property.name === 'firestore') {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
46
118
|
}
|
|
47
119
|
}
|
|
48
120
|
if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
@@ -75,6 +147,15 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
|
|
|
75
147
|
const data = sourceCode.getText(args[1]);
|
|
76
148
|
return `${object}.set(${docRef}, ${data}, { merge: true })`;
|
|
77
149
|
}
|
|
150
|
+
if (object.includes('batchManager')) {
|
|
151
|
+
const docRef = sourceCode.getText(args[0]);
|
|
152
|
+
const data = sourceCode.getText(args[1]);
|
|
153
|
+
return `${object}.set({
|
|
154
|
+
ref: ${docRef},
|
|
155
|
+
data: ${data},
|
|
156
|
+
merge: true,
|
|
157
|
+
})`;
|
|
158
|
+
}
|
|
78
159
|
const data = sourceCode.getText(args[0]);
|
|
79
160
|
return `${object}.set(${data}, { merge: true })`;
|
|
80
161
|
}
|
|
@@ -69,6 +69,7 @@ const VERBS_SET = new Set([
|
|
|
69
69
|
'auto',
|
|
70
70
|
'avoid',
|
|
71
71
|
'back',
|
|
72
|
+
'backfill',
|
|
72
73
|
'backup',
|
|
73
74
|
'bake',
|
|
74
75
|
'balance',
|
|
@@ -432,6 +433,7 @@ const VERBS_SET = new Set([
|
|
|
432
433
|
'include',
|
|
433
434
|
'includes',
|
|
434
435
|
'increase',
|
|
436
|
+
'increment',
|
|
435
437
|
'index',
|
|
436
438
|
'influence',
|
|
437
439
|
'inform',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noComplexCloudParams: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noComplexObjects", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noComplexCloudParams = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.noComplexCloudParams = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'no-complex-cloud-params',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'problem',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Disallow passing complex objects to cloud functions',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
noComplexObjects: 'Do not pass complex objects to cloud functions. Complex objects include class instances, objects with methods, non-serializable values (RegExp, BigInt, TypedArray, etc.), or objects with nested complex properties.',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultOptions: [],
|
|
20
|
+
create(context) {
|
|
21
|
+
// Track imported cloud functions
|
|
22
|
+
const cloudFunctions = new Set();
|
|
23
|
+
// Track objects to detect circular references
|
|
24
|
+
const objectsInPath = new Set();
|
|
25
|
+
// Track nodes that have already been reported
|
|
26
|
+
const reportedNodes = new Set();
|
|
27
|
+
function isFunction(node) {
|
|
28
|
+
return [
|
|
29
|
+
utils_1.AST_NODE_TYPES.FunctionExpression,
|
|
30
|
+
utils_1.AST_NODE_TYPES.ArrowFunctionExpression,
|
|
31
|
+
utils_1.AST_NODE_TYPES.MethodDefinition,
|
|
32
|
+
].includes(node.type);
|
|
33
|
+
}
|
|
34
|
+
function isMethod(node) {
|
|
35
|
+
if (node.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (node.type === utils_1.AST_NODE_TYPES.Property) {
|
|
39
|
+
// Check for methods, getters, setters
|
|
40
|
+
if (node.method || node.kind === 'get' || node.kind === 'set') {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
// Check if the value is a function
|
|
44
|
+
if (node.value && isFunction(node.value)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
// Check for bound functions
|
|
48
|
+
if (node.value &&
|
|
49
|
+
node.value.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
50
|
+
node.value.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
51
|
+
node.value.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
52
|
+
node.value.callee.property.name === 'bind') {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
// Check for generator methods
|
|
56
|
+
if (node.value &&
|
|
57
|
+
node.value.type === utils_1.AST_NODE_TYPES.FunctionExpression &&
|
|
58
|
+
node.value.generator) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
// Check for async methods
|
|
62
|
+
if (node.value &&
|
|
63
|
+
node.value.type === utils_1.AST_NODE_TYPES.FunctionExpression &&
|
|
64
|
+
node.value.async) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
function isClassInstance(node) {
|
|
71
|
+
if (node.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
72
|
+
// Check for known non-serializable constructors
|
|
73
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
74
|
+
const nonSerializableTypes = new Set([
|
|
75
|
+
'RegExp',
|
|
76
|
+
'BigInt',
|
|
77
|
+
'Int8Array',
|
|
78
|
+
'Uint8Array',
|
|
79
|
+
'Uint8ClampedArray',
|
|
80
|
+
'Int16Array',
|
|
81
|
+
'Uint16Array',
|
|
82
|
+
'Int32Array',
|
|
83
|
+
'Uint32Array',
|
|
84
|
+
'Float32Array',
|
|
85
|
+
'Float64Array',
|
|
86
|
+
'BigInt64Array',
|
|
87
|
+
'BigUint64Array',
|
|
88
|
+
'WeakMap',
|
|
89
|
+
'WeakSet',
|
|
90
|
+
'Promise',
|
|
91
|
+
'Error',
|
|
92
|
+
'Proxy',
|
|
93
|
+
'Map',
|
|
94
|
+
'Set',
|
|
95
|
+
'ArrayBuffer',
|
|
96
|
+
'SharedArrayBuffer',
|
|
97
|
+
'DataView',
|
|
98
|
+
]);
|
|
99
|
+
if (nonSerializableTypes.has(node.callee.name)) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
// Allow Date objects as they are serializable
|
|
103
|
+
if (node.callee.name === 'Date') {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
110
|
+
// Try to find the variable declaration
|
|
111
|
+
const scope = context.getScope();
|
|
112
|
+
const variable = scope.variables.find((v) => v.name === node.name);
|
|
113
|
+
if (variable && variable.defs.length > 0) {
|
|
114
|
+
const def = variable.defs[0];
|
|
115
|
+
if (def.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
116
|
+
def.node.init) {
|
|
117
|
+
return isClassInstance(def.node.init);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Check if the identifier starts with a capital letter (potential class instance)
|
|
121
|
+
return node.name[0] === node.name[0].toUpperCase();
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
function isNonSerializableLiteral(node) {
|
|
126
|
+
if (node.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
127
|
+
// Check for RegExp literal
|
|
128
|
+
if ('regex' in node && node.regex) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
// Check for BigInt literal
|
|
132
|
+
if ('bigint' in node && node.bigint) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
// Check for RegExp constructor or literal
|
|
138
|
+
if ((node.type === utils_1.AST_NODE_TYPES.NewExpression ||
|
|
139
|
+
node.type === utils_1.AST_NODE_TYPES.CallExpression) &&
|
|
140
|
+
node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
141
|
+
node.callee.name === 'RegExp') {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
// Check for BigInt constructor or function
|
|
145
|
+
if ((node.type === utils_1.AST_NODE_TYPES.CallExpression ||
|
|
146
|
+
node.type === utils_1.AST_NODE_TYPES.NewExpression) &&
|
|
147
|
+
node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
148
|
+
node.callee.name === 'BigInt') {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
function isComplexValue(node) {
|
|
154
|
+
// Prevent infinite recursion with circular references
|
|
155
|
+
if (objectsInPath.has(node)) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
objectsInPath.add(node);
|
|
159
|
+
try {
|
|
160
|
+
// Check for function expressions
|
|
161
|
+
if (isFunction(node)) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
// Check for class instances
|
|
165
|
+
if (isClassInstance(node)) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
// Check for non-serializable literals
|
|
169
|
+
if (isNonSerializableLiteral(node)) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
// Check for method calls that could create complex objects
|
|
173
|
+
if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
174
|
+
// Allow JSON.stringify
|
|
175
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
176
|
+
node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
177
|
+
node.callee.object.name === 'JSON' &&
|
|
178
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
179
|
+
node.callee.property.name === 'stringify') {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
// Allow Object.create(null)
|
|
183
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
184
|
+
node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
185
|
+
node.callee.object.name === 'Object' &&
|
|
186
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
187
|
+
node.callee.property.name === 'create') {
|
|
188
|
+
// Only allow Object.create(null), check if the prototype object is complex
|
|
189
|
+
if (node.arguments.length === 1) {
|
|
190
|
+
if (node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal &&
|
|
191
|
+
node.arguments[0].value === null) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
return isComplexValue(node.arguments[0]);
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
// Check for function binding
|
|
199
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
200
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
201
|
+
node.callee.property.name === 'bind') {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
return isComplexValue(node.callee);
|
|
205
|
+
}
|
|
206
|
+
// Check for arrays
|
|
207
|
+
if (node.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
|
|
208
|
+
return node.elements.some((element) => element !== null && isComplexValue(element));
|
|
209
|
+
}
|
|
210
|
+
// Check for objects
|
|
211
|
+
if (node.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
212
|
+
return node.properties.some((prop) => {
|
|
213
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
214
|
+
// Check for computed properties (including Symbols)
|
|
215
|
+
if (prop.computed) {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
// Check for methods, getters, and setters
|
|
219
|
+
if (isMethod(prop)) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
// Check property value
|
|
223
|
+
return isComplexValue(prop.value);
|
|
224
|
+
}
|
|
225
|
+
// SpreadElement or other non-Property types are considered complex
|
|
226
|
+
return true;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
// Check for member expressions that might be complex
|
|
230
|
+
if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
231
|
+
// Check for prototype chain access
|
|
232
|
+
if (node.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
233
|
+
(node.property.name === 'prototype' ||
|
|
234
|
+
node.property.name === '__proto__')) {
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
// Check for Symbol properties
|
|
238
|
+
if (node.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
239
|
+
node.object.name === 'Symbol') {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
// Check for WeakMap, WeakSet, Promise, Error constructors
|
|
243
|
+
if (node.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
244
|
+
['WeakMap', 'WeakSet', 'Promise', 'Error'].includes(node.object.name)) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
// Check for circular references in member expressions
|
|
248
|
+
return isComplexValue(node.object) || isComplexValue(node.property);
|
|
249
|
+
}
|
|
250
|
+
// Check for Symbols
|
|
251
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier && node.name === 'Symbol') {
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
// Check for object references that might be circular
|
|
255
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
256
|
+
const scope = context.getScope();
|
|
257
|
+
const variable = scope.variables.find((v) => v.name === node.name);
|
|
258
|
+
if (variable && variable.references.length > 0) {
|
|
259
|
+
// Check if this identifier is used in a way that creates a circular reference
|
|
260
|
+
const isCircular = variable.references.some((ref) => {
|
|
261
|
+
const refParent = ref.identifier.parent;
|
|
262
|
+
if (refParent) {
|
|
263
|
+
// Check for direct assignment
|
|
264
|
+
if (refParent.type === utils_1.AST_NODE_TYPES.AssignmentExpression) {
|
|
265
|
+
if (refParent.right === ref.identifier) {
|
|
266
|
+
// The identifier is being assigned to a property of itself
|
|
267
|
+
let current = refParent.parent;
|
|
268
|
+
while (current) {
|
|
269
|
+
if (current === node) {
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
current = current.parent;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Check for property assignment
|
|
277
|
+
if (refParent.type === utils_1.AST_NODE_TYPES.Property) {
|
|
278
|
+
let current = refParent.parent;
|
|
279
|
+
while (current) {
|
|
280
|
+
if (current === node) {
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
current = current.parent;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return false;
|
|
288
|
+
});
|
|
289
|
+
if (isCircular) {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
// Check the value the identifier refers to
|
|
293
|
+
if (variable.defs.length > 0) {
|
|
294
|
+
const def = variable.defs[0];
|
|
295
|
+
if (def.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
296
|
+
def.node.init) {
|
|
297
|
+
return isComplexValue(def.node.init);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
finally {
|
|
305
|
+
objectsInPath.delete(node);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function hasComplexProperties(node) {
|
|
309
|
+
return isComplexValue(node);
|
|
310
|
+
}
|
|
311
|
+
function checkCloudFunctionCall(node) {
|
|
312
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
313
|
+
cloudFunctions.has(node.callee.name)) {
|
|
314
|
+
// Check each argument for complex objects
|
|
315
|
+
node.arguments.forEach((arg) => {
|
|
316
|
+
if (hasComplexProperties(arg) && !reportedNodes.has(node)) {
|
|
317
|
+
reportedNodes.add(node);
|
|
318
|
+
context.report({
|
|
319
|
+
node,
|
|
320
|
+
messageId: 'noComplexObjects',
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
// Track cloud function imports
|
|
328
|
+
ImportExpression(node) {
|
|
329
|
+
if (node.source.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
330
|
+
typeof node.source.value === 'string' &&
|
|
331
|
+
node.source.value.includes('firebaseCloud')) {
|
|
332
|
+
// Find the variable declarator that contains this import
|
|
333
|
+
let parent = node.parent;
|
|
334
|
+
while (parent && parent.type !== utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
335
|
+
parent = parent.parent;
|
|
336
|
+
}
|
|
337
|
+
if (parent && parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
338
|
+
// Handle destructuring pattern
|
|
339
|
+
if (parent.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
340
|
+
parent.id.properties.forEach((prop) => {
|
|
341
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
342
|
+
prop.value.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
343
|
+
cloudFunctions.add(prop.value.name);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
// Check for complex objects in cloud function calls
|
|
351
|
+
CallExpression(node) {
|
|
352
|
+
checkCloudFunctionCall(node);
|
|
353
|
+
},
|
|
354
|
+
// Check for await expressions with cloud function calls
|
|
355
|
+
AwaitExpression(node) {
|
|
356
|
+
if (node.argument.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
357
|
+
checkCloudFunctionCall(node.argument);
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
//# sourceMappingURL=no-complex-cloud-params.js.map
|
|
@@ -94,9 +94,17 @@ function isTypeGuardFunction(node) {
|
|
|
94
94
|
if (returnType.type !== utils_1.AST_NODE_TYPES.TSTypeAnnotation)
|
|
95
95
|
return false;
|
|
96
96
|
const typeAnnotation = returnType.typeAnnotation;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
// Check for type predicates (is keyword)
|
|
98
|
+
if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypePredicate)
|
|
99
|
+
return true;
|
|
100
|
+
// Check for assertion functions (asserts keyword)
|
|
101
|
+
if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
102
|
+
const typeName = typeAnnotation.typeName;
|
|
103
|
+
if (typeName.type === utils_1.AST_NODE_TYPES.Identifier && typeName.name === 'asserts') {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
100
108
|
}
|
|
101
109
|
exports.noExplicitReturnType = (0, createRule_1.createRule)({
|
|
102
110
|
name: 'no-explicit-return-type',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noFirestoreJestMock: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noFirestoreJestMock", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noFirestoreJestMock = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.noFirestoreJestMock = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'no-firestore-jest-mock',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'problem',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Prevent importing firestore-jest-mock in test files',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
noFirestoreJestMock: 'Do not import from firestore-jest-mock. Use mockFirestore from the centralized mock utility instead.',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
const filename = context.getFilename();
|
|
23
|
+
// Only apply rule to test files
|
|
24
|
+
if (!filename.endsWith('.test.ts')) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
ImportDeclaration(node) {
|
|
29
|
+
if (node.source.value === 'firestore-jest-mock') {
|
|
30
|
+
context.report({
|
|
31
|
+
node,
|
|
32
|
+
messageId: 'noFirestoreJestMock',
|
|
33
|
+
fix(fixer) {
|
|
34
|
+
// Replace with mockFirestore import
|
|
35
|
+
return fixer.replaceText(node, "import { mockFirestore } from '../../../../../__mocks__/functions/src/config/mockFirestore';");
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
CallExpression(node) {
|
|
41
|
+
// Check for jest.mock('firestore-jest-mock')
|
|
42
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
43
|
+
node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
44
|
+
node.callee.object.name === 'jest' &&
|
|
45
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
46
|
+
node.callee.property.name === 'mock' &&
|
|
47
|
+
node.arguments.length > 0 &&
|
|
48
|
+
node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal &&
|
|
49
|
+
node.arguments[0].value === 'firestore-jest-mock') {
|
|
50
|
+
context.report({
|
|
51
|
+
node,
|
|
52
|
+
messageId: 'noFirestoreJestMock',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
//# sourceMappingURL=no-firestore-jest-mock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noMixedFirestoreTransactions: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noMixedTransactions", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|