@blumintinc/eslint-plugin-blumint 1.7.3 → 1.8.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 +39 -62
- package/lib/index.js +37 -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-firestore-facade.js +8 -0
- package/lib/rules/enforce-microdiff.d.ts +3 -0
- package/lib/rules/enforce-microdiff.js +379 -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 +363 -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-render-hits-memoization.js +155 -26
- package/lib/rules/enforce-timestamp-now.d.ts +1 -0
- package/lib/rules/enforce-timestamp-now.js +223 -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/no-always-true-false-conditions.d.ts +3 -0
- package/lib/rules/no-always-true-false-conditions.js +1202 -0
- package/lib/rules/no-firestore-jest-mock.js +27 -4
- package/lib/rules/no-mock-firebase-admin.js +20 -7
- package/lib/rules/no-type-assertion-returns.d.ts +9 -0
- package/lib/rules/no-type-assertion-returns.js +289 -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/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-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 +256 -0
- package/package.json +5 -5
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforceMicrodiff = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.enforceMicrodiff = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'enforce-microdiff',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Enforce using microdiff for object and array comparison operations',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
enforceMicrodiff: 'Use the microdiff library for object and array comparison operations',
|
|
18
|
+
enforceMicrodiffImport: 'Import diff from microdiff instead of {{importSource}}',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultOptions: [],
|
|
22
|
+
create(context) {
|
|
23
|
+
const sourceCode = context.getSourceCode();
|
|
24
|
+
const importedDiffLibraries = new Map();
|
|
25
|
+
const importedFunctions = new Map(); // Map of imported function names to their sources
|
|
26
|
+
let hasMicrodiffImport = false;
|
|
27
|
+
const reportedNodes = new Set();
|
|
28
|
+
// Add a specific set to track which import names are used
|
|
29
|
+
const usedImportNames = new Set();
|
|
30
|
+
// Check if a node is an object or array type
|
|
31
|
+
function isObjectOrArrayType(node) {
|
|
32
|
+
if (node.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
|
|
33
|
+
node.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
// For identifiers, we'll make a simple assumption based on naming conventions
|
|
37
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
38
|
+
const name = node.name.toLowerCase();
|
|
39
|
+
// Names that likely represent objects or arrays
|
|
40
|
+
if (name.includes('obj') ||
|
|
41
|
+
name.includes('config') ||
|
|
42
|
+
name.includes('options') ||
|
|
43
|
+
name.includes('data') ||
|
|
44
|
+
name.includes('state') ||
|
|
45
|
+
name.includes('props') ||
|
|
46
|
+
name.includes('items') ||
|
|
47
|
+
name.includes('array') ||
|
|
48
|
+
name.includes('list') ||
|
|
49
|
+
name.endsWith('s')) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// For member expressions, assume they could be objects/arrays
|
|
54
|
+
if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
// Track imports of diffing libraries
|
|
61
|
+
ImportDeclaration(node) {
|
|
62
|
+
const importSource = node.source.value;
|
|
63
|
+
// Check for microdiff import
|
|
64
|
+
if (importSource === 'microdiff') {
|
|
65
|
+
hasMicrodiffImport = true;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Track other diffing libraries
|
|
69
|
+
if ([
|
|
70
|
+
'deep-diff',
|
|
71
|
+
'fast-diff',
|
|
72
|
+
'diff',
|
|
73
|
+
'deep-object-diff',
|
|
74
|
+
'fast-deep-equal',
|
|
75
|
+
'fast-deep-equal/es6',
|
|
76
|
+
].includes(importSource)) {
|
|
77
|
+
// Track imported function names and their sources
|
|
78
|
+
node.specifiers.forEach((specifier) => {
|
|
79
|
+
if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
80
|
+
(specifier.imported.name === 'diff' ||
|
|
81
|
+
specifier.imported.name === 'diffArrays' ||
|
|
82
|
+
specifier.imported.name === 'detailedDiff')) {
|
|
83
|
+
// Track the local name (which could be different due to renaming)
|
|
84
|
+
const localName = specifier.local.name;
|
|
85
|
+
importedFunctions.set(localName, importSource);
|
|
86
|
+
}
|
|
87
|
+
else if (importSource === 'fast-deep-equal' ||
|
|
88
|
+
importSource === 'fast-deep-equal/es6') {
|
|
89
|
+
// Handle default imports for fast-deep-equal
|
|
90
|
+
if (specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier) {
|
|
91
|
+
importedFunctions.set(specifier.local.name, importSource);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
// Report all competing diffing libraries right away
|
|
96
|
+
context.report({
|
|
97
|
+
node,
|
|
98
|
+
messageId: 'enforceMicrodiffImport',
|
|
99
|
+
data: {
|
|
100
|
+
importSource,
|
|
101
|
+
},
|
|
102
|
+
fix(fixer) {
|
|
103
|
+
// If we already have a microdiff import, just remove this import
|
|
104
|
+
if (hasMicrodiffImport) {
|
|
105
|
+
return fixer.remove(node);
|
|
106
|
+
}
|
|
107
|
+
// Otherwise, replace with microdiff import
|
|
108
|
+
return fixer.replaceText(node, `import { diff } from 'microdiff';`);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
// Check if importing a diff function or a known equality library
|
|
112
|
+
const hasDiffImport = node.specifiers.some((specifier) => {
|
|
113
|
+
if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier) {
|
|
114
|
+
return (specifier.imported.name === 'diff' ||
|
|
115
|
+
specifier.imported.name === 'diffArrays' ||
|
|
116
|
+
specifier.imported.name === 'detailedDiff');
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
});
|
|
120
|
+
if (hasDiffImport ||
|
|
121
|
+
importSource === 'fast-deep-equal' ||
|
|
122
|
+
importSource === 'fast-deep-equal/es6') {
|
|
123
|
+
importedDiffLibraries.set(importSource, node);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
// Check for usage of other diffing libraries
|
|
128
|
+
CallExpression(node) {
|
|
129
|
+
// Skip if we've already reported this node
|
|
130
|
+
if (reportedNodes.has(node)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const { callee } = node;
|
|
134
|
+
// Check for direct calls to imported diff functions
|
|
135
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
136
|
+
const name = callee.name;
|
|
137
|
+
// Check if this is a function we specifically imported from a diff library
|
|
138
|
+
if (importedFunctions.has(name)) {
|
|
139
|
+
usedImportNames.add(name);
|
|
140
|
+
// Always report it if it's from a tracked library
|
|
141
|
+
reportedNodes.add(node);
|
|
142
|
+
context.report({
|
|
143
|
+
node,
|
|
144
|
+
messageId: 'enforceMicrodiff',
|
|
145
|
+
fix(fixer) {
|
|
146
|
+
return fixer.replaceText(callee, 'diff');
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Check known diff function names
|
|
152
|
+
const isDiffFunction = [
|
|
153
|
+
'deepDiff',
|
|
154
|
+
'fastDiff',
|
|
155
|
+
'diffArrays',
|
|
156
|
+
'detailedDiff',
|
|
157
|
+
'fastDeepEqual',
|
|
158
|
+
'isEqual',
|
|
159
|
+
].includes(name);
|
|
160
|
+
if (isDiffFunction) {
|
|
161
|
+
// Track this import name as used
|
|
162
|
+
usedImportNames.add(name);
|
|
163
|
+
// Check if we have at least 2 arguments that are objects or arrays
|
|
164
|
+
if (node.arguments.length >= 2 &&
|
|
165
|
+
isObjectOrArrayType(node.arguments[0]) &&
|
|
166
|
+
isObjectOrArrayType(node.arguments[1])) {
|
|
167
|
+
reportedNodes.add(node);
|
|
168
|
+
context.report({
|
|
169
|
+
node,
|
|
170
|
+
messageId: 'enforceMicrodiff',
|
|
171
|
+
fix(fixer) {
|
|
172
|
+
// When handling fast-diff and similar libraries, need to ensure the function name is replaced
|
|
173
|
+
return fixer.replaceText(callee, 'diff');
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Check for lodash difference functions
|
|
180
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
181
|
+
callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
182
|
+
callee.object.name === '_') {
|
|
183
|
+
const property = callee.property;
|
|
184
|
+
if (property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
185
|
+
['difference', 'differenceBy', 'differenceWith'].includes(property.name)) {
|
|
186
|
+
reportedNodes.add(node);
|
|
187
|
+
context.report({
|
|
188
|
+
node,
|
|
189
|
+
messageId: 'enforceMicrodiff',
|
|
190
|
+
fix(fixer) {
|
|
191
|
+
// Replace with microdiff
|
|
192
|
+
return fixer.replaceText(node, `diff(${node.arguments
|
|
193
|
+
.map((arg) => sourceCode.getText(arg))
|
|
194
|
+
.join(', ')})`);
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
// Check for manual object comparison patterns
|
|
201
|
+
BinaryExpression(node) {
|
|
202
|
+
// Skip if we've already reported this node or its parent function
|
|
203
|
+
if (reportedNodes.has(node)) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// Find the parent function or method
|
|
207
|
+
let current = node;
|
|
208
|
+
while (current &&
|
|
209
|
+
current.type !== utils_1.AST_NODE_TYPES.FunctionDeclaration &&
|
|
210
|
+
current.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
211
|
+
current = current.parent;
|
|
212
|
+
}
|
|
213
|
+
// If we already reported the parent function, skip this node
|
|
214
|
+
if (current && reportedNodes.has(current)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// Check for JSON.stringify comparison pattern
|
|
218
|
+
if ((node.operator === '===' || node.operator === '!==') &&
|
|
219
|
+
node.left.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
220
|
+
node.right.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
221
|
+
const isJsonStringify = (expr) => {
|
|
222
|
+
return (expr.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
223
|
+
expr.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
224
|
+
expr.callee.object.name === 'JSON' &&
|
|
225
|
+
expr.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
226
|
+
expr.callee.property.name === 'stringify');
|
|
227
|
+
};
|
|
228
|
+
if (isJsonStringify(node.left) && isJsonStringify(node.right)) {
|
|
229
|
+
const leftArg = node.left.arguments[0];
|
|
230
|
+
const rightArg = node.right.arguments[0];
|
|
231
|
+
if (isObjectOrArrayType(leftArg) && isObjectOrArrayType(rightArg)) {
|
|
232
|
+
reportedNodes.add(node);
|
|
233
|
+
const isEqual = node.operator === '===';
|
|
234
|
+
context.report({
|
|
235
|
+
node,
|
|
236
|
+
messageId: 'enforceMicrodiff',
|
|
237
|
+
fix(fixer) {
|
|
238
|
+
// Find the containing function to add the import
|
|
239
|
+
let functionNode = node;
|
|
240
|
+
while (functionNode &&
|
|
241
|
+
functionNode.type !== utils_1.AST_NODE_TYPES.FunctionDeclaration &&
|
|
242
|
+
functionNode.type !==
|
|
243
|
+
utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
244
|
+
functionNode.type !== utils_1.AST_NODE_TYPES.Program) {
|
|
245
|
+
functionNode = functionNode.parent;
|
|
246
|
+
}
|
|
247
|
+
// If we found a program node and microdiff isn't imported,
|
|
248
|
+
// we'll need to add the import manually
|
|
249
|
+
if (functionNode &&
|
|
250
|
+
functionNode.type === utils_1.AST_NODE_TYPES.Program &&
|
|
251
|
+
!hasMicrodiffImport) {
|
|
252
|
+
// Need to add an import
|
|
253
|
+
const importFix = fixer.insertTextBeforeRange([0, 0], "import { diff } from 'microdiff';\n\n");
|
|
254
|
+
// Replace JSON.stringify comparison
|
|
255
|
+
const compareFix = fixer.replaceText(node, `diff(${sourceCode.getText(leftArg)}, ${sourceCode.getText(rightArg)})${isEqual ? '.length === 0' : '.length > 0'}`);
|
|
256
|
+
return [importFix, compareFix];
|
|
257
|
+
}
|
|
258
|
+
// Otherwise just replace the comparison
|
|
259
|
+
return fixer.replaceText(node, `diff(${sourceCode.getText(leftArg)}, ${sourceCode.getText(rightArg)})${isEqual ? '.length === 0' : '.length > 0'}`);
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
// Check for custom deep comparison functions
|
|
267
|
+
FunctionDeclaration(node) {
|
|
268
|
+
// Skip if we've already reported this node
|
|
269
|
+
if (reportedNodes.has(node)) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
// Look for functions that might be implementing diff logic
|
|
273
|
+
if (node.id &&
|
|
274
|
+
[
|
|
275
|
+
'detectChanges',
|
|
276
|
+
'hasConfigChanged',
|
|
277
|
+
'compareObjects',
|
|
278
|
+
'compareArrays',
|
|
279
|
+
'findChanges',
|
|
280
|
+
'detectDifferences',
|
|
281
|
+
'hasStateChanged',
|
|
282
|
+
'stateHasUpdated',
|
|
283
|
+
'arrayHasChanged',
|
|
284
|
+
'settingsChanged',
|
|
285
|
+
].includes(node.id.name)) {
|
|
286
|
+
// Check if function has two parameters that might be objects/arrays
|
|
287
|
+
if (node.params.length >= 2) {
|
|
288
|
+
const body = node.body;
|
|
289
|
+
const bodyText = sourceCode.getText(body);
|
|
290
|
+
// Check if the function body contains a JSON.stringify comparison
|
|
291
|
+
if (node.id.name === 'hasConfigChanged' &&
|
|
292
|
+
bodyText.includes('JSON.stringify') &&
|
|
293
|
+
bodyText.includes('!==')) {
|
|
294
|
+
reportedNodes.add(node);
|
|
295
|
+
const param1 = sourceCode.getText(node.params[0]);
|
|
296
|
+
const param2 = sourceCode.getText(node.params[1]);
|
|
297
|
+
context.report({
|
|
298
|
+
node,
|
|
299
|
+
messageId: 'enforceMicrodiff',
|
|
300
|
+
fix(fixer) {
|
|
301
|
+
// Create a new version of the function with microdiff
|
|
302
|
+
const newFunctionBody = `{
|
|
303
|
+
return diff(${param1}, ${param2}).length > 0;
|
|
304
|
+
}`;
|
|
305
|
+
if (!hasMicrodiffImport) {
|
|
306
|
+
// Create a new import statement
|
|
307
|
+
return fixer.replaceText(node, `import { diff } from 'microdiff';\n\nfunction ${node.id?.name}(${param1}, ${param2}) ${newFunctionBody}`);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
// Just replace the function body
|
|
311
|
+
return fixer.replaceText(body, newFunctionBody);
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
// Look for patterns that suggest object/array comparison
|
|
318
|
+
const hasComparisonLogic = bodyText.includes('JSON.stringify') ||
|
|
319
|
+
bodyText.includes('Object.keys') ||
|
|
320
|
+
bodyText.includes('for (') ||
|
|
321
|
+
bodyText.includes('.some(') ||
|
|
322
|
+
bodyText.includes('.every(');
|
|
323
|
+
if (hasComparisonLogic) {
|
|
324
|
+
reportedNodes.add(node);
|
|
325
|
+
context.report({
|
|
326
|
+
node,
|
|
327
|
+
messageId: 'enforceMicrodiff',
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
// Check for custom deep comparison in arrow functions
|
|
334
|
+
ArrowFunctionExpression(node) {
|
|
335
|
+
// Skip if we've already reported this node
|
|
336
|
+
if (reportedNodes.has(node)) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
// Only check arrow functions assigned to variables with comparison-like names
|
|
340
|
+
const parent = node.parent;
|
|
341
|
+
if (parent &&
|
|
342
|
+
parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
343
|
+
parent.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
344
|
+
[
|
|
345
|
+
'detectChanges',
|
|
346
|
+
'hasConfigChanged',
|
|
347
|
+
'compareObjects',
|
|
348
|
+
'compareArrays',
|
|
349
|
+
'findChanges',
|
|
350
|
+
'detectDifferences',
|
|
351
|
+
'hasStateChanged',
|
|
352
|
+
'stateHasUpdated',
|
|
353
|
+
'arrayHasChanged',
|
|
354
|
+
'settingsChanged',
|
|
355
|
+
].includes(parent.id.name)) {
|
|
356
|
+
// Check if function has two parameters that might be objects/arrays
|
|
357
|
+
if (node.params.length >= 2) {
|
|
358
|
+
const body = node.body;
|
|
359
|
+
// Look for patterns that suggest object/array comparison
|
|
360
|
+
const bodyText = sourceCode.getText(body);
|
|
361
|
+
const hasComparisonLogic = bodyText.includes('JSON.stringify') ||
|
|
362
|
+
bodyText.includes('Object.keys') ||
|
|
363
|
+
bodyText.includes('for (') ||
|
|
364
|
+
bodyText.includes('.some(') ||
|
|
365
|
+
bodyText.includes('.every(');
|
|
366
|
+
if (hasComparisonLogic) {
|
|
367
|
+
reportedNodes.add(node);
|
|
368
|
+
context.report({
|
|
369
|
+
node,
|
|
370
|
+
messageId: 'enforceMicrodiff',
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
//# sourceMappingURL=enforce-microdiff.js.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforceObjectLiteralAsConst = void 0;
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
exports.enforceObjectLiteralAsConst = (0, createRule_1.createRule)({
|
|
6
|
+
name: 'enforce-object-literal-as-const',
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'suggestion',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Enforce that object literals returned from functions should be marked with `as const` to ensure type safety and immutability.',
|
|
11
|
+
recommended: 'error',
|
|
12
|
+
},
|
|
13
|
+
fixable: 'code',
|
|
14
|
+
messages: {
|
|
15
|
+
enforceAsConst: 'Object literals returned from functions should be marked with `as const` to ensure type safety and immutability',
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
},
|
|
19
|
+
defaultOptions: [],
|
|
20
|
+
create(context) {
|
|
21
|
+
return {
|
|
22
|
+
ReturnStatement(node) {
|
|
23
|
+
// Skip if there's no argument in the return statement
|
|
24
|
+
if (!node.argument) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Check if the return statement is inside a function
|
|
28
|
+
const sourceCode = context.getSourceCode();
|
|
29
|
+
// Use context.getAncestors() for now, but note it's deprecated
|
|
30
|
+
// We'll need to update this when upgrading to ESLint v9
|
|
31
|
+
const ancestors = context.getAncestors();
|
|
32
|
+
const isInFunction = ancestors.some((ancestor) => ancestor.type === 'FunctionDeclaration' ||
|
|
33
|
+
ancestor.type === 'ArrowFunctionExpression' ||
|
|
34
|
+
ancestor.type === 'FunctionExpression' ||
|
|
35
|
+
ancestor.type === 'MethodDefinition');
|
|
36
|
+
if (!isInFunction) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Check if the return value is an object or array literal
|
|
40
|
+
const { argument } = node;
|
|
41
|
+
// Skip if the return value already has 'as const' assertion
|
|
42
|
+
if (argument.type === 'TSAsExpression') {
|
|
43
|
+
const tsAsExpression = argument;
|
|
44
|
+
// Check if the type annotation is 'const'
|
|
45
|
+
if (tsAsExpression.typeAnnotation.type === 'TSTypeReference' &&
|
|
46
|
+
tsAsExpression.typeAnnotation.typeName.type === 'Identifier' &&
|
|
47
|
+
tsAsExpression.typeAnnotation.typeName.name === 'const') {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// If it has another type assertion but not 'as const', we still need to check
|
|
51
|
+
// if the expression is an object/array literal
|
|
52
|
+
if (tsAsExpression.expression.type !== 'ObjectExpression' &&
|
|
53
|
+
tsAsExpression.expression.type !== 'ArrayExpression') {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (argument.type !== 'ObjectExpression' &&
|
|
58
|
+
argument.type !== 'ArrayExpression') {
|
|
59
|
+
// Skip if not an object/array literal and not a type assertion
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Skip if the return value uses spread operator
|
|
63
|
+
if ((argument.type === 'ObjectExpression' &&
|
|
64
|
+
argument.properties.some((prop) => prop.type === 'SpreadElement')) ||
|
|
65
|
+
(argument.type === 'ArrayExpression' &&
|
|
66
|
+
argument.elements.some((elem) => elem !== null && elem.type === 'SpreadElement'))) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Report the issue and provide a fix
|
|
70
|
+
context.report({
|
|
71
|
+
node,
|
|
72
|
+
messageId: 'enforceAsConst',
|
|
73
|
+
fix(fixer) {
|
|
74
|
+
const text = sourceCode.getText(argument);
|
|
75
|
+
// If it's already a type assertion but not 'as const'
|
|
76
|
+
if (argument.type === 'TSAsExpression') {
|
|
77
|
+
// Get the expression part (before the 'as')
|
|
78
|
+
const expressionText = sourceCode.getText(argument.expression);
|
|
79
|
+
return fixer.replaceText(argument, `${expressionText} as const`);
|
|
80
|
+
}
|
|
81
|
+
return fixer.replaceText(argument, `${text} as const`);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=enforce-object-literal-as-const.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const enforcePositiveNaming: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"avoidNegativeNaming", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|