@blumintinc/eslint-plugin-blumint 1.11.1 → 1.12.1

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
@@ -106,7 +106,7 @@ const enforce_boolean_naming_prefixes_1 = require("./rules/enforce-boolean-namin
106
106
  module.exports = {
107
107
  meta: {
108
108
  name: '@blumintinc/eslint-plugin-blumint',
109
- version: '1.11.1',
109
+ version: '1.12.1',
110
110
  },
111
111
  parseOptions: {
112
112
  ecmaVersion: 2020,
@@ -121,9 +121,45 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
121
121
  }
122
122
  // Check for logical expressions (&&, ||)
123
123
  if (node.init.type === utils_1.AST_NODE_TYPES.LogicalExpression &&
124
- ['&&', '||'].includes(node.init.operator)) {
124
+ node.init.operator === '&&') {
125
125
  return true;
126
126
  }
127
+ // Special case for logical OR (||) - only consider it boolean if:
128
+ // 1. It's used with boolean literals or
129
+ // 2. It's not used with array/object literals as fallbacks
130
+ if (node.init.type === utils_1.AST_NODE_TYPES.LogicalExpression &&
131
+ node.init.operator === '||') {
132
+ // Check if right side is a non-boolean literal (array, object, string, number)
133
+ const rightSide = node.init.right;
134
+ if (rightSide.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
135
+ rightSide.type === utils_1.AST_NODE_TYPES.ObjectExpression ||
136
+ (rightSide.type === utils_1.AST_NODE_TYPES.Literal &&
137
+ typeof rightSide.value !== 'boolean')) {
138
+ return false;
139
+ }
140
+ // If right side is a boolean literal, it's likely a boolean variable
141
+ if (rightSide.type === utils_1.AST_NODE_TYPES.Literal &&
142
+ typeof rightSide.value === 'boolean') {
143
+ return true;
144
+ }
145
+ // For other cases, we need to be more careful
146
+ // If we can determine the left side is a boolean, then it's a boolean variable
147
+ const leftSide = node.init.left;
148
+ if ((leftSide.type === utils_1.AST_NODE_TYPES.Literal &&
149
+ typeof leftSide.value === 'boolean') ||
150
+ (leftSide.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
151
+ leftSide.operator === '!')) {
152
+ return true;
153
+ }
154
+ // For function calls, check if the function name suggests it returns a boolean
155
+ if (leftSide.type === utils_1.AST_NODE_TYPES.CallExpression &&
156
+ leftSide.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
157
+ const calleeName = leftSide.callee.name;
158
+ return approvedPrefixes.some((prefix) => calleeName.toLowerCase().startsWith(prefix.toLowerCase()));
159
+ }
160
+ // Default to false for other cases with || to avoid false positives
161
+ return false;
162
+ }
127
163
  // Check for unary expressions with ! operator
128
164
  if (node.init.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
129
165
  node.init.operator === '!') {
@@ -349,6 +385,17 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
349
385
  });
350
386
  }
351
387
  }
388
+ /**
389
+ * Check if an identifier is imported from an external module
390
+ */
391
+ function isImportedIdentifier(name) {
392
+ const scope = context.getScope();
393
+ const variable = scope.variables.find((v) => v.name === name);
394
+ if (!variable)
395
+ return false;
396
+ // Check if it's an import binding
397
+ return variable.defs.some((def) => def.type === 'ImportBinding');
398
+ }
352
399
  /**
353
400
  * Check property definitions for boolean values
354
401
  */
@@ -370,16 +417,51 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
370
417
  utils_1.AST_NODE_TYPES.TSBooleanKeyword) {
371
418
  isBooleanProperty = true;
372
419
  }
420
+ // Skip checking if this property is part of an object literal passed to an external function
373
421
  if (isBooleanProperty && !hasApprovedPrefix(propertyName)) {
374
- context.report({
375
- node: node.key,
376
- messageId: 'missingBooleanPrefix',
377
- data: {
378
- type: 'property',
379
- name: propertyName,
380
- prefixes: formatPrefixes(),
381
- },
382
- });
422
+ // Special cases for common Node.js API boolean properties
423
+ if ((propertyName === 'recursive' || propertyName === 'keepAlive') &&
424
+ node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
425
+ node.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
426
+ return; // Skip checking for these properties in object literals passed to functions
427
+ }
428
+ // Check if this property is in an object literal that's an argument to a function call
429
+ let isExternalApiCall = false;
430
+ // Navigate up to find if we're in an object expression that's an argument to a function call
431
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
432
+ node.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
433
+ const callExpression = node.parent.parent;
434
+ // Check if the function being called is an identifier (like mkdirSync, createServer, etc.)
435
+ if (callExpression.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
436
+ const calleeName = callExpression.callee.name;
437
+ if (isImportedIdentifier(calleeName)) {
438
+ isExternalApiCall = true;
439
+ }
440
+ }
441
+ // Also check for member expressions like fs.mkdirSync
442
+ if (callExpression.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
443
+ // For member expressions, check if the object is imported
444
+ const objectNode = callExpression.callee.object;
445
+ if (objectNode.type === utils_1.AST_NODE_TYPES.Identifier) {
446
+ const objectName = objectNode.name;
447
+ if (isImportedIdentifier(objectName)) {
448
+ isExternalApiCall = true;
449
+ }
450
+ }
451
+ }
452
+ }
453
+ // Only report if it's not an external API call
454
+ if (!isExternalApiCall) {
455
+ context.report({
456
+ node: node.key,
457
+ messageId: 'missingBooleanPrefix',
458
+ data: {
459
+ type: 'property',
460
+ name: propertyName,
461
+ prefixes: formatPrefixes(),
462
+ },
463
+ });
464
+ }
383
465
  }
