@blumintinc/eslint-plugin-blumint 1.5.4 → 1.6.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.
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.preferBatchOperations = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const SETTER_METHODS = new Set(['set', 'overwrite']);
7
+ const ARRAY_METHODS = new Set(['map', 'forEach', 'filter', 'reduce', 'every', 'some']);
8
+ function isArrayMethod(node) {
9
+ if (node.type !== utils_1.AST_NODE_TYPES.CallExpression)
10
+ return { isValid: false };
11
+ const callee = node.callee;
12
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
13
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
14
+ ARRAY_METHODS.has(callee.property.name)) {
15
+ return { isValid: true, methodName: callee.property.name };
16
+ }
17
+ return { isValid: false };
18
+ }
19
+ function isPromiseAll(node) {
20
+ if (node.type !== utils_1.AST_NODE_TYPES.CallExpression)
21
+ return false;
22
+ const callee = node.callee;
23
+ return (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
24
+ callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
25
+ callee.object.name === 'Promise' &&
26
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
27
+ callee.property.name === 'all');
28
+ }
29
+ function findLoopNode(node) {
30
+ let current = node;
31
+ let loopNode = null;
32
+ while (current) {
33
+ switch (current.type) {
34
+ case utils_1.AST_NODE_TYPES.ForStatement:
35
+ case utils_1.AST_NODE_TYPES.ForInStatement:
36
+ case utils_1.AST_NODE_TYPES.ForOfStatement:
37
+ case utils_1.AST_NODE_TYPES.WhileStatement:
38
+ case utils_1.AST_NODE_TYPES.DoWhileStatement:
39
+ loopNode = current;
40
+ break;
41
+ case utils_1.AST_NODE_TYPES.CallExpression:
42
+ // Check for Promise.all
43
+ if (isPromiseAll(current)) {
44
+ return { node: current, isArrayMethod: 'map' };
45
+ }
46
+ // Check for array methods
47
+ const { isValid, methodName: currentMethodName } = isArrayMethod(current);
48
+ if (isValid && currentMethodName) {
49
+ // For sequential array methods, check if the callback is async
50
+ if (currentMethodName === 'forEach' || currentMethodName === 'reduce' || currentMethodName === 'filter') {
51
+ const callback = current.arguments[0];
52
+ if (callback && (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
53
+ callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
54
+ callback.async) {
55
+ return { node: current, isArrayMethod: currentMethodName };
56
+ }
57
+ }
58
+ return { node: current, isArrayMethod: currentMethodName };
59
+ }
60
+ break;
61
+ case utils_1.AST_NODE_TYPES.Program:
62
+ // Return loop if we found one
63
+ if (loopNode) {
64
+ return { node: loopNode };
65
+ }
66
+ return undefined;
67
+ }
68
+ current = current.parent;
69
+ }
70
+ return undefined;
71
+ }
72
+ function isSetterMethodCall(node) {
73
+ if (node.type !== utils_1.AST_NODE_TYPES.CallExpression)
74
+ return { isValid: false };
75
+ const callee = node.callee;
76
+ if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression)
77
+ return { isValid: false };
78
+ if (callee.property.type !== utils_1.AST_NODE_TYPES.Identifier)
79
+ return { isValid: false };
80
+ if (!SETTER_METHODS.has(callee.property.name))
81
+ return { isValid: false };
82
+ // Get the setter instance
83
+ const object = callee.object;
84
+ if (object.type !== utils_1.AST_NODE_TYPES.Identifier)
85
+ return { isValid: false };
86
+ const setterInstance = object.name;
87
+ // Get the method name
88
+ const methodName = callee.property.name;
89
+ return { isValid: true, methodName, setterInstance };
90
+ }
91
+ const loopSetterCalls = new Map();
92
+ exports.preferBatchOperations = (0, createRule_1.createRule)({
93
+ name: 'prefer-batch-operations',
94
+ meta: {
95
+ type: 'suggestion',
96
+ docs: {
97
+ description: 'Enforce using setAll() and overwriteAll() instead of multiple set() or overwrite() calls',
98
+ recommended: 'error',
99
+ },
100
+ fixable: 'code',
101
+ schema: [],
102
+ messages: {
103
+ preferSetAll: 'Use setAll() instead of multiple set() calls for better performance',
104
+ preferOverwriteAll: 'Use overwriteAll() instead of multiple overwrite() calls for better performance',
105
+ },
106
+ },
107
+ defaultOptions: [],
108
+ create(context) {
109
+ return {
110
+ 'Program:exit'() {
111
+ // Clear the maps for the next file
112
+ loopSetterCalls.clear();
113
+ },
114
+ CallExpression(node) {
115
+ const { isValid, methodName, setterInstance } = isSetterMethodCall(node);
116
+ if (!isValid || !methodName || !setterInstance)
117
+ return;
118
+ // Check if we're in a loop or Promise.all
119
+ const loopInfo = findLoopNode(node);
120
+ if (!loopInfo)
121
+ return;
122
+ // Get or create the setter calls map for this loop
123
+ let setterCalls = loopSetterCalls.get(loopInfo.node);
124
+ if (!setterCalls) {
125
+ setterCalls = new Map();
126
+ loopSetterCalls.set(loopInfo.node, setterCalls);
127
+ }
128
+ // Track setter instance and method calls for this loop
129
+ const key = setterInstance;
130
+ const existing = setterCalls.get(key);
131
+ if (existing) {
132
+ // If we see a different method on the same setter instance, don't report
133
+ if (existing.methodName !== methodName)
134
+ return;
135
+ existing.count++;
136
+ }
137
+ else {
138
+ setterCalls.set(key, { methodName, count: 1 });
139
+ }
140
+ // Report on the first occurrence of a repeated call
141
+ // For Promise.all and array methods, report on the first occurrence
142
+ // For regular loops, report on the first occurrence too since we know it's in a loop
143
+ const shouldReport = loopInfo.isArrayMethod
144
+ ? ['forEach', 'reduce', 'filter', 'map'].includes(loopInfo.isArrayMethod)
145
+ : setterCalls.get(key).count === 1;
146
+ // Don't report if we have multiple different setter instances in a loop
147
+ // Only check this for regular loops, not array methods or Promise.all
148
+ if (shouldReport && !loopInfo.isArrayMethod) {
149
+ const setterInstances = new Set(Array.from(setterCalls.keys()));
150
+ if (setterInstances.size > 1) {
151
+ // This is a valid use case when using multiple setters in a loop
152
+ // For example: userSetter.set(doc.user) and orderSetter.set(doc.order)
153
+ // Each setter operates on a different collection, so they can't be batched together
154
+ // We only want to report when using the same setter instance multiple times
155
+ // For example: userSetter.set(doc.user) multiple times should use userSetter.setAll()
156
+ if (loopInfo.node.type.startsWith('For') || loopInfo.node.type.startsWith('While') || loopInfo.node.type.startsWith('Do')) {
157
+ return;
158
+ }
159
+ }
160
+ }
161
+ // Report on the first occurrence of a repeated call
162
+ // For Promise.all and array methods, report on the first occurrence
163
+ // For regular loops, report on the first occurrence too since we know it's in a loop
164
+ if (shouldReport) {
165
+ const messageId = methodName === 'set' ? 'preferSetAll' : 'preferOverwriteAll';
166
+ context.report({
167
+ node,
168
+ messageId,
169
+ fix: () => null, // We can't provide a fix because we don't know the array structure
170
+ });
171
+ }
172
+ },
173
+ };
174
+ },
175
+ });
176
+ //# sourceMappingURL=prefer-batch-operations.js.map
@@ -51,6 +51,16 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
51
51
  if (param.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
52
52
  return getParameterType(param.left);
53
53
  }
