@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
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noMixedFirestoreTransactions = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const NON_TRANSACTIONAL_CLASSES = new Set([
|
|
7
|
+
'DocSetter',
|
|
8
|
+
'FirestoreDocFetcher',
|
|
9
|
+
'FirestoreFetcher',
|
|
10
|
+
]);
|
|
11
|
+
exports.noMixedFirestoreTransactions = (0, createRule_1.createRule)({
|
|
12
|
+
name: 'no-mixed-firestore-transactions',
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'problem',
|
|
15
|
+
docs: {
|
|
16
|
+
description: 'Prevent mixing transactional and non-transactional Firestore operations within a transaction',
|
|
17
|
+
recommended: 'error',
|
|
18
|
+
},
|
|
19
|
+
schema: [],
|
|
20
|
+
messages: {
|
|
21
|
+
noMixedTransactions: 'Do not use non-transactional Firestore operations ({{ className }}) inside a transaction. Use {{ transactionalClass }} instead.',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultOptions: [],
|
|
25
|
+
create(context) {
|
|
26
|
+
const transactionScopes = new Set();
|
|
27
|
+
function getTransactionalClassName(className) {
|
|
28
|
+
if (className === 'DocSetter')
|
|
29
|
+
return 'DocSetterTransaction';
|
|
30
|
+
if (className === 'FirestoreDocFetcher')
|
|
31
|
+
return 'FirestoreDocFetcherTransaction';
|
|
32
|
+
if (className === 'FirestoreFetcher')
|
|
33
|
+
return 'FirestoreFetcherTransaction';
|
|
34
|
+
return className;
|
|
35
|
+
}
|
|
36
|
+
function isFirestoreTransaction(node) {
|
|
37
|
+
const callee = node.callee;
|
|
38
|
+
if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression)
|
|
39
|
+
return false;
|
|
40
|
+
const property = callee.property;
|
|
41
|
+
return property.type === utils_1.AST_NODE_TYPES.Identifier && property.name === 'runTransaction';
|
|
42
|
+
}
|
|
43
|
+
function isNonTransactionalClass(node) {
|
|
44
|
+
const callee = node.callee;
|
|
45
|
+
if (callee.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
46
|
+
return false;
|
|
47
|
+
return NON_TRANSACTIONAL_CLASSES.has(callee.name);
|
|
48
|
+
}
|
|
49
|
+
function isTransactionParameter(param) {
|
|
50
|
+
if (param.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
51
|
+
return false;
|
|
52
|
+
const typeAnnotation = param.typeAnnotation;
|
|
53
|
+
if (!typeAnnotation || typeAnnotation.type !== utils_1.AST_NODE_TYPES.TSTypeAnnotation)
|
|
54
|
+
return false;
|
|
55
|
+
const type = typeAnnotation.typeAnnotation;
|
|
56
|
+
if (type.type !== utils_1.AST_NODE_TYPES.TSTypeReference)
|
|
57
|
+
return false;
|
|
58
|
+
const typeName = type.typeName;
|
|
59
|
+
if (typeName.type !== utils_1.AST_NODE_TYPES.TSQualifiedName)
|
|
60
|
+
return false;
|
|
61
|
+
return typeName.left.type === utils_1.AST_NODE_TYPES.Identifier && typeName.left.name === 'FirebaseFirestore' &&
|
|
62
|
+
typeName.right.type === utils_1.AST_NODE_TYPES.Identifier && typeName.right.name === 'Transaction';
|
|
63
|
+
}
|
|
64
|
+
function isInTransactionScope(node) {
|
|
65
|
+
let current = node;
|
|
66
|
+
while (current) {
|
|
67
|
+
if (transactionScopes.has(current)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
if (current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
71
|
+
current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
72
|
+
current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
73
|
+
const params = current.params;
|
|
74
|
+
if (params.some(isTransactionParameter)) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
current = current.parent;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
'CallExpression[callee.property.name="runTransaction"]'(node) {
|
|
84
|
+
if (!isFirestoreTransaction(node))
|
|
85
|
+
return;
|
|
86
|
+
const callback = node.arguments[0];
|
|
87
|
+
if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
88
|
+
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
89
|
+
transactionScopes.add(callback.body);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
'CallExpression[callee.property.name="runTransaction"]:exit'(node) {
|
|
93
|
+
const callback = node.arguments[0];
|
|
94
|
+
if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
95
|
+
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
96
|
+
transactionScopes.delete(callback.body);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
NewExpression(node) {
|
|
100
|
+
if (!isInTransactionScope(node) || !isNonTransactionalClass(node))
|
|
101
|
+
return;
|
|
102
|
+
const className = node.callee.name;
|
|
103
|
+
context.report({
|
|
104
|
+
node,
|
|
105
|
+
messageId: 'noMixedTransactions',
|
|
106
|
+
data: {
|
|
107
|
+
className,
|
|
108
|
+
transactionalClass: getTransactionalClassName(className),
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
//# sourceMappingURL=no-mixed-firestore-transactions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noMockFirebaseAdmin: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noMockFirebaseAdmin", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noMockFirebaseAdmin = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const FIREBASE_ADMIN_PATHS = [
|
|
7
|
+
'../../config/firebaseAdmin',
|
|
8
|
+
'../config/firebaseAdmin',
|
|
9
|
+
'./config/firebaseAdmin',
|
|
10
|
+
'functions/src/config/firebaseAdmin',
|
|
11
|
+
];
|
|
12
|
+
exports.noMockFirebaseAdmin = (0, createRule_1.createRule)({
|
|
13
|
+
name: 'no-mock-firebase-admin',
|
|
14
|
+
meta: {
|
|
15
|
+
type: 'problem',
|
|
16
|
+
docs: {
|
|
17
|
+
description: 'Prevent mocking of functions/src/config/firebaseAdmin',
|
|
18
|
+
recommended: 'error',
|
|
19
|
+
},
|
|
20
|
+
schema: [],
|
|
21
|
+
messages: {
|
|
22
|
+
noMockFirebaseAdmin: 'Do not mock firebaseAdmin directly. Use mockFirestore from __mocks__/functions/src/config/mockFirestore instead.',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultOptions: [],
|
|
26
|
+
create(context) {
|
|
27
|
+
return {
|
|
28
|
+
CallExpression(node) {
|
|
29
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
30
|
+
node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
31
|
+
node.callee.object.name === 'jest' &&
|
|
32
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
33
|
+
node.callee.property.name === 'mock' &&
|
|
34
|
+
node.arguments.length > 0 &&
|
|
35
|
+
node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal &&
|
|
36
|
+
typeof node.arguments[0].value === 'string') {
|
|
37
|
+
const mockPath = node.arguments[0].value;
|
|
38
|
+
const isFirebaseAdminMock = FIREBASE_ADMIN_PATHS.some(path => mockPath.endsWith(path) || mockPath.includes('firebaseAdmin'));
|
|
39
|
+
if (isFirebaseAdminMock) {
|
|
40
|
+
context.report({
|
|
41
|
+
node,
|
|
42
|
+
messageId: 'noMockFirebaseAdmin',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=no-mock-firebase-admin.js.map
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preferBatchOperations = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const SETTER_METHODS = new Set(['set', 'overwrite']);
|
|
7
|
+
const ARRAY_METHODS = new Set(['map', 'forEach', 'filter', 'reduce', 'every', 'some']);
|
|
8
|
+
function isArrayMethod(node) {
|
|
9
|
+
if (node.type !== utils_1.AST_NODE_TYPES.CallExpression)
|
|
10
|
+
return { isValid: false };
|
|
11
|
+
const callee = node.callee;
|
|
12
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
13
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
14
|
+
ARRAY_METHODS.has(callee.property.name)) {
|
|
15
|
+
return { isValid: true, methodName: callee.property.name };
|
|
16
|
+
}
|
|
17
|
+
return { isValid: false };
|
|
18
|
+
}
|
|
19
|
+
function isPromiseAll(node) {
|
|
20
|
+
if (node.type !== utils_1.AST_NODE_TYPES.CallExpression)
|
|
21
|
+
return false;
|
|
22
|
+
const callee = node.callee;
|
|
23
|
+
return (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
24
|
+
callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
25
|
+
callee.object.name === 'Promise' &&
|
|
26
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
27
|
+
callee.property.name === 'all');
|
|
28
|
+
}
|
|
29
|
+
function findLoopNode(node) {
|
|
30
|
+
let current = node;
|
|
31
|
+
let loopNode = null;
|
|
32
|
+
while (current) {
|
|
33
|
+
switch (current.type) {
|
|
34
|
+
case utils_1.AST_NODE_TYPES.ForStatement:
|
|
35
|
+
case utils_1.AST_NODE_TYPES.ForInStatement:
|
|
36
|
+
case utils_1.AST_NODE_TYPES.ForOfStatement:
|
|
37
|
+
case utils_1.AST_NODE_TYPES.WhileStatement:
|
|
38
|
+
case utils_1.AST_NODE_TYPES.DoWhileStatement:
|
|
39
|
+
loopNode = current;
|
|
40
|
+
break;
|
|
41
|
+
case utils_1.AST_NODE_TYPES.CallExpression:
|
|
42
|
+
// Check for Promise.all
|
|
43
|
+
if (isPromiseAll(current)) {
|
|
44
|
+
return { node: current, isArrayMethod: 'map' };
|
|
45
|
+
}
|
|
46
|
+
// Check for array methods
|
|
47
|
+
const { isValid, methodName: currentMethodName } = isArrayMethod(current);
|
|
48
|
+
if (isValid && currentMethodName) {
|
|
49
|
+
// For sequential array methods, check if the callback is async
|
|
50
|
+
if (currentMethodName === 'forEach' || currentMethodName === 'reduce' || currentMethodName === 'filter') {
|
|
51
|
+
const callback = current.arguments[0];
|
|
52
|
+
if (callback && (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
53
|
+
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
|
|
54
|
+
callback.async) {
|
|
55
|
+
return { node: current, isArrayMethod: currentMethodName };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { node: current, isArrayMethod: currentMethodName };
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
case utils_1.AST_NODE_TYPES.Program:
|
|
62
|
+
// Return loop if we found one
|
|
63
|
+
if (loopNode) {
|
|
64
|
+
return { node: loopNode };
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
current = current.parent;
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
function isSetterMethodCall(node) {
|
|
73
|
+
if (node.type !== utils_1.AST_NODE_TYPES.CallExpression)
|
|
74
|
+
return { isValid: false };
|
|
75
|
+
const callee = node.callee;
|
|
76
|
+
if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression)
|
|
77
|
+
return { isValid: false };
|
|
78
|
+
if (callee.property.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
79
|
+
return { isValid: false };
|
|
80
|
+
if (!SETTER_METHODS.has(callee.property.name))
|
|
81
|
+
return { isValid: false };
|
|
82
|
+
// Get the setter instance
|
|
83
|
+
const object = callee.object;
|
|
84
|
+
if (object.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
85
|
+
return { isValid: false };
|
|
86
|
+
const setterInstance = object.name;
|
|
87
|
+
// Get the method name
|
|
88
|
+
const methodName = callee.property.name;
|
|
89
|
+
return { isValid: true, methodName, setterInstance };
|
|
90
|
+
}
|
|
91
|
+
const loopSetterCalls = new Map();
|
|
92
|
+
exports.preferBatchOperations = (0, createRule_1.createRule)({
|
|
93
|
+
name: 'prefer-batch-operations',
|
|
94
|
+
meta: {
|
|
95
|
+
type: 'suggestion',
|
|
96
|
+
docs: {
|
|
97
|
+
description: 'Enforce using setAll() and overwriteAll() instead of multiple set() or overwrite() calls',
|
|
98
|
+
recommended: 'error',
|
|
99
|
+
},
|
|
100
|
+
fixable: 'code',
|
|
101
|
+
schema: [],
|
|
102
|
+
messages: {
|
|
103
|
+
preferSetAll: 'Use setAll() instead of multiple set() calls for better performance',
|
|
104
|
+
preferOverwriteAll: 'Use overwriteAll() instead of multiple overwrite() calls for better performance',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
defaultOptions: [],
|
|
108
|
+
create(context) {
|
|
109
|
+
return {
|
|
110
|
+
'Program:exit'() {
|
|
111
|
+
// Clear the maps for the next file
|
|
112
|
+
loopSetterCalls.clear();
|
|
113
|
+
},
|
|
114
|
+
CallExpression(node) {
|
|
115
|
+
const { isValid, methodName, setterInstance } = isSetterMethodCall(node);
|
|
116
|
+
if (!isValid || !methodName || !setterInstance)
|
|
117
|
+
return;
|
|
118
|
+
// Check if we're in a loop or Promise.all
|
|
119
|
+
const loopInfo = findLoopNode(node);
|
|
120
|
+
if (!loopInfo)
|
|
121
|
+
return;
|
|
122
|
+
// Get or create the setter calls map for this loop
|
|
123
|
+
let setterCalls = loopSetterCalls.get(loopInfo.node);
|
|
124
|
+
if (!setterCalls) {
|
|
125
|
+
setterCalls = new Map();
|
|
126
|
+
loopSetterCalls.set(loopInfo.node, setterCalls);
|
|
127
|
+
}
|
|
128
|
+
// Track setter instance and method calls for this loop
|
|
129
|
+
const key = setterInstance;
|
|
130
|
+
const existing = setterCalls.get(key);
|
|
131
|
+
if (existing) {
|
|
132
|
+
// If we see a different method on the same setter instance, don't report
|
|
133
|
+
if (existing.methodName !== methodName)
|
|
134
|
+
return;
|
|
135
|
+
existing.count++;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
setterCalls.set(key, { methodName, count: 1 });
|
|
139
|
+
}
|
|
140
|
+
// Report on the first occurrence of a repeated call
|
|
141
|
+
// For Promise.all and array methods, report on the first occurrence
|
|
142
|
+
// For regular loops, report on the first occurrence too since we know it's in a loop
|
|
143
|
+
const shouldReport = loopInfo.isArrayMethod
|
|
144
|
+
? ['forEach', 'reduce', 'filter', 'map'].includes(loopInfo.isArrayMethod)
|
|
145
|
+
: setterCalls.get(key).count === 1;
|
|
146
|
+
// Don't report if we have multiple different setter instances in a loop
|
|
147
|
+
// Only check this for regular loops, not array methods or Promise.all
|
|
148
|
+
if (shouldReport && !loopInfo.isArrayMethod) {
|
|
149
|
+
const setterInstances = new Set(Array.from(setterCalls.keys()));
|
|
150
|
+
if (setterInstances.size > 1) {
|
|
151
|
+
// This is a valid use case when using multiple setters in a loop
|
|
152
|
+
// For example: userSetter.set(doc.user) and orderSetter.set(doc.order)
|
|
153
|
+
// Each setter operates on a different collection, so they can't be batched together
|
|
154
|
+
// We only want to report when using the same setter instance multiple times
|
|
155
|
+
// For example: userSetter.set(doc.user) multiple times should use userSetter.setAll()
|
|
156
|
+
if (loopInfo.node.type.startsWith('For') || loopInfo.node.type.startsWith('While') || loopInfo.node.type.startsWith('Do')) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Report on the first occurrence of a repeated call
|
|
162
|
+
// For Promise.all and array methods, report on the first occurrence
|
|
163
|
+
// For regular loops, report on the first occurrence too since we know it's in a loop
|
|
164
|
+
if (shouldReport) {
|
|
165
|
+
const messageId = methodName === 'set' ? 'preferSetAll' : 'preferOverwriteAll';
|
|
166
|
+
context.report({
|
|
167
|
+
node,
|
|
168
|
+
messageId,
|
|
169
|
+
fix: () => null, // We can't provide a fix because we don't know the array structure
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
//# sourceMappingURL=prefer-batch-operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const preferCloneDeep: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"preferCloneDeep", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preferCloneDeep = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'prefer-clone-deep',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Prefer using cloneDeep over nested spread copying',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
preferCloneDeep: 'Use cloneDeep from functions/src/util/cloneDeep.ts instead of nested spread operators for deep object copying',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
function hasNestedSpread(node) {
|
|
23
|
+
let spreadCount = 0;
|
|
24
|
+
let hasFunction = false;
|
|
25
|
+
let hasSymbol = false;
|
|
26
|
+
function visit(node) {
|
|
27
|
+
if (node.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
28
|
+
spreadCount++;
|
|
29
|
+
}
|
|
30
|
+
else if (node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
31
|
+
node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
32
|
+
hasFunction = true;
|
|
33
|
+
}
|
|
34
|
+
else if (node.type === utils_1.AST_NODE_TYPES.Property &&
|
|
35
|
+
node.computed &&
|
|
36
|
+
node.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
37
|
+
node.key.name === 'Symbol') {
|
|
38
|
+
hasSymbol = true;
|
|
39
|
+
}
|
|
40
|
+
for (const key in node) {
|
|
41
|
+
const value = node[key];
|
|
42
|
+
if (value && typeof value === 'object') {
|
|
43
|
+
visit(value);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
visit(node);
|
|
48
|
+
return spreadCount > 1 && !hasFunction && !hasSymbol;
|
|
49
|
+
}
|
|
50
|
+
function getSourceText(node) {
|
|
51
|
+
return context.getSourceCode().getText(node);
|
|
52
|
+
}
|
|
53
|
+
function generateCloneDeepFix(node) {
|
|
54
|
+
const sourceText = getSourceText(node);
|
|
55
|
+
return `cloneDeep(${sourceText.replace(/\.\.\./g, '')}, {} as const)`;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
ObjectExpression(node) {
|
|
59
|
+
if (hasNestedSpread(node)) {
|
|
60
|
+
context.report({
|
|
61
|
+
node,
|
|
62
|
+
messageId: 'preferCloneDeep',
|
|
63
|
+
fix(fixer) {
|
|
64
|
+
return fixer.replaceText(node, generateCloneDeepFix(node));
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=prefer-clone-deep.js.map
|
|
@@ -51,6 +51,16 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
|
|
|
51
51
|
if (param.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
|
|
52
52
|
return getParameterType(param.left);
|
|
53
53
|
}
|
|
54
|
+
if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern && param.typeAnnotation) {
|
|
55
|
+
// For destructured parameters, use the type annotation name
|
|
56
|
+
const typeNode = param.typeAnnotation.typeAnnotation;
|
|
57
|
+
if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
58
|
+
return typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier
|
|
59
|
+
? typeNode.typeName.name
|
|
60
|
+
: 'unknown';
|
|
61
|
+
}
|
|
62
|
+
return typeNode.type;
|
|
63
|
+
}
|
|
54
64
|
if (param.type === utils_1.AST_NODE_TYPES.Identifier && param.typeAnnotation) {
|
|
55
65
|
const typeNode = param.typeAnnotation.typeAnnotation;
|
|
56
66
|
if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
@@ -72,18 +82,158 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
|
|
|
72
82
|
const typeMap = new Map();
|
|
73
83
|
for (const param of params) {
|
|
74
84
|
const type = getParameterType(param);
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
const count = (typeMap.get(type) || 0) + 1;
|
|
86
|
+
typeMap.set(type, count);
|
|
87
|
+
if (count > 1) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
function isBuiltInOrThirdParty(node) {
|
|
94
|
+
// Check if the node is part of a new expression (constructor call)
|
|
95
|
+
let current = node;
|
|
96
|
+
while (current.parent) {
|
|
97
|
+
const parent = current.parent;
|
|
98
|
+
// Check if we're in a constructor call
|
|
99
|
+
if (parent.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
100
|
+
const callee = parent.callee;
|
|
101
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
102
|
+
// List of built-in objects that should be ignored
|
|
103
|
+
const builtInObjects = new Set([
|
|
104
|
+
'Promise',
|
|
105
|
+
'Map',
|
|
106
|
+
'Set',
|
|
107
|
+
'WeakMap',
|
|
108
|
+
'WeakSet',
|
|
109
|
+
'Int8Array',
|
|
110
|
+
'Uint8Array',
|
|
111
|
+
'Uint8ClampedArray',
|
|
112
|
+
'Int16Array',
|
|
113
|
+
'Uint16Array',
|
|
114
|
+
'Int32Array',
|
|
115
|
+
'Uint32Array',
|
|
116
|
+
'Float32Array',
|
|
117
|
+
'Float64Array',
|
|
118
|
+
'BigInt64Array',
|
|
119
|
+
'BigUint64Array',
|
|
120
|
+
'ArrayBuffer',
|
|
121
|
+
'SharedArrayBuffer',
|
|
122
|
+
'DataView',
|
|
123
|
+
'Date',
|
|
124
|
+
'RegExp',
|
|
125
|
+
'Error',
|
|
126
|
+
'AggregateError',
|
|
127
|
+
'EvalError',
|
|
128
|
+
'RangeError',
|
|
129
|
+
'ReferenceError',
|
|
130
|
+
'SyntaxError',
|
|
131
|
+
'TypeError',
|
|
132
|
+
'URIError',
|
|
133
|
+
'Transform', // Added Transform to built-in objects
|
|
134
|
+
]);
|
|
135
|
+
if (builtInObjects.has(callee.name)) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
// Check if the identifier is imported from a third-party module
|
|
139
|
+
const scope = context.getScope();
|
|
140
|
+
const variable = scope.variables.find((v) => v.name === callee.name);
|
|
141
|
+
if (variable) {
|
|
142
|
+
const def = variable.defs[0];
|
|
143
|
+
if (def?.type === 'ImportBinding') {
|
|
144
|
+
const importDecl = def.parent;
|
|
145
|
+
let source;
|
|
146
|
+
if (importDecl.type === utils_1.AST_NODE_TYPES.ImportDeclaration) {
|
|
147
|
+
source = importDecl.source.value;
|
|
148
|
+
}
|
|
149
|
+
else if (importDecl.type ===
|
|
150
|
+
utils_1.AST_NODE_TYPES.TSImportEqualsDeclaration &&
|
|
151
|
+
importDecl.moduleReference.type ===
|
|
152
|
+
utils_1.AST_NODE_TYPES.TSExternalModuleReference &&
|
|
153
|
+
importDecl.moduleReference.expression.type ===
|
|
154
|
+
utils_1.AST_NODE_TYPES.Literal) {
|
|
155
|
+
source = importDecl.moduleReference.expression
|
|
156
|
+
.value;
|
|
157
|
+
}
|
|
158
|
+
// If it's a third-party module (doesn't start with '.' or '/'), ignore it
|
|
159
|
+
if (source &&
|
|
160
|
+
!source.startsWith('.') &&
|
|
161
|
+
!source.startsWith('/')) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
168
|
+
// Handle cases like React.Component or lodash.debounce
|
|
169
|
+
const obj = callee.object;
|
|
170
|
+
if (obj.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
171
|
+
const scope = context.getScope();
|
|
172
|
+
const variable = scope.variables.find((v) => v.name === obj.name);
|
|
173
|
+
if (variable) {
|
|
174
|
+
const def = variable.defs[0];
|
|
175
|
+
if (def?.type === 'ImportBinding') {
|
|
176
|
+
const importDecl = def.parent;
|
|
177
|
+
let source;
|
|
178
|
+
if (importDecl.type === utils_1.AST_NODE_TYPES.ImportDeclaration) {
|
|
179
|
+
source = importDecl.source.value;
|
|
180
|
+
}
|
|
181
|
+
else if (importDecl.type ===
|
|
182
|
+
utils_1.AST_NODE_TYPES.TSImportEqualsDeclaration &&
|
|
183
|
+
importDecl.moduleReference.type ===
|
|
184
|
+
utils_1.AST_NODE_TYPES.TSExternalModuleReference &&
|
|
185
|
+
importDecl.moduleReference.expression.type ===
|
|
186
|
+
utils_1.AST_NODE_TYPES.Literal) {
|
|
187
|
+
source = importDecl.moduleReference.expression
|
|
188
|
+
.value;
|
|
189
|
+
}
|
|
190
|
+
// If it's a third-party module (doesn't start with '.' or '/'), ignore it
|
|
191
|
+
if (source &&
|
|
192
|
+
!source.startsWith('.') &&
|
|
193
|
+
!source.startsWith('/')) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Also check if we're in a property of an object that's passed to a constructor
|
|
202
|
+
if (parent.type === utils_1.AST_NODE_TYPES.Property &&
|
|
203
|
+
parent.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
204
|
+
parent.parent.parent?.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
77
205
|
return true;
|
|
78
206
|
}
|
|
207
|
+
current = parent;
|
|
208
|
+
}
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
function hasABPattern(params) {
|
|
212
|
+
if (params.length !== 2)
|
|
213
|
+
return false;
|
|
214
|
+
const paramNames = params.map(param => {
|
|
215
|
+
if (param.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
216
|
+
return param.name;
|
|
217
|
+
}
|
|
218
|
+
return '';
|
|
219
|
+
});
|
|
220
|
+
// Check if both parameters end with 'A' and 'B' respectively and have the same prefix
|
|
221
|
+
const [first, second] = paramNames;
|
|
222
|
+
if (first.endsWith('A') && second.endsWith('B')) {
|
|
223
|
+
const firstPrefix = first.slice(0, -1);
|
|
224
|
+
const secondPrefix = second.slice(0, -1);
|
|
225
|
+
return firstPrefix === secondPrefix;
|
|
79
226
|
}
|
|
80
227
|
return false;
|
|
81
228
|
}
|
|
82
229
|
function shouldIgnoreNode(node) {
|
|
230
|
+
// Ignore built-in objects and third-party modules
|
|
231
|
+
if (isBuiltInOrThirdParty(node))
|
|
232
|
+
return true;
|
|
83
233
|
// Ignore variadic functions if configured
|
|
84
234
|
if (finalOptions.ignoreVariadicFunctions) {
|
|
85
235
|
const hasRestParam = node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration &&
|
|
86
|
-
node.params.some(param => param.type === utils_1.AST_NODE_TYPES.RestElement);
|
|
236
|
+
node.params.some((param) => param.type === utils_1.AST_NODE_TYPES.RestElement);
|
|
87
237
|
if (hasRestParam)
|
|
88
238
|
return true;
|
|
89
239
|
}
|
|
@@ -98,6 +248,16 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
|
|
|
98
248
|
parent = parent.parent;
|
|
99
249
|
}
|
|
100
250
|
}
|
|
251
|
+
// Ignore functions with A/B pattern parameters
|
|
252
|
+
if ((node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
253
|
+
node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
254
|
+
node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
255
|
+
node.type === utils_1.AST_NODE_TYPES.TSMethodSignature ||
|
|
256
|
+
node.type === utils_1.AST_NODE_TYPES.TSFunctionType) &&
|
|
257
|
+
Array.isArray(node.params)) {
|
|
258
|
+
if (hasABPattern(node.params))
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
101
261
|
return false;
|
|
102
262
|
}
|
|
103
263
|
function checkFunction(node) {
|
|
@@ -105,9 +265,7 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
|
|
|
105
265
|
return;
|
|
106
266
|
const params = node.params;
|
|
107
267
|
// Check for too many parameters first
|
|
108
|
-
const minParams = finalOptions.minimumParameters
|
|
109
|
-
? finalOptions.minimumParameters
|
|
110
|
-
: defaultOptions.minimumParameters;
|
|
268
|
+
const minParams = finalOptions.minimumParameters ?? defaultOptions.minimumParameters ?? 3;
|
|
111
269
|
if (params.length >= minParams) {
|
|
112
270
|
context.report({
|
|
113
271
|
node,
|
|
@@ -4,6 +4,7 @@ exports.semanticFunctionPrefixes = void 0;
|
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
6
|
const DISALLOWED_PREFIXES = new Set(['get', 'update', 'check', 'manage', 'process', 'do']);
|
|
7
|
+
const NEXTJS_DATA_FUNCTIONS = new Set(['getServerSideProps', 'getStaticProps', 'getStaticPaths']);
|
|
7
8
|
const SUGGESTED_ALTERNATIVES = {
|
|
8
9
|
get: ['fetch', 'retrieve', 'compute', 'derive'],
|
|
9
10
|
update: ['modify', 'set', 'apply'],
|
|
@@ -38,6 +39,9 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
|
|
|
38
39
|
// Skip if method starts with 'is' (boolean check methods are okay)
|
|
39
40
|
if (methodName.startsWith('is'))
|
|
40
41
|
return;
|
|
42
|
+
// Skip Next.js data-fetching functions
|
|
43
|
+
if (NEXTJS_DATA_FUNCTIONS.has(methodName))
|
|
44
|
+
return;
|
|
41
45
|
// Extract first word from PascalCase/camelCase
|
|
42
46
|
let firstWord = methodName;
|
|
43
47
|
for (let i = 1; i < methodName.length; i++) {
|
|
@@ -79,6 +83,9 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
|
|
|
79
83
|
// Skip if function starts with 'is' (boolean check functions are okay)
|
|
80
84
|
if (functionName.startsWith('is'))
|
|
81
85
|
return;
|
|
86
|
+
// Skip Next.js data-fetching functions
|
|
87
|
+
if (NEXTJS_DATA_FUNCTIONS.has(functionName))
|
|
88
|
+
return;
|
|
82
89
|
// Extract first word from PascalCase/camelCase
|
|
83
90
|
let firstWord = functionName;
|
|
84
91
|
for (let i = 1; i < functionName.length; i++) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const syncOnwriteNameFunc: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"mismatchedName", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|