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