@ttsc/lint 0.5.0-dev.20260429

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,1236 @@
1
+ // Bulk implementation of ESLint's "Suggestions" category — rules that
2
+ // don't catch outright bugs but flag stylistic or maintainability
3
+ // patterns. AST-only, no scope analysis.
4
+ package lint
5
+
6
+ import (
7
+ "strings"
8
+
9
+ shimast "github.com/microsoft/typescript-go/shim/ast"
10
+ )
11
+
12
+ // no-alert: `alert()` / `confirm()` / `prompt()`. Rarely the right
13
+ // answer in production code.
14
+ type noAlert struct{}
15
+
16
+ func (noAlert) Name() string { return "no-alert" }
17
+ func (noAlert) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCallExpression} }
18
+ func (noAlert) Check(ctx *Context, node *shimast.Node) {
19
+ call := node.AsCallExpression()
20
+ if call == nil {
21
+ return
22
+ }
23
+ switch callCalleeName(call) {
24
+ case "alert", "confirm", "prompt":
25
+ ctx.Report(node, "Unexpected "+callCalleeName(call)+".")
26
+ }
27
+ }
28
+
29
+ // no-bitwise: `&`, `|`, `^`, `~`, `<<`, `>>`, `>>>` — almost always a
30
+ // typo for the boolean-logic operators.
31
+ type noBitwise struct{}
32
+
33
+ func (noBitwise) Name() string { return "no-bitwise" }
34
+ func (noBitwise) Visits() []shimast.Kind {
35
+ return []shimast.Kind{shimast.KindBinaryExpression, shimast.KindPrefixUnaryExpression}
36
+ }
37
+ func (noBitwise) Check(ctx *Context, node *shimast.Node) {
38
+ if node.Kind == shimast.KindBinaryExpression {
39
+ expr := node.AsBinaryExpression()
40
+ if expr == nil || expr.OperatorToken == nil {
41
+ return
42
+ }
43
+ switch expr.OperatorToken.Kind {
44
+ case shimast.KindAmpersandToken,
45
+ shimast.KindBarToken,
46
+ shimast.KindCaretToken,
47
+ shimast.KindLessThanLessThanToken,
48
+ shimast.KindGreaterThanGreaterThanToken,
49
+ shimast.KindGreaterThanGreaterThanGreaterThanToken,
50
+ shimast.KindAmpersandEqualsToken,
51
+ shimast.KindBarEqualsToken,
52
+ shimast.KindCaretEqualsToken,
53
+ shimast.KindLessThanLessThanEqualsToken,
54
+ shimast.KindGreaterThanGreaterThanEqualsToken,
55
+ shimast.KindGreaterThanGreaterThanGreaterThanEqualsToken:
56
+ ctx.Report(node, "Unexpected use of bitwise operator.")
57
+ }
58
+ return
59
+ }
60
+ prefix := node.AsPrefixUnaryExpression()
61
+ if prefix == nil {
62
+ return
63
+ }
64
+ if prefix.Operator == shimast.KindTildeToken {
65
+ ctx.Report(node, "Unexpected use of bitwise operator.")
66
+ }
67
+ }
68
+
69
+ // no-caller: `arguments.caller` / `arguments.callee` — strict-mode
70
+ // errors elsewhere; lint catches them earlier.
71
+ type noCaller struct{}
72
+
73
+ func (noCaller) Name() string { return "no-caller" }
74
+ func (noCaller) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindPropertyAccessExpression} }
75
+ func (noCaller) Check(ctx *Context, node *shimast.Node) {
76
+ access := node.AsPropertyAccessExpression()
77
+ if access == nil {
78
+ return
79
+ }
80
+ if identifierText(access.Expression) != "arguments" {
81
+ return
82
+ }
83
+ switch identifierText(access.Name()) {
84
+ case "caller", "callee":
85
+ ctx.Report(node, "Avoid arguments."+identifierText(access.Name())+".")
86
+ }
87
+ }
88
+
89
+ // no-case-declarations: `switch (x) { case 1: let y = 2; break; }` —
90
+ // block-scoped declarations leak across case labels.
91
+ type noCaseDeclarations struct{}
92
+
93
+ func (noCaseDeclarations) Name() string { return "no-case-declarations" }
94
+ func (noCaseDeclarations) Visits() []shimast.Kind {
95
+ return []shimast.Kind{shimast.KindCaseClause, shimast.KindDefaultClause}
96
+ }
97
+ func (noCaseDeclarations) Check(ctx *Context, node *shimast.Node) {
98
+ clause := node.AsCaseOrDefaultClause()
99
+ if clause == nil || clause.Statements == nil {
100
+ return
101
+ }
102
+ for _, stmt := range clause.Statements.Nodes {
103
+ if stmt == nil {
104
+ continue
105
+ }
106
+ if stmt.Kind == shimast.KindVariableStatement {
107
+ vstmt := stmt.AsVariableStatement()
108
+ if vstmt != nil && vstmt.DeclarationList != nil && !shimast.IsVar(vstmt.DeclarationList) {
109
+ ctx.Report(stmt, "Unexpected lexical declaration in case block.")
110
+ continue
111
+ }
112
+ }
113
+ switch stmt.Kind {
114
+ case shimast.KindFunctionDeclaration, shimast.KindClassDeclaration:
115
+ ctx.Report(stmt, "Unexpected lexical declaration in case block.")
116
+ }
117
+ }
118
+ }
119
+
120
+ // no-continue: `continue` keyword. ESLint flags it as a code-smell
121
+ // (loop body should usually be reorganized).
122
+ type noContinue struct{}
123
+
124
+ func (noContinue) Name() string { return "no-continue" }
125
+ func (noContinue) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindContinueStatement} }
126
+ func (noContinue) Check(ctx *Context, node *shimast.Node) {
127
+ ctx.Report(node, "Unexpected use of continue statement.")
128
+ }
129
+
130
+ // no-delete-var: `delete x` where `x` is a variable. Strict-mode error.
131
+ type noDeleteVar struct{}
132
+
133
+ func (noDeleteVar) Name() string { return "no-delete-var" }
134
+ func (noDeleteVar) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindDeleteExpression} }
135
+ func (noDeleteVar) Check(ctx *Context, node *shimast.Node) {
136
+ del := node.AsDeleteExpression()
137
+ if del == nil {
138
+ return
139
+ }
140
+ if del.Expression != nil && del.Expression.Kind == shimast.KindIdentifier {
141
+ ctx.Report(node, "Variables should not be deleted.")
142
+ }
143
+ }
144
+
145
+ // no-eq-null: `x == null` — ambiguous with eqeqeq's `null` exception
146
+ // when developers want to also catch `undefined`.
147
+ type noEqNull struct{}
148
+
149
+ func (noEqNull) Name() string { return "no-eq-null" }
150
+ func (noEqNull) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
151
+ func (noEqNull) Check(ctx *Context, node *shimast.Node) {
152
+ expr := node.AsBinaryExpression()
153
+ if expr == nil || expr.OperatorToken == nil {
154
+ return
155
+ }
156
+ if expr.OperatorToken.Kind != shimast.KindEqualsEqualsToken && expr.OperatorToken.Kind != shimast.KindExclamationEqualsToken {
157
+ return
158
+ }
159
+ if isNullLiteral(expr.Left) || isNullLiteral(expr.Right) {
160
+ ctx.Report(node, "Use '===' to compare with null.")
161
+ }
162
+ }
163
+
164
+ func isNullLiteral(node *shimast.Node) bool {
165
+ return node != nil && node.Kind == shimast.KindNullKeyword
166
+ }
167
+
168
+ // no-extra-bind: `(function () {}).bind(this)` where `this` isn't used
169
+ // — only flag the case where the bind target is empty/parameterless,
170
+ // keeping false-positives down.
171
+ type noExtraBind struct{}
172
+
173
+ func (noExtraBind) Name() string { return "no-extra-bind" }
174
+ func (noExtraBind) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCallExpression} }
175
+ func (noExtraBind) Check(ctx *Context, node *shimast.Node) {
176
+ call := node.AsCallExpression()
177
+ if call == nil || call.Expression == nil {
178
+ return
179
+ }
180
+ if call.Expression.Kind != shimast.KindPropertyAccessExpression {
181
+ return
182
+ }
183
+ access := call.Expression.AsPropertyAccessExpression()
184
+ if access == nil || identifierText(access.Name()) != "bind" {
185
+ return
186
+ }
187
+ target := stripParens(access.Expression)
188
+ if target == nil {
189
+ return
190
+ }
191
+ if target.Kind != shimast.KindArrowFunction && target.Kind != shimast.KindFunctionExpression {
192
+ return
193
+ }
194
+ // Arrow functions don't have their own `this`; `.bind` is always
195
+ // useless on them.
196
+ if target.Kind == shimast.KindArrowFunction {
197
+ ctx.Report(node, "The function binding is unnecessary.")
198
+ return
199
+ }
200
+ body := target.Body()
201
+ if body != nil && !bodyReferencesThis(body) {
202
+ ctx.Report(node, "The function binding is unnecessary.")
203
+ }
204
+ }
205
+
206
+ func bodyReferencesThis(node *shimast.Node) bool {
207
+ if node == nil {
208
+ return false
209
+ }
210
+ if node.Kind == shimast.KindThisKeyword {
211
+ return true
212
+ }
213
+ // Don't descend into nested function-likes — their `this` is
214
+ // independent.
215
+ if isFunctionLikeKind(node) && node.Parent != nil {
216
+ return false
217
+ }
218
+ found := false
219
+ node.ForEachChild(func(child *shimast.Node) bool {
220
+ if found {
221
+ return true
222
+ }
223
+ if bodyReferencesThis(child) {
224
+ found = true
225
+ return true
226
+ }
227
+ return false
228
+ })
229
+ return found
230
+ }
231
+
232
+ // no-labels: labels (`outer: for (...) { break outer; }`) are
233
+ // confusing and rarely needed.
234
+ type noLabels struct{}
235
+
236
+ func (noLabels) Name() string { return "no-labels" }
237
+ func (noLabels) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindLabeledStatement} }
238
+ func (noLabels) Check(ctx *Context, node *shimast.Node) {
239
+ ctx.Report(node, "Unexpected labeled statement.")
240
+ }
241
+
242
+ // no-lone-blocks: `{ doStuff(); }` outside a control flow context —
243
+ // the braces add no scope (in non-strict mode) and obscure intent.
244
+ type noLoneBlocks struct{}
245
+
246
+ func (noLoneBlocks) Name() string { return "no-lone-blocks" }
247
+ func (noLoneBlocks) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBlock} }
248
+ func (noLoneBlocks) Check(ctx *Context, node *shimast.Node) {
249
+ parent := node.Parent
250
+ if parent == nil {
251
+ return
252
+ }
253
+ switch parent.Kind {
254
+ case shimast.KindBlock, shimast.KindSourceFile, shimast.KindModuleBlock:
255
+ default:
256
+ return
257
+ }
258
+ // Skip blocks that are themselves a function/method body — those
259
+ // are tracked by isFunctionLikeKind on the parent.
260
+ if isFunctionLikeKind(parent) {
261
+ return
262
+ }
263
+ block := node.AsBlock()
264
+ if block == nil || block.Statements == nil {
265
+ return
266
+ }
267
+ // Empty block is `no-empty`'s domain.
268
+ if len(block.Statements.Nodes) == 0 {
269
+ return
270
+ }
271
+ // Allow blocks whose only contents are block-scoped declarations
272
+ // (`{ const x = 1; }` is occasionally used to limit scope).
273
+ for _, stmt := range block.Statements.Nodes {
274
+ if stmt == nil {
275
+ continue
276
+ }
277
+ if stmt.Kind == shimast.KindVariableStatement {
278
+ vstmt := stmt.AsVariableStatement()
279
+ if vstmt != nil && vstmt.DeclarationList != nil && !shimast.IsVar(vstmt.DeclarationList) {
280
+ return
281
+ }
282
+ }
283
+ if stmt.Kind == shimast.KindClassDeclaration || stmt.Kind == shimast.KindFunctionDeclaration {
284
+ return
285
+ }
286
+ }
287
+ ctx.Report(node, "Block is redundant.")
288
+ }
289
+
290
+ // no-lonely-if: `else { if (...) {...} }` should be `else if (...)`.
291
+ type noLonelyIf struct{}
292
+
293
+ func (noLonelyIf) Name() string { return "no-lonely-if" }
294
+ func (noLonelyIf) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindIfStatement} }
295
+ func (noLonelyIf) Check(ctx *Context, node *shimast.Node) {
296
+ parent := node.Parent
297
+ if parent == nil || parent.Kind != shimast.KindBlock {
298
+ return
299
+ }
300
+ block := parent.AsBlock()
301
+ if block == nil || block.Statements == nil {
302
+ return
303
+ }
304
+ if len(block.Statements.Nodes) != 1 {
305
+ return
306
+ }
307
+ grand := parent.Parent
308
+ if grand == nil || grand.Kind != shimast.KindIfStatement {
309
+ return
310
+ }
311
+ gif := grand.AsIfStatement()
312
+ if gif == nil || gif.ElseStatement != parent {
313
+ return
314
+ }
315
+ ctx.Report(node, "Unexpected if as the only statement in an else block.")
316
+ }
317
+
318
+ // no-multi-assign: `a = b = 1`. Confusing right-to-left chains.
319
+ type noMultiAssign struct{}
320
+
321
+ func (noMultiAssign) Name() string { return "no-multi-assign" }
322
+ func (noMultiAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
323
+ func (noMultiAssign) Check(ctx *Context, node *shimast.Node) {
324
+ expr := node.AsBinaryExpression()
325
+ if expr == nil || expr.OperatorToken == nil {
326
+ return
327
+ }
328
+ if expr.OperatorToken.Kind != shimast.KindEqualsToken {
329
+ return
330
+ }
331
+ if expr.Right != nil && expr.Right.Kind == shimast.KindBinaryExpression {
332
+ inner := expr.Right.AsBinaryExpression()
333
+ if inner != nil && inner.OperatorToken != nil && inner.OperatorToken.Kind == shimast.KindEqualsToken {
334
+ ctx.Report(node, "Unexpected chained assignment.")
335
+ }
336
+ }
337
+ }
338
+
339
+ // no-negated-condition: `if (!x) {} else {}`. Easier to read with the
340
+ // branches swapped.
341
+ type noNegatedCondition struct{}
342
+
343
+ func (noNegatedCondition) Name() string { return "no-negated-condition" }
344
+ func (noNegatedCondition) Visits() []shimast.Kind {
345
+ return []shimast.Kind{shimast.KindIfStatement, shimast.KindConditionalExpression}
346
+ }
347
+ func (noNegatedCondition) Check(ctx *Context, node *shimast.Node) {
348
+ if node.Kind == shimast.KindIfStatement {
349
+ stmt := node.AsIfStatement()
350
+ if stmt == nil || stmt.ElseStatement == nil {
351
+ return
352
+ }
353
+ // Allow `else if` chains — the branches aren't symmetric.
354
+ if stmt.ElseStatement.Kind == shimast.KindIfStatement {
355
+ return
356
+ }
357
+ if isNegatedExpression(stmt.Expression) {
358
+ ctx.Report(node, "Unexpected negated condition.")
359
+ }
360
+ return
361
+ }
362
+ cond := node.AsConditionalExpression()
363
+ if cond == nil {
364
+ return
365
+ }
366
+ if isNegatedExpression(cond.Condition) {
367
+ ctx.Report(node, "Unexpected negated condition.")
368
+ }
369
+ }
370
+
371
+ func isNegatedExpression(node *shimast.Node) bool {
372
+ expr := stripParens(node)
373
+ if expr == nil {
374
+ return false
375
+ }
376
+ if expr.Kind == shimast.KindPrefixUnaryExpression {
377
+ prefix := expr.AsPrefixUnaryExpression()
378
+ if prefix != nil && prefix.Operator == shimast.KindExclamationToken {
379
+ return true
380
+ }
381
+ }
382
+ if expr.Kind == shimast.KindBinaryExpression {
383
+ bin := expr.AsBinaryExpression()
384
+ if bin != nil && bin.OperatorToken != nil {
385
+ switch bin.OperatorToken.Kind {
386
+ case shimast.KindExclamationEqualsToken, shimast.KindExclamationEqualsEqualsToken:
387
+ return true
388
+ }
389
+ }
390
+ }
391
+ return false
392
+ }
393
+
394
+ // no-nested-ternary: `a ? b : c ? d : e`.
395
+ type noNestedTernary struct{}
396
+
397
+ func (noNestedTernary) Name() string { return "no-nested-ternary" }
398
+ func (noNestedTernary) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindConditionalExpression} }
399
+ func (noNestedTernary) Check(ctx *Context, node *shimast.Node) {
400
+ cond := node.AsConditionalExpression()
401
+ if cond == nil {
402
+ return
403
+ }
404
+ if hasConditional(cond.WhenTrue) || hasConditional(cond.WhenFalse) {
405
+ ctx.Report(node, "Do not nest ternary expressions.")
406
+ }
407
+ }
408
+
409
+ func hasConditional(node *shimast.Node) bool {
410
+ expr := stripParens(node)
411
+ return expr != nil && expr.Kind == shimast.KindConditionalExpression
412
+ }
413
+
414
+ // no-new: `new Foo()` whose result is discarded. Either store it or
415
+ // avoid the constructor.
416
+ type noNew struct{}
417
+
418
+ func (noNew) Name() string { return "no-new" }
419
+ func (noNew) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindExpressionStatement} }
420
+ func (noNew) Check(ctx *Context, node *shimast.Node) {
421
+ stmt := node.AsExpressionStatement()
422
+ if stmt == nil || stmt.Expression == nil {
423
+ return
424
+ }
425
+ if stmt.Expression.Kind == shimast.KindNewExpression {
426
+ ctx.Report(node, "Do not use 'new' for side effects.")
427
+ }
428
+ }
429
+
430
+ // no-new-func: `new Function("...")` — a third form of dynamic eval.
431
+ type noNewFunc struct{}
432
+
433
+ func (noNewFunc) Name() string { return "no-new-func" }
434
+ func (noNewFunc) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindNewExpression, shimast.KindCallExpression} }
435
+ func (noNewFunc) Check(ctx *Context, node *shimast.Node) {
436
+ var callee *shimast.Node
437
+ if node.Kind == shimast.KindNewExpression {
438
+ callee = node.AsNewExpression().Expression
439
+ } else {
440
+ callee = node.AsCallExpression().Expression
441
+ }
442
+ if identifierText(callee) == "Function" {
443
+ ctx.Report(node, "The Function constructor is eval.")
444
+ }
445
+ }
446
+
447
+ // no-object-constructor: `new Object()` / `Object()` — same shape as
448
+ // no-array-constructor but for objects.
449
+ type noObjectConstructor struct{}
450
+
451
+ func (noObjectConstructor) Name() string { return "no-object-constructor" }
452
+ func (noObjectConstructor) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindNewExpression, shimast.KindCallExpression} }
453
+ func (noObjectConstructor) Check(ctx *Context, node *shimast.Node) {
454
+ var callee *shimast.Node
455
+ var argCount int
456
+ if node.Kind == shimast.KindNewExpression {
457
+ ne := node.AsNewExpression()
458
+ callee = ne.Expression
459
+ if ne.Arguments != nil {
460
+ argCount = len(ne.Arguments.Nodes)
461
+ }
462
+ } else {
463
+ call := node.AsCallExpression()
464
+ callee = call.Expression
465
+ if call.Arguments != nil {
466
+ argCount = len(call.Arguments.Nodes)
467
+ }
468
+ }
469
+ if argCount != 0 {
470
+ return // 1+ args is a "make a wrapper", not "make an empty object".
471
+ }
472
+ if identifierText(callee) == "Object" {
473
+ ctx.Report(node, "The object literal notation {} is preferable.")
474
+ }
475
+ }
476
+
477
+ // no-octal-escape: `"\251"` — octal escapes in string literals are
478
+ // deprecated and forbidden in template literals.
479
+ type noOctalEscape struct{}
480
+
481
+ func (noOctalEscape) Name() string { return "no-octal-escape" }
482
+ func (noOctalEscape) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral} }
483
+ func (noOctalEscape) Check(ctx *Context, node *shimast.Node) {
484
+ src := nodeText(ctx.File, node)
485
+ if hasOctalEscape(src) {
486
+ ctx.Report(node, "Don't use octal escape sequences.")
487
+ }
488
+ }
489
+
490
+ func hasOctalEscape(src string) bool {
491
+ for i := 0; i < len(src)-1; i++ {
492
+ if src[i] != '\\' {
493
+ continue
494
+ }
495
+ next := src[i+1]
496
+ // A literal `\0` followed by a non-digit is not an octal
497
+ // escape, just NUL — those are allowed.
498
+ if next < '0' || next > '7' {
499
+ i++
500
+ continue
501
+ }
502
+ if next == '0' {
503
+ if i+2 >= len(src) || src[i+2] < '0' || src[i+2] > '9' {
504
+ i++
505
+ continue
506
+ }
507
+ }
508
+ return true
509
+ }
510
+ return false
511
+ }
512
+
513
+ // no-plusplus: `++x` / `x++`. Equivalent to `x += 1`, considered less
514
+ // clear in some style guides.
515
+ type noPlusPlus struct{}
516
+
517
+ func (noPlusPlus) Name() string { return "no-plusplus" }
518
+ func (noPlusPlus) Visits() []shimast.Kind {
519
+ return []shimast.Kind{shimast.KindPrefixUnaryExpression, shimast.KindPostfixUnaryExpression}
520
+ }
521
+ func (noPlusPlus) Check(ctx *Context, node *shimast.Node) {
522
+ var op shimast.Kind
523
+ if node.Kind == shimast.KindPrefixUnaryExpression {
524
+ op = node.AsPrefixUnaryExpression().Operator
525
+ } else {
526
+ op = node.AsPostfixUnaryExpression().Operator
527
+ }
528
+ switch op {
529
+ case shimast.KindPlusPlusToken, shimast.KindMinusMinusToken:
530
+ ctx.Report(node, "Unary operator '++'/'--' used.")
531
+ }
532
+ }
533
+
534
+ // no-regex-spaces: multiple spaces in a regex literal — confusing
535
+ // because the count is invisible.
536
+ type noRegexSpaces struct{}
537
+
538
+ func (noRegexSpaces) Name() string { return "no-regex-spaces" }
539
+ func (noRegexSpaces) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindRegularExpressionLiteral} }
540
+ func (noRegexSpaces) Check(ctx *Context, node *shimast.Node) {
541
+ src := nodeText(ctx.File, node)
542
+ if regexHasMultipleSpaces(src) {
543
+ ctx.Report(node, "Spaces are hard to count. Use {N}.")
544
+ }
545
+ }
546
+
547
+ func regexHasMultipleSpaces(src string) bool {
548
+ // Strip trailing flags.
549
+ end := strings.LastIndex(src, "/")
550
+ if end <= 0 {
551
+ return false
552
+ }
553
+ body := src[:end]
554
+ inClass := false
555
+ run := 0
556
+ for i := 0; i < len(body); i++ {
557
+ c := body[i]
558
+ switch c {
559
+ case '\\':
560
+ i++
561
+ run = 0
562
+ case '[':
563
+ inClass = true
564
+ run = 0
565
+ case ']':
566
+ inClass = false
567
+ run = 0
568
+ case ' ':
569
+ if inClass {
570
+ run = 0
571
+ continue
572
+ }
573
+ run++
574
+ if run >= 2 {
575
+ return true
576
+ }
577
+ default:
578
+ run = 0
579
+ }
580
+ }
581
+ return false
582
+ }
583
+
584
+ // no-return-assign: `return a = b` mixes assignment with return.
585
+ type noReturnAssign struct{}
586
+
587
+ func (noReturnAssign) Name() string { return "no-return-assign" }
588
+ func (noReturnAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindReturnStatement, shimast.KindArrowFunction} }
589
+ func (noReturnAssign) Check(ctx *Context, node *shimast.Node) {
590
+ switch node.Kind {
591
+ case shimast.KindReturnStatement:
592
+ ret := node.AsReturnStatement()
593
+ if ret == nil || ret.Expression == nil {
594
+ return
595
+ }
596
+ if isAssignmentExpression(stripParens(ret.Expression)) {
597
+ ctx.Report(node, "Return statement should not contain assignment.")
598
+ }
599
+ case shimast.KindArrowFunction:
600
+ arrow := node.AsArrowFunction()
601
+ if arrow == nil || arrow.Body == nil || arrow.Body.Kind == shimast.KindBlock {
602
+ return
603
+ }
604
+ if isAssignmentExpression(stripParens(arrow.Body)) {
605
+ ctx.Report(node, "Arrow function should not return an assignment.")
606
+ }
607
+ }
608
+ }
609
+
610
+ // no-sequences: `(a, b)` — comma operator. Almost always a confusing
611
+ // pattern outside of `for` headers.
612
+ type noSequences struct{}
613
+
614
+ func (noSequences) Name() string { return "no-sequences" }
615
+ func (noSequences) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
616
+ func (noSequences) Check(ctx *Context, node *shimast.Node) {
617
+ expr := node.AsBinaryExpression()
618
+ if expr == nil || expr.OperatorToken == nil {
619
+ return
620
+ }
621
+ if expr.OperatorToken.Kind != shimast.KindCommaToken {
622
+ return
623
+ }
624
+ // `for (a; b; c)` headers naturally use the comma operator;
625
+ // suppress when the parent is a ForStatement initializer/incrementor.
626
+ parent := node.Parent
627
+ if parent != nil && parent.Kind == shimast.KindForStatement {
628
+ return
629
+ }
630
+ // Allow when wrapped in parens (the canonical "I really mean it"
631
+ // idiom).
632
+ if parent != nil && parent.Kind == shimast.KindParenthesizedExpression {
633
+ return
634
+ }
635
+ ctx.Report(node, "Unexpected use of comma operator.")
636
+ }
637
+
638
+ // no-shadow-restricted-names: redeclaring `undefined`, `NaN`, `Infinity`,
639
+ // `arguments`, or `eval`.
640
+ type noShadowRestrictedNames struct{}
641
+
642
+ func (noShadowRestrictedNames) Name() string { return "no-shadow-restricted-names" }
643
+ func (noShadowRestrictedNames) Visits() []shimast.Kind {
644
+ return []shimast.Kind{shimast.KindVariableDeclaration, shimast.KindParameter, shimast.KindFunctionDeclaration}
645
+ }
646
+ func (noShadowRestrictedNames) Check(ctx *Context, node *shimast.Node) {
647
+ var nameNode *shimast.Node
648
+ switch node.Kind {
649
+ case shimast.KindVariableDeclaration:
650
+ nameNode = node.AsVariableDeclaration().Name()
651
+ case shimast.KindParameter:
652
+ nameNode = node.AsParameterDeclaration().Name()
653
+ case shimast.KindFunctionDeclaration:
654
+ nameNode = node.AsFunctionDeclaration().Name()
655
+ }
656
+ name := identifierText(nameNode)
657
+ if name == "" {
658
+ return
659
+ }
660
+ switch name {
661
+ case "undefined", "NaN", "Infinity", "arguments", "eval":
662
+ ctx.Report(node, "Shadowing of global property '"+name+"'.")
663
+ }
664
+ }
665
+
666
+ // no-undefined: literal `undefined` (vs `void 0`). Easier to misuse
667
+ // because it's writable in older environments.
668
+ type noUndefined struct{}
669
+
670
+ func (noUndefined) Name() string { return "no-undefined" }
671
+ func (noUndefined) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindIdentifier} }
672
+ func (noUndefined) Check(ctx *Context, node *shimast.Node) {
673
+ if identifierText(node) != "undefined" {
674
+ return
675
+ }
676
+ parent := node.Parent
677
+ if parent == nil {
678
+ return
679
+ }
680
+ // Don't flag a declaration *named* `undefined` (no-shadow-restricted-names
681
+ // covers that), or a member-access / object-key position
682
+ // (`x.undefined`, `{ undefined: 1 }`).
683
+ switch parent.Kind {
684
+ case shimast.KindParameter:
685
+ decl := parent.AsParameterDeclaration()
686
+ if decl != nil && decl.Name() != nil && nodesShareLoc(decl.Name(), node) {
687
+ return
688
+ }
689
+ case shimast.KindVariableDeclaration:
690
+ decl := parent.AsVariableDeclaration()
691
+ if decl != nil && decl.Name() != nil && nodesShareLoc(decl.Name(), node) {
692
+ return
693
+ }
694
+ case shimast.KindPropertyAccessExpression:
695
+ access := parent.AsPropertyAccessExpression()
696
+ if access != nil && access.Name() != nil && nodesShareLoc(access.Name(), node) {
697
+ return
698
+ }
699
+ case shimast.KindPropertyAssignment:
700
+ assign := parent.AsPropertyAssignment()
701
+ if assign != nil && assign.Name() != nil && nodesShareLoc(assign.Name(), node) {
702
+ return
703
+ }
704
+ }
705
+ ctx.Report(node, "Unexpected use of undefined.")
706
+ }
707
+
708
+ // nodesShareLoc reports whether two `*ast.Node` references describe the
709
+ // same syntactic site. Identity comparison is unreliable when the
710
+ // parser exposes its fields through accessor methods that may return
711
+ // fresh wrappers; comparing positions works regardless.
712
+ func nodesShareLoc(a, b *shimast.Node) bool {
713
+ if a == nil || b == nil {
714
+ return false
715
+ }
716
+ return a == b || (a.Pos() == b.Pos() && a.End() == b.End())
717
+ }
718
+
719
+ // no-unneeded-ternary: `x ? true : false` → `Boolean(x)` / `!!x`.
720
+ type noUnneededTernary struct{}
721
+
722
+ func (noUnneededTernary) Name() string { return "no-unneeded-ternary" }
723
+ func (noUnneededTernary) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindConditionalExpression} }
724
+ func (noUnneededTernary) Check(ctx *Context, node *shimast.Node) {
725
+ cond := node.AsConditionalExpression()
726
+ if cond == nil {
727
+ return
728
+ }
729
+ t := stripParens(cond.WhenTrue)
730
+ f := stripParens(cond.WhenFalse)
731
+ tBool, tOk := isLiteralBoolean(t)
732
+ fBool, fOk := isLiteralBoolean(f)
733
+ if tOk && fOk && tBool != fBool {
734
+ ctx.Report(node, "Unnecessary use of conditional expression for boolean.")
735
+ }
736
+ }
737
+
738
+ // no-unused-expressions: an expression statement whose value isn't used.
739
+ // Filtered down to common patterns ESLint flags by default.
740
+ type noUnusedExpressions struct{}
741
+
742
+ func (noUnusedExpressions) Name() string { return "no-unused-expressions" }
743
+ func (noUnusedExpressions) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindExpressionStatement} }
744
+ func (noUnusedExpressions) Check(ctx *Context, node *shimast.Node) {
745
+ stmt := node.AsExpressionStatement()
746
+ if stmt == nil || stmt.Expression == nil {
747
+ return
748
+ }
749
+ if isProductiveExpression(stmt.Expression) {
750
+ return
751
+ }
752
+ ctx.Report(node, "Expected an assignment or function call and instead saw an expression.")
753
+ }
754
+
755
+ func isProductiveExpression(node *shimast.Node) bool {
756
+ expr := stripParens(node)
757
+ if expr == nil {
758
+ return false
759
+ }
760
+ switch expr.Kind {
761
+ case shimast.KindCallExpression,
762
+ shimast.KindNewExpression,
763
+ shimast.KindAwaitExpression,
764
+ shimast.KindYieldExpression,
765
+ shimast.KindDeleteExpression,
766
+ shimast.KindBinaryExpression,
767
+ shimast.KindPrefixUnaryExpression,
768
+ shimast.KindPostfixUnaryExpression,
769
+ shimast.KindTaggedTemplateExpression:
770
+ // These can have side effects. The narrower checks
771
+ // (no-cond-assign, no-bitwise) handle the suspicious shapes.
772
+ switch expr.Kind {
773
+ case shimast.KindBinaryExpression:
774
+ bin := expr.AsBinaryExpression()
775
+ if bin != nil && bin.OperatorToken != nil && isAssignmentOperator(bin.OperatorToken.Kind) {
776
+ return true
777
+ }
778
+ return false
779
+ case shimast.KindPrefixUnaryExpression:
780
+ prefix := expr.AsPrefixUnaryExpression()
781
+ if prefix != nil && (prefix.Operator == shimast.KindPlusPlusToken || prefix.Operator == shimast.KindMinusMinusToken) {
782
+ return true
783
+ }
784
+ return false
785
+ case shimast.KindPostfixUnaryExpression:
786
+ return true
787
+ }
788
+ return true
789
+ case shimast.KindStringLiteral:
790
+ // "use strict" prologue.
791
+ text := expr.AsStringLiteral()
792
+ if text != nil && (text.Text == "use strict" || text.Text == "use asm") {
793
+ return true
794
+ }
795
+ }
796
+ return false
797
+ }
798
+
799
+ // no-useless-call: `func.call(undefined, ...args)` / `func.apply(undefined, args)`
800
+ // — call/apply with no this binding is just a regular call.
801
+ type noUselessCall struct{}
802
+
803
+ func (noUselessCall) Name() string { return "no-useless-call" }
804
+ func (noUselessCall) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCallExpression} }
805
+ func (noUselessCall) Check(ctx *Context, node *shimast.Node) {
806
+ call := node.AsCallExpression()
807
+ if call == nil || call.Expression == nil || call.Expression.Kind != shimast.KindPropertyAccessExpression {
808
+ return
809
+ }
810
+ access := call.Expression.AsPropertyAccessExpression()
811
+ method := identifierText(access.Name())
812
+ if method != "call" && method != "apply" {
813
+ return
814
+ }
815
+ if call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
816
+ return
817
+ }
818
+ first := call.Arguments.Nodes[0]
819
+ first = stripParens(first)
820
+ if first == nil {
821
+ return
822
+ }
823
+ if first.Kind == shimast.KindNullKeyword || identifierText(first) == "undefined" {
824
+ ctx.Report(node, "Unnecessary "+method+"().")
825
+ }
826
+ }
827
+
828
+ // no-useless-computed-key: `{ ["foo"]: 1 }` could be `{ foo: 1 }`.
829
+ type noUselessComputedKey struct{}
830
+
831
+ func (noUselessComputedKey) Name() string { return "no-useless-computed-key" }
832
+ func (noUselessComputedKey) Visits() []shimast.Kind {
833
+ return []shimast.Kind{shimast.KindPropertyAssignment, shimast.KindMethodDeclaration}
834
+ }
835
+ func (noUselessComputedKey) Check(ctx *Context, node *shimast.Node) {
836
+ var name *shimast.Node
837
+ switch node.Kind {
838
+ case shimast.KindPropertyAssignment:
839
+ name = node.AsPropertyAssignment().Name()
840
+ case shimast.KindMethodDeclaration:
841
+ name = node.AsMethodDeclaration().Name()
842
+ }
843
+ if name == nil || name.Kind != shimast.KindComputedPropertyName {
844
+ return
845
+ }
846
+ computed := name.AsComputedPropertyName()
847
+ if computed == nil || computed.Expression == nil {
848
+ return
849
+ }
850
+ // Only fire when the computed key is a string / numeric / template
851
+ // literal — a bare identifier inside `[ ]` reads its *value* and is
852
+ // not equivalent to the same identifier as a static key.
853
+ expr := stripParens(computed.Expression)
854
+ switch expr.Kind {
855
+ case shimast.KindStringLiteral,
856
+ shimast.KindNoSubstitutionTemplateLiteral,
857
+ shimast.KindNumericLiteral,
858
+ shimast.KindBigIntLiteral:
859
+ ctx.Report(name, "Unnecessarily computed property key.")
860
+ }
861
+ }
862
+
863
+ // no-useless-rename: `import { x as x } from ...` / `const { x: x } = obj`
864
+ // — the rename is a no-op.
865
+ type noUselessRename struct{}
866
+
867
+ func (noUselessRename) Name() string { return "no-useless-rename" }
868
+ func (noUselessRename) Visits() []shimast.Kind {
869
+ return []shimast.Kind{shimast.KindImportSpecifier, shimast.KindExportSpecifier, shimast.KindBindingElement}
870
+ }
871
+ func (noUselessRename) Check(ctx *Context, node *shimast.Node) {
872
+ switch node.Kind {
873
+ case shimast.KindImportSpecifier:
874
+ spec := node.AsImportSpecifier()
875
+ if spec == nil || spec.PropertyName == nil {
876
+ return
877
+ }
878
+ if identifierText(spec.PropertyName) == identifierText(spec.Name()) {
879
+ ctx.Report(node, "Import { x as x } is redundant.")
880
+ }
881
+ case shimast.KindExportSpecifier:
882
+ spec := node.AsExportSpecifier()
883
+ if spec == nil || spec.PropertyName == nil {
884
+ return
885
+ }
886
+ if identifierText(spec.PropertyName) == identifierText(spec.Name()) {
887
+ ctx.Report(node, "Export { x as x } is redundant.")
888
+ }
889
+ case shimast.KindBindingElement:
890
+ el := node.AsBindingElement()
891
+ if el == nil || el.PropertyName == nil {
892
+ return
893
+ }
894
+ if identifierText(el.PropertyName) == identifierText(el.Name()) {
895
+ ctx.Report(node, "Destructuring rename to the same name is redundant.")
896
+ }
897
+ }
898
+ }
899
+
900
+ // object-shorthand: `{ x: x }` → `{ x }`.
901
+ type objectShorthand struct{}
902
+
903
+ func (objectShorthand) Name() string { return "object-shorthand" }
904
+ func (objectShorthand) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindPropertyAssignment} }
905
+ func (objectShorthand) Check(ctx *Context, node *shimast.Node) {
906
+ prop := node.AsPropertyAssignment()
907
+ if prop == nil || prop.Name() == nil || prop.Initializer == nil {
908
+ return
909
+ }
910
+ keyName := identifierText(prop.Name())
911
+ valueName := identifierText(prop.Initializer)
912
+ if keyName == "" || valueName == "" {
913
+ return
914
+ }
915
+ if keyName == valueName {
916
+ ctx.Report(node, "Expected property shorthand.")
917
+ }
918
+ }
919
+
920
+ // operator-assignment: `x = x + 1` → `x += 1`.
921
+ type operatorAssignment struct{}
922
+
923
+ func (operatorAssignment) Name() string { return "operator-assignment" }
924
+ func (operatorAssignment) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
925
+ func (operatorAssignment) Check(ctx *Context, node *shimast.Node) {
926
+ expr := node.AsBinaryExpression()
927
+ if expr == nil || expr.OperatorToken == nil {
928
+ return
929
+ }
930
+ if expr.OperatorToken.Kind != shimast.KindEqualsToken {
931
+ return
932
+ }
933
+ if expr.Right == nil || expr.Right.Kind != shimast.KindBinaryExpression {
934
+ return
935
+ }
936
+ right := expr.Right.AsBinaryExpression()
937
+ if right == nil || right.OperatorToken == nil {
938
+ return
939
+ }
940
+ if !isCompoundEligibleOperator(right.OperatorToken.Kind) {
941
+ return
942
+ }
943
+ if nodeText(ctx.File, expr.Left) == nodeText(ctx.File, right.Left) {
944
+ ctx.Report(node, "Assignment can be replaced with compound operator.")
945
+ }
946
+ }
947
+
948
+ func isCompoundEligibleOperator(kind shimast.Kind) bool {
949
+ switch kind {
950
+ case shimast.KindPlusToken, shimast.KindAsteriskToken, shimast.KindSlashToken,
951
+ shimast.KindAsteriskAsteriskToken, shimast.KindAmpersandToken, shimast.KindBarToken,
952
+ shimast.KindCaretToken:
953
+ return true
954
+ }
955
+ return false
956
+ }
957
+
958
+ // prefer-exponentiation-operator: `Math.pow(a, b)` → `a ** b`.
959
+ type preferExponentiationOperator struct{}
960
+
961
+ func (preferExponentiationOperator) Name() string { return "prefer-exponentiation-operator" }
962
+ func (preferExponentiationOperator) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCallExpression} }
963
+ func (preferExponentiationOperator) Check(ctx *Context, node *shimast.Node) {
964
+ call := node.AsCallExpression()
965
+ if call == nil {
966
+ return
967
+ }
968
+ if !isMatchingPropertyAccess(call.Expression, "Math", "pow") {
969
+ return
970
+ }
971
+ ctx.Report(node, "Use the '**' operator instead of 'Math.pow'.")
972
+ }
973
+
974
+ // prefer-spread: `fn.apply(null, args)` → `fn(...args)`.
975
+ type preferSpread struct{}
976
+
977
+ func (preferSpread) Name() string { return "prefer-spread" }
978
+ func (preferSpread) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCallExpression} }
979
+ func (preferSpread) Check(ctx *Context, node *shimast.Node) {
980
+ call := node.AsCallExpression()
981
+ if call == nil || call.Expression == nil {
982
+ return
983
+ }
984
+ if call.Expression.Kind != shimast.KindPropertyAccessExpression {
985
+ return
986
+ }
987
+ access := call.Expression.AsPropertyAccessExpression()
988
+ if access == nil || identifierText(access.Name()) != "apply" {
989
+ return
990
+ }
991
+ if call.Arguments == nil || len(call.Arguments.Nodes) != 2 {
992
+ return
993
+ }
994
+ first := stripParens(call.Arguments.Nodes[0])
995
+ if first == nil {
996
+ return
997
+ }
998
+ // ESLint default: only fire when the `this` arg is null/undefined,
999
+ // which is the canonical "I just want to spread" pattern.
1000
+ if first.Kind == shimast.KindNullKeyword || identifierText(first) == "undefined" {
1001
+ ctx.Report(node, "Use the spread operator instead of '.apply()'.")
1002
+ }
1003
+ }
1004
+
1005
+ // prefer-template: string concatenation that would read better as a
1006
+ // template literal — heuristic: any `+` involving a string literal AND
1007
+ // a non-literal.
1008
+ type preferTemplate struct{}
1009
+
1010
+ func (preferTemplate) Name() string { return "prefer-template" }
1011
+ func (preferTemplate) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
1012
+ func (preferTemplate) Check(ctx *Context, node *shimast.Node) {
1013
+ expr := node.AsBinaryExpression()
1014
+ if expr == nil || expr.OperatorToken == nil {
1015
+ return
1016
+ }
1017
+ if expr.OperatorToken.Kind != shimast.KindPlusToken {
1018
+ return
1019
+ }
1020
+ // Skip when the parent is also a string-concat — only the topmost
1021
+ // `+` chain emits one finding.
1022
+ parent := node.Parent
1023
+ if parent != nil && parent.Kind == shimast.KindBinaryExpression {
1024
+ parentBin := parent.AsBinaryExpression()
1025
+ if parentBin != nil && parentBin.OperatorToken != nil && parentBin.OperatorToken.Kind == shimast.KindPlusToken {
1026
+ return
1027
+ }
1028
+ }
1029
+ hasString, hasOther := concatChainShape(node)
1030
+ if hasString && hasOther {
1031
+ ctx.Report(node, "Unexpected string concatenation.")
1032
+ }
1033
+ }
1034
+
1035
+ func concatChainShape(node *shimast.Node) (hasString bool, hasOther bool) {
1036
+ if node == nil {
1037
+ return false, false
1038
+ }
1039
+ if node.Kind == shimast.KindBinaryExpression {
1040
+ bin := node.AsBinaryExpression()
1041
+ if bin != nil && bin.OperatorToken != nil && bin.OperatorToken.Kind == shimast.KindPlusToken {
1042
+ ls, lo := concatChainShape(bin.Left)
1043
+ rs, ro := concatChainShape(bin.Right)
1044
+ return ls || rs, lo || ro
1045
+ }
1046
+ }
1047
+ if isStringLikeLiteral(stripParens(node)) {
1048
+ return true, false
1049
+ }
1050
+ return false, true
1051
+ }
1052
+
1053
+ // require-yield: `function* gen() { return 1; }` — generators that
1054
+ // never yield are usually unintended.
1055
+ type requireYield struct{}
1056
+
1057
+ func (requireYield) Name() string { return "require-yield" }
1058
+ func (requireYield) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindFunctionDeclaration, shimast.KindFunctionExpression, shimast.KindMethodDeclaration} }
1059
+ func (requireYield) Check(ctx *Context, node *shimast.Node) {
1060
+ if !hasAsteriskModifier(node) {
1061
+ return
1062
+ }
1063
+ body := node.Body()
1064
+ if body == nil {
1065
+ return
1066
+ }
1067
+ if !subtreeContainsYield(body) {
1068
+ ctx.Report(node, "This generator function does not have 'yield'.")
1069
+ }
1070
+ }
1071
+
1072
+ func hasAsteriskModifier(node *shimast.Node) bool {
1073
+ if node == nil {
1074
+ return false
1075
+ }
1076
+ switch node.Kind {
1077
+ case shimast.KindFunctionDeclaration:
1078
+ decl := node.AsFunctionDeclaration()
1079
+ return decl != nil && decl.AsteriskToken != nil
1080
+ case shimast.KindFunctionExpression:
1081
+ decl := node.AsFunctionExpression()
1082
+ return decl != nil && decl.AsteriskToken != nil
1083
+ case shimast.KindMethodDeclaration:
1084
+ decl := node.AsMethodDeclaration()
1085
+ return decl != nil && decl.AsteriskToken != nil
1086
+ }
1087
+ return false
1088
+ }
1089
+
1090
+ func subtreeContainsYield(node *shimast.Node) bool {
1091
+ if node == nil {
1092
+ return false
1093
+ }
1094
+ if node.Kind == shimast.KindYieldExpression {
1095
+ return true
1096
+ }
1097
+ if isFunctionLikeKind(node) && node.Parent != nil {
1098
+ return false
1099
+ }
1100
+ found := false
1101
+ node.ForEachChild(func(child *shimast.Node) bool {
1102
+ if subtreeContainsYield(child) {
1103
+ found = true
1104
+ return true
1105
+ }
1106
+ return false
1107
+ })
1108
+ return found
1109
+ }
1110
+
1111
+ // vars-on-top: `var` declarations should appear at the top of their
1112
+ // function/script scope.
1113
+ type varsOnTop struct{}
1114
+
1115
+ func (varsOnTop) Name() string { return "vars-on-top" }
1116
+ func (varsOnTop) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindVariableStatement} }
1117
+ func (varsOnTop) Check(ctx *Context, node *shimast.Node) {
1118
+ stmt := node.AsVariableStatement()
1119
+ if stmt == nil || stmt.DeclarationList == nil {
1120
+ return
1121
+ }
1122
+ if !shimast.IsVar(stmt.DeclarationList) {
1123
+ return
1124
+ }
1125
+ parent := node.Parent
1126
+ if parent == nil {
1127
+ return
1128
+ }
1129
+ switch parent.Kind {
1130
+ case shimast.KindSourceFile, shimast.KindModuleBlock:
1131
+ case shimast.KindBlock:
1132
+ grand := parent.Parent
1133
+ if grand == nil || !isFunctionLikeKind(grand) {
1134
+ ctx.Report(node, "All 'var' declarations must be at the top of the function scope.")
1135
+ return
1136
+ }
1137
+ default:
1138
+ ctx.Report(node, "All 'var' declarations must be at the top of the function scope.")
1139
+ return
1140
+ }
1141
+ // Same-block: must be the first non-trivial statement.
1142
+ siblings := parentStatements(parent)
1143
+ for _, sib := range siblings {
1144
+ if sib == node {
1145
+ return
1146
+ }
1147
+ if sib.Kind == shimast.KindVariableStatement {
1148
+ continue
1149
+ }
1150
+ ctx.Report(node, "All 'var' declarations must be at the top of the function scope.")
1151
+ return
1152
+ }
1153
+ }
1154
+
1155
+ func parentStatements(parent *shimast.Node) []*shimast.Node {
1156
+ if parent == nil {
1157
+ return nil
1158
+ }
1159
+ switch parent.Kind {
1160
+ case shimast.KindBlock:
1161
+ block := parent.AsBlock()
1162
+ if block != nil && block.Statements != nil {
1163
+ return block.Statements.Nodes
1164
+ }
1165
+ case shimast.KindSourceFile:
1166
+ file := parent.AsSourceFile()
1167
+ if file != nil && file.Statements != nil {
1168
+ return file.Statements.Nodes
1169
+ }
1170
+ case shimast.KindModuleBlock:
1171
+ mb := parent.AsModuleBlock()
1172
+ if mb != nil && mb.Statements != nil {
1173
+ return mb.Statements.Nodes
1174
+ }
1175
+ }
1176
+ return nil
1177
+ }
1178
+
1179
+ // yoda: `if (1 === x)` — ESLint flags literals on the left of a
1180
+ // comparison as "yoda conditions". Default mode forbids them.
1181
+ type yoda struct{}
1182
+
1183
+ func (yoda) Name() string { return "yoda" }
1184
+ func (yoda) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
1185
+ func (yoda) Check(ctx *Context, node *shimast.Node) {
1186
+ expr := node.AsBinaryExpression()
1187
+ if expr == nil || expr.OperatorToken == nil {
1188
+ return
1189
+ }
1190
+ if !isComparisonOperator(expr.OperatorToken.Kind) {
1191
+ return
1192
+ }
1193
+ if isLiteralExpression(stripParens(expr.Left)) && !isLiteralExpression(stripParens(expr.Right)) {
1194
+ ctx.Report(node, "Expected literal to be on the right side of comparison.")
1195
+ }
1196
+ }
1197
+
1198
+ func init() {
1199
+ Register(noAlert{})
1200
+ Register(noBitwise{})
1201
+ Register(noCaller{})
1202
+ Register(noCaseDeclarations{})
1203
+ Register(noContinue{})
1204
+ Register(noDeleteVar{})
1205
+ Register(noEqNull{})
1206
+ Register(noExtraBind{})
1207
+ Register(noLabels{})
1208
+ Register(noLoneBlocks{})
1209
+ Register(noLonelyIf{})
1210
+ Register(noMultiAssign{})
1211
+ Register(noNegatedCondition{})
1212
+ Register(noNestedTernary{})
1213
+ Register(noNew{})
1214
+ Register(noNewFunc{})
1215
+ Register(noObjectConstructor{})
1216
+ Register(noOctalEscape{})
1217
+ Register(noPlusPlus{})
1218
+ Register(noRegexSpaces{})
1219
+ Register(noReturnAssign{})
1220
+ Register(noSequences{})
1221
+ Register(noShadowRestrictedNames{})
1222
+ Register(noUndefined{})
1223
+ Register(noUnneededTernary{})
1224
+ Register(noUnusedExpressions{})
1225
+ Register(noUselessCall{})
1226
+ Register(noUselessComputedKey{})
1227
+ Register(noUselessRename{})
1228
+ Register(objectShorthand{})
1229
+ Register(operatorAssignment{})
1230
+ Register(preferExponentiationOperator{})
1231
+ Register(preferSpread{})
1232
+ Register(preferTemplate{})
1233
+ Register(requireYield{})
1234
+ Register(varsOnTop{})
1235
+ Register(yoda{})
1236
+ }