@blumintinc/eslint-plugin-blumint 1.1.9 → 1.2.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 CHANGED
@@ -59,12 +59,14 @@ Or use the recommended config:
59
59
  | Name                                | Description | 💼 | ⚠️ | 🔧 |
60
60
  | :--------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- |
61
61
  | [array-methods-this-context](docs/rules/array-methods-this-context.md) | Prevent misuse of Array methods in OOP | | ✅ | |
62
+ | [avoid-utils-directory](docs/rules/avoid-utils-directory.md) | Enforce using util/ instead of utils/ directory | ✅ | | 🔧 |
62
63
  | [class-methods-read-top-to-bottom](docs/rules/class-methods-read-top-to-bottom.md) | Ensures classes read linearly from top to bottom. | | ✅ | 🔧 |
63
64
  | [consistent-callback-naming](docs/rules/consistent-callback-naming.md) | Enforce consistent naming conventions for callback props and functions | ✅ | | 🔧 |
64
65
  | [dynamic-https-errors](docs/rules/dynamic-https-errors.md) | Dynamic error details should only be in the third argument of the HttpsError constructor. The second argument is hashed to produce a unique id. | | ✅ | |
65
66
  | [enforce-callable-types](docs/rules/enforce-callable-types.md) | Enforce Params and Response type exports in callable functions | ✅ | | |
66
67
  | [enforce-callback-memo](docs/rules/enforce-callback-memo.md) | Enforce useCallback or useMemo for inline functions in JSX props | ✅ | | |
67
68
  | [enforce-dynamic-firebase-imports](docs/rules/enforce-dynamic-firebase-imports.md) | Enforce dynamic importing for modules within the firebaseCloud directory | ✅ | | |
69
+ | [enforce-firestore-path-utils](docs/rules/enforce-firestore-path-utils.md) | Enforce usage of utility functions for Firestore paths | ✅ | | |
68
70
  | [enforce-identifiable-firestore-type](docs/rules/enforce-identifiable-firestore-type.md) | Enforce that Firestore type definitions extend Identifiable and match their folder name | ✅ | | |
69
71
  | [enforce-safe-stringify](docs/rules/enforce-safe-stringify.md) | Enforce using safe-stable-stringify instead of JSON.stringify | ✅ | | 🔧 |
70
72
  | [export-if-in-doubt](docs/rules/export-if-in-doubt.md) | All top-level const definitions, type definitions, and functions should be exported | | | |
@@ -74,6 +76,7 @@ Or use the recommended config:
74
76
  | [no-async-array-filter](docs/rules/no-async-array-filter.md) | Disallow async callbacks for Array.filter | ✅ | | |
75
77
  | [no-async-foreach](docs/rules/no-async-foreach.md) | Disallow Array.forEach with an async callback function | ✅ | | |
76
78
  | [no-conditional-literals-in-jsx](docs/rules/no-conditional-literals-in-jsx.md) | Disallow use of conditional literals in JSX code | ✅ | | |
79
+ | [no-entire-object-hook-deps](docs/rules/no-entire-object-hook-deps.md) | Avoid using entire objects in React hook dependency arrays when only specific fields are used | ✅ | | 🔧 |
77
80
  | [no-filter-without-return](docs/rules/no-filter-without-return.md) | Disallow Array.filter callbacks without an explicit return (if part of a block statement) | ✅ | | |
78
81
  | [no-jsx-whitespace-literal](docs/rules/no-jsx-whitespace-literal.md) | Disallow the use of {" "} elements in JSX code | ✅ | | |
79
82
  | [no-misused-switch-case](docs/rules/no-misused-switch-case.md) | Prevent misuse of logical OR in switch case statements | ✅ | | |
package/lib/index.js CHANGED
@@ -36,10 +36,11 @@ const enforce_safe_stringify_1 = require("./rules/enforce-safe-stringify");
36
36
  const avoid_utils_directory_1 = require("./rules/avoid-utils-directory");
37
37
  const no_entire_object_hook_deps_1 = require("./rules/no-entire-object-hook-deps");
38
38
  const enforce_firestore_path_utils_1 = require("./rules/enforce-firestore-path-utils");
