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