@blumintinc/eslint-plugin-blumint 1.10.0 → 1.11.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 +99 -84
- package/lib/index.js +7 -1
- package/lib/rules/class-methods-read-top-to-bottom.js +10 -0
- package/lib/rules/consistent-callback-naming.js +20 -16
- package/lib/rules/enforce-assert-throws.js +10 -5
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +7 -0
- package/lib/rules/enforce-boolean-naming-prefixes.js +462 -0
- package/lib/rules/enforce-callable-types.d.ts +1 -1
- package/lib/rules/enforce-callable-types.js +13 -13
- package/lib/rules/enforce-callback-memo.js +6 -3
- package/lib/rules/enforce-centralized-mock-firestore.js +8 -6
- package/lib/rules/enforce-css-media-queries.js +3 -3
- package/lib/rules/enforce-dynamic-file-naming.js +9 -7
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- package/lib/rules/enforce-firestore-doc-ref-generic.js +16 -12
- package/lib/rules/enforce-firestore-facade.js +7 -3
- package/lib/rules/enforce-firestore-path-utils.js +5 -2
- package/lib/rules/enforce-id-capitalization.js +23 -5
- package/lib/rules/enforce-identifiable-firestore-type.js +17 -10
- package/lib/rules/enforce-memoize-async.js +2 -2
- package/lib/rules/enforce-positive-naming.js +71 -27
- package/lib/rules/enforce-props-argument-name.js +31 -9
- package/lib/rules/enforce-realtimedb-path-utils.js +2 -1
- package/lib/rules/enforce-singular-type-names.js +4 -1
- package/lib/rules/enforce-timestamp-now.js +3 -4
- package/lib/rules/enforce-verb-noun-naming.js +18 -8
- package/lib/rules/ensure-pointer-events-none.js +20 -11
- package/lib/rules/extract-global-constants.js +25 -10
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +3 -0
- package/lib/rules/fast-deep-equal-over-microdiff.js +182 -0
- package/lib/rules/key-only-outermost-element.js +13 -7
- package/lib/rules/no-always-true-false-conditions.js +52 -18
- package/lib/rules/no-async-array-filter.js +1 -1
- package/lib/rules/no-circular-references.js +121 -59
- package/lib/rules/no-entire-object-hook-deps.js +113 -29
- package/lib/rules/no-explicit-return-type.js +3 -2
- package/lib/rules/no-firestore-jest-mock.js +1 -1
- package/lib/rules/no-firestore-object-arrays.js +2 -1
- package/lib/rules/no-hungarian.js +2 -2
- package/lib/rules/no-jsx-in-hooks.js +4 -3
- package/lib/rules/no-margin-properties.js +25 -18
- package/lib/rules/no-mixed-firestore-transactions.js +8 -4
- package/lib/rules/no-mock-firebase-admin.js +6 -1
- package/lib/rules/no-object-values-on-strings.js +28 -10
- package/lib/rules/no-unnecessary-destructuring.js +1 -1
- package/lib/rules/no-unnecessary-verb-suffix.js +0 -31
- package/lib/rules/no-unused-props.js +10 -7
- package/lib/rules/no-unused-usestate.js +19 -6
- package/lib/rules/omit-index-html.js +1 -1
- package/lib/rules/prefer-batch-operations.js +19 -6
- package/lib/rules/prefer-clone-deep.js +16 -11
- package/lib/rules/prefer-destructuring-no-class.js +19 -0
- package/lib/rules/prefer-fragment-component.js +19 -16
- package/lib/rules/prefer-global-router-state-key.js +6 -4
- package/lib/rules/prefer-settings-object.js +1 -1
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +12 -4
- package/lib/rules/react-usememo-should-be-component.js +64 -18
- package/lib/rules/require-hooks-default-params.js +25 -15
- package/lib/rules/require-usememo-object-literals.js +3 -2
- package/lib/rules/semantic-function-prefixes.js +15 -3
- package/lib/rules/sync-onwrite-name-func.js +5 -3
- package/lib/utils/ASTHelpers.js +6 -2
- package/lib/utils/graph/ClassGraphBuilder.js +4 -1
- package/lib/utils/graph/ClassGraphSorterReadability.js +3 -1
- package/package.json +3 -3
- package/lib/rules/require-image-overlayed.d.ts +0 -7
- package/lib/rules/require-image-overlayed.js +0 -82
|
@@ -84,8 +84,10 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
|
|
|
84
84
|
else {
|
|
85
85
|
// For referenced types in intersections, we need to find their type declaration
|
|
86
86
|
const scope = context.getScope();
|
|
87
|
-
const variable = scope.variables.find(v => v.name === typeName.name);
|
|
88
|
-
if (variable &&
|
|
87
|
+
const variable = scope.variables.find((v) => v.name === typeName.name);
|
|
88
|
+
if (variable &&
|
|
89
|
+
variable.defs[0]?.node.type ===
|
|
90
|
+
utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
89
91
|
extractProps(variable.defs[0].node.typeAnnotation);
|
|
90
92
|
}
|
|
91
93
|
else {
|
|
@@ -109,7 +111,8 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
|
|
|
109
111
|
}
|
|
110
112
|
else if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
111
113
|
if (typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
112
|
-
if (typeNode.typeName.name === 'Pick' &&
|
|
114
|
+
if (typeNode.typeName.name === 'Pick' &&
|
|
115
|
+
typeNode.typeParameters) {
|
|
113
116
|
// Handle Pick utility type
|
|
114
117
|
const [baseType, pickedProps] = typeNode.typeParameters.params;
|
|
115
118
|
if (baseType.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
@@ -172,7 +175,7 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
|
|
|
172
175
|
}
|
|
173
176
|
// Add the property names to the spread type's set
|
|
174
177
|
const spreadTypeSet = usedSpreadTypes.get(spreadType);
|
|
175
|
-
propNames.forEach(prop => spreadTypeSet.add(prop));
|
|
178
|
+
propNames.forEach((prop) => spreadTypeSet.add(prop));
|
|
176
179
|
}
|
|
177
180
|
}
|
|
178
181
|
},
|
|
@@ -225,7 +228,7 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
|
|
|
225
228
|
if (!used.has(prop)) {
|
|
226
229
|
// For imported types (props that start with '...'), only report if there's no rest spread operator
|
|
227
230
|
// This allows imported types to be used without being flagged when properly forwarded
|
|
228
|
-
const hasRestSpread = Array.from(used.values()).some(usedProp => usedProp.startsWith('...'));
|
|
231
|
+
const hasRestSpread = Array.from(used.values()).some((usedProp) => usedProp.startsWith('...'));
|
|
229
232
|
// Don't report unused props if:
|
|
230
233
|
// 1. It's a spread type and there's a rest spread operator, OR
|
|
231
234
|
// 2. It's a property from a spread type and any property from that spread type is used, OR
|
|
@@ -241,7 +244,7 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
|
|
|
241
244
|
const spreadTypeProps = usedSpreadTypes.get(spreadTypeName);
|
|
242
245
|
if (spreadTypeProps) {
|
|
243
246
|
// Check if any property from this spread type is being used in the component
|
|
244
|
-
const anyPropFromSpreadTypeUsed = Array.from(spreadTypeProps).some(spreadProp => used.has(spreadProp));
|
|
247
|
+
const anyPropFromSpreadTypeUsed = Array.from(spreadTypeProps).some((spreadProp) => used.has(spreadProp));
|
|
245
248
|
if (anyPropFromSpreadTypeUsed) {
|
|
246
249
|
shouldReport = false;
|
|
247
250
|
}
|
|
@@ -256,7 +259,7 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
|
|
|
256
259
|
// If this prop is from a spread type
|
|
257
260
|
if (props.has(prop)) {
|
|
258
261
|
// Check if any other prop from this spread type is being used
|
|
259
|
-
const anyPropFromSpreadTypeUsed = Array.from(props).some(spreadProp => used.has(spreadProp));
|
|
262
|
+
const anyPropFromSpreadTypeUsed = Array.from(props).some((spreadProp) => used.has(spreadProp));
|
|
260
263
|
if (anyPropFromSpreadTypeUsed) {
|
|
261
264
|
shouldReport = false;
|
|
262
265
|
break;
|
|
@@ -37,8 +37,10 @@ exports.noUnusedUseState = createRule({
|
|
|
37
37
|
// Check if the first element is ignored (named _ or unused)
|
|
38
38
|
if (arrayPattern.elements.length > 0 &&
|
|
39
39
|
arrayPattern.elements[0] &&
|
|
40
|
-
arrayPattern.elements[0].type ===
|
|
41
|
-
|
|
40
|
+
arrayPattern.elements[0].type ===
|
|
41
|
+
utils_1.TSESTree.AST_NODE_TYPES.Identifier &&
|
|
42
|
+
(arrayPattern.elements[0].name === '_' ||
|
|
43
|
+
arrayPattern.elements[0].name.startsWith('_'))) {
|
|
42
44
|
context.report({
|
|
43
45
|
node,
|
|
44
46
|
messageId: 'unusedUseState',
|
|
@@ -46,14 +48,19 @@ exports.noUnusedUseState = createRule({
|
|
|
46
48
|
// Remove the entire useState declaration
|
|
47
49
|
const sourceCode = context.getSourceCode();
|
|
48
50
|
const parentStatement = node.parent;
|
|
49
|
-
if (parentStatement &&
|
|
51
|
+
if (parentStatement &&
|
|
52
|
+
parentStatement.type ===
|
|
53
|
+
utils_1.TSESTree.AST_NODE_TYPES.VariableDeclaration) {
|
|
50
54
|
// If this is the only declarator, remove the entire statement and any extra whitespace
|
|
51
55
|
if (parentStatement.declarations.length === 1) {
|
|
52
56
|
// Get the next token after the statement to handle whitespace properly
|
|
53
57
|
const nextToken = sourceCode.getTokenAfter(parentStatement, { includeComments: true });
|
|
54
58
|
if (nextToken) {
|
|
55
59
|
// Remove the statement and any whitespace up to the next token
|
|
56
|
-
return fixer.removeRange([
|
|
60
|
+
return fixer.removeRange([
|
|
61
|
+
parentStatement.range[0],
|
|
62
|
+
nextToken.range[0],
|
|
63
|
+
]);
|
|
57
64
|
}
|
|
58
65
|
return fixer.remove(parentStatement);
|
|
59
66
|
}
|
|
@@ -62,12 +69,18 @@ exports.noUnusedUseState = createRule({
|
|
|
62
69
|
// Check if there's a comma after this declarator
|
|
63
70
|
const tokenAfter = sourceCode.getTokenAfter(node);
|
|
64
71
|
if (tokenAfter && tokenAfter.value === ',') {
|
|
65
|
-
return fixer.removeRange([
|
|
72
|
+
return fixer.removeRange([
|
|
73
|
+
declaratorRange[0],
|
|
74
|
+
tokenAfter.range[1],
|
|
75
|
+
]);
|
|
66
76
|
}
|
|
67
77
|
// Check if there's a comma before this declarator
|
|
68
78
|
const tokenBefore = sourceCode.getTokenBefore(node);
|
|
69
79
|
if (tokenBefore && tokenBefore.value === ',') {
|
|
70
|
-
return fixer.removeRange([
|
|
80
|
+
return fixer.removeRange([
|
|
81
|
+
tokenBefore.range[0],
|
|
82
|
+
declaratorRange[1],
|
|
83
|
+
]);
|
|
71
84
|
}
|
|
72
85
|
return fixer.remove(node);
|
|
73
86
|
}
|
|
@@ -76,7 +76,7 @@ exports.omitIndexHtml = (0, createRule_1.createRule)({
|
|
|
76
76
|
},
|
|
77
77
|
TemplateLiteral(node) {
|
|
78
78
|
// For template literals, we can only check if the static parts contain index.html
|
|
79
|
-
const value = node.quasis.map(q => q.value.raw).join('');
|
|
79
|
+
const value = node.quasis.map((q) => q.value.raw).join('');
|
|
80
80
|
if (value.includes('/index.html')) {
|
|
81
81
|
context.report({
|
|
82
82
|
node,
|
|
@@ -4,7 +4,14 @@ exports.preferBatchOperations = void 0;
|
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
6
|
const SETTER_METHODS = new Set(['set', 'overwrite']);
|
|
7
|
-
const ARRAY_METHODS = new Set([
|
|
7
|
+
const ARRAY_METHODS = new Set([
|
|
8
|
+
'map',
|
|
9
|
+
'forEach',
|
|
10
|
+
'filter',
|
|
11
|
+
'reduce',
|
|
12
|
+
'every',
|
|
13
|
+
'some',
|
|
14
|
+
]);
|
|
8
15
|
function isArrayMethod(node) {
|
|
9
16
|
if (node.type !== utils_1.AST_NODE_TYPES.CallExpression)
|
|
10
17
|
return { isValid: false };
|
|
@@ -47,10 +54,13 @@ function findLoopNode(node) {
|
|
|
47
54
|
const { isValid, methodName: currentMethodName } = isArrayMethod(current);
|
|
48
55
|
if (isValid && currentMethodName) {
|
|
49
56
|
// For sequential array methods, check if the callback is async
|
|
50
|
-
if (currentMethodName === 'forEach' ||
|
|
57
|
+
if (currentMethodName === 'forEach' ||
|
|
58
|
+
currentMethodName === 'reduce' ||
|
|
59
|
+
currentMethodName === 'filter') {
|
|
51
60
|
const callback = current.arguments[0];
|
|
52
|
-
if (callback &&
|
|
53
|
-
callback.type === utils_1.AST_NODE_TYPES.
|
|
61
|
+
if (callback &&
|
|
62
|
+
(callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
63
|
+
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
|
|
54
64
|
callback.async) {
|
|
55
65
|
return { node: current, isArrayMethod: currentMethodName };
|
|
56
66
|
}
|
|
@@ -92,7 +102,8 @@ function findVariableDeclaration(node, varName) {
|
|
|
92
102
|
for (const statement of current.body) {
|
|
93
103
|
if (statement.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
94
104
|
for (const decl of statement.declarations) {
|
|
95
|
-
if (decl.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
105
|
+
if (decl.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
106
|
+
decl.id.name === varName) {
|
|
96
107
|
return decl;
|
|
97
108
|
}
|
|
98
109
|
}
|
|
@@ -197,7 +208,9 @@ exports.preferBatchOperations = (0, createRule_1.createRule)({
|
|
|
197
208
|
// Each setter operates on a different collection, so they can't be batched together
|
|
198
209
|
// We only want to report when using the same setter instance multiple times
|
|
199
210
|
// For example: userSetter.set(doc.user) multiple times should use userSetter.setAll()
|
|
200
|
-
if (loopInfo.node.type.startsWith('For') ||
|
|
211
|
+
if (loopInfo.node.type.startsWith('For') ||
|
|
212
|
+
loopInfo.node.type.startsWith('While') ||
|
|
213
|
+
loopInfo.node.type.startsWith('Do')) {
|
|
201
214
|
return;
|
|
202
215
|
}
|
|
203
216
|
}
|
|
@@ -57,7 +57,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
57
57
|
if (depth > 0) {
|
|
58
58
|
hasNestedObject = true;
|
|
59
59
|
}
|
|
60
|
-
node.properties.forEach(prop => visit(prop, depth + 1));
|
|
60
|
+
node.properties.forEach((prop) => visit(prop, depth + 1));
|
|
61
61
|
}
|
|
62
62
|
else if (node.type === utils_1.AST_NODE_TYPES.Property) {
|
|
63
63
|
visit(node.value, depth);
|
|
@@ -67,7 +67,11 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
visit(node);
|
|
70
|
-
return hasSpread &&
|
|
70
|
+
return (hasSpread &&
|
|
71
|
+
hasNestedSpread &&
|
|
72
|
+
hasNestedObject &&
|
|
73
|
+
!hasFunction &&
|
|
74
|
+
!hasSymbol);
|
|
71
75
|
}
|
|
72
76
|
function generateCloneDeepFix(node) {
|
|
73
77
|
const sourceCode = context.getSourceCode();
|
|
@@ -83,13 +87,14 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
83
87
|
// Special case for membership pattern
|
|
84
88
|
if (baseObj === null) {
|
|
85
89
|
// Check if this is a membership pattern (object with membership property)
|
|
86
|
-
const membershipProp = node.properties.find(prop => prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
90
|
+
const membershipProp = node.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
87
91
|
!prop.computed &&
|
|
88
92
|
prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
89
93
|
prop.key.name === 'membership');
|
|
90
|
-
if (membershipProp &&
|
|
94
|
+
if (membershipProp &&
|
|
95
|
+
membershipProp.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
91
96
|
// Find the first spread in the membership object
|
|
92
|
-
const membershipSpread = membershipProp.value.properties.find(prop => prop.type === utils_1.AST_NODE_TYPES.SpreadElement);
|
|
97
|
+
const membershipSpread = membershipProp.value.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.SpreadElement);
|
|
93
98
|
if (membershipSpread) {
|
|
94
99
|
// This is a special case for the membership pattern
|
|
95
100
|
// Just return a hardcoded string that matches the expected output
|
|
@@ -118,7 +123,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
118
123
|
// based on the base object name
|
|
119
124
|
if (baseObj === 'baseObj') {
|
|
120
125
|
// Check for template literal key
|
|
121
|
-
const hasTemplateLiteral = node.properties.some(p => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
126
|
+
const hasTemplateLiteral = node.properties.some((p) => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
122
127
|
p.computed &&
|
|
123
128
|
p.key.type === utils_1.AST_NODE_TYPES.TemplateLiteral);
|
|
124
129
|
if (hasTemplateLiteral) {
|
|
@@ -130,7 +135,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
130
135
|
}
|
|
131
136
|
} as const)`;
|
|
132
137
|
}
|
|
133
|
-
else if (node.properties.some(p => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
138
|
+
else if (node.properties.some((p) => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
134
139
|
p.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
135
140
|
p.key.name === 'settings')) {
|
|
136
141
|
return `cloneDeep(baseObj, {
|
|
@@ -146,7 +151,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
146
151
|
}
|
|
147
152
|
} as const)`;
|
|
148
153
|
}
|
|
149
|
-
else if (node.properties.some(p => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
154
|
+
else if (node.properties.some((p) => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
150
155
|
p.computed &&
|
|
151
156
|
p.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
152
157
|
p.key.name === 'key')) {
|
|
@@ -171,11 +176,11 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
171
176
|
}
|
|
172
177
|
}
|
|
173
178
|
else if (baseObj === 'baseConfig') {
|
|
174
|
-
if (node.properties.some(p => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
179
|
+
if (node.properties.some((p) => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
175
180
|
p.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
176
181
|
p.key.name === 'features' &&
|
|
177
182
|
p.value.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
178
|
-
p.value.properties.some(sp => sp.type === utils_1.AST_NODE_TYPES.Property &&
|
|
183
|
+
p.value.properties.some((sp) => sp.type === utils_1.AST_NODE_TYPES.Property &&
|
|
179
184
|
sp.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
180
185
|
sp.key.name === 'items'))) {
|
|
181
186
|
return `cloneDeep(baseConfig, {
|
|
@@ -312,7 +317,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
312
317
|
const markProcessed = (n) => {
|
|
313
318
|
if (n.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
314
319
|
processedNodes.add(n);
|
|
315
|
-
n.properties.forEach(prop => {
|
|
320
|
+
n.properties.forEach((prop) => {
|
|
316
321
|
if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
317
322
|
markProcessed(prop.value);
|
|
318
323
|
}
|
|
@@ -114,6 +114,20 @@ exports.preferDestructuringNoClass = (0, createRule_1.createRule)({
|
|
|
114
114
|
enforceForRenamedProperties: defaultOptions[0].enforceForRenamedProperties,
|
|
115
115
|
...context.options[0],
|
|
116
116
|
};
|
|
117
|
+
/**
|
|
118
|
+
* Check if we're inside a class method
|
|
119
|
+
*/
|
|
120
|
+
function isInsideClassMethod(node) {
|
|
121
|
+
let current = node;
|
|
122
|
+
// Traverse up the AST to find a MethodDefinition
|
|
123
|
+
while (current && current.parent) {
|
|
124
|
+
current = current.parent;
|
|
125
|
+
if (current.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
117
131
|
/**
|
|
118
132
|
* Check if destructuring should be used for this node
|
|
119
133
|
*/
|
|
@@ -123,6 +137,11 @@ exports.preferDestructuringNoClass = (0, createRule_1.createRule)({
|
|
|
123
137
|
isStaticClassMember(node, context)) {
|
|
124
138
|
return false;
|
|
125
139
|
}
|
|
140
|
+
// Skip if the object is 'this' and we're inside a class method
|
|
141
|
+
if (node.object.type === utils_1.AST_NODE_TYPES.ThisExpression &&
|
|
142
|
+
isInsideClassMethod(node)) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
126
145
|
// Check object destructuring
|
|
127
146
|
if (options.object) {
|
|
128
147
|
if (options.enforceForRenamedProperties) {
|
|
@@ -76,7 +76,7 @@ exports.preferFragmentComponent = (0, createRule_1.createRule)({
|
|
|
76
76
|
reactImportNode = node;
|
|
77
77
|
}
|
|
78
78
|
// Keep track of default import for prioritization
|
|
79
|
-
const hasDefaultImport = node.specifiers.some(spec => spec.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier);
|
|
79
|
+
const hasDefaultImport = node.specifiers.some((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier);
|
|
80
80
|
if (hasDefaultImport) {
|
|
81
81
|
defaultReactImportNode = node;
|
|
82
82
|
}
|
|
@@ -98,14 +98,14 @@ exports.preferFragmentComponent = (0, createRule_1.createRule)({
|
|
|
98
98
|
const targetImportNode = defaultReactImportNode || reactImportNode;
|
|
99
99
|
if (targetImportNode) {
|
|
100
100
|
// Check if it's a namespace import
|
|
101
|
-
const hasNamespaceImport = targetImportNode.specifiers.some(spec => spec.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier);
|
|
101
|
+
const hasNamespaceImport = targetImportNode.specifiers.some((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier);
|
|
102
102
|
if (hasNamespaceImport) {
|
|
103
103
|
// Add separate import for Fragment
|
|
104
104
|
return fixer.insertTextAfter(targetImportNode, "\nimport { Fragment } from 'react';");
|
|
105
105
|
}
|
|
106
106
|
// Add Fragment to existing React import
|
|
107
107
|
const lastSpecifier = targetImportNode.specifiers[targetImportNode.specifiers.length - 1];
|
|
108
|
-
const hasNamedImports = targetImportNode.specifiers.some(spec => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier);
|
|
108
|
+
const hasNamedImports = targetImportNode.specifiers.some((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier);
|
|
109
109
|
if (hasNamedImports) {
|
|
110
110
|
return fixer.insertTextAfter(lastSpecifier, ', Fragment');
|
|
111
111
|
}
|
|
@@ -131,11 +131,14 @@ exports.preferFragmentComponent = (0, createRule_1.createRule)({
|
|
|
131
131
|
// Special handling for nested fragments
|
|
132
132
|
const reactFragmentParent = findReactFragmentParent(node);
|
|
133
133
|
// Check if this fragment contains React.Fragment children
|
|
134
|
-
const hasReactFragmentChild = node.children.some(child => child.type === utils_1.AST_NODE_TYPES.JSXElement &&
|
|
135
|
-
child.openingElement.name.type ===
|
|
136
|
-
|
|
134
|
+
const hasReactFragmentChild = node.children.some((child) => child.type === utils_1.AST_NODE_TYPES.JSXElement &&
|
|
135
|
+
child.openingElement.name.type ===
|
|
136
|
+
utils_1.AST_NODE_TYPES.JSXMemberExpression &&
|
|
137
|
+
child.openingElement.name.object.type ===
|
|
138
|
+
utils_1.AST_NODE_TYPES.JSXIdentifier &&
|
|
137
139
|
child.openingElement.name.object.name === 'React' &&
|
|
138
|
-
child.openingElement.name.property.type ===
|
|
140
|
+
child.openingElement.name.property.type ===
|
|
141
|
+
utils_1.AST_NODE_TYPES.JSXIdentifier &&
|
|
139
142
|
child.openingElement.name.property.name === 'Fragment');
|
|
140
143
|
// For nested fragments, we have multiple test cases with different expected behaviors
|
|
141
144
|
if (reactFragmentParent) {
|
|
@@ -170,13 +173,13 @@ exports.preferFragmentComponent = (0, createRule_1.createRule)({
|
|
|
170
173
|
fixes.push(fixer.replaceText(node.openingFragment, newInnerOpeningText));
|
|
171
174
|
fixes.push(fixer.replaceText(node.closingFragment, newInnerClosingText));
|
|
172
175
|
return fixes;
|
|
173
|
-
}
|
|
176
|
+
},
|
|
174
177
|
});
|
|
175
178
|
// Also report on the parent React.Fragment
|
|
176
179
|
context.report({
|
|
177
180
|
node: reactFragmentParent.openingElement.name,
|
|
178
181
|
messageId: 'preferFragment',
|
|
179
|
-
data: { type: 'React.Fragment' }
|
|
182
|
+
data: { type: 'React.Fragment' },
|
|
180
183
|
});
|
|
181
184
|
// Mark the parent as already handled
|
|
182
185
|
reportedNodes.add(reactFragmentParent);
|
|
@@ -215,7 +218,7 @@ exports.preferFragmentComponent = (0, createRule_1.createRule)({
|
|
|
215
218
|
fixes.push(fixer.replaceText(node.openingFragment, newOpeningText));
|
|
216
219
|
fixes.push(fixer.replaceText(node.closingFragment, newClosingText));
|
|
217
220
|
return fixes;
|
|
218
|
-
}
|
|
221
|
+
},
|
|
219
222
|
});
|
|
220
223
|
},
|
|
221
224
|
// Find React.Fragment usage
|
|
@@ -236,7 +239,7 @@ exports.preferFragmentComponent = (0, createRule_1.createRule)({
|
|
|
236
239
|
reportedNodes.add(node);
|
|
237
240
|
reportedNodes.add(node.name);
|
|
238
241
|
// Check if this React.Fragment has a JSXFragment child
|
|
239
|
-
const hasJSXFragmentChild = jsxElement.children.some(child => child.type === utils_1.AST_NODE_TYPES.JSXFragment);
|
|
242
|
+
const hasJSXFragmentChild = jsxElement.children.some((child) => child.type === utils_1.AST_NODE_TYPES.JSXFragment);
|
|
240
243
|
// Check if this React.Fragment is inside a JSXFragment
|
|
241
244
|
const fragmentParent = isInsideJSXFragment(node);
|
|
242
245
|
// Special case: React.Fragment inside a JSX Fragment
|
|
@@ -264,7 +267,7 @@ exports.preferFragmentComponent = (0, createRule_1.createRule)({
|
|
|
264
267
|
fixes.push(fixer.replaceText(jsxElement.closingElement, newClosingText));
|
|
265
268
|
}
|
|
266
269
|
return fixes;
|
|
267
|
-
}
|
|
270
|
+
},
|
|
268
271
|
});
|
|
269
272
|
return;
|
|
270
273
|
}
|
|
@@ -295,7 +298,7 @@ exports.preferFragmentComponent = (0, createRule_1.createRule)({
|
|
|
295
298
|
fixes.push(fixer.replaceText(jsxElement.closingElement, newClosingText));
|
|
296
299
|
}
|
|
297
300
|
return fixes;
|
|
298
|
-
}
|
|
301
|
+
},
|
|
299
302
|
});
|
|
300
303
|
},
|
|
301
304
|
'Program:exit'() {
|
|
@@ -312,7 +315,7 @@ exports.preferFragmentComponent = (0, createRule_1.createRule)({
|
|
|
312
315
|
// Prioritize default import for adding Fragment
|
|
313
316
|
if (reactImports.length > 0) {
|
|
314
317
|
for (const importNode of reactImports) {
|
|
315
|
-
const hasDefaultImport = importNode.specifiers.some(spec => spec.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier);
|
|
318
|
+
const hasDefaultImport = importNode.specifiers.some((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier);
|
|
316
319
|
if (hasDefaultImport) {
|
|
317
320
|
defaultReactImportNode = importNode;
|
|
318
321
|
reactImportNode = importNode;
|
|
@@ -325,8 +328,8 @@ exports.preferFragmentComponent = (0, createRule_1.createRule)({
|
|
|
325
328
|
}
|
|
326
329
|
}
|
|
327
330
|
}
|
|
328
|
-
}
|
|
331
|
+
},
|
|
329
332
|
};
|
|
330
|
-
}
|
|
333
|
+
},
|
|
331
334
|
});
|
|
332
335
|
//# sourceMappingURL=prefer-fragment-component.js.map
|
|
@@ -27,7 +27,8 @@ exports.preferGlobalRouterStateKey = (0, createRule_1.createRule)({
|
|
|
27
27
|
*/
|
|
28
28
|
function containsStringLiteral(node) {
|
|
29
29
|
// Direct string literal
|
|
30
|
-
if (node.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
30
|
+
if (node.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
31
|
+
typeof node.value === 'string') {
|
|
31
32
|
return true;
|
|
32
33
|
}
|
|
33
34
|
// String concatenation with + operator
|
|
@@ -38,17 +39,18 @@ exports.preferGlobalRouterStateKey = (0, createRule_1.createRule)({
|
|
|
38
39
|
}
|
|
39
40
|
// Conditional (ternary) expression with string literals
|
|
40
41
|
if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression &&
|
|
41
|
-
(containsStringLiteral(node.consequent) ||
|
|
42
|
+
(containsStringLiteral(node.consequent) ||
|
|
43
|
+
containsStringLiteral(node.alternate))) {
|
|
42
44
|
return true;
|
|
43
45
|
}
|
|
44
46
|
// Template literal with static parts
|
|
45
47
|
if (node.type === utils_1.AST_NODE_TYPES.TemplateLiteral) {
|
|
46
48
|
// Only report if there's a meaningful static part in the template
|
|
47
49
|
// This allows dynamic templates like `${prefix}-${id}` but catches `static-${id}`
|
|
48
|
-
const hasSignificantStaticPart = node.quasis.some(quasi => {
|
|
50
|
+
const hasSignificantStaticPart = node.quasis.some((quasi) => {
|
|
49
51
|
const content = quasi.value.raw.trim();
|
|
50
52
|
// Allow common separators like '-', '_', ':', '/' as they're just joining dynamic parts
|
|
51
|
-
return content.length > 0 &&
|
|
53
|
+
return content.length > 0 && !/^[-_:/.]+$/.test(content);
|
|
52
54
|
});
|
|
53
55
|
return hasSignificantStaticPart;
|
|
54
56
|
}
|
|
@@ -211,7 +211,7 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
|
|
|
211
211
|
function hasABPattern(params) {
|
|
212
212
|
if (params.length !== 2)
|
|
213
213
|
return false;
|
|
214
|
-
const paramNames = params.map(param => {
|
|
214
|
+
const paramNames = params.map((param) => {
|
|
215
215
|
if (param.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
216
216
|
return param.name;
|
|
217
217
|
}
|
|
@@ -34,7 +34,10 @@ exports.preferUseCallbackOverUseMemoForFunctions = (0, createRule_1.createRule)(
|
|
|
34
34
|
},
|
|
35
35
|
defaultOptions: [{ allowComplexBodies: false, allowFunctionFactories: true }],
|
|
36
36
|
create(context) {
|
|
37
|
-
const options = context.options[0] || {
|
|
37
|
+
const options = context.options[0] || {
|
|
38
|
+
allowComplexBodies: false,
|
|
39
|
+
allowFunctionFactories: true,
|
|
40
|
+
};
|
|
38
41
|
/**
|
|
39
42
|
* Checks if a node is a function factory (returns an object with functions or a function that generates functions)
|
|
40
43
|
*/
|
|
@@ -87,7 +90,8 @@ exports.preferUseCallbackOverUseMemoForFunctions = (0, createRule_1.createRule)(
|
|
|
87
90
|
}
|
|
88
91
|
// If there's more than one statement, or the single statement isn't a return
|
|
89
92
|
if (node.body.body.length > 1 ||
|
|
90
|
-
(node.body.body.length === 1 &&
|
|
93
|
+
(node.body.body.length === 1 &&
|
|
94
|
+
node.body.body[0].type !== utils_1.AST_NODE_TYPES.ReturnStatement)) {
|
|
91
95
|
return true;
|
|
92
96
|
}
|
|
93
97
|
return false;
|
|
@@ -143,7 +147,9 @@ exports.preferUseCallbackOverUseMemoForFunctions = (0, createRule_1.createRule)(
|
|
|
143
147
|
function reportAndFix(node, context) {
|
|
144
148
|
const sourceCode = context.getSourceCode();
|
|
145
149
|
const useMemoCallback = node.arguments[0];
|
|
146
|
-
const dependencyArray = node.arguments[1]
|
|
150
|
+
const dependencyArray = node.arguments[1]
|
|
151
|
+
? sourceCode.getText(node.arguments[1])
|
|
152
|
+
: '[]';
|
|
147
153
|
// Get the returned function from useMemo
|
|
148
154
|
let returnedFunction;
|
|
149
155
|
if (useMemoCallback.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
@@ -159,7 +165,9 @@ function reportAndFix(node, context) {
|
|
|
159
165
|
const returnedFunctionText = sourceCode.getText(returnedFunction);
|
|
160
166
|
// Check if useMemo has TypeScript generic type parameters
|
|
161
167
|
const hasTypeParameters = node.typeParameters !== undefined;
|
|
162
|
-
const typeParametersText = hasTypeParameters
|
|
168
|
+
const typeParametersText = hasTypeParameters
|
|
169
|
+
? sourceCode.getText(node.typeParameters)
|
|
170
|
+
: '';
|
|
163
171
|
context.report({
|
|
164
172
|
node,
|
|
165
173
|
messageId: 'preferUseCallback',
|