@blumintinc/eslint-plugin-blumint 1.9.1 → 1.11.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 +99 -83
- package/lib/index.js +16 -1
- package/lib/rules/class-methods-read-top-to-bottom.js +10 -0
- package/lib/rules/consistent-callback-naming.js +20 -16
- package/lib/rules/enforce-assert-throws.js +10 -5
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +7 -0
- package/lib/rules/enforce-boolean-naming-prefixes.js +462 -0
- package/lib/rules/enforce-callable-types.d.ts +1 -1
- package/lib/rules/enforce-callable-types.js +13 -13
- package/lib/rules/enforce-callback-memo.js +6 -3
- package/lib/rules/enforce-centralized-mock-firestore.js +8 -6
- package/lib/rules/enforce-css-media-queries.js +3 -3
- package/lib/rules/enforce-dynamic-file-naming.d.ts +3 -0
- package/lib/rules/enforce-dynamic-file-naming.js +82 -0
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- package/lib/rules/enforce-firestore-doc-ref-generic.js +16 -12
- package/lib/rules/enforce-firestore-facade.js +7 -3
- package/lib/rules/enforce-firestore-path-utils.js +5 -2
- package/lib/rules/enforce-id-capitalization.js +59 -0
- package/lib/rules/enforce-identifiable-firestore-type.js +17 -10
- package/lib/rules/enforce-memoize-async.js +2 -2
- package/lib/rules/enforce-positive-naming.js +71 -27
- package/lib/rules/enforce-props-argument-name.js +31 -9
- package/lib/rules/enforce-realtimedb-path-utils.js +2 -1
- package/lib/rules/enforce-singular-type-names.js +4 -1
- package/lib/rules/enforce-timestamp-now.js +3 -4
- package/lib/rules/enforce-verb-noun-naming.js +18 -8
- package/lib/rules/ensure-pointer-events-none.js +20 -11
- package/lib/rules/extract-global-constants.js +25 -10
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +3 -0
- package/lib/rules/fast-deep-equal-over-microdiff.js +182 -0
- package/lib/rules/key-only-outermost-element.js +13 -7
- package/lib/rules/no-always-true-false-conditions.js +189 -5
- package/lib/rules/no-async-array-filter.js +1 -1
- package/lib/rules/no-circular-references.js +121 -59
- package/lib/rules/no-complex-cloud-params.js +4 -2
- package/lib/rules/no-entire-object-hook-deps.js +113 -29
- package/lib/rules/no-explicit-return-type.js +3 -2
- package/lib/rules/no-firestore-jest-mock.js +1 -1
- package/lib/rules/no-firestore-object-arrays.js +2 -1
- package/lib/rules/no-hungarian.js +18 -1
- package/lib/rules/no-jsx-in-hooks.js +4 -3
- package/lib/rules/no-margin-properties.d.ts +1 -0
- package/lib/rules/no-margin-properties.js +314 -0
- package/lib/rules/no-mixed-firestore-transactions.js +8 -4
- package/lib/rules/no-mock-firebase-admin.js +6 -1
- package/lib/rules/no-object-values-on-strings.js +28 -10
- package/lib/rules/no-type-assertion-returns.js +47 -6
- package/lib/rules/no-unnecessary-destructuring.js +1 -1
- package/lib/rules/no-unnecessary-verb-suffix.js +0 -31
- package/lib/rules/no-unused-props.js +10 -7
- package/lib/rules/no-unused-usestate.js +19 -6
- package/lib/rules/omit-index-html.js +1 -1
- package/lib/rules/prefer-batch-operations.js +19 -6
- package/lib/rules/prefer-clone-deep.js +16 -11
- package/lib/rules/prefer-destructuring-no-class.js +19 -0
- package/lib/rules/prefer-fragment-component.js +19 -16
- package/lib/rules/prefer-global-router-state-key.js +6 -4
- package/lib/rules/prefer-settings-object.js +1 -1
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.d.ts +8 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +180 -0
- package/lib/rules/react-usememo-should-be-component.js +139 -17
- package/lib/rules/require-hooks-default-params.js +25 -15
- package/lib/rules/require-usememo-object-literals.js +3 -2
- package/lib/rules/semantic-function-prefixes.js +15 -3
- package/lib/rules/sync-onwrite-name-func.js +5 -3
- package/lib/utils/ASTHelpers.js +6 -2
- package/lib/utils/graph/ClassGraphBuilder.js +4 -1
- package/lib/utils/graph/ClassGraphSorterReadability.js +3 -1
- package/package.json +3 -3
- package/lib/rules/require-image-overlayed.d.ts +0 -7
- package/lib/rules/require-image-overlayed.js +0 -82
|
@@ -8,14 +8,14 @@ exports.enforceCallableTypes = (0, createRule_1.createRule)({
|
|
|
8
8
|
meta: {
|
|
9
9
|
type: 'problem',
|
|
10
10
|
docs: {
|
|
11
|
-
description: 'Enforce
|
|
11
|
+
description: 'Enforce Props and Response type exports in callable functions',
|
|
12
12
|
recommended: 'error',
|
|
13
13
|
},
|
|
14
14
|
schema: [],
|
|
15
15
|
messages: {
|
|
16
|
-
|
|
16
|
+
missingPropsType: 'Missing Props type export in callable function file',
|
|
17
17
|
missingResponseType: 'Missing Response type export in callable function file',
|
|
18
|
-
|
|
18
|
+
unusedPropsType: 'Props type is exported but not used in the callable function',
|
|
19
19
|
unusedResponseType: 'Response type is exported but not used in the callable function',
|
|
20
20
|
},
|
|
21
21
|
},
|
|
@@ -28,18 +28,18 @@ exports.enforceCallableTypes = (0, createRule_1.createRule)({
|
|
|
28
28
|
filename.includes('/callable/scripts/')) {
|
|
29
29
|
return {};
|
|
30
30
|
}
|
|
31
|
-
let
|
|
31
|
+
let hasPropsExport = false;
|
|
32
32
|
let hasResponseExport = false;
|
|
33
33
|
let hasCallableFunction = false;
|
|
34
|
-
let
|
|
34
|
+
let propsTypeUsed = false;
|
|
35
35
|
let responseTypeUsed = false;
|
|
36
36
|
return {
|
|
37
37
|
// Check for type exports
|
|
38
38
|
ExportNamedDeclaration(node) {
|
|
39
39
|
if (node.declaration?.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
40
40
|
const typeName = node.declaration.id.name;
|
|
41
|
-
if (typeName === '
|
|
42
|
-
|
|
41
|
+
if (typeName === 'Props') {
|
|
42
|
+
hasPropsExport = true;
|
|
43
43
|
}
|
|
44
44
|
else if (typeName === 'Response') {
|
|
45
45
|
hasResponseExport = true;
|
|
@@ -56,8 +56,8 @@ exports.enforceCallableTypes = (0, createRule_1.createRule)({
|
|
|
56
56
|
// Check for type usage in function parameters and return types
|
|
57
57
|
TSTypeReference(node) {
|
|
58
58
|
if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
59
|
-
if (node.typeName.name === '
|
|
60
|
-
|
|
59
|
+
if (node.typeName.name === 'Props') {
|
|
60
|
+
propsTypeUsed = true;
|
|
61
61
|
}
|
|
62
62
|
else if (node.typeName.name === 'Response') {
|
|
63
63
|
responseTypeUsed = true;
|
|
@@ -68,16 +68,16 @@ exports.enforceCallableTypes = (0, createRule_1.createRule)({
|
|
|
68
68
|
if (!hasCallableFunction) {
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
|
-
if (!
|
|
71
|
+
if (!hasPropsExport) {
|
|
72
72
|
context.report({
|
|
73
73
|
loc: { line: 1, column: 0 },
|
|
74
|
-
messageId: '
|
|
74
|
+
messageId: 'missingPropsType',
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
|
-
else if (!
|
|
77
|
+
else if (!propsTypeUsed) {
|
|
78
78
|
context.report({
|
|
79
79
|
loc: { line: 1, column: 0 },
|
|
80
|
-
messageId: '
|
|
80
|
+
messageId: 'unusedPropsType',
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
if (!hasResponseExport) {
|
|
@@ -43,8 +43,10 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
43
43
|
function hasJSXWithFunction(node) {
|
|
44
44
|
if (node.type === utils_1.TSESTree.AST_NODE_TYPES.JSXElement) {
|
|
45
45
|
return node.openingElement.attributes.some((attr) => {
|
|
46
|
-
if (attr.type === utils_1.TSESTree.AST_NODE_TYPES.JSXAttribute &&
|
|
47
|
-
|
|
46
|
+
if (attr.type === utils_1.TSESTree.AST_NODE_TYPES.JSXAttribute &&
|
|
47
|
+
attr.value) {
|
|
48
|
+
if (attr.value.type ===
|
|
49
|
+
utils_1.TSESTree.AST_NODE_TYPES.JSXExpressionContainer) {
|
|
48
50
|
return containsFunction(attr.value.expression);
|
|
49
51
|
}
|
|
50
52
|
}
|
|
@@ -80,7 +82,8 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
80
82
|
expression.type === utils_1.TSESTree.AST_NODE_TYPES.JSXElement) &&
|
|
81
83
|
(containsFunction(expression) || hasJSXWithFunction(expression))) {
|
|
82
84
|
// Skip reporting if this is a JSX element and we already reported an inline function
|
|
83
|
-
if (expression.type === utils_1.TSESTree.AST_NODE_TYPES.JSXElement &&
|
|
85
|
+
if (expression.type === utils_1.TSESTree.AST_NODE_TYPES.JSXElement &&
|
|
86
|
+
hasJSXWithFunction(expression)) {
|
|
84
87
|
return;
|
|
85
88
|
}
|
|
86
89
|
context.report({
|
|
@@ -157,7 +157,7 @@ exports.enforceCentralizedMockFirestore = (0, createRule_1.createRule)({
|
|
|
157
157
|
// Find all the lines that need to be removed
|
|
158
158
|
const linesToRemove = new Set();
|
|
159
159
|
// Process all nodes that need to be removed
|
|
160
|
-
mockFirestoreNodes.forEach(node => {
|
|
160
|
+
mockFirestoreNodes.forEach((node) => {
|
|
161
161
|
const startLine = sourceCode.getLocFromIndex(node.range[0]).line - 1;
|
|
162
162
|
const endLine = sourceCode.getLocFromIndex(node.range[1]).line - 1;
|
|
163
163
|
if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
@@ -186,21 +186,21 @@ exports.enforceCentralizedMockFirestore = (0, createRule_1.createRule)({
|
|
|
186
186
|
// Replace custom mockFirestore references with the standard one
|
|
187
187
|
const replacements = [];
|
|
188
188
|
// Add replacements for custom mockFirestore names
|
|
189
|
-
customMockFirestoreCallExpressions.forEach(node => {
|
|
189
|
+
customMockFirestoreCallExpressions.forEach((node) => {
|
|
190
190
|
if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
191
191
|
replacements.push([
|
|
192
192
|
node.callee.range[0],
|
|
193
193
|
node.callee.name,
|
|
194
|
-
'mockFirestore'
|
|
194
|
+
'mockFirestore',
|
|
195
195
|
]);
|
|
196
196
|
}
|
|
197
197
|
});
|
|
198
198
|
// Add replacements for this.mockFirestore
|
|
199
|
-
thisExpressions.forEach(expr => {
|
|
199
|
+
thisExpressions.forEach((expr) => {
|
|
200
200
|
replacements.push([
|
|
201
201
|
expr.range[0],
|
|
202
202
|
sourceCode.getText(expr),
|
|
203
|
-
'mockFirestore'
|
|
203
|
+
'mockFirestore',
|
|
204
204
|
]);
|
|
205
205
|
});
|
|
206
206
|
// Sort replacements in reverse order to avoid range issues
|
|
@@ -214,7 +214,9 @@ exports.enforceCentralizedMockFirestore = (0, createRule_1.createRule)({
|
|
|
214
214
|
fixedText.substring(pos + oldText.length);
|
|
215
215
|
}
|
|
216
216
|
// Filter out the lines to remove
|
|
217
|
-
const fixedLines = fixedText
|
|
217
|
+
const fixedLines = fixedText
|
|
218
|
+
.split('\n')
|
|
219
|
+
.filter((_, i) => !linesToRemove.has(i));
|
|
218
220
|
// Add the import statement at the beginning
|
|
219
221
|
if (!hasCentralizedImport) {
|
|
220
222
|
fixedLines.unshift(importLine);
|
|
@@ -59,9 +59,8 @@ exports.enforceCssMediaQueries = (0, createRule_1.createRule)({
|
|
|
59
59
|
if (node.parent &&
|
|
60
60
|
node.parent.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
|
|
61
61
|
node.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
62
|
-
(
|
|
63
62
|
// Check for useMediaQuery from @mui/material
|
|
64
|
-
(node.parent.source.value === '@mui/material' &&
|
|
63
|
+
((node.parent.source.value === '@mui/material' &&
|
|
65
64
|
node.imported.name === 'useMediaQuery') ||
|
|
66
65
|
// Check for useMobile from any source
|
|
67
66
|
node.imported.name === 'useMobile')) {
|
|
@@ -74,7 +73,8 @@ exports.enforceCssMediaQueries = (0, createRule_1.createRule)({
|
|
|
74
73
|
// Check for useMediaQuery and useMobile calls
|
|
75
74
|
CallExpression(node) {
|
|
76
75
|
if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
77
|
-
(node.callee.name === 'useMediaQuery' ||
|
|
76
|
+
(node.callee.name === 'useMediaQuery' ||
|
|
77
|
+
node.callee.name === 'useMobile')) {
|
|
78
78
|
context.report({
|
|
79
79
|
node,
|
|
80
80
|
messageId: 'enforceCssMediaQueries',
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const RULE_NAME = "enforce-dynamic-file-naming";
|
|
2
|
+
declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"requireDynamicExtension" | "requireDisableDirective", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RULE_NAME = void 0;
|
|
7
|
+
const createRule_1 = require("../utils/createRule");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
exports.RULE_NAME = 'enforce-dynamic-file-naming';
|
|
10
|
+
exports.default = (0, createRule_1.createRule)({
|
|
11
|
+
name: exports.RULE_NAME,
|
|
12
|
+
meta: {
|
|
13
|
+
type: 'suggestion',
|
|
14
|
+
docs: {
|
|
15
|
+
description: 'Enforce .dynamic.ts(x) file naming when @blumintinc/blumint/enforce-dynamic-imports or @blumintinc/blumint/require-dynamic-firebase-imports rule is disabled',
|
|
16
|
+
recommended: 'error',
|
|
17
|
+
},
|
|
18
|
+
schema: [],
|
|
19
|
+
messages: {
|
|
20
|
+
requireDynamicExtension: 'Files with disabled @blumintinc/blumint/enforce-dynamic-imports or @blumintinc/blumint/require-dynamic-firebase-imports rule must use .dynamic.ts(x) extension',
|
|
21
|
+
requireDisableDirective: 'Files with .dynamic.ts(x) extension must have at least one @blumintinc/blumint/enforce-dynamic-imports or @blumintinc/blumint/require-dynamic-firebase-imports disable directive',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultOptions: [],
|
|
25
|
+
create(context) {
|
|
26
|
+
// Get the file path and name
|
|
27
|
+
const filePath = context.getFilename();
|
|
28
|
+
const fileName = path_1.default.basename(filePath);
|
|
29
|
+
// Check if the file is a TypeScript file (ends with .ts or .tsx)
|
|
30
|
+
// Ignore files with other extensions like .test.ts, .deprecated.ts, etc.
|
|
31
|
+
const isTypeScriptFile = /^[^.]+\.tsx?$/.test(fileName);
|
|
32
|
+
// Check if the file has .dynamic.ts or .dynamic.tsx extension
|
|
33
|
+
const hasDynamicExtension = /\.dynamic\.tsx?$/.test(fileName);
|
|
34
|
+
// Skip if not a TypeScript file or has other extensions
|
|
35
|
+
if (!isTypeScriptFile && !hasDynamicExtension) {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
// Track if we found a disable directive for enforce-dynamic-imports
|
|
39
|
+
let foundDisableDirective = false;
|
|
40
|
+
return {
|
|
41
|
+
Program() {
|
|
42
|
+
// Get the source code
|
|
43
|
+
const sourceCode = context.getSourceCode();
|
|
44
|
+
const comments = sourceCode.getAllComments();
|
|
45
|
+
// Check all comments for disable directives
|
|
46
|
+
for (const comment of comments) {
|
|
47
|
+
const commentText = comment.value.trim();
|
|
48
|
+
// Check for inline disable directive for either rule
|
|
49
|
+
if ((commentText.includes('eslint-disable-next-line') ||
|
|
50
|
+
commentText.includes('ednl')) &&
|
|
51
|
+
(commentText.includes('@blumintinc/blumint/enforce-dynamic-imports') ||
|
|
52
|
+
commentText.includes('@blumintinc/blumint/require-dynamic-firebase-imports'))) {
|
|
53
|
+
foundDisableDirective = true;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
// Check for block disable directive for either rule
|
|
57
|
+
if (commentText.includes('eslint-disable ') &&
|
|
58
|
+
(commentText.includes('@blumintinc/blumint/enforce-dynamic-imports') ||
|
|
59
|
+
commentText.includes('@blumintinc/blumint/require-dynamic-firebase-imports'))) {
|
|
60
|
+
foundDisableDirective = true;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// If we found a disable directive but the file doesn't have .dynamic extension
|
|
65
|
+
if (foundDisableDirective && !hasDynamicExtension) {
|
|
66
|
+
context.report({
|
|
67
|
+
loc: { line: 1, column: 0 },
|
|
68
|
+
messageId: 'requireDynamicExtension',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// If the file has .dynamic extension but no disable directive
|
|
72
|
+
if (hasDynamicExtension && !foundDisableDirective) {
|
|
73
|
+
context.report({
|
|
74
|
+
loc: { line: 1, column: 0 },
|
|
75
|
+
messageId: 'requireDisableDirective',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
//# sourceMappingURL=enforce-dynamic-file-naming.js.map
|
|
@@ -63,7 +63,7 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
63
63
|
}
|
|
64
64
|
// Check if all specifiers are type imports
|
|
65
65
|
if (node.specifiers.length > 0 &&
|
|
66
|
-
node.specifiers.every(spec =>
|
|
66
|
+
node.specifiers.every((spec) => spec.type === 'ImportSpecifier' && spec.importKind === 'type')) {
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -119,19 +119,22 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
|
|
|
119
119
|
return true;
|
|
120
120
|
}
|
|
121
121
|
// Variable declarations with type annotations
|
|
122
|
-
if (current.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
122
|
+
if (current.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
123
|
+
current.id.typeAnnotation) {
|
|
123
124
|
nodeCache.set(node, true);
|
|
124
125
|
return true;
|
|
125
126
|
}
|
|
126
127
|
// Class property definitions with type annotations
|
|
127
|
-
if (current.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
|
|
128
|
+
if (current.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
|
|
129
|
+
current.typeAnnotation) {
|
|
128
130
|
nodeCache.set(node, true);
|
|
129
131
|
return true;
|
|
130
132
|
}
|
|
131
133
|
// Return statements in functions with return type annotations
|
|
132
134
|
if (current.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
|
|
133
135
|
const func = current.parent?.parent;
|
|
134
|
-
if (func?.type === utils_1.AST_NODE_TYPES.FunctionDeclaration &&
|
|
136
|
+
if (func?.type === utils_1.AST_NODE_TYPES.FunctionDeclaration &&
|
|
137
|
+
func.returnType) {
|
|
135
138
|
nodeCache.set(node, true);
|
|
136
139
|
return true;
|
|
137
140
|
}
|
|
@@ -146,7 +149,8 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
|
|
|
146
149
|
if (classNode) {
|
|
147
150
|
const property = classNode.body.body.find((member) => member.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
|
|
148
151
|
member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
149
|
-
member.key.name ===
|
|
152
|
+
member.key.name ===
|
|
153
|
+
left.property.name);
|
|
150
154
|
if (property?.typeAnnotation) {
|
|
151
155
|
nodeCache.set(node, true);
|
|
152
156
|
return true;
|
|
@@ -202,7 +206,7 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
|
|
|
202
206
|
context.report({
|
|
203
207
|
node,
|
|
204
208
|
messageId: 'missingGeneric',
|
|
205
|
-
data: { type: typeName }
|
|
209
|
+
data: { type: typeName },
|
|
206
210
|
});
|
|
207
211
|
return;
|
|
208
212
|
}
|
|
@@ -212,7 +216,7 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
|
|
|
212
216
|
context.report({
|
|
213
217
|
node,
|
|
214
218
|
messageId: 'invalidGeneric',
|
|
215
|
-
data: { type: typeName }
|
|
219
|
+
data: { type: typeName },
|
|
216
220
|
});
|
|
217
221
|
}
|
|
218
222
|
}
|
|
@@ -231,14 +235,14 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
|
|
|
231
235
|
context.report({
|
|
232
236
|
node,
|
|
233
237
|
messageId: 'missingGeneric',
|
|
234
|
-
data: { type: 'DocumentReference' }
|
|
238
|
+
data: { type: 'DocumentReference' },
|
|
235
239
|
});
|
|
236
240
|
}
|
|
237
241
|
else if (hasInvalidType(typeAnnotation.params[0])) {
|
|
238
242
|
context.report({
|
|
239
243
|
node,
|
|
240
244
|
messageId: 'invalidGeneric',
|
|
241
|
-
data: { type: 'DocumentReference' }
|
|
245
|
+
data: { type: 'DocumentReference' },
|
|
242
246
|
});
|
|
243
247
|
}
|
|
244
248
|
}
|
|
@@ -252,14 +256,14 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
|
|
|
252
256
|
context.report({
|
|
253
257
|
node,
|
|
254
258
|
messageId: 'missingGeneric',
|
|
255
|
-
data: { type: 'CollectionReference' }
|
|
259
|
+
data: { type: 'CollectionReference' },
|
|
256
260
|
});
|
|
257
261
|
}
|
|
258
262
|
else if (hasInvalidType(typeAnnotation.params[0])) {
|
|
259
263
|
context.report({
|
|
260
264
|
node,
|
|
261
265
|
messageId: 'invalidGeneric',
|
|
262
|
-
data: { type: 'CollectionReference' }
|
|
266
|
+
data: { type: 'CollectionReference' },
|
|
263
267
|
});
|
|
264
268
|
}
|
|
265
269
|
}
|
|
@@ -272,14 +276,14 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
|
|
|
272
276
|
context.report({
|
|
273
277
|
node,
|
|
274
278
|
messageId: 'missingGeneric',
|
|
275
|
-
data: { type: 'CollectionGroup' }
|
|
279
|
+
data: { type: 'CollectionGroup' },
|
|
276
280
|
});
|
|
277
281
|
}
|
|
278
282
|
else if (hasInvalidType(typeAnnotation.params[0])) {
|
|
279
283
|
context.report({
|
|
280
284
|
node,
|
|
281
285
|
messageId: 'invalidGeneric',
|
|
282
|
-
data: { type: 'CollectionGroup' }
|
|
286
|
+
data: { type: 'CollectionGroup' },
|
|
283
287
|
});
|
|
284
288
|
}
|
|
285
289
|
}
|
|
@@ -19,7 +19,9 @@ const isFirestoreMethodCall = (node) => {
|
|
|
19
19
|
if (isIdentifier(object)) {
|
|
20
20
|
const name = object.name;
|
|
21
21
|
// Skip if it's a facade instance
|
|
22
|
-
if (name.includes('Fetcher') ||
|
|
22
|
+
if (name.includes('Fetcher') ||
|
|
23
|
+
name.includes('Setter') ||
|
|
24
|
+
name.includes('Tx')) {
|
|
23
25
|
return false;
|
|
24
26
|
}
|
|
25
27
|
// Check for batch or transaction
|
|
@@ -39,7 +41,8 @@ const isFirestoreMethodCall = (node) => {
|
|
|
39
41
|
const callee = current.callee;
|
|
40
42
|
if (isMemberExpression(callee)) {
|
|
41
43
|
const property = callee.property;
|
|
42
|
-
if (isIdentifier(property) &&
|
|
44
|
+
if (isIdentifier(property) &&
|
|
45
|
+
(property.name === 'doc' || property.name === 'collection')) {
|
|
43
46
|
foundDocOrCollection = true;
|
|
44
47
|
break;
|
|
45
48
|
}
|
|
@@ -60,7 +63,8 @@ const isFirestoreMethodCall = (node) => {
|
|
|
60
63
|
if (!foundDocOrCollection && isIdentifier(object)) {
|
|
61
64
|
const name = object.name;
|
|
62
65
|
// If the variable name contains 'doc' or 'ref', it's likely a Firestore reference
|
|
63
|
-
if (name.toLowerCase().includes('doc') ||
|
|
66
|
+
if (name.toLowerCase().includes('doc') ||
|
|
67
|
+
name.toLowerCase().includes('ref')) {
|
|
64
68
|
return true;
|
|
65
69
|
}
|
|
66
70
|
}
|
|
@@ -30,7 +30,8 @@ exports.enforceFirestorePathUtils = (0, createRule_1.createRule)({
|
|
|
30
30
|
return FIRESTORE_METHODS.has(property.name);
|
|
31
31
|
}
|
|
32
32
|
function isStringLiteralOrTemplate(node) {
|
|
33
|
-
return (node.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
33
|
+
return ((node.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
34
|
+
typeof node.value === 'string') ||
|
|
34
35
|
node.type === utils_1.AST_NODE_TYPES.TemplateLiteral);
|
|
35
36
|
}
|
|
36
37
|
function isUtilityFunction(node) {
|
|
@@ -64,7 +65,9 @@ exports.enforceFirestorePathUtils = (0, createRule_1.createRule)({
|
|
|
64
65
|
}
|
|
65
66
|
// Skip test files
|
|
66
67
|
const filename = context.getFilename();
|
|
67
|
-
if (filename.includes('__tests__') ||
|
|
68
|
+
if (filename.includes('__tests__') ||
|
|
69
|
+
filename.includes('.test.') ||
|
|
70
|
+
filename.includes('.spec.')) {
|
|
68
71
|
return;
|
|
69
72
|
}
|
|
70
73
|
context.report({
|
|
@@ -61,6 +61,55 @@ exports.enforceIdCapitalization = (0, createRule_1.createRule)({
|
|
|
61
61
|
node.parent.id === node) {
|
|
62
62
|
return true;
|
|
63
63
|
}
|
|
64
|
+
// Check if the node is in an object property context
|
|
65
|
+
if (node.parent &&
|
|
66
|
+
node.parent.type === utils_1.AST_NODE_TYPES.Property &&
|
|
67
|
+
node.parent.value === node) {
|
|
68
|
+
// Check if this is a property in an object pattern (destructuring)
|
|
69
|
+
let currentNode = node.parent;
|
|
70
|
+
while (currentNode.parent) {
|
|
71
|
+
if (currentNode.parent.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
currentNode = currentNode.parent;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Check if the node is in a property assignment context
|
|
78
|
+
if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
// Check if the node is in a property access context
|
|
82
|
+
if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
// Check if the node is a string literal used for property access
|
|
86
|
+
// This handles cases like obj['id'] or OverwolfGame['id']
|
|
87
|
+
if (node.parent &&
|
|
88
|
+
node.parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
89
|
+
node.parent.computed === true &&
|
|
90
|
+
node.parent.property === node) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
// Check if the node is a string literal in a type definition context
|
|
94
|
+
// This handles cases like Pick<Type, 'id' | 'name'>
|
|
95
|
+
if (node.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
96
|
+
typeof node.value === 'string') {
|
|
97
|
+
let currentNode = node;
|
|
98
|
+
while (currentNode.parent) {
|
|
99
|
+
// Check for TypeScript type contexts
|
|
100
|
+
if (currentNode.parent.type === utils_1.AST_NODE_TYPES.TSTypeReference ||
|
|
101
|
+
currentNode.parent.type ===
|
|
102
|
+
utils_1.AST_NODE_TYPES.TSTypeParameterInstantiation ||
|
|
103
|
+
currentNode.parent.type === utils_1.AST_NODE_TYPES.TSUnionType ||
|
|
104
|
+
currentNode.parent.type === utils_1.AST_NODE_TYPES.TSIntersectionType ||
|
|
105
|
+
currentNode.parent.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration ||
|
|
106
|
+
currentNode.parent.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration ||
|
|
107
|
+
currentNode.parent.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
currentNode = currentNode.parent;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
64
113
|
return false;
|
|
65
114
|
}
|
|
66
115
|
/**
|
|
@@ -72,6 +121,16 @@ exports.enforceIdCapitalization = (0, createRule_1.createRule)({
|
|
|
72
121
|
// Skip checking if the node is in an excluded context
|
|
73
122
|
if (isExcludedContext(node))
|
|
74
123
|
return;
|
|
124
|
+
// Check if this is a variable declaration with a string literal containing 'id'
|
|
125
|
+
if (node.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
126
|
+
node.parent &&
|
|
127
|
+
node.parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
128
|
+
node.parent.init === node &&
|
|
129
|
+
node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
130
|
+
node.parent.id.name.toLowerCase().includes('id')) {
|
|
131
|
+
// Skip checking variable assignments where the variable name contains 'id'
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
75
134
|
// Reset the regex lastIndex to ensure consistent behavior
|
|
76
135
|
idRegex.lastIndex = 0;
|
|
77
136
|
// Check if the string contains "id" as a standalone word
|
|
@@ -67,7 +67,8 @@ exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
|
|
|
67
67
|
const findIdentifiable = (type, checkedTypes = new Set()) => {
|
|
68
68
|
if (!type)
|
|
69
69
|
return false;
|
|
70
|
-
if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
70
|
+
if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
71
|
+
type.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
71
72
|
const typeName = type.typeName.name;
|
|
72
73
|
if (typeName === 'Identifiable') {
|
|
73
74
|
return true;
|
|
@@ -76,19 +77,23 @@ exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
|
|
|
76
77
|
checkedTypes.add(typeName);
|
|
77
78
|
// Look for the type in all scopes
|
|
78
79
|
const scope = context.getScope();
|
|
79
|
-
const variable = scope.variables.find(v => v.name === typeName);
|
|
80
|
+
const variable = scope.variables.find((v) => v.name === typeName);
|
|
80
81
|
if (variable) {
|
|
81
|
-
const def = variable.defs.find(d => d.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
|
|
82
|
-
if (def &&
|
|
82
|
+
const def = variable.defs.find((d) => d.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
|
|
83
|
+
if (def &&
|
|
84
|
+
'typeAnnotation' in def.node &&
|
|
85
|
+
def.node.typeAnnotation) {
|
|
83
86
|
return findIdentifiable(def.node.typeAnnotation, checkedTypes);
|
|
84
87
|
}
|
|
85
88
|
}
|
|
86
89
|
// Try looking in the parent scope
|
|
87
90
|
if (scope.upper) {
|
|
88
|
-
const parentVariable = scope.upper.variables.find(v => v.name === typeName);
|
|
91
|
+
const parentVariable = scope.upper.variables.find((v) => v.name === typeName);
|
|
89
92
|
if (parentVariable) {
|
|
90
|
-
const def = parentVariable.defs.find(d => d.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
|
|
91
|
-
if (def &&
|
|
93
|
+
const def = parentVariable.defs.find((d) => d.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
|
|
94
|
+
if (def &&
|
|
95
|
+
'typeAnnotation' in def.node &&
|
|
96
|
+
def.node.typeAnnotation) {
|
|
92
97
|
return findIdentifiable(def.node.typeAnnotation, checkedTypes);
|
|
93
98
|
}
|
|
94
99
|
}
|
|
@@ -97,7 +102,7 @@ exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
|
|
|
97
102
|
}
|
|
98
103
|
else if (type.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
|
|
99
104
|
// For intersection types, check each part
|
|
100
|
-
return type.types.some(part => findIdentifiable(part, checkedTypes));
|
|
105
|
+
return type.types.some((part) => findIdentifiable(part, checkedTypes));
|
|
101
106
|
}
|
|
102
107
|
return false;
|
|
103
108
|
};
|
|
@@ -114,7 +119,8 @@ exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
|
|
|
114
119
|
return type.members.some((member) => member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
|
|
115
120
|
member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
116
121
|
member.key.name === 'id' &&
|
|
117
|
-
member.typeAnnotation?.typeAnnotation.type ===
|
|
122
|
+
member.typeAnnotation?.typeAnnotation.type ===
|
|
123
|
+
utils_1.AST_NODE_TYPES.TSStringKeyword);
|
|
118
124
|
}
|
|
119
125
|
// Check intersection types
|
|
120
126
|
if (type.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
|
|
@@ -135,7 +141,8 @@ exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
|
|
|
135
141
|
return true;
|
|
136
142
|
}
|
|
137
143
|
// Check if type has id: string field (only for utility types)
|
|
138
|
-
if (isUtilityType(type) &&
|
|
144
|
+
if (isUtilityType(type) &&
|
|
145
|
+
checkIdField(type.typeParameters.params[0])) {
|
|
139
146
|
return true;
|
|
140
147
|
}
|
|
141
148
|
// Check if type is wrapped in a utility type
|
|
@@ -24,7 +24,7 @@ exports.enforceMemoizeAsync = (0, createRule_1.createRule)({
|
|
|
24
24
|
return {
|
|
25
25
|
ImportDeclaration(node) {
|
|
26
26
|
if (node.source.value === 'typescript-memoize') {
|
|
27
|
-
const memoizeSpecifier = node.specifiers.find(spec => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
27
|
+
const memoizeSpecifier = node.specifiers.find((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
28
28
|
spec.imported.name === 'Memoize');
|
|
29
29
|
if (memoizeSpecifier) {
|
|
30
30
|
hasMemoizeImport = true;
|
|
@@ -46,7 +46,7 @@ exports.enforceMemoizeAsync = (0, createRule_1.createRule)({
|
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
// Check if method already has @Memoize decorator
|
|
49
|
-
const hasDecorator = node.decorators?.some(decorator => {
|
|
49
|
+
const hasDecorator = node.decorators?.some((decorator) => {
|
|
50
50
|
if (decorator.expression.type !== utils_1.AST_NODE_TYPES.CallExpression) {
|
|
51
51
|
return false;
|
|
52
52
|
}
|