384
466
  }
385
467
  /**
@@ -394,15 +476,35 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
394
476
  node.typeAnnotation.typeAnnotation.type ===
395
477
  utils_1.AST_NODE_TYPES.TSBooleanKeyword &&
396
478
  !hasApprovedPrefix(propertyName)) {
397
- context.report({
398
- node: node.key,
399
- messageId: 'missingBooleanPrefix',
400
- data: {
401
- type: 'property',
402
- name: propertyName,
403
- prefixes: formatPrefixes(),
404
- },
405
- });
479
+ // Skip if this property is part of a parameter's type annotation
480
+ // Check if this property signature is inside a parameter's type annotation
481
+ let isInParameterType = false;
482
+ let parent = node.parent;
483
+ while (parent) {
484
+ if (parent.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
485
+ const grandParent = parent.parent;
486
+ if (grandParent?.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation &&
487
+ grandParent.parent?.type === utils_1.AST_NODE_TYPES.Identifier &&
488
+ grandParent.parent.parent?.type ===
489
+ utils_1.AST_NODE_TYPES.FunctionDeclaration) {
490
+ isInParameterType = true;
491
+ break;
492
+ }
493
+ }
494
+ parent = parent.parent;
495
+ }
496
+ // Only report if not in a parameter type annotation
497
+ if (!isInParameterType) {
498
+ context.report({
499
+ node: node.key,
500
+ messageId: 'missingBooleanPrefix',
501
+ data: {
502
+ type: 'property',
503
+ name: propertyName,
504
+ prefixes: formatPrefixes(),
505
+ },
506
+ });
507
+ }
406
508
  }
407
509
  }
408
510
  /**
@@ -427,6 +529,31 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
427
529
  },
428
530
  });
429
531
  }
532
+ // Check if the parameter has an object type with boolean properties
533
+ if (node.typeAnnotation?.typeAnnotation &&
534
+ node.typeAnnotation.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
535
+ const typeLiteral = node.typeAnnotation.typeAnnotation;
536
+ // Check each member of the type literal
537
+ for (const member of typeLiteral.members) {
538
+ if (member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
539
+ member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
540
+ member.typeAnnotation?.typeAnnotation.type ===
541
+ utils_1.AST_NODE_TYPES.TSBooleanKeyword) {
542
+ const propertyName = member.key.name;
543
+ if (!hasApprovedPrefix(propertyName)) {
544
+ context.report({
545
+ node: member.key,
546
+ messageId: 'missingBooleanPrefix',
547
+ data: {
548
+ type: 'property',
549
+ name: propertyName,
550
+ prefixes: formatPrefixes(),
551
+ },
552
+ });
553
+ }
554
+ }
555
+ }
556
+ }
430
557
  }
431
558
  return {
432
559
  VariableDeclarator: checkVariableDeclaration,
@@ -5,11 +5,853 @@ const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
6
  // Common negative prefixes for boolean variables
7
7
  const BOOLEAN_NEGATIVE_PREFIXES = ['not', 'no', 'non', 'un', 'in', 'dis'];
8
+ const IN_EXCEPTIONS = [
9
+ 'in',
10
+ 'inane',
11
+ 'inaugural',
12
+ 'inaugurated',
13
+ 'inauguration',
14
+ 'inbound',
15
+ 'inbox',
16
+ 'incandescent',
17
+ 'incarcerated',
18
+ 'incarceration',
19
+ 'incarnation',
20
+ 'incendiary',
21
+ 'incense',
22
+ 'incentive',
23
+ 'incentives',
24
+ 'inception',
25
+ 'incest',
26
+ 'inch',
27
+ 'inches',
28
+ 'incidence',
29
+ 'incident',
30
+ 'incidental',
31
+ 'incidentally',
32
+ 'incidents',
33
+ 'incision',
34
+ 'incite',
35
+ 'inciting',
36
+ 'inclination',
37
+ 'incline',
38
+ 'inclined',
39
+ 'include',
40
+ 'included',
41
+ 'includes',
42
+ 'including',
43
+ 'inclusion',
44
+ 'inclusive',
45
+ 'income',
46
+ 'incomes',
47
+ 'incoming',
48
+ 'incorporate',
49
+ 'incorporated',
50
+ 'incorporates',
51
+ 'incorporating',
52
+ 'incorporation',
53
+ 'increase',
54
+ 'increased',
55
+ 'increases',
56
+ 'increasing',
57
+ 'increasingly',
58
+ 'incremental',
59
+ 'increments',
60
+ 'incriminating',
61
+ 'incubation',
62
+ 'incubator',
63
+ 'incumbent',
64
+ 'incumbents',
65
+ 'incur',
66
+ 'incurred',
67
+ 'indebted',
68
+ 'indeed',
69
+ 'index',
70
+ 'indexed',
71
+ 'indexes',
72
+ 'indexing',
73
+ 'indicate',
74
+ 'indicated',
75
+ 'indicates',
76
+ 'indicating',
77
+ 'indication',
78
+ 'indications',
79
+ 'indicative',
80
+ 'indicator',
81
+ 'indicators',
82
+ 'indices',
83
+ 'indicted',
84
+ 'indictment',
85
+ 'indictments',
86
+ 'indie',
87
+ 'indies',
88
+ 'indigenous',
89
+ 'indignation',
90
+ 'indigo',
91
+ 'indoor',
92
+ 'indoors',
93
+ 'induce',
94
+ 'induced',
95
+ 'induces',
96
+ 'inducing',
97
+ 'inducted',
98
+ 'induction',
99
+ 'indulge',
100
+ 'indulgence',
101
+ 'indulgent',
102
+ 'indulging',
103
+ 'industrial',
104
+ 'industrialized',
105
+ 'industries',
106
+ 'industry',
107
+ 'inexpensive',
108
+ 'infancy',
109
+ 'infant',
110
+ 'infantry',
111
+ 'infants',
112
+ 'infect',
113
+ 'infected',
114
+ 'infection',
115
+ 'infections',
116
+ 'infectious',
117
+ 'infer',
118
+ 'inference',
119
+ 'inferior',
120
+ 'inferno',
121
+ 'inferred',
122
+ 'infertility',
123
+ 'infestation',
124
+ 'infested',
125
+ 'infidelity',
126
+ 'infield',
127
+ 'infiltrate',
128
+ 'infiltrated',
129
+ 'infiltration',
130
+ 'infinite',
131
+ 'infinitely',
132
+ 'infinity',
133
+ 'infirmary',
134
+ 'inflamed',
135
+ 'inflammation',
136
+ 'inflammatory',
137
+ 'inflatable',
138
+ 'inflate',
139
+ 'inflated',
140
+ 'inflation',
141
+ 'inflict',
142
+ 'inflicted',
143
+ 'inflicting',
144
+ 'influence',
145
+ 'influenced',
146
+ 'influencers',
147
+ 'influences',
148
+ 'influencing',
149
+ 'influential',
150
+ 'influenza',
151
+ 'influx',
152
+ 'info',
153
+ 'infographic',
154
+ 'inform',
155
+ 'informant',
156
+ 'informants',
157
+ 'informatics',
158
+ 'information',
159
+ 'informational',
160
+ 'informative',
161
+ 'informed',
162
+ 'informing',
163
+ 'informs',
164
+ 'infrared',
165
+ 'infrastructure',
166
+ 'infringed',
167
+ 'infringement',
168
+ 'infringing',
169
+ 'infuriating',
170
+ 'infused',
171
+ 'infusion',
172
+ 'ingenious',
173
+ 'ingenuity',
174
+ 'ingested',
175
+ 'ingot',
176
+ 'ingrained',
177
+ 'ingredient',
178
+ 'ingredients',
179
+ 'inhabit',
180
+ 'inhabitants',
181
+ 'inhabited',
182
+ 'inhalation',
183
+ 'inhale',
184
+ 'inhaled',
185
+ 'inhaling',
186
+ 'inherent',
187
+ 'inherently',
188
+ 'inheritance',
189
+ 'inherited',
190
+ 'initial',
191
+ 'initially',
192
+ 'initials',
193
+ 'initiate',
194
+ 'initiated',
195
+ 'initiates',
196
+ 'initiating',
197
+ 'initiation',
198
+ 'initiative',
199
+ 'initiatives',
200
+ 'inject',
201
+ 'injected',
202
+ 'injecting',
203
+ 'injection',
204
+ 'injections',
205
+ 'injunction',
206
+ 'injure',
207
+ 'injured',
208
+ 'injuries',
209
+ 'injuring',
210
+ 'injury',
211
+ 'ink',
212
+ 'inked',
213
+ 'inks',
214
+ 'inland',
215
+ 'inlay',
216
+ 'inlet',
217
+ 'inmate',
218
+ 'inmates',
219
+ 'inn',
220
+ 'innate',
221
+ 'inner',
222
+ 'inning',
223
+ 'innings',
224
+ 'innovate',
225
+ 'innovation',
226
+ 'innovations',
227
+ 'innovative',
228
+ 'innovator',
229
+ 'innovators',
230
+ 'inns',
231
+ 'inpatient',
232
+ 'input',
233
+ 'inputs',
234
+ 'inquest',
235
+ 'inquire',
236
+ 'inquiries',
237
+ 'inquiry',
238
+ 'inquisition',
239
+ 'ins',
240
+ 'inscribed',
241
+ 'inscription',
242
+ 'inscriptions',
243
+ 'insect',
244
+ 'insects',
245
+ 'insert',
246
+ 'inserted',
247
+ 'inserting',
248
+ 'insertion',
249
+ 'inserts',
250
+ 'inside',
251
+ 'insider',
252
+ 'insiders',
253
+ 'insides',
254
+ 'insidious',
255
+ 'insight',
256
+ 'insightful',
257
+ 'insights',
258
+ 'insignia',
259
+ 'insist',
260
+ 'insisted',
261
+ 'insistence',
262
+ 'insistent',
263
+ 'insisting',
264
+ 'insists',
265
+ 'insofar',
266
+ 'inspect',
267
+ 'inspected',
268
+ 'inspecting',
269
+ 'inspection',
270
+ 'inspections',
271
+ 'inspector',
272
+ 'inspectors',
273
+ 'inspiration',
274
+ 'inspirational',
275
+ 'inspirations',
276
+ 'inspire',
277
+ 'inspired',
278
+ 'inspires',
279
+ 'inspiring',
280
+ 'install',
281
+ 'installation',
282
+ 'installations',
283
+ 'installed',
284
+ 'installing',
285
+ 'installment',
286
+ 'installments',
287
+ 'instalment',
288
+ 'instance',
289
+ 'instances',
290
+ 'instant',
291
+ 'instantaneous',
292
+ 'instantly',
293
+ 'instead',
294
+ 'instigated',
295
+ 'instinct',
296
+ 'instinctive',
297
+ 'instinctively',
298
+ 'instincts',
299
+ 'institute',
300
+ 'instituted',
301
+ 'institutes',
302
+ 'institution',
303
+ 'institutional',
304
+ 'institutions',
305
+ 'instruct',
306
+ 'instructed',
307
+ 'instructing',
308
+ 'instruction',
309
+ 'instructional',
310
+ 'instructions',
311
+ 'instructor',
312
+ 'instructors',
313
+ 'instrument',
314
+ 'instrumental',
315
+ 'instrumentation',
316
+ 'instruments',
317
+ 'insular',
318
+ 'insulated',
319
+ 'insulating',
320
+ 'insulation',
321
+ 'insulin',
322
+ 'insult',
323
+ 'insulted',
324
+ 'insulting',
325
+ 'insults',
326
+ 'insurance',
327
+ 'insure',
328
+ 'insured',
329
+ 'insurer',
330
+ 'insurers',
331
+ 'insurgency',
332
+ 'insurgent',
333
+ 'insurgents',
334
+ 'insurrection',
335
+ 'intake',
336
+ 'integers',
337
+ 'intel',
338
+ 'intellect',
339
+ 'intellectual',
340
+ 'intellectually',
341
+ 'intellectuals',
342
+ 'intelligence',
343
+ 'intelligent',
344
+ 'intelligently',
345
+ 'intend',
346
+ 'intended',
347
+ 'intending',
348
+ 'intends',
349
+ 'intense',
350
+ 'intensely',
351
+ 'intensified',
352
+ 'intensifies',
353
+ 'intensify',
354
+ 'intensity',
355
+ 'intensive',
356
+ 'intent',
357
+ 'intention',
358
+ 'intentional',
359
+ 'intentionally',
360
+ 'intentions',
361
+ 'inter',
362
+ 'interact',
363
+ 'interacted',
364
+ 'interacting',
365
+ 'interaction',
366
+ 'interactions',
367
+ 'interactive',
368
+ 'interacts',
369
+ 'intercept',
370
+ 'intercepted',
371
+ 'interception',
372
+ 'interceptions',
373
+ 'interchange',
374
+ 'interchangeable',
375
+ 'interconnected',
376
+ 'intercourse',
377
+ 'interest',
378
+ 'interested',
379
+ 'interesting',
380
+ 'interestingly',
381
+ 'interests',
382
+ 'interface',
383
+ 'interfaces',
384
+ 'interfere',
385
+ 'interfered',
386
+ 'interference',
387
+ 'interferes',
388
+ 'interfering',
389
+ 'interim',
390
+ 'interior',
391
+ 'interiors',
392
+ 'interlude',
393
+ 'intermediary',
394
+ 'intermediate',
395
+ 'intermission',
396
+ 'intermittent',
397
+ 'intermittently',
398
+ 'intern',
399
+ 'internal',
400
+ 'internally',
401
+ 'international',
402
+ 'internationally',
403
+ 'internationals',
404
+ 'interns',
405
+ 'internship',
406
+ 'internships',
407
+ 'interpersonal',
408
+ 'interplay',
409
+ 'interpret',
410
+ 'interpretation',
411
+ 'interpretations',
412
+ 'interpreted',
413
+ 'interpreter',
414
+ 'interpreters',
415
+ 'interpreting',
416
+ 'interpretive',
417
+ 'interracial',
418
+ 'interrogated',
419
+ 'interrogation',
420
+ 'interrupt',
421
+ 'interrupted',
422
+ 'interrupting',
423
+ 'interruption',
424
+ 'interruptions',
425
+ 'interrupts',
426
+ 'intersect',
427
+ 'intersection',
428
+ 'intersections',
429
+ 'interspersed',
430
+ 'interstate',
431
+ 'interstellar',
432
+ 'intertwined',
433
+ 'interval',
434
+ 'intervals',
435
+ 'intervene',
436
+ 'intervened',
437
+ 'intervening',
438
+ 'intervention',
439
+ 'interventions',
440
+ 'interview',
441
+ 'interviewed',
442
+ 'interviewer',
443
+ 'interviewing',
444
+ 'interviews',
445
+ 'intestinal',
446
+ 'intestine',
447
+ 'intestines',
448
+ 'intimacy',
449
+ 'intimate',
450
+ 'intimately',
451
+ 'intimidate',
452
+ 'intimidated',
453
+ 'intimidating',
454
+ 'intimidation',
455
+ 'into',
456
+ 'intoxicated',
457
+ 'intoxication',
458
+ 'intracellular',
459
+ 'intravenous',
460
+ 'intricate',
461
+ 'intrigue',
462
+ 'intrigued',
463
+ 'intriguing',
464
+ 'intrinsic',
465
+ 'intrinsically',
466
+ 'intro',
467
+ 'introduce',
468
+ 'introduced',
469
+ 'introduces',
470
+ 'introducing',
471
+ 'introduction',
472
+ 'introductions',
473
+ 'introductory',
474
+ 'intruder',
475
+ 'intruders',
476
+ 'intrusion',
477
+ 'intrusive',
478
+ 'intuition',
479
+ 'intuitive',
480
+ 'intuitively',
481
+ 'inundated',
482
+ 'invade',
483
+ 'invaded',
484
+ 'invaders',
485
+ 'invading',
486
+ 'invasion',
487
+ 'invasions',
488
+ 'invasive',
489
+ 'invent',
490
+ 'invented',
491
+ 'inventing',
492
+ 'invention',
493
+ 'inventions',
494
+ 'inventive',
495
+ 'inventor',
496
+ 'inventories',
497
+ 'inventors',
498
+ 'inventory',
499
+ 'inverness',
500
+ 'inverse',
501
+ 'inversion',
502
+ 'inverted',
503
+ 'invest',
504
+ 'invested',
505
+ 'investigate',
506
+ 'investigated',
507
+ 'investigates',
508
+ 'investigating',
509
+ 'investigation',
510
+ 'investigations',
511
+ 'investigative',
512
+ 'investigator',
513
+ 'investigators',
514
+ 'investing',
515
+ 'investment',
516
+ 'investments',
517
+ 'investor',
518
+ 'investors',
519
+ 'invests',
520
+ 'invitation',
521
+ 'invitational',
522
+ 'invitations',
523
+ 'invite',
524
+ 'invited',
525
+ 'invites',
526
+ 'inviting',
527
+ 'invoice',
528
+ 'invoices',
529
+ 'invoke',
530
+ 'invoked',
531
+ 'invoking',
532
+ 'involve',
533
+ 'involved',
534
+ 'involvement',
535
+ 'involves',
536
+ 'involving',
537
+ 'inward',
538
+ 'init',
539
+ 'initial',
540
+ 'initialization',
541
+ 'initialize',
542
+ 'initialized',
543
+ 'initializing',
544
+ 'initializes',
545
+ 'initializing',
546
+ 'initializes',
547
+ ];
548
+ const NO_EXCEPTIONS = [
549
+ 'nobility',
550
+ 'noble',
551
+ 'nobleman',
552
+ 'nobles',
553
+ 'nobly',
554
+ 'nocturnal',
555
+ 'nod',
556
+ 'nodded',
557
+ 'nodding',
558
+ 'node',
559
+ 'nodes',
560
+ 'nods',
561
+ 'noel',
562
+ 'noir',
563
+ 'noise',
564
+ 'noises',
565
+ 'noisy',
566
+ 'nomad',
567
+ 'nomadic',
568
+ 'nomenclature',
569
+ 'nominal',
570
+ 'nominally',
571
+ 'nominate',
572
+ 'nominated',
573
+ 'nominating',
574
+ 'nomination',
575
+ 'nominations',
576
+ 'nominee',
577
+ 'nominees',
578
+ 'noodle',
579
+ 'noodles',
580
+ 'nook',
581
+ 'noon',
582
+ 'noose',
583
+ 'norM',
584
+ 'norma',
585
+ 'normal',
586
+ 'normalized',
587
+ 'normally',
588
+ 'normative',
589
+ 'norms',
590
+ 'north',
591
+ 'northbound',
592
+ 'northeast',
593
+ 'northeastern',
594
+ 'northern',
595
+ 'northward',
596
+ 'northwest',
597
+ 'northwestern',
598
+ 'nose',
599
+ 'nosey',
600
+ 'nostalgia',
601
+ 'nostalgic',
602
+ 'nostrils',
603
+ 'notable',
604
+ 'notably',
605
+ 'notary',
606
+ 'notation',
607
+ 'notch',
608
+ 'notched',
609
+ 'notebook',
610
+ 'notebooks',
611
+ 'noted',
612
+ 'notes',
613
+ 'noteworthy',
614
+ 'notice',
615
+ 'noticeable',
616
+ 'noticeably',
617
+ 'noticed',
618
+ 'notices',
619
+ 'noticing',
620
+ 'notification',
621
+ 'notifications',
622
+ 'notified',
623
+ 'notify',
624
+ 'notifying',
625
+ 'notion',
626
+ 'notions',
627
+ 'notoriety',
628
+ 'notorious',
629
+ 'notoriously',
630
+ 'noun',
631
+ 'nouns',
632
+ 'nourishment',
633
+ 'nouveau',
634
+ 'nova',
635
+ 'novel',
636
+ 'novelist',
637
+ 'novelists',
638
+ 'novella',
639
+ 'novels',
640
+ 'novelty',
641
+ 'novice',
642
+ 'now',
643
+ 'nowadays',
644
+ 'nozzle',
645
+ ];
646
+ const UN_EXCEPTIONS = [
647
+ 'unanimous',
648
+ 'unanimously',
649
+ 'uncle',
650
+ 'uncles',
651
+ 'under',
652
+ 'underage',
653
+ 'undercover',
654
+ 'undercut',
655
+ 'underdog',
656
+ 'underestimate',
657
+ 'underestimated',
658
+ 'undergo',
659
+ 'undergoes',
660
+ 'undergoing',
661
+ 'undergone',
662
+ 'undergraduate',
663
+ 'undergraduates',
664
+ 'underground',
665
+ 'underlined',
666
+ 'underlying',
667
+ 'undermine',
668
+ 'undermined',
669
+ 'undermines',
670
+ 'undermining',
671
+ 'underneath',
672
+ 'underrated',
673
+ 'underside',
674
+ 'understand',
675
+ 'understandable',
676
+ 'understandably',
677
+ 'understanding',
678
+ 'understands',
679
+ 'understated',
680
+ 'understatement',
681
+ 'understood',
682
+ 'undertake',
683
+ 'undertaken',
684
+ 'undertaker',
685
+ 'undertaking',
686
+ 'undertook',
687
+ 'underwater',
688
+ 'underway',
689
+ 'underwear',
690
+ 'underwent',
691
+ 'underwood',
692
+ 'underworld',
693
+ 'underwriting',
694
+ 'unicorn',
695
+ 'unicorns',
696
+ 'unification',
697
+ 'unified',
698
+ 'uniform',
699
+ 'uniformed',
700
+ 'uniformity',
701
+ 'uniformly',
702
+ 'uniforms',
703
+ 'unify',
704
+ 'unifying',
705
+ 'unilateral',
706
+ 'unilaterally',
707
+ 'union',
708
+ 'unionist',
709
+ 'unionists',
710
+ 'unions',
711
+ 'unique',
712
+ 'uniquely',
713
+ 'uniqueness',
714
+ 'unison',
715
+ 'unit',
716
+ 'unitary',
717
+ 'unite',
718
+ 'united',
719
+ 'unites',
720
+ 'uniting',
721
+ 'units',
722
+ 'unity',
723
+ 'universal',
724
+ 'universally',
725
+ 'universe',
726
+ 'universes',
727
+ 'universities',
728
+ 'university',
729
+ 'unless',
730
+ 'until',
731
+ 'unto',
732
+ ];
733
+ const DIS_EXCEPTIONS = [
734
+ 'disc',
735
+ 'disciple',
736
+ 'disciples',
737
+ 'disciplinary',
738
+ 'discipline',
739
+ 'disciplined',
740
+ 'disciplines',
741
+ 'disco',
742
+ 'discography',
743
+ 'discern',
744
+ 'discerning',
745
+ 'discourse',
746
+ 'discreet',
747
+ 'discreetly',
748
+ 'discrepancies',
749
+ 'discrepancy',
750
+ 'discrete',
751
+ 'discretion',
752
+ 'discretionary',
753
+ 'discriminate',
754
+ 'discriminated',
755
+ 'discriminating',
756
+ 'discrimination',
757
+ 'discriminatory',
758
+ 'discs',
759
+ 'discuss',
760
+ 'discussed',
761
+ 'discusses',
762
+ 'discussing',
763
+ 'discussion',
764
+ 'discussions',
765
+ 'disengage',
766
+ 'disparate',
767
+ 'dispensary',
768
+ 'dispense',
769
+ 'dispenser',
770
+ 'dispensing',
771
+ 'disperse',
772
+ 'dispersed',
773
+ 'dispersion',
774
+ 'disposition',
775
+ 'disseminate',
776
+ 'dissemination',
777
+ 'dissertation',
778
+ 'dissident',
779
+ 'dissidents',
780
+ 'dissimilar',
781
+ 'dissipated',
782
+ 'dissolution',
783
+ 'dissolve',
784
+ 'dissolved',
785
+ 'dissolving',
786
+ 'distal',
787
+ 'distance',
788
+ 'distances',
789
+ 'distancing',
790
+ 'distant',
791
+ 'distillation',
792
+ 'distilled',
793
+ 'distillery',
794
+ 'distinct',
795
+ 'distinction',
796
+ 'distinctions',
797
+ 'distinctive',
798
+ 'distinctly',
799
+ 'distinguish',
800
+ 'distinguished',
801
+ 'distinguishes',
802
+ 'distinguishing',
803
+ 'distort',
804
+ 'distorted',
805
+ 'distortion',
806
+ 'distortions',
807
+ 'distract',
808
+ 'distracted',
809
+ 'distracting',
810
+ 'distraction',
811
+ 'distractions',
812
+ 'distraught',
813
+ 'distribute',
814
+ 'distributed',
815
+ 'distributes',
816
+ 'distributing',
817
+ 'distribution',
818
+ 'distributions',
819
+ 'distributor',
820
+ 'distributors',
821
+ 'district',
822
+ 'districts',
823
+ 'dismal',
824
+ 'disaster',
825
+ 'disasters',
826
+ 'disastrous',
827
+ ];
8
828
  // Words that contain negative prefixes but should be treated as valid
9
- const EXCEPTION_WORDS = ['interaction', 'interactions'];
829
+ const EXCEPTION_WORDS = [
830
+ ...IN_EXCEPTIONS,
831
+ ...NO_EXCEPTIONS,
832
+ ...UN_EXCEPTIONS,
833
+ ...DIS_EXCEPTIONS,
834
+ ];
835
+ /**
836
+ * Splits a variable name into individual words based on naming conventions:
837
+ * - camelCase or PascalCase: split on uppercase letters
838
+ * - SCREAMING_SNAKE_CASE: split on underscores
839
+ * @param name Variable name to split
840
+ * @returns Array of individual words in lowercase
841
+ */
842
+ function splitNameIntoWords(name) {
843
+ // Handle SCREAMING_SNAKE_CASE
844
+ if (name.includes('_')) {
845
+ return name.toLowerCase().split('_').filter(Boolean);
846
+ }
847
+ // Handle camelCase and PascalCase
848
+ // Add space before uppercase letters and then split
849
+ const spacedName = name.replace(/([A-Z])/g, ' $1');
850
+ return spacedName.toLowerCase().trim().split(/\s+/).filter(Boolean);
851
+ }
10
852
  // Map of negative boolean terms to suggested positive alternatives
