@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
|
@@ -32,6 +32,21 @@ const COMPOSITING_VALUES = new Set([
|
|
|
32
32
|
'translateZ',
|
|
33
33
|
'transparent',
|
|
34
34
|
]);
|
|
35
|
+
// CSS reset/identity values that explicitly DON'T promote a layer for a given
|
|
36
|
+
// property. These are the opt-out counterparts to COMPOSITING_VALUES: `none`
|
|
37
|
+
// removes the effect, `auto`/global keywords disable the hint, and the default
|
|
38
|
+
// keyword leaves the element un-promoted. Keyed by normalized property name so
|
|
39
|
+
// the allowlist stays property-specific (e.g. `none` clears `transform` but is
|
|
40
|
+
// not a valid no-op for `opacity`).
|
|
41
|
+
const NON_COMPOSITING_VALUES = {
|
|
42
|
+
filter: new Set(['none']),
|
|
43
|
+
'backdrop-filter': new Set(['none']),
|
|
44
|
+
transform: new Set(['none']),
|
|
45
|
+
contain: new Set(['none']),
|
|
46
|
+
perspective: new Set(['none']),
|
|
47
|
+
'will-change': new Set(['auto', 'unset', 'initial', 'inherit', 'revert']),
|
|
48
|
+
'backface-visibility': new Set(['visible']),
|
|
49
|
+
};
|
|
35
50
|
exports.noCompositingLayerProps = (0, createRule_1.createRule)({
|
|
36
51
|
name: 'no-compositing-layer-props',
|
|
37
52
|
meta: {
|
|
@@ -54,6 +69,14 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
|
|
|
54
69
|
value.includes('scale3d') ||
|
|
55
70
|
value.includes('translateZ'));
|
|
56
71
|
}
|
|
72
|
+
// Strip `!important` and normalize casing so reset/identity values written
|
|
73
|
+
// as e.g. `none !important` are still recognized as non-promoting.
|
|
74
|
+
function normalizePropertyValue(value) {
|
|
75
|
+
return value
|
|
76
|
+
.replace(/\s*!important\s*$/i, '')
|
|
77
|
+
.trim()
|
|
78
|
+
.toLowerCase();
|
|
79
|
+
}
|
|
57
80
|
function checkProperty(propertyName, propertyValue) {
|
|
58
81
|
const normalizedName = normalizePropertyName(propertyName);
|
|
59
82
|
if (COMPOSITING_PROPERTIES.has(normalizedName)) {
|
|
@@ -66,6 +89,14 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
|
|
|
66
89
|
return false;
|
|
67
90
|
return numValue > 0 && numValue < 1;
|
|
68
91
|
}
|
|
92
|
+
// CSS reset/identity values can't create a compositing layer, so don't
|
|
93
|
+
// flag them (mirrors the opacity value-awareness for the other props).
|
|
94
|
+
const allowedValues = NON_COMPOSITING_VALUES[normalizedName];
|
|
95
|
+
if (allowedValues &&
|
|
96
|
+
propertyValue &&
|
|
97
|
+
allowedValues.has(normalizePropertyValue(propertyValue))) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
69
100
|
return true;
|
|
70
101
|
}
|
|
71
102
|
if (propertyValue && checkPropertyValue(propertyValue)) {
|
|
@@ -4,6 +4,7 @@ exports.noConsoleError = void 0;
|
|
|
4
4
|
const minimatch_1 = require("minimatch");
|
|
5
5
|
const utils_1 = require("@typescript-eslint/utils");
|
|
6
6
|
const createRule_1 = require("../utils/createRule");
|
|
7
|
+
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
7
8
|
const defaultIgnorePatterns = [
|
|
8
9
|
'**/__tests__/**',
|
|
9
10
|
'**/__mocks__/**',
|
|
@@ -69,19 +70,20 @@ const getResolvedVariable = (scope, identifier) => {
|
|
|
69
70
|
return findVariable(scope, identifier.name);
|
|
70
71
|
};
|
|
71
72
|
const getScopeForNode = (context, node) => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
try {
|
|
74
|
+
const sourceCode = context.getSourceCode();
|
|
75
|
+
const sourceCodeWithScope = sourceCode;
|
|
76
|
+
if (typeof sourceCodeWithScope.getScope === 'function') {
|
|
77
|
+
return sourceCodeWithScope.getScope(node) ?? context.getScope();
|
|
78
|
+
}
|
|
79
|
+
return context.getScope();
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return context.getScope();
|
|
76
83
|
}
|
|
77
|
-
return context.getScope();
|
|
78
84
|
};
|
|
79
85
|
const getDeclaredVariablesForNode = (context, node) => {
|
|
80
|
-
|
|
81
|
-
if (typeof sourceCodeWithDeclaredVariables.getDeclaredVariables === 'function') {
|
|
82
|
-
return sourceCodeWithDeclaredVariables.getDeclaredVariables(node);
|
|
83
|
-
}
|
|
84
|
-
return context.getDeclaredVariables(node);
|
|
86
|
+
return ASTHelpers_1.ASTHelpers.getDeclaredVariables(context, node);
|
|
85
87
|
};
|
|
86
88
|
const isErrorKey = (key, computed) => {
|
|
87
89
|
if (!computed && key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
@@ -515,7 +515,7 @@ exports.noEmptyDependencyUseCallbacks = (0, createRule_1.createRule)({
|
|
|
515
515
|
const callback = getCallbackArg(callExpression);
|
|
516
516
|
if (!callback)
|
|
517
517
|
return;
|
|
518
|
-
if (ASTHelpers_1.ASTHelpers.returnsJSX(callback.body))
|
|
518
|
+
if (ASTHelpers_1.ASTHelpers.returnsJSX(callback.body, context))
|
|
519
519
|
return;
|
|
520
520
|
const extraTypeRoots = [];
|
|
521
521
|
if (callExpression.parent &&
|
|
@@ -55,12 +55,23 @@ function isArrayOrPrimitive(checker, esTreeNode, nodeMap) {
|
|
|
55
55
|
return false;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
function unwrapExpression(expr) {
|
|
59
|
+
let current = expr;
|
|
60
|
+
while (current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
61
|
+
current.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
62
|
+
current.type === utils_1.AST_NODE_TYPES.ChainExpression ||
|
|
63
|
+
current.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
|
|
64
|
+
current = current.expression;
|
|
65
|
+
}
|
|
66
|
+
return current;
|
|
67
|
+
}
|
|
58
68
|
function getObjectUsagesInHook(hookBody, objectName,
|
|
59
69
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
70
|
context) {
|
|
61
71
|
const usages = new Map(); // Track usage and its position
|
|
62
72
|
const visited = new Set();
|
|
63
73
|
let needsEntireObject = false;
|
|
74
|
+
let isUsed = false;
|
|
64
75
|
// Built-in array methods that indicate usage of the entire array
|
|
65
76
|
const ARRAY_METHODS = new Set([
|
|
66
77
|
'map',
|
|
@@ -125,12 +136,12 @@ context) {
|
|
|
125
136
|
// For computed properties with variables (like user[key]), we need the entire object
|
|
126
137
|
if (memberExpr.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
127
138
|
// Check if this is accessing our target object
|
|
128
|
-
let
|
|
129
|
-
while (
|
|
130
|
-
|
|
139
|
+
let currentBase = unwrapExpression(memberExpr.object);
|
|
140
|
+
while (currentBase.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
141
|
+
currentBase = unwrapExpression(currentBase.object);
|
|
131
142
|
}
|
|
132
|
-
if (
|
|
133
|
-
|
|
143
|
+
if (currentBase.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
144
|
+
currentBase.name === objectName) {
|
|
134
145
|
// This is a computed property access on our target object, so we need the entire object
|
|
135
146
|
needsEntireObject = true;
|
|
136
147
|
}
|
|
@@ -172,29 +183,29 @@ context) {
|
|
|
172
183
|
(ARRAY_METHODS.has(memberExpr.property.name) ||
|
|
173
184
|
STRING_METHODS.has(memberExpr.property.name))) {
|
|
174
185
|
// Check if this is accessing our target object or a property of it
|
|
175
|
-
let
|
|
186
|
+
let currentBase = unwrapExpression(memberExpr.object);
|
|
176
187
|
let pathParts = [];
|
|
177
|
-
let
|
|
188
|
+
let hasOptionalChainingInMethod = false;
|
|
178
189
|
// Build the path to the array/string being accessed
|
|
179
|
-
while (
|
|
180
|
-
const currentMember =
|
|
190
|
+
while (currentBase.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
191
|
+
const currentMember = currentBase;
|
|
181
192
|
if (currentMember.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
182
193
|
pathParts.unshift(currentMember.property.name);
|
|
183
194
|
}
|
|
184
195
|
if (currentMember.optional) {
|
|
185
|
-
|
|
196
|
+
hasOptionalChainingInMethod = true;
|
|
186
197
|
}
|
|
187
|
-
|
|
198
|
+
currentBase = unwrapExpression(currentMember.object);
|
|
188
199
|
}
|
|
189
|
-
if (
|
|
190
|
-
|
|
200
|
+
if (currentBase.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
201
|
+
currentBase.name === objectName) {
|
|
191
202
|
if (pathParts.length === 0) {
|
|
192
203
|
// Direct method call on the object (e.g., userData.map(...))
|
|
193
204
|
needsEntireObject = true;
|
|
194
205
|
}
|
|
195
206
|
else {
|
|
196
207
|
// Method call on a property (e.g., userData.items.map(...) or userData?.items?.map(...))
|
|
197
|
-
let path = objectName + (
|
|
208
|
+
let path = objectName + (hasOptionalChainingInMethod ? '?' : '');
|
|
198
209
|
path += '.' + pathParts.join('.');
|
|
199
210
|
usages.set(path, memberExpr.range?.[0] || 0);
|
|
200
211
|
}
|
|
@@ -206,11 +217,11 @@ context) {
|
|
|
206
217
|
if (memberExpr.optional) {
|
|
207
218
|
hasOptionalChaining = true;
|
|
208
219
|
}
|
|
209
|
-
current = memberExpr.object;
|
|
220
|
+
current = unwrapExpression(memberExpr.object);
|
|
210
221
|
}
|
|
211
222
|
// Check if we reached the target identifier
|
|
212
|
-
|
|
213
|
-
|
|
223
|
+
const base = unwrapExpression(current);
|
|
224
|
+
if (base.type === utils_1.AST_NODE_TYPES.Identifier && base.name === objectName) {
|
|
214
225
|
// Build the path with optional chaining
|
|
215
226
|
let path = objectName + (hasOptionalChaining ? '?' : '');
|
|
216
227
|
// Add each part with proper formatting (dot notation or bracket notation)
|
|
@@ -230,15 +241,74 @@ context) {
|
|
|
230
241
|
if (!node || visited.has(node))
|
|
231
242
|
return;
|
|
232
243
|
visited.add(node);
|
|
244
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier && node.name === objectName) {
|
|
245
|
+
// Skip TS type assertions, ChainExpression and TSNonNullExpression wrappers
|
|
246
|
+
// (used around the Identifier) so we can attribute the Identifier's usage
|
|
247
|
+
// to its actual parent context (e.g., call/member/assignment) and avoid
|
|
248
|
+
// misclassifying the dependency when determining if the whole object is referenced.
|
|
249
|
+
let wrapperNode = node;
|
|
250
|
+
let effectiveParent = node.parent;
|
|
251
|
+
while (effectiveParent &&
|
|
252
|
+
(effectiveParent.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
253
|
+
effectiveParent.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
254
|
+
effectiveParent.type === utils_1.AST_NODE_TYPES.ChainExpression ||
|
|
255
|
+
effectiveParent.type === utils_1.AST_NODE_TYPES.TSNonNullExpression)) {
|
|
256
|
+
wrapperNode = effectiveParent;
|
|
257
|
+
effectiveParent = effectiveParent.parent;
|
|
258
|
+
}
|
|
259
|
+
// Exclude: property name in `other.objectName` (not our target object)
|
|
260
|
+
const isMemberProperty = effectiveParent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
261
|
+
effectiveParent.property === wrapperNode &&
|
|
262
|
+
!effectiveParent.computed;
|
|
263
|
+
// Exclude: object in `objectName.prop` (handled by MemberExpression visitor for field tracking)
|
|
264
|
+
const isMemberObject = effectiveParent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
265
|
+
effectiveParent.object === wrapperNode;
|
|
266
|
+
// Exclude: key in `{ objectName: value }` (not usage, just a label)
|
|
267
|
+
// Include: shorthand `{ objectName }` (actual usage)
|
|
268
|
+
const isPropertyKey = effectiveParent?.type === utils_1.AST_NODE_TYPES.Property &&
|
|
269
|
+
effectiveParent.key === wrapperNode &&
|
|
270
|
+
!effectiveParent.computed &&
|
|
271
|
+
!effectiveParent.shorthand;
|
|
272
|
+
if (!isMemberProperty && !isMemberObject && !isPropertyKey) {
|
|
273
|
+
isUsed = true;
|
|
274
|
+
// Patterns that require the entire object (cannot refactor to specific fields)
|
|
275
|
+
const isTypeAUsage = effectiveParent?.type === utils_1.AST_NODE_TYPES.ReturnStatement ||
|
|
276
|
+
effectiveParent?.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
|
|
277
|
+
effectiveParent?.type === utils_1.AST_NODE_TYPES.BinaryExpression ||
|
|
278
|
+
effectiveParent?.type === utils_1.AST_NODE_TYPES.LogicalExpression ||
|
|
279
|
+
effectiveParent?.type === utils_1.AST_NODE_TYPES.ConditionalExpression ||
|
|
280
|
+
effectiveParent?.type === utils_1.AST_NODE_TYPES.UnaryExpression ||
|
|
281
|
+
(effectiveParent?.type === utils_1.AST_NODE_TYPES.Property &&
|
|
282
|
+
(effectiveParent.value === wrapperNode ||
|
|
283
|
+
effectiveParent.shorthand ||
|
|
284
|
+
(effectiveParent.key === wrapperNode &&
|
|
285
|
+
effectiveParent.computed))) ||
|
|
286
|
+
effectiveParent?.type === utils_1.AST_NODE_TYPES.TemplateLiteral ||
|
|
287
|
+
effectiveParent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator ||
|
|
288
|
+
effectiveParent?.type === utils_1.AST_NODE_TYPES.AssignmentExpression ||
|
|
289
|
+
effectiveParent?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer ||
|
|
290
|
+
effectiveParent?.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute ||
|
|
291
|
+
effectiveParent?.type === utils_1.AST_NODE_TYPES.SpreadElement ||
|
|
292
|
+
effectiveParent?.type === utils_1.AST_NODE_TYPES.ForInStatement ||
|
|
293
|
+
effectiveParent?.type === utils_1.AST_NODE_TYPES.ForOfStatement ||
|
|
294
|
+
effectiveParent?.type === utils_1.AST_NODE_TYPES.CallExpression;
|
|
295
|
+
if (isTypeAUsage) {
|
|
296
|
+
needsEntireObject = true;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
233
300
|
if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
234
301
|
// Check if the object is being called as a function
|
|
235
|
-
|
|
236
|
-
|
|
302
|
+
const callee = unwrapExpression(node.callee);
|
|
303
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
304
|
+
callee.name === objectName) {
|
|
237
305
|
needsEntireObject = true;
|
|
238
306
|
}
|
|
239
307
|
// Check if the object is directly passed as an argument
|
|
240
308
|
node.arguments.forEach((arg) => {
|
|
241
|
-
|
|
309
|
+
const unwrappedArg = unwrapExpression(arg);
|
|
310
|
+
if (unwrappedArg.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
311
|
+
unwrappedArg.name === objectName) {
|
|
242
312
|
needsEntireObject = true;
|
|
243
313
|
}
|
|
244
314
|
});
|
|
@@ -248,18 +318,21 @@ context) {
|
|
|
248
318
|
// If we find a JSX element, check its attributes for spread operator
|
|
249
319
|
if (node.type === utils_1.AST_NODE_TYPES.JSXElement) {
|
|
250
320
|
node.openingElement.attributes.forEach((attr) => {
|
|
251
|
-
if (attr.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
321
|
+
if (attr.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute) {
|
|
322
|
+
const argument = unwrapExpression(attr.argument);
|
|
323
|
+
if (argument.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
324
|
+
argument.name === objectName) {
|
|
325
|
+
needsEntireObject = true;
|
|
326
|
+
}
|
|
255
327
|
}
|
|
256
328
|
});
|
|
257
329
|
}
|
|
258
330
|
}
|
|
259
331
|
else if (node.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
260
332
|
// If we find a spread operator with our target object, consider it as accessing all properties
|
|
261
|
-
|
|
262
|
-
|
|
333
|
+
const argument = unwrapExpression(node.argument);
|
|
334
|
+
if (argument.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
335
|
+
argument.name === objectName) {
|
|
263
336
|
needsEntireObject = true;
|
|
264
337
|
return;
|
|
265
338
|
}
|
|
@@ -267,43 +340,51 @@ context) {
|
|
|
267
340
|
else if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
268
341
|
// Check if this is accessing a property of our target object
|
|
269
342
|
const memberExpr = node;
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
|
|
273
|
-
if
|
|
274
|
-
|
|
275
|
-
|
|
343
|
+
// Skip TS type assertions, ChainExpression and TSNonNullExpression wrappers
|
|
344
|
+
// so we can attribute the MemberExpression's usage to its actual parent
|
|
345
|
+
// context and avoid misclassifying the dependency.
|
|
346
|
+
// We only process if this is the outermost member expression in a chain.
|
|
347
|
+
let effectiveParent = memberExpr.parent;
|
|
348
|
+
while (effectiveParent &&
|
|
349
|
+
(effectiveParent.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
350
|
+
effectiveParent.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
351
|
+
effectiveParent.type === utils_1.AST_NODE_TYPES.ChainExpression ||
|
|
352
|
+
effectiveParent.type === utils_1.AST_NODE_TYPES.TSNonNullExpression)) {
|
|
353
|
+
effectiveParent = effectiveParent.parent;
|
|
276
354
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
355
|
+
const isIntermediate = effectiveParent && effectiveParent.type === utils_1.AST_NODE_TYPES.MemberExpression;
|
|
356
|
+
if (!isIntermediate) {
|
|
357
|
+
// Check if this member expression involves our target object
|
|
358
|
+
let current = memberExpr;
|
|
359
|
+
let foundTargetObject = false;
|
|
360
|
+
let hasDynamicComputed = false;
|
|
361
|
+
// Walk up the member expression chain to see if it involves our target object
|
|
362
|
+
while (current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
363
|
+
const currentMember = current;
|
|
364
|
+
// Check if this level uses dynamic computed property access
|
|
365
|
+
if (currentMember.computed &&
|
|
366
|
+
currentMember.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
367
|
+
hasDynamicComputed = true;
|
|
368
|
+
}
|
|
369
|
+
current = unwrapExpression(currentMember.object);
|
|
288
370
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
foundTargetObject = true;
|
|
295
|
-
}
|
|
296
|
-
if (foundTargetObject) {
|
|
297
|
-
if (hasDynamicComputed) {
|
|
298
|
-
// Dynamic computed property access means we need the entire object
|
|
299
|
-
needsEntireObject = true;
|
|
300
|
-
return;
|
|
371
|
+
// Check if we reached our target object
|
|
372
|
+
const base = unwrapExpression(current);
|
|
373
|
+
if (base.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
374
|
+
base.name === objectName) {
|
|
375
|
+
foundTargetObject = true;
|
|
301
376
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
377
|
+
if (foundTargetObject) {
|
|
378
|
+
if (hasDynamicComputed) {
|
|
379
|
+
// Dynamic computed property access means we need the entire object
|
|
380
|
+
needsEntireObject = true;
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
// Static property access - add to usages
|
|
384
|
+
const path = buildAccessPath(memberExpr);
|
|
385
|
+
if (path) {
|
|
386
|
+
usages.set(path, memberExpr.range?.[0] || 0);
|
|
387
|
+
}
|
|
307
388
|
}
|
|
308
389
|
}
|
|
309
390
|
}
|
|
@@ -431,7 +512,7 @@ context) {
|
|
|
431
512
|
return posA - posB;
|
|
432
513
|
});
|
|
433
514
|
const filteredUsages = new Set(sortedPaths);
|
|
434
|
-
const notUsed = !needsEntireObject && filteredUsages.size === 0;
|
|
515
|
+
const notUsed = !needsEntireObject && !isUsed && filteredUsages.size === 0;
|
|
435
516
|
return {
|
|
436
517
|
usages: filteredUsages,
|
|
437
518
|
needsEntireObject,
|
|
@@ -486,16 +567,17 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
|
|
|
486
567
|
}
|
|
487
568
|
// Check each dependency in the array
|
|
488
569
|
depsArg.elements.forEach((element) => {
|
|
489
|
-
|
|
570
|
+
const unwrappedElement = element ? unwrapExpression(element) : null;
|
|
571
|
+
if (!unwrappedElement)
|
|
490
572
|
return; // Skip null elements (holes in the array)
|
|
491
|
-
if (
|
|
492
|
-
const objectName =
|
|
573
|
+
if (unwrappedElement.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
574
|
+
const objectName = unwrappedElement.name;
|
|
493
575
|
// Skip type checking if we don't have TypeScript services
|
|
494
576
|
if (hasFullTypeChecking && parserServices) {
|
|
495
577
|
const checker = parserServices.program.getTypeChecker();
|
|
496
578
|
const nodeMap = parserServices.esTreeNodeToTSNodeMap;
|
|
497
579
|
// Skip if the dependency is an array or primitive type
|
|
498
|
-
if (isArrayOrPrimitive(checker,
|
|
580
|
+
if (isArrayOrPrimitive(checker, unwrappedElement, nodeMap)) {
|
|
499
581
|
return;
|
|
500
582
|
}
|
|
501
583
|
}
|
|
@@ -225,6 +225,12 @@ function isTypeGuardFunction(node) {
|
|
|
225
225
|
// Check for type predicates (is keyword)
|
|
226
226
|
if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypePredicate)
|
|
227
227
|
return true;
|
|
228
|
+
// `never` is never inferred: TypeScript infers `void` for a function whose
|
|
229
|
+
// every path throws, so an explicit `: never` always carries more information
|
|
230
|
+
// than inference (callers rely on it for control-flow narrowing and
|
|
231
|
+
// exhaustiveness). Removing it would silently widen the type to `void`.
|
|
232
|
+
if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSNeverKeyword)
|
|
233
|
+
return true;
|
|
228
234
|
// Check for assertion functions (asserts keyword)
|
|
229
235
|
if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
230
236
|
const typeName = typeAnnotation.typeName;
|