@cookshack/eslint-config 2.0.5 → 4.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$2
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'
@@ -121,18 +120,20 @@ function getConditionalContext
121
120
  return ''
122
121
  }
123
122
 
124
- function nodeContains(node, target) {
125
- if (node === target)
123
+ function nodeContains
124
+ (node, target) {
125
+ if (node == target)
126
126
  return true
127
- if (node && typeof node === 'object')
127
+ if (node && typeof node == 'object')
128
128
  for (let key in node)
129
129
  if (nodeHas(node[key], target))
130
130
  return true
131
131
  return false
132
132
  }
133
133
 
134
- function nodeHas(value, target) {
135
- if (value === target)
134
+ function nodeHas
135
+ (value, target) {
136
+ if (value == target)
136
137
  return true
137
138
  if (Array.isArray(value))
138
139
  return value.some(v => nodeContains(v, target))
@@ -146,15 +147,15 @@ function isConditionalRef
146
147
  node = ref.identifier.parent;
147
148
 
148
149
  while (node) {
149
- if (node === narrowestScope.block)
150
+ if (node == narrowestScope.block)
150
151
  break
151
- if (node.type === 'BlockStatement') {
152
+ if (node.type == 'BlockStatement') {
152
153
  let parent;
153
154
 
154
155
  parent = node.parent;
155
- if (parent?.type === 'IfStatement' && (parent.consequent === node || parent.alternate === node))
156
+ if (parent?.type == 'IfStatement' && (parent.consequent == node || parent.alternate == node))
156
157
  return true
157
- if ([ 'WhileStatement', 'DoWhileStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement' ].includes(parent?.type) && parent.body === node)
158
+ if ([ 'WhileStatement', 'DoWhileStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement' ].includes(parent?.type) && parent.body == node)
158
159
  return true
159
160
  }
160
161
  node = node.parent;
@@ -186,19 +187,17 @@ function mayBeReadBeforeAnyWrite
186
187
  let item;
187
188
 
188
189
  if (isReadRef(ref))
189
- // a possible read
190
190
  return 1
191
191
 
192
192
  item = ref.cookshackNarrowestScopeItem;
193
193
  if (item.ctx == 'B' || isConditionalRef(ref, narrowestScope))
194
- // a conditional write
195
194
  continue
196
- // A guaranteed write before any possible read.
197
195
  return 0
198
196
  }
199
197
  }
200
198
 
201
- function scopeStart(scope) {
199
+ function scopeStart
200
+ (scope) {
202
201
  if (scope.block == null)
203
202
  return Infinity
204
203
  if (scope.type == 'function' && scope.block.id)
@@ -208,50 +207,73 @@ function scopeStart(scope) {
208
207
  return scope.block.range[0]
209
208
  }
210
209
 
211
- function buildScopeTree(scope, prefix, scopeToNode) {
210
+ function ensureInVarIds
211
+ (variable) {
212
+ if (varIds.has(variable))
213
+ return
214
+ varIds.set(variable, nextVarId++);
215
+ }
216
+
217
+ function isCompoundAssignmentOp
218
+ (op) {
219
+ if (op == '=')
220
+ return 0
221
+ return 1
222
+ }
223
+
224
+ function buildScopeTree
225
+ (scope, prefix, scopeToNode, astToTree) {
212
226
  let node, siblingNum;
213
227
 
214
- node = {
215
- scope,
216
- prefix,
217
- items: [],
218
- children: []
219
- };
228
+ node = { scope,
229
+ prefix,
230
+ items: [],
231
+ children: [] };
232
+
220
233
  scopeToNode.set(scope, node);
234
+ if (scope.block && astToTree)
235
+ astToTree.set(scope.block, node);
221
236
 
222
237
  siblingNum = 0;
223
238
  for (let child of scope.childScopes) {
224
239
  siblingNum++;
225
- node.children.push(buildScopeTree(child, prefix + '.' + siblingNum, scopeToNode));
240
+ node.children.push(buildScopeTree(child, prefix + '.' + siblingNum, scopeToNode, astToTree));
226
241
  }
227
242
 
228
243
  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] });
244
+ if (variable.defs.length > 0) {
245
+ ensureInVarIds(variable);
246
+ 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) });
247
+ }
231
248
 
232
249
  for (let ref of variable.references) {
233
250
  let targetNode;
234
251
 
235
252
  targetNode = scopeToNode.get(ref.from);
236
253
  if (targetNode) {
237
- let parent, sortPos, ctx, item;
254
+ let parent, sortPos, ctx, item1, item2;
238
255
 
239
256
  ctx = getConditionalContext(ref);
240
257
  parent = ref.identifier.parent;
241
258
 
242
259
  if (isWriteRef(ref))
243
260
  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] };
261
+ item1 = { ref, type: 'READ', name: ref.identifier.name, ctx, pos: ref.identifier.range[0] };
262
+ item2 = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] };
246
263
  }