54
+ if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern && param.typeAnnotation) {
55
+ // For destructured parameters, use the type annotation name
56
+ const typeNode = param.typeAnnotation.typeAnnotation;
57
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
58
+ return typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier
59
+ ? typeNode.typeName.name
60
+ : 'unknown';
61
+ }
62
+ return typeNode.type;
63
+ }
54
64
  if (param.type === utils_1.AST_NODE_TYPES.Identifier && param.typeAnnotation) {
55
65
  const typeNode = param.typeAnnotation.typeAnnotation;
56
66
  if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
@@ -72,18 +82,140 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
72
82
  const typeMap = new Map();
73
83
  for (const param of params) {
74
84
  const type = getParameterType(param);
75
- typeMap.set(type, (typeMap.get(type) || 0) + 1);
76
- if (typeMap.get(type) > 1) {
85
+ const count = (typeMap.get(type) || 0) + 1;
86
+ typeMap.set(type, count);
87
+ if (count > 1) {
88
+ return true;
89
+ }
90
+ }
91
+ return false;
92
+ }
93
+ function isBuiltInOrThirdParty(node) {
94
+ // Check if the node is part of a new expression (constructor call)
95
+ let current = node;
96
+ while (current.parent) {
97
+ const parent = current.parent;
98
+ // Check if we're in a constructor call
99
+ if (parent.type === utils_1.AST_NODE_TYPES.NewExpression) {
100
+ const callee = parent.callee;
101
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
102
+ // List of built-in objects that should be ignored
103
+ const builtInObjects = new Set([
104
+ 'Promise',
105
+ 'Map',
106
+ 'Set',
107
+ 'WeakMap',
108
+ 'WeakSet',
109
+ 'Int8Array',
110
+ 'Uint8Array',
111
+ 'Uint8ClampedArray',
112
+ 'Int16Array',
113
+ 'Uint16Array',
114
+ 'Int32Array',
115
+ 'Uint32Array',
116
+ 'Float32Array',
117
+ 'Float64Array',
118
+ 'BigInt64Array',
119
+ 'BigUint64Array',
120
+ 'ArrayBuffer',
121
+ 'SharedArrayBuffer',
122
+ 'DataView',
123
+ 'Date',
124
+ 'RegExp',
125
+ 'Error',
126
+ 'AggregateError',
127
+ 'EvalError',
128
+ 'RangeError',
129
+ 'ReferenceError',
130
+ 'SyntaxError',
131
+ 'TypeError',
132
+ 'URIError',
133
+ 'Transform', // Added Transform to built-in objects
134
+ ]);
135
+ if (builtInObjects.has(callee.name)) {
136
+ return true;
137
+ }
138
+ // Check if the identifier is imported from a third-party module
139
+ const scope = context.getScope();
140
+ const variable = scope.variables.find((v) => v.name === callee.name);
141
+ if (variable) {
142
+ const def = variable.defs[0];
143
+ if (def?.type === 'ImportBinding') {
144
+ const importDecl = def.parent;
145
+ let source;
146
+ if (importDecl.type === utils_1.AST_NODE_TYPES.ImportDeclaration) {
147
+ source = importDecl.source.value;
148
+ }
149
+ else if (importDecl.type ===
150
+ utils_1.AST_NODE_TYPES.TSImportEqualsDeclaration &&
151
+ importDecl.moduleReference.type ===
152
+ utils_1.AST_NODE_TYPES.TSExternalModuleReference &&
153
+ importDecl.moduleReference.expression.type ===
154
+ utils_1.AST_NODE_TYPES.Literal) {
155
+ source = importDecl.moduleReference.expression
156
+ .value;
157
+ }
158
+ // If it's a third-party module (doesn't start with '.' or '/'), ignore it
159
+ if (source &&
160
+ !source.startsWith('.') &&
161
+ !source.startsWith('/')) {
162
+ return true;
163
+ }
164
+ }
165
+ }
166
+ }
167
+ else if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
168
+ // Handle cases like React.Component or lodash.debounce
169
+ const obj = callee.object;
170
+ if (obj.type === utils_1.AST_NODE_TYPES.Identifier) {
171
+ const scope = context.getScope();
172
+ const variable = scope.variables.find((v) => v.name === obj.name);
173
+ if (variable) {
174
+ const def = variable.defs[0];
175
+ if (def?.type === 'ImportBinding') {
176
+ const importDecl = def.parent;
177
+ let source;
178
+ if (importDecl.type === utils_1.AST_NODE_TYPES.ImportDeclaration) {
179
+ source = importDecl.source.value;
180
+ }
181
+ else if (importDecl.type ===
182
+ utils_1.AST_NODE_TYPES.TSImportEqualsDeclaration &&
183
+ importDecl.moduleReference.type ===
184
+ utils_1.AST_NODE_TYPES.TSExternalModuleReference &&
185
+ importDecl.moduleReference.expression.type ===
186
+ utils_1.AST_NODE_TYPES.Literal) {
187
+ source = importDecl.moduleReference.expression
188
+ .value;
189
+ }
190
+ // If it's a third-party module (doesn't start with '.' or '/'), ignore it
191
+ if (source &&
192
+ !source.startsWith('.') &&
193
+ !source.startsWith('/')) {
194
+ return true;
195
+ }
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+ // Also check if we're in a property of an object that's passed to a constructor
202
+ if (parent.type === utils_1.AST_NODE_TYPES.Property &&
203
+ parent.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
204
+ parent.parent.parent?.type === utils_1.AST_NODE_TYPES.NewExpression) {
77
205
  return true;
78
206
  }
207
+ current = parent;
79
208
  }
80
209
  return false;
81
210
  }
82
211
  function shouldIgnoreNode(node) {
212
+ // Ignore built-in objects and third-party modules
213
+ if (isBuiltInOrThirdParty(node))
214
+ return true;
83
215
  // Ignore variadic functions if configured
84
216
  if (finalOptions.ignoreVariadicFunctions) {
85
217
  const hasRestParam = node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration &&
86
- node.params.some(param => param.type === utils_1.AST_NODE_TYPES.RestElement);
218
+ node.params.some((param) => param.type === utils_1.AST_NODE_TYPES.RestElement);
87
219
  if (hasRestParam)
88
220
  return true;
89
221
  }
@@ -105,9 +237,7 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
105
237
  return;
106
238
  const params = node.params;
107
239
  // Check for too many parameters first
108
- const minParams = finalOptions.minimumParameters !== undefined
109
- ? finalOptions.minimumParameters
110
- : defaultOptions.minimumParameters;
240
+ const minParams = finalOptions.minimumParameters ?? defaultOptions.minimumParameters ?? 3;
111
241
  if (params.length >= minParams) {
112
242
  context.report({
113
243
  node,
@@ -4,6 +4,7 @@ exports.semanticFunctionPrefixes = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
6
  const DISALLOWED_PREFIXES = new Set(['get', 'update', 'check', 'manage', 'process', 'do']);
7
+ const NEXTJS_DATA_FUNCTIONS = new Set(['getServerSideProps', 'getStaticProps', 'getStaticPaths']);
7
8
  const SUGGESTED_ALTERNATIVES = {
8
9
  get: ['fetch', 'retrieve', 'compute', 'derive'],
9
10
  update: ['modify', 'set', 'apply'],
@@ -38,6 +39,9 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
38
39
  // Skip if method starts with 'is' (boolean check methods are okay)
39
40
  if (methodName.startsWith('is'))
40
41
  return;
42
+ // Skip Next.js data-fetching functions
43
+ if (NEXTJS_DATA_FUNCTIONS.has(methodName))
44
+ return;
41
45
  // Extract first word from PascalCase/camelCase
42
46
  let firstWord = methodName;
43
47
  for (let i = 1; i < methodName.length; i++) {
@@ -79,6 +83,9 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
79
83
  // Skip if function starts with 'is' (boolean check functions are okay)
80
84
  if (functionName.startsWith('is'))
81
85
  return;
86
+ // Skip Next.js data-fetching functions
87
+ if (NEXTJS_DATA_FUNCTIONS.has(functionName))
88
+ return;
82
89
  // Extract first word from PascalCase/camelCase
83
90
  let firstWord = functionName;
84
91
  for (let i = 1; i < functionName.length; i++) {
@@ -0,0 +1 @@
1
+ export declare const syncOnwriteNameFunc: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"mismatchedName", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.syncOnwriteNameFunc = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.syncOnwriteNameFunc = (0, createRule_1.createRule)({
7
+ name: 'sync-onwrite-name-func',
8
+ meta: {
9
+ type: 'problem',
10
+ docs: {
11
+ description: 'Ensure that the name field matches the func field in onWrite handlers',
12
+ recommended: 'error',
13
+ },
14
+ fixable: 'code',
15
+ schema: [],
16
+ messages: {
17
+ mismatchedName: 'The name field should match the func field in onWrite handlers',
18
+ },
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ return {
23
+ ObjectExpression(node) {
24
+ let nameProperty;
25
+ let funcProperty;
26
+ for (const property of node.properties) {
27
+ if (property.type !== utils_1.AST_NODE_TYPES.Property)
28
+ continue;
29
+ if (property.key.type === utils_1.AST_NODE_TYPES.Identifier &&
30
+ property.key.name === 'name' &&
31
+ property.value.type === utils_1.AST_NODE_TYPES.Literal &&
32
+ typeof property.value.value === 'string') {
33
+ nameProperty = property;
34
+ }
35
+ if (property.key.type === utils_1.AST_NODE_TYPES.Identifier &&
36
+ property.key.name === 'func') {
37
+ funcProperty = property;
38
+ }
39
+ }
40
+ if (!nameProperty || !funcProperty) {
41
+ return;
42
+ }
43
+ const nameValue = nameProperty.value.value;
44
+ let funcName;
45
+ // Handle variable references
46
+ if (funcProperty.value.type === utils_1.AST_NODE_TYPES.Identifier) {
47
+ const funcIdentifier = funcProperty.value;
48
+ const scope = context.getScope();
49
+ const variable = scope.references.find(ref => ref.identifier === funcIdentifier)?.resolved;
50
+ if (variable?.defs[0]?.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
51
+ variable.defs[0].node.init?.type === utils_1.AST_NODE_TYPES.Identifier) {
52
+ // If the variable is initialized with another identifier, use that name
53
+ funcName = variable.defs[0].node.init.name;
54
+ }
55
+ else {
56
+ // Otherwise use the variable name itself
57
+ funcName = funcIdentifier.name;
58
+ }
59
+ }
60
+ else {
61
+ return; // Skip if func is not an identifier
62
+ }
63
+ if (nameValue !== funcName) {
64
+ const value = nameProperty.value;
65
+ if (value.type === utils_1.AST_NODE_TYPES.Literal) {
66
+ context.report({
67
+ node: nameProperty,
68
+ messageId: 'mismatchedName',
69
+ fix(fixer) {
70
+ return fixer.replaceText(value, `'${funcName}'`);
71
+ },
72
+ });
73
+ }
74
+ }
75
+ },
76
+ };
77
+ },
78
+ });
79
+ //# sourceMappingURL=sync-onwrite-name-func.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.5.4",
3
+ "version": "1.6.0",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",