@blumintinc/eslint-plugin-blumint 1.6.0 → 1.7.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.
@@ -69,6 +69,40 @@ function findLoopNode(node) {
69
69
  }
70
70
  return undefined;
71
71
  }
72
+ function isFirestoreSetterInstance(node) {
73
+ // Check if it's a DocSetter instance
74
+ if (node.type === utils_1.AST_NODE_TYPES.NewExpression) {
75
+ return (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
76
+ node.callee.name === 'DocSetter');
77
+ }
78
+ return false;
79
+ }
80
+ function isMapInstance(node) {
81
+ // Check if it's a Map instance
82
+ if (node.type === utils_1.AST_NODE_TYPES.NewExpression) {
83
+ return (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
84
+ node.callee.name === 'Map');
85
+ }
86
+ return false;
87
+ }
88
+ function findVariableDeclaration(node, varName) {
89
+ let current = node;
90
+ while (current) {
91
+ if (current.type === utils_1.AST_NODE_TYPES.Program) {
92
+ for (const statement of current.body) {
93
+ if (statement.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
94
+ for (const decl of statement.declarations) {
95
+ if (decl.id.type === utils_1.AST_NODE_TYPES.Identifier && decl.id.name === varName) {
96
+ return decl;
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ current = current.parent;
103
+ }
104
+ return undefined;
105
+ }
72
106
  function isSetterMethodCall(node) {
73
107
  if (node.type !== utils_1.AST_NODE_TYPES.CallExpression)
74
108
  return { isValid: false };
@@ -84,6 +118,16 @@ function isSetterMethodCall(node) {
84
118
  if (object.type !== utils_1.AST_NODE_TYPES.Identifier)
85
119
  return { isValid: false };
86
120
  const setterInstance = object.name;
121
+ // Find the variable declaration
122
+ const decl = findVariableDeclaration(node, setterInstance);
123
+ if (!decl || !decl.init)
124
+ return { isValid: false };
125
+ // Skip if it's a Map instance
126
+ if (isMapInstance(decl.init))
127
+ return { isValid: false };
128
+ // Check if it's a Firestore setter instance
129
+ if (!isFirestoreSetterInstance(decl.init))
130
+ return { isValid: false };
87
131
  // Get the method name
88
132
  const methodName = callee.property.name;
89
133
  return { isValid: true, methodName, setterInstance };
@@ -0,0 +1 @@
1
+ export declare const preferCloneDeep: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"preferCloneDeep", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.preferCloneDeep = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.preferCloneDeep = (0, createRule_1.createRule)({
7
+ name: 'prefer-clone-deep',
8
+ meta: {
9
+ type: 'suggestion',
10
+ docs: {
11
+ description: 'Prefer using cloneDeep over nested spread copying',
12
+ recommended: 'error',
13
+ },
14
+ fixable: 'code',
15
+ schema: [],
16
+ messages: {
17
+ preferCloneDeep: 'Use cloneDeep from functions/src/util/cloneDeep.ts instead of nested spread operators for deep object copying',
18
+ },
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ function hasNestedSpread(node) {
23
+ let spreadCount = 0;
24
+ let hasFunction = false;
25
+ let hasSymbol = false;
26
+ function visit(node) {
27
+ if (node.type === utils_1.AST_NODE_TYPES.SpreadElement) {
28
+ spreadCount++;
29
+ }
30
+ else if (node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
31
+ node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
32
+ hasFunction = true;
33
+ }
34
+ else if (node.type === utils_1.AST_NODE_TYPES.Property &&
35
+ node.computed &&
36
+ node.key.type === utils_1.AST_NODE_TYPES.Identifier &&
37
+ node.key.name === 'Symbol') {
38
+ hasSymbol = true;
39
+ }
40
+ for (const key in node) {
41
+ const value = node[key];
42
+ if (value && typeof value === 'object') {
43
+ visit(value);
44
+ }
45
+ }
46
+ }
47
+ visit(node);
48
+ return spreadCount > 1 && !hasFunction && !hasSymbol;
49
+ }
50
+ function getSourceText(node) {
51
+ return context.getSourceCode().getText(node);
52
+ }
53
+ function generateCloneDeepFix(node) {
54
+ const sourceText = getSourceText(node);
55
+ return `cloneDeep(${sourceText.replace(/\.\.\./g, '')}, {} as const)`;
56
+ }
57
+ return {
58
+ ObjectExpression(node) {
59
+ if (hasNestedSpread(node)) {
60
+ context.report({
61
+ node,
62
+ messageId: 'preferCloneDeep',
63
+ fix(fixer) {
64
+ return fixer.replaceText(node, generateCloneDeepFix(node));
65
+ },
66
+ });
67
+ }
68
+ },
69
+ };
70
+ },
71
+ });
72
+ //# sourceMappingURL=prefer-clone-deep.js.map
@@ -208,6 +208,24 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
208
208
  }
209
209
  return false;
210
210
  }
211
+ function hasABPattern(params) {
212
+ if (params.length !== 2)
213
+ return false;
214
+ const paramNames = params.map(param => {
215
+ if (param.type === utils_1.AST_NODE_TYPES.Identifier) {
216
+ return param.name;
217
+ }
218
+ return '';
219
+ });
220
+ // Check if both parameters end with 'A' and 'B' respectively and have the same prefix
221
+ const [first, second] = paramNames;
222
+ if (first.endsWith('A') && second.endsWith('B')) {
223
+ const firstPrefix = first.slice(0, -1);
224
+ const secondPrefix = second.slice(0, -1);
225
+ return firstPrefix === secondPrefix;
226
+ }
227
+ return false;
228
+ }
211
229
  function shouldIgnoreNode(node) {
212
230
  // Ignore built-in objects and third-party modules
213
231
  if (isBuiltInOrThirdParty(node))
@@ -230,6 +248,16 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
230
248
  parent = parent.parent;
231
249
  }
232
250
  }
251
+ // Ignore functions with A/B pattern parameters
252
+ if ((node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
253
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
254
+ node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
255
+ node.type === utils_1.AST_NODE_TYPES.TSMethodSignature ||
256
+ node.type === utils_1.AST_NODE_TYPES.TSFunctionType) &&
257
+ Array.isArray(node.params)) {
258
+ if (hasABPattern(node.params))
259
+ return true;
260
+ }
233
261
  return false;
234
262
  }
235
263
  function checkFunction(node) {
@@ -41,9 +41,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
41
41
  const scope = context.getScope();
42
42
  const variable = scope.variables.find(v => v.name === typeName.name);
43
43
  if (!variable || !variable.defs[0]?.node) {
44
- // If we can't find the type definition, assume it's a type with all optional properties
44
+ // If we can't find the type definition, assume it's a type with required properties
45
45
  // This handles cases where the type is imported from another module
46
- return true;
46
+ return false;
47
47
  }
48
48
  const def = variable.defs[0].node;
49
49
  if (def.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
@@ -58,9 +58,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
58
58
  });
59
59
  }
60
60
  // If we found the type definition but it's not a type alias or interface declaration,
61
- // assume it's a type with all optional properties
61
+ // assume it's a type with required properties
62
62
  // This handles cases where the type is imported from another module
63
- return true;
63
+ return false;
64
64
  }
65
65
  // Handle type alias declarations
66
66
  if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
@@ -77,71 +77,130 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
77
77
  }
