@cookshack/eslint-config 2.0.5 → 3.0.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/dist/index.cjs CHANGED
@@ -4,19 +4,18 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var globals = require('globals');
6
6
 
7
- exports.rules = void 0; exports.languageOptions = void 0; exports.plugins = void 0;
8
-
9
- let printBuffer;
7
+ let varIds, nextVarId, printBuffer;
10
8
 
11
9
  printBuffer = [];
10
+ varIds = new Map();
11
+ nextVarId = 0;
12
12
 
13
13
  function print
14
14
  (...args) {
15
- //console.log(args.join(' '))
16
15
  printBuffer.push(args.join(' '));
17
16
  }
18
17
 
19
- function trace
18
+ function trace$1
20
19
  (...args) {
21
20
  }
22
21
 
@@ -36,7 +35,7 @@ function getNarrowestScope
36
35
 
37
36
  common = null;
38
37
  for (let ref of variable.references) {
39
- if (variable.defs.some(def => def.name === ref.identifier))
38
+ if (variable.defs.some(def => def.name == ref.identifier))
40
39
  continue
41
40
  if (ref.from)
42
41
  if (common)
@@ -106,15 +105,15 @@ function getConditionalContext
106
105
  prevNode = ref.identifier;
107
106
  node = ref.identifier.parent;
108
107
  while (node) {
109
- if (node === scopeBlock)
108
+ if (node == scopeBlock)
110
109
  break
111
- if (node.type === 'IfStatement')
112
- if (prevNode === node.test || nodeContains(node.test, prevNode))
110
+ if (node.type == 'IfStatement')
111
+ if (prevNode == node.test || nodeContains(node.test, prevNode))
113
112
  prevNode = node;
114
113
  else
115
114
  return 'B'
116
115
  else if ([ 'WhileStatement', 'DoWhileStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement', 'SwitchStatement' ].includes(node.type))
117
- if (prevNode === node.test || nodeContains(node.test, prevNode))
116
+ if (prevNode == node.test || nodeContains(node.test, prevNode))
118
117
  prevNode = node;
119
118
  else
120
119
  return 'B'
@@ -126,9 +125,9 @@ function getConditionalContext
126
125
  }
127
126
 
128
127
  function nodeContains(node, target) {
129
- if (node === target)
128
+ if (node == target)
130
129
  return true
131
- if (node && typeof node === 'object')
130
+ if (node && typeof node == 'object')
132
131
  for (let key in node)
133
132
  if (nodeHas(node[key], target))
134
133
  return true
@@ -136,7 +135,7 @@ function nodeContains(node, target) {
136
135
  }
137
136
 
138
137
  function nodeHas(value, target) {
139
- if (value === target)
138
+ if (value == target)
140
139
  return true
141
140
  if (Array.isArray(value))
142
141
  return value.some(v => nodeContains(v, target))
@@ -150,15 +149,15 @@ function isConditionalRef
150
149
  node = ref.identifier.parent;
151
150
 
152
151
  while (node) {
153
- if (node === narrowestScope.block)
152
+ if (node == narrowestScope.block)
154
153
  break
155
- if (node.type === 'BlockStatement') {
154
+ if (node.type == 'BlockStatement') {
156
155
  let parent;
157
156
 
158
157
  parent = node.parent;
159
- if (parent?.type === 'IfStatement' && (parent.consequent === node || parent.alternate === node))
158
+ if (parent?.type == 'IfStatement' && (parent.consequent == node || parent.alternate == node))
160
159
  return true
161
- if ([ 'WhileStatement', 'DoWhileStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement' ].includes(parent?.type) && parent.body === node)
160
+ if ([ 'WhileStatement', 'DoWhileStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement' ].includes(parent?.type) && parent.body == node)
162
161
  return true
163
162
  }
164
163
  node = node.parent;
@@ -190,14 +189,11 @@ function mayBeReadBeforeAnyWrite
190
189
  let item;
191
190
 
192
191
  if (isReadRef(ref))
193
- // a possible read
194
192
  return 1
195
193
 
196
194
  item = ref.cookshackNarrowestScopeItem;
197
195
  if (item.ctx == 'B' || isConditionalRef(ref, narrowestScope))
198
- // a conditional write
199
196
  continue
200
- // A guaranteed write before any possible read.
201
197
  return 0
202
198
  }
203
199
  }
@@ -212,7 +208,22 @@ function scopeStart(scope) {
212
208
  return scope.block.range[0]
213
209
  }
214
210
 
215
- function buildScopeTree(scope, prefix, scopeToNode) {
211
+ function ensureInVarIds
212
+ (variable) {
213
+ if (varIds.has(variable))
214
+ return
215
+ varIds.set(variable, nextVarId++);
216
+ }
217
+
218
+ function isCompoundAssignmentOp
219
+ (op) {
220
+ if (op == '=')
221
+ return 0
222
+ return 1
223
+ }
224
+
225
+ function buildScopeTree
226
+ (scope, prefix, scopeToNode, astToTree) {
216
227
  let node, siblingNum;
217
228
 
218
229
  node = {
@@ -222,40 +233,49 @@ function buildScopeTree(scope, prefix, scopeToNode) {
222
233
  children: []
223
234
  };
224
235
  scopeToNode.set(scope, node);
236
+ if (scope.block && astToTree)
237
+ astToTree.set(scope.block, node);
225
238
 
226
239
  siblingNum = 0;
227
240
  for (let child of scope.childScopes) {
228
241
  siblingNum++;
229
- node.children.push(buildScopeTree(child, prefix + '.' + siblingNum, scopeToNode));
242
+ node.children.push(buildScopeTree(child, prefix + '.' + siblingNum, scopeToNode, astToTree));
230
243
  }
231
244
 
232
245
  for (let variable of scope.variables) {
233
- if (variable.defs.length > 0)
234
- node.items.push({ type: 'LET', name: variable.name, pos: variable.defs[0].name.range[0] });
246
+ if (variable.defs.length > 0) {
247
+ ensureInVarIds(variable);
248
+ node.items.push({ type: 'LET', name: variable.name, pos: variable.defs[0].name.range[0], defNode: variable.defs[0].node, defType: variable.defs[0].type, identifier: variable.defs[0].name, variable, varId: varIds.get(variable) });
249
+ }
235
250
 
236
251
  for (let ref of variable.references) {
237
252
  let targetNode;
238
253
 
239
254
  targetNode = scopeToNode.get(ref.from);
240
255
  if (targetNode) {
241
- let parent, sortPos, ctx, item;
256
+ let parent, sortPos, ctx, item1, item2;
242
257
 
243
258
  ctx = getConditionalContext(ref);
244
259
  parent = ref.identifier.parent;
245
260
 
246
261
  if (isWriteRef(ref))
247
262
  if (ref.identifier.parent?.type == 'UpdateExpression') {
248
- targetNode.items.push({ type: 'READ', name: ref.identifier.name, ctx, pos: ref.identifier.range[0] });
249
- item = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] };
263
+ item1 = { ref, type: 'READ', name: ref.identifier.name, ctx, pos: ref.identifier.range[0] };
264
+ item2 = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] };
250
265
  }
251
266
  else if (ref.identifier.parent?.type == 'AssignmentExpression') {
252
267
  sortPos = parent.right.range[1] + 0.4;
253
- item = { ref, type: 'WRITE', name: ref.identifier.name, ctx, pos: sortPos };
268
+ if (ref.identifier.parent.left == ref.identifier && isCompoundAssignmentOp(ref.identifier.parent.operator)) {
269
+ item1 = { ref, type: 'READ', name: ref.identifier.name, ctx, pos: ref.identifier.range[0] };
270
+ item2 = { ref, type: 'WRITE', name: ref.identifier.name, pos: sortPos };
271
+ }
272
+ else
273
+ item1 = { ref, type: 'WRITE', name: ref.identifier.name, ctx, pos: sortPos };
254
274
  }
255
275
  else if (ref.identifier.parent?.type == 'VariableDeclarator')
256
- item = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] + 0.4 };
276
+ item1 = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] + 0.4 };
257
277
  else
258
- item = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] };
278
+ item1 = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] };
259
279
  else {
260
280
  let declarator;
261
281
 
@@ -269,10 +289,16 @@ function buildScopeTree(scope, prefix, scopeToNode) {
269
289
  sortPos = declarator.id ? declarator.id.range[0] - 0.4 : ref.identifier.range[0];
270
290
  else
271
291
  sortPos = ref.identifier.range[0];
272
- item = { ref, type: 'READ', name: ref.identifier.name, ctx, pos: sortPos };
292
+ item1 = { ref, type: 'READ', name: ref.identifier.name, ctx, pos: sortPos };
293
+ }
294
+ ensureInVarIds(variable);
295
+ item1.varId = varIds.get(variable);
296
+ targetNode.items.push(item1);
297
+ if (item2) {
298
+ item2.varId = varIds.get(variable);
299
+ targetNode.items.push(item2);
273
300
  }
274
- targetNode.items.push(item);
275
- ref.cookshackNarrowestScopeItem = item;
301
+ ref.cookshackNarrowestScopeItem = item2 || item1;
276
302
  }
