@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.
@@ -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
+ }