11
853
  const BOOLEAN_POSITIVE_ALTERNATIVES = {
12
- // Boolean prefixes
854
+ // Boolean prefixes - These will be removed from suggestions
13
855
  isNot: ['is'],
14
856
  isUn: ['is'],
15
857
  isDis: ['is'],
@@ -21,6 +863,28 @@ const BOOLEAN_POSITIVE_ALTERNATIVES = {
21
863
  shouldNot: ['should'],
22
864
  willNot: ['will'],
23
865
  doesNot: ['does'],
866
+ IsNot: ['Is'],
867
+ IsUn: ['Is'],
868
+ IsDis: ['Is'],
869
+ IsIn: ['Is'],
870
+ IsNon: ['Is'],
871
+ HasNo: ['Has'],
872
+ HasNot: ['Has'],
873
+ CanNot: ['Can'],
874
+ ShouldNot: ['Should'],
875
+ WillNot: ['Will'],
876
+ DoesNot: ['Does'],
877
+ IS_NOT: ['IS'],
878
+ IS_UN: ['IS'],
879
+ IS_DIS: ['IS'],
880
+ IS_IN: ['IS'],
881
+ IS_NON: ['IS'],
882
+ HAS_NO: ['HAS'],
883
+ HAS_NOT: ['HAS'],
884
+ CAN_NOT: ['CAN'],
885
+ SHOULD_NOT: ['SHOULD'],
886
+ WILL_NOT: ['WILL'],
887
+ DOES_NOT: ['DOES'],
24
888
  };
25
889
  exports.enforcePositiveNaming = (0, createRule_1.createRule)({
26
890
  name: 'enforce-positive-naming',
@@ -63,46 +927,98 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
63
927
  alternatives: BOOLEAN_POSITIVE_ALTERNATIVES[name],
64
928
  };
65
929
  }
930
+ // Split the name into words
931
+ const words = splitNameIntoWords(name);
932
+ // Check if this follows the pattern IS_NOT_SOMETHING or HAS_NO_SOMETHING
933
+ const secondWord = words[1].toLowerCase();
934
+ if (EXCEPTION_WORDS.some((exception) => secondWord === exception.toLowerCase())) {
935
+ return { isNegative: false, alternatives: [] };
936
+ }
937
+ const nameLowercase = name.toLowerCase();
66
938
  // Check for negative prefixes in boolean-like variables
67
- if (name.startsWith('is') ||
68
- name.startsWith('has') ||
69
- name.startsWith('can') ||
70
- name.startsWith('should') ||
71
- name.startsWith('will') ||
72
- name.startsWith('does')) {
73
- // Check if the name contains any exception words
74
- for (const exceptionWord of EXCEPTION_WORDS) {
75
- const lowerCaseName = name.toLowerCase();
76
- if (lowerCaseName.includes(exceptionWord.toLowerCase())) {
77
- return { isNegative: false, alternatives: [] };
78
- }
79
- }
939
+ if (nameLowercase.startsWith('is') ||
940
+ nameLowercase.startsWith('has') ||
941
+ nameLowercase.startsWith('can') ||
942
+ nameLowercase.startsWith('should') ||
943
+ nameLowercase.startsWith('will') ||
944
+ nameLowercase.startsWith('does')) {
945
+ // We already checked exception words above, so no need to check again
80
946
  for (const prefix of BOOLEAN_NEGATIVE_PREFIXES) {
947
+ const prefixCapitalized = prefix.charAt(0).toUpperCase() + prefix.slice(1);
948
+ const prefixUppercase = prefix.toUpperCase();
81
949
  // Check for patterns like isNot, hasNo, canNot, etc.
82
950
  const prefixPatterns = [
83
951
  {
84
- pattern: new RegExp(`^is${prefix}`, 'i'),
85
- key: `is${prefix.charAt(0).toUpperCase() + prefix.slice(1)}`,
952
+ pattern: new RegExp(`^is${prefixCapitalized}`, 'i'),
953
+ key: `is${prefixCapitalized}`,
954
+ },
955
+ {
956
+ pattern: new RegExp(`^has${prefixCapitalized}`, 'i'),
957
+ key: `has${prefixCapitalized}`,
958
+ },
959
+ {
960
+ pattern: new RegExp(`^can${prefixCapitalized}`, 'i'),
961
+ key: `can${prefixCapitalized}`,
962
+ },
963
+ {
964
+ pattern: new RegExp(`^should${prefixCapitalized}`, 'i'),
965
+ key: `should${prefixCapitalized}`,
966
+ },
967
+ {
968
+ pattern: new RegExp(`^will${prefixCapitalized}`, 'i'),
969
+ key: `will${prefixCapitalized}`,
970
+ },
971
+ {
972
+ pattern: new RegExp(`^does${prefixCapitalized}`, 'i'),
973
+ key: `does${prefixCapitalized}`,
974
+ },
975
+ {
976
+ pattern: new RegExp(`^Is${prefixCapitalized}`, 'i'),
977
+ key: `Is${prefixCapitalized}`,
978
+ },
979
+ {
980
+ pattern: new RegExp(`^Has${prefixCapitalized}`, 'i'),
981
+ key: `Has${prefixCapitalized}`,
982
+ },
983
+ {
984
+ pattern: new RegExp(`^Can${prefixCapitalized}`, 'i'),
985
+ key: `Can${prefixCapitalized}`,
986
+ },
987
+ {
988
+ pattern: new RegExp(`^Should${prefixCapitalized}`, 'i'),
989
+ key: `Should${prefixCapitalized}`,
990
+ },
991
+ {
992
+ pattern: new RegExp(`^Will${prefixCapitalized}`, 'i'),
993
+ key: `Will${prefixCapitalized}`,
994
+ },
995
+ {
996
+ pattern: new RegExp(`^Does${prefixCapitalized}`, 'i'),
997
+ key: `Does${prefixCapitalized}`,
998
+ },
999
+ {
1000
+ pattern: new RegExp(`^IS_${prefixUppercase}`, 'i'),
1001
+ key: `IS_${prefixUppercase}`,
86
1002
  },
87
1003
  {
88
- pattern: new RegExp(`^has${prefix}`, 'i'),
89
- key: `has${prefix.charAt(0).toUpperCase() + prefix.slice(1)}`,
1004
+ pattern: new RegExp(`^HAS_${prefixUppercase}`, 'i'),
1005
+ key: `HAS_${prefixUppercase}`,
90
1006
  },
91
1007
  {
92
- pattern: new RegExp(`^can${prefix}`, 'i'),
93
- key: `can${prefix.charAt(0).toUpperCase() + prefix.slice(1)}`,
1008
+ pattern: new RegExp(`^CAN_${prefixUppercase}`, 'i'),
1009
+ key: `CAN_${prefixUppercase}`,
94
1010
  },
95
1011
  {
96
- pattern: new RegExp(`^should${prefix}`, 'i'),
97
- key: `should${prefix.charAt(0).toUpperCase() + prefix.slice(1)}`,
1012
+ pattern: new RegExp(`^SHOULD_${prefixUppercase}`, 'i'),
1013
+ key: `SHOULD_${prefixUppercase}`,
98
1014
  },
99
1015
  {
100
- pattern: new RegExp(`^will${prefix}`, 'i'),
101
- key: `will${prefix.charAt(0).toUpperCase() + prefix.slice(1)}`,
1016
+ pattern: new RegExp(`^WILL_${prefixUppercase}`, 'i'),
1017
+ key: `WILL_${prefixUppercase}`,
102
1018
  },
103
1019
  {
104
- pattern: new RegExp(`^does${prefix}`, 'i'),
105
- key: `does${prefix.charAt(0).toUpperCase() + prefix.slice(1)}`,
1020
+ pattern: new RegExp(`^DOES_${prefixUppercase}`, 'i'),
1021
+ key: `DOES_${prefixUppercase}`,
106
1022
  },
107
1023
  ];
108
1024
  for (const { pattern, key } of prefixPatterns) {
@@ -37,7 +37,7 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
37
37
  meta: {
38
38
  type: 'suggestion',
39
39
  docs: {
40
- description: 'Warn when using CSS properties that trigger compositing layers, which can impact performance. Properties like transform, opacity, filter, and will-change create new GPU layers. While sometimes beneficial for animations, excessive layer creation can increase memory usage and hurt performance. Consider alternatives or explicitly document intentional layer promotion.',
40
+ description: 'Warn when using CSS properties that trigger compositing layers, which can impact performance. Properties like transform, opacity, filter, and will-change create new GPU layers. While sometimes beneficial for animations, excessive layer creation can increase memory usage and hurt performance. This rule checks both regular style objects and MUI sx props. Consider alternatives or explicitly document intentional layer promotion.',
41
41
  recommended: 'error',
42
42
  },
43
43
  schema: [],
@@ -79,7 +79,7 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
79
79
  // Check for JSX style attribute
80
80
  if (current.parent.type === utils_1.AST_NODE_TYPES.JSXAttribute &&
81
81
  current.parent.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier &&
82
- current.parent.name.name === 'style') {
82
+ (current.parent.name.name === 'style' || current.parent.name.name === 'sx')) {
83
83
  return true;
84
84
  }
85
85
  // Check for style-related variable names or properties
@@ -91,7 +91,7 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
91
91
  // Check for style-related object property assignments
92
92
  if (current.parent.type === utils_1.AST_NODE_TYPES.Property &&
93
93
  current.parent.key.type === utils_1.AST_NODE_TYPES.Identifier &&
94
- /style/i.test(current.parent.key.name)) {
94
+ (/style/i.test(current.parent.key.name) || current.parent.key.name === 'sx')) {
95
95
  return true;
96
96
  }
97
97
  // Skip if we're in a TypeScript type definition
@@ -142,9 +142,9 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
142
142
  return;
143
143
  checkNode(node);
144
144
  },
145
- // Handle JSX style attributes
145
+ // Handle JSX style and sx attributes
146
146
  JSXAttribute(node) {
147
- if (node.name.name !== 'style')
147
+ if (node.name.name !== 'style' && node.name.name !== 'sx')
148
148
  return;
149
149
  if (node.value?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer &&
150
150
  node.value.expression.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.11.1",
3
+ "version": "1.12.1",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",