@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
package/index.js
CHANGED
|
@@ -1,471 +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'
|
|
2
10
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
let printBuffer
|
|
6
|
-
|
|
7
|
-
printBuffer = []
|
|
8
|
-
|
|
9
|
-
function print
|
|
10
|
-
(...args) {
|
|
11
|
-
//console.log(args.join(' '))
|
|
12
|
-
printBuffer.push(args.join(' '))
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function trace
|
|
16
|
-
(...args) {
|
|
17
|
-
if (0)
|
|
18
|
-
console.log('TRACE', ...args)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export
|
|
22
|
-
function getPrintBuffer
|
|
23
|
-
() {
|
|
24
|
-
return printBuffer.join('\n')
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function clearPrintBuffer
|
|
28
|
-
() {
|
|
29
|
-
printBuffer = []
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function getNarrowestScope
|
|
33
|
-
(variable) {
|
|
34
|
-
let common
|
|
35
|
-
|
|
36
|
-
common = null
|
|
37
|
-
for (let ref of variable.references) {
|
|
38
|
-
if (variable.defs.some(def => def.name === ref.identifier))
|
|
39
|
-
continue
|
|
40
|
-
if (ref.from)
|
|
41
|
-
if (common)
|
|
42
|
-
common = getCommonAncestor(common, ref.from)
|
|
43
|
-
else
|
|
44
|
-
common = ref.from
|
|
45
|
-
}
|
|
46
|
-
return common
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function getCommonAncestor
|
|
50
|
-
(scope1, scope2) {
|
|
51
|
-
let ancestors, s
|
|
52
|
-
|
|
53
|
-
ancestors = []
|
|
54
|
-
s = scope1
|
|
55
|
-
while (s) {
|
|
56
|
-
if (s.type == 'global')
|
|
57
|
-
break
|
|
58
|
-
ancestors.push(s)
|
|
59
|
-
s = s.upper
|
|
60
|
-
}
|
|
61
|
-
s = scope2
|
|
62
|
-
while (s) {
|
|
63
|
-
if (s.type == 'global')
|
|
64
|
-
break
|
|
65
|
-
if (ancestors.includes(s))
|
|
66
|
-
return s
|
|
67
|
-
s = s.upper
|
|
68
|
-
}
|
|
69
|
-
return scope1
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function getDefinitionScope
|
|
73
|
-
(variable) {
|
|
74
|
-
return variable.scope
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function isWriteRef
|
|
78
|
-
(ref) {
|
|
79
|
-
let parent
|
|
80
|
-
|
|
81
|
-
parent = ref.identifier.parent
|
|
82
|
-
if (parent) {
|
|
83
|
-
if (parent.type == 'AssignmentExpression' && parent.left == ref.identifier)
|
|
84
|
-
return 1
|
|
85
|
-
if (parent.type == 'UpdateExpression')
|
|
86
|
-
return 1
|
|
87
|
-
if (parent.type == 'VariableDeclarator' && parent.id == ref.identifier)
|
|
88
|
-
return 1
|
|
89
|
-
}
|
|
90
|
-
return 0
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function isReadRef
|
|
94
|
-
(ref) {
|
|
95
|
-
if (isWriteRef(ref))
|
|
96
|
-
return 0
|
|
97
|
-
return 1
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function getConditionalContext
|
|
101
|
-
(ref) {
|
|
102
|
-
let node, prevNode, scopeBlock
|
|
103
|
-
|
|
104
|
-
scopeBlock = ref.from.block
|
|
105
|
-
prevNode = ref.identifier
|
|
106
|
-
node = ref.identifier.parent
|
|
107
|
-
while (node) {
|
|
108
|
-
if (node === scopeBlock)
|
|
109
|
-
break
|
|
110
|
-
if (node.type === 'IfStatement')
|
|
111
|
-
if (prevNode === node.test || nodeContains(node.test, prevNode))
|
|
112
|
-
prevNode = node
|
|
113
|
-
else
|
|
114
|
-
return 'B'
|
|
115
|
-
else if ([ 'WhileStatement', 'DoWhileStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement', 'SwitchStatement' ].includes(node.type))
|
|
116
|
-
if (prevNode === node.test || nodeContains(node.test, prevNode))
|
|
117
|
-
prevNode = node
|
|
118
|
-
else
|
|
119
|
-
return 'B'
|
|
120
|
-
else
|
|
121
|
-
prevNode = node
|
|
122
|
-
node = node.parent
|
|
123
|
-
}
|
|
124
|
-
return ''
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function nodeContains(node, target) {
|
|
128
|
-
if (node === target)
|
|
129
|
-
return true
|
|
130
|
-
if (node && typeof node === 'object')
|
|
131
|
-
for (let key in node)
|
|
132
|
-
if (nodeHas(node[key], target))
|
|
133
|
-
return true
|
|
134
|
-
return false
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function nodeHas(value, target) {
|
|
138
|
-
if (value === target)
|
|
139
|
-
return true
|
|
140
|
-
if (Array.isArray(value))
|
|
141
|
-
return value.some(v => nodeContains(v, target))
|
|
142
|
-
return false
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function hasReadBeforeWriteInNestedScope(variable, defScope) {
|
|
146
|
-
let nestedFunctions
|
|
147
|
-
|
|
148
|
-
nestedFunctions = new Set(variable.references
|
|
149
|
-
.filter(ref => {
|
|
150
|
-
let refScope
|
|
151
|
-
|
|
152
|
-
refScope = ref.from
|
|
153
|
-
if (refScope == defScope)
|
|
154
|
-
return 0
|
|
155
|
-
return isProperAncestor(defScope, refScope) && (refScope.type == 'function' || refScope.type == 'arrow')
|
|
156
|
-
})
|
|
157
|
-
.map(ref => ref.from))
|
|
158
|
-
for (let fnScope of nestedFunctions) {
|
|
159
|
-
let fnRefs, hasRead, hasWrite
|
|
160
|
-
|
|
161
|
-
fnRefs = variable.references.filter(ref => ref.from === fnScope || isProperAncestor(fnScope, ref.from))
|
|
162
|
-
hasRead = fnRefs.some(ref => isReadRef(ref))
|
|
163
|
-
hasWrite = fnRefs.some(ref => isWriteRef(ref))
|
|
164
|
-
if (hasRead && hasWrite)
|
|
165
|
-
return 1
|
|
166
|
-
}
|
|
167
|
-
return 0
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function isConditionalRef
|
|
171
|
-
(ref, narrowestScope) {
|
|
172
|
-
let node
|
|
173
|
-
|
|
174
|
-
node = ref.identifier.parent
|
|
175
|
-
|
|
176
|
-
while (node) {
|
|
177
|
-
if (node === narrowestScope.block)
|
|
178
|
-
break
|
|
179
|
-
if (node.type === 'BlockStatement') {
|
|
180
|
-
let parent
|
|
181
|
-
|
|
182
|
-
parent = node.parent
|
|
183
|
-
if (parent?.type === 'IfStatement' && (parent.consequent === node || parent.alternate === node))
|
|
184
|
-
return true
|
|
185
|
-
if ([ 'WhileStatement', 'DoWhileStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement' ].includes(parent?.type) && parent.body === node)
|
|
186
|
-
return true
|
|
187
|
-
}
|
|
188
|
-
node = node.parent
|
|
189
|
-
}
|
|
190
|
-
return false
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function markConditionalRefs
|
|
194
|
-
(variable, scopeToNode, narrowestScope) {
|
|
195
|
-
for (let ref of variable.references) {
|
|
196
|
-
let refNode, rItems, item
|
|
197
|
-
|
|
198
|
-
refNode = scopeToNode.get(ref.from)
|
|
199
|
-
rItems = refNode.items.filter(i => i.ref == ref)
|
|
200
|
-
item = rItems[0]
|
|
201
|
-
if (item && (item.ctx == 'B' || isConditionalRef(ref, narrowestScope)))
|
|
202
|
-
item.isConditional = true
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function mayBeReadBeforeAnyWrite
|
|
207
|
-
(variable, scopeToNode, narrowestScope) {
|
|
208
|
-
let refs
|
|
209
|
-
|
|
210
|
-
refs = [ ...variable.references ]
|
|
211
|
-
refs.sort((a, b) => (a.cookshackNarrowestScopeItem?.pos ?? a.identifier.range[0]) - (b.cookshackNarrowestScopeItem?.pos ?? b.identifier.range[0]))
|
|
212
|
-
|
|
213
|
-
for (let ref of refs) {
|
|
214
|
-
let item
|
|
215
|
-
|
|
216
|
-
if (isReadRef(ref))
|
|
217
|
-
// a possible read
|
|
218
|
-
return 1
|
|
11
|
+
export { getPrintBuffer }
|
|
219
12
|
|
|
220
|
-
|
|
221
|
-
if (item.ctx == 'B' || isConditionalRef(ref, narrowestScope))
|
|
222
|
-
// a conditional write
|
|
223
|
-
continue
|
|
224
|
-
// A guaranteed write before any possible read.
|
|
225
|
-
return 0
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function isProperAncestor(ancestor, descendant) {
|
|
230
|
-
let s
|
|
231
|
-
|
|
232
|
-
s = descendant.upper
|
|
233
|
-
while (s) {
|
|
234
|
-
if (s == ancestor)
|
|
235
|
-
return 1
|
|
236
|
-
s = s.upper
|
|
237
|
-
}
|
|
238
|
-
return 0
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function scopeStart(scope) {
|
|
242
|
-
if (scope.block == null)
|
|
243
|
-
return Infinity
|
|
244
|
-
if (scope.type == 'function' && scope.block.id)
|
|
245
|
-
return scope.block.id.range[1]
|
|
246
|
-
if (scope.type == 'class' && scope.block.id)
|
|
247
|
-
return scope.block.id.range[0]
|
|
248
|
-
return scope.block.range[0]
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function buildScopeTree(scope, prefix, scopeToNode) {
|
|
252
|
-
let node, siblingNum
|
|
253
|
-
|
|
254
|
-
node = {
|
|
255
|
-
scope,
|
|
256
|
-
prefix,
|
|
257
|
-
items: [],
|
|
258
|
-
children: []
|
|
259
|
-
}
|
|
260
|
-
scopeToNode.set(scope, node)
|
|
261
|
-
|
|
262
|
-
siblingNum = 0
|
|
263
|
-
for (let child of scope.childScopes) {
|
|
264
|
-
siblingNum++
|
|
265
|
-
node.children.push(buildScopeTree(child, prefix + '.' + siblingNum, scopeToNode))
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
for (let variable of scope.variables) {
|
|
269
|
-
if (variable.defs.length > 0)
|
|
270
|
-
node.items.push({ type: 'LET', name: variable.name, pos: variable.defs[0].name.range[0] })
|
|
271
|
-
|
|
272
|
-
for (let ref of variable.references) {
|
|
273
|
-
let targetNode
|
|
274
|
-
|
|
275
|
-
targetNode = scopeToNode.get(ref.from)
|
|
276
|
-
if (targetNode) {
|
|
277
|
-
let parent, sortPos, ctx, item
|
|
278
|
-
|
|
279
|
-
ctx = getConditionalContext(ref)
|
|
280
|
-
parent = ref.identifier.parent
|
|
281
|
-
|
|
282
|
-
if (isWriteRef(ref))
|
|
283
|
-
if (ref.identifier.parent?.type == 'UpdateExpression') {
|
|
284
|
-
targetNode.items.push({ type: 'READ', name: ref.identifier.name, ctx, pos: ref.identifier.range[0] })
|
|
285
|
-
item = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] }
|
|
286
|
-
}
|
|
287
|
-
else if (ref.identifier.parent?.type == 'AssignmentExpression') {
|
|
288
|
-
sortPos = parent.right.range[1] + 0.4
|
|
289
|
-
item = { ref, type: 'WRITE', name: ref.identifier.name, ctx, pos: sortPos }
|
|
290
|
-
}
|
|
291
|
-
else if (ref.identifier.parent?.type == 'VariableDeclarator')
|
|
292
|
-
item = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] + 0.4 }
|
|
293
|
-
else
|
|
294
|
-
item = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] }
|
|
295
|
-
else {
|
|
296
|
-
let declarator
|
|
297
|
-
|
|
298
|
-
declarator = parent
|
|
299
|
-
while (declarator)
|
|
300
|
-
if (declarator.type == 'VariableDeclarator')
|
|
301
|
-
break
|
|
302
|
-
else
|
|
303
|
-
declarator = declarator.parent
|
|
304
|
-
if (declarator?.type == 'VariableDeclarator' && nodeContains(declarator.init, ref.identifier))
|
|
305
|
-
sortPos = declarator.id ? declarator.id.range[0] - 0.4 : ref.identifier.range[0]
|
|
306
|
-
else
|
|
307
|
-
sortPos = ref.identifier.range[0]
|
|
308
|
-
item = { ref, type: 'READ', name: ref.identifier.name, ctx, pos: sortPos }
|
|
309
|
-
}
|
|
310
|
-
targetNode.items.push(item)
|
|
311
|
-
ref.cookshackNarrowestScopeItem = item
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
node.items.sort((a, b) => a.pos - b.pos)
|
|
317
|
-
|
|
318
|
-
return node
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function checkScopeNode(context, treeNode, reported, scopeToNode) {
|
|
322
|
-
let indent
|
|
323
|
-
|
|
324
|
-
reported = reported || new Set
|
|
325
|
-
indent = ' '.repeat(treeNode.prefix.split('.').length - 1)
|
|
326
|
-
|
|
327
|
-
for (let variable of treeNode.scope.variables) {
|
|
328
|
-
let defNode
|
|
329
|
-
|
|
330
|
-
if (reported.has(variable))
|
|
331
|
-
continue
|
|
332
|
-
if (variable.defs.length === 0)
|
|
333
|
-
continue
|
|
334
|
-
if ([ 'Parameter', 'FunctionName', 'ImportBinding', 'CatchClause', 'ClassName' ].includes(variable.defs[0].type))
|
|
335
|
-
continue
|
|
336
|
-
if (variable.defs[0].node.parent?.parent?.type === 'ExportNamedDeclaration')
|
|
337
|
-
continue
|
|
338
|
-
|
|
339
|
-
defNode = variable.defs[0]?.name
|
|
340
|
-
if (defNode) {
|
|
341
|
-
let defScope, narrowestScope, defNodePrefix
|
|
342
|
-
|
|
343
|
-
defScope = getDefinitionScope(variable)
|
|
344
|
-
defNodePrefix = scopeToNode.get(defScope)?.prefix ?? '?'
|
|
345
|
-
trace(indent, '1 found decl scope of', variable.name + ':', defNodePrefix + ' ' + defScope.type.toUpperCase())
|
|
346
|
-
|
|
347
|
-
narrowestScope = getNarrowestScope(variable)
|
|
348
|
-
if (narrowestScope) {
|
|
349
|
-
let narrowestPrefix
|
|
350
|
-
|
|
351
|
-
narrowestPrefix = scopeToNode.get(narrowestScope)?.prefix ?? '?'
|
|
352
|
-
trace(indent, '2 found narrowest scope of', variable.name + ':', narrowestPrefix + ' ' + narrowestScope?.type.toUpperCase())
|
|
353
|
-
|
|
354
|
-
markConditionalRefs(variable, scopeToNode, narrowestScope)
|
|
355
|
-
|
|
356
|
-
if (defScope == narrowestScope)
|
|
357
|
-
continue
|
|
358
|
-
trace(indent, '3', variable.name, 'could be moved to a narrower scope')
|
|
359
|
-
|
|
360
|
-
if (defScope.type == 'for') {
|
|
361
|
-
trace(indent, '4 exception:', variable.name, 'is in a for loop header')
|
|
362
|
-
continue
|
|
363
|
-
}
|
|
364
|
-
if (0 && hasReadBeforeWriteInNestedScope(variable, defScope)) {
|
|
365
|
-
trace(indent, '4 exception:', variable.name, 'hasReadBeforeWriteInNestedScope')
|
|
366
|
-
continue
|
|
367
|
-
}
|
|
368
|
-
if (mayBeReadBeforeAnyWrite(variable, scopeToNode, narrowestScope)) {
|
|
369
|
-
trace(indent, '4 exception:', variable.name, 'mayBeReadBeforeAnyWrite')
|
|
370
|
-
continue
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
trace(indent, '5', variable.name, 'is too broad')
|
|
374
|
-
|
|
375
|
-
reported.add(variable)
|
|
376
|
-
context.report({
|
|
377
|
-
node: defNode,
|
|
378
|
-
messageId: 'tooBroad',
|
|
379
|
-
data: { name: variable.name }
|
|
380
|
-
})
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
for (let child of treeNode.children)
|
|
386
|
-
checkScopeNode(context, child, reported, scopeToNode)
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
function printTree(node, siblingNum) {
|
|
390
|
-
let prefix, all, indent
|
|
391
|
-
|
|
392
|
-
prefix = siblingNum === 0 ? node.prefix : node.prefix.split('.').slice(0, -1).join('.') + '.' + siblingNum
|
|
393
|
-
indent = ' '.repeat(prefix.split('.').length - 1)
|
|
394
|
-
{
|
|
395
|
-
let name
|
|
396
|
-
|
|
397
|
-
name = node.scope.block?.id?.name ?? node.scope.block?.parent?.key?.name
|
|
398
|
-
print(indent + 'SCOPE ' + prefix + ' ' + node.scope.type.toUpperCase() + ' pos ' + scopeStart(node.scope) + (name ? ' name ' + name : ''))
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
all = [ ...node.items.map(i => ({ pos: i.pos, type: 'item', data: i })),
|
|
402
|
-
...node.children.map((c, i) => ({ pos: scopeStart(c.scope), type: 'scope', data: c, sibling: i + 1 })) ]
|
|
403
|
-
all.sort((a, b) => a.pos - b.pos)
|
|
404
|
-
|
|
405
|
-
for (let entry of all)
|
|
406
|
-
if (entry.type === 'item')
|
|
407
|
-
print(indent
|
|
408
|
-
+ ' ' + entry.data.type.padEnd(5)
|
|
409
|
-
+ ' ' + entry.data.name
|
|
410
|
-
// B: is the ref conditional within the scope that "owns" the ref (for single statement `if`)
|
|
411
|
-
+ (entry.data.ctx ? ' ' + entry.data.ctx : '').padEnd(3)
|
|
412
|
-
// C: is the ref conditional within the variable's narrowestScope?
|
|
413
|
-
+ (entry.data.isConditional ? 'C' : ' ').padEnd(2)
|
|
414
|
-
+ 'pos ' + entry.data.pos)
|
|
415
|
-
else
|
|
416
|
-
printTree(entry.data, entry.sibling)
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
function createNarrowestScope
|
|
420
|
-
(context) {
|
|
421
|
-
let scopeManager
|
|
422
|
-
|
|
423
|
-
clearPrintBuffer()
|
|
424
|
-
scopeManager = context.sourceCode.scopeManager
|
|
425
|
-
if (scopeManager)
|
|
426
|
-
return {
|
|
427
|
-
'Program:exit'() {
|
|
428
|
-
let tree, scopeToNode
|
|
429
|
-
|
|
430
|
-
scopeToNode = new Map
|
|
431
|
-
tree = buildScopeTree(scopeManager.scopes[0], '1', scopeToNode)
|
|
432
|
-
checkScopeNode(context, tree, null, scopeToNode)
|
|
433
|
-
printTree(tree, 0)
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
function createPositiveVibes
|
|
439
|
-
(context) {
|
|
440
|
-
return {
|
|
441
|
-
UnaryExpression(node) {
|
|
442
|
-
if (node.operator == '!')
|
|
443
|
-
context.report({ node,
|
|
444
|
-
messageId: 'positiveVibes' })
|
|
445
|
-
},
|
|
446
|
-
BinaryExpression(node) {
|
|
447
|
-
if (node.operator == '!=')
|
|
448
|
-
context.report({ node,
|
|
449
|
-
messageId: 'equality' })
|
|
450
|
-
else if (node.operator == '!==')
|
|
451
|
-
context.report({ node,
|
|
452
|
-
messageId: 'strictEquality' })
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
13
|
+
export let rules, languageOptions, plugins
|
|
456
14
|
|
|
457
|
-
plugins = { 'cookshack': { rules: { 'positive-vibes':
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
'narrowest-scope': { meta: { type: 'suggestion',
|
|
465
|
-
docs: { description: 'Enforce variables are declared in their narrowest possible scope.' },
|
|
466
|
-
messages: { tooBroad: 'Variable "{{ name }}" is declared in a broader scope than necessary.' },
|
|
467
|
-
schema: [] },
|
|
468
|
-
create: createNarrowestScope } } } }
|
|
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 } } }
|
|
469
22
|
|
|
470
23
|
rules = {
|
|
471
24
|
'array-bracket-newline': [ 'error', 'never' ],
|
|
@@ -496,8 +49,14 @@ rules = {
|
|
|
496
49
|
{ blankLine: 'never', prev: 'let', next: 'let' } ],
|
|
497
50
|
'no-case-declarations': 'error',
|
|
498
51
|
'no-global-assign': 'error',
|
|
499
|
-
'cookshack/narrowest-scope': 'error',
|
|
500
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',
|
|
501
60
|
'no-mixed-operators': 'error',
|
|
502
61
|
'no-multi-spaces': 'error',
|
|
503
62
|
'no-multiple-empty-lines': [ 'error', { max: 1, maxEOF: 0 } ],
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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 test/rules/*.js",
|
|
8
|
+
"check": "npx eslint --no-warn-ignored -f ./formatter.js -c ./index.js *.js test/rules/*.js plugins/*.js",
|
|
9
9
|
"test": "mocha test/**/*.js",
|
|
10
10
|
"prepare": "husky && rollup -c"
|
|
11
11
|
},
|
|
@@ -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 }
|