@cookshack/eslint-config 2.0.5 → 4.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/formatter.cjs +2 -1
- package/dist/formatter.js +2 -1
- package/dist/index.cjs +784 -156
- package/dist/index.js +784 -156
- package/formatter.js +2 -1
- package/index.js +80 -526
- package/package.json +2 -2
- package/plugins/always-let.js +15 -0
- package/plugins/fn-args-nl.js +47 -0
- package/plugins/fn-decl-block-start.js +37 -0
- package/plugins/indent-struct.js +145 -0
- package/plugins/init-before-use.js +382 -0
- package/plugins/narrowest-scope.js +472 -0
- package/plugins/positive-vibes.js +27 -0
- package/plugins/use-risky-equal.js +13 -0
- package/plugins/var-decl-block-start.js +37 -0
- package/test/rules/always-let.js +46 -0
- package/test/rules/fn-args-nl.js +114 -0
- package/test/rules/fn-decl-block-start.js +48 -0
- package/test/rules/indent-struct.js +198 -0
- package/test/rules/init-before-use.js +196 -0
- package/test/rules/narrowest-scope.js +36 -5
- package/test/rules/use-risky-equal.js +42 -0
- package/test/rules/var-decl-block-start.js +108 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
function FnArgsNl
|
|
2
|
+
(node, context) {
|
|
3
|
+
let nameLine, parenLine, nameEnd, i, newlines, parent
|
|
4
|
+
|
|
5
|
+
parent = node.parent
|
|
6
|
+
|
|
7
|
+
if (parent?.type == 'Property' && (parent.method || parent.kind == 'get' || parent.kind == 'set')) {
|
|
8
|
+
nameLine = parent.key.loc.start.line
|
|
9
|
+
nameEnd = parent.key.range[1]
|
|
10
|
+
}
|
|
11
|
+
else if (parent?.type == 'MethodDefinition') {
|
|
12
|
+
nameLine = parent.key.loc.start.line
|
|
13
|
+
nameEnd = parent.key.range[1]
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
nameLine = node.loc.start.line
|
|
17
|
+
nameEnd = node.range[0]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
i = nameEnd
|
|
21
|
+
newlines = 0
|
|
22
|
+
while (i < context.sourceCode.text.length) {
|
|
23
|
+
if (context.sourceCode.text[i] == '(')
|
|
24
|
+
break
|
|
25
|
+
if (context.sourceCode.text[i] == '\n')
|
|
26
|
+
newlines++
|
|
27
|
+
i++
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
parenLine = nameLine + newlines
|
|
31
|
+
|
|
32
|
+
if (parenLine - nameLine == 1)
|
|
33
|
+
return
|
|
34
|
+
context.report({ node, messageId: 'fnArgsNl' })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function create
|
|
38
|
+
(context) {
|
|
39
|
+
return { FunctionDeclaration: node => FnArgsNl(node, context),
|
|
40
|
+
FunctionExpression: node => FnArgsNl(node, context) }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default { meta: { type: 'suggestion',
|
|
44
|
+
docs: { description: 'Require function args on the line immediately after the function name.' },
|
|
45
|
+
messages: { fnArgsNl: 'Fn args must be on the line immediately after the function name.' },
|
|
46
|
+
schema: [] },
|
|
47
|
+
create }
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
function FunctionDeclaration
|
|
2
|
+
(context, node) {
|
|
3
|
+
let parent
|
|
4
|
+
|
|
5
|
+
for (parent = node.parent; parent; parent = parent.parent)
|
|
6
|
+
if (parent.type == 'BlockStatement')
|
|
7
|
+
break
|
|
8
|
+
|
|
9
|
+
if (parent) {
|
|
10
|
+
let idx
|
|
11
|
+
|
|
12
|
+
if (parent.parent?.type == 'CatchClause')
|
|
13
|
+
return
|
|
14
|
+
|
|
15
|
+
idx = parent.body.indexOf(node)
|
|
16
|
+
for (let i = 0; i < idx; i++) {
|
|
17
|
+
if (parent.body[i].type == 'VariableDeclaration'
|
|
18
|
+
|| parent.body[i].type == 'FunctionDeclaration'
|
|
19
|
+
|| parent.body[i].type == 'EmptyStatement')
|
|
20
|
+
continue
|
|
21
|
+
context.report({ node, messageId: 'fnDeclBlockStart' })
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function create
|
|
28
|
+
(context) {
|
|
29
|
+
return { FunctionDeclaration: node => FunctionDeclaration(context, node) }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export
|
|
33
|
+
default { meta: { type: 'suggestion',
|
|
34
|
+
docs: { description: 'Require function declarations to be at the start of the block.' },
|
|
35
|
+
messages: { fnDeclBlockStart: 'FnDecl must be the start the block (after VarDecls).' },
|
|
36
|
+
schema: [] },
|
|
37
|
+
create }
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
function trace
|
|
2
|
+
(...args) {
|
|
3
|
+
if (0)
|
|
4
|
+
console.log(...args)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function unit
|
|
8
|
+
(context) {
|
|
9
|
+
return context.options[0] ?? 2
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function checkObjectExpressionProperties
|
|
13
|
+
(properties, node, context) {
|
|
14
|
+
let firstProp, sourceCode, lastProp, lastPropEnd, closingBrace, firstPropLine, firstPropCol, afterLastProp, closingLine, lastPropValueEndLine
|
|
15
|
+
|
|
16
|
+
sourceCode = context.sourceCode.text
|
|
17
|
+
firstProp = properties[0]
|
|
18
|
+
lastProp = properties[properties.length - 1]
|
|
19
|
+
firstPropLine = firstProp.loc.start.line
|
|
20
|
+
firstPropCol = firstProp.loc.start.column
|
|
21
|
+
lastPropEnd = lastProp.range[1]
|
|
22
|
+
closingBrace = sourceCode.indexOf('}', lastPropEnd)
|
|
23
|
+
closingLine = sourceCode.slice(0, closingBrace).split('\n').length
|
|
24
|
+
afterLastProp = sourceCode.slice(lastPropEnd, closingBrace)
|
|
25
|
+
lastPropValueEndLine = sourceCode.slice(0, lastPropEnd).split('\n').length
|
|
26
|
+
|
|
27
|
+
trace('ALGORITHM 1: Single-line objects: always valid (no check needed)')
|
|
28
|
+
trace('CHECK POINT 1: is single-line? firstPropLine=%d, braceLine=%d, closingLine=%d', firstPropLine, node.loc.start.line, closingLine)
|
|
29
|
+
if (firstPropLine == node.loc.start.line && closingLine == firstPropLine)
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
trace('ALGORITHM 2: When first property is on a line after {: must use 1 indent unit from the column where { appears')
|
|
33
|
+
trace('CHECK POINT 2: is firstPropLine (%d) != braceLine (%d)?', firstPropLine, node.loc.start.line)
|
|
34
|
+
if (firstPropLine == node.loc.start.line) {
|
|
35
|
+
// first property is on brace line
|
|
36
|
+
}
|
|
37
|
+
else if (firstPropCol == node.loc.start.column + unit(context)) {
|
|
38
|
+
// ok
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
trace('CHECK 2 FAIL')
|
|
42
|
+
context.report({ node: firstProp, messageId: 'indentStruct' })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
trace('ALGORITHM 3: Multi-line: all properties must align to first propertys column')
|
|
46
|
+
trace('CHECK POINT 3: checking property alignment, firstPropCol=%d', firstPropCol)
|
|
47
|
+
for (let i = 1; i < properties.length; i++) {
|
|
48
|
+
let prop
|
|
49
|
+
|
|
50
|
+
prop = properties[i]
|
|
51
|
+
if (prop.loc.start.column == firstPropCol) {
|
|
52
|
+
// ok
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
trace('CHECK 3 FAIL')
|
|
56
|
+
context.report({ node: prop, messageId: 'indentStruct' })
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
trace('ALGORITHM 4: } on a line following the last prop value must align with {')
|
|
61
|
+
trace('CHECK POINT 4: is closing on a line following last prop value? closingLine=%d, lastPropValueEndLine=%d', closingLine, lastPropValueEndLine)
|
|
62
|
+
if (closingLine > lastPropValueEndLine) {
|
|
63
|
+
let braceCol, closingCol
|
|
64
|
+
|
|
65
|
+
braceCol = node.loc.start.column
|
|
66
|
+
closingCol = node.loc.end.column - 1
|
|
67
|
+
trace('CHECK 4: braceCol=%d, closingCol=%d', braceCol, closingCol)
|
|
68
|
+
if (closingCol == braceCol) {
|
|
69
|
+
// ok
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
trace('CHECK 4 FAIL')
|
|
73
|
+
context.report({ node, messageId: 'indentStruct' })
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
trace('ALGORITHM 5: } on same line as last prop value must have a space before it')
|
|
78
|
+
trace('CHECK POINT 5: is closing on same line as last prop value? closingLine=%d, lastPropValueEndLine=%d', closingLine, lastPropValueEndLine)
|
|
79
|
+
if (closingLine == lastPropValueEndLine) {
|
|
80
|
+
trace('CHECK 5: lastPropEnd=%d, closingBrace=%d, afterLastProp=%j', lastPropEnd, closingBrace, afterLastProp)
|
|
81
|
+
if (afterLastProp == ' ') {
|
|
82
|
+
// ok
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
trace('CHECK 5 FAIL')
|
|
86
|
+
context.report({ node, messageId: 'indentStruct' })
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
trace('ALGORITHM 6: When a param list is on a line after the field name, it must align with the field name')
|
|
91
|
+
trace('CHECK POINT 6: checking method param alignment')
|
|
92
|
+
for (let prop of properties)
|
|
93
|
+
if (prop.method) {
|
|
94
|
+
let keyLine, keyEnd, i, newlines, parenLine
|
|
95
|
+
|
|
96
|
+
keyLine = prop.key.loc.start.line
|
|
97
|
+
keyEnd = prop.key.range[1]
|
|
98
|
+
i = keyEnd
|
|
99
|
+
newlines = 0
|
|
100
|
+
|
|
101
|
+
while (i < sourceCode.length) {
|
|
102
|
+
if (sourceCode[i] == '(')
|
|
103
|
+
break
|
|
104
|
+
if (sourceCode[i] == '\n')
|
|
105
|
+
newlines++
|
|
106
|
+
i++
|
|
107
|
+
}
|
|
108
|
+
parenLine = keyLine + newlines
|
|
109
|
+
if (parenLine > keyLine) {
|
|
110
|
+
let parenCol
|
|
111
|
+
|
|
112
|
+
parenCol = i - sourceCode.lastIndexOf('\n', i)
|
|
113
|
+
if (prop.value.type == 'FunctionExpression' && prop.value.async) {
|
|
114
|
+
// async methods can have whitespace between key and paren
|
|
115
|
+
}
|
|
116
|
+
else if (parenCol - 1 == prop.key.loc.start.column) {
|
|
117
|
+
// ok
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
trace('CHECK 6 FAIL')
|
|
121
|
+
context.report({ node: prop, messageId: 'indentStruct' })
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function checkObjectExpression
|
|
128
|
+
(node, context) {
|
|
129
|
+
let properties
|
|
130
|
+
|
|
131
|
+
properties = node.properties
|
|
132
|
+
if (properties.length)
|
|
133
|
+
checkObjectExpressionProperties(properties, node, context)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function create
|
|
137
|
+
(context) {
|
|
138
|
+
return { ObjectExpression: node => checkObjectExpression(node, context) }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export default { meta: { type: 'suggestion',
|
|
142
|
+
docs: { description: 'Struct alignment rules.' },
|
|
143
|
+
messages: { indentStruct: 'Indent structure' },
|
|
144
|
+
schema: [] },
|
|
145
|
+
create }
|
|
@@ -0,0 +1,382 @@
|
|
|
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
|
|
22
|
+
(context) {
|
|
23
|
+
let scopeManager
|
|
24
|
+
|
|
25
|
+
scopeManager = context.sourceCode.scopeManager
|
|
26
|
+
if (scopeManager)
|
|
27
|
+
return { 'Program:exit'
|
|
28
|
+
() {
|
|
29
|
+
let scopeToNode, astToTree, astToOst
|
|
30
|
+
|
|
31
|
+
errorCount = 0
|
|
32
|
+
scopeToNode = new Map
|
|
33
|
+
astToTree = new Map
|
|
34
|
+
astToOst = new Map
|
|
35
|
+
buildScopeTree(scopeManager.scopes[0], '1', scopeToNode, astToTree)
|
|
36
|
+
|
|
37
|
+
ostIdCounter = 0
|
|
38
|
+
ost = processAst(context.sourceCode.ast, null, astToTree, astToOst, '', new Set())
|
|
39
|
+
|
|
40
|
+
ostAnnotate(ost, astToOst, context)
|
|
41
|
+
|
|
42
|
+
ostCheck(ost, context)
|
|
43
|
+
|
|
44
|
+
trace('ERRORS: ' + errorCount)
|
|
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
|
|
59
|
+
(astNode, parentOst, astToTree, astToOst, indent, visited) {
|
|
60
|
+
if (astNode) {
|
|
61
|
+
let treeNode, scopeName, lets, reads, writes, ost, children
|
|
62
|
+
|
|
63
|
+
if (visited.has(astNode))
|
|
64
|
+
return
|
|
65
|
+
visited.add(astNode)
|
|
66
|
+
|
|
67
|
+
treeNode = astToTree.get(astNode) ?? parentOst?.treeNode
|
|
68
|
+
|
|
69
|
+
scopeName = treeNode?.scope ? `${treeNode.scope.type}` : 'no-scope'
|
|
70
|
+
if (treeNode?.scope?.block?.id?.name)
|
|
71
|
+
scopeName += `(${treeNode.scope.block.id.name})`
|
|
72
|
+
trace(`${indent}${astNode.type}`)
|
|
73
|
+
trace(`${indent} | scope: ${scopeName}`)
|
|
74
|
+
|
|
75
|
+
lets = []
|
|
76
|
+
reads = []
|
|
77
|
+
writes = []
|
|
78
|
+
|
|
79
|
+
for (let item of treeNode?.items ?? [])
|
|
80
|
+
if (isRegularDeclaration(item)) {
|
|
81
|
+
let scopeCreator
|
|
82
|
+
|
|
83
|
+
scopeCreator = treeNode?.scope?.block
|
|
84
|
+
if (scopeCreator && astNode == scopeCreator) {
|
|
85
|
+
lets.push({ item })
|
|
86
|
+
trace(`${indent} | LET ${item.name}:${item.varId}`)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (item.ref)
|
|
90
|
+
if (astNode == item.ref.identifier)
|
|
91
|
+
if (item.type == 'READ') {
|
|
92
|
+
reads.push({ item })
|
|
93
|
+
trace(`${indent} | READ ${item.name}:${item.varId}`)
|
|
94
|
+
}
|
|
95
|
+
else if (item.type == 'WRITE') {
|
|
96
|
+
writes.push({ item })
|
|
97
|
+
trace(`${indent} | WRITE ${item.name}:${item.varId}`)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
ost = { id: ostIdCounter++,
|
|
101
|
+
astNode,
|
|
102
|
+
treeNode,
|
|
103
|
+
scopeItems: treeNode?.items ?? [],
|
|
104
|
+
lets,
|
|
105
|
+
reads,
|
|
106
|
+
writes,
|
|
107
|
+
children: [],
|
|
108
|
+
fnDefOst: null }
|
|
109
|
+
|
|
110
|
+
astToOst.set(astNode, ost)
|
|
111
|
+
|
|
112
|
+
children = []
|
|
113
|
+
|
|
114
|
+
if (astNode.type == 'ForOfStatement' || astNode.type == 'ForInStatement') {
|
|
115
|
+
if (astNode.right)
|
|
116
|
+
children.push(astNode.right)
|
|
117
|
+
if (astNode.left)
|
|
118
|
+
children.push(astNode.left)
|
|
119
|
+
if (astNode.body)
|
|
120
|
+
children.push(astNode.body)
|
|
121
|
+
}
|
|
122
|
+
else if (astNode.type == 'ForStatement') {
|
|
123
|
+
if (astNode.init)
|
|
124
|
+
children.push(astNode.init)
|
|
125
|
+
if (astNode.test)
|
|
126
|
+
children.push(astNode.test)
|
|
127
|
+
if (astNode.update)
|
|
128
|
+
children.push(astNode.update)
|
|
129
|
+
if (astNode.body)
|
|
130
|
+
children.push(astNode.body)
|
|
131
|
+
}
|
|
132
|
+
else if (astNode.type == 'AssignmentExpression') {
|
|
133
|
+
if (astNode.right)
|
|
134
|
+
children.push(astNode.right)
|
|
135
|
+
if (astNode.left)
|
|
136
|
+
children.push(astNode.left)
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
if (astNode.body)
|
|
140
|
+
if (Array.isArray(astNode.body))
|
|
141
|
+
children.push(...astNode.body)
|
|
142
|
+
else
|
|
143
|
+
children.push(astNode.body)
|
|
144
|
+
if (astNode.consequent)
|
|
145
|
+
children.push(astNode.consequent)
|
|
146
|
+
if (astNode.alternate)
|
|
147
|
+
children.push(astNode.alternate)
|
|
148
|
+
if (astNode.block)
|
|
149
|
+
children.push(astNode.block)
|
|
150
|
+
if (astNode.expression)
|
|
151
|
+
children.push(astNode.expression)
|
|
152
|
+
if (astNode.callee)
|
|
153
|
+
children.push(astNode.callee)
|
|
154
|
+
if (astNode.object)
|
|
155
|
+
children.push(astNode.object)
|
|
156
|
+
if (astNode.property)
|
|
157
|
+
children.push(astNode.property)
|
|
158
|
+
if (astNode.init)
|
|
159
|
+
children.push(astNode.init)
|
|
160
|
+
if (astNode.id)
|
|
161
|
+
children.push(astNode.id)
|
|
162
|
+
if (astNode.declarations)
|
|
163
|
+
children.push(...astNode.declarations)
|
|
164
|
+
if (astNode.test)
|
|
165
|
+
children.push(astNode.test)
|
|
166
|
+
if (astNode.update)
|
|
167
|
+
children.push(astNode.update)
|
|
168
|
+
if (astNode.left)
|
|
169
|
+
children.push(astNode.left)
|
|
170
|
+
if (astNode.right)
|
|
171
|
+
children.push(astNode.right)
|
|
172
|
+
if (astNode.argument)
|
|
173
|
+
children.push(astNode.argument)
|
|
174
|
+
if (astNode.arguments)
|
|
175
|
+
children.push(...astNode.arguments)
|
|
176
|
+
if (astNode.elements)
|
|
177
|
+
children.push(...astNode.elements)
|
|
178
|
+
if (astNode.properties)
|
|
179
|
+
children.push(...astNode.properties)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
for (let child of children) {
|
|
183
|
+
let childOst
|
|
184
|
+
|
|
185
|
+
childOst = processAst(child, ost, astToTree, astToOst, indent + ' ', visited)
|
|
186
|
+
if (childOst)
|
|
187
|
+
ost.children.push(childOst)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return ost
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function ostAnnotate
|
|
195
|
+
(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({ node: letInfo.item.identifier,
|
|
208
|
+
messageId: 'mustInit',
|
|
209
|
+
data: { name: letInfo.item.name } })
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (ost.astNode.type == 'CallExpression' && ost.astNode.callee?.type == 'Identifier')
|
|
213
|
+
for (let child of ost.children)
|
|
214
|
+
if (child.astNode == ost.astNode.callee && child.reads.length > 0) {
|
|
215
|
+
let readRef
|
|
216
|
+
|
|
217
|
+
readRef = child.reads[0].item.ref
|
|
218
|
+
if (readRef?.resolved) {
|
|
219
|
+
let variable
|
|
220
|
+
|
|
221
|
+
variable = readRef.resolved
|
|
222
|
+
if (variable.defs.length > 0) {
|
|
223
|
+
let fnDefAst
|
|
224
|
+
|
|
225
|
+
fnDefAst = variable.defs[0].node
|
|
226
|
+
if (fnDefAst) {
|
|
227
|
+
if (fnDefAst.init?.type == 'ArrowFunctionExpression' || fnDefAst.init?.type == 'FunctionExpression')
|
|
228
|
+
fnDefAst = fnDefAst.init
|
|
229
|
+
ost.fnDefOst = astToOst.get(fnDefAst)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
for (let child of ost.children)
|
|
236
|
+
ostAnnotate(child, astToOst, context)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function findFirstWrite
|
|
241
|
+
(ost, letInfo) {
|
|
242
|
+
if (ost.astNode.type == 'FunctionDeclaration' || ost.astNode.type == 'ArrowFunctionExpression' || ost.astNode.type == 'FunctionExpression')
|
|
243
|
+
for (let child of ost.children)
|
|
244
|
+
if (child.astNode.type == 'BlockStatement')
|
|
245
|
+
return findFirstWriteInSubtree(child, letInfo)
|
|
246
|
+
return findFirstWriteInSubtree(ost, letInfo)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function findFirstWriteInSubtree
|
|
250
|
+
(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
|
|
276
|
+
(ost, context) {
|
|
277
|
+
if (ost) {
|
|
278
|
+
for (let letInfo of ost.lets)
|
|
279
|
+
if (letInfo.firstWrite)
|
|
280
|
+
walk2Start(ost, letInfo, context)
|
|
281
|
+
|
|
282
|
+
for (let child of ost.children)
|
|
283
|
+
ostCheck(child, context)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function walk2Start
|
|
288
|
+
(node, letInfo, context) {
|
|
289
|
+
if (node.astNode.type == 'FunctionDeclaration')
|
|
290
|
+
for (let child of node.children)
|
|
291
|
+
if (child.astNode.type == 'BlockStatement')
|
|
292
|
+
return walk2(child, letInfo, context, new Set())
|
|
293
|
+
return walk2(node, letInfo, context, new Set())
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function walk2
|
|
297
|
+
(node, letInfo, context, visited) {
|
|
298
|
+
if (node) {
|
|
299
|
+
if (node.astNode.type == 'FunctionDeclaration' || node.astNode.type == 'ArrowFunctionExpression' || node.astNode.type == 'FunctionExpression')
|
|
300
|
+
return false
|
|
301
|
+
|
|
302
|
+
if (node == letInfo.firstWrite) {
|
|
303
|
+
for (let readInfo of node.reads)
|
|
304
|
+
if (readInfo.item.ref.resolved == letInfo.item.variable) {
|
|
305
|
+
errorCount++
|
|
306
|
+
context.report({ node: readInfo.item.ref.identifier,
|
|
307
|
+
messageId: 'initBeforeUse',
|
|
308
|
+
data: { name: letInfo.item.name } })
|
|
309
|
+
}
|
|
310
|
+
return true
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (node.astNode.type == 'CallExpression' && node.fnDefOst) {
|
|
314
|
+
let fnType
|
|
315
|
+
|
|
316
|
+
fnType = node.fnDefOst.astNode.type
|
|
317
|
+
|
|
318
|
+
if (fnType == 'FunctionDeclaration' || fnType == 'ArrowFunctionExpression' || fnType == 'FunctionExpression') {
|
|
319
|
+
let key
|
|
320
|
+
|
|
321
|
+
key = `${letInfo.item.name}:${node.fnDefOst.id}`
|
|
322
|
+
if (visited.has(key)) {
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
visited.add(key)
|
|
326
|
+
for (let child of node.fnDefOst.children)
|
|
327
|
+
if (child.astNode.type == 'BlockStatement' && walk2(child, letInfo, context, visited))
|
|
328
|
+
return true
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
for (let readInfo of node.reads)
|
|
334
|
+
if (readInfo.item.ref.resolved == letInfo.item.variable) {
|
|
335
|
+
errorCount++
|
|
336
|
+
context.report({ node: readInfo.item.ref.identifier,
|
|
337
|
+
messageId: 'initBeforeUse',
|
|
338
|
+
data: { name: letInfo.item.name } })
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
for (let child of node.children)
|
|
342
|
+
if (walk2(child, letInfo, context, visited))
|
|
343
|
+
return true
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return false
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export
|
|
350
|
+
function ostString
|
|
351
|
+
(ost, indent) {
|
|
352
|
+
if (ost) {
|
|
353
|
+
let lets, reads, writes, fnDef, extra, scopeName, result
|
|
354
|
+
|
|
355
|
+
indent = indent || ''
|
|
356
|
+
|
|
357
|
+
lets = ost.lets.length ? ` ${ost.lets.map(l => `LET:${l.item.name}:${l.item.varId}` + (l.firstWrite ? ` (fw:${l.firstWrite.id})` : ' (no fw)')).join(', ')}` : ''
|
|
358
|
+
reads = ost.reads.length ? ` READ:${ost.reads.map(r => `${r.item.name}:${r.item.varId}`).join(', ')}` : ''
|
|
359
|
+
writes = ost.writes.length ? ` WRITE:${ost.writes.map(w => `${w.item.name}:${w.item.varId}`).join(', ')}` : ''
|
|
360
|
+
fnDef = ost.fnDefOst ? ` fnDefOst:${ost.fnDefOst.id}` : ''
|
|
361
|
+
extra = lets + reads + writes + fnDef
|
|
362
|
+
|
|
363
|
+
scopeName = ost.treeNode?.scope ? `${ost.treeNode.scope.type}` : 'no-scope'
|
|
364
|
+
if (ost.treeNode?.scope?.block?.id?.name)
|
|
365
|
+
scopeName += `(${ost.treeNode.scope.block.id.name})`
|
|
366
|
+
|
|
367
|
+
result = `${indent}${ost.id} ${ost.astNode.type} [${scopeName}]${extra}\n`
|
|
368
|
+
|
|
369
|
+
for (let child of ost.children)
|
|
370
|
+
result += ostString(child, indent + ' ')
|
|
371
|
+
|
|
372
|
+
return result
|
|
373
|
+
}
|
|
374
|
+
return ''
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export default { meta: { type: 'problem',
|
|
378
|
+
docs: { description: 'Warn when a variable is used before being explicitly initialized.' },
|
|
379
|
+
messages: { initBeforeUse: "'{{name}}' used before initialization.",
|
|
380
|
+
mustInit: "'{{name}}' must be initialized." },
|
|
381
|
+
schema: [] },
|
|
382
|
+
create: createInitBeforeUse }
|