@blumintinc/eslint-plugin-blumint 1.20.66 → 1.20.67

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/lib/index.js CHANGED
@@ -223,7 +223,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
223
223
  module.exports = {
224
224
  meta: {
225
225
  name: '@blumintinc/eslint-plugin-blumint',
226
- version: '1.20.66',
226
+ version: '1.20.67',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -559,6 +559,11 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
559
559
  if (isJsonStringify(node.left) && isJsonStringify(node.right)) {
560
560
  const leftArg = node.left.arguments[0];
561
561
  const rightArg = node.right.arguments[0];
562
+ // A zero-argument JSON.stringify() leaves these undefined. The
563
+ // indexed read is typed non-optional, so nothing forces the check.
564
+ if (!leftArg || !rightArg) {
565
+ return;
566
+ }
562
567
  if (isObjectOrArrayType(leftArg) && isObjectOrArrayType(rightArg)) {
563
568
  reportedNodes.add(node);
564
569
  const isEqual = node.operator === '===';
@@ -712,8 +712,12 @@ See: https://react.dev/learn/your-first-component#nesting-and-organizing-compone
712
712
  if (node.name.type !== utils_1.AST_NODE_TYPES.JSXIdentifier)
713
713
  return;
714
714
  const attrName = node.name.name;
715
- // Check if it's a component-type prop
716
- if (!/(Wrapper|Component|Template|Header|Footer)$/.test(attrName)) {
715
+ // Check if it's a component-type prop. A non-PascalCase prop (e.g.
716
+ // renderHeader) is a render callback used with a render={...} prop, not
717
+ // a component—skip it, mirroring the binding-side carve-out above. The
718
+ // suffix alone is not enough: it matches the tail of renderHeader.
719
+ if (!isPascalCaseName(attrName) ||
720
+ !/(Wrapper|Component|Template|Header|Footer)$/.test(attrName)) {
717
721
  return;
718
722
  }
719
723
  if (!node.value ||
@@ -46,6 +46,10 @@ const NON_COMPOSITING_VALUES = {
46
46
  perspective: new Set(['none']),
47
47
  'will-change': new Set(['auto', 'unset', 'initial', 'inherit', 'revert']),
48
48
  'backface-visibility': new Set(['visible']),
49
+ // `normal` is the initial value and creates no stacking context. Because
50
+ // mix-blend-mode is not inherited, `initial`/`unset`/`revert` all resolve to
51
+ // it. `inherit` is excluded: it can resolve to a blending value set higher up.
52
+ 'mix-blend-mode': new Set(['normal', 'initial', 'unset', 'revert']),
49
53
  };
50
54
  exports.noCompositingLayerProps = (0, createRule_1.createRule)({
51
55
  name: 'no-compositing-layer-props',
@@ -114,9 +114,12 @@ const containsJsxInUseMemo = (node) => {
114
114
  body.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
115
115
  body.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
116
116
  body.callee.property.name === 'map') {
117
+ // A zero-argument .map() leaves this undefined. The indexed read is
118
+ // typed non-optional, so nothing forces the check.
117
119
  const mapCallback = body.arguments[0];
118
- if ((mapCallback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
119
- mapCallback.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
120
+ if (mapCallback &&
121
+ (mapCallback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
122
+ mapCallback.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
120
123
  isJsxElement(mapCallback.body)) {
121
124
  return true;
122
125
  }
@@ -1 +1,2 @@
1
- export declare const optimizeObjectBooleanConditions: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"extractBooleanCondition", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const optimizeObjectBooleanConditions: TSESLint.RuleModule<"extractBooleanCondition", [], TSESLint.RuleListener>;
@@ -4,6 +4,270 @@ exports.optimizeObjectBooleanConditions = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
6
  const HOOK_NAMES = new Set(['useEffect', 'useCallback', 'useMemo']);
7
+ const STATE_HOOK_NAMES = new Set(['useState']);
8
+ /**
9
+ * Approved boolean-name prefixes, kept in sync with `DEFAULT_BOOLEAN_PREFIXES`
10
+ * in `enforce-boolean-naming-prefixes`. The list is duplicated rather than
11
+ * imported because that rule keeps it module-private, and exporting it would
12
+ * make a single change span two rule files, which the repository's
13
+ * one-rule-per-commit scope contract forbids.
14
+ *
15
+ * The duplication matters for correctness, not style: `enforce-boolean-naming-prefixes`
16
+ * *requires* booleans to carry one of these prefixes, so treating `!isCollapsed`
17
+ * as an object dependency would make the two rules contradict each other.
18
+ *
19
+ * `have` and `were` are the plural forms that rule derives at runtime from
20
+ * `has` and `was`; `are` already ships in its base list.
21
+ */
22
+ const BOOLEAN_NAME_PREFIXES = [
23
+ 'is',
24
+ 'are',
25
+ 'has',
26
+ 'have',
27
+ 'does',
28
+ 'can',
29
+ 'should',
30
+ 'will',
31
+ 'was',
32
+ 'were',
33
+ 'had',
34
+ 'did',
35
+ 'would',
36
+ 'must',
37
+ 'allows',
38
+ 'supports',
39
+ 'needs',
40
+ 'asserts',
41
+ 'includes',
42
+ ];
43
+ /**
44
+ * A name carries a boolean prefix only when the prefix ends on a word boundary,
45
+ * so `isVisible` matches while object names that merely start with the same
46
+ * letters (`isolatedConfig`, `canvasContext`, `willowTree`) do not.
47
+ */
48
+ function hasBooleanNamePrefix(name) {
49
+ const normalized = name.startsWith('_') ? name.slice(1) : name;
50
+ const lowerName = normalized.toLowerCase();
51
+ return BOOLEAN_NAME_PREFIXES.some((prefix) => {
52
+ if (!lowerName.startsWith(prefix)) {
53
+ return false;
54
+ }
55
+ if (normalized.length === prefix.length) {
56
+ return true;
57
+ }
58
+ const nextChar = normalized.charAt(prefix.length);
59
+ const isScreamingSnakeCase = normalized === normalized.toUpperCase() && /[a-z]/i.test(normalized);
60
+ if (isScreamingSnakeCase) {
61
+ return nextChar === '_';
62
+ }
63
+ return (nextChar === '_' ||
64
+ nextChar === '$' ||
65
+ (nextChar >= '0' && nextChar <= '9') ||
66
+ (nextChar === nextChar.toUpperCase() &&
67
+ nextChar !== nextChar.toLowerCase()));
68
+ });
69
+ }
70
+ const PRIMITIVE_TYPE_KEYWORDS = new Set([
71
+ utils_1.AST_NODE_TYPES.TSBooleanKeyword,
72
+ utils_1.AST_NODE_TYPES.TSNumberKeyword,
73
+ utils_1.AST_NODE_TYPES.TSStringKeyword,
74
+ utils_1.AST_NODE_TYPES.TSBigIntKeyword,
75
+ ]);
76
+ const NULLISH_TYPE_KEYWORDS = new Set([
77
+ utils_1.AST_NODE_TYPES.TSNullKeyword,
78
+ utils_1.AST_NODE_TYPES.TSUndefinedKeyword,
79
+ ]);
80
+ function isPrimitiveTypeNode(node) {
81
+ if (!node) {
82
+ return false;
83
+ }
84
+ if (PRIMITIVE_TYPE_KEYWORDS.has(node.type)) {
85
+ return true;
86
+ }
87
+ // Literal types (`true`, `'grid'`, `2`) are primitives too.
88
+ if (node.type === utils_1.AST_NODE_TYPES.TSLiteralType) {
89
+ return true;
90
+ }
91
+ // `boolean | undefined` stays a primitive; a union qualifies only when every
92
+ // member is primitive or nullish, and at least one is an actual primitive.
93
+ if (node.type === utils_1.AST_NODE_TYPES.TSUnionType) {
94
+ return (node.types.some((member) => isPrimitiveTypeNode(member)) &&
95
+ node.types.every((member) => isPrimitiveTypeNode(member) || NULLISH_TYPE_KEYWORDS.has(member.type)));
96
+ }
97
+ return false;
98
+ }
99
+ /**
100
+ * Every expression form handled here evaluates to a primitive regardless of its
101
+ * operands: comparisons and arithmetic yield numbers/strings/booleans, and `!`,
102
+ * `typeof` and friends yield booleans/strings/numbers.
103
+ */
104
+ function isPrimitiveExpression(node) {
105
+ if (!node) {
106
+ return false;
107
+ }
108
+ switch (node.type) {
109
+ case utils_1.AST_NODE_TYPES.Literal: {
110
+ const { value } = node;
111
+ return (typeof value === 'string' ||
112
+ typeof value === 'number' ||
113
+ typeof value === 'boolean' ||
114
+ typeof value === 'bigint');
115
+ }
116
+ case utils_1.AST_NODE_TYPES.TemplateLiteral:
117
+ return true;
118
+ case utils_1.AST_NODE_TYPES.UnaryExpression:
119
+ return ['!', '-', '+', '~', 'typeof', 'void'].includes(node.operator);
120
+ case utils_1.AST_NODE_TYPES.BinaryExpression:
121
+ return true;
122
+ case utils_1.AST_NODE_TYPES.TSAsExpression:
123
+ return isPrimitiveTypeNode(node.typeAnnotation);
124
+ default:
125
+ return false;
126
+ }
127
+ }
128
+ function propertyKeyNameOf(key, computed) {
129
+ if (!computed && key.type === utils_1.AST_NODE_TYPES.Identifier) {
130
+ return key.name;
131
+ }
132
+ if (key.type === utils_1.AST_NODE_TYPES.Literal && typeof key.value === 'string') {
133
+ return key.value;
134
+ }
135
+ return undefined;
136
+ }
137
+ function memberTypeOf(members, keyName) {
138
+ for (const member of members) {
139
+ if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
140
+ continue;
141
+ }
142
+ if (propertyKeyNameOf(member.key, member.computed === true) === keyName) {
143
+ return member.typeAnnotation?.typeAnnotation;
144
+ }
145
+ }
146
+ return undefined;
147
+ }
148
+ function findVariableInScopes(scope, name) {
149
+ let currentScope = scope;
150
+ while (currentScope) {
151
+ const variable = currentScope.variables.find((v) => v.name === name);
152
+ if (variable) {
153
+ return variable;
154
+ }
155
+ currentScope = currentScope.upper;
156
+ }
157
+ return undefined;
158
+ }
159
+ /**
160
+ * Resolves the declared type of a property inside an object type, following at
161
+ * most a couple of hops through same-file type aliases and interfaces. Anything
162
+ * that needs cross-file resolution stays unresolved: the rule then falls back to
163
+ * reporting, which is the conservative direction for a suggestion-only rule.
164
+ */
165
+ function propertyTypeOf(typeNode, keyName, scope, depth = 0) {
166
+ if (!typeNode || depth > 2) {
167
+ return undefined;
168
+ }
169
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
170
+ return memberTypeOf(typeNode.members, keyName);
171
+ }
172
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
173
+ for (const member of typeNode.types) {
174
+ const resolved = propertyTypeOf(member, keyName, scope, depth + 1);
175
+ if (resolved) {
176
+ return resolved;
177
+ }
178
+ }
179
+ return undefined;
180
+ }
181
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
182
+ typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
183
+ const variable = findVariableInScopes(scope, typeNode.typeName.name);
184
+ for (const def of variable?.defs ?? []) {
185
+ if (def.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
186
+ return propertyTypeOf(def.node.typeAnnotation, keyName, scope, depth + 1);
187
+ }
188
+ if (def.node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
189
+ return memberTypeOf(def.node.body.body, keyName);
190
+ }
191
+ }
192
+ }
193
+ return undefined;
194
+ }
195
+ /**
196
+ * Finds the type annotation attached to a binding, including the annotation a
197
+ * destructured prop inherits from its object pattern (`({ isOpen }: Props)`).
198
+ */
199
+ function declaredTypeOf(nameNode, scope) {
200
+ if (nameNode.typeAnnotation) {
201
+ return nameNode.typeAnnotation.typeAnnotation;
202
+ }
203
+ let binding = nameNode;
204
+ const defaulted = binding.parent;
205
+ if (defaulted?.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
206
+ defaulted.left === binding) {
207
+ if (defaulted.typeAnnotation) {
208
+ return defaulted.typeAnnotation.typeAnnotation;
209
+ }
210
+ binding = defaulted;
211
+ }
212
+ const property = binding.parent;
213
+ if (property?.type !== utils_1.AST_NODE_TYPES.Property ||
214
+ property.value !== binding ||
215
+ property.parent?.type !== utils_1.AST_NODE_TYPES.ObjectPattern) {
216
+ return undefined;
217
+ }
218
+ const keyName = propertyKeyNameOf(property.key, property.computed);
219
+ if (!keyName) {
220
+ return undefined;
221
+ }
222
+ return propertyTypeOf(property.parent.typeAnnotation?.typeAnnotation, keyName, scope);
223
+ }
224
+ function isPrimitiveStateHookCall(node) {
225
+ if (node.type !== utils_1.AST_NODE_TYPES.CallExpression) {
226
+ return false;
227
+ }
228
+ const { callee } = node;
229
+ const isStateHook = (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
230
+ STATE_HOOK_NAMES.has(callee.name)) ||
231
+ (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
232
+ !callee.computed &&
233
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
234
+ STATE_HOOK_NAMES.has(callee.property.name));
235
+ if (!isStateHook) {
236
+ return false;
237
+ }
238
+ const explicitType = node.typeParameters?.params[0];
239
+ if (explicitType) {
240
+ return isPrimitiveTypeNode(explicitType);
241
+ }
242
+ return isPrimitiveExpression(node.arguments[0]);
243
+ }
244
+ function isPrimitiveInitializedVariable(nameNode, declarator) {
245
+ if (!declarator.init) {
246
+ return false;
247
+ }
248
+ if (declarator.id === nameNode) {
249
+ return isPrimitiveExpression(declarator.init);
250
+ }
251
+ // `const [isOpen, setIsOpen] = useState(false)` binds the state value first.
252
+ if (declarator.id.type === utils_1.AST_NODE_TYPES.ArrayPattern &&
253
+ declarator.id.elements[0] === nameNode) {
254
+ return isPrimitiveStateHookCall(declarator.init);
255
+ }
256
+ return false;
257
+ }
258
+ function isPrimitiveBinding(variable, scope) {
259
+ return variable.defs.some((def) => {
260
+ const nameNode = def.name;
261
+ if (nameNode.type !== utils_1.AST_NODE_TYPES.Identifier) {
262
+ return false;
263
+ }
264
+ if (isPrimitiveTypeNode(declaredTypeOf(nameNode, scope))) {
265
+ return true;
266
+ }
267
+ return (def.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
268
+ isPrimitiveInitializedVariable(nameNode, def.node));
269
+ });
270
+ }
7
271
  function isHookCall(node) {
8
272
  const callee = node.callee;
9
273
  return ((callee.type === utils_1.AST_NODE_TYPES.Identifier &&
@@ -29,20 +293,20 @@ function generateBooleanVariableName(objectName, conditionType, isNegated) {
29
293
  function capitalize(str) {
30
294
  return str.charAt(0).toUpperCase() + str.slice(1);
31
295
  }
32
- function isObjectExistenceCheck(node) {
296
+ function isObjectExistenceCheck(node, isObjectIdentifier) {
33
297
  // Check for patterns like !obj
34
298
  if (node.type === utils_1.AST_NODE_TYPES.UnaryExpression) {
35
299
  // !obj
36
300
  if (node.operator === '!' &&
37
301
  node.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
38
- return true;
302
+ return isObjectIdentifier(node.argument);
39
303
  }
40
304
  // !!obj
41
305
  if (node.operator === '!' &&
42
306
  node.argument.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
43
307
  node.argument.operator === '!' &&
44
308
  node.argument.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
45
- return true;
309
+ return isObjectIdentifier(node.argument.argument);
46
310
  }
47
311
  }
48
312
  return false;
@@ -75,21 +339,25 @@ function isObjectKeyCountCheck(node) {
75
339
  }
76
340
  return false;
77
341
  }
78
- function isComplexBooleanExpression(node) {
342
+ function isComplexBooleanExpression(node, isObjectIdentifier) {
79
343
  // Check for complex expressions involving objects
80
344
  if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
81
- return (containsObjectCondition(node.left) || containsObjectCondition(node.right));
345
+ return (containsObjectCondition(node.left, isObjectIdentifier) ||
346
+ containsObjectCondition(node.right, isObjectIdentifier));
82
347
  }
83
348
  return false;
84
349
  }
85
- function containsObjectCondition(node) {
86
- return (isObjectExistenceCheck(node) ||
350
+ function containsObjectCondition(node, isObjectIdentifier) {
351
+ return (isObjectExistenceCheck(node, isObjectIdentifier) ||
87
352
  isObjectKeyCountCheck(node) ||
88
- isComplexBooleanExpression(node));
353
+ isComplexBooleanExpression(node, isObjectIdentifier));
89
354
  }
90
- function extractObjectName(node) {
355
+ function extractObjectName(node, isObjectIdentifier) {
91
356
  if (node.type === utils_1.AST_NODE_TYPES.UnaryExpression) {
92
- if (node.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
357
+ // Mirror the existence matcher so a primitive negation inside a larger
358
+ // expression never becomes the reported "object".
359
+ if (node.argument.type === utils_1.AST_NODE_TYPES.Identifier &&
360
+ isObjectIdentifier(node.argument)) {
93
361
  return node.argument.name;
94
362
  }
95
363
  }
@@ -105,18 +373,18 @@ function extractObjectName(node) {
105
373
  }
106
374
  else if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
107
375
  // For complex expressions, try to extract from the first object condition found
108
- const leftObj = extractObjectName(node.left);
376
+ const leftObj = extractObjectName(node.left, isObjectIdentifier);
109
377
  if (leftObj)
110
378
  return leftObj;
111
- return extractObjectName(node.right);
379
+ return extractObjectName(node.right, isObjectIdentifier);
112
380
  }
113
381
  return null;
114
382
  }
115
- function analyzeBooleanCondition(node, context) {
383
+ function analyzeBooleanCondition(node, context, isObjectIdentifier) {
116
384
  const sourceCode = context.sourceCode;
117
385
  const expression = sourceCode.getText(node);
118
- if (isObjectExistenceCheck(node)) {
119
- const objectName = extractObjectName(node);
386
+ if (isObjectExistenceCheck(node, isObjectIdentifier)) {
387
+ const objectName = extractObjectName(node, isObjectIdentifier);
120
388
  if (objectName) {
121
389
  const isNegated = node.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
122
390
  node.operator === '!' &&
@@ -132,7 +400,7 @@ function analyzeBooleanCondition(node, context) {
132
400
  }
133
401
  }
134
402
  else if (isObjectKeyCountCheck(node)) {
135
- const objectName = extractObjectName(node);
403
+ const objectName = extractObjectName(node, isObjectIdentifier);
136
404
  if (objectName) {
137
405
  const isNegated = expression.includes('=== 0') || expression.includes('<= 0');
138
406
  return {
@@ -144,8 +412,8 @@ function analyzeBooleanCondition(node, context) {
144
412
  };
145
413
  }
146
414
  }
147
- else if (isComplexBooleanExpression(node)) {
148
- const objectName = extractObjectName(node);
415
+ else if (isComplexBooleanExpression(node, isObjectIdentifier)) {
416
+ const objectName = extractObjectName(node, isObjectIdentifier);
149
417
  if (objectName) {
150
418
  return {
151
419
  type: 'complex',
@@ -158,12 +426,12 @@ function analyzeBooleanCondition(node, context) {
158
426
  }
159
427
  return null;
160
428
  }
161
- function findBooleanConditionsInDependencies(depsArray, context) {
429
+ function findBooleanConditionsInDependencies(depsArray, context, isObjectIdentifier) {
162
430
  const patterns = [];
163
431
  for (const element of depsArray.elements) {
164
432
  if (!element)
165
433
  continue; // Skip holes in array
166
- const pattern = analyzeBooleanCondition(element, context);
434
+ const pattern = analyzeBooleanCondition(element, context, isObjectIdentifier);
167
435
  if (pattern) {
168
436
  patterns.push(pattern);
169
437
  }
@@ -172,20 +440,7 @@ function findBooleanConditionsInDependencies(depsArray, context) {
172
440
  }
173
441
  function isAlreadyBooleanVariable(node) {
174
442
  // Check if the dependency is already a boolean variable (starts with is, has, can, etc.)
175
- if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
176
- const name = node.name;
177
- const booleanPrefixes = [
178
- 'is',
179
- 'has',
180
- 'can',
181
- 'should',
182
- 'will',
183
- 'was',
184
- 'were',
185
- ];
186
- return booleanPrefixes.some((prefix) => name.toLowerCase().startsWith(prefix.toLowerCase()));
187
- }
188
- return false;
443
+ return (node.type === utils_1.AST_NODE_TYPES.Identifier && hasBooleanNamePrefix(node.name));
189
444
  }
190
445
  exports.optimizeObjectBooleanConditions = (0, createRule_1.createRule)({
191
446
  name: 'optimize-object-boolean-conditions',
@@ -212,8 +467,28 @@ exports.optimizeObjectBooleanConditions = (0, createRule_1.createRule)({
212
467
  if (!depsArg || depsArg.type !== utils_1.AST_NODE_TYPES.ArrayExpression) {
213
468
  return;
214
469
  }
470
+ const scope = context.getScope();
471
+ const primitiveByName = new Map();
472
+ // A negated identifier only carries the cost this rule targets when it
473
+ // can actually hold an object. Approved boolean prefixes (which
474
+ // enforce-boolean-naming-prefixes mandates) and same-file primitive
475
+ // evidence both rule that out; unresolved identifiers keep reporting.
476
+ const isObjectIdentifier = (identifier) => {
477
+ const { name } = identifier;
478
+ if (hasBooleanNamePrefix(name)) {
479
+ return false;
480
+ }
481
+ const cached = primitiveByName.get(name);
482
+ if (cached !== undefined) {
483
+ return !cached;
484
+ }
485
+ const variable = findVariableInScopes(scope, name);
486
+ const isPrimitive = !!variable && isPrimitiveBinding(variable, scope);
487
+ primitiveByName.set(name, isPrimitive);
488
+ return !isPrimitive;
489
+ };
215
490
  // Find boolean conditions in dependencies
216
- const patterns = findBooleanConditionsInDependencies(depsArg, context);
491
+ const patterns = findBooleanConditionsInDependencies(depsArg, context, isObjectIdentifier);
217
492
  for (const pattern of patterns) {
218
493
  // Skip if it's already a boolean variable
219
494
  if (isAlreadyBooleanVariable(pattern.node)) {
@@ -199,7 +199,7 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
199
199
  ],
200
200
  messages: {
201
201
  preferGetter: 'Problem: Method "{{name}}" is a parameterless synchronous method that returns a value. → Impact: Calling it with parentheses disguises that it behaves like a computed property, increasing the chance callers omit parentheses or treat it like an action. → Solution: Convert it to a getter: "get {{suggestedName}}()" to signal property semantics and remove call-site parentheses.',
202
- preferGetterSideEffect: 'Problem: Method "{{name}}" looks like a computed value. → Impact: However, {{reason}}, so converting it to a getter would execute side effects on every property access and violate the principle of least surprise. → Solution: Keep it as a method to indicate it performs work, or remove the side effects before converting to "get {{suggestedName}}()".',
202
+ preferGetterSideEffect: 'Problem: Method "{{name}}" looks like a computed value. → Impact: However, {{reason}}, so converting it to a getter would execute side effects on every property access and violate the principle of least surprise. → Solution: If the side effects are intentional, declare them with an "@sideEffect" (or "@mutates") tag inside a /** */ JSDoc block placed immediately above "{{name}}" with no blank line between: the tag warns callers that reading this value performs work, and the rule honors it (option "respectJsDocSideEffects", enabled by default) and stops reporting the method. If the side effects are not intentional, remove them and convert the method to "get {{suggestedName}}()".',
203
203
  },
204
204
  },
205
205
  defaultOptions: [DEFAULT_OPTIONS],
@@ -3,6 +3,7 @@ type Options = [
3
3
  {
4
4
  components?: string[];
5
5
  allowedProps?: string[];
6
+ printWidth?: number;
6
7
  }
7
8
  ];
8
9
  export declare const preferSxPropOverSystemProps: TSESLint.RuleModule<"preferSxProp", Options, TSESLint.RuleListener>;