@blumintinc/eslint-plugin-blumint 1.5.0 → 1.5.2

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.
@@ -0,0 +1 @@
1
+ export declare const noFirestoreObjectArrays: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noObjectArrays", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noFirestoreObjectArrays = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const PRIMITIVE_TYPES = new Set([
7
+ 'string',
8
+ 'number',
9
+ 'boolean',
10
+ 'Date',
11
+ 'Timestamp',
12
+ 'null',
13
+ 'undefined',
14
+ 'GeoPoint',
15
+ ]);
16
+ const isInFirestoreTypesDirectory = (filename) => {
17
+ return filename.includes('functions/src/types/firestore');
18
+ };
19
+ const isObjectType = (node) => {
20
+ switch (node.type) {
21
+ case utils_1.AST_NODE_TYPES.TSTypeLiteral:
22
+ return true;
23
+ case utils_1.AST_NODE_TYPES.TSTypeReference:
24
+ if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
25
+ const typeName = node.typeName.name;
26
+ return !PRIMITIVE_TYPES.has(typeName);
27
+ }
28
+ else if (node.typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
29
+ // Handle namespace.Type cases
30
+ return true;
31
+ }
32
+ return true;
33
+ case utils_1.AST_NODE_TYPES.TSIntersectionType:
34
+ case utils_1.AST_NODE_TYPES.TSUnionType:
35
+ return node.types.some(isObjectType);
36
+ case utils_1.AST_NODE_TYPES.TSMappedType:
37
+ case utils_1.AST_NODE_TYPES.TSIndexedAccessType:
38
+ return true;
39
+ default:
40
+ return false;
41
+ }
42
+ };
43
+ exports.noFirestoreObjectArrays = (0, createRule_1.createRule)({
44
+ name: 'no-firestore-object-arrays',
45
+ meta: {
46
+ type: 'problem',
47
+ docs: {
48
+ description: 'Disallow arrays of objects in Firestore type definitions to optimize performance and avoid unnecessary fetches',
49
+ recommended: 'warn',
50
+ },
51
+ schema: [],
52
+ messages: {
53
+ noObjectArrays: 'Arrays of objects are not recommended in Firestore. Use subcollections, arrays of IDs, or structured maps (Record<string, T>) instead.',
54
+ },
55
+ },
56
+ defaultOptions: [],
57
+ create(context) {
58
+ if (!isInFirestoreTypesDirectory(context.getFilename())) {
59
+ return {};
60
+ }
61
+ return {
62
+ TSArrayType(node) {
63
+ if (isObjectType(node.elementType)) {
64
+ context.report({
65
+ node,
66
+ messageId: 'noObjectArrays',
67
+ });
68
+ }
69
+ },
70
+ TSTypeReference(node) {
71
+ // Handle Array<T> and ReadonlyArray<T> syntax
72
+ const typeName = node.typeName.name;
73
+ if ((typeName === 'Array' || typeName === 'ReadonlyArray') && node.typeParameters) {
74
+ const elementType = node.typeParameters.params[0];
75
+ if (isObjectType(elementType)) {
76
+ context.report({
77
+ node,
78
+ messageId: 'noObjectArrays',
79
+ });
80
+ }
81
+ }
82
+ },
83
+ };
84
+ },
85
+ });
86
+ //# sourceMappingURL=no-firestore-object-arrays.js.map
@@ -0,0 +1 @@
1
+ export declare const noJsxInHooks: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noJsxInHooks", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noJsxInHooks = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const isJsxElement = (node) => {
7
+ if (!node)
8
+ return false;
9
+ if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
10
+ return isJsxElement(node.consequent) || isJsxElement(node.alternate);
11
+ }
12
+ return (node.type === utils_1.AST_NODE_TYPES.JSXElement ||
13
+ node.type === utils_1.AST_NODE_TYPES.JSXFragment ||
14
+ node.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer);
15
+ };
16
+ const isJsxReturnType = (node) => {
17
+ if (node.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
18
+ const typeName = node.typeAnnotation.typeName;
19
+ if (typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
20
+ return ['JSX', 'ReactNode', 'ReactElement'].includes(typeName.name);
21
+ }
22
+ if (typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
23
+ return typeName.left.type === utils_1.AST_NODE_TYPES.Identifier &&
24
+ typeName.left.name === 'JSX' &&
25
+ typeName.right.type === utils_1.AST_NODE_TYPES.Identifier &&
26
+ typeName.right.name === 'Element';
27
+ }
28
+ }
29
+ return false;
30
+ };
31
+ const containsJsxInBlockStatement = (node) => {
32
+ const variablesWithJsx = new Set();
33
+ for (const statement of node.body) {
34
+ // Check variable declarations for JSX assignments
35
+ if (statement.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
36
+ for (const declarator of statement.declarations) {
37
+ if (declarator.init) {
38
+ if (declarator.init.type === utils_1.AST_NODE_TYPES.CallExpression &&
39
+ containsJsxInUseMemo(declarator.init)) {
40
+ if (declarator.id.type === utils_1.AST_NODE_TYPES.Identifier) {
41
+ variablesWithJsx.add(declarator.id.name);
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ // Check return statements
48
+ if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement && statement.argument) {
49
+ if (isJsxElement(statement.argument)) {
50
+ return true;
51
+ }
52
+ if (statement.argument.type === utils_1.AST_NODE_TYPES.CallExpression) {
53
+ if (containsJsxInUseMemo(statement.argument)) {
54
+ return true;
55
+ }
56
+ }
57
+ if (statement.argument.type === utils_1.AST_NODE_TYPES.Identifier &&
58
+ variablesWithJsx.has(statement.argument.name)) {
59
+ return true;
60
+ }
61
+ }
62
+ // Check if statements
63
+ if (statement.type === utils_1.AST_NODE_TYPES.IfStatement) {
64
+ if (statement.consequent.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
65
+ statement.consequent.argument) {
66
+ if (isJsxElement(statement.consequent.argument)) {
67
+ return true;
68
+ }
69
+ if (statement.consequent.argument.type === utils_1.AST_NODE_TYPES.Identifier &&
70
+ variablesWithJsx.has(statement.consequent.argument.name)) {
71
+ return true;
72
+ }
73
+ }
74
+ if (statement.consequent.type === utils_1.AST_NODE_TYPES.BlockStatement &&
75
+ containsJsxInBlockStatement(statement.consequent)) {
76
+ return true;
77
+ }
78
+ if (statement.alternate) {
79
+ if (statement.alternate.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
80
+ statement.alternate.argument) {
81
+ if (isJsxElement(statement.alternate.argument)) {
82
+ return true;
83
+ }
84
+ if (statement.alternate.argument.type === utils_1.AST_NODE_TYPES.Identifier &&
85
+ variablesWithJsx.has(statement.alternate.argument.name)) {
86
+ return true;
87
+ }
88
+ }
89
+ if (statement.alternate.type === utils_1.AST_NODE_TYPES.BlockStatement &&
90
+ containsJsxInBlockStatement(statement.alternate)) {
91
+ return true;
92
+ }
93
+ }
94
+ }
95
+ }
96
+ return false;
97
+ };
98
+ const containsJsxInUseMemo = (node) => {
99
+ if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
100
+ node.callee.name === 'useMemo' &&
101
+ node.arguments.length > 0) {
102
+ const callback = node.arguments[0];
103
+ if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
104
+ callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
105
+ const body = callback.body;
106
+ if (isJsxElement(body)) {
107
+ return true;
108
+ }
109
+ if (body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
110
+ return containsJsxInBlockStatement(body);
111
+ }
112
+ if (body.type === utils_1.AST_NODE_TYPES.CallExpression &&
113
+ body.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
114
+ body.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
115
+ body.callee.property.name === 'map') {
116
+ const mapCallback = body.arguments[0];
117
+ if ((mapCallback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
118
+ mapCallback.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
119
+ isJsxElement(mapCallback.body)) {
120
+ return true;
121
+ }
122
+ }
123
+ }
124
+ }
125
+ return false;
126
+ };
127
+ exports.noJsxInHooks = (0, createRule_1.createRule)({
128
+ name: 'no-jsx-in-hooks',
129
+ meta: {
130
+ type: 'problem',
131
+ docs: {
132
+ description: 'Prevent hooks from returning JSX',
133
+ recommended: 'error',
134
+ },
135
+ schema: [],
136
+ messages: {
137
+ noJsxInHooks: 'Hooks should not return JSX. Convert this hook into a component or extract the JSX into a separate component.',
138
+ },
139
+ },
140
+ defaultOptions: [],
141
+ create(context) {
142
+ return {
143
+ FunctionDeclaration(node) {
144
+ if (node.id && node.id.name.startsWith('use')) {
145
+ // Check return type annotation
146
+ if (node.returnType && isJsxReturnType(node.returnType)) {
147
+ context.report({
148
+ node: node.id,
149
+ messageId: 'noJsxInHooks',
150
+ });
151
+ return;
152
+ }
153
+ // Check return statements
154
+ if (node.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
155
+ if (containsJsxInBlockStatement(node.body)) {
156
+ context.report({
157
+ node: node.id,
158
+ messageId: 'noJsxInHooks',
159
+ });
160
+ }
161
+ }
162
+ }
163
+ },
164
+ ArrowFunctionExpression(node) {
165
+ const parent = node.parent;
166
+ if (parent &&
167
+ parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
168
+ parent.id.type === utils_1.AST_NODE_TYPES.Identifier &&
169
+ parent.id.name.startsWith('use')) {
170
+ // Check return type annotation
171
+ if (node.returnType && isJsxReturnType(node.returnType)) {
172
+ context.report({
173
+ node: parent.id,
174
+ messageId: 'noJsxInHooks',
175
+ });
176
+ return;
177
+ }
178
+ // Check direct JSX return
179
+ if (isJsxElement(node.body)) {
180
+ context.report({
181
+ node: parent.id,
182
+ messageId: 'noJsxInHooks',
183
+ });
184
+ return;
185
+ }
186
+ // Check block body returns
187
+ if (node.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
188
+ if (containsJsxInBlockStatement(node.body)) {
189
+ context.report({
190
+ node: parent.id,
191
+ messageId: 'noJsxInHooks',
192
+ });
193
+ }
194
+ }
195
+ }
196
+ },
197
+ };
198
+ },
199
+ });
200
+ //# sourceMappingURL=no-jsx-in-hooks.js.map
@@ -0,0 +1 @@
1
+ export declare const noMemoizeOnStatic: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noMemoizeOnStatic", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noMemoizeOnStatic = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.noMemoizeOnStatic = (0, createRule_1.createRule)({
7
+ name: 'no-memoize-on-static',
8
+ meta: {
9
+ type: 'problem',
10
+ docs: {
11
+ description: 'Prevent using @Memoize() decorator on static methods',
12
+ recommended: 'error',
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ noMemoizeOnStatic: '@Memoize() decorator should not be used on static methods',
17
+ },
18
+ },
19
+ defaultOptions: [],
20
+ create(context) {
21
+ // Track renamed imports of Memoize
22
+ const memoizeAliases = new Set(['Memoize']);
23
+ return {
24
+ ImportSpecifier(node) {
25
+ if (node.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
26
+ node.imported.name === 'Memoize' &&
27
+ node.local.type === utils_1.AST_NODE_TYPES.Identifier) {
28
+ memoizeAliases.add(node.local.name);
29
+ }
30
+ },
31
+ 'MethodDefinition[static=true]'(node) {
32
+ if (node.decorators) {
33
+ for (const decorator of node.decorators) {
34
+ const expr = decorator.expression;
35
+ if (
36
+ // Handle @Memoize()
37
+ (expr.type === utils_1.AST_NODE_TYPES.CallExpression &&
38
+ expr.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
39
+ memoizeAliases.has(expr.callee.name)) ||
40
+ // Handle @Memoize (without parentheses)
41
+ (expr.type === utils_1.AST_NODE_TYPES.Identifier &&
42
+ memoizeAliases.has(expr.name))) {
43
+ context.report({
44
+ node: decorator,
45
+ messageId: 'noMemoizeOnStatic',
46
+ });
47
+ }
48
+ }
49
+ }
50
+ },
51
+ };
52
+ },
53
+ });
54
+ //# sourceMappingURL=no-memoize-on-static.js.map
@@ -0,0 +1,3 @@
1
+ type MessageIds = 'unsafeObjectSpread' | 'unsafeArraySpread';
2
+ export declare const noUnsafeFirestoreSpread: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
3
+ export {};
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noUnsafeFirestoreSpread = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.noUnsafeFirestoreSpread = (0, createRule_1.createRule)({
7
+ name: 'no-unsafe-firestore-spread',
8
+ meta: {
9
+ type: 'problem',
10
+ docs: {
11
+ description: 'Prevent unsafe object/array spreads in Firestore updates',
12
+ recommended: 'error',
13
+ },
14
+ fixable: 'code',
15
+ schema: [],
16
+ messages: {
17
+ unsafeObjectSpread: 'Avoid using object spread in Firestore updates. Use dot notation with FieldPath instead.',
18
+ unsafeArraySpread: 'Avoid using array spread in Firestore updates. Use FieldValue.arrayUnion() instead.',
19
+ },
20
+ },
21
+ defaultOptions: [],
22
+ create(context) {
23
+ function isFirestoreSetMergeCall(node) {
24
+ // Check for merge: true in the last argument
25
+ const lastArg = node.arguments[node.arguments.length - 1];
26
+ if (lastArg?.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
27
+ const hasMergeTrue = lastArg.properties.some((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
28
+ prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
29
+ prop.key.name === 'merge' &&
30
+ prop.value.type === utils_1.AST_NODE_TYPES.Literal &&
31
+ prop.value.value === true);
32
+ if (!hasMergeTrue)
33
+ return false;
34
+ // Check if it's a set() method call or setDoc() function call
35
+ if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
36
+ const property = node.callee.property;
37
+ return (property.type === utils_1.AST_NODE_TYPES.Identifier &&
38
+ property.name === 'set');
39
+ }
40
+ else if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
41
+ return node.callee.name === 'setDoc';
42
+ }
43
+ }
44
+ return false;
45
+ }
46
+ function checkObjectForSpreads(node, parentPath = '') {
47
+ for (const property of node.properties) {
48
+ if (property.type === utils_1.AST_NODE_TYPES.SpreadElement) {
49
+ context.report({
50
+ node: property,
51
+ messageId: 'unsafeObjectSpread',
52
+ fix: null,
53
+ });
54
+ }
55
+ else if (property.type === utils_1.AST_NODE_TYPES.Property) {
56
+ const key = property.key.type === utils_1.AST_NODE_TYPES.Identifier
57
+ ? property.key.name
58
+ : '';
59
+ const newPath = parentPath ? `${parentPath}.${key}` : key;
60
+ if (property.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
61
+ checkObjectForSpreads(property.value, newPath);
62
+ }
63
+ else if (property.value.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
64
+ checkArrayForSpreads(property.value);
65
+ }
66
+ else if (property.value.type === utils_1.AST_NODE_TYPES.CallExpression) {
67
+ // Handle chained array methods like [...array].filter()
68
+ let current = property.value;
69
+ while (current) {
70
+ if (current.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
71
+ checkArrayForSpreads(current);
72
+ break;
73
+ }
74
+ else if (current.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
75
+ checkObjectForSpreads(current, newPath);
76
+ break;
77
+ }
78
+ // Move up to check the caller if it's a method chain
79
+ if (current.type === utils_1.AST_NODE_TYPES.CallExpression &&
80
+ current.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
81
+ current = current.callee.object;
82
+ }
83
+ else {
84
+ break;
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ function checkArrayForSpreads(node) {
92
+ // Check for spreads in the array expression itself
93
+ for (const element of node.elements) {
94
+ if (element?.type === utils_1.AST_NODE_TYPES.SpreadElement) {
95
+ context.report({
96
+ node: element,
97
+ messageId: 'unsafeArraySpread',
98
+ fix: null,
99
+ });
100
+ }
101
+ }
102
+ }
103
+ return {
104
+ CallExpression(node) {
105
+ if (!isFirestoreSetMergeCall(node))
106
+ return;
107
+ // For set() calls, the update object can be either the first or second argument
108
+ // If it's a docRef.set(data) call, it's the first argument
109
+ // If it's a docRef.set(docRef, data) call (like in transactions/batches), it's the second argument
110
+ let updateArg;
111
+ if (node.arguments.length === 2) {
112
+ updateArg = node.arguments[0];
113
+ }
114
+ else if (node.arguments.length === 3) {
115
+ // In a transaction or batch operation, the data is the second argument
116
+ updateArg = node.arguments[1];
117
+ }
118
+ if (!updateArg || updateArg.type !== utils_1.AST_NODE_TYPES.ObjectExpression)
119
+ return;
120
+ checkObjectForSpreads(updateArg);
121
+ },
122
+ };
123
+ },
124
+ });
125
+ //# sourceMappingURL=no-unsafe-firestore-spread.js.map
@@ -0,0 +1 @@
1
+ export declare const requireHooksDefaultParams: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"requireDefaultParams", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;