247
264
  else if (ref.identifier.parent?.type == 'AssignmentExpression') {
248
265
  sortPos = parent.right.range[1] + 0.4;
249
- item = { ref, type: 'WRITE', name: ref.identifier.name, ctx, pos: sortPos };
266
+ if (ref.identifier.parent.left == ref.identifier && isCompoundAssignmentOp(ref.identifier.parent.operator)) {
267
+ item1 = { ref, type: 'READ', name: ref.identifier.name, ctx, pos: ref.identifier.range[0] };
268
+ item2 = { ref, type: 'WRITE', name: ref.identifier.name, pos: sortPos };
269
+ }
270
+ else
271
+ item1 = { ref, type: 'WRITE', name: ref.identifier.name, ctx, pos: sortPos };
250
272
  }
251
273
  else if (ref.identifier.parent?.type == 'VariableDeclarator')
252
- item = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] + 0.4 };
274
+ item1 = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] + 0.4 };
253
275
  else
254
- item = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] };
276
+ item1 = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] };
255
277
  else {
256
278
  let declarator;
257
279
 
@@ -265,10 +287,16 @@ function buildScopeTree(scope, prefix, scopeToNode) {
265
287
  sortPos = declarator.id ? declarator.id.range[0] - 0.4 : ref.identifier.range[0];
266
288
  else
267
289
  sortPos = ref.identifier.range[0];
268
- item = { ref, type: 'READ', name: ref.identifier.name, ctx, pos: sortPos };
290
+ item1 = { ref, type: 'READ', name: ref.identifier.name, ctx, pos: sortPos };
291
+ }
292
+ ensureInVarIds(variable);
293
+ item1.varId = varIds.get(variable);
294
+ targetNode.items.push(item1);
295
+ if (item2) {
296
+ item2.varId = varIds.get(variable);
297
+ targetNode.items.push(item2);
269
298
  }
270
- targetNode.items.push(item);
271
- ref.cookshackNarrowestScopeItem = item;
299
+ ref.cookshackNarrowestScopeItem = item2 || item1;
272
300
  }
273
301
  }
274
302
  }
@@ -278,7 +306,8 @@ function buildScopeTree(scope, prefix, scopeToNode) {
278
306
  return node
279
307
  }
280
308
 