78
78
  return false;
79
79
  }
80
- function checkHookParam(param) {
81
- // If it's already an assignment pattern, check if the left side is an object pattern
82
- if (param.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
83
- if (param.left.type === utils_1.AST_NODE_TYPES.ObjectPattern && param.left.typeAnnotation) {
84
- if (hasAllOptionalProperties(param.left.typeAnnotation.typeAnnotation)) {
85
- return; // Already has a default value and is correctly typed
80
+ return {
81
+ 'ArrowFunctionExpression, FunctionDeclaration'(node) {
82
+ // Check if it's a hook function
83
+ let isHook = false;
84
+ if (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
85
+ isHook = node.id ? isHookName(node.id.name) : false;
86
+ }
87
+ else {
88
+ const parent = node.parent;
89
+ if (parent &&
90
+ parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
91
+ parent.id &&
92
+ parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
93
+ isHook = isHookName(parent.id.name);
86
94
  }
87
95
  }
88
- return;
89
- }
90
- // If it's an object pattern, check if it needs a default value
91
- if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern && param.typeAnnotation) {
92
- const typeAnnotation = param.typeAnnotation.typeAnnotation;
93
- if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
94
- const typeName = typeAnnotation.typeName;
95
- if (typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
96
- const scope = context.getScope();
97
- const variable = scope.variables.find(v => v.name === typeName.name);
98
- if (!variable || !variable.defs[0]?.node) {
99
- // If we can't find the type definition, assume it's a type with all optional properties
100
- // This handles cases where the type is imported from another module
101
- context.report({
102
- node: param,
103
- messageId: 'requireDefaultParams',
104
- fix(fixer) {
105
- const paramText = context.getSourceCode().getText(param);
106
- return fixer.replaceText(param, `${paramText} = {}`);
107
- },
108
- });
109
- return;
110
- }
111
- const def = variable.defs[0].node;
112
- if (def.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
113
- if (hasAllOptionalProperties(def.typeAnnotation)) {
114
- context.report({
115
- node: param,
116
- messageId: 'requireDefaultParams',
117
- fix(fixer) {
118
- const paramText = context.getSourceCode().getText(param);
119
- return fixer.replaceText(param, `${paramText} = {}`);
120
- },
121
- });
96
+ if (!isHook) {
97
+ return;
98
+ }
99
+ // Check if it has exactly one parameter
100
+ if (node.params.length !== 1) {
101
+ return;
102
+ }
103
+ // Check if the parameter is already an assignment pattern
104
+ const param = node.params[0];
105
+ if (param.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
106
+ return;
107
+ }
108
+ // Check if the parameter has a type annotation
109
+ if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern && param.typeAnnotation) {
110
+ const typeAnnotation = param.typeAnnotation.typeAnnotation;
111
+ if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
112
+ const typeName = typeAnnotation.typeName;
113
+ if (typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
114
+ const scope = context.getScope();
115
+ const variable = scope.variables.find(v => v.name === typeName.name);
116
+ if (variable && variable.defs[0]?.node) {
117
+ const def = variable.defs[0].node;
118
+ if (def.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
119
+ if (hasAllOptionalProperties(def.typeAnnotation)) {
120
+ context.report({
121
+ node: param,
122
+ messageId: 'requireDefaultParams',
123
+ fix(fixer) {
124
+ const paramText = context.getSourceCode().getText(param);
125
+ return fixer.replaceText(param, `${paramText} = {}`);
126
+ },
127
+ });
128
+ }
129
+ }
130
+ else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
131
+ if (def.body.body.every(member => {
132
+ if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
133
+ return false;
134
+ }
135
+ return member.optional === true;
136
+ })) {
137
+ context.report({
138
+ node: param,
139
+ messageId: 'requireDefaultParams',
140
+ fix(fixer) {
141
+ const paramText = context.getSourceCode().getText(param);
142
+ return fixer.replaceText(param, `${paramText} = {}`);
143
+ },
144
+ });
145
+ }
146
+ }
122
147
  }
123
- }
124
- else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
125
- if (def.body.body.every(member => {
126
- if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
148
+ else {
149
+ // If we can't find the type definition, check if it's defined in the same file
150
+ const program = context.getSourceCode().ast;
151
+ const typeDefinitions = program.body.filter(node => {
152
+ if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration || node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
153
+ if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
154
+ return node.id.name === typeName.name;
155
+ }
156
+ else {
157
+ return node.id.name === typeName.name;
158
+ }
159
+ }
127
160
  return false;
128
- }
129
- return member.optional === true;
130
- })) {
131
- context.report({
132
- node: param,
133
- messageId: 'requireDefaultParams',
134
- fix(fixer) {
135
- const paramText = context.getSourceCode().getText(param);
136
- return fixer.replaceText(param, `${paramText} = {}`);
137
- },
138
161
  });
162
+ if (typeDefinitions.length > 0) {
163
+ const def = typeDefinitions[0];
164
+ if (def.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
165
+ if (hasAllOptionalProperties(def.typeAnnotation)) {
166
+ context.report({
167
+ node: param,
168
+ messageId: 'requireDefaultParams',
169
+ fix(fixer) {
170
+ const paramText = context.getSourceCode().getText(param);
171
+ return fixer.replaceText(param, `${paramText} = {}`);
172
+ },
173
+ });
174
+ }
175
+ }
176
+ else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
177
+ if (def.body.body.every(member => {
178
+ if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
179
+ return false;
180
+ }
181
+ return member.optional === true;
182
+ })) {
183
+ context.report({
184
+ node: param,
185
+ messageId: 'requireDefaultParams',
186
+ fix(fixer) {
187
+ const paramText = context.getSourceCode().getText(param);
188
+ return fixer.replaceText(param, `${paramText} = {}`);
189
+ },
190
+ });
191
+ }
192
+ }
193
+ }
139
194
  }
140
195
  }
141
- else {
142
- // If we found the type definition but it's not a type alias or interface declaration,
143
- // assume it's a type with all optional properties
144
- // This handles cases where the type is imported from another module
196
+ }
197
+ else if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
198
+ if (typeAnnotation.members.every(member => {
199
+ if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
200
+ return false;
201
+ }
202
+ return member.optional === true;
203
+ })) {
145
204
  context.report({
146
205
  node: param,
147
206
  messageId: 'requireDefaultParams',
@@ -153,41 +212,6 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
153
212
  }
154
213
  }
155
214
  }
156
- else if (hasAllOptionalProperties(typeAnnotation)) {
157
- context.report({
158
- node: param,
159
- messageId: 'requireDefaultParams',
160
- fix(fixer) {
161
- const paramText = context.getSourceCode().getText(param);
162
- return fixer.replaceText(param, `${paramText} = {}`);
163
- },
164
- });
165
- }
166
- }
167
- }
168
- return {
169
- FunctionDeclaration(node) {
170
- if (!node.id || !isHookName(node.id.name)) {
171
- return;
172
- }
173
- if (node.params.length !== 1) {
174
- return;
175
- }
176
- checkHookParam(node.params[0]);
177
- },
178
- ArrowFunctionExpression(node) {
179
- const parent = node.parent;
180
- if (!parent ||
181
- parent.type !== utils_1.AST_NODE_TYPES.VariableDeclarator ||
182
- !parent.id ||
183
- parent.id.type !== utils_1.AST_NODE_TYPES.Identifier ||
184
- !isHookName(parent.id.name)) {
185
- return;
186
- }
187
- if (node.params.length !== 1) {
188
- return;
189
- }
190
- checkHookParam(node.params[0]);
191
215
  },
192
216
  };
193
217
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.6.0",
3
+ "version": "1.7.1",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -71,7 +71,7 @@
71
71
  "dotenv-cli": "5.0.0",
72
72
  "eslint": "8.57.0",
73
73
  "eslint-config-prettier": "8.5.0",
74
- "eslint-doc-generator": "1.7.1",
74
+ "eslint-doc-generator": "^1.7.1",
75
75
  "eslint-import-resolver-typescript": "3.5.5",
76
76
  "eslint-plugin-eslint-plugin": "5.0.0",
77
77
  "eslint-plugin-import": "2.26.0",