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