@blumintinc/eslint-plugin-blumint 1.14.0 → 1.16.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 +36 -14
- package/lib/index.js +20 -12
- package/lib/rules/avoid-utils-directory.js +0 -4
- package/lib/rules/consistent-callback-naming.js +68 -3
- package/lib/rules/dynamic-https-errors.d.ts +1 -1
- package/lib/rules/dynamic-https-errors.js +119 -49
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +1 -0
- package/lib/rules/enforce-boolean-naming-prefixes.js +86 -331
- package/lib/rules/enforce-date-ttime.d.ts +1 -0
- package/lib/rules/enforce-date-ttime.js +156 -0
- package/lib/rules/enforce-dynamic-firebase-imports.d.ts +2 -1
- package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
- package/lib/rules/enforce-dynamic-imports.d.ts +2 -1
- package/lib/rules/enforce-dynamic-imports.js +42 -21
- package/lib/rules/enforce-f-extension-for-entry-points.d.ts +8 -0
- package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
- package/lib/rules/enforce-global-constants.js +3 -3
- package/lib/rules/enforce-id-capitalization.js +1 -1
- package/lib/rules/enforce-memoize-async.js +66 -15
- package/lib/rules/enforce-mui-rounded-icons.js +42 -1
- package/lib/rules/enforce-props-argument-name.js +42 -16
- package/lib/rules/enforce-stable-hash-spread-props.js +3 -3
- package/lib/rules/enforce-transform-memoization.js +1 -1
- package/lib/rules/enforce-verb-noun-naming.js +3817 -4641
- package/lib/rules/global-const-style.js +25 -4
- package/lib/rules/logical-top-to-bottom-grouping.js +80 -2
- package/lib/rules/memo-compare-deeply-complex-props.js +183 -6
- package/lib/rules/memo-nested-react-components.js +243 -103
- package/lib/rules/no-array-length-in-deps.js +74 -3
- package/lib/rules/no-async-foreach.js +7 -2
- package/lib/rules/no-circular-references.d.ts +2 -1
- package/lib/rules/no-circular-references.js +150 -489
- package/lib/rules/no-compositing-layer-props.js +31 -0
- package/lib/rules/no-console-error.js +12 -10
- package/lib/rules/no-empty-dependency-use-callbacks.js +1 -1
- package/lib/rules/no-entire-object-hook-deps.js +147 -65
- package/lib/rules/no-excessive-parent-chain.js +3 -0
- package/lib/rules/no-explicit-return-type.js +6 -0
- package/lib/rules/no-hungarian.js +119 -24
- package/lib/rules/no-inline-component-prop.js +16 -7
- package/lib/rules/no-margin-properties.js +7 -38
- package/lib/rules/no-passthrough-getters.d.ts +2 -2
- package/lib/rules/no-passthrough-getters.js +83 -1
- package/lib/rules/no-redundant-this-params.js +50 -1
- package/lib/rules/no-unmemoized-memo-without-props.js +1 -1
- package/lib/rules/no-unnecessary-destructuring-rename.js +2 -5
- package/lib/rules/no-unnecessary-verb-suffix.js +79 -0
- package/lib/rules/no-unused-props.js +215 -37
- package/lib/rules/no-useless-fragment.js +10 -2
- package/lib/rules/parallelize-async-operations.js +117 -54
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +109 -4
- package/lib/rules/prefer-params-over-parent-id.js +1 -1
- package/lib/rules/prefer-settings-object.js +27 -10
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +75 -4
- package/lib/rules/prefer-use-deep-compare-memo.js +8 -14
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +1 -1
- package/lib/rules/prevent-children-clobber.js +9 -5
- package/lib/rules/react-memoize-literals.js +218 -13
- package/lib/rules/require-https-error-cause.js +30 -11
- package/lib/rules/require-memo.js +17 -9
- package/lib/rules/require-migration-script-metadata.d.ts +9 -0
- package/lib/rules/require-migration-script-metadata.js +206 -0
- package/lib/rules/warn-https-error-message-user-friendly.d.ts +1 -0
- package/lib/rules/warn-https-error-message-user-friendly.js +239 -0
- package/lib/utils/ASTHelpers.d.ts +49 -1
- package/lib/utils/ASTHelpers.js +394 -112
- package/package.json +7 -6
- package/release-manifest.json +166 -0
|
@@ -33,6 +33,14 @@ const DEFAULT_BOOLEAN_PREFIXES = [
|
|
|
33
33
|
const DEFAULT_OPTIONS = {
|
|
34
34
|
prefixes: DEFAULT_BOOLEAN_PREFIXES,
|
|
35
35
|
ignoreOverriddenGetters: false,
|
|
36
|
+
// Property names declared in interfaces and type aliases are frequently
|
|
37
|
+
// dictated by contracts the author cannot rename: external API request/response
|
|
38
|
+
// shapes, third-party library interfaces, and persisted data-model schemas
|
|
39
|
+
// (e.g. Firestore document fields). Flagging those produces unavoidable false
|
|
40
|
+
// positives, so property-signature enforcement is opt-in. Names the author does
|
|
41
|
+
// choose freshly — variables, function returns, class fields, parameters — stay
|
|
42
|
+
// enforced by default.
|
|
43
|
+
enforceForPropertySignatures: false,
|
|
36
44
|
};
|
|
37
45
|
const BOOLEANISH_BINARY_OPERATORS = new Set(['===', '!==', '==', '!=', '>', '<', '>=', '<=', 'in', 'instanceof']);
|
|
38
46
|
exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
@@ -58,6 +66,10 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
58
66
|
type: 'boolean',
|
|
59
67
|
default: false,
|
|
60
68
|
},
|
|
69
|
+
enforceForPropertySignatures: {
|
|
70
|
+
type: 'boolean',
|
|
71
|
+
default: false,
|
|
72
|
+
},
|
|
61
73
|
},
|
|
62
74
|
additionalProperties: false,
|
|
63
75
|
},
|
|
@@ -71,10 +83,11 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
71
83
|
defaultOptions: [DEFAULT_OPTIONS],
|
|
72
84
|
create(context, [options]) {
|
|
73
85
|
const approvedPrefixes = options.prefixes || DEFAULT_OPTIONS.prefixes;
|
|
86
|
+
const approvedPrefixesWithoutAsserts = approvedPrefixes.filter((p) => p !== 'asserts');
|
|
74
87
|
const ignoreOverriddenGetters = options.ignoreOverriddenGetters ??
|
|
75
88
|
DEFAULT_OPTIONS.ignoreOverriddenGetters;
|
|
76
|
-
const
|
|
77
|
-
|
|
89
|
+
const enforceForPropertySignatures = options.enforceForPropertySignatures ??
|
|
90
|
+
DEFAULT_OPTIONS.enforceForPropertySignatures;
|
|
78
91
|
function findVariableInScopes(name) {
|
|
79
92
|
let currentScope = context.getScope();
|
|
80
93
|
while (currentScope) {
|
|
@@ -85,32 +98,58 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
85
98
|
}
|
|
86
99
|
return undefined;
|
|
87
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Check if a name is prefixed by a boolean keyword with proper boundaries.
|
|
103
|
+
* Supports camelCase (isSomething), snake_case (is_something, IS_SOMETHING),
|
|
104
|
+
* and exact matches.
|
|
105
|
+
*/
|
|
106
|
+
function isPrefixedByBooleanKeyword(name, prefixes) {
|
|
107
|
+
const normalizedName = name.startsWith('_') ? name.slice(1) : name;
|
|
108
|
+
const checkPrefix = (p) => {
|
|
109
|
+
if (!normalizedName.toLowerCase().startsWith(p.toLowerCase())) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
if (normalizedName.length === p.length) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
const nextChar = normalizedName.charAt(p.length);
|
|
116
|
+
// For SCREAMING_SNAKE_CASE or similar all-uppercase names,
|
|
117
|
+
// we require an underscore boundary.
|
|
118
|
+
const isAllUppercase = normalizedName === normalizedName.toUpperCase() &&
|
|
119
|
+
/[a-z]/i.test(normalizedName);
|
|
120
|
+
if (isAllUppercase) {
|
|
121
|
+
return nextChar === '_';
|
|
122
|
+
}
|
|
123
|
+
// For camelCase, the next char must be uppercase, a digit, or $
|
|
124
|
+
return (nextChar === '_' ||
|
|
125
|
+
nextChar === '$' ||
|
|
126
|
+
(nextChar >= '0' && nextChar <= '9') ||
|
|
127
|
+
(nextChar === nextChar.toUpperCase() &&
|
|
128
|
+
nextChar !== nextChar.toLowerCase()));
|
|
129
|
+
};
|
|
130
|
+
const checkPrefixWithPlural = (p) => {
|
|
131
|
+
if (checkPrefix(p))
|
|
132
|
+
return true;
|
|
133
|
+
if (['is', 'has', 'does', 'was', 'had', 'did'].includes(p)) {
|
|
134
|
+
const pluralPrefix = pluralize_1.default.plural(p);
|
|
135
|
+
if (checkPrefix(pluralPrefix))
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
};
|
|
140
|
+
return prefixes.some((prefix) => checkPrefixWithPlural(prefix));
|
|
141
|
+
}
|
|
88
142
|
/**
|
|
89
143
|
* Check if a name starts with any of the approved prefixes, their plural forms,
|
|
90
144
|
* or if it starts with an underscore (which indicates a private/internal property)
|
|
91
145
|
*/
|
|
92
146
|
function hasApprovedPrefix(name, options) {
|
|
93
147
|
const treatLeadingUnderscoreAsApproved = options?.treatLeadingUnderscoreAsApproved ?? true;
|
|
94
|
-
const normalizedName = name.startsWith('_') ? name.slice(1) : name;
|
|
95
148
|
// Skip checking properties that start with an underscore (private/internal properties)
|
|
96
149
|
if (treatLeadingUnderscoreAsApproved && name.startsWith('_')) {
|
|
97
150
|
return true;
|
|
98
151
|
}
|
|
99
|
-
return approvedPrefixes
|
|
100
|
-
// Check for exact prefix match
|
|
101
|
-
if (normalizedName.toLowerCase().startsWith(prefix.toLowerCase())) {
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
// Check for plural form of the prefix
|
|
105
|
-
// Only apply pluralization to certain prefixes that have meaningful plural forms
|
|
106
|
-
if (['is', 'has', 'does', 'was', 'had', 'did'].includes(prefix)) {
|
|
107
|
-
const pluralPrefix = pluralize_1.default.plural(prefix);
|
|
108
|
-
return normalizedName
|
|
109
|
-
.toLowerCase()
|
|
110
|
-
.startsWith(pluralPrefix.toLowerCase());
|
|
111
|
-
}
|
|
112
|
-
return false;
|
|
113
|
-
});
|
|
152
|
+
return isPrefixedByBooleanKeyword(name, approvedPrefixes);
|
|
114
153
|
}
|
|
115
154
|
function nameSuggestsBoolean(name) {
|
|
116
155
|
const normalizedName = name.startsWith('_') ? name.slice(1) : name;
|
|
@@ -127,9 +166,8 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
127
166
|
'authenticated',
|
|
128
167
|
'authorized',
|
|
129
168
|
];
|
|
130
|
-
return (
|
|
131
|
-
|
|
132
|
-
}) || suffixKeywords.some((keyword) => lowerName.endsWith(keyword)));
|
|
169
|
+
return (isPrefixedByBooleanKeyword(normalizedName, approvedPrefixes) ||
|
|
170
|
+
suffixKeywords.some((keyword) => lowerName.endsWith(keyword)));
|
|
133
171
|
}
|
|
134
172
|
/**
|
|
135
173
|
* Capitalize the first letter of a name for use in suggested alternatives
|
|
@@ -207,94 +245,20 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
207
245
|
// Check for logical expressions (&&)
|
|
208
246
|
if (node.init.type === utils_1.AST_NODE_TYPES.LogicalExpression &&
|
|
209
247
|
node.init.operator === '&&') {
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const calleeName = rightSide.callee.name;
|
|
216
|
-
const lowerCallee = calleeName.toLowerCase();
|
|
217
|
-
// For assert*-style utilities, only treat as boolean if we can confirm boolean return type
|
|
218
|
-
if (lowerCallee.startsWith('assert')) {
|
|
219
|
-
return identifierReturnsBoolean(calleeName);
|
|
220
|
-
}
|
|
221
|
-
// Otherwise, infer based on naming heuristics
|
|
222
|
-
const isBooleanCall = approvedPrefixes.some((prefix) => prefix !== 'asserts' &&
|
|
223
|
-
lowerCallee.startsWith(prefix.toLowerCase()));
|
|
224
|
-
if (isBooleanCall ||
|
|
225
|
-
lowerCallee.includes('boolean') ||
|
|
226
|
-
lowerCallee.includes('enabled') ||
|
|
227
|
-
lowerCallee.includes('auth') ||
|
|
228
|
-
lowerCallee.includes('valid') ||
|
|
229
|
-
lowerCallee.includes('check')) {
|
|
230
|
-
return true;
|
|
231
|
-
}
|
|
232
|
-
if (lowerCallee.startsWith('get') ||
|
|
233
|
-
lowerCallee.startsWith('fetch') ||
|
|
234
|
-
lowerCallee.startsWith('retrieve') ||
|
|
235
|
-
lowerCallee.startsWith('load') ||
|
|
236
|
-
lowerCallee.startsWith('read')) {
|
|
237
|
-
return false;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
// If the method name doesn't suggest it returns a boolean, don't flag it
|
|
241
|
-
if (rightSide.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
242
|
-
rightSide.callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
243
|
-
const methodName = rightSide.callee.property.name;
|
|
244
|
-
const lowerMethodName = methodName.toLowerCase();
|
|
245
|
-
// Ignore assert*-style methods which often return the input value
|
|
246
|
-
if (lowerMethodName.startsWith('assert')) {
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
|
-
// Check if the method name suggests it returns a boolean
|
|
250
|
-
const isBooleanMethod = approvedPrefixes.some((prefix) => prefix !== 'asserts' &&
|
|
251
|
-
lowerMethodName.startsWith(prefix.toLowerCase()));
|
|
252
|
-
// If the method name suggests it returns a boolean (starts with a boolean prefix or contains 'boolean' or 'enabled'),
|
|
253
|
-
// then the variable should be treated as a boolean
|
|
254
|
-
if (isBooleanMethod ||
|
|
255
|
-
lowerMethodName.includes('boolean') ||
|
|
256
|
-
lowerMethodName.includes('enabled') ||
|
|
257
|
-
lowerMethodName.includes('auth') ||
|
|
258
|
-
lowerMethodName.includes('valid') ||
|
|
259
|
-
lowerMethodName.includes('check')) {
|
|
260
|
-
return true;
|
|
261
|
-
}
|
|
262
|
-
// For methods like getVolume(), getData(), etc., assume they return non-boolean values
|
|
263
|
-
if (lowerMethodName.startsWith('get') ||
|
|
264
|
-
lowerMethodName.startsWith('fetch') ||
|
|
265
|
-
lowerMethodName.startsWith('retrieve') ||
|
|
266
|
-
lowerMethodName.startsWith('load') ||
|
|
267
|
-
lowerMethodName.startsWith('read')) {
|
|
268
|
-
return false;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
248
|
+
const left = evaluateBooleanishExpression(node.init.left);
|
|
249
|
+
const right = evaluateBooleanishExpression(node.init.right);
|
|
250
|
+
// If both sides are boolean, the result is boolean.
|
|
251
|
+
if (left === 'boolean' && right === 'boolean') {
|
|
252
|
+
return true;
|
|
271
253
|
}
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (lowerPropertyName.includes('parent') ||
|
|
279
|
-
lowerPropertyName.includes('element') ||
|
|
280
|
-
lowerPropertyName.includes('node') ||
|
|
281
|
-
lowerPropertyName.includes('child') ||
|
|
282
|
-
lowerPropertyName.includes('sibling')) {
|
|
283
|
-
return false;
|
|
284
|
-
}
|
|
285
|
-
// Ignore assert*-style properties which often return the input value
|
|
286
|
-
if (lowerPropertyName.startsWith('assert')) {
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
// For property access like user.isAuthenticated, treat as boolean
|
|
290
|
-
const isBooleanProperty = approvedPrefixes.some((prefix) => prefix !== 'asserts' &&
|
|
291
|
-
lowerPropertyName.startsWith(prefix.toLowerCase()));
|
|
292
|
-
if (isBooleanProperty) {
|
|
293
|
-
return true;
|
|
294
|
-
}
|
|
254
|
+
// If the right side is boolean, and the left side is unknown (but not non-boolean),
|
|
255
|
+
// we treat it as boolean to avoid false negatives for common patterns like `user && user.isActive`.
|
|
256
|
+
// This is a trade-off: it might cause some false positives for non-boolean variables
|
|
257
|
+
// used as guards, but those are less common than the `user && user.isActive` pattern.
|
|
258
|
+
if (right === 'boolean' && left === 'unknown') {
|
|
259
|
+
return true;
|
|
295
260
|
}
|
|
296
|
-
|
|
297
|
-
return true;
|
|
261
|
+
return false;
|
|
298
262
|
}
|
|
299
263
|
// Special case for logical OR (||) - only consider it boolean if:
|
|
300
264
|
// 1. It's used with boolean literals or
|
|
@@ -332,8 +296,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
332
296
|
if (lowerCallee.startsWith('assert')) {
|
|
333
297
|
return identifierReturnsBoolean(calleeName);
|
|
334
298
|
}
|
|
335
|
-
return
|
|
336
|
-
lowerCallee.startsWith(prefix.toLowerCase()));
|
|
299
|
+
return isPrefixedByBooleanKeyword(calleeName, approvedPrefixesWithoutAsserts);
|
|
337
300
|
}
|
|
338
301
|
// Default to false for other cases with || to avoid false positives
|
|
339
302
|
return false;
|
|
@@ -353,8 +316,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
353
316
|
return identifierReturnsBoolean(calleeName);
|
|
354
317
|
}
|
|
355
318
|
// Check if the function name suggests it returns a boolean
|
|
356
|
-
return approvedPrefixes.
|
|
357
|
-
lowerCallee.startsWith(prefix.toLowerCase()));
|
|
319
|
+
return isPrefixedByBooleanKeyword(calleeName, approvedPrefixes.filter((p) => p !== 'asserts'));
|
|
358
320
|
}
|
|
359
321
|
}
|
|
360
322
|
return false;
|
|
@@ -437,8 +399,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
437
399
|
if (lowerCallee.startsWith('assert')) {
|
|
438
400
|
return identifierReturnsBoolean(calleeName) ? 'boolean' : 'unknown';
|
|
439
401
|
}
|
|
440
|
-
const matchesPrefix =
|
|
441
|
-
lowerCallee.startsWith(prefix.toLowerCase()));
|
|
402
|
+
const matchesPrefix = isPrefixedByBooleanKeyword(calleeName, approvedPrefixesWithoutAsserts);
|
|
442
403
|
if (matchesPrefix ||
|
|
443
404
|
lowerCallee.includes('boolean') ||
|
|
444
405
|
lowerCallee.includes('enabled') ||
|
|
@@ -462,8 +423,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
462
423
|
if (lowerMethodName.startsWith('assert')) {
|
|
463
424
|
return 'unknown';
|
|
464
425
|
}
|
|
465
|
-
const matchesPrefix =
|
|
466
|
-
lowerMethodName.startsWith(prefix.toLowerCase()));
|
|
426
|
+
const matchesPrefix = isPrefixedByBooleanKeyword(methodName, approvedPrefixesWithoutAsserts);
|
|
467
427
|
if (matchesPrefix ||
|
|
468
428
|
lowerMethodName.includes('boolean') ||
|
|
469
429
|
lowerMethodName.includes('enabled') ||
|
|
@@ -477,7 +437,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
477
437
|
}
|
|
478
438
|
function evaluateBooleanishExpression(expression) {
|
|
479
439
|
if (!expression)
|
|
480
|
-
return '
|
|
440
|
+
return 'unknown';
|
|
481
441
|
let currentExpression = expression;
|
|
482
442
|
if (currentExpression.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
483
443
|
currentExpression.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
@@ -827,10 +787,10 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
827
787
|
});
|
|
828
788
|
}
|
|
829
789
|
/**
|
|
830
|
-
* Check if a variable is
|
|
790
|
+
* Check if a variable is initialized with a boolean-suggesting member expression
|
|
831
791
|
* This helps identify variables that should be flagged as needing a boolean prefix
|
|
832
792
|
*/
|
|
833
|
-
function
|
|
793
|
+
function isLikelyBooleanByMemberExpression(node) {
|
|
834
794
|
// Check if the variable is initialized with a boolean-related value
|
|
835
795
|
const variableDeclarator = node.parent;
|
|
836
796
|
if (variableDeclarator?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
@@ -846,7 +806,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
846
806
|
init.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
847
807
|
const propertyName = init.property.name;
|
|
848
808
|
// If the property name suggests it's a boolean (starts with a boolean prefix)
|
|
849
|
-
const isBooleanProperty =
|
|
809
|
+
const isBooleanProperty = isPrefixedByBooleanKeyword(propertyName, approvedPrefixes);
|
|
850
810
|
if (isBooleanProperty) {
|
|
851
811
|
return true;
|
|
852
812
|
}
|
|
@@ -897,8 +857,8 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
897
857
|
return;
|
|
898
858
|
// Check if it's a boolean variable
|
|
899
859
|
let isBooleanVar = hasBooleanTypeAnnotation(node.id) || hasInitialBooleanValue(node);
|
|
900
|
-
// Check if it's a boolean variable used in a while loop
|
|
901
|
-
if (!isBooleanVar &&
|
|
860
|
+
// Check if it's a boolean variable used in a while loop or initialized with a boolean property
|
|
861
|
+
if (!isBooleanVar && isLikelyBooleanByMemberExpression(node.id)) {
|
|
902
862
|
isBooleanVar = true;
|
|
903
863
|
}
|
|
904
864
|
// Check if it's an arrow function with boolean return type
|
|
@@ -1072,219 +1032,15 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
1072
1032
|
});
|
|
1073
1033
|
}
|
|
1074
1034
|
}
|
|
1075
|
-
/**
|
|
1076
|
-
* Check if an identifier is imported from an external module
|
|
1077
|
-
*/
|
|
1078
|
-
function isImportedIdentifier(name) {
|
|
1079
|
-
if (importStatusCache.has(name)) {
|
|
1080
|
-
const cached = importStatusCache.get(name);
|
|
1081
|
-
if (cached !== undefined)
|
|
1082
|
-
return cached;
|
|
1083
|
-
}
|
|
1084
|
-
const variable = findVariableInScopes(name);
|
|
1085
|
-
if (!variable) {
|
|
1086
|
-
importStatusCache.set(name, false);
|
|
1087
|
-
return false;
|
|
1088
|
-
}
|
|
1089
|
-
const isImport = variable.defs.some((def) => def.type === 'ImportBinding');
|
|
1090
|
-
importStatusCache.set(name, isImport);
|
|
1091
|
-
return isImport;
|
|
1092
|
-
}
|
|
1093
|
-
/**
|
|
1094
|
-
* Check if a variable is used with an external API
|
|
1095
|
-
*/
|
|
1096
|
-
function isVariableUsedWithExternalApi(variableName) {
|
|
1097
|
-
if (externalApiUsageCache.has(variableName)) {
|
|
1098
|
-
const cached = externalApiUsageCache.get(variableName);
|
|
1099
|
-
if (cached !== undefined)
|
|
1100
|
-
return cached;
|
|
1101
|
-
}
|
|
1102
|
-
const variable = findVariableInScopes(variableName);
|
|
1103
|
-
if (!variable) {
|
|
1104
|
-
externalApiUsageCache.set(variableName, false);
|
|
1105
|
-
return false;
|
|
1106
|
-
}
|
|
1107
|
-
for (const reference of variable.references) {
|
|
1108
|
-
if (reference.identifier === variable.identifiers[0])
|
|
1109
|
-
continue;
|
|
1110
|
-
const id = reference.identifier;
|
|
1111
|
-
const markAndReturnTrue = () => {
|
|
1112
|
-
externalApiUsageCache.set(variableName, true);
|
|
1113
|
-
return true;
|
|
1114
|
-
};
|
|
1115
|
-
if (id.parent?.type === utils_1.AST_NODE_TYPES.Property &&
|
|
1116
|
-
id.parent.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
1117
|
-
id.parent.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
1118
|
-
id.parent.parent.parent.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1119
|
-
isImportedIdentifier(id.parent.parent.parent.callee.name)) {
|
|
1120
|
-
return markAndReturnTrue();
|
|
1121
|
-
}
|
|
1122
|
-
if (id.parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
1123
|
-
if (id.parent.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1124
|
-
isImportedIdentifier(id.parent.callee.name)) {
|
|
1125
|
-
return markAndReturnTrue();
|
|
1126
|
-
}
|
|
1127
|
-
if (id.parent.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
1128
|
-
id.parent.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1129
|
-
isImportedIdentifier(id.parent.callee.object.name)) {
|
|
1130
|
-
return markAndReturnTrue();
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
if (id.parent?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer &&
|
|
1134
|
-
id.parent.parent?.type === utils_1.AST_NODE_TYPES.JSXAttribute &&
|
|
1135
|
-
id.parent.parent.parent?.type === utils_1.AST_NODE_TYPES.JSXOpeningElement &&
|
|
1136
|
-
id.parent.parent.parent.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier &&
|
|
1137
|
-
isImportedIdentifier(id.parent.parent.parent.name.name)) {
|
|
1138
|
-
return markAndReturnTrue();
|
|
1139
|
-
}
|
|
1140
|
-
if (id.parent?.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute &&
|
|
1141
|
-
id.parent.parent?.type === utils_1.AST_NODE_TYPES.JSXOpeningElement &&
|
|
1142
|
-
id.parent.parent.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier &&
|
|
1143
|
-
isImportedIdentifier(id.parent.parent.name.name)) {
|
|
1144
|
-
return markAndReturnTrue();
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
externalApiUsageCache.set(variableName, false);
|
|
1148
|
-
return false;
|
|
1149
|
-
}
|
|
1150
|
-
const HOOK_NAMES = new Set(['useMemo', 'useCallback', 'useState']);
|
|
1151
|
-
function findVariableFromReactHook(objectExpr) {
|
|
1152
|
-
let current = objectExpr;
|
|
1153
|
-
while (current) {
|
|
1154
|
-
if (current.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
|
|
1155
|
-
current = current.parent;
|
|
1156
|
-
continue;
|
|
1157
|
-
}
|
|
1158
|
-
if (current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
1159
|
-
current.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
1160
|
-
current.parent.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1161
|
-
HOOK_NAMES.has(current.parent.callee.name) &&
|
|
1162
|
-
current.parent.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
1163
|
-
current.parent.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
1164
|
-
return current.parent.parent.id.name;
|
|
1165
|
-
}
|
|
1166
|
-
current = current.parent;
|
|
1167
|
-
}
|
|
1168
|
-
return undefined;
|
|
1169
|
-
}
|
|
1170
|
-
/**
|
|
1171
|
-
* Check property definitions for boolean values
|
|
1172
|
-
*/
|
|
1173
|
-
function checkProperty(node) {
|
|
1174
|
-
if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
1175
|
-
return;
|
|
1176
|
-
const propertyName = node.key.name;
|
|
1177
|
-
// Check if it's a boolean property
|
|
1178
|
-
let isBooleanProperty = false;
|
|
1179
|
-
// Check if it's initialized with a boolean value
|
|
1180
|
-
if (node.value.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
1181
|
-
typeof node.value.value === 'boolean') {
|
|
1182
|
-
isBooleanProperty = true;
|
|
1183
|
-
}
|
|
1184
|
-
// Check if it's a method that returns a boolean
|
|
1185
|
-
if ((node.value.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
1186
|
-
node.value.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) &&
|
|
1187
|
-
node.value.returnType?.typeAnnotation.type ===
|
|
1188
|
-
utils_1.AST_NODE_TYPES.TSBooleanKeyword) {
|
|
1189
|
-
isBooleanProperty = true;
|
|
1190
|
-
}
|
|
1191
|
-
// Skip checking if this property is part of an object literal passed to an external function
|
|
1192
|
-
if (isBooleanProperty && !hasApprovedPrefix(propertyName)) {
|
|
1193
|
-
// Special cases for common Node.js API boolean properties
|
|
1194
|
-
if ((propertyName === 'recursive' || propertyName === 'keepAlive') &&
|
|
1195
|
-
node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
1196
|
-
node.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
1197
|
-
return; // Skip checking for these properties in object literals passed to functions
|
|
1198
|
-
}
|
|
1199
|
-
// Check if this property is in an object literal that's an argument to a function call
|
|
1200
|
-
let isExternalApiCall = false;
|
|
1201
|
-
// Navigate up to find if we're in an object expression that's an argument to a function call
|
|
1202
|
-
if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
1203
|
-
node.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
1204
|
-
const callExpression = node.parent.parent;
|
|
1205
|
-
// Check if the function being called is an identifier (like mkdirSync, createServer, etc.)
|
|
1206
|
-
if (callExpression.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
1207
|
-
const calleeName = callExpression.callee.name;
|
|
1208
|
-
if (isImportedIdentifier(calleeName)) {
|
|
1209
|
-
isExternalApiCall = true;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
// Also check for member expressions like fs.mkdirSync
|
|
1213
|
-
if (callExpression.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
1214
|
-
// For member expressions, check if the object is imported
|
|
1215
|
-
const objectNode = callExpression.callee.object;
|
|
1216
|
-
if (objectNode.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
1217
|
-
const objectName = objectNode.name;
|
|
1218
|
-
if (isImportedIdentifier(objectName)) {
|
|
1219
|
-
isExternalApiCall = true;
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
// Check if this property is in an object literal that's being assigned to a variable
|
|
1225
|
-
// This handles cases like const messageInputProps = { grow: true }
|
|
1226
|
-
if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
1227
|
-
node.parent.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
1228
|
-
node.parent.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
1229
|
-
const variableName = node.parent.parent.id.name;
|
|
1230
|
-
if (isVariableUsedWithExternalApi(variableName)) {
|
|
1231
|
-
isExternalApiCall = true;
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
// Special case for useMemo and other React hooks
|
|
1235
|
-
// This handles cases like:
|
|
1236
|
-
// 1. const messageInputProps = useMemo(() => ({ grow: true }), [])
|
|
1237
|
-
// 2. const messageInputProps = useMemo(() => { return { grow: true }; }, [])
|
|
1238
|
-
if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
1239
|
-
const hookVariable = findVariableFromReactHook(node.parent);
|
|
1240
|
-
if (hookVariable && isVariableUsedWithExternalApi(hookVariable)) {
|
|
1241
|
-
isExternalApiCall = true;
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
// Check if this property is in an object literal that's directly passed to an imported function
|
|
1245
|
-
// This handles cases like ExternalComponent({ grow: true })
|
|
1246
|
-
if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
1247
|
-
node.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
1248
|
-
node.parent.parent.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
1249
|
-
const calleeName = node.parent.parent.callee.name;
|
|
1250
|
-
if (isImportedIdentifier(calleeName)) {
|
|
1251
|
-
isExternalApiCall = true;
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
// Check if this property is in an object literal that's directly passed as a JSX attribute
|
|
1255
|
-
// This handles cases like <ExternalComponent config={{ active: true }} />
|
|
1256
|
-
if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
1257
|
-
node.parent.parent?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer &&
|
|
1258
|
-
node.parent.parent.parent?.type === utils_1.AST_NODE_TYPES.JSXAttribute &&
|
|
1259
|
-
node.parent.parent.parent.parent?.type ===
|
|
1260
|
-
utils_1.AST_NODE_TYPES.JSXOpeningElement) {
|
|
1261
|
-
const jsxOpeningElement = node.parent.parent.parent.parent;
|
|
1262
|
-
if (jsxOpeningElement.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier) {
|
|
1263
|
-
const componentName = jsxOpeningElement.name.name;
|
|
1264
|
-
if (isImportedIdentifier(componentName)) {
|
|
1265
|
-
isExternalApiCall = true;
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
// Only report if it's not an external API call
|
|
1270
|
-
if (!isExternalApiCall) {
|
|
1271
|
-
context.report({
|
|
1272
|
-
node: node.key,
|
|
1273
|
-
messageId: 'missingBooleanPrefix',
|
|
1274
|
-
data: {
|
|
1275
|
-
type: 'property',
|
|
1276
|
-
name: propertyName,
|
|
1277
|
-
capitalizedName: capitalizeFirst(propertyName),
|
|
1278
|
-
prefixes: formatPrefixes(),
|
|
1279
|
-
},
|
|
1280
|
-
});
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
1035
|
/**
|
|
1285
1036
|
* Check property signatures in interfaces/types for boolean types
|
|
1286
1037
|
*/
|
|
1287
1038
|
function checkPropertySignature(node) {
|
|
1039
|
+
// Interface/type-alias property names are commonly imposed by contracts the
|
|
1040
|
+
// author cannot rename (external APIs, data-model schemas), so they are only
|
|
1041
|
+
// enforced when explicitly opted in.
|
|
1042
|
+
if (!enforceForPropertySignatures)
|
|
1043
|
+
return;
|
|
1288
1044
|
if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
1289
1045
|
return;
|
|
1290
1046
|
const propertyName = node.key.name;
|
|
@@ -1390,7 +1146,6 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
|
|
|
1390
1146
|
},
|
|
1391
1147
|
MethodDefinition: checkMethodDefinition,
|
|
1392
1148
|
TSAbstractMethodDefinition: checkMethodDefinition,
|
|
1393
|
-
Property: checkProperty,
|
|
1394
1149
|
ClassProperty: checkClassProperty,
|
|
1395
1150
|
PropertyDefinition: checkClassPropertyDeclaration,
|
|
1396
1151
|
TSPropertySignature: checkPropertySignature,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const enforceDateTTime: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"enforceDateTTime", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|