281
- function checkScopeNode(context, treeNode, reported, scopeToNode) {
309
+ function checkScopeNode
310
+ (context, treeNode, reported, scopeToNode) {
282
311
  let indent;
283
312
 
284
313
  reported = reported || new Set;
@@ -289,11 +318,11 @@ function checkScopeNode(context, treeNode, reported, scopeToNode) {
289
318
 
290
319
  if (reported.has(variable))
291
320
  continue
292
- if (variable.defs.length === 0)
321
+ if (variable.defs.length == 0)
293
322
  continue
294
323
  if ([ 'Parameter', 'FunctionName', 'ImportBinding', 'CatchClause', 'ClassName' ].includes(variable.defs[0].type))
295
324
  continue
296
- if (variable.defs[0].node.parent?.parent?.type === 'ExportNamedDeclaration')
325
+ if (variable.defs[0].node.parent?.parent?.type == 'ExportNamedDeclaration')
297
326
  continue
298
327
 
299
328
  defNode = variable.defs[0]?.name;
@@ -302,38 +331,36 @@ function checkScopeNode(context, treeNode, reported, scopeToNode) {
302
331
 
303
332
  defScope = getDefinitionScope(variable);
304
333
  defNodePrefix = scopeToNode.get(defScope)?.prefix ?? '?';
305
- trace(indent, '1 found decl scope of', variable.name + ':', defNodePrefix + ' ' + defScope.type.toUpperCase());
334
+ trace$2(indent, '1 found decl scope of', variable.name + ':', defNodePrefix + ' ' + defScope.type.toUpperCase());
306
335
 
307
336
  narrowestScope = getNarrowestScope(variable);
308
337
  if (narrowestScope) {
309
338
  let narrowestPrefix;
310
339
 
311
340
  narrowestPrefix = scopeToNode.get(narrowestScope)?.prefix ?? '?';
312
- trace(indent, '2 found narrowest scope of', variable.name + ':', narrowestPrefix + ' ' + narrowestScope?.type.toUpperCase());
341
+ trace$2(indent, '2 found narrowest scope of', variable.name + ':', narrowestPrefix + ' ' + narrowestScope?.type.toUpperCase());
313
342
 
314
343
  markConditionalRefs(variable, scopeToNode, narrowestScope);
315
344
 
316
345
  if (defScope == narrowestScope)
317
346
  continue
318
- trace(indent, '3', variable.name, 'could be moved to a narrower scope');
347
+ trace$2(indent, '3', variable.name, 'could be moved to a narrower scope');
319
348
 
320
349
  if (defScope.type == 'for') {
321
- trace(indent, '4 exception:', variable.name, 'is in a for loop header');
350
+ trace$2(indent, '4 exception:', variable.name, 'is in a for loop header');
322
351
  continue
323
352
  }
324
353
  if (mayBeReadBeforeAnyWrite(variable, scopeToNode, narrowestScope)) {
325
- trace(indent, '4 exception:', variable.name, 'mayBeReadBeforeAnyWrite');
354
+ trace$2(indent, '4 exception:', variable.name, 'mayBeReadBeforeAnyWrite');
326
355
  continue
327
356
  }
328
357
 
329
- trace(indent, '5', variable.name, 'is too broad');
358
+ trace$2(indent, '5', variable.name, 'is too broad');
330
359
 
331
360
  reported.add(variable);
332
- context.report({
333
- node: defNode,
334
- messageId: 'tooBroad',
335
- data: { name: variable.name }
336
- });
361
+ context.report({ node: defNode,
362
+ messageId: 'tooBroad',
363
+ data: { name: variable.name } });
337
364
  }
338
365
  }
339
366
  }
@@ -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
@@ -379,111 +405,713 @@ function createNarrowestScope
379
405
  clearPrintBuffer();
380
406
  scopeManager = context.sourceCode.scopeManager;
381
407
  if (scopeManager)
382
- return {
383
- 'Program:exit'() {
384
- let tree, scopeToNode;
385
-
386
- scopeToNode = new Map;
387
- tree = buildScopeTree(scopeManager.scopes[0], '1', scopeToNode);
388
- checkScopeNode(context, tree, null, scopeToNode);
389
- printTree(tree, 0);
408
+ return { 'Program:exit'
409
+ () {
410
+ let tree, scopeToNode;
411
+
412
+ scopeToNode = new Map;
413
+ nextVarId = 0;
414
+ tree = buildScopeTree(scopeManager.scopes[0], '1', scopeToNode);
415
+ checkScopeNode(context, tree, null, scopeToNode);
416
+ printTree(tree, 0);
417
+ } }
418
+ }
419
+
420
+ var narrowestScopePlugin = { 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 };
425
+
426
+ function createPositiveVibes
427
+ (context) {
428
+ return { UnaryExpression
429
+ (node) {
430
+ if (node.operator == '!')
431
+ context.report({ node,
432
+ messageId: 'positiveVibes' });
433
+ },
434
+ BinaryExpression
435
+ (node) {
436
+ if (node.operator == '!=')
437
+ context.report({ node,
438
+ messageId: 'equality' });
439
+ else if (node.operator == '!==')
440
+ context.report({ node,
441
+ messageId: 'strictEquality' });
442
+ } }
443
+ }
444
+
445
+ var positiveVibesPlugin = { meta: { type: 'problem',
446
+ docs: { description: 'Prefer positive expressions.' },
447
+ messages: { positiveVibes: 'Be positive!',
448
+ equality: 'Use ==.',
449
+ strictEquality: 'Use ===.' },
450
+ schema: [] },
451
+ create: createPositiveVibes };
452
+
453
+ var useRiskyEqualPlugin = { meta: { type: 'problem',
454
+ docs: { description: 'Enforce use of == instead of ===.' },
455
+ messages: { risky: 'Use ==.' },
456
+ schema: [] },
457
+ create
458
+ (context) {
459
+ return { BinaryExpression
460
+ (node) {
461
+ if (node.operator == '===')
462
+ context.report({ node, messageId: 'risky' });
463
+ } }
464
+ } };
465
+
466
+ function create$4
467
+ (context) {
468
+ return { VariableDeclaration
469
+ (node) {
470
+ if (node.kind == 'const' || node.kind == 'var')
471
+ context.report({ node, messageId: 'useLet' });
472
+ } }
473
+ }
474
+
475
+ var alwaysLetPlugin = { meta: { type: 'problem',
476
+ docs: { description: 'Enforce use of let instead of const or var.' },
477
+ messages: { useLet: 'Use let.' },
478
+ schema: [] },
479
+ create: create$4 };
480
+
481
+ let ostIdCounter, ost;
482
+
483
+ ostIdCounter = 0;
484
+ ost = 0;
485
+
486
+ function trace$1
487
+ (...args) {
488
+ }
489
+
490
+ function createInitBeforeUse
491
+ (context) {
492
+ let scopeManager;
493
+
494
+ scopeManager = context.sourceCode.scopeManager;
495
+ if (scopeManager)
496
+ return { 'Program:exit'
497
+ () {
498
+ let scopeToNode, astToTree, astToOst;
499
+ scopeToNode = new Map;
500
+ astToTree = new Map;
501
+ astToOst = new Map;
502
+ buildScopeTree(scopeManager.scopes[0], '1', scopeToNode, astToTree);
503
+
504
+ ostIdCounter = 0;
505
+ ost = processAst(context.sourceCode.ast, null, astToTree, astToOst, '', new Set());
506
+
507
+ ostAnnotate(ost, astToOst, context);
508
+
509
+ ostCheck(ost, context);
510
+ } }
511
+ }
512
+
513
+ function isRegularDeclaration
514
+ (item) {
515
+ if (item.type == 'LET') {
516
+ if (item.defType == 'FunctionName' || item.defType == 'Parameter')
517
+ return 0
518
+ return 1
519
+ }
520
+ return 0
521
+ }
522
+
523
+ function processAst
524
+ (astNode, parentOst, astToTree, astToOst, indent, visited) {
525
+ if (astNode) {
526
+ let treeNode, scopeName, lets, reads, writes, ost, children;
527
+
528
+ if (visited.has(astNode))
529
+ return
530
+ visited.add(astNode);
531
+
532
+ treeNode = astToTree.get(astNode) ?? parentOst?.treeNode;
533
+
534
+ scopeName = treeNode?.scope ? `${treeNode.scope.type}` : 'no-scope';
535
+ if (treeNode?.scope?.block?.id?.name)
536
+ scopeName += `(${treeNode.scope.block.id.name})`;
537
+ trace$1(`${indent}${astNode.type}`);
538
+
539
+ lets = [];
540
+ reads = [];
541
+ writes = [];
542
+
543
+ for (let item of treeNode?.items ?? [])
544
+ if (isRegularDeclaration(item)) {
545
+ let scopeCreator;
546
+
547
+ scopeCreator = treeNode?.scope?.block;
548
+ if (scopeCreator && astNode == scopeCreator) {
549
+ lets.push({ item });
550
+ trace$1(`${indent} | LET ${item.name}:${item.varId}`);
551
+ }
390
552
  }
553
+ else if (item.ref)
554
+ if (astNode == item.ref.identifier)
555
+ if (item.type == 'READ') {
556
+ reads.push({ item });
557
+ trace$1(`${indent} | READ ${item.name}:${item.varId}`);
558
+ }
559
+ else if (item.type == 'WRITE') {
560
+ writes.push({ item });
561
+ trace$1(`${indent} | WRITE ${item.name}:${item.varId}`);
562
+ }
563
+
564
+ ost = { id: ostIdCounter++,
565
+ astNode,
566
+ treeNode,
567
+ scopeItems: treeNode?.items ?? [],
568
+ lets,
569
+ reads,
570
+ writes,
571
+ children: [],
572
+ fnDefOst: null };
573
+
574
+ astToOst.set(astNode, ost);
575
+
576
+ children = [];
577
+
578
+ if (astNode.type == 'ForOfStatement' || astNode.type == 'ForInStatement') {
579
+ if (astNode.right)
580
+ children.push(astNode.right);
581
+ if (astNode.left)
582
+ children.push(astNode.left);
583
+ if (astNode.body)
584
+ children.push(astNode.body);
585
+ }
586
+ else if (astNode.type == 'ForStatement') {
587
+ if (astNode.init)
588
+ children.push(astNode.init);
589
+ if (astNode.test)
590
+ children.push(astNode.test);
591
+ if (astNode.update)
592
+ children.push(astNode.update);
593
+ if (astNode.body)
594
+ children.push(astNode.body);
595
+ }
596
+ else if (astNode.type == 'AssignmentExpression') {
597
+ if (astNode.right)
598
+ children.push(astNode.right);
599
+ if (astNode.left)
600
+ children.push(astNode.left);
601
+ }
602
+ else {
603
+ if (astNode.body)
604
+ if (Array.isArray(astNode.body))
605
+ children.push(...astNode.body);
606
+ else
607
+ children.push(astNode.body);
608
+ if (astNode.consequent)
609
+ children.push(astNode.consequent);
610
+ if (astNode.alternate)
611
+ children.push(astNode.alternate);
612
+ if (astNode.block)
613
+ children.push(astNode.block);
614
+ if (astNode.expression)
615
+ children.push(astNode.expression);
616
+ if (astNode.callee)
617
+ children.push(astNode.callee);
618
+ if (astNode.object)
619
+ children.push(astNode.object);
620
+ if (astNode.property)
621
+ children.push(astNode.property);
622
+ if (astNode.init)
623
+ children.push(astNode.init);
624
+ if (astNode.id)
625
+ children.push(astNode.id);
626
+ if (astNode.declarations)
627
+ children.push(...astNode.declarations);
628
+ if (astNode.test)
629
+ children.push(astNode.test);
630
+ if (astNode.update)
631
+ children.push(astNode.update);
632
+ if (astNode.left)
633
+ children.push(astNode.left);
634
+ if (astNode.right)
635
+ children.push(astNode.right);
636
+ if (astNode.argument)
637
+ children.push(astNode.argument);
638
+ if (astNode.arguments)
639
+ children.push(...astNode.arguments);
640
+ if (astNode.elements)
641
+ children.push(...astNode.elements);
642
+ if (astNode.properties)
643
+ children.push(...astNode.properties);
391
644
  }
645
+
646
+ for (let child of children) {
647
+ let childOst;
648
+
649
+ childOst = processAst(child, ost, astToTree, astToOst, indent + ' ', visited);
650
+ if (childOst)
651
+ ost.children.push(childOst);
652
+ }
653
+
654
+ return ost
655
+ }
392
656
  }
393
657
 
394
- function createPositiveVibes
658
+ function ostAnnotate
659
+ (ost, astToOst, context) {
660
+ if (ost) {
661
+ for (let letInfo of ost.lets) {
662
+ let writeOst;
663
+
664
+ writeOst = findFirstWrite(ost, letInfo);
665
+ letInfo.firstWrite = writeOst;
666
+ if (writeOst)
667
+ continue
668
+ if (letInfo.item.defType == 'ImportBinding')
669
+ continue
670
+ context.report({ node: letInfo.item.identifier,
671
+ messageId: 'mustInit',
672
+ data: { name: letInfo.item.name } });
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
704
+ (ost, letInfo) {
705
+ if (ost.astNode.type == 'FunctionDeclaration' || ost.astNode.type == 'ArrowFunctionExpression' || ost.astNode.type == 'FunctionExpression')
706
+ for (let child of ost.children)
707
+ if (child.astNode.type == 'BlockStatement')
708
+ return findFirstWriteInSubtree(child, letInfo)
709
+ return findFirstWriteInSubtree(ost, letInfo)
710
+ }
711
+
712
+ function findFirstWriteInSubtree
713
+ (ost, letInfo) {
714
+ if (ost) {
715
+ if (ost.astNode.type == 'FunctionDeclaration' || ost.astNode.type == 'ArrowFunctionExpression' || ost.astNode.type == 'FunctionExpression')
716
+ return null
717
+
718
+ for (let writeInfo of ost.writes) {
719
+ let writeVar;
720
+
721
+ writeVar = writeInfo.item.ref.resolved;
722
+ if (writeVar == letInfo.item.variable)
723
+ return ost
724
+ }
725
+
726
+ for (let child of ost.children) {
727
+ let result;
728
+
729
+ result = findFirstWriteInSubtree(child, letInfo);
730
+ if (result)
731
+ return result
732
+ }
733
+ }
734
+
735
+ return null
736
+ }
737
+
738
+ function ostCheck
739
+ (ost, context) {
740
+ if (ost) {
741
+ for (let letInfo of ost.lets)
742
+ if (letInfo.firstWrite)
743
+ walk2Start(ost, letInfo, context);
744
+
745
+ for (let child of ost.children)
746
+ ostCheck(child, context);
747
+ }
748
+ }
749
+
750
+ function walk2Start
751
+ (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
760
+ (node, letInfo, context, visited) {
761
+ if (node) {
762
+ if (node.astNode.type == 'FunctionDeclaration' || node.astNode.type == 'ArrowFunctionExpression' || node.astNode.type == 'FunctionExpression')
763
+ return false
764
+
765
+ if (node == letInfo.firstWrite) {
766
+ for (let readInfo of node.reads)
767
+ if (readInfo.item.ref.resolved == letInfo.item.variable) {
768
+ context.report({ node: readInfo.item.ref.identifier,
769
+ messageId: 'initBeforeUse',
770
+ data: { name: letInfo.item.name } });
771
+ }
772
+ return true
773
+ }
774
+
775
+ if (node.astNode.type == 'CallExpression' && node.fnDefOst) {
776
+ let fnType;
777
+
778
+ fnType = node.fnDefOst.astNode.type;
779
+
780
+ if (fnType == 'FunctionDeclaration' || fnType == 'ArrowFunctionExpression' || fnType == 'FunctionExpression') {
781
+ let key;
782
+
783
+ key = `${letInfo.item.name}:${node.fnDefOst.id}`;
784
+ if (visited.has(key)) ;
785
+ else {
786
+ visited.add(key);
787
+ for (let child of node.fnDefOst.children)
788
+ if (child.astNode.type == 'BlockStatement' && walk2(child, letInfo, context, visited))
789
+ return true
790
+ }
791
+ }
792
+ }
793
+
794
+ for (let readInfo of node.reads)
795
+ if (readInfo.item.ref.resolved == letInfo.item.variable) {
796
+ context.report({ node: readInfo.item.ref.identifier,
797
+ messageId: 'initBeforeUse',
798
+ data: { name: letInfo.item.name } });
799
+ }
800
+
801
+ for (let child of node.children)
802
+ if (walk2(child, letInfo, context, visited))
803
+ return true
804
+ }
805
+
806
+ return false
807
+ }
808
+
809
+ var initBeforeUsePlugin = { meta: { type: 'problem',
810
+ docs: { description: 'Warn when a variable is used before being explicitly initialized.' },
811
+ messages: { initBeforeUse: "'{{name}}' used before initialization.",
812
+ mustInit: "'{{name}}' must be initialized." },
813
+ schema: [] },
814
+ create: createInitBeforeUse };
815
+
816
+ function VariableDeclaration
817
+ (context, node) {
818
+ let parent;
819
+
820
+ parent = node.parent;
821
+
822
+ for (parent = node.parent; parent; parent = parent.parent)
823
+ if (parent.type == 'BlockStatement')
824
+ break
825
+
826
+ if (parent) {
827
+ let idx;
828
+
829
+ if (parent.parent?.type == 'CatchClause')
830
+ return
831
+
832
+ idx = parent.body.indexOf(node);
833
+ for (let i = 0; i < idx; i++) {
834
+ if (parent.body[i].type == 'VariableDeclaration')
835
+ continue
836
+ context.report({ node, messageId: 'varDeclBlockStart' });
837
+ return
838
+ }
839
+ }
840
+ }
841
+
842
+ function create$3
395
843
  (context) {
396
- return {
397
- UnaryExpression(node) {
398
- if (node.operator == '!')
399
- context.report({ node,
400
- messageId: 'positiveVibes' });
401
- },
402
- BinaryExpression(node) {
403
- if (node.operator == '!=')
404
- context.report({ node,
405
- messageId: 'equality' });
406
- else if (node.operator == '!==')
407
- context.report({ node,
408
- messageId: 'strictEquality' });
844
+ return { VariableDeclaration: node => VariableDeclaration(context, node) }
845
+ }
846
+
847
+ var varDeclBlockStartPlugin = { meta: { type: 'suggestion',
848
+ docs: { description: 'Require variable declarations to be at the start of the block.' },
849
+ messages: { varDeclBlockStart: 'VarDecl must be at start of block.' },
850
+ schema: [] },
851
+ create: create$3 };
852
+
853
+ function FunctionDeclaration
854
+ (context, node) {
855
+ let parent;
856
+
857
+ for (parent = node.parent; parent; parent = parent.parent)
858
+ if (parent.type == 'BlockStatement')
859
+ break
860
+
861
+ if (parent) {
862
+ let idx;
863
+
864
+ if (parent.parent?.type == 'CatchClause')
865
+ return
866
+
867
+ idx = parent.body.indexOf(node);
868
+ for (let i = 0; i < idx; i++) {
869
+ if (parent.body[i].type == 'VariableDeclaration'
870
+ || parent.body[i].type == 'FunctionDeclaration'
871
+ || parent.body[i].type == 'EmptyStatement')
872
+ continue
873
+ context.report({ node, messageId: 'fnDeclBlockStart' });
874
+ return
409
875
  }
410
876
  }
411
877
  }
412
878
 
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 } } } };
425
-
426
- rules = {
427
- 'array-bracket-newline': [ 'error', 'never' ],
428
- 'array-bracket-spacing': [ 'error', 'always' ],
429
- 'arrow-parens': [ 'error', 'as-needed' ],
430
- 'brace-style': [ 'error', 'stroustrup' ],
431
- 'comma-dangle': 'error',
432
- 'curly': [ 'error', 'multi' ],
433
- 'eol-last': [ 'error', 'always' ],
434
- 'function-paren-newline': [ 'error', 'never' ],
435
- 'indent': [ 'error', 2, { ArrayExpression: 'first',
436
- CallExpression: { arguments: 'first' },
437
- //flatTernaryExpressions: true,
438
- //offsetTernaryExpressions: true,
439
- // ternary, because overhangs strangely (eg multiline in array def)
440
- 'ignoredNodes': [ 'ConditionalExpression' ],
441
- FunctionDeclaration: { parameters: 'first', body: 1 },
442
- FunctionExpression: { parameters: 'first', body: 1 },
443
- ImportDeclaration: 'first',
444
- ObjectExpression: 'first',
445
- offsetTernaryExpressions: true,
446
- VariableDeclarator: 'first' } ],
447
- 'init-declarations': [ 'error', 'never', { 'ignoreForLoopInit': true } ],
448
- 'keyword-spacing': [ 'error', { before: true, after: true } ],
449
- 'linebreak-style': [ 'error', 'unix' ],
450
- 'padding-line-between-statements': [ 'error',
451
- { blankLine: 'always', prev: 'let', next: '*' },
452
- { blankLine: 'never', prev: 'let', next: 'let' } ],
453
- 'no-case-declarations': 'error',
454
- 'no-global-assign': 'error',
455
- 'cookshack/narrowest-scope': 'error',
456
- 'cookshack/positive-vibes': 'error',
457
- 'no-mixed-operators': 'error',
458
- 'no-multi-spaces': 'error',
459
- 'no-multiple-empty-lines': [ 'error', { max: 1, maxEOF: 0 } ],
460
- 'no-negated-condition': 'error',
461
- 'no-redeclare': 'error',
462
- 'no-sequences': 'error',
463
- 'no-sparse-arrays': 'error',
464
- 'no-tabs': 'error',
465
- 'no-trailing-spaces': 'error',
466
- 'no-undef': 'error',
467
- 'no-unsafe-negation': 'error',
468
- 'no-unused-vars': 'error',
469
- 'no-var': 'error',
470
- 'object-curly-spacing': [ 'error', 'always' ],
471
- 'object-shorthand': [ 'error', 'always' ],
472
- quotes: [ 'error', 'single', { avoidEscape: true } ],
473
- semi: [ 'error', 'never' ]
474
- //'vars-on-top': [ 'error' ], // want version for let
475
- //'newline-before-function-paren': ['error', 'always'],
476
- };
477
-
478
- languageOptions = {
479
- globals: {
480
- ...globals.node
481
- },
482
- parserOptions: {
483
- ecmaVersion: 2025,
484
- sourceType: 'module'
879
+ function create$2
880
+ (context) {
881
+ return { FunctionDeclaration: node => FunctionDeclaration(context, node) }
882
+ }
883
+
884
+ var fnDeclBlockStartPlugin = { meta: { type: 'suggestion',
885
+ docs: { description: 'Require function declarations to be at the start of the block.' },
886
+ messages: { fnDeclBlockStart: 'FnDecl must be the start the block (after VarDecls).' },
887
+ schema: [] },
888
+ create: create$2 };
889
+
890
+ function FnArgsNl
891
+ (node, context) {
892
+ let nameLine, parenLine, nameEnd, i, newlines, parent;
893
+
894
+ parent = node.parent;
895
+
896
+ if (parent?.type == 'Property' && (parent.method || parent.kind == 'get' || parent.kind == 'set')) {
897
+ nameLine = parent.key.loc.start.line;
898
+ nameEnd = parent.key.range[1];
899
+ }
900
+ else if (parent?.type == 'MethodDefinition') {
901
+ nameLine = parent.key.loc.start.line;
902
+ nameEnd = parent.key.range[1];
903
+ }
904
+ else {
905
+ nameLine = node.loc.start.line;
906
+ nameEnd = node.range[0];
907
+ }
908
+
909
+ i = nameEnd;
910
+ newlines = 0;
911
+ while (i < context.sourceCode.text.length) {
912
+ if (context.sourceCode.text[i] == '(')
913
+ break
914
+ if (context.sourceCode.text[i] == '\n')
915
+ newlines++;
916
+ i++;
917
+ }
918
+
919
+ parenLine = nameLine + newlines;
920
+
921
+ if (parenLine - nameLine == 1)
922
+ return
923
+ context.report({ node, messageId: 'fnArgsNl' });
924
+ }
925
+
926
+ function create$1
927
+ (context) {
928
+ return { FunctionDeclaration: node => FnArgsNl(node, context),
929
+ FunctionExpression: node => FnArgsNl(node, context) }
930
+ }
931
+
932
+ var fnArgsNlPlugin = { meta: { type: 'suggestion',
933
+ docs: { description: 'Require function args on the line immediately after the function name.' },
934
+ messages: { fnArgsNl: 'Fn args must be on the line immediately after the function name.' },
935
+ schema: [] },
936
+ create: create$1 };
937
+
938
+ function trace
939
+ (...args) {
940
+ }
941
+
942
+ function unit
943
+ (context) {
944
+ return context.options[0] ?? 2
945
+ }
946
+
947
+ function checkObjectExpressionProperties
948
+ (properties, node, context) {
949
+ let firstProp, sourceCode, lastProp, lastPropEnd, closingBrace, firstPropLine, firstPropCol, afterLastProp, closingLine, lastPropValueEndLine;
950
+
951
+ sourceCode = context.sourceCode.text;
952
+ firstProp = properties[0];
953
+ lastProp = properties[properties.length - 1];
954
+ firstPropLine = firstProp.loc.start.line;
955
+ firstPropCol = firstProp.loc.start.column;
956
+ lastPropEnd = lastProp.range[1];
957
+ closingBrace = sourceCode.indexOf('}', lastPropEnd);
958
+ closingLine = sourceCode.slice(0, closingBrace).split('\n').length;
959
+ afterLastProp = sourceCode.slice(lastPropEnd, closingBrace);
960
+ lastPropValueEndLine = sourceCode.slice(0, lastPropEnd).split('\n').length;
961
+ trace('CHECK POINT 1: is single-line? firstPropLine=%d, braceLine=%d, closingLine=%d', firstPropLine, node.loc.start.line, closingLine);
962
+ if (firstPropLine == node.loc.start.line && closingLine == firstPropLine)
963
+ return
964
+ trace('CHECK POINT 2: is firstPropLine (%d) != braceLine (%d)?', firstPropLine, node.loc.start.line);
965
+ if (firstPropLine == node.loc.start.line) ;
966
+ else if (firstPropCol == node.loc.start.column + unit(context)) ;
967
+ else {
968
+ context.report({ node: firstProp, messageId: 'indentStruct' });
485
969
  }
486
- };
970
+ for (let i = 1; i < properties.length; i++) {
971
+ let prop;
972
+
973
+ prop = properties[i];
974
+ if (prop.loc.start.column == firstPropCol) ;
975
+ else {
976
+ context.report({ node: prop, messageId: 'indentStruct' });
977
+ }
978
+ }
979
+ if (closingLine > lastPropValueEndLine) {
980
+ let braceCol, closingCol;
981
+
982
+ braceCol = node.loc.start.column;
983
+ closingCol = node.loc.end.column - 1;
984
+ if (closingCol == braceCol) ;
985
+ else {
986
+ context.report({ node, messageId: 'indentStruct' });
987
+ }
988
+ }
989
+ if (closingLine == lastPropValueEndLine) {
990
+ if (afterLastProp == ' ') ;
991
+ else {
992
+ context.report({ node, messageId: 'indentStruct' });
993
+ }
994
+ }
995
+ for (let prop of properties)
996
+ if (prop.method) {
997
+ let keyLine, keyEnd, i, newlines, parenLine;
998
+
999
+ keyLine = prop.key.loc.start.line;
1000
+ keyEnd = prop.key.range[1];
1001
+ i = keyEnd;
1002
+ newlines = 0;
1003
+
1004
+ while (i < sourceCode.length) {
1005
+ if (sourceCode[i] == '(')
1006
+ break
1007
+ if (sourceCode[i] == '\n')
1008
+ newlines++;
1009
+ i++;
1010
+ }
1011
+ parenLine = keyLine + newlines;
1012
+ if (parenLine > keyLine) {
1013
+ let parenCol;
1014
+
1015
+ parenCol = i - sourceCode.lastIndexOf('\n', i);
1016
+ if (prop.value.type == 'FunctionExpression' && prop.value.async) ;
1017
+ else if (parenCol - 1 == prop.key.loc.start.column) ;
1018
+ else {
1019
+ context.report({ node: prop, messageId: 'indentStruct' });
1020
+ }
1021
+ }
1022
+ }
1023
+ }
1024
+
1025
+ function checkObjectExpression
1026
+ (node, context) {
1027
+ let properties;
1028
+
1029
+ properties = node.properties;
1030
+ if (properties.length)
1031
+ checkObjectExpressionProperties(properties, node, context);
1032
+ }
1033
+
1034
+ function create
1035
+ (context) {
1036
+ return { ObjectExpression: node => checkObjectExpression(node, context) }
1037
+ }
1038
+
1039
+ var indentStructPlugin = { meta: { type: 'suggestion',
1040
+ docs: { description: 'Struct alignment rules.' },
1041
+ messages: { indentStruct: 'Indent structure' },
1042
+ schema: [] },
1043
+ create };
1044
+
1045
+ let rules, languageOptions, plugins;
1046
+
1047
+ plugins = { 'cookshack': { rules: { 'positive-vibes': positiveVibesPlugin,
1048
+ 'narrowest-scope': narrowestScopePlugin,
1049
+ 'use-risky-equal': useRiskyEqualPlugin,
1050
+ 'always-let': alwaysLetPlugin,
1051
+ 'init-before-use': initBeforeUsePlugin,
1052
+ 'var-decl-block-start': varDeclBlockStartPlugin,
1053
+ 'fn-decl-block-start': fnDeclBlockStartPlugin,
1054
+ 'fn-args-nl': fnArgsNlPlugin,
1055
+ 'indent-struct': indentStructPlugin } } };
1056
+
1057
+ rules = { 'array-bracket-newline': [ 'error', 'never' ],
1058
+ 'array-bracket-spacing': [ 'error', 'always' ],
1059
+ 'arrow-parens': [ 'error', 'as-needed' ],
1060
+ 'brace-style': [ 'error', 'stroustrup' ],
1061
+ 'comma-dangle': 'error',
1062
+ 'curly': [ 'error', 'multi' ],
1063
+ 'eol-last': [ 'error', 'always' ],
1064
+ 'function-paren-newline': [ 'error', 'never' ],
1065
+ 'indent': [ 'error', 2, { ArrayExpression: 'first',
1066
+ CallExpression: { arguments: 'first' },
1067
+ //flatTernaryExpressions: true,
1068
+ //offsetTernaryExpressions: true,
1069
+ // ternary, because overhangs strangely (eg multiline in array def)
1070
+ 'ignoredNodes': [ 'ConditionalExpression', 'ObjectExpression *' ],
1071
+ FunctionDeclaration: { parameters: 'first', body: 1 },
1072
+ FunctionExpression: { parameters: 'first', body: 1 },
1073
+ ImportDeclaration: 'first',
1074
+ offsetTernaryExpressions: true,
1075
+ VariableDeclarator: 'first' } ],
1076
+ 'init-declarations': [ 'error', 'never', { 'ignoreForLoopInit': true } ],
1077
+ 'keyword-spacing': [ 'error', { before: true, after: true } ],
1078
+ 'linebreak-style': [ 'error', 'unix' ],
1079
+ 'padding-line-between-statements': [ 'error',
1080
+ { blankLine: 'always', prev: 'let', next: '*' },
1081
+ { blankLine: 'never', prev: 'let', next: 'let' } ],
1082
+ 'no-case-declarations': 'error',
1083
+ 'no-global-assign': 'error',
1084
+ 'cookshack/positive-vibes': 'error',
1085
+ 'cookshack/narrowest-scope': 'error',
1086
+ 'cookshack/use-risky-equal': 'error',
1087
+ 'cookshack/always-let': 'error',
1088
+ // using the implicit inititialization to undefined fits better
1089
+ //'cookshack/init-before-use': 'error',
1090
+ 'cookshack/var-decl-block-start': 'error',
1091
+ 'cookshack/fn-decl-block-start': 'error',
1092
+ 'cookshack/fn-args-nl': 'error',
1093
+ 'cookshack/indent-struct': 'error',
1094
+ 'no-mixed-operators': 'error',
1095
+ 'no-multi-spaces': 'error',
1096
+ 'no-multiple-empty-lines': [ 'error', { max: 1, maxEOF: 0 } ],
1097
+ 'no-negated-condition': 'error',
1098
+ 'no-redeclare': 'error',
1099
+ 'no-sequences': 'error',
1100
+ 'no-sparse-arrays': 'error',
1101
+ 'no-tabs': 'error',
1102
+ 'no-trailing-spaces': 'error',
1103
+ 'no-undef': 'error',
1104
+ 'no-unsafe-negation': 'error',
1105
+ 'no-unused-vars': 'error',
1106
+ 'no-var': 'error',
1107
+ 'object-curly-spacing': [ 'error', 'always' ],
1108
+ 'object-shorthand': [ 'error', 'always' ],
1109
+ quotes: [ 'error', 'single', { avoidEscape: true } ],
1110
+ semi: [ 'error', 'never' ] };
1111
+
1112
+ languageOptions = { globals: { ...globals.node },
1113
+ parserOptions: { ecmaVersion: 2025,
1114
+ sourceType: 'module' } };
487
1115
 
488
1116
  var index = [ { ignores: [ 'TAGS.mjs' ] },
489
1117
  { languageOptions,