@blumintinc/eslint-plugin-blumint 1.11.1 → 1.12.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/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.0',
110
110
  },
111
111
  parseOptions: {
112
112
  ecmaVersion: 2020,
@@ -349,6 +349,17 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
349
349
  });
350
350
  }
351
351
  }
352
+ /**
353
+ * Check if an identifier is imported from an external module
354
+ */
355
+ function isImportedIdentifier(name) {
356
+ const scope = context.getScope();
357
+ const variable = scope.variables.find((v) => v.name === name);
358
+ if (!variable)
359
+ return false;
360
+ // Check if it's an import binding
361
+ return variable.defs.some((def) => def.type === 'ImportBinding');
362
+ }
352
363
  /**
353
364
  * Check property definitions for boolean values
354
365
  */
@@ -370,16 +381,51 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
370
381
  utils_1.AST_NODE_TYPES.TSBooleanKeyword) {
371
382
  isBooleanProperty = true;
372
383
  }
384
+ // Skip checking if this property is part of an object literal passed to an external function
373
385
  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
- });
386
+ // Special cases for common Node.js API boolean properties
387
+ if ((propertyName === 'recursive' || propertyName === 'keepAlive') &&
388
+ node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
389
+ node.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
390
+ return; // Skip checking for these properties in object literals passed to functions
391
+ }
392
+ // Check if this property is in an object literal that's an argument to a function call
393
+ let isExternalApiCall = false;
394
+ // Navigate up to find if we're in an object expression that's an argument to a function call
395
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
396
+ node.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
397
+ const callExpression = node.parent.parent;
398
+ // Check if the function being called is an identifier (like mkdirSync, createServer, etc.)
399
+ if (callExpression.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
400
+ const calleeName = callExpression.callee.name;
401
+ if (isImportedIdentifier(calleeName)) {
402
+ isExternalApiCall = true;
403
+ }
404
+ }
405
+ // Also check for member expressions like fs.mkdirSync
406
+ if (callExpression.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
407
+ // For member expressions, check if the object is imported
408
+ const objectNode = callExpression.callee.object;
409
+ if (objectNode.type === utils_1.AST_NODE_TYPES.Identifier) {
410
+ const objectName = objectNode.name;
411
+ if (isImportedIdentifier(objectName)) {
412
+ isExternalApiCall = true;
413
+ }
414
+ }
415
+ }
416
+ }
417
+ // Only report if it's not an external API call
418
+ if (!isExternalApiCall) {
419
+ context.report({
420
+ node: node.key,
421
+ messageId: 'missingBooleanPrefix',
422
+ data: {
423
+ type: 'property',
424
+ name: propertyName,
425
+ prefixes: formatPrefixes(),
426
+ },
427
+ });
428
+ }
383
429
  }
384
430
  }
385
431
  /**
@@ -394,15 +440,35 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
394
440
  node.typeAnnotation.typeAnnotation.type ===
395
441
  utils_1.AST_NODE_TYPES.TSBooleanKeyword &&
396
442
  !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
- });
443
+ // Skip if this property is part of a parameter's type annotation
444
+ // Check if this property signature is inside a parameter's type annotation
445
+ let isInParameterType = false;
446
+ let parent = node.parent;
447
+ while (parent) {
448
+ if (parent.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
449
+ const grandParent = parent.parent;
450
+ if (grandParent?.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation &&
451
+ grandParent.parent?.type === utils_1.AST_NODE_TYPES.Identifier &&
452
+ grandParent.parent.parent?.type ===
453
+ utils_1.AST_NODE_TYPES.FunctionDeclaration) {
454
+ isInParameterType = true;
455
+ break;
456
+ }
457
+ }
458
+ parent = parent.parent;
459
+ }
460
+ // Only report if not in a parameter type annotation
461
+ if (!isInParameterType) {
462
+ context.report({
463
+ node: node.key,
464
+ messageId: 'missingBooleanPrefix',
465
+ data: {
466
+ type: 'property',
467
+ name: propertyName,
468
+ prefixes: formatPrefixes(),
469
+ },
470
+ });
471
+ }
406
472
  }
407
473
  }
408
474
  /**
@@ -427,6 +493,31 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
427
493
  },
428
494
  });
429
495
  }
496
+ // Check if the parameter has an object type with boolean properties
497
+ if (node.typeAnnotation?.typeAnnotation &&
498
+ node.typeAnnotation.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
499
+ const typeLiteral = node.typeAnnotation.typeAnnotation;
500
+ // Check each member of the type literal
501
+ for (const member of typeLiteral.members) {
502
+ if (member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
503
+ member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
504
+ member.typeAnnotation?.typeAnnotation.type ===
505
+ utils_1.AST_NODE_TYPES.TSBooleanKeyword) {
506
+ const propertyName = member.key.name;
507
+ if (!hasApprovedPrefix(propertyName)) {
508
+ context.report({
509
+ node: member.key,
510
+ messageId: 'missingBooleanPrefix',
511
+ data: {
512
+ type: 'property',
513
+ name: propertyName,
514
+ prefixes: formatPrefixes(),
515
+ },
516
+ });
517
+ }
518
+ }
519
+ }
520
+ }
430
521
  }
431
522
  return {
432
523
  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) {
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.0",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",