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