277
303
  }
278
304
  }
@@ -282,7 +308,8 @@ function buildScopeTree(scope, prefix, scopeToNode) {
282
308
  return node
283
309
  }
284
310
 
285
- function checkScopeNode(context, treeNode, reported, scopeToNode) {
311
+ function checkScopeNode
312
+ (context, treeNode, reported, scopeToNode) {
286
313
  let indent;
287
314
 
288
315
  reported = reported || new Set;
@@ -293,11 +320,11 @@ function checkScopeNode(context, treeNode, reported, scopeToNode) {
293
320
 
294
321
  if (reported.has(variable))
295
322
  continue
296
- if (variable.defs.length === 0)
323
+ if (variable.defs.length == 0)
297
324
  continue
298
325
  if ([ 'Parameter', 'FunctionName', 'ImportBinding', 'CatchClause', 'ClassName' ].includes(variable.defs[0].type))
299
326
  continue
300
- if (variable.defs[0].node.parent?.parent?.type === 'ExportNamedDeclaration')
327
+ if (variable.defs[0].node.parent?.parent?.type == 'ExportNamedDeclaration')
301
328
  continue
302
329
 
303
330
  defNode = variable.defs[0]?.name;
@@ -306,31 +333,31 @@ function checkScopeNode(context, treeNode, reported, scopeToNode) {
306
333
 
307
334
  defScope = getDefinitionScope(variable);
308
335
  defNodePrefix = scopeToNode.get(defScope)?.prefix ?? '?';
309
- trace(indent, '1 found decl scope of', variable.name + ':', defNodePrefix + ' ' + defScope.type.toUpperCase());
336
+ trace$1(indent, '1 found decl scope of', variable.name + ':', defNodePrefix + ' ' + defScope.type.toUpperCase());
310
337
 
311
338
  narrowestScope = getNarrowestScope(variable);
312
339
  if (narrowestScope) {
313
340
  let narrowestPrefix;
314
341
 
315
342
  narrowestPrefix = scopeToNode.get(narrowestScope)?.prefix ?? '?';
316
- trace(indent, '2 found narrowest scope of', variable.name + ':', narrowestPrefix + ' ' + narrowestScope?.type.toUpperCase());
343
+ trace$1(indent, '2 found narrowest scope of', variable.name + ':', narrowestPrefix + ' ' + narrowestScope?.type.toUpperCase());
317
344
 
318
345
  markConditionalRefs(variable, scopeToNode, narrowestScope);
319
346
 
320
347
  if (defScope == narrowestScope)
321
348
  continue
322
- trace(indent, '3', variable.name, 'could be moved to a narrower scope');
349
+ trace$1(indent, '3', variable.name, 'could be moved to a narrower scope');
323
350
 
324
351
  if (defScope.type == 'for') {
325
- trace(indent, '4 exception:', variable.name, 'is in a for loop header');
352
+ trace$1(indent, '4 exception:', variable.name, 'is in a for loop header');
326
353
  continue
327
354
  }
328
355
  if (mayBeReadBeforeAnyWrite(variable, scopeToNode, narrowestScope)) {
329
- trace(indent, '4 exception:', variable.name, 'mayBeReadBeforeAnyWrite');
356
+ trace$1(indent, '4 exception:', variable.name, 'mayBeReadBeforeAnyWrite');
330
357
  continue
331
358
  }
332
359
 
333
- trace(indent, '5', variable.name, 'is too broad');
360
+ trace$1(indent, '5', variable.name, 'is too broad');
334
361
 
335
362
  reported.add(variable);
336
363
  context.report({
@@ -346,10 +373,11 @@ function checkScopeNode(context, treeNode, reported, scopeToNode) {
346
373
  checkScopeNode(context, child, reported, scopeToNode);
347
374
  }
348
375
 
349
- function printTree(node, siblingNum) {
376
+ function printTree
377
+ (node, siblingNum) {
350
378
  let prefix, all, indent;
351
379
 
352
- prefix = siblingNum === 0 ? node.prefix : node.prefix.split('.').slice(0, -1).join('.') + '.' + siblingNum;
380
+ prefix = siblingNum == 0 ? node.prefix : node.prefix.split('.').slice(0, -1).join('.') + '.' + siblingNum;
353
381
  indent = ' '.repeat(prefix.split('.').length - 1);
354
382
  {
355
383
  let name;
@@ -363,13 +391,11 @@ function printTree(node, siblingNum) {
363
391
  all.sort((a, b) => a.pos - b.pos);
364
392
 
365
393
  for (let entry of all)
366
- if (entry.type === 'item')
394
+ if (entry.type == 'item')
367
395
  print(indent
368
396
  + ' ' + entry.data.type.padEnd(5)
369
397
  + ' ' + entry.data.name
370
- // B: is the ref conditional within the scope that "owns" the ref (for single statement `if`)
371
398
  + (entry.data.ctx ? ' ' + entry.data.ctx : '').padEnd(3)
372
- // C: is the ref conditional within the variable's narrowestScope?
373
399
  + (entry.data.isConditional ? 'C' : ' ').padEnd(2)
374
400
  + 'pos ' + entry.data.pos);
375
401
  else
@@ -388,6 +414,7 @@ function createNarrowestScope
388
414
  let tree, scopeToNode;
389
415
 
390
416
  scopeToNode = new Map;
417
+ nextVarId = 0;
391
418
  tree = buildScopeTree(scopeManager.scopes[0], '1', scopeToNode);
392
419
  checkScopeNode(context, tree, null, scopeToNode);
393
420
  printTree(tree, 0);
@@ -395,6 +422,12 @@ function createNarrowestScope
395
422
  }
396
423
  }
397
424
 
425
+ var narrowestScopePlugin = { meta: { type: 'suggestion',
426
+ docs: { description: 'Enforce variables are declared in their narrowest possible scope.' },
427
+ messages: { tooBroad: 'Variable "{{ name }}" is declared in a broader scope than necessary.' },
428
+ schema: [] },
429
+ create: createNarrowestScope };
430
+
398
431
  function createPositiveVibes
399
432
  (context) {
400
433
  return {
@@ -414,18 +447,463 @@ function createPositiveVibes
414
447
  }
415
448
  }
416
449
 
417
- exports.plugins = { 'cookshack': { rules: { 'positive-vibes': { meta: { type: 'problem',
418
- docs: { description: 'Prefer positive expressions.' },
419
- messages: { positiveVibes: 'Be positive!',
420
- equality: 'Use ==.',
421
- strictEquality: 'Use ===.' },
422
- schema: [] },
423
- create: createPositiveVibes },
424
- 'narrowest-scope': { meta: { type: 'suggestion',
425
- docs: { description: 'Enforce variables are declared in their narrowest possible scope.' },
426
- messages: { tooBroad: 'Variable "{{ name }}" is declared in a broader scope than necessary.' },
427
- schema: [] },
428
- create: createNarrowestScope } } } };
450
+ var positiveVibesPlugin = { meta: { type: 'problem',
451
+ docs: { description: 'Prefer positive expressions.' },
452
+ messages: { positiveVibes: 'Be positive!',
453
+ equality: 'Use ==.',
454
+ strictEquality: 'Use ===.' },
455
+ schema: [] },
456
+ create: createPositiveVibes };
457
+
458
+ var useRiskyEqualPlugin = { meta: { type: 'problem',
459
+ docs: { description: 'Enforce use of == instead of ===.' },
460
+ messages: { risky: 'Use ==.' },
461
+ schema: [] },
462
+ create(context) {
463
+ return { BinaryExpression(node) {
464
+ if (node.operator == '===')
465
+ context.report({ node, messageId: 'risky' });
466
+ } }
467
+ } };
468
+
469
+ function create$2
470
+ (context) {
471
+ return { VariableDeclaration(node) {
472
+ if (node.kind == 'const' || node.kind == 'var')
473
+ context.report({ node, messageId: 'useLet' });
474
+ } }
475
+ }
476
+
477
+ var alwaysLetPlugin = { meta: { type: 'problem',
478
+ docs: { description: 'Enforce use of let instead of const or var.' },
479
+ messages: { useLet: 'Use let.' },
480
+ schema: [] },
481
+ create: create$2 };
482
+
483
+ let ostIdCounter, ost;
484
+
485
+ ostIdCounter = 0;
486
+ ost = 0;
487
+
488
+ function trace
489
+ (...args) {
490
+ }
491
+
492
+ function createInitBeforeUse(context) {
493
+ let scopeManager;
494
+
495
+ scopeManager = context.sourceCode.scopeManager;
496
+ if (scopeManager)
497
+ return {
498
+ 'Program:exit'() {
499
+ let scopeToNode, astToTree, astToOst;
500
+ scopeToNode = new Map;
501
+ astToTree = new Map;
502
+ astToOst = new Map;
503
+ buildScopeTree(scopeManager.scopes[0], '1', scopeToNode, astToTree);
504
+
505
+ ostIdCounter = 0;
506
+ ost = processAst(context.sourceCode.ast, null, astToTree, astToOst, '', new Set());
507
+
508
+ ostAnnotate(ost, astToOst, context);
509
+
510
+ ostCheck(ost, context);
511
+ }
512
+ }
513
+ }
514
+
515
+ function isRegularDeclaration
516
+ (item) {
517
+ if (item.type == 'LET') {
518
+ if (item.defType == 'FunctionName' || item.defType == 'Parameter')
519
+ return 0
520
+ return 1
521
+ }
522
+ return 0
523
+ }
524
+
525
+ function processAst(astNode, parentOst, astToTree, astToOst, indent, visited) {
526
+ if (astNode) {
527
+ let treeNode, scopeName, lets, reads, writes, ost, children;
528
+
529
+ if (visited.has(astNode))
530
+ return
531
+ visited.add(astNode);
532
+
533
+ treeNode = astToTree.get(astNode) ?? parentOst?.treeNode;
534
+
535
+ scopeName = treeNode?.scope ? `${treeNode.scope.type}` : 'no-scope';
536
+ if (treeNode?.scope?.block?.id?.name)
537
+ scopeName += `(${treeNode.scope.block.id.name})`;
538
+ trace(`${indent}${astNode.type}`);
539
+
540
+ lets = [];
541
+ reads = [];
542
+ writes = [];
543
+
544
+ for (let item of treeNode?.items ?? [])
545
+ if (isRegularDeclaration(item)) {
546
+ let scopeCreator;
547
+
548
+ scopeCreator = treeNode?.scope?.block;
549
+ if (scopeCreator && astNode == scopeCreator) {
550
+ lets.push({ item });
551
+ trace(`${indent} | LET ${item.name}:${item.varId}`);
552
+ }
553
+ }
554
+ else if (item.ref)
555
+ if (astNode == item.ref.identifier)
556
+ if (item.type == 'READ') {
557
+ reads.push({ item });
558
+ trace(`${indent} | READ ${item.name}:${item.varId}`);
559
+ }
560
+ else if (item.type == 'WRITE') {
561
+ writes.push({ item });
562
+ trace(`${indent} | WRITE ${item.name}:${item.varId}`);
563
+ }
564
+
565
+ ost = {
566
+ id: ostIdCounter++,
567
+ astNode,
568
+ treeNode,
569
+ scopeItems: treeNode?.items ?? [],
570
+ lets,
571
+ reads,
572
+ writes,
573
+ children: [],
574
+ fnDefOst: null
575
+ };
576
+
577
+ astToOst.set(astNode, ost);
578
+
579
+ children = [];
580
+
581
+ if (astNode.type == 'ForOfStatement' || astNode.type == 'ForInStatement') {
582
+ if (astNode.right)
583
+ children.push(astNode.right);
584
+ if (astNode.left)
585
+ children.push(astNode.left);
586
+ if (astNode.body)
587
+ children.push(astNode.body);
588
+ }
589
+ else if (astNode.type == 'ForStatement') {
590
+ if (astNode.init)
591
+ children.push(astNode.init);
592
+ if (astNode.test)
593
+ children.push(astNode.test);
594
+ if (astNode.update)
595
+ children.push(astNode.update);
596
+ if (astNode.body)
597
+ children.push(astNode.body);
598
+ }
599
+ else if (astNode.type == 'AssignmentExpression') {
600
+ if (astNode.right)
601
+ children.push(astNode.right);
602
+ if (astNode.left)
603
+ children.push(astNode.left);
604
+ }
605
+ else {
606
+ if (astNode.body)
607
+ if (Array.isArray(astNode.body))
608
+ children.push(...astNode.body);
609
+ else
610
+ children.push(astNode.body);
611
+ if (astNode.consequent)
612
+ children.push(astNode.consequent);
613
+ if (astNode.alternate)
614
+ children.push(astNode.alternate);
615
+ if (astNode.block)
616
+ children.push(astNode.block);
617
+ if (astNode.expression)
618
+ children.push(astNode.expression);
619
+ if (astNode.callee)
620
+ children.push(astNode.callee);
621
+ if (astNode.object)
622
+ children.push(astNode.object);
623
+ if (astNode.property)
624
+ children.push(astNode.property);
625
+ if (astNode.init)
626
+ children.push(astNode.init);
627
+ if (astNode.id)
628
+ children.push(astNode.id);
629
+ if (astNode.declarations)
630
+ children.push(...astNode.declarations);
631
+ if (astNode.test)
632
+ children.push(astNode.test);
633
+ if (astNode.update)
634
+ children.push(astNode.update);
635
+ if (astNode.left)
636
+ children.push(astNode.left);
637
+ if (astNode.right)
638
+ children.push(astNode.right);
639
+ if (astNode.argument)
640
+ children.push(astNode.argument);
641
+ if (astNode.arguments)
642
+ children.push(...astNode.arguments);
643
+ if (astNode.elements)
644
+ children.push(...astNode.elements);
645
+ if (astNode.properties)
646
+ children.push(...astNode.properties);
647
+ }
648
+
649
+ for (let child of children) {
650
+ let childOst;
651
+
652
+ childOst = processAst(child, ost, astToTree, astToOst, indent + ' ', visited);
653
+ if (childOst)
654
+ ost.children.push(childOst);
655
+ }
656
+
657
+ return ost
658
+ }
659
+ }
660
+
661
+ function ostAnnotate(ost, astToOst, context) {
662
+ if (ost) {
663
+ for (let letInfo of ost.lets) {
664
+ let writeOst;
665
+
666
+ writeOst = findFirstWrite(ost, letInfo);
667
+ letInfo.firstWrite = writeOst;
668
+ if (writeOst)
669
+ continue
670
+ if (letInfo.item.defType == 'ImportBinding')
671
+ continue
672
+ context.report({
673
+ node: letInfo.item.identifier,
674
+ messageId: 'mustInit',
675
+ data: { name: letInfo.item.name }
676
+ });
677
+ }
678
+
679
+ if (ost.astNode.type == 'CallExpression' && ost.astNode.callee?.type == 'Identifier')
680
+ for (let child of ost.children)
681
+ if (child.astNode == ost.astNode.callee && child.reads.length > 0) {
682
+ let readRef;
683
+
684
+ readRef = child.reads[0].item.ref;
685
+ if (readRef?.resolved) {
686
+ let variable;
687
+
688
+ variable = readRef.resolved;
689
+ if (variable.defs.length > 0) {
690
+ let fnDefAst;
691
+
692
+ fnDefAst = variable.defs[0].node;
693
+ if (fnDefAst) {
694
+ if (fnDefAst.init?.type == 'ArrowFunctionExpression' || fnDefAst.init?.type == 'FunctionExpression')
695
+ fnDefAst = fnDefAst.init;
696
+ ost.fnDefOst = astToOst.get(fnDefAst);
697
+ }
698
+ }
699
+ }
700
+ }
701
+
702
+ for (let child of ost.children)
703
+ ostAnnotate(child, astToOst, context);
704
+ }
705
+ }
706
+
707
+ function findFirstWrite(ost, letInfo) {
708
+ if (ost.astNode.type == 'FunctionDeclaration' || ost.astNode.type == 'ArrowFunctionExpression' || ost.astNode.type == 'FunctionExpression')
709
+ for (let child of ost.children)
710
+ if (child.astNode.type == 'BlockStatement')
711
+ return findFirstWriteInSubtree(child, letInfo)
712
+ return findFirstWriteInSubtree(ost, letInfo)
713
+ }
714
+
715
+ function findFirstWriteInSubtree(ost, letInfo) {
716
+ if (ost) {
717
+ if (ost.astNode.type == 'FunctionDeclaration' || ost.astNode.type == 'ArrowFunctionExpression' || ost.astNode.type == 'FunctionExpression')
718
+ return null
719
+
720
+ for (let writeInfo of ost.writes) {
721
+ let writeVar;
722
+
723
+ writeVar = writeInfo.item.ref.resolved;
724
+ if (writeVar == letInfo.item.variable)
725
+ return ost
726
+ }
727
+
728
+ for (let child of ost.children) {
729
+ let result;
730
+
731
+ result = findFirstWriteInSubtree(child, letInfo);
732
+ if (result)
733
+ return result
734
+ }
735
+ }
736
+
737
+ return null
738
+ }
739
+
740
+ function ostCheck(ost, context) {
741
+ if (ost) {
742
+ for (let letInfo of ost.lets)
743
+ if (letInfo.firstWrite)
744
+ walk2Start(ost, letInfo, context);
745
+
746
+ for (let child of ost.children)
747
+ ostCheck(child, context);
748
+ }
749
+ }
750
+
751
+ function walk2Start(node, letInfo, context) {
752
+ if (node.astNode.type == 'FunctionDeclaration')
753
+ for (let child of node.children)
754
+ if (child.astNode.type == 'BlockStatement')
755
+ return walk2(child, letInfo, context, new Set())
756
+ return walk2(node, letInfo, context, new Set())
757
+ }
758
+
759
+ function walk2(node, letInfo, context, visited) {
760
+ if (node) {
761
+ if (node.astNode.type == 'FunctionDeclaration' || node.astNode.type == 'ArrowFunctionExpression' || node.astNode.type == 'FunctionExpression')
762
+ return false
763
+
764
+ if (node == letInfo.firstWrite) {
765
+ for (let readInfo of node.reads)
766
+ if (readInfo.item.ref.resolved == letInfo.item.variable) {
767
+ context.report({
768
+ node: readInfo.item.ref.identifier,
769
+ messageId: 'initBeforeUse',
770
+ data: { name: letInfo.item.name }
771
+ });
772
+ }
773
+ return true
774
+ }
775
+
776
+ if (node.astNode.type == 'CallExpression' && node.fnDefOst) {
777
+ let fnType;
778
+
779
+ fnType = node.fnDefOst.astNode.type;
780
+
781
+ if (fnType == 'FunctionDeclaration' || fnType == 'ArrowFunctionExpression' || fnType == 'FunctionExpression') {
782
+ let key;
783
+
784
+ key = `${letInfo.item.name}:${node.fnDefOst.id}`;
785
+ if (visited.has(key)) ;
786
+ else {
787
+ visited.add(key);
788
+ for (let child of node.fnDefOst.children)
789
+ if (child.astNode.type == 'BlockStatement' && walk2(child, letInfo, context, visited))
790
+ return true
791
+ }
792
+ }
793
+ }
794
+
795
+ for (let readInfo of node.reads)
796
+ if (readInfo.item.ref.resolved == letInfo.item.variable) {
797
+ context.report({
798
+ node: readInfo.item.ref.identifier,
799
+ messageId: 'initBeforeUse',
800
+ data: { name: letInfo.item.name }
801
+ });
802
+ }
803
+
804
+ for (let child of node.children)
805
+ if (walk2(child, letInfo, context, visited))
806
+ return true
807
+ }
808
+
809
+ return false
810
+ }
811
+
812
+ var initBeforeUsePlugin = {
813
+ meta: {
814
+ type: 'problem',
815
+ docs: { description: 'Warn when a variable is used before being explicitly initialized.' },
816
+ messages: { initBeforeUse: "'{{name}}' used before initialization.",
817
+ mustInit: "'{{name}}' must be initialized." },
818
+ schema: []
819
+ },
820
+ create: createInitBeforeUse
821
+ };
822
+
823
+ function
824
+ VariableDeclaration
825
+ (context, node) {
826
+ let parent;
827
+
828
+ parent = node.parent;
829
+
830
+ for (parent = node.parent; parent; parent = parent.parent)
831
+ if (parent.type == 'BlockStatement')
832
+ break
833
+
834
+ if (parent) {
835
+ let idx;
836
+
837
+ if (parent.parent?.type == 'CatchClause')
838
+ return
839
+
840
+ idx = parent.body.indexOf(node);
841
+ for (let i = 0; i < idx; i++) {
842
+ if (parent.body[i].type == 'VariableDeclaration')
843
+ continue
844
+ context.report({ node, messageId: 'varDeclBlockStart' });
845
+ return
846
+ }
847
+ }
848
+ }
849
+ function create$1
850
+ (context) {
851
+ return { VariableDeclaration: node => VariableDeclaration(context, node) }
852
+ }
853
+
854
+ var varDeclBlockStartPlugin = { meta: { type: 'suggestion',
855
+ docs: { description: 'Require variable declarations to be at the start of the block.' },
856
+ messages: { varDeclBlockStart: 'VarDecl must be at start of block.' },
857
+ schema: [] },
858
+ create: create$1 };
859
+
860
+ function
861
+ FunctionDeclaration
862
+ (context, node) {
863
+ let parent;
864
+
865
+ for (parent = node.parent; parent; parent = parent.parent)
866
+ if (parent.type == 'BlockStatement')
867
+ break
868
+
869
+ if (parent) {
870
+ let idx;
871
+
872
+ if (parent.parent?.type == 'CatchClause')
873
+ return
874
+
875
+ idx = parent.body.indexOf(node);
876
+ for (let i = 0; i < idx; i++) {
877
+ if (parent.body[i].type == 'VariableDeclaration'
878
+ || parent.body[i].type == 'FunctionDeclaration'
879
+ || parent.body[i].type == 'EmptyStatement')
880
+ continue
881
+ context.report({ node, messageId: 'fnDeclBlockStart' });
882
+ return
883
+ }
884
+ }
885
+ }
886
+
887
+ function create
888
+ (context) {
889
+ return { FunctionDeclaration: node => FunctionDeclaration(context, node) }
890
+ }
891
+
892
+ var fnDeclBlockStartPlugin = { meta: { type: 'suggestion',
893
+ docs: { description: 'Require function declarations to be at the start of the block.' },
894
+ messages: { fnDeclBlockStart: 'FnDecl must be the start the block (after VarDecls).' },
895
+ schema: [] },
896
+ create };
897
+
898
+ exports.rules = void 0; exports.languageOptions = void 0; exports.plugins = void 0;
899
+
900
+ exports.plugins = { 'cookshack': { rules: { 'positive-vibes': positiveVibesPlugin,
901
+ 'narrowest-scope': narrowestScopePlugin,
902
+ 'use-risky-equal': useRiskyEqualPlugin,
903
+ 'always-let': alwaysLetPlugin,
904
+ 'init-before-use': initBeforeUsePlugin,
905
+ 'var-decl-block-start': varDeclBlockStartPlugin,
906
+ 'fn-decl-block-start': fnDeclBlockStartPlugin } } };
429
907
 
430
908
  exports.rules = {
431
909
  'array-bracket-newline': [ 'error', 'never' ],
@@ -456,8 +934,14 @@ exports.rules = {
456
934
  { blankLine: 'never', prev: 'let', next: 'let' } ],
457
935
  'no-case-declarations': 'error',
458
936
  'no-global-assign': 'error',
459
- 'cookshack/narrowest-scope': 'error',
460
937
  'cookshack/positive-vibes': 'error',
938
+ 'cookshack/narrowest-scope': 'error',
939
+ 'cookshack/use-risky-equal': 'error',
940
+ 'cookshack/always-let': 'error',
941
+ // using the implicit inititialization to undefined fits better
942
+ //'cookshack/init-before-use': 'error',
943
+ 'cookshack/var-decl-block-start': 'error',
944
+ 'cookshack/fn-decl-block-start': 'error',
461
945
  'no-mixed-operators': 'error',
462
946
  'no-multi-spaces': 'error',
463
947
  'no-multiple-empty-lines': [ 'error', { max: 1, maxEOF: 0 } ],