39
+ const no_compositing_layer_props_1 = require("./rules/no-compositing-layer-props");
39
40
  module.exports = {
40
41
  meta: {
41
42
  name: '@blumintinc/eslint-plugin-blumint',
42
- version: '1.1.9',
43
+ version: '1.2.0',
43
44
  },
44
45
  parseOptions: {
45
46
  ecmaVersion: 2020,
@@ -81,6 +82,7 @@ module.exports = {
81
82
  '@blumintinc/blumint/require-usememo-object-literals': 'error',
82
83
  '@blumintinc/blumint/enforce-safe-stringify': 'error',
83
84
  '@blumintinc/blumint/no-entire-object-hook-deps': 'error',
85
+ '@blumintinc/blumint/no-compositing-layer-props': 'warn',
84
86
  },
85
87
  },
86
88
  },
@@ -118,6 +120,7 @@ module.exports = {
118
120
  'avoid-utils-directory': avoid_utils_directory_1.avoidUtilsDirectory,
119
121
  'no-entire-object-hook-deps': no_entire_object_hook_deps_1.noEntireObjectHookDeps,
120
122
  'enforce-firestore-path-utils': enforce_firestore_path_utils_1.enforceFirestorePathUtils,
123
+ 'no-compositing-layer-props': no_compositing_layer_props_1.noCompositingLayerProps,
121
124
  },
122
125
  };
