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