@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/index.js
CHANGED
|
@@ -1,30 +1,24 @@
|
|
|
1
1
|
import globals from 'globals'
|
|
2
|
+
import narrowestScopePlugin from './plugins/narrowest-scope.js'
|
|
3
|
+
import { getPrintBuffer } from './plugins/narrowest-scope.js'
|
|
4
|
+
import positiveVibesPlugin from './plugins/positive-vibes.js'
|
|
5
|
+
import useRiskyEqualPlugin from './plugins/use-risky-equal.js'
|
|
6
|
+
import alwaysLetPlugin from './plugins/always-let.js'
|
|
7
|
+
import initBeforeUsePlugin from './plugins/init-before-use.js'
|
|
8
|
+
import varDeclBlockStartPlugin from './plugins/var-decl-block-start.js'
|
|
9
|
+
import fnDeclBlockStartPlugin from './plugins/fn-decl-block-start.js'
|
|
10
|
+
|
|
11
|
+
export { getPrintBuffer }
|
|
2
12
|
|
|
3
13
|
export let rules, languageOptions, plugins
|
|
4
14
|
|
|
5
|
-
plugins = { 'cookshack': { rules: { '
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return {
|
|
13
|
-
UnaryExpression(node) {
|
|
14
|
-
if (node.operator == '!')
|
|
15
|
-
context.report({ node,
|
|
16
|
-
messageId: 'logicalNot' })
|
|
17
|
-
},
|
|
18
|
-
BinaryExpression(node) {
|
|
19
|
-
if (node.operator == '!=')
|
|
20
|
-
context.report({ node,
|
|
21
|
-
messageId: 'inequality' })
|
|
22
|
-
else if (node.operator == '!==')
|
|
23
|
-
context.report({ node,
|
|
24
|
-
messageId: 'strictInequality' })
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
} } } } }
|
|
15
|
+
plugins = { 'cookshack': { rules: { 'positive-vibes': positiveVibesPlugin,
|
|
16
|
+
'narrowest-scope': narrowestScopePlugin,
|
|
17
|
+
'use-risky-equal': useRiskyEqualPlugin,
|
|
18
|
+
'always-let': alwaysLetPlugin,
|
|
19
|
+
'init-before-use': initBeforeUsePlugin,
|
|
20
|
+
'var-decl-block-start': varDeclBlockStartPlugin,
|
|
21
|
+
'fn-decl-block-start': fnDeclBlockStartPlugin } } }
|
|
28
22
|
|
|
29
23
|
rules = {
|
|
30
24
|
'array-bracket-newline': [ 'error', 'never' ],
|
|
@@ -55,7 +49,14 @@ rules = {
|
|
|
55
49
|
{ blankLine: 'never', prev: 'let', next: 'let' } ],
|
|
56
50
|
'no-case-declarations': 'error',
|
|
57
51
|
'no-global-assign': 'error',
|
|
58
|
-
'cookshack/
|
|
52
|
+
'cookshack/positive-vibes': 'error',
|
|
53
|
+
'cookshack/narrowest-scope': 'error',
|
|
54
|
+
'cookshack/use-risky-equal': 'error',
|
|
55
|
+
'cookshack/always-let': 'error',
|
|
56
|
+
// using the implicit inititialization to undefined fits better
|
|
57
|
+
//'cookshack/init-before-use': 'error',
|
|
58
|
+
'cookshack/var-decl-block-start': 'error',
|
|
59
|
+
'cookshack/fn-decl-block-start': 'error',
|
|
59
60
|
'no-mixed-operators': 'error',
|
|
60
61
|
'no-multi-spaces': 'error',
|
|
61
62
|
'no-multiple-empty-lines': [ 'error', { max: 1, maxEOF: 0 } ],
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cookshack/eslint-config",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "ESLint config for Cookshack projects",
|
|
5
5
|
"homepage": "https://git.sr.ht/~mattmundell/eslint-config",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"check": "npx eslint --no-warn-ignored -f ./formatter.js -c ./index.js *.js",
|
|
9
|
-
"test": "
|
|
8
|
+
"check": "npx eslint --no-warn-ignored -f ./formatter.js -c ./index.js *.js test/rules/*.js plugins/*.js",
|
|
9
|
+
"test": "mocha test/**/*.js",
|
|
10
10
|
"prepare": "husky && rollup -c"
|
|
11
11
|
},
|
|
12
12
|
"main": "dist/index.cjs",
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
"globals": "^15.10.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
+
"diff": "^8.0.4",
|
|
29
30
|
"husky": "^9.1.7",
|
|
31
|
+
"mocha": "^11.7.5",
|
|
30
32
|
"rollup": "^4.27.2"
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function create
|
|
2
|
+
(context) {
|
|
3
|
+
return { VariableDeclaration(node) {
|
|
4
|
+
if (node.kind == 'const' || node.kind == 'var')
|
|
5
|
+
context.report({ node, messageId: 'useLet' })
|
|
6
|
+
} }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export
|
|
10
|
+
default { meta: { type: 'problem',
|
|
11
|
+
docs: { description: 'Enforce use of let instead of const or var.' },
|
|
12
|
+
messages: { useLet: 'Use let.' },
|
|
13
|
+
schema: [] },
|
|
14
|
+
create }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function
|
|
2
|
+
FunctionDeclaration
|
|
3
|
+
(context, node) {
|
|
4
|
+
let parent
|
|
5
|
+
|
|
6
|
+
for (parent = node.parent; parent; parent = parent.parent)
|
|
7
|
+
if (parent.type == 'BlockStatement')
|
|
8
|
+
break
|
|
9
|
+
|
|
10
|
+
if (parent) {
|
|
11
|
+
let idx
|
|
12
|
+
|
|
13
|
+
if (parent.parent?.type == 'CatchClause')
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
idx = parent.body.indexOf(node)
|
|
17
|
+
for (let i = 0; i < idx; i++) {
|
|
18
|
+
if (parent.body[i].type == 'VariableDeclaration'
|
|
19
|
+
|| parent.body[i].type == 'FunctionDeclaration'
|
|
20
|
+
|| parent.body[i].type == 'EmptyStatement')
|
|
21
|
+
continue
|
|
22
|
+
context.report({ node, messageId: 'fnDeclBlockStart' })
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function create
|
|
29
|
+
(context) {
|
|
30
|
+
return { FunctionDeclaration: node => FunctionDeclaration(context, node) }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export
|
|
34
|
+
default { meta: { type: 'suggestion',
|
|
35
|
+
docs: { description: 'Require function declarations to be at the start of the block.' },
|
|
36
|
+
messages: { fnDeclBlockStart: 'FnDecl must be the start the block (after VarDecls).' },
|
|
37
|
+
schema: [] },
|
|
38
|
+
create }
|
|
@@ -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
|
+
}
|