123
126
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,3 @@
1
+ type MessageIds = 'missingExportedType' | 'missingExportedReturnType' | 'missingExportedPropsType';
2
+ export declare const enforceExportedFunctionTypes: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
3
+ export {};
@@ -0,0 +1,249 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enforceExportedFunctionTypes = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
7
+ name: 'enforce-exported-function-types',
8
+ meta: {
9
+ type: 'suggestion',
10
+ docs: {
11
+ description: 'Enforce exporting types for function props and return values',
12
+ recommended: 'error',
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ missingExportedType: 'Type {{typeName}} should be exported since it is used in an exported function',
17
+ missingExportedReturnType: 'Return type {{typeName}} should be exported since it is used in an exported function',
18
+ missingExportedPropsType: 'Props type {{typeName}} should be exported since it is used in an exported React component',
19
+ },
20
+ },
21
+ defaultOptions: [],
22
+ create(context) {
23
+ const reportedTypes = new Set();
24
+ function isExported(node) {
25
+ if (!node)
26
+ return false;
27
+ if (node.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration ||
28
+ node.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration) {
29
+ return true;
30
+ }
31
+ const parent = node.parent;
32
+ if (!parent)
33
+ return false;
34
+ if (parent.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration ||
35
+ parent.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration ||
36
+ parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator && isExported(parent.parent)) {
37
+ return true;
38
+ }
39
+ return false;
40
+ }
41
+ function getTypeName(node) {
42
+ if (!node)
43
+ return undefined;
44
+ switch (node.type) {
45
+ case utils_1.AST_NODE_TYPES.TSTypeReference:
46
+ if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
47
+ return node.typeName.name;
48
+ }
49
+ break;
50
+ case utils_1.AST_NODE_TYPES.TSTypeLiteral:
51
+ return 'AnonymousType';
52
+ }
53
+ return undefined;
54
+ }
55
+ function isTypeExported(typeName) {
56
+ const sourceCode = context.getSourceCode();
57
+ const program = sourceCode.ast;
58
+ // Check for exported type declarations
59
+ const exportedTypes = program.body.filter(node => {
60
+ if (node.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) {
61
+ if (node.declaration?.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
62
+ return node.declaration.id.name === typeName;
63
+ }
64
+ if (node.declaration?.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
65
+ return node.declaration.id.name === typeName;
66
+ }
67
+ }
68
+ return false;
69
+ });
70
+ if (exportedTypes.length > 0) {
71
+ return true;
72
+ }
73
+ // Check for type aliases in the current scope
74
+ const scope = context.getScope();
75
+ const variable = scope.variables.find(v => v.name === typeName);
76
+ if (!variable)
77
+ return false;
78
+ const def = variable.defs[0];
79
+ if (!def)
80
+ return false;
81
+ // Check if the type is directly exported
82
+ if (isExported(def.node))
83
+ return true;
84
+ // Check if the type is part of an export declaration
85
+ const parent = def.node.parent;
86
+ if (!parent)
87
+ return false;
88
+ // Handle type aliases in export declarations
89
+ if (parent.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) {
90
+ return true;
91
+ }
92
+ // Handle type aliases in variable declarations
93
+ if (parent.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
94
+ if (parent.parent?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) {
95
+ return true;
96
+ }
97
+ }
98
+ // Handle type aliases in interface declarations
99
+ if (parent.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
100
+ if (parent.parent?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) {
101
+ return true;
102
+ }
103
+ }
104
+ return false;
105
+ }
106
+ return {
107
+ FunctionDeclaration(node) {
108
+ if (!isExported(node))
109
+ return;
110
+ // Skip React components
111
+ if (node.id?.name && /^[A-Z]/.test(node.id.name))
112
+ return;
113
+ // Check return type
114
+ if (node.returnType?.typeAnnotation) {
115
+ const typeName = getTypeName(node.returnType.typeAnnotation);
116
+ if (typeName && !isTypeExported(typeName)) {
117
+ // Check if we've already reported this type
118
+ const key = `${typeName}-${node.loc?.start.line}-${node.loc?.start.column}`;
119
+ if (!reportedTypes.has(key)) {
120
+ reportedTypes.add(key);
121
+ context.report({
122
+ node: node.returnType,
123
+ messageId: 'missingExportedReturnType',
124
+ data: { typeName },
125
+ });
126
+ }
127
+ }
128
+ }
129
+ // Check parameter types
130
+ node.params.forEach(param => {
131
+ if (param.type === utils_1.AST_NODE_TYPES.Identifier && param.typeAnnotation) {
132
+ const typeName = getTypeName(param.typeAnnotation.typeAnnotation);
133
+ if (typeName && !isTypeExported(typeName)) {
134
+ // Check if we've already reported this type
135
+ const key = `${typeName}-${param.loc?.start.line}-${param.loc?.start.column}`;
136
+ if (!reportedTypes.has(key)) {
137
+ reportedTypes.add(key);
138
+ context.report({
139
+ node: param.typeAnnotation,
140
+ messageId: 'missingExportedType',
141
+ data: { typeName },
142
+ });
143
+ }
144
+ }
145
+ }
146
+ });
147
+ },
148
+ 'VariableDeclarator > ArrowFunctionExpression'(node) {
149
+ if (!node.parent?.parent || !isExported(node.parent.parent))
150
+ return;
151
+ // Skip React components
152
+ if (node.parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
153
+ node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier &&
154
+ /^[A-Z]/.test(node.parent.id.name))
155
+ return;
156
+ // Check return type
157
+ if (node.returnType?.typeAnnotation) {
158
+ const typeName = getTypeName(node.returnType.typeAnnotation);
159
+ if (typeName && !isTypeExported(typeName)) {
160
+ // Check if we've already reported this type
161
+ const key = `${typeName}-${node.loc?.start.line}-${node.loc?.start.column}`;
162
+ if (!reportedTypes.has(key)) {
163
+ reportedTypes.add(key);
164
+ context.report({
165
+ node: node.returnType,
166
+ messageId: 'missingExportedReturnType',
167
+ data: { typeName },
168
+ });
169
+ }
170
+ }
171
+ }
172
+ // Check parameter types
173
+ node.params.forEach(param => {
174
+ if (param.type === utils_1.AST_NODE_TYPES.Identifier && param.typeAnnotation) {
175
+ const typeName = getTypeName(param.typeAnnotation.typeAnnotation);
176
+ if (typeName && !isTypeExported(typeName)) {
177
+ // Check if we've already reported this type
178
+ const key = `${typeName}-${param.loc?.start.line}-${param.loc?.start.column}`;
179
+ if (!reportedTypes.has(key)) {
180
+ reportedTypes.add(key);
181
+ context.report({
182
+ node: param.typeAnnotation,
183
+ messageId: 'missingExportedType',
184
+ data: { typeName },
185
+ });
186
+ }
187
+ }
188
+ }
189
+ });
190
+ },
191
+ // Handle React components
192
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation]'(node) {
193
+ if (!isExported(node.parent))
194
+ return;
195
+ // Check props parameter
196
+ if (node.typeAnnotation) {
197
+ const typeName = getTypeName(node.typeAnnotation.typeAnnotation);
198
+ if (typeName && !isTypeExported(typeName)) {
199
+ // Check if we've already reported this type
200
+ const key = `${typeName}-${node.loc?.start.line}-${node.loc?.start.column}`;
201
+ if (!reportedTypes.has(key)) {
202
+ reportedTypes.add(key);
203
+ context.report({
204
+ node: node.typeAnnotation,
205
+ messageId: 'missingExportedType',
206
+ data: { typeName },
207
+ });
208
+ return;
209
+ }
210
+ }
211
+ }
212
+ },
213
+ // Skip type checking for React components since we handle them separately
214
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > TSTypeAnnotation'() { },
215
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > TSTypeAnnotation > TSTypeReference'() { },
216
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > TSTypeAnnotation'() { },
217
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > TSTypeAnnotation > TSTypeReference'() { },
218
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation'() { },
219
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation'() { },
220
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference'() { },
221
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference'() { },
222
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > Identifier'() { },
223
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > Identifier'() { },
224
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName'() { },
225
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName'() { },
226
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > Identifier'() { },
227
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > Identifier'() { },
228
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName'() { },
229
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName'() { },
230
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > Identifier'() { },
231
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > Identifier'() { },
232
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
233
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
234
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
235
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
236
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
237
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
238
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
239
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
240
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
241
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
242
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
243
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
244
+ 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
245
+ 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
246
+ };
247
+ },
248
+ });
249
+ //# sourceMappingURL=enforce-exported-function-types.js.map
@@ -0,0 +1 @@
1
+ export declare const enforceMemoizeAsync: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"requireMemoize", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enforceMemoizeAsync = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.enforceMemoizeAsync = (0, createRule_1.createRule)({
7
+ name: 'enforce-memoize-async',
8
+ meta: {
9
+ type: 'suggestion',
10
+ docs: {
11
+ description: 'Enforce @Memoize() decorator on async methods with 0-1 parameters',
12
+ recommended: 'error',
13
+ },
14
+ fixable: 'code',
15
+ schema: [],
16
+ messages: {
17
+ requireMemoize: 'Async methods with 0-1 parameters should be decorated with @Memoize()',
18
+ },
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ let hasMemoizeImport = false;
23
+ let memoizeAlias = 'Memoize';
24
+ return {
25
+ ImportDeclaration(node) {
26
+ if (node.source.value === 'typescript-memoize') {
27
+ const memoizeSpecifier = node.specifiers.find(spec => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
28
+ spec.imported.name === 'Memoize');
29
+ if (memoizeSpecifier) {
30
+ hasMemoizeImport = true;
31
+ if (memoizeSpecifier.local) {
32
+ memoizeAlias = memoizeSpecifier.local.name;
33
+ }
34
+ }
35
+ }
36
+ },
37
+ MethodDefinition(node) {
38
+ // Only process async methods
39
+ if (node.value.type !== utils_1.AST_NODE_TYPES.FunctionExpression || !node.value.async) {
40
+ return;
41
+ }
42
+ // Skip methods with more than one parameter
43
+ if (node.value.params.length > 1) {
44
+ return;
45
+ }
46
+ // Check if method already has @Memoize decorator
47
+ const hasDecorator = node.decorators?.some(decorator => {
48
+ if (decorator.expression.type !== utils_1.AST_NODE_TYPES.CallExpression) {
49
+ return false;
50
+ }
51
+ const callee = decorator.expression.callee;
52
+ return (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
53
+ callee.name === memoizeAlias);
54
+ });
55
+ if (!hasDecorator && hasMemoizeImport) {
56
+ context.report({
57
+ node,
58
+ messageId: 'requireMemoize',
59
+ fix(fixer) {
60
+ // Add import if needed
61
+ // Add decorator
62
+ return fixer.insertTextBefore(node, `@${memoizeAlias}()\n${' '.repeat(node.loc.start.column)}`);
63
+ },
64
+ });
65
+ }
66
+ },
67
+ };
68
+ },
69
+ });
70
+ //# sourceMappingURL=enforce-memoize-async.js.map
@@ -0,0 +1 @@
1
+ export declare const noCompositingLayerProps: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"compositingLayer", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noCompositingLayerProps = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ // Convert camelCase to kebab-case
7
+ function toKebabCase(str) {
8
+ return str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
9
+ }
10
+ // Normalize property name to kebab-case for consistent lookup
11
+ function normalizePropertyName(name) {
12
+ // If already contains hyphens, assume it's kebab-case
13
+ if (name.includes('-'))
14
+ return name.toLowerCase();
15
+ // Convert camelCase to kebab-case
16
+ return toKebabCase(name).toLowerCase();
17
+ }
18
+ const COMPOSITING_PROPERTIES = new Set([
19
+ 'filter',
20
+ 'backdrop-filter',
21
+ 'will-change',
22
+ 'transform',
23
+ 'perspective',
24
+ 'backface-visibility',
25
+ 'contain',
26
+ 'mix-blend-mode',
27
+ 'opacity',
28
+ ]);
29
+ const COMPOSITING_VALUES = new Set([
30
+ 'translate3d',
31
+ 'scale3d',
32
+ 'translateZ',
33
+ 'transparent',
34
+ ]);
35
+ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
36
+ name: 'no-compositing-layer-props',
37
+ meta: {
38
+ type: 'suggestion',
39
+ docs: {
40
+ description: 'Warn when using CSS properties that trigger compositing layers',
41
+ recommended: 'warn',
42
+ },
43
+ schema: [],
44
+ messages: {
45
+ compositingLayer: '{{property}} may trigger a new compositing layer which can impact performance. Consider using alternative properties or add an eslint-disable comment if the layer promotion is intentional.',
46
+ },
47
+ },
48
+ defaultOptions: [],
49
+ create(context) {
50
+ const seenNodes = new WeakSet();
51
+ function checkPropertyValue(value) {
52
+ return COMPOSITING_VALUES.has(value) ||
53
+ value.includes('translate3d') ||
54
+ value.includes('scale3d') ||
55
+ value.includes('translateZ');
56
+ }
57
+ function checkProperty(propertyName, propertyValue) {
58
+ const normalizedName = normalizePropertyName(propertyName);
59
+ if (COMPOSITING_PROPERTIES.has(normalizedName)) {
60
+ // Special case for opacity - only warn if it's animated or fractional
61
+ if (normalizedName === 'opacity') {
62
+ if (!propertyValue)
63
+ return false;
64
+ const numValue = Number.parseFloat(propertyValue);
65
+ if (Number.isNaN(numValue))
66
+ return false;
67
+ return numValue > 0 && numValue < 1;
68
+ }
69
+ return true;
70
+ }
71
+ if (propertyValue && checkPropertyValue(propertyValue)) {
72
+ return true;
73
+ }
74
+ return false;
75
+ }
76
+ function checkNode(node) {
77
+ // Skip if we've already processed this node
78
+ if (seenNodes.has(node))
79
+ return;
80
+ seenNodes.add(node);
81
+ let propertyName = '';
82
+ let propertyValue = '';
83
+ // Get property name
84
+ if (node.key.type === utils_1.AST_NODE_TYPES.Identifier) {
85
+ propertyName = node.key.name;
86
+ }
87
+ else if (node.key.type === utils_1.AST_NODE_TYPES.Literal) {
88
+ propertyName = String(node.key.value);
89
+ }
90
+ // Get property value if it's a string literal or numeric literal
91
+ if (node.value.type === utils_1.AST_NODE_TYPES.Literal) {
92
+ propertyValue = String(node.value.value);
93
+ }
94
+ if (checkProperty(propertyName, propertyValue)) {
95
+ context.report({
96
+ node,
97
+ messageId: 'compositingLayer',
98
+ data: {
99
+ property: propertyName,
100
+ },
101
+ });
102
+ }
103
+ }
104
+ return {
105
+ // Handle object literal properties (inline styles)
106
+ Property(node) {
107
+ if (node.parent?.type !== utils_1.AST_NODE_TYPES.ObjectExpression)
108
+ return;
109
+ checkNode(node);
110
+ },
111
+ // Handle JSX style attributes
112
+ JSXAttribute(node) {
113
+ if (node.name.name !== 'style')
114
+ return;
115
+ if (node.value?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer &&
116
+ node.value.expression.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
117
+ node.value.expression.properties.forEach((prop) => {
118
+ if (prop.type === utils_1.AST_NODE_TYPES.Property) {
119
+ checkNode(prop);
120
+ }
121
+ });
122
+ }
123
+ },
124
+ };
125
+ },
126
+ });
127
+ //# sourceMappingURL=no-compositing-layer-props.js.map
@@ -0,0 +1,11 @@
1
+ type MessageIds = 'tooManyParams' | 'sameTypeParams';
2
+ type Options = [
3
+ {
4
+ minimumParameters?: number;
5
+ checkSameTypeParameters?: boolean;
6
+ ignoreBoundMethods?: boolean;
7
+ ignoreVariadicFunctions?: boolean;
8
+ }
9
+ ];
10
+ export declare const preferSettingsObject: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
11
+ export {};
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.preferSettingsObject = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const defaultOptions = {
7
+ minimumParameters: 3,
8
+ checkSameTypeParameters: true,
9
+ ignoreBoundMethods: true,
10
+ ignoreVariadicFunctions: true,
11
+ };
12
+ exports.preferSettingsObject = (0, createRule_1.createRule)({
13
+ name: 'prefer-settings-object',
14
+ meta: {
15
+ type: 'suggestion',
16
+ docs: {
17
+ description: 'Enforce using a settings object for functions with multiple parameters',
18
+ recommended: 'error',
19
+ },
20
+ fixable: 'code',
21
+ schema: [
22
+ {
23
+ type: 'object',
24
+ properties: {
25
+ minimumParameters: {
26
+ type: 'number',
27
+ minimum: 2,
28
+ },
29
+ checkSameTypeParameters: {
30
+ type: 'boolean',
31
+ },
32
+ ignoreBoundMethods: {
33
+ type: 'boolean',
34
+ },
35
+ ignoreVariadicFunctions: {
36
+ type: 'boolean',
37
+ },
38
+ },
39
+ additionalProperties: false,
40
+ },
41
+ ],
42
+ messages: {
43
+ tooManyParams: 'Function has too many parameters ({{count}}). Use a settings object instead.',
44
+ sameTypeParams: 'Function has multiple parameters of the same type. Use a settings object instead.',
45
+ },
46
+ },
47
+ defaultOptions: [defaultOptions],
48
+ create(context, [options]) {
49
+ const finalOptions = { ...defaultOptions, ...options };
50
+ function getParameterType(param) {
51
+ if (param.type === utils_1.AST_NODE_TYPES.Identifier && param.typeAnnotation) {
52
+ const typeNode = param.typeAnnotation.typeAnnotation;
53
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
54
+ return typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier
55
+ ? typeNode.typeName.name
56
+ : 'unknown';
57
+ }
58
+ return typeNode.type;
59
+ }
60
+ return 'unknown';
61
+ }
62
+ function hasSameTypeParameters(params) {
63
+ const typeMap = new Map();
64
+ for (const param of params) {
65
+ const type = getParameterType(param);
66
+ typeMap.set(type, (typeMap.get(type) || 0) + 1);
67
+ if (typeMap.get(type) > 1) {
68
+ return true;
69
+ }
70
+ }
71
+ return false;
72
+ }
73
+ function shouldIgnoreNode(node) {
74
+ // Ignore variadic functions if configured
75
+ if (finalOptions.ignoreVariadicFunctions) {
76
+ const hasRestParam = node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration &&
77
+ node.params.some(param => param.type === utils_1.AST_NODE_TYPES.RestElement);
78
+ if (hasRestParam)
79
+ return true;
80
+ }
81
+ // Ignore bound methods if configured
82
+ if (finalOptions.ignoreBoundMethods) {
83
+ let parent = node.parent;
84
+ while (parent) {
85
+ if (parent.type === utils_1.AST_NODE_TYPES.CallExpression ||
86
+ parent.type === utils_1.AST_NODE_TYPES.TSCallSignatureDeclaration) {
87
+ return true;
88
+ }
89
+ parent = parent.parent;
90
+ }
91
+ }
92
+ return false;
93
+ }
94
+ function checkFunction(node) {
95
+ if (shouldIgnoreNode(node))
96
+ return;
97
+ const params = node.params;
98
+ // Check for too many parameters
99
+ const minParams = finalOptions.minimumParameters !== undefined
100
+ ? finalOptions.minimumParameters
101
+ : defaultOptions.minimumParameters;
102
+ if (params.length >= minParams) {
103
+ context.report({
104
+ node,
105
+ messageId: 'tooManyParams',
106
+ data: { count: params.length },
107
+ });
108
+ return;
109
+ }
110
+ // Check for same type parameters if enabled
111
+ if (finalOptions.checkSameTypeParameters && params.length >= 2) {
112
+ if (hasSameTypeParameters(params)) {
113
+ context.report({
114
+ node,
115
+ messageId: 'sameTypeParams',
116
+ });
117
+ }
118
+ }
119
+ }
120
+ return {
121
+ FunctionDeclaration: checkFunction,
122
+ FunctionExpression: checkFunction,
123
+ ArrowFunctionExpression: checkFunction,
124
+ TSMethodSignature: checkFunction,
125
+ TSFunctionType: checkFunction,
126
+ };
127
+ },
128
+ });
129
+ //# sourceMappingURL=prefer-settings-object.js.map
@@ -7,6 +7,7 @@ module.exports = (0, createRule_1.createRule)({
7
7
  docs: {
8
8
  description: 'Enforce using ImageOverlayed component instead of next/image or img tags',
9
9
  recommended: 'error',
10
+ requiresTypeChecking: false,
10
11
  },
11
12
  fixable: 'code',
12
13
  schema: [
@@ -15,6 +16,7 @@ module.exports = (0, createRule_1.createRule)({
15
16
  properties: {
16
17
  componentPath: {
17
18
  type: 'string',
19
+ description: 'The import path for the ImageOverlayed component',
18
20
  default: 'src/components/ImageOverlayed',
19
21
  },
20
22
  },
@@ -0,0 +1 @@
1
+ export declare const useCustomMemo: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"useCustomMemo", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useCustomMemo = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.useCustomMemo = (0, createRule_1.createRule)({
7
+ name: 'use-custom-memo',
8
+ meta: {
9
+ type: 'suggestion',
10
+ docs: {
11
+ description: 'Enforce using src/util/memo instead of React memo',
12
+ recommended: 'error',
13
+ },
14
+ fixable: 'code',
15
+ schema: [],
16
+ messages: {
17
+ useCustomMemo: 'Import memo from src/util/memo instead of react',
18
+ },
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ return {
23
+ ImportDeclaration(node) {
24
+ if (node.source.value === 'react') {
25
+ const specifiers = node.specifiers.filter((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
26
+ specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
27
+ specifier.imported.name === 'memo');
28
+ if (specifiers.length > 0) {
29
+ context.report({
30
+ node,
31
+ messageId: 'useCustomMemo',
32
+ fix(fixer) {
33
+ // If there are other imports from react, keep them
34
+ const otherSpecifiers = node.specifiers.filter((specifier) => specifier.type !== utils_1.AST_NODE_TYPES.ImportSpecifier ||
35
+ (specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
36
+ specifier.imported.name !== 'memo'));
37
+ if (otherSpecifiers.length === 0) {
38
+ // If memo is the only import, replace the entire import
39
+ return fixer.replaceText(node, `import { ${specifiers
40
+ .map((s) => s.local.name !== s.imported.name
41
+ ? `memo as ${s.local.name}`
42
+ : 'memo')
43
+ .join(', ')} } from 'src/util/memo';`);
44
+ }
45
+ else {
46
+ // Create a new import for memo and keep other imports
47
+ const memoImport = `import { ${specifiers
48
+ .map((s) => s.local.name !== s.imported.name
49
+ ? `memo as ${s.local.name}`
50
+ : 'memo')
51
+ .join(', ')} } from 'src/util/memo';\n`;
52
+ const otherImports = `import { ${otherSpecifiers
53
+ .map((s) => s.local.name)
54
+ .join(', ')} } from 'react';`;
55
+ return fixer.replaceText(node, memoImport + otherImports);
56
+ }
57
+ },
58
+ });
59
+ }
60
+ }
61
+ },
62
+ };
63
+ },
64
+ });
65
+ //# sourceMappingURL=use-custom-memo.js.map
@@ -91,6 +91,20 @@ class ASTHelpers {
91
91
  return node.expressions.some((expr) => this.declarationIncludesIdentifier(expr));
92
92
  case 'TSAsExpression':
93
93
  return this.declarationIncludesIdentifier(node.expression);
94
+ case 'TSTypeReference':
95
+ // Handle type references (e.g., T in generic types)
96
+ return false;
97
+ case 'TSTypeParameterDeclaration':
98
+ // Handle type parameter declarations (e.g., <T extends ...>)
99
+ return false;
100
+ case 'TSTypeParameterInstantiation':
101
+ // Handle type parameter instantiations (e.g., <string>)
102
+ return false;
103
+ case 'TSIntersectionType':
104
+ case 'TSUnionType':
105
+ case 'TSTypeLiteral':
106
+ // Handle type constraints and literals
107
+ return false;
94
108
  default:
95
109
  return false;
96
110
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.1.9",
3
+ "version": "1.2.0",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",