@blumintinc/eslint-plugin-blumint 1.4.0 → 1.5.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.
Files changed (45) hide show
  1. package/lib/index.js +19 -1
  2. package/lib/rules/array-methods-this-context.js +2 -2
  3. package/lib/rules/enforce-callback-memo.js +3 -3
  4. package/lib/rules/enforce-dynamic-firebase-imports.js +2 -2
  5. package/lib/rules/enforce-exported-function-types.js +115 -85
  6. package/lib/rules/enforce-firestore-doc-ref-generic.js +2 -2
  7. package/lib/rules/enforce-firestore-path-utils.js +2 -2
  8. package/lib/rules/enforce-firestore-set-merge.js +35 -4
  9. package/lib/rules/enforce-identifiable-firestore-type.js +2 -2
  10. package/lib/rules/enforce-memoize-async.js +6 -4
  11. package/lib/rules/enforce-mock-firestore.js +3 -3
  12. package/lib/rules/enforce-realtimedb-path-utils.js +1 -1
  13. package/lib/rules/enforce-safe-stringify.js +2 -2
  14. package/lib/rules/enforce-serializable-params.js +3 -3
  15. package/lib/rules/enforce-verb-noun-naming.js +34 -12
  16. package/lib/rules/no-async-array-filter.js +2 -2
  17. package/lib/rules/no-async-foreach.js +1 -1
  18. package/lib/rules/no-class-instance-destructuring.d.ts +1 -0
  19. package/lib/rules/no-class-instance-destructuring.js +95 -0
  20. package/lib/rules/no-compositing-layer-props.js +1 -1
  21. package/lib/rules/no-conditional-literals-in-jsx.js +1 -1
  22. package/lib/rules/no-entire-object-hook-deps.js +1 -1
  23. package/lib/rules/no-explicit-return-type.d.ts +1 -0
  24. package/lib/rules/no-explicit-return-type.js +5 -2
  25. package/lib/rules/no-filter-without-return.js +1 -1
  26. package/lib/rules/no-firestore-object-arrays.d.ts +1 -0
  27. package/lib/rules/no-firestore-object-arrays.js +86 -0
  28. package/lib/rules/no-jsx-in-hooks.d.ts +1 -0
  29. package/lib/rules/no-jsx-in-hooks.js +200 -0
  30. package/lib/rules/no-memoize-on-static.d.ts +1 -0
  31. package/lib/rules/no-memoize-on-static.js +54 -0
  32. package/lib/rules/no-misused-switch-case.js +2 -2
  33. package/lib/rules/no-redundant-param-types.d.ts +2 -0
  34. package/lib/rules/no-redundant-param-types.js +129 -0
  35. package/lib/rules/no-unsafe-firestore-spread.d.ts +3 -0
  36. package/lib/rules/no-unsafe-firestore-spread.js +125 -0
  37. package/lib/rules/no-unused-props.js +1 -1
  38. package/lib/rules/no-useless-fragment.js +1 -1
  39. package/lib/rules/prefer-destructuring-no-class.d.ts +8 -0
  40. package/lib/rules/prefer-destructuring-no-class.js +200 -0
  41. package/lib/rules/require-hooks-default-params.d.ts +1 -0
  42. package/lib/rules/require-hooks-default-params.js +195 -0
  43. package/lib/rules/require-usememo-object-literals.js +1 -1
  44. package/lib/rules/semantic-function-prefixes.js +43 -4
  45. package/package.json +1 -1
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireHooksDefaultParams = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
7
+ name: 'require-hooks-default-params',
8
+ meta: {
9
+ type: 'suggestion',
10
+ docs: {
11
+ description: 'Enforce React hooks with optional parameters to default to an empty object',
12
+ recommended: 'error',
13
+ },
14
+ fixable: 'code',
15
+ schema: [],
16
+ messages: {
17
+ requireDefaultParams: 'React hooks with all optional parameters should default to an empty object',
18
+ },
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ function isHookName(name) {
23
+ return name.startsWith('use') && name[3]?.toUpperCase() === name[3];
24
+ }
25
+ function hasAllOptionalProperties(typeNode) {
26
+ // Handle type literals directly
27
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
28
+ return typeNode.members.every(member => {
29
+ if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
30
+ return false;
31
+ }
32
+ return member.optional === true;
33
+ });
34
+ }
35
+ // Handle type references
36
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
37
+ const typeName = typeNode.typeName;
38
+ if (typeName.type !== utils_1.AST_NODE_TYPES.Identifier) {
39
+ return false;
40
+ }
41
+ const scope = context.getScope();
42
+ const variable = scope.variables.find(v => v.name === typeName.name);
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
45
+ // This handles cases where the type is imported from another module
46
+ return true;
47
+ }
48
+ const def = variable.defs[0].node;
49
+ if (def.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
50
+ return hasAllOptionalProperties(def.typeAnnotation);
51
+ }
52
+ else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
53
+ return def.body.body.every(member => {
54
+ if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
55
+ return false;
56
+ }
57
+ return member.optional === true;
58
+ });
59
+ }
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
62
+ // This handles cases where the type is imported from another module
63
+ return true;
64
+ }
65
+ // Handle type alias declarations
66
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
67
+ return hasAllOptionalProperties(typeNode.typeAnnotation);
68
+ }
69
+ // Handle interface declarations
70
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
71
+ return typeNode.body.body.every(member => {
72
+ if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
73
+ return false;
74
+ }
75
+ return member.optional === true;
76
+ });
77
+ }
78
+ return false;
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
86
+ }
87
+ }
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
+ });
122
+ }
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) {
127
+ 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
+ });
139
+ }
140
+ }
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
145
+ context.report({
146
+ node: param,
147
+ messageId: 'requireDefaultParams',
148
+ fix(fixer) {
149
+ const paramText = context.getSourceCode().getText(param);
150
+ return fixer.replaceText(param, `${paramText} = {}`);
151
+ },
152
+ });
153
+ }
154
+ }
155
+ }
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
+ },
192
+ };
193
+ },
194
+ });
195
+ //# sourceMappingURL=require-hooks-default-params.js.map
@@ -7,7 +7,7 @@ exports.requireUseMemoObjectLiterals = (0, createRule_1.createRule)({
7
7
  meta: {
8
8
  type: 'suggestion',
9
9
  docs: {
10
- description: 'Enforce using useMemo for inline object literals passed as props to JSX components',
10
+ description: 'Enforce using useMemo for inline object/array literals passed as props to JSX components to prevent unnecessary re-renders. When object/array literals are defined inline in JSX, they create new references on every render, causing child components to re-render even if the values haven\'t changed. Wrap them in useMemo to maintain referential equality.',
11
11
  recommended: 'error',
12
12
  },
13
13
  messages: {
@@ -27,6 +27,40 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
27
27
  },
28
28
  defaultOptions: [],
29
29
  create(context) {
30
+ function checkMethodName(node) {
31
+ // Skip getters and setters
32
+ if (node.kind === 'get' || node.kind === 'set') {
33
+ return;
34
+ }
35
+ const methodName = node.key.type === utils_1.AST_NODE_TYPES.Identifier ? node.key.name : '';
36
+ if (!methodName)
37
+ return;
38
+ // Skip if method starts with 'is' (boolean check methods are okay)
39
+ if (methodName.startsWith('is'))
40
+ return;
41
+ // Extract first word from PascalCase/camelCase
42
+ let firstWord = methodName;
43
+ for (let i = 1; i < methodName.length; i++) {
44
+ if (methodName[i] >= 'A' && methodName[i] <= 'Z') {
45
+ firstWord = methodName.substring(0, i);
46
+ break;
47
+ }
48
+ }
49
+ // Check for disallowed prefixes
50
+ for (const prefix of DISALLOWED_PREFIXES) {
51
+ if (firstWord.toLowerCase() === prefix.toLowerCase()) {
52
+ context.report({
53
+ node: node.key,
54
+ messageId: 'avoidGenericPrefix',
55
+ data: {
56
+ prefix,
57
+ alternatives: SUGGESTED_ALTERNATIVES[prefix].join(', '),
58
+ },
59
+ });
60
+ break;
61
+ }
62
+ }
63
+ }
30
64
  function checkFunctionName(node) {
31
65
  // Skip anonymous functions
32
66
  if (!node.id && node.parent?.type !== utils_1.AST_NODE_TYPES.VariableDeclarator) {
@@ -45,13 +79,17 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
45
79
  // Skip if function starts with 'is' (boolean check functions are okay)
46
80
  if (functionName.startsWith('is'))
47
81
  return;
48
- // Skip class getters
49
- if (node.parent?.type === utils_1.AST_NODE_TYPES.MethodDefinition && node.parent.kind === 'get') {
50
- return;
82
+ // Extract first word from PascalCase/camelCase
83
+ let firstWord = functionName;
84
+ for (let i = 1; i < functionName.length; i++) {
85
+ if (functionName[i] >= 'A' && functionName[i] <= 'Z') {
86
+ firstWord = functionName.substring(0, i);
87
+ break;
88
+ }
51
89
  }
52
90
  // Check for disallowed prefixes
53
91
  for (const prefix of DISALLOWED_PREFIXES) {
54
- if (functionName.toLowerCase().startsWith(prefix.toLowerCase())) {
92
+ if (firstWord.toLowerCase() === prefix.toLowerCase()) {
55
93
  context.report({
56
94
  node: node.id || node,
57
95
  messageId: 'avoidGenericPrefix',
@@ -68,6 +106,7 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
68
106
  FunctionDeclaration: checkFunctionName,
69
107
  FunctionExpression: checkFunctionName,
70
108
  ArrowFunctionExpression: checkFunctionName,
109
+ MethodDefinition: checkMethodName,
71
110
  };
72
111
  },
73
112
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",