@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,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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",