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