@blumintinc/eslint-plugin-blumint 1.11.0 → 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
@@ -15,6 +15,7 @@ const enforce_mui_rounded_icons_1 = require("./rules/enforce-mui-rounded-icons")
15
15
  const enforce_react_type_naming_1 = require("./rules/enforce-react-type-naming");
16
16
  const export_if_in_doubt_1 = require("./rules/export-if-in-doubt");
17
17
  const extract_global_constants_1 = require("./rules/extract-global-constants");
18
+ const enforce_global_constants_1 = require("./rules/enforce-global-constants");
18
19
  const generic_starts_with_t_1 = require("./rules/generic-starts-with-t");
19
20
  const global_const_style_1 = __importDefault(require("./rules/global-const-style"));
20
21
  const no_async_array_filter_1 = require("./rules/no-async-array-filter");
@@ -105,7 +106,7 @@ const enforce_boolean_naming_prefixes_1 = require("./rules/enforce-boolean-namin
105
106
  module.exports = {
106
107
  meta: {
107
108
  name: '@blumintinc/eslint-plugin-blumint',
108
- version: '1.11.0',
109
+ version: '1.12.0',
109
110
  },
110
111
  parseOptions: {
111
112
  ecmaVersion: 2020,
@@ -130,6 +131,7 @@ module.exports = {
130
131
  '@blumintinc/blumint/enforce-react-type-naming': 'error',
131
132
  // '@blumintinc/blumint/export-if-in-doubt': 'warn',
132
133
  '@blumintinc/blumint/extract-global-constants': 'error',
134
+ '@blumintinc/blumint/enforce-global-constants': 'error',
133
135
  '@blumintinc/blumint/generic-starts-with-t': 'error',
134
136
  '@blumintinc/blumint/global-const-style': 'error',
135
137
  '@blumintinc/blumint/no-async-array-filter': 'error',
@@ -236,6 +238,7 @@ module.exports = {
236
238
  'enforce-mui-rounded-icons': enforce_mui_rounded_icons_1.enforceMuiRoundedIcons,
237
239
  'export-if-in-doubt': export_if_in_doubt_1.exportIfInDoubt,
238
240
  'extract-global-constants': extract_global_constants_1.extractGlobalConstants,
241
+ 'enforce-global-constants': enforce_global_constants_1.enforceGlobalConstants,
239
242
  'generic-starts-with-t': generic_starts_with_t_1.genericStartsWithT,
240
243
  'global-const-style': global_const_style_1.default,
241
244
  'no-async-array-filter': no_async_array_filter_1.noAsyncArrayFilter,
@@ -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,
@@ -0,0 +1 @@
1
+ export declare const enforceGlobalConstants: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"useGlobalConstant", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enforceGlobalConstants = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.enforceGlobalConstants = (0, createRule_1.createRule)({
7
+ name: 'enforce-global-constants',
8
+ meta: {
9
+ type: 'suggestion',
10
+ docs: {
11
+ description: 'Enforce using global static constants instead of useMemo with empty dependency arrays for object literals',
12
+ recommended: 'error',
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ useGlobalConstant: 'Use a global static constant instead of useMemo with an empty dependency array for object literals',
17
+ },
18
+ },
19
+ defaultOptions: [],
20
+ create(context) {
21
+ return {
22
+ CallExpression(node) {
23
+ // Check if it's a useMemo call
24
+ if (node.callee.type !== utils_1.AST_NODE_TYPES.Identifier ||
25
+ node.callee.name !== 'useMemo') {
26
+ return;
27
+ }
28
+ // Check if it has exactly two arguments
29
+ if (node.arguments.length !== 2) {
30
+ return;
31
+ }
32
+ // Check if the second argument is an empty array
33
+ const depsArray = node.arguments[1];
34
+ if (depsArray.type !== utils_1.AST_NODE_TYPES.ArrayExpression ||
35
+ depsArray.elements.length !== 0) {
36
+ return;
37
+ }
38
+ // Check if the first argument is an arrow function
39
+ const callback = node.arguments[0];
40
+ if (callback.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
41
+ return;
42
+ }
43
+ // Check if the arrow function body is a block statement with a return statement
44
+ // or a direct expression (implicit return)
45
+ let returnValue = null;
46
+ if (callback.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
47
+ // If it's a block, find the return statement
48
+ const returnStatement = callback.body.body.find((stmt) => stmt.type === utils_1.AST_NODE_TYPES.ReturnStatement);
49
+ if (!returnStatement || !returnStatement.argument) {
50
+ return;
51
+ }
52
+ returnValue = returnStatement.argument;
53
+ }
54
+ else {
55
+ // If it's an expression (implicit return)
56
+ returnValue = callback.body;
57
+ }
58
+ // Handle 'as const' type assertions
59
+ let actualReturnValue = returnValue;
60
+ if (returnValue.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
61
+ actualReturnValue = returnValue.expression;
62
+ }
63
+ // Check if the return value is an object literal or an array of object literals
64
+ if (actualReturnValue.type === utils_1.AST_NODE_TYPES.ObjectExpression ||
65
+ (actualReturnValue.type === utils_1.AST_NODE_TYPES.ArrayExpression &&
66
+ actualReturnValue.elements.some((element) => element !== null &&
67
+ element.type === utils_1.AST_NODE_TYPES.ObjectExpression))) {
68
+ context.report({
69
+ node,
70
+ messageId: 'useGlobalConstant',
71
+ });
72
+ }
73
+ },
74
+ };
75
+ },
76
+ });
77
+ //# sourceMappingURL=enforce-global-constants.js.map
@@ -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) {
@@ -31,7 +31,7 @@ const TYPE_MARKERS = [
31
31
  ...COMMON_TYPES,
32
32
  'Class',
33
33
  'Interface',
34
- 'Type',
34
+ //'Type', people like to use 'type' as a general purpose noun
35
35
  'Enum',
36
36
  ];
37
37
  // Allowed descriptive suffixes that should not be flagged as Hungarian notation
@@ -168,9 +168,21 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
168
168
  return true;
169
169
  }
170
170
  // Allow type assertions as arguments to constructors
171
- if (node.parent?.type === utils_1.AST_NODE_TYPES.NewExpression) {
171
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.NewExpression ||
172
+ (node.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
173
+ node.parent.parent?.type === utils_1.AST_NODE_TYPES.NewExpression)) {
172
174
  return true;
173
175
  }
176
+ // Allow type assertions as arguments to constructor calls
177
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
178
+ let current = node.parent;
179
+ while (current?.parent) {
180
+ if (current.parent.type === utils_1.AST_NODE_TYPES.NewExpression) {
181
+ return true;
182
+ }
183
+ current = current.parent;
184
+ }
185
+ }
174
186
  // Allow type assertions in JSX attributes/props or object properties
175
187
  if (isInsideJSXAttributeOrObjectProperty(node)) {
176
188
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",