@cookshack/eslint-config 1.2.0 → 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,30 +1,905 @@
1
1
  import globals from 'globals';
2
2
 
3
+ let varIds, nextVarId, printBuffer;
4
+
5
+ printBuffer = [];
6
+ varIds = new Map();
7
+ nextVarId = 0;
8
+
9
+ function print
10
+ (...args) {
11
+ printBuffer.push(args.join(' '));
12
+ }
13
+
14
+ function trace$1
15
+ (...args) {
16
+ }
17
+
18
+ function getPrintBuffer
19
+ () {
20
+ return printBuffer.join('\n')
21
+ }
22
+
23
+ function clearPrintBuffer
24
+ () {
25
+ printBuffer = [];
26
+ }
27
+
28
+ function getNarrowestScope
29
+ (variable) {
30
+ let common;
31
+
32
+ common = null;
33
+ for (let ref of variable.references) {
34
+ if (variable.defs.some(def => def.name == ref.identifier))
35
+ continue
36
+ if (ref.from)
37
+ if (common)
38
+ common = getCommonAncestor(common, ref.from);
39
+ else
40
+ common = ref.from;
41
+ }
42
+ return common
43
+ }
44
+
45
+ function getCommonAncestor
46
+ (scope1, scope2) {
47
+ let ancestors, s;
48
+
49
+ ancestors = [];
50
+ s = scope1;
51
+ while (s) {
52
+ if (s.type == 'global')
53
+ break
54
+ ancestors.push(s);
55
+ s = s.upper;
56
+ }
57
+ s = scope2;
58
+ while (s) {
59
+ if (s.type == 'global')
60
+ break
61
+ if (ancestors.includes(s))
62
+ return s
63
+ s = s.upper;
64
+ }
65
+ return scope1
66
+ }
67
+
68
+ function getDefinitionScope
69
+ (variable) {
70
+ return variable.scope
71
+ }
72
+
73
+ function isWriteRef
74
+ (ref) {
75
+ let parent;
76
+
77
+ parent = ref.identifier.parent;
78
+ if (parent) {
79
+ if (parent.type == 'AssignmentExpression' && parent.left == ref.identifier)
80
+ return 1
81
+ if (parent.type == 'UpdateExpression')
82
+ return 1
83
+ if (parent.type == 'VariableDeclarator' && parent.id == ref.identifier)
84
+ return 1
85
+ }
86
+ return 0
87
+ }
88
+
89
+ function isReadRef
90
+ (ref) {
91
+ if (isWriteRef(ref))
92
+ return 0
93
+ return 1
94
+ }
95
+
96
+ function getConditionalContext
97
+ (ref) {
98
+ let node, prevNode, scopeBlock;
99
+
100
+ scopeBlock = ref.from.block;
101
+ prevNode = ref.identifier;
102
+ node = ref.identifier.parent;
103
+ while (node) {
104
+ if (node == scopeBlock)
105
+ break
106
+ if (node.type == 'IfStatement')
107
+ if (prevNode == node.test || nodeContains(node.test, prevNode))
108
+ prevNode = node;
109
+ else
110
+ return 'B'
111
+ else if ([ 'WhileStatement', 'DoWhileStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement', 'SwitchStatement' ].includes(node.type))
112
+ if (prevNode == node.test || nodeContains(node.test, prevNode))
113
+ prevNode = node;
114
+ else
115
+ return 'B'
116
+ else
117
+ prevNode = node;
118
+ node = node.parent;
119
+ }
120
+ return ''
121
+ }
122
+
123
+ function nodeContains(node, target) {
124
+ if (node == target)
125
+ return true
126
+ if (node && typeof node == 'object')
127
+ for (let key in node)
128
+ if (nodeHas(node[key], target))
129
+ return true
130
+ return false
131
+ }
132
+
133
+ function nodeHas(value, target) {
134
+ if (value == target)
135
+ return true
136
+ if (Array.isArray(value))
137
+ return value.some(v => nodeContains(v, target))
138
+ return false
139
+ }
140
+
141
+ function isConditionalRef
142
+ (ref, narrowestScope) {
143
+ let node;
144
+
145
+ node = ref.identifier.parent;
146
+
147
+ while (node) {
148
+ if (node == narrowestScope.block)
149
+ break
150
+ if (node.type == 'BlockStatement') {
151
+ let parent;
152
+
153
+ parent = node.parent;
154
+ if (parent?.type == 'IfStatement' && (parent.consequent == node || parent.alternate == node))
155
+ return true
156
+ if ([ 'WhileStatement', 'DoWhileStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement' ].includes(parent?.type) && parent.body == node)
157
+ return true
158
+ }
159
+ node = node.parent;
160
+ }
161
+ return false
162
+ }
163
+
164
+ function markConditionalRefs
165
+ (variable, scopeToNode, narrowestScope) {
166
+ for (let ref of variable.references) {
167
+ let refNode, rItems, item;
168
+
169
+ refNode = scopeToNode.get(ref.from);
170
+ rItems = refNode.items.filter(i => i.ref == ref);
171
+ item = rItems[0];
172
+ if (item && (item.ctx == 'B' || isConditionalRef(ref, narrowestScope)))
173
+ item.isConditional = true;
174
+ }
175
+ }
176
+
177
+ function mayBeReadBeforeAnyWrite
178
+ (variable, scopeToNode, narrowestScope) {
179
+ let refs;
180
+
181
+ refs = [ ...variable.references ];
182
+ refs.sort((a, b) => (a.cookshackNarrowestScopeItem?.pos ?? a.identifier.range[0]) - (b.cookshackNarrowestScopeItem?.pos ?? b.identifier.range[0]));
183
+
184
+ for (let ref of refs) {
185
+ let item;
186
+
187
+ if (isReadRef(ref))
188
+ return 1
189
+
190
+ item = ref.cookshackNarrowestScopeItem;
191
+ if (item.ctx == 'B' || isConditionalRef(ref, narrowestScope))
192
+ continue
193
+ return 0
194
+ }
195
+ }
196
+
197
+ function scopeStart(scope) {
198
+ if (scope.block == null)
199
+ return Infinity
200
+ if (scope.type == 'function' && scope.block.id)
201
+ return scope.block.id.range[1]
202
+ if (scope.type == 'class' && scope.block.id)
203
+ return scope.block.id.range[0]
204
+ return scope.block.range[0]
205
+ }
206
+
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) {
223
+ let node, siblingNum;
224
+
225
+ node = {
226
+ scope,
227
+ prefix,
228
+ items: [],
229
+ children: []
230
+ };
231
+ scopeToNode.set(scope, node);
232
+ if (scope.block && astToTree)
233
+ astToTree.set(scope.block, node);
234
+
235
+ siblingNum = 0;
236
+ for (let child of scope.childScopes) {
237
+ siblingNum++;
238
+ node.children.push(buildScopeTree(child, prefix + '.' + siblingNum, scopeToNode, astToTree));
239
+ }
240
+
241
+ for (let variable of scope.variables) {
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
+ }
246
+
247
+ for (let ref of variable.references) {
248
+ let targetNode;
249
+
250
+ targetNode = scopeToNode.get(ref.from);
251
+ if (targetNode) {
252
+ let parent, sortPos, ctx, item1, item2;
253
+
254
+ ctx = getConditionalContext(ref);
255
+ parent = ref.identifier.parent;
256
+
257
+ if (isWriteRef(ref))
258
+ if (ref.identifier.parent?.type == 'UpdateExpression') {
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] };
261
+ }
262
+ else if (ref.identifier.parent?.type == 'AssignmentExpression') {
263
+ sortPos = parent.right.range[1] + 0.4;
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 };
270
+ }
271
+ else if (ref.identifier.parent?.type == 'VariableDeclarator')
272
+ item1 = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] + 0.4 };
273
+ else
274
+ item1 = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] };
275
+ else {
276
+ let declarator;
277
+
278
+ declarator = parent;
279
+ while (declarator)
280
+ if (declarator.type == 'VariableDeclarator')
281
+ break
282
+ else
283
+ declarator = declarator.parent;
284
+ if (declarator?.type == 'VariableDeclarator' && nodeContains(declarator.init, ref.identifier))
285
+ sortPos = declarator.id ? declarator.id.range[0] - 0.4 : ref.identifier.range[0];
286
+ else
287
+ sortPos = ref.identifier.range[0];
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);
296
+ }
297
+ ref.cookshackNarrowestScopeItem = item2 || item1;
298
+ }
299
+ }
300
+ }
301
+
302
+ node.items.sort((a, b) => a.pos - b.pos);
303
+
304
+ return node
305
+ }
306
+
307
+ function checkScopeNode
308
+ (context, treeNode, reported, scopeToNode) {
309
+ let indent;
310
+
311
+ reported = reported || new Set;
312
+ indent = ' '.repeat(treeNode.prefix.split('.').length - 1);
313
+
314
+ for (let variable of treeNode.scope.variables) {
315
+ let defNode;
316
+
317
+ if (reported.has(variable))
318
+ continue
319
+ if (variable.defs.length == 0)
320
+ continue
321
+ if ([ 'Parameter', 'FunctionName', 'ImportBinding', 'CatchClause', 'ClassName' ].includes(variable.defs[0].type))
322
+ continue
323
+ if (variable.defs[0].node.parent?.parent?.type == 'ExportNamedDeclaration')
324
+ continue
325
+
326
+ defNode = variable.defs[0]?.name;
327
+ if (defNode) {
328
+ let defScope, narrowestScope, defNodePrefix;
329
+
330
+ defScope = getDefinitionScope(variable);
331
+ defNodePrefix = scopeToNode.get(defScope)?.prefix ?? '?';
332
+ trace$1(indent, '1 found decl scope of', variable.name + ':', defNodePrefix + ' ' + defScope.type.toUpperCase());
333
+
334
+ narrowestScope = getNarrowestScope(variable);
335
+ if (narrowestScope) {
336
+ let narrowestPrefix;
337
+
338
+ narrowestPrefix = scopeToNode.get(narrowestScope)?.prefix ?? '?';
339
+ trace$1(indent, '2 found narrowest scope of', variable.name + ':', narrowestPrefix + ' ' + narrowestScope?.type.toUpperCase());
340
+
341
+ markConditionalRefs(variable, scopeToNode, narrowestScope);
342
+
343
+ if (defScope == narrowestScope)
344
+ continue
345
+ trace$1(indent, '3', variable.name, 'could be moved to a narrower scope');
346
+
347
+ if (defScope.type == 'for') {
348
+ trace$1(indent, '4 exception:', variable.name, 'is in a for loop header');
349
+ continue
350
+ }
351
+ if (mayBeReadBeforeAnyWrite(variable, scopeToNode, narrowestScope)) {
352
+ trace$1(indent, '4 exception:', variable.name, 'mayBeReadBeforeAnyWrite');
353
+ continue
354
+ }
355
+
356
+ trace$1(indent, '5', variable.name, 'is too broad');
357
+
358
+ reported.add(variable);
359
+ context.report({
360
+ node: defNode,
361
+ messageId: 'tooBroad',
362
+ data: { name: variable.name }
363
+ });
364
+ }
365
+ }
366
+ }
367
+
368
+ for (let child of treeNode.children)
369
+ checkScopeNode(context, child, reported, scopeToNode);
370
+ }
371
+
372
+ function printTree
373
+ (node, siblingNum) {
374
+ let prefix, all, indent;
375
+
376
+ prefix = siblingNum == 0 ? node.prefix : node.prefix.split('.').slice(0, -1).join('.') + '.' + siblingNum;
377
+ indent = ' '.repeat(prefix.split('.').length - 1);
378
+ {
379
+ let name;
380
+
381
+ name = node.scope.block?.id?.name ?? node.scope.block?.parent?.key?.name;
382
+ print(indent + 'SCOPE ' + prefix + ' ' + node.scope.type.toUpperCase() + ' pos ' + scopeStart(node.scope) + (name ? ' name ' + name : ''));
383
+ }
384
+
385
+ all = [ ...node.items.map(i => ({ pos: i.pos, type: 'item', data: i })),
386
+ ...node.children.map((c, i) => ({ pos: scopeStart(c.scope), type: 'scope', data: c, sibling: i + 1 })) ];
387
+ all.sort((a, b) => a.pos - b.pos);
388
+
389
+ for (let entry of all)
390
+ if (entry.type == 'item')
391
+ print(indent
392
+ + ' ' + entry.data.type.padEnd(5)
393
+ + ' ' + entry.data.name
394
+ + (entry.data.ctx ? ' ' + entry.data.ctx : '').padEnd(3)
395
+ + (entry.data.isConditional ? 'C' : ' ').padEnd(2)
396
+ + 'pos ' + entry.data.pos);
397
+ else
398
+ printTree(entry.data, entry.sibling);
399
+ }
400
+
401
+ function createNarrowestScope
402
+ (context) {
403
+ let scopeManager;
404
+
405
+ clearPrintBuffer();
406
+ scopeManager = context.sourceCode.scopeManager;
407
+ if (scopeManager)
408
+ return {
409
+ 'Program:exit'() {
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
+
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
+
427
+ function createPositiveVibes
428
+ (context) {
429
+ return {
430
+ UnaryExpression(node) {
431
+ if (node.operator == '!')
432
+ context.report({ node,
433
+ messageId: 'positiveVibes' });
434
+ },
435
+ BinaryExpression(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
+
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
+
3
894
  let rules, languageOptions, plugins;
4
895
 
5
- plugins = { 'cookshack': { rules: { 'no-logical-not': { meta: { type: 'problem',
6
- docs: { description: 'Prevent !.' },
7
- messages: { logicalNot: 'Logical not used.',
8
- inequality: 'Inequality operator used.',
9
- strictInequality: 'Strict inequality operator used.' },
10
- schema: [] }, // options
11
- create(context) {
12
- return {
13
- UnaryExpression(node) {
14
- if (node.operator == '!')
15
- context.report({ node,
16
- messageId: 'logicalNot' });
17
- },
18
- BinaryExpression(node) {
19
- if (node.operator == '!=')
20
- context.report({ node,
21
- messageId: 'inequality' });
22
- else if (node.operator == '!==')
23
- context.report({ node,
24
- messageId: 'strictInequality' });
25
- }
26
- }
27
- } } } } };
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 } } };
28
903
 
29
904
  rules = {
30
905
  'array-bracket-newline': [ 'error', 'never' ],
@@ -55,7 +930,14 @@ rules = {
55
930
  { blankLine: 'never', prev: 'let', next: 'let' } ],
56
931
  'no-case-declarations': 'error',
57
932
  'no-global-assign': 'error',
58
- 'cookshack/no-logical-not': 'error',
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',
59
941
  'no-mixed-operators': 'error',
60
942
  'no-multi-spaces': 'error',
61
943
  'no-multiple-empty-lines': [ 'error', { max: 1, maxEOF: 0 } ],
@@ -92,4 +974,4 @@ var index = [ { ignores: [ 'TAGS.mjs' ] },
92
974
  plugins,
93
975
  rules } ];
94
976
 
95
- export { index as default, languageOptions, plugins, rules };
977
+ export { index as default, getPrintBuffer, languageOptions, plugins, rules };