@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
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { buildScopeTree } from './narrowest-scope.js'
|
|
2
|
+
|
|
3
|
+
let ostIdCounter, errorCount, ost
|
|
4
|
+
|
|
5
|
+
ostIdCounter = 0
|
|
6
|
+
errorCount = 0
|
|
7
|
+
ost = 0
|
|
8
|
+
|
|
9
|
+
function trace
|
|
10
|
+
(...args) {
|
|
11
|
+
if (0)
|
|
12
|
+
console.log(...args)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export
|
|
16
|
+
function lastOst
|
|
17
|
+
() {
|
|
18
|
+
return ost
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createInitBeforeUse(context) {
|
|
22
|
+
let scopeManager
|
|
23
|
+
|
|
24
|
+
scopeManager = context.sourceCode.scopeManager
|
|
25
|
+
if (scopeManager)
|
|
26
|
+
return {
|
|
27
|
+
'Program:exit'() {
|
|
28
|
+
let scopeToNode, astToTree, astToOst
|
|
29
|
+
|
|
30
|
+
errorCount = 0
|
|
31
|
+
scopeToNode = new Map
|
|
32
|
+
astToTree = new Map
|
|
33
|
+
astToOst = new Map
|
|
34
|
+
buildScopeTree(scopeManager.scopes[0], '1', scopeToNode, astToTree)
|
|
35
|
+
|
|
36
|
+
ostIdCounter = 0
|
|
37
|
+
ost = processAst(context.sourceCode.ast, null, astToTree, astToOst, '', new Set())
|
|
38
|
+
|
|
39
|
+
ostAnnotate(ost, astToOst, context)
|
|
40
|
+
|
|
41
|
+
ostCheck(ost, context)
|
|
42
|
+
|
|
43
|
+
trace('ERRORS: ' + errorCount)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isRegularDeclaration
|
|
49
|
+
(item) {
|
|
50
|
+
if (item.type == 'LET') {
|
|
51
|
+
if (item.defType == 'FunctionName' || item.defType == 'Parameter')
|
|
52
|
+
return 0
|
|
53
|
+
return 1
|
|
54
|
+
}
|
|
55
|
+
return 0
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function processAst(astNode, parentOst, astToTree, astToOst, indent, visited) {
|
|
59
|
+
if (astNode) {
|
|
60
|
+
let treeNode, scopeName, lets, reads, writes, ost, children
|
|
61
|
+
|
|
62
|
+
if (visited.has(astNode))
|
|
63
|
+
return
|
|
64
|
+
visited.add(astNode)
|
|
65
|
+
|
|
66
|
+
treeNode = astToTree.get(astNode) ?? parentOst?.treeNode
|
|
67
|
+
|
|
68
|
+
scopeName = treeNode?.scope ? `${treeNode.scope.type}` : 'no-scope'
|
|
69
|
+
if (treeNode?.scope?.block?.id?.name)
|
|
70
|
+
scopeName += `(${treeNode.scope.block.id.name})`
|
|
71
|
+
trace(`${indent}${astNode.type}`)
|
|
72
|
+
trace(`${indent} | scope: ${scopeName}`)
|
|
73
|
+
|
|
74
|
+
lets = []
|
|
75
|
+
reads = []
|
|
76
|
+
writes = []
|
|
77
|
+
|
|
78
|
+
for (let item of treeNode?.items ?? [])
|
|
79
|
+
if (isRegularDeclaration(item)) {
|
|
80
|
+
let scopeCreator
|
|
81
|
+
|
|
82
|
+
scopeCreator = treeNode?.scope?.block
|
|
83
|
+
if (scopeCreator && astNode == scopeCreator) {
|
|
84
|
+
lets.push({ item })
|
|
85
|
+
trace(`${indent} | LET ${item.name}:${item.varId}`)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (item.ref)
|
|
89
|
+
if (astNode == item.ref.identifier)
|
|
90
|
+
if (item.type == 'READ') {
|
|
91
|
+
reads.push({ item })
|
|
92
|
+
trace(`${indent} | READ ${item.name}:${item.varId}`)
|
|
93
|
+
}
|
|
94
|
+
else if (item.type == 'WRITE') {
|
|
95
|
+
writes.push({ item })
|
|
96
|
+
trace(`${indent} | WRITE ${item.name}:${item.varId}`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
ost = {
|
|
100
|
+
id: ostIdCounter++,
|
|
101
|
+
astNode,
|
|
102
|
+
treeNode,
|
|
103
|
+
scopeItems: treeNode?.items ?? [],
|
|
104
|
+
lets,
|
|
105
|
+
reads,
|
|
106
|
+
writes,
|
|
107
|
+
children: [],
|
|
108
|
+
fnDefOst: null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
astToOst.set(astNode, ost)
|
|
112
|
+
|
|
113
|
+
children = []
|
|
114
|
+
|
|
115
|
+
if (astNode.type == 'ForOfStatement' || astNode.type == 'ForInStatement') {
|
|
116
|
+
if (astNode.right)
|
|
117
|
+
children.push(astNode.right)
|
|
118
|
+
if (astNode.left)
|
|
119
|
+
children.push(astNode.left)
|
|
120
|
+
if (astNode.body)
|
|
121
|
+
children.push(astNode.body)
|
|
122
|
+
}
|
|
123
|
+
else if (astNode.type == 'ForStatement') {
|
|
124
|
+
if (astNode.init)
|
|
125
|
+
children.push(astNode.init)
|
|
126
|
+
if (astNode.test)
|
|
127
|
+
children.push(astNode.test)
|
|
128
|
+
if (astNode.update)
|
|
129
|
+
children.push(astNode.update)
|
|
130
|
+
if (astNode.body)
|
|
131
|
+
children.push(astNode.body)
|
|
132
|
+
}
|
|
133
|
+
else if (astNode.type == 'AssignmentExpression') {
|
|
134
|
+
if (astNode.right)
|
|
135
|
+
children.push(astNode.right)
|
|
136
|
+
if (astNode.left)
|
|
137
|
+
children.push(astNode.left)
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
if (astNode.body)
|
|
141
|
+
if (Array.isArray(astNode.body))
|
|
142
|
+
children.push(...astNode.body)
|
|
143
|
+
else
|
|
144
|
+
children.push(astNode.body)
|
|
145
|
+
if (astNode.consequent)
|
|
146
|
+
children.push(astNode.consequent)
|
|
147
|
+
if (astNode.alternate)
|
|
148
|
+
children.push(astNode.alternate)
|
|
149
|
+
if (astNode.block)
|
|
150
|
+
children.push(astNode.block)
|
|
151
|
+
if (astNode.expression)
|
|
152
|
+
children.push(astNode.expression)
|
|
153
|
+
if (astNode.callee)
|
|
154
|
+
children.push(astNode.callee)
|
|
155
|
+
if (astNode.object)
|
|
156
|
+
children.push(astNode.object)
|
|
157
|
+
if (astNode.property)
|
|
158
|
+
children.push(astNode.property)
|
|
159
|
+
if (astNode.init)
|
|
160
|
+
children.push(astNode.init)
|
|
161
|
+
if (astNode.id)
|
|
162
|
+
children.push(astNode.id)
|
|
163
|
+
if (astNode.declarations)
|
|
164
|
+
children.push(...astNode.declarations)
|
|
165
|
+
if (astNode.test)
|
|
166
|
+
children.push(astNode.test)
|
|
167
|
+
if (astNode.update)
|
|
168
|
+
children.push(astNode.update)
|
|
169
|
+
if (astNode.left)
|
|
170
|
+
children.push(astNode.left)
|
|
171
|
+
if (astNode.right)
|
|
172
|
+
children.push(astNode.right)
|
|
173
|
+
if (astNode.argument)
|
|
174
|
+
children.push(astNode.argument)
|
|
175
|
+
if (astNode.arguments)
|
|
176
|
+
children.push(...astNode.arguments)
|
|
177
|
+
if (astNode.elements)
|
|
178
|
+
children.push(...astNode.elements)
|
|
179
|
+
if (astNode.properties)
|
|
180
|
+
children.push(...astNode.properties)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (let child of children) {
|
|
184
|
+
let childOst
|
|
185
|
+
|
|
186
|
+
childOst = processAst(child, ost, astToTree, astToOst, indent + ' ', visited)
|
|
187
|
+
if (childOst)
|
|
188
|
+
ost.children.push(childOst)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return ost
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function ostAnnotate(ost, astToOst, context) {
|
|
196
|
+
if (ost) {
|
|
197
|
+
for (let letInfo of ost.lets) {
|
|
198
|
+
let writeOst
|
|
199
|
+
|
|
200
|
+
writeOst = findFirstWrite(ost, letInfo)
|
|
201
|
+
letInfo.firstWrite = writeOst
|
|
202
|
+
if (writeOst)
|
|
203
|
+
continue
|
|
204
|
+
if (letInfo.item.defType == 'ImportBinding')
|
|
205
|
+
continue
|
|
206
|
+
errorCount++
|
|
207
|
+
context.report({
|
|
208
|
+
node: letInfo.item.identifier,
|
|
209
|
+
messageId: 'mustInit',
|
|
210
|
+
data: { name: letInfo.item.name }
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (ost.astNode.type == 'CallExpression' && ost.astNode.callee?.type == 'Identifier')
|
|
215
|
+
for (let child of ost.children)
|
|
216
|
+
if (child.astNode == ost.astNode.callee && child.reads.length > 0) {
|
|
217
|
+
let readRef
|
|
218
|
+
|
|
219
|
+
readRef = child.reads[0].item.ref
|
|
220
|
+
if (readRef?.resolved) {
|
|
221
|
+
let variable
|
|
222
|
+
|
|
223
|
+
variable = readRef.resolved
|
|
224
|
+
if (variable.defs.length > 0) {
|
|
225
|
+
let fnDefAst
|
|
226
|
+
|
|
227
|
+
fnDefAst = variable.defs[0].node
|
|
228
|
+
if (fnDefAst) {
|
|
229
|
+
if (fnDefAst.init?.type == 'ArrowFunctionExpression' || fnDefAst.init?.type == 'FunctionExpression')
|
|
230
|
+
fnDefAst = fnDefAst.init
|
|
231
|
+
ost.fnDefOst = astToOst.get(fnDefAst)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
for (let child of ost.children)
|
|
238
|
+
ostAnnotate(child, astToOst, context)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function findFirstWrite(ost, letInfo) {
|
|
243
|
+
if (ost.astNode.type == 'FunctionDeclaration' || ost.astNode.type == 'ArrowFunctionExpression' || ost.astNode.type == 'FunctionExpression')
|
|
244
|
+
for (let child of ost.children)
|
|
245
|
+
if (child.astNode.type == 'BlockStatement')
|
|
246
|
+
return findFirstWriteInSubtree(child, letInfo)
|
|
247
|
+
return findFirstWriteInSubtree(ost, letInfo)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function findFirstWriteInSubtree(ost, letInfo) {
|
|
251
|
+
if (ost) {
|
|
252
|
+
if (ost.astNode.type == 'FunctionDeclaration' || ost.astNode.type == 'ArrowFunctionExpression' || ost.astNode.type == 'FunctionExpression')
|
|
253
|
+
return null
|
|
254
|
+
|
|
255
|
+
for (let writeInfo of ost.writes) {
|
|
256
|
+
let writeVar
|
|
257
|
+
|
|
258
|
+
writeVar = writeInfo.item.ref.resolved
|
|
259
|
+
if (writeVar == letInfo.item.variable)
|
|
260
|
+
return ost
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for (let child of ost.children) {
|
|
264
|
+
let result
|
|
265
|
+
|
|
266
|
+
result = findFirstWriteInSubtree(child, letInfo)
|
|
267
|
+
if (result)
|
|
268
|
+
return result
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return null
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function ostCheck(ost, context) {
|
|
276
|
+
if (ost) {
|
|
277
|
+
for (let letInfo of ost.lets)
|
|
278
|
+
if (letInfo.firstWrite)
|
|
279
|
+
walk2Start(ost, letInfo, context)
|
|
280
|
+
|
|
281
|
+
for (let child of ost.children)
|
|
282
|
+
ostCheck(child, context)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function walk2Start(node, letInfo, context) {
|
|
287
|
+
if (node.astNode.type == 'FunctionDeclaration')
|
|
288
|
+
for (let child of node.children)
|
|
289
|
+
if (child.astNode.type == 'BlockStatement')
|
|
290
|
+
return walk2(child, letInfo, context, new Set())
|
|
291
|
+
return walk2(node, letInfo, context, new Set())
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function walk2(node, letInfo, context, visited) {
|
|
295
|
+
if (node) {
|
|
296
|
+
if (node.astNode.type == 'FunctionDeclaration' || node.astNode.type == 'ArrowFunctionExpression' || node.astNode.type == 'FunctionExpression')
|
|
297
|
+
return false
|
|
298
|
+
|
|
299
|
+
if (node == letInfo.firstWrite) {
|
|
300
|
+
for (let readInfo of node.reads)
|
|
301
|
+
if (readInfo.item.ref.resolved == letInfo.item.variable) {
|
|
302
|
+
errorCount++
|
|
303
|
+
context.report({
|
|
304
|
+
node: readInfo.item.ref.identifier,
|
|
305
|
+
messageId: 'initBeforeUse',
|
|
306
|
+
data: { name: letInfo.item.name }
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
return true
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (node.astNode.type == 'CallExpression' && node.fnDefOst) {
|
|
313
|
+
let fnType
|
|
314
|
+
|
|
315
|
+
fnType = node.fnDefOst.astNode.type
|
|
316
|
+
|
|
317
|
+
if (fnType == 'FunctionDeclaration' || fnType == 'ArrowFunctionExpression' || fnType == 'FunctionExpression') {
|
|
318
|
+
let key
|
|
319
|
+
|
|
320
|
+
key = `${letInfo.item.name}:${node.fnDefOst.id}`
|
|
321
|
+
if (visited.has(key)) {
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
visited.add(key)
|
|
325
|
+
for (let child of node.fnDefOst.children)
|
|
326
|
+
if (child.astNode.type == 'BlockStatement' && walk2(child, letInfo, context, visited))
|
|
327
|
+
return true
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
for (let readInfo of node.reads)
|
|
333
|
+
if (readInfo.item.ref.resolved == letInfo.item.variable) {
|
|
334
|
+
errorCount++
|
|
335
|
+
context.report({
|
|
336
|
+
node: readInfo.item.ref.identifier,
|
|
337
|
+
messageId: 'initBeforeUse',
|
|
338
|
+
data: { name: letInfo.item.name }
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
for (let child of node.children)
|
|
343
|
+
if (walk2(child, letInfo, context, visited))
|
|
344
|
+
return true
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return false
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export
|
|
351
|
+
function ostString
|
|
352
|
+
(ost, indent) {
|
|
353
|
+
if (ost) {
|
|
354
|
+
let lets, reads, writes, fnDef, extra, scopeName, result
|
|
355
|
+
|
|
356
|
+
indent = indent || ''
|
|
357
|
+
|
|
358
|
+
lets = ost.lets.length ? ` ${ost.lets.map(l => `LET:${l.item.name}:${l.item.varId}` + (l.firstWrite ? ` (fw:${l.firstWrite.id})` : ' (no fw)')).join(', ')}` : ''
|
|
359
|
+
reads = ost.reads.length ? ` READ:${ost.reads.map(r => `${r.item.name}:${r.item.varId}`).join(', ')}` : ''
|
|
360
|
+
writes = ost.writes.length ? ` WRITE:${ost.writes.map(w => `${w.item.name}:${w.item.varId}`).join(', ')}` : ''
|
|
361
|
+
fnDef = ost.fnDefOst ? ` fnDefOst:${ost.fnDefOst.id}` : ''
|
|
362
|
+
extra = lets + reads + writes + fnDef
|
|
363
|
+
|
|
364
|
+
scopeName = ost.treeNode?.scope ? `${ost.treeNode.scope.type}` : 'no-scope'
|
|
365
|
+
if (ost.treeNode?.scope?.block?.id?.name)
|
|
366
|
+
scopeName += `(${ost.treeNode.scope.block.id.name})`
|
|
367
|
+
|
|
368
|
+
result = `${indent}${ost.id} ${ost.astNode.type} [${scopeName}]${extra}\n`
|
|
369
|
+
|
|
370
|
+
for (let child of ost.children)
|
|
371
|
+
result += ostString(child, indent + ' ')
|
|
372
|
+
|
|
373
|
+
return result
|
|
374
|
+
}
|
|
375
|
+
return ''
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export default {
|
|
379
|
+
meta: {
|
|
380
|
+
type: 'problem',
|
|
381
|
+
docs: { description: 'Warn when a variable is used before being explicitly initialized.' },
|
|
382
|
+
messages: { initBeforeUse: "'{{name}}' used before initialization.",
|
|
383
|
+
mustInit: "'{{name}}' must be initialized." },
|
|
384
|
+
schema: []
|
|
385
|
+
},
|
|
386
|
+
create: createInitBeforeUse
|
|
387
|
+
}
|