@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,472 @@
|
|
|
1
|
+
let varIds, nextVarId, printBuffer
|
|
2
|
+
|
|
3
|
+
printBuffer = []
|
|
4
|
+
varIds = new Map()
|
|
5
|
+
nextVarId = 0
|
|
6
|
+
|
|
7
|
+
function print
|
|
8
|
+
(...args) {
|
|
9
|
+
printBuffer.push(args.join(' '))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function trace
|
|
13
|
+
(...args) {
|
|
14
|
+
if (0)
|
|
15
|
+
console.log('TRACE', ...args)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export
|
|
19
|
+
function getPrintBuffer
|
|
20
|
+
() {
|
|
21
|
+
return printBuffer.join('\n')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function clearPrintBuffer
|
|
25
|
+
() {
|
|
26
|
+
printBuffer = []
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getNarrowestScope
|
|
30
|
+
(variable) {
|
|
31
|
+
let common
|
|
32
|
+
|
|
33
|
+
common = null
|
|
34
|
+
for (let ref of variable.references) {
|
|
35
|
+
if (variable.defs.some(def => def.name == ref.identifier))
|
|
36
|
+
continue
|
|
37
|
+
if (ref.from)
|
|
38
|
+
if (common)
|
|
39
|
+
common = getCommonAncestor(common, ref.from)
|
|
40
|
+
else
|
|
41
|
+
common = ref.from
|
|
42
|
+
}
|
|
43
|
+
return common
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getCommonAncestor
|
|
47
|
+
(scope1, scope2) {
|
|
48
|
+
let ancestors, s
|
|
49
|
+
|
|
50
|
+
ancestors = []
|
|
51
|
+
s = scope1
|
|
52
|
+
while (s) {
|
|
53
|
+
if (s.type == 'global')
|
|
54
|
+
break
|
|
55
|
+
ancestors.push(s)
|
|
56
|
+
s = s.upper
|
|
57
|
+
}
|
|
58
|
+
s = scope2
|
|
59
|
+
while (s) {
|
|
60
|
+
if (s.type == 'global')
|
|
61
|
+
break
|
|
62
|
+
if (ancestors.includes(s))
|
|
63
|
+
return s
|
|
64
|
+
s = s.upper
|
|
65
|
+
}
|
|
66
|
+
return scope1
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getDefinitionScope
|
|
70
|
+
(variable) {
|
|
71
|
+
return variable.scope
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isWriteRef
|
|
75
|
+
(ref) {
|
|
76
|
+
let parent
|
|
77
|
+
|
|
78
|
+
parent = ref.identifier.parent
|
|
79
|
+
if (parent) {
|
|
80
|
+
if (parent.type == 'AssignmentExpression' && parent.left == ref.identifier)
|
|
81
|
+
return 1
|
|
82
|
+
if (parent.type == 'UpdateExpression')
|
|
83
|
+
return 1
|
|
84
|
+
if (parent.type == 'VariableDeclarator' && parent.id == ref.identifier)
|
|
85
|
+
return 1
|
|
86
|
+
}
|
|
87
|
+
return 0
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isReadRef
|
|
91
|
+
(ref) {
|
|
92
|
+
if (isWriteRef(ref))
|
|
93
|
+
return 0
|
|
94
|
+
return 1
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getConditionalContext
|
|
98
|
+
(ref) {
|
|
99
|
+
let node, prevNode, scopeBlock
|
|
100
|
+
|
|
101
|
+
scopeBlock = ref.from.block
|
|
102
|
+
prevNode = ref.identifier
|
|
103
|
+
node = ref.identifier.parent
|
|
104
|
+
while (node) {
|
|
105
|
+
if (node == scopeBlock)
|
|
106
|
+
break
|
|
107
|
+
if (node.type == 'IfStatement')
|
|
108
|
+
if (prevNode == node.test || nodeContains(node.test, prevNode))
|
|
109
|
+
prevNode = node
|
|
110
|
+
else
|
|
111
|
+
return 'B'
|
|
112
|
+
else if ([ 'WhileStatement', 'DoWhileStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement', 'SwitchStatement' ].includes(node.type))
|
|
113
|
+
if (prevNode == node.test || nodeContains(node.test, prevNode))
|
|
114
|
+
prevNode = node
|
|
115
|
+
else
|
|
116
|
+
return 'B'
|
|
117
|
+
else
|
|
118
|
+
prevNode = node
|
|
119
|
+
node = node.parent
|
|
120
|
+
}
|
|
121
|
+
return ''
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function nodeContains
|
|
125
|
+
(node, target) {
|
|
126
|
+
if (node == target)
|
|
127
|
+
return true
|
|
128
|
+
if (node && typeof node == 'object')
|
|
129
|
+
for (let key in node)
|
|
130
|
+
if (nodeHas(node[key], target))
|
|
131
|
+
return true
|
|
132
|
+
return false
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function nodeHas
|
|
136
|
+
(value, target) {
|
|
137
|
+
if (value == target)
|
|
138
|
+
return true
|
|
139
|
+
if (Array.isArray(value))
|
|
140
|
+
return value.some(v => nodeContains(v, target))
|
|
141
|
+
return false
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function hasReadBeforeWriteInNestedScope
|
|
145
|
+
(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
|
+
return 1
|
|
218
|
+
|
|
219
|
+
item = ref.cookshackNarrowestScopeItem
|
|
220
|
+
if (item.ctx == 'B' || isConditionalRef(ref, narrowestScope))
|
|
221
|
+
continue
|
|
222
|
+
return 0
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isProperAncestor
|
|
227
|
+
(ancestor, descendant) {
|
|
228
|
+
let s
|
|
229
|
+
|
|
230
|
+
s = descendant.upper
|
|
231
|
+
while (s) {
|
|
232
|
+
if (s == ancestor)
|
|
233
|
+
return 1
|
|
234
|
+
s = s.upper
|
|
235
|
+
}
|
|
236
|
+
return 0
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function scopeStart
|
|
240
|
+
(scope) {
|
|
241
|
+
if (scope.block == null)
|
|
242
|
+
return Infinity
|
|
243
|
+
if (scope.type == 'function' && scope.block.id)
|
|
244
|
+
return scope.block.id.range[1]
|
|
245
|
+
if (scope.type == 'class' && scope.block.id)
|
|
246
|
+
return scope.block.id.range[0]
|
|
247
|
+
return scope.block.range[0]
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export { isReadRef, isWriteRef, buildScopeTree, scopeStart }
|
|
251
|
+
|
|
252
|
+
function ensureInVarIds
|
|
253
|
+
(variable) {
|
|
254
|
+
if (varIds.has(variable))
|
|
255
|
+
return
|
|
256
|
+
varIds.set(variable, nextVarId++)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function isCompoundAssignmentOp
|
|
260
|
+
(op) {
|
|
261
|
+
if (op == '=')
|
|
262
|
+
return 0
|
|
263
|
+
return 1
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function buildScopeTree
|
|
267
|
+
(scope, prefix, scopeToNode, astToTree) {
|
|
268
|
+
let node, siblingNum
|
|
269
|
+
|
|
270
|
+
node = { scope,
|
|
271
|
+
prefix,
|
|
272
|
+
items: [],
|
|
273
|
+
children: [] }
|
|
274
|
+
|
|
275
|
+
scopeToNode.set(scope, node)
|
|
276
|
+
if (scope.block && astToTree)
|
|
277
|
+
astToTree.set(scope.block, node)
|
|
278
|
+
|
|
279
|
+
siblingNum = 0
|
|
280
|
+
for (let child of scope.childScopes) {
|
|
281
|
+
siblingNum++
|
|
282
|
+
node.children.push(buildScopeTree(child, prefix + '.' + siblingNum, scopeToNode, astToTree))
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
for (let variable of scope.variables) {
|
|
286
|
+
if (variable.defs.length > 0) {
|
|
287
|
+
ensureInVarIds(variable)
|
|
288
|
+
node.items.push({ type: 'LET', name: variable.name, pos: variable.defs[0].name.range[0], defNode: variable.defs[0].node, defType: variable.defs[0].type, identifier: variable.defs[0].name, variable, varId: varIds.get(variable) })
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
for (let ref of variable.references) {
|
|
292
|
+
let targetNode
|
|
293
|
+
|
|
294
|
+
targetNode = scopeToNode.get(ref.from)
|
|
295
|
+
if (targetNode) {
|
|
296
|
+
let parent, sortPos, ctx, item1, item2
|
|
297
|
+
|
|
298
|
+
ctx = getConditionalContext(ref)
|
|
299
|
+
parent = ref.identifier.parent
|
|
300
|
+
|
|
301
|
+
if (isWriteRef(ref))
|
|
302
|
+
if (ref.identifier.parent?.type == 'UpdateExpression') {
|
|
303
|
+
item1 = { ref, type: 'READ', name: ref.identifier.name, ctx, pos: ref.identifier.range[0] }
|
|
304
|
+
item2 = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] }
|
|
305
|
+
}
|
|
306
|
+
else if (ref.identifier.parent?.type == 'AssignmentExpression') {
|
|
307
|
+
sortPos = parent.right.range[1] + 0.4
|
|
308
|
+
if (ref.identifier.parent.left == ref.identifier && isCompoundAssignmentOp(ref.identifier.parent.operator)) {
|
|
309
|
+
item1 = { ref, type: 'READ', name: ref.identifier.name, ctx, pos: ref.identifier.range[0] }
|
|
310
|
+
item2 = { ref, type: 'WRITE', name: ref.identifier.name, pos: sortPos }
|
|
311
|
+
}
|
|
312
|
+
else
|
|
313
|
+
item1 = { ref, type: 'WRITE', name: ref.identifier.name, ctx, pos: sortPos }
|
|
314
|
+
}
|
|
315
|
+
else if (ref.identifier.parent?.type == 'VariableDeclarator')
|
|
316
|
+
item1 = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] + 0.4 }
|
|
317
|
+
else
|
|
318
|
+
item1 = { ref, type: 'WRITE', name: ref.identifier.name, pos: ref.identifier.range[0] }
|
|
319
|
+
else {
|
|
320
|
+
let declarator
|
|
321
|
+
|
|
322
|
+
declarator = parent
|
|
323
|
+
while (declarator)
|
|
324
|
+
if (declarator.type == 'VariableDeclarator')
|
|
325
|
+
break
|
|
326
|
+
else
|
|
327
|
+
declarator = declarator.parent
|
|
328
|
+
if (declarator?.type == 'VariableDeclarator' && nodeContains(declarator.init, ref.identifier))
|
|
329
|
+
sortPos = declarator.id ? declarator.id.range[0] - 0.4 : ref.identifier.range[0]
|
|
330
|
+
else
|
|
331
|
+
sortPos = ref.identifier.range[0]
|
|
332
|
+
item1 = { ref, type: 'READ', name: ref.identifier.name, ctx, pos: sortPos }
|
|
333
|
+
}
|
|
334
|
+
ensureInVarIds(variable)
|
|
335
|
+
item1.varId = varIds.get(variable)
|
|
336
|
+
targetNode.items.push(item1)
|
|
337
|
+
if (item2) {
|
|
338
|
+
item2.varId = varIds.get(variable)
|
|
339
|
+
targetNode.items.push(item2)
|
|
340
|
+
}
|
|
341
|
+
ref.cookshackNarrowestScopeItem = item2 || item1
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
node.items.sort((a, b) => a.pos - b.pos)
|
|
347
|
+
|
|
348
|
+
return node
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function checkScopeNode
|
|
352
|
+
(context, treeNode, reported, scopeToNode) {
|
|
353
|
+
let indent
|
|
354
|
+
|
|
355
|
+
reported = reported || new Set
|
|
356
|
+
indent = ' '.repeat(treeNode.prefix.split('.').length - 1)
|
|
357
|
+
|
|
358
|
+
for (let variable of treeNode.scope.variables) {
|
|
359
|
+
let defNode
|
|
360
|
+
|
|
361
|
+
if (reported.has(variable))
|
|
362
|
+
continue
|
|
363
|
+
if (variable.defs.length == 0)
|
|
364
|
+
continue
|
|
365
|
+
if ([ 'Parameter', 'FunctionName', 'ImportBinding', 'CatchClause', 'ClassName' ].includes(variable.defs[0].type))
|
|
366
|
+
continue
|
|
367
|
+
if (variable.defs[0].node.parent?.parent?.type == 'ExportNamedDeclaration')
|
|
368
|
+
continue
|
|
369
|
+
|
|
370
|
+
defNode = variable.defs[0]?.name
|
|
371
|
+
if (defNode) {
|
|
372
|
+
let defScope, narrowestScope, defNodePrefix
|
|
373
|
+
|
|
374
|
+
defScope = getDefinitionScope(variable)
|
|
375
|
+
defNodePrefix = scopeToNode.get(defScope)?.prefix ?? '?'
|
|
376
|
+
trace(indent, '1 found decl scope of', variable.name + ':', defNodePrefix + ' ' + defScope.type.toUpperCase())
|
|
377
|
+
|
|
378
|
+
narrowestScope = getNarrowestScope(variable)
|
|
379
|
+
if (narrowestScope) {
|
|
380
|
+
let narrowestPrefix
|
|
381
|
+
|
|
382
|
+
narrowestPrefix = scopeToNode.get(narrowestScope)?.prefix ?? '?'
|
|
383
|
+
trace(indent, '2 found narrowest scope of', variable.name + ':', narrowestPrefix + ' ' + narrowestScope?.type.toUpperCase())
|
|
384
|
+
|
|
385
|
+
markConditionalRefs(variable, scopeToNode, narrowestScope)
|
|
386
|
+
|
|
387
|
+
if (defScope == narrowestScope)
|
|
388
|
+
continue
|
|
389
|
+
trace(indent, '3', variable.name, 'could be moved to a narrower scope')
|
|
390
|
+
|
|
391
|
+
if (defScope.type == 'for') {
|
|
392
|
+
trace(indent, '4 exception:', variable.name, 'is in a for loop header')
|
|
393
|
+
continue
|
|
394
|
+
}
|
|
395
|
+
if (0 && hasReadBeforeWriteInNestedScope(variable, defScope)) {
|
|
396
|
+
trace(indent, '4 exception:', variable.name, 'hasReadBeforeWriteInNestedScope')
|
|
397
|
+
continue
|
|
398
|
+
}
|
|
399
|
+
if (mayBeReadBeforeAnyWrite(variable, scopeToNode, narrowestScope)) {
|
|
400
|
+
trace(indent, '4 exception:', variable.name, 'mayBeReadBeforeAnyWrite')
|
|
401
|
+
continue
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
trace(indent, '5', variable.name, 'is too broad')
|
|
405
|
+
|
|
406
|
+
reported.add(variable)
|
|
407
|
+
context.report({ node: defNode,
|
|
408
|
+
messageId: 'tooBroad',
|
|
409
|
+
data: { name: variable.name } })
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
for (let child of treeNode.children)
|
|
415
|
+
checkScopeNode(context, child, reported, scopeToNode)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function printTree
|
|
419
|
+
(node, siblingNum) {
|
|
420
|
+
let prefix, all, indent
|
|
421
|
+
|
|
422
|
+
prefix = siblingNum == 0 ? node.prefix : node.prefix.split('.').slice(0, -1).join('.') + '.' + siblingNum
|
|
423
|
+
indent = ' '.repeat(prefix.split('.').length - 1)
|
|
424
|
+
{
|
|
425
|
+
let name
|
|
426
|
+
|
|
427
|
+
name = node.scope.block?.id?.name ?? node.scope.block?.parent?.key?.name
|
|
428
|
+
print(indent + 'SCOPE ' + prefix + ' ' + node.scope.type.toUpperCase() + ' pos ' + scopeStart(node.scope) + (name ? ' name ' + name : ''))
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
all = [ ...node.items.map(i => ({ pos: i.pos, type: 'item', data: i })),
|
|
432
|
+
...node.children.map((c, i) => ({ pos: scopeStart(c.scope), type: 'scope', data: c, sibling: i + 1 })) ]
|
|
433
|
+
all.sort((a, b) => a.pos - b.pos)
|
|
434
|
+
|
|
435
|
+
for (let entry of all)
|
|
436
|
+
if (entry.type == 'item')
|
|
437
|
+
print(indent
|
|
438
|
+
+ ' ' + entry.data.type.padEnd(5)
|
|
439
|
+
+ ' ' + entry.data.name
|
|
440
|
+
+ (entry.data.ctx ? ' ' + entry.data.ctx : '').padEnd(3)
|
|
441
|
+
+ (entry.data.isConditional ? 'C' : ' ').padEnd(2)
|
|
442
|
+
+ 'pos ' + entry.data.pos)
|
|
443
|
+
else
|
|
444
|
+
printTree(entry.data, entry.sibling)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export
|
|
448
|
+
function createNarrowestScope
|
|
449
|
+
(context) {
|
|
450
|
+
let scopeManager
|
|
451
|
+
|
|
452
|
+
clearPrintBuffer()
|
|
453
|
+
scopeManager = context.sourceCode.scopeManager
|
|
454
|
+
if (scopeManager)
|
|
455
|
+
return { 'Program:exit'
|
|
456
|
+
() {
|
|
457
|
+
let tree, scopeToNode
|
|
458
|
+
|
|
459
|
+
scopeToNode = new Map
|
|
460
|
+
nextVarId = 0
|
|
461
|
+
tree = buildScopeTree(scopeManager.scopes[0], '1', scopeToNode)
|
|
462
|
+
checkScopeNode(context, tree, null, scopeToNode)
|
|
463
|
+
printTree(tree, 0)
|
|
464
|
+
} }
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export
|
|
468
|
+
default { meta: { type: 'suggestion',
|
|
469
|
+
docs: { description: 'Enforce variables are declared in their narrowest possible scope.' },
|
|
470
|
+
messages: { tooBroad: 'Variable "{{ name }}" is declared in a broader scope than necessary.' },
|
|
471
|
+
schema: [] },
|
|
472
|
+
create: createNarrowestScope }
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function createPositiveVibes
|
|
2
|
+
(context) {
|
|
3
|
+
return { UnaryExpression
|
|
4
|
+
(node) {
|
|
5
|
+
if (node.operator == '!')
|
|
6
|
+
context.report({ node,
|
|
7
|
+
messageId: 'positiveVibes' })
|
|
8
|
+
},
|
|
9
|
+
BinaryExpression
|
|
10
|
+
(node) {
|
|
11
|
+
if (node.operator == '!=')
|
|
12
|
+
context.report({ node,
|
|
13
|
+
messageId: 'equality' })
|
|
14
|
+
else if (node.operator == '!==')
|
|
15
|
+
context.report({ node,
|
|
16
|
+
messageId: 'strictEquality' })
|
|
17
|
+
} }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export
|
|
21
|
+
default { meta: { type: 'problem',
|
|
22
|
+
docs: { description: 'Prefer positive expressions.' },
|
|
23
|
+
messages: { positiveVibes: 'Be positive!',
|
|
24
|
+
equality: 'Use ==.',
|
|
25
|
+
strictEquality: 'Use ===.' },
|
|
26
|
+
schema: [] },
|
|
27
|
+
create: createPositiveVibes }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export
|
|
2
|
+
default { meta: { type: 'problem',
|
|
3
|
+
docs: { description: 'Enforce use of == instead of ===.' },
|
|
4
|
+
messages: { risky: 'Use ==.' },
|
|
5
|
+
schema: [] },
|
|
6
|
+
create
|
|
7
|
+
(context) {
|
|
8
|
+
return { BinaryExpression
|
|
9
|
+
(node) {
|
|
10
|
+
if (node.operator == '===')
|
|
11
|
+
context.report({ node, messageId: 'risky' })
|
|
12
|
+
} }
|
|
13
|
+
} }
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
function VariableDeclaration
|
|
2
|
+
(context, node) {
|
|
3
|
+
let parent
|
|
4
|
+
|
|
5
|
+
parent = node.parent
|
|
6
|
+
|
|
7
|
+
for (parent = node.parent; parent; parent = parent.parent)
|
|
8
|
+
if (parent.type == 'BlockStatement')
|
|
9
|
+
break
|
|
10
|
+
|
|
11
|
+
if (parent) {
|
|
12
|
+
let idx
|
|
13
|
+
|
|
14
|
+
if (parent.parent?.type == 'CatchClause')
|
|
15
|
+
return
|
|
16
|
+
|
|
17
|
+
idx = parent.body.indexOf(node)
|
|
18
|
+
for (let i = 0; i < idx; i++) {
|
|
19
|
+
if (parent.body[i].type == 'VariableDeclaration')
|
|
20
|
+
continue
|
|
21
|
+
context.report({ node, messageId: 'varDeclBlockStart' })
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function create
|
|
28
|
+
(context) {
|
|
29
|
+
return { VariableDeclaration: node => VariableDeclaration(context, node) }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export
|
|
33
|
+
default { meta: { type: 'suggestion',
|
|
34
|
+
docs: { description: 'Require variable declarations to be at the start of the block.' },
|
|
35
|
+
messages: { varDeclBlockStart: 'VarDecl must be at start of block.' },
|
|
36
|
+
schema: [] },
|
|
37
|
+
create }
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { RuleTester } from 'eslint'
|
|
2
|
+
import { plugins } from '../../index.js'
|
|
3
|
+
|
|
4
|
+
let ruleTester, validCases, invalidCases
|
|
5
|
+
|
|
6
|
+
ruleTester = new RuleTester()
|
|
7
|
+
validCases = []
|
|
8
|
+
invalidCases = []
|
|
9
|
+
|
|
10
|
+
function pass
|
|
11
|
+
(code) {
|
|
12
|
+
validCases.push({ code })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function fail
|
|
16
|
+
(count, code) {
|
|
17
|
+
let errors
|
|
18
|
+
|
|
19
|
+
errors = []
|
|
20
|
+
while (count > 0) {
|
|
21
|
+
errors.push({ messageId: 'useLet' })
|
|
22
|
+
count--
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
invalidCases.push({ code, errors })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pass('let x = 1')
|
|
29
|
+
|
|
30
|
+
pass('let x = 1, y = 2')
|
|
31
|
+
|
|
32
|
+
pass('for (let i = 1; i < 3; i++) console.log(i)')
|
|
33
|
+
|
|
34
|
+
fail(1, 'const x = 1')
|
|
35
|
+
|
|
36
|
+
fail(1, 'var x = 1')
|
|
37
|
+
|
|
38
|
+
fail(1, 'var x, y')
|
|
39
|
+
|
|
40
|
+
fail(1, 'for (const key in node) console.log(key)')
|
|
41
|
+
|
|
42
|
+
globalThis.describe('always-let',
|
|
43
|
+
() => ruleTester.run('always-let',
|
|
44
|
+
plugins.cookshack.rules['always-let'],
|
|
45
|
+
{ valid: validCases,
|
|
46
|
+
invalid: invalidCases }))
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { RuleTester } from 'eslint'
|
|
2
|
+
import { plugins } from '../../index.js'
|
|
3
|
+
|
|
4
|
+
let ruleTester, validCases, invalidCases
|
|
5
|
+
|
|
6
|
+
ruleTester = new RuleTester()
|
|
7
|
+
validCases = []
|
|
8
|
+
invalidCases = []
|
|
9
|
+
|
|
10
|
+
function pass
|
|
11
|
+
(code) {
|
|
12
|
+
validCases.push({ code })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function fail
|
|
16
|
+
(count, code) {
|
|
17
|
+
let errors
|
|
18
|
+
|
|
19
|
+
errors = []
|
|
20
|
+
while (count > 0) {
|
|
21
|
+
errors.push({ messageId: 'fnArgsNl' })
|
|
22
|
+
count--
|
|
23
|
+
}
|
|
24
|
+
invalidCases.push({ code, errors })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pass(`function f
|
|
28
|
+
(arg) {
|
|
29
|
+
}`)
|
|
30
|
+
|
|
31
|
+
pass(`function f
|
|
32
|
+
() {}`)
|
|
33
|
+
|
|
34
|
+
pass(`function f
|
|
35
|
+
(arg1, arg2) {}`)
|
|
36
|
+
|
|
37
|
+
pass(`function f
|
|
38
|
+
(
|
|
39
|
+
arg
|
|
40
|
+
) {}`)
|
|
41
|
+
|
|
42
|
+
pass(`export default
|
|
43
|
+
function
|
|
44
|
+
() {
|
|
45
|
+
return 1
|
|
46
|
+
}
|
|
47
|
+
`)
|
|
48
|
+
|
|
49
|
+
pass(`
|
|
50
|
+
function f
|
|
51
|
+
() {
|
|
52
|
+
return { f1
|
|
53
|
+
() {
|
|
54
|
+
return 1
|
|
55
|
+
} }
|
|
56
|
+
}
|
|
57
|
+
`)
|
|
58
|
+
|
|
59
|
+
pass(`
|
|
60
|
+
function f
|
|
61
|
+
() {
|
|
62
|
+
return {
|
|
63
|
+
f1
|
|
64
|
+
() {
|
|
65
|
+
return 1
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
`)
|
|
70
|
+
|
|
71
|
+
pass(`
|
|
72
|
+
class Marker {
|
|
73
|
+
constructor
|
|
74
|
+
(name, num) {
|
|
75
|
+
}
|
|
76
|
+
toDOM
|
|
77
|
+
() {
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
`)
|
|
81
|
+
|
|
82
|
+
pass(`
|
|
83
|
+
area = { get name
|
|
84
|
+
() {
|
|
85
|
+
return name
|
|
86
|
+
},
|
|
87
|
+
get value
|
|
88
|
+
() {
|
|
89
|
+
return value
|
|
90
|
+
} }
|
|
91
|
+
`)
|
|
92
|
+
|
|
93
|
+
fail(1, 'function f (arg) {}')
|
|
94
|
+
|
|
95
|
+
fail(1, 'function f() {}')
|
|
96
|
+
|
|
97
|
+
fail(1, `function f (arg,
|
|
98
|
+
arg2) {
|
|
99
|
+
}`)
|
|
100
|
+
|
|
101
|
+
fail(1, `function f
|
|
102
|
+
|
|
103
|
+
(arg) {}`)
|
|
104
|
+
|
|
105
|
+
fail(1, `function f
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
(arg) {}`)
|
|
109
|
+
|
|
110
|
+
globalThis.describe('fn-args-nl',
|
|
111
|
+
() => ruleTester.run('fn-args-nl',
|
|
112
|
+
plugins.cookshack.rules['fn-args-nl'],
|
|
113
|
+
{ valid: validCases,
|
|
114
|
+
invalid: invalidCases }))
|