@blumintinc/eslint-plugin-blumint 1.7.3 → 1.8.1
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 +83 -61
- package/lib/index.js +85 -1
- package/lib/rules/enforce-assert-throws.js +48 -3
- package/lib/rules/enforce-assertSafe-object-key.d.ts +2 -0
- package/lib/rules/enforce-assertSafe-object-key.js +284 -0
- package/lib/rules/enforce-centralized-mock-firestore.js +192 -14
- package/lib/rules/enforce-css-media-queries.d.ts +5 -0
- package/lib/rules/enforce-css-media-queries.js +87 -0
- package/lib/rules/enforce-dynamic-imports.d.ts +9 -0
- package/lib/rules/enforce-dynamic-imports.js +84 -0
- package/lib/rules/enforce-firestore-facade.js +8 -0
- package/lib/rules/enforce-id-capitalization.d.ts +6 -0
- package/lib/rules/enforce-id-capitalization.js +78 -0
- package/lib/rules/enforce-microdiff.d.ts +3 -0
- package/lib/rules/enforce-microdiff.js +379 -0
- package/lib/rules/enforce-mui-rounded-icons.d.ts +1 -0
- package/lib/rules/enforce-mui-rounded-icons.js +54 -0
- package/lib/rules/enforce-object-literal-as-const.d.ts +4 -0
- package/lib/rules/enforce-object-literal-as-const.js +88 -0
- package/lib/rules/enforce-positive-naming.d.ts +1 -0
- package/lib/rules/enforce-positive-naming.js +387 -0
- package/lib/rules/enforce-props-argument-name.d.ts +8 -0
- package/lib/rules/enforce-props-argument-name.js +182 -0
- package/lib/rules/enforce-react-type-naming.d.ts +3 -0
- package/lib/rules/enforce-react-type-naming.js +191 -0
- package/lib/rules/enforce-render-hits-memoization.js +155 -26
- package/lib/rules/enforce-singular-type-names.d.ts +2 -0
- package/lib/rules/enforce-singular-type-names.js +112 -0
- package/lib/rules/enforce-timestamp-now.d.ts +1 -0
- package/lib/rules/enforce-timestamp-now.js +223 -0
- package/lib/rules/enforce-verb-noun-naming.js +5 -2
- package/lib/rules/ensure-pointer-events-none.d.ts +1 -0
- package/lib/rules/ensure-pointer-events-none.js +211 -0
- package/lib/rules/extract-global-constants.d.ts +1 -1
- package/lib/rules/extract-global-constants.js +109 -1
- package/lib/rules/global-const-style.js +3 -2
- package/lib/rules/key-only-outermost-element.d.ts +3 -0
- package/lib/rules/key-only-outermost-element.js +152 -0
- package/lib/rules/no-always-true-false-conditions.d.ts +3 -0
- package/lib/rules/no-always-true-false-conditions.js +1221 -0
- package/lib/rules/no-circular-references.d.ts +1 -0
- package/lib/rules/no-circular-references.js +523 -0
- package/lib/rules/no-firestore-jest-mock.js +27 -4
- package/lib/rules/no-hungarian.d.ts +5 -0
- package/lib/rules/no-hungarian.js +223 -0
- package/lib/rules/no-mock-firebase-admin.js +20 -7
- package/lib/rules/no-object-values-on-strings.d.ts +2 -0
- package/lib/rules/no-object-values-on-strings.js +294 -0
- package/lib/rules/no-type-assertion-returns.d.ts +9 -0
- package/lib/rules/no-type-assertion-returns.js +299 -0
- package/lib/rules/no-unnecessary-destructuring.d.ts +5 -0
- package/lib/rules/no-unnecessary-destructuring.js +69 -0
- package/lib/rules/no-unnecessary-verb-suffix.d.ts +1 -0
- package/lib/rules/no-unnecessary-verb-suffix.js +203 -0
- package/lib/rules/no-unused-props.js +10 -5
- package/lib/rules/no-unused-usestate.d.ts +8 -0
- package/lib/rules/no-unused-usestate.js +84 -0
- package/lib/rules/omit-index-html.d.ts +7 -0
- package/lib/rules/omit-index-html.js +91 -0
- package/lib/rules/prefer-clone-deep.js +294 -22
- package/lib/rules/prefer-fragment-component.js +265 -34
- package/lib/rules/prefer-global-router-state-key.d.ts +5 -0
- package/lib/rules/prefer-global-router-state-key.js +89 -0
- package/lib/rules/prefer-usememo-over-useeffect-usestate.d.ts +5 -0
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +129 -0
- package/lib/rules/prefer-utility-function-over-private-static.d.ts +1 -0
- package/lib/rules/prefer-utility-function-over-private-static.js +77 -0
- package/lib/rules/react-usememo-should-be-component.d.ts +1 -0
- package/lib/rules/react-usememo-should-be-component.js +623 -0
- package/package.json +10 -7
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforceAssertSafeObjectKey = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'enforce-assertSafe-object-key',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'problem',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Enforce the use of assertSafe(id) when accessing object properties with computed keys that involve string interpolation or explicit string conversion.',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
useAssertSafe: 'Use assertSafe() for object key access to ensure safe property access. Replace `obj[String(id)]` or `obj[`${id}`]` with `obj[assertSafe(id)]`.',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
let hasAssertSafeImport = false;
|
|
23
|
+
/**
|
|
24
|
+
* Helper function to add assertSafe import if needed
|
|
25
|
+
*/
|
|
26
|
+
const addAssertSafeImport = (fixer) => {
|
|
27
|
+
const program = context.getSourceCode().ast;
|
|
28
|
+
const firstImport = program.body.find((node) => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
|
|
29
|
+
const importStatement = "import { assertSafe } from 'utils/assertions';\n";
|
|
30
|
+
if (firstImport) {
|
|
31
|
+
return fixer.insertTextBefore(firstImport, importStatement);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
return fixer.insertTextBefore(program.body[0], importStatement);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Helper function to create fixes for a node
|
|
39
|
+
*/
|
|
40
|
+
const createFixes = (fixer, node, argText) => {
|
|
41
|
+
const fixes = [];
|
|
42
|
+
// Add import if not present
|
|
43
|
+
if (!hasAssertSafeImport) {
|
|
44
|
+
fixes.push(addAssertSafeImport(fixer));
|
|
45
|
+
hasAssertSafeImport = true;
|
|
46
|
+
}
|
|
47
|
+
// Replace the node with assertSafe(argText)
|
|
48
|
+
fixes.push(fixer.replaceText(node, `assertSafe(${argText})`));
|
|
49
|
+
return fixes;
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
ImportDeclaration(node) {
|
|
53
|
+
// Check if assertSafe is already imported
|
|
54
|
+
if (node.specifiers.some((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
55
|
+
specifier.imported.name === 'assertSafe')) {
|
|
56
|
+
hasAssertSafeImport = true;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
// Handle computed property in object destructuring
|
|
60
|
+
Property(node) {
|
|
61
|
+
if (node.computed && node.key) {
|
|
62
|
+
const key = node.key;
|
|
63
|
+
// Check for String(id) pattern
|
|
64
|
+
if (key.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
65
|
+
key.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
66
|
+
key.callee.name === 'String') {
|
|
67
|
+
context.report({
|
|
68
|
+
node: key,
|
|
69
|
+
messageId: 'useAssertSafe',
|
|
70
|
+
fix(fixer) {
|
|
71
|
+
const arg = key.arguments[0];
|
|
72
|
+
const argText = context.getSourceCode().getText(arg);
|
|
73
|
+
return createFixes(fixer, key, argText);
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
// Check for template literals like `${id}`
|
|
78
|
+
if (key.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
|
|
79
|
+
key.expressions.length === 1 &&
|
|
80
|
+
key.quasis.length === 2 &&
|
|
81
|
+
key.quasis[0].value.raw === '' &&
|
|
82
|
+
key.quasis[1].value.raw === '') {
|
|
83
|
+
context.report({
|
|
84
|
+
node: key,
|
|
85
|
+
messageId: 'useAssertSafe',
|
|
86
|
+
fix(fixer) {
|
|
87
|
+
const expr = key.expressions[0];
|
|
88
|
+
const exprText = context.getSourceCode().getText(expr);
|
|
89
|
+
return createFixes(fixer, key, exprText);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
// Handle binary expressions like 'key' in obj
|
|
96
|
+
BinaryExpression(node) {
|
|
97
|
+
if (node.operator === 'in') {
|
|
98
|
+
const left = node.left;
|
|
99
|
+
// Check for String(id) pattern
|
|
100
|
+
if (left.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
101
|
+
left.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
102
|
+
left.callee.name === 'String') {
|
|
103
|
+
context.report({
|
|
104
|
+
node: left,
|
|
105
|
+
messageId: 'useAssertSafe',
|
|
106
|
+
fix(fixer) {
|
|
107
|
+
const arg = left.arguments[0];
|
|
108
|
+
const argText = context.getSourceCode().getText(arg);
|
|
109
|
+
return createFixes(fixer, left, argText);
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// Check for template literals like `${id}`
|
|
114
|
+
if (left.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
|
|
115
|
+
left.expressions.length === 1 &&
|
|
116
|
+
left.quasis.length === 2 &&
|
|
117
|
+
left.quasis[0].value.raw === '' &&
|
|
118
|
+
left.quasis[1].value.raw === '') {
|
|
119
|
+
context.report({
|
|
120
|
+
node: left,
|
|
121
|
+
messageId: 'useAssertSafe',
|
|
122
|
+
fix(fixer) {
|
|
123
|
+
const expr = left.expressions[0];
|
|
124
|
+
const exprText = context.getSourceCode().getText(expr);
|
|
125
|
+
return createFixes(fixer, left, exprText);
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
MemberExpression(node) {
|
|
132
|
+
if (node.computed) {
|
|
133
|
+
const property = node.property;
|
|
134
|
+
// Skip if already using assertSafe
|
|
135
|
+
if (property.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
136
|
+
property.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
137
|
+
property.callee.name === 'assertSafe') {
|
|
138
|
+
// Already using assertSafe, this is valid
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Try to determine if this is likely an array or dictionary
|
|
142
|
+
const objectNode = node.object;
|
|
143
|
+
let objectName = '';
|
|
144
|
+
let isLikelyArray = false;
|
|
145
|
+
if (objectNode.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
146
|
+
objectName = objectNode.name.toLowerCase();
|
|
147
|
+
isLikelyArray =
|
|
148
|
+
/^(array|arr|items|elements|list|collection|data)s?$/i.test(objectName);
|
|
149
|
+
}
|
|
150
|
+
// Check for string literals - allow them for dictionaries but not for regular objects
|
|
151
|
+
if (property.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
152
|
+
typeof property.value === 'string') {
|
|
153
|
+
// String literals are fine, no need for assertSafe
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// Check for numeric literals - always allow for arrays
|
|
157
|
+
if (property.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
158
|
+
typeof property.value === 'number') {
|
|
159
|
+
// Numeric literals are fine, no need for assertSafe
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// Check if we're using String(id) pattern
|
|
163
|
+
if (property.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
164
|
+
property.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
165
|
+
property.callee.name === 'String') {
|
|
166
|
+
context.report({
|
|
167
|
+
node: property,
|
|
168
|
+
messageId: 'useAssertSafe',
|
|
169
|
+
fix(fixer) {
|
|
170
|
+
const arg = property.arguments[0];
|
|
171
|
+
const argText = context.getSourceCode().getText(arg);
|
|
172
|
+
return createFixes(fixer, property, argText);
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// Check for template literals
|
|
178
|
+
if (property.type === utils_1.AST_NODE_TYPES.TemplateLiteral) {
|
|
179
|
+
// If it's a template literal in an array, allow it
|
|
180
|
+
if (isLikelyArray) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// Only flag simple template literals that are just `${id}`
|
|
184
|
+
// Complex templates with additional text like `prefix_${id}_suffix` are allowed
|
|
185
|
+
const isSimpleVarInterpolation = property.expressions.length === 1 &&
|
|
186
|
+
property.quasis.length === 2 &&
|
|
187
|
+
property.quasis[0].value.raw === '' &&
|
|
188
|
+
property.quasis[1].value.raw === '';
|
|
189
|
+
if (!isSimpleVarInterpolation) {
|
|
190
|
+
// Complex template literals with additional text are fine
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
context.report({
|
|
194
|
+
node: property,
|
|
195
|
+
messageId: 'useAssertSafe',
|
|
196
|
+
fix(fixer) {
|
|
197
|
+
// Extract the expression from the template literal
|
|
198
|
+
const expr = property.expressions[0];
|
|
199
|
+
const exprText = context.getSourceCode().getText(expr);
|
|
200
|
+
return createFixes(fixer, property, exprText);
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// Check for direct variable usage (identifiers)
|
|
206
|
+
if (property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
207
|
+
// Skip numeric literals, they're safe
|
|
208
|
+
if (/^\d+$/.test(property.name)) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
// If it looks like an array access, allow it
|
|
212
|
+
if (isLikelyArray) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
context.report({
|
|
216
|
+
node: property,
|
|
217
|
+
messageId: 'useAssertSafe',
|
|
218
|
+
fix(fixer) {
|
|
219
|
+
// For direct variable use, just use the variable name
|
|
220
|
+
const propText = context.getSourceCode().getText(property);
|
|
221
|
+
return createFixes(fixer, property, propText);
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// Check for binary expressions (like index + 1)
|
|
227
|
+
if (property.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
|
|
228
|
+
// Allow binary expressions in array access
|
|
229
|
+
if (isLikelyArray) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
context.report({
|
|
233
|
+
node: property,
|
|
234
|
+
messageId: 'useAssertSafe',
|
|
235
|
+
fix(fixer) {
|
|
236
|
+
const propText = context.getSourceCode().getText(property);
|
|
237
|
+
return createFixes(fixer, property, propText);
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// Check for boolean expressions and other literals
|
|
243
|
+
if (property.type === utils_1.AST_NODE_TYPES.Literal ||
|
|
244
|
+
property.type === utils_1.AST_NODE_TYPES.LogicalExpression ||
|
|
245
|
+
property.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
|
|
246
|
+
// Allow these expressions in array access
|
|
247
|
+
if (isLikelyArray) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
context.report({
|
|
251
|
+
node: property,
|
|
252
|
+
messageId: 'useAssertSafe',
|
|
253
|
+
fix(fixer) {
|
|
254
|
+
const propText = context.getSourceCode().getText(property);
|
|
255
|
+
return createFixes(fixer, property, propText);
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
// Check for function calls (anything that isn't handled above)
|
|
261
|
+
if (property.type === utils_1.AST_NODE_TYPES.MemberExpression ||
|
|
262
|
+
(property.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
263
|
+
!(property.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
264
|
+
property.callee.name === 'String'))) {
|
|
265
|
+
// Allow member expressions and function calls in array access
|
|
266
|
+
if (isLikelyArray) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
context.report({
|
|
270
|
+
node: property,
|
|
271
|
+
messageId: 'useAssertSafe',
|
|
272
|
+
fix(fixer) {
|
|
273
|
+
const propText = context.getSourceCode().getText(property);
|
|
274
|
+
return createFixes(fixer, property, propText);
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
//# sourceMappingURL=enforce-assertSafe-object-key.js.map
|
|
@@ -21,34 +21,212 @@ exports.enforceCentralizedMockFirestore = (0, createRule_1.createRule)({
|
|
|
21
21
|
defaultOptions: [],
|
|
22
22
|
create(context) {
|
|
23
23
|
let hasCentralizedImport = false;
|
|
24
|
-
|
|
24
|
+
const mockFirestoreNodes = new Set();
|
|
25
|
+
const customMockFirestoreNames = new Set();
|
|
26
|
+
const customMockFirestoreCallExpressions = new Set();
|
|
27
|
+
const thisExpressions = [];
|
|
25
28
|
return {
|
|
26
29
|
ImportDeclaration(node) {
|
|
27
30
|
if (node.source.value.endsWith(MOCK_FIRESTORE_PATH)) {
|
|
28
31
|
hasCentralizedImport = true;
|
|
32
|
+
// Check for renamed imports
|
|
33
|
+
for (const specifier of node.specifiers) {
|
|
34
|
+
if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
35
|
+
specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
36
|
+
specifier.imported.name === 'mockFirestore' &&
|
|
37
|
+
specifier.local.name !== 'mockFirestore') {
|
|
38
|
+
customMockFirestoreNames.add(specifier.local.name);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
29
41
|
}
|
|
30
42
|
},
|
|
31
43
|
VariableDeclarator(node) {
|
|
32
44
|
if (node.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
33
45
|
node.id.name === 'mockFirestore') {
|
|
34
|
-
mockFirestoreNodes.
|
|
46
|
+
mockFirestoreNodes.add(node);
|
|
47
|
+
}
|
|
48
|
+
else if (node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
49
|
+
for (const prop of node.id.properties) {
|
|
50
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
51
|
+
prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
52
|
+
prop.key.name === 'mockFirestore') {
|
|
53
|
+
mockFirestoreNodes.add(node);
|
|
54
|
+
// Track renamed destructured imports
|
|
55
|
+
if (prop.value.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
56
|
+
prop.value.name !== 'mockFirestore') {
|
|
57
|
+
customMockFirestoreNames.add(prop.value.name);
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
35
62
|
}
|
|
36
63
|
},
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
64
|
+
PropertyDefinition(node) {
|
|
65
|
+
if (node.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
66
|
+
node.key.name === 'mockFirestore') {
|
|
67
|
+
mockFirestoreNodes.add(node);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
CallExpression(node) {
|
|
71
|
+
// Track calls to custom mockFirestore names
|
|
72
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
73
|
+
customMockFirestoreNames.has(node.callee.name)) {
|
|
74
|
+
customMockFirestoreCallExpressions.add(node);
|
|
75
|
+
}
|
|
76
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
77
|
+
node.callee.name === 'require' &&
|
|
78
|
+
node.arguments.length > 0 &&
|
|
79
|
+
node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal &&
|
|
80
|
+
typeof node.arguments[0].value === 'string' &&
|
|
81
|
+
!node.arguments[0].value.endsWith(MOCK_FIRESTORE_PATH)) {
|
|
82
|
+
const parent = node.parent;
|
|
83
|
+
if (parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
84
|
+
parent.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
85
|
+
for (const prop of parent.id.properties) {
|
|
86
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
87
|
+
prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
88
|
+
prop.key.name === 'mockFirestore') {
|
|
89
|
+
mockFirestoreNodes.add(parent);
|
|
90
|
+
// Track renamed destructured imports
|
|
91
|
+
if (prop.value.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
92
|
+
prop.value.name !== 'mockFirestore') {
|
|
93
|
+
customMockFirestoreNames.add(prop.value.name);
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
// Handle dynamic imports
|
|
102
|
+
'AwaitExpression > CallExpression[callee.type="ImportExpression"]'(node) {
|
|
103
|
+
const parent = node.parent;
|
|
104
|
+
if (parent?.type === utils_1.AST_NODE_TYPES.AwaitExpression &&
|
|
105
|
+
parent.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
106
|
+
parent.parent.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
107
|
+
for (const prop of parent.parent.id.properties) {
|
|
108
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
109
|
+
prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
110
|
+
prop.key.name === 'mockFirestore') {
|
|
111
|
+
mockFirestoreNodes.add(parent.parent);
|
|
112
|
+
// Track renamed destructured imports
|
|
113
|
+
if (prop.value.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
114
|
+
prop.value.name !== 'mockFirestore') {
|
|
115
|
+
customMockFirestoreNames.add(prop.value.name);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
49
119
|
}
|
|
50
120
|
}
|
|
51
121
|
},
|
|
122
|
+
// Handle complex object destructuring
|
|
123
|
+
'ObjectPattern > Property > ObjectPattern > Property > ObjectPattern > Property[key.name="mockFirestore"]'(node) {
|
|
124
|
+
let current = node;
|
|
125
|
+
while (current.parent) {
|
|
126
|
+
if (current.parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
127
|
+
mockFirestoreNodes.add(current.parent);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
current = current.parent;
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
// Capture this.mockFirestore expressions
|
|
134
|
+
MemberExpression(node) {
|
|
135
|
+
if (node.object.type === utils_1.AST_NODE_TYPES.ThisExpression &&
|
|
136
|
+
node.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
137
|
+
node.property.name === 'mockFirestore') {
|
|
138
|
+
thisExpressions.push(node);
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
'Program:exit'() {
|
|
142
|
+
if (mockFirestoreNodes.size > 0) {
|
|
143
|
+
const sourceCode = context.getSourceCode();
|
|
144
|
+
// Report only once for the entire file
|
|
145
|
+
context.report({
|
|
146
|
+
node: Array.from(mockFirestoreNodes)[0],
|
|
147
|
+
messageId: 'useCentralizedMockFirestore',
|
|
148
|
+
fix(fixer) {
|
|
149
|
+
// Instead of trying to modify the code incrementally, we'll generate the entire fixed code
|
|
150
|
+
const originalText = sourceCode.getText();
|
|
151
|
+
const lines = originalText.split('\n');
|
|
152
|
+
// Find the indentation of the code
|
|
153
|
+
const indentMatch = lines[0].match(/^(\s*)/);
|
|
154
|
+
const indent = indentMatch ? indentMatch[1] : '';
|
|
155
|
+
// Create the import statement
|
|
156
|
+
const importLine = `${indent}import { mockFirestore } from '${MOCK_FIRESTORE_PATH}';`;
|
|
157
|
+
// Find all the lines that need to be removed
|
|
158
|
+
const linesToRemove = new Set();
|
|
159
|
+
// Process all nodes that need to be removed
|
|
160
|
+
mockFirestoreNodes.forEach(node => {
|
|
161
|
+
const startLine = sourceCode.getLocFromIndex(node.range[0]).line - 1;
|
|
162
|
+
const endLine = sourceCode.getLocFromIndex(node.range[1]).line - 1;
|
|
163
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
164
|
+
// If it's the only declarator, remove the entire declaration
|
|
165
|
+
if (node.parent.declarations.length === 1) {
|
|
166
|
+
const declStartLine = sourceCode.getLocFromIndex(node.parent.range[0]).line - 1;
|
|
167
|
+
const declEndLine = sourceCode.getLocFromIndex(node.parent.range[1]).line - 1;
|
|
168
|
+
for (let i = declStartLine; i <= declEndLine; i++) {
|
|
169
|
+
linesToRemove.add(i);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// Otherwise, just remove this declarator
|
|
174
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
175
|
+
linesToRemove.add(i);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else if (node.type === utils_1.AST_NODE_TYPES.PropertyDefinition) {
|
|
180
|
+
// Remove class property
|
|
181
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
182
|
+
linesToRemove.add(i);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
// Replace custom mockFirestore references with the standard one
|
|
187
|
+
const replacements = [];
|
|
188
|
+
// Add replacements for custom mockFirestore names
|
|
189
|
+
customMockFirestoreCallExpressions.forEach(node => {
|
|
190
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
191
|
+
replacements.push([
|
|
192
|
+
node.callee.range[0],
|
|
193
|
+
node.callee.name,
|
|
194
|
+
'mockFirestore'
|
|
195
|
+
]);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
// Add replacements for this.mockFirestore
|
|
199
|
+
thisExpressions.forEach(expr => {
|
|
200
|
+
replacements.push([
|
|
201
|
+
expr.range[0],
|
|
202
|
+
sourceCode.getText(expr),
|
|
203
|
+
'mockFirestore'
|
|
204
|
+
]);
|
|
205
|
+
});
|
|
206
|
+
// Sort replacements in reverse order to avoid range issues
|
|
207
|
+
replacements.sort((a, b) => b[0] - a[0]);
|
|
208
|
+
// Apply replacements to the original text
|
|
209
|
+
let fixedText = originalText;
|
|
210
|
+
for (const [pos, oldText, newText] of replacements) {
|
|
211
|
+
fixedText =
|
|
212
|
+
fixedText.substring(0, pos) +
|
|
213
|
+
newText +
|
|
214
|
+
fixedText.substring(pos + oldText.length);
|
|
215
|
+
}
|
|
216
|
+
// Filter out the lines to remove
|
|
217
|
+
const fixedLines = fixedText.split('\n').filter((_, i) => !linesToRemove.has(i));
|
|
218
|
+
// Add the import statement at the beginning
|
|
219
|
+
if (!hasCentralizedImport) {
|
|
220
|
+
fixedLines.unshift(importLine);
|
|
221
|
+
}
|
|
222
|
+
// Join the lines back together
|
|
223
|
+
const result = fixedLines.join('\n');
|
|
224
|
+
// Return the fixed text
|
|
225
|
+
return fixer.replaceText(sourceCode.ast, result);
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
},
|
|
52
230
|
};
|
|
53
231
|
},
|
|
54
232
|
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This rule enforces the use of CSS media queries instead of JavaScript-based breakpoints
|
|
3
|
+
* in React components for better performance and separation of concerns.
|
|
4
|
+
*/
|
|
5
|
+
export declare const enforceCssMediaQueries: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"enforceCssMediaQueries", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforceCssMediaQueries = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
/**
|
|
7
|
+
* This rule enforces the use of CSS media queries instead of JavaScript-based breakpoints
|
|
8
|
+
* in React components for better performance and separation of concerns.
|
|
9
|
+
*/
|
|
10
|
+
exports.enforceCssMediaQueries = (0, createRule_1.createRule)({
|
|
11
|
+
name: 'enforce-css-media-queries',
|
|
12
|
+
meta: {
|
|
13
|
+
type: 'suggestion',
|
|
14
|
+
docs: {
|
|
15
|
+
description: 'Enforce CSS media queries over JS breakpoints',
|
|
16
|
+
recommended: 'error',
|
|
17
|
+
},
|
|
18
|
+
messages: {
|
|
19
|
+
enforceCssMediaQueries: 'Use CSS media queries instead of JavaScript breakpoints for responsive design. JavaScript breakpoint handling can cause unnecessary re-renders and impact performance.',
|
|
20
|
+
},
|
|
21
|
+
schema: [],
|
|
22
|
+
},
|
|
23
|
+
defaultOptions: [],
|
|
24
|
+
create(context) {
|
|
25
|
+
return {
|
|
26
|
+
// Check for Material-UI's useMediaQuery and react-responsive imports
|
|
27
|
+
ImportDeclaration(node) {
|
|
28
|
+
// Check for @mui/material useMediaQuery
|
|
29
|
+
if (node.source.value === '@mui/material' &&
|
|
30
|
+
node.specifiers.some((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
31
|
+
specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
32
|
+
specifier.imported.name === 'useMediaQuery')) {
|
|
33
|
+
context.report({
|
|
34
|
+
node,
|
|
35
|
+
messageId: 'enforceCssMediaQueries',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
// Check for react-responsive imports
|
|
39
|
+
if (node.source.value === 'react-responsive' ||
|
|
40
|
+
node.source.value.includes('react-responsive/')) {
|
|
41
|
+
context.report({
|
|
42
|
+
node,
|
|
43
|
+
messageId: 'enforceCssMediaQueries',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// Check for useMobile import from hooks/useMobile
|
|
47
|
+
if (node.source.value.includes('hooks/useMobile') &&
|
|
48
|
+
node.specifiers.some((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
49
|
+
specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
50
|
+
specifier.imported.name === 'useMobile')) {
|
|
51
|
+
context.report({
|
|
52
|
+
node,
|
|
53
|
+
messageId: 'enforceCssMediaQueries',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
// Check for specific import specifiers
|
|
58
|
+
ImportSpecifier(node) {
|
|
59
|
+
if (node.parent &&
|
|
60
|
+
node.parent.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
|
|
61
|
+
node.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
62
|
+
(
|
|
63
|
+
// Check for useMediaQuery from @mui/material
|
|
64
|
+
(node.parent.source.value === '@mui/material' &&
|
|
65
|
+
node.imported.name === 'useMediaQuery') ||
|
|
66
|
+
// Check for useMobile from any source
|
|
67
|
+
node.imported.name === 'useMobile')) {
|
|
68
|
+
context.report({
|
|
69
|
+
node,
|
|
70
|
+
messageId: 'enforceCssMediaQueries',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
// Check for useMediaQuery and useMobile calls
|
|
75
|
+
CallExpression(node) {
|
|
76
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
77
|
+
(node.callee.name === 'useMediaQuery' || node.callee.name === 'useMobile')) {
|
|
78
|
+
context.report({
|
|
79
|
+
node,
|
|
80
|
+
messageId: 'enforceCssMediaQueries',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
//# sourceMappingURL=enforce-css-media-queries.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const RULE_NAME = "enforce-dynamic-imports";
|
|
2
|
+
type Options = [
|
|
3
|
+
{
|
|
4
|
+
libraries: string[];
|
|
5
|
+
allowImportType?: boolean;
|
|
6
|
+
}
|
|
7
|
+
];
|
|
8
|
+
declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"dynamicImportRequired", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
9
|
+
export default _default;
|