@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,571 @@
1
+ // Bulk implementation of ESLint's "Possible Problems" category.
2
+ //
3
+ // Each rule keeps to pure-AST checks (no scope analysis, no checker
4
+ // queries beyond what's already in the rule's own walk) so they remain
5
+ // fast and predictable. Rules that require scope binding or
6
+ // flow-sensitive analysis are intentionally not implemented here —
7
+ // those are upstream's job.
8
+ package lint
9
+
10
+ import (
11
+ "strings"
12
+
13
+ shimast "github.com/microsoft/typescript-go/shim/ast"
14
+ )
15
+
16
+ // no-dupe-else-if: `if (a) {} else if (a) {}` — the second branch is
17
+ // unreachable.
18
+ type noDupeElseIf struct{}
19
+
20
+ func (noDupeElseIf) Name() string { return "no-dupe-else-if" }
21
+ func (noDupeElseIf) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindIfStatement} }
22
+ func (noDupeElseIf) Check(ctx *Context, node *shimast.Node) {
23
+ // Only fire on the *outermost* if; the recursion below scans the
24
+ // chain once.
25
+ if parent := node.Parent; parent != nil {
26
+ if parent.Kind == shimast.KindIfStatement {
27
+ outer := parent.AsIfStatement()
28
+ if outer != nil && outer.ElseStatement == node {
29
+ return
30
+ }
31
+ }
32
+ }
33
+ seen := map[string]bool{}
34
+ cur := node
35
+ for cur != nil && cur.Kind == shimast.KindIfStatement {
36
+ stmt := cur.AsIfStatement()
37
+ if stmt == nil || stmt.Expression == nil {
38
+ break
39
+ }
40
+ key := nodeText(ctx.File, stmt.Expression)
41
+ if key != "" {
42
+ if seen[key] {
43
+ ctx.Report(stmt.Expression, "This branch can never execute. Its condition is a duplicate of an earlier branch.")
44
+ } else {
45
+ seen[key] = true
46
+ }
47
+ }
48
+ cur = stmt.ElseStatement
49
+ }
50
+ }
51
+
52
+ // no-ex-assign: `try { } catch (e) { e = 1; }` — reassigning the catch
53
+ // binding silently throws away the error.
54
+ type noExAssign struct{}
55
+
56
+ func (noExAssign) Name() string { return "no-ex-assign" }
57
+ func (noExAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCatchClause} }
58
+ func (noExAssign) Check(ctx *Context, node *shimast.Node) {
59
+ clause := node.AsCatchClause()
60
+ if clause == nil || clause.VariableDeclaration == nil || clause.Block == nil {
61
+ return
62
+ }
63
+ binding := clause.VariableDeclaration.AsVariableDeclaration()
64
+ if binding == nil {
65
+ return
66
+ }
67
+ name := identifierText(binding.Name())
68
+ if name == "" {
69
+ return
70
+ }
71
+ walkAssignments(clause.Block, name, func(target *shimast.Node) {
72
+ ctx.Report(target, "Do not assign to the exception parameter.")
73
+ })
74
+ }
75
+
76
+ // walkAssignments invokes `report` on every `<name> = ...` shape inside
77
+ // `root`. Used by no-ex-assign and friends.
78
+ func walkAssignments(root *shimast.Node, name string, report func(*shimast.Node)) {
79
+ if root == nil {
80
+ return
81
+ }
82
+ root.ForEachChild(func(child *shimast.Node) bool {
83
+ if child == nil {
84
+ return false
85
+ }
86
+ if child.Kind == shimast.KindBinaryExpression {
87
+ expr := child.AsBinaryExpression()
88
+ if expr != nil && expr.OperatorToken != nil && isAssignmentOperator(expr.OperatorToken.Kind) {
89
+ if identifierText(expr.Left) == name {
90
+ report(expr.Left)
91
+ }
92
+ }
93
+ }
94
+ walkAssignments(child, name, report)
95
+ return false
96
+ })
97
+ }
98
+
99
+ // no-empty-character-class: `/[]/` matches nothing.
100
+ type noEmptyCharacterClass struct{}
101
+
102
+ func (noEmptyCharacterClass) Name() string { return "no-empty-character-class" }
103
+ func (noEmptyCharacterClass) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindRegularExpressionLiteral} }
104
+ func (noEmptyCharacterClass) Check(ctx *Context, node *shimast.Node) {
105
+ src := nodeText(ctx.File, node)
106
+ if hasEmptyCharClass(src) {
107
+ ctx.Report(node, "Empty class.")
108
+ }
109
+ }
110
+
111
+ func hasEmptyCharClass(src string) bool {
112
+ // Walk the regex literal source manually, respecting escapes.
113
+ for i := 0; i < len(src); i++ {
114
+ switch src[i] {
115
+ case '\\':
116
+ i++ // skip escape
117
+ case '[':
118
+ j := i + 1
119
+ if j < len(src) && src[j] == '^' {
120
+ j++
121
+ }
122
+ if j < len(src) && src[j] == ']' {
123
+ return true
124
+ }
125
+ }
126
+ }
127
+ return false
128
+ }
129
+
130
+ // no-misleading-character-class: `/[👍]/` — surrogate pairs in regex.
131
+ type noMisleadingCharacterClass struct{}
132
+
133
+ func (noMisleadingCharacterClass) Name() string { return "no-misleading-character-class" }
134
+ func (noMisleadingCharacterClass) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindRegularExpressionLiteral} }
135
+ func (noMisleadingCharacterClass) Check(ctx *Context, node *shimast.Node) {
136
+ src := nodeText(ctx.File, node)
137
+ if regexHasSurrogatePair(src) {
138
+ ctx.Report(node, "Unexpected surrogate pair in character class. Use the 'u' flag.")
139
+ }
140
+ }
141
+
142
+ func regexHasSurrogatePair(src string) bool {
143
+ // Strip the trailing flags so we don't misread the `u` flag — it
144
+ // suppresses this rule.
145
+ end := strings.LastIndex(src, "/")
146
+ if end < 0 {
147
+ return false
148
+ }
149
+ flags := src[end+1:]
150
+ if strings.ContainsRune(flags, 'u') {
151
+ return false
152
+ }
153
+ body := src[:end]
154
+ in := false
155
+ for _, r := range body {
156
+ switch r {
157
+ case '[':
158
+ in = true
159
+ case ']':
160
+ in = false
161
+ }
162
+ if in && r >= 0x10000 {
163
+ return true
164
+ }
165
+ }
166
+ return false
167
+ }
168
+
169
+ // no-loss-of-precision: `9007199254740993` — integer literal larger than
170
+ // `Number.MAX_SAFE_INTEGER`. We read the *source* form (not the parser's
171
+ // normalized .Text, which has already lost precision) and decide off
172
+ // that.
173
+ type noLossOfPrecision struct{}
174
+
175
+ func (noLossOfPrecision) Name() string { return "no-loss-of-precision" }
176
+ func (noLossOfPrecision) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindNumericLiteral} }
177
+ func (noLossOfPrecision) Check(ctx *Context, node *shimast.Node) {
178
+ source := strings.TrimSpace(nodeText(ctx.File, node))
179
+ if source == "" {
180
+ return
181
+ }
182
+ if numericLiteralLosesPrecision(source) {
183
+ ctx.Report(node, "This number literal will lose precision at runtime.")
184
+ }
185
+ }
186
+
187
+ func numericLiteralLosesPrecision(text string) bool {
188
+ // Strip underscore separators, exponents, decimal/hex/oct/binary
189
+ // markers — for the simple-base-10 integer case the round-trip
190
+ // parse → format check is sufficient.
191
+ clean := strings.ReplaceAll(text, "_", "")
192
+ if strings.ContainsAny(clean, "eE.xXoObB") {
193
+ return false
194
+ }
195
+ if len(clean) < 16 {
196
+ return false
197
+ }
198
+ // Trim leading zeros for comparison.
199
+ trimmed := strings.TrimLeft(clean, "0")
200
+ if trimmed == "" {
201
+ return false
202
+ }
203
+ // 2^53 = 9007199254740992; anything larger as an integer literal loses precision.
204
+ const maxSafe = "9007199254740992"
205
+ if len(trimmed) < len(maxSafe) {
206
+ return false
207
+ }
208
+ if len(trimmed) > len(maxSafe) {
209
+ return true
210
+ }
211
+ return trimmed > maxSafe
212
+ }
213
+
214
+ // no-class-assign: assigning to a class declaration's name.
215
+ type noClassAssign struct{}
216
+
217
+ func (noClassAssign) Name() string { return "no-class-assign" }
218
+ func (noClassAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindClassDeclaration} }
219
+ func (noClassAssign) Check(ctx *Context, node *shimast.Node) {
220
+ decl := node.AsClassDeclaration()
221
+ if decl == nil || decl.Name() == nil {
222
+ return
223
+ }
224
+ name := identifierText(decl.Name())
225
+ if name == "" {
226
+ return
227
+ }
228
+ walkAssignments(ctx.File.AsNode(), name, func(target *shimast.Node) {
229
+ ctx.Report(target, "'"+name+"' is a class.")
230
+ })
231
+ }
232
+
233
+ // no-func-assign: same idea, but for function declarations.
234
+ type noFuncAssign struct{}
235
+
236
+ func (noFuncAssign) Name() string { return "no-func-assign" }
237
+ func (noFuncAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindFunctionDeclaration} }
238
+ func (noFuncAssign) Check(ctx *Context, node *shimast.Node) {
239
+ decl := node.AsFunctionDeclaration()
240
+ if decl == nil || decl.Name() == nil {
241
+ return
242
+ }
243
+ name := identifierText(decl.Name())
244
+ if name == "" {
245
+ return
246
+ }
247
+ walkAssignments(ctx.File.AsNode(), name, func(target *shimast.Node) {
248
+ ctx.Report(target, "'"+name+"' is a function.")
249
+ })
250
+ }
251
+
252
+ // no-prototype-builtins: `obj.hasOwnProperty(x)` — should be
253
+ // `Object.prototype.hasOwnProperty.call(obj, x)` or `Object.hasOwn`.
254
+ type noPrototypeBuiltins struct{}
255
+
256
+ func (noPrototypeBuiltins) Name() string { return "no-prototype-builtins" }
257
+ func (noPrototypeBuiltins) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCallExpression} }
258
+ func (noPrototypeBuiltins) Check(ctx *Context, node *shimast.Node) {
259
+ call := node.AsCallExpression()
260
+ if call == nil || call.Expression == nil {
261
+ return
262
+ }
263
+ if call.Expression.Kind != shimast.KindPropertyAccessExpression {
264
+ return
265
+ }
266
+ access := call.Expression.AsPropertyAccessExpression()
267
+ if access == nil {
268
+ return
269
+ }
270
+ method := identifierText(access.Name())
271
+ switch method {
272
+ case "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable":
273
+ ctx.Report(node, "Do not access Object.prototype method '"+method+"' from target object.")
274
+ }
275
+ }
276
+
277
+ // no-async-promise-executor: `new Promise(async (resolve) => {...})`.
278
+ type noAsyncPromiseExecutor struct{}
279
+
280
+ func (noAsyncPromiseExecutor) Name() string { return "no-async-promise-executor" }
281
+ func (noAsyncPromiseExecutor) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindNewExpression} }
282
+ func (noAsyncPromiseExecutor) Check(ctx *Context, node *shimast.Node) {
283
+ ne := node.AsNewExpression()
284
+ if ne == nil || identifierText(ne.Expression) != "Promise" {
285
+ return
286
+ }
287
+ if ne.Arguments == nil || len(ne.Arguments.Nodes) == 0 {
288
+ return
289
+ }
290
+ executor := ne.Arguments.Nodes[0]
291
+ if executor == nil {
292
+ return
293
+ }
294
+ if !isFunctionLikeKind(executor) {
295
+ return
296
+ }
297
+ if hasAsyncModifier(executor) {
298
+ ctx.Report(executor, "Promise executor functions should not be async.")
299
+ }
300
+ }
301
+
302
+ // no-promise-executor-return: `new Promise(() => 1)` — the value is
303
+ // thrown away.
304
+ type noPromiseExecutorReturn struct{}
305
+
306
+ func (noPromiseExecutorReturn) Name() string { return "no-promise-executor-return" }
307
+ func (noPromiseExecutorReturn) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindNewExpression} }
308
+ func (noPromiseExecutorReturn) Check(ctx *Context, node *shimast.Node) {
309
+ ne := node.AsNewExpression()
310
+ if ne == nil || identifierText(ne.Expression) != "Promise" {
311
+ return
312
+ }
313
+ if ne.Arguments == nil || len(ne.Arguments.Nodes) == 0 {
314
+ return
315
+ }
316
+ executor := ne.Arguments.Nodes[0]
317
+ if executor == nil || executor.Kind != shimast.KindArrowFunction {
318
+ return
319
+ }
320
+ arrow := executor.AsArrowFunction()
321
+ if arrow == nil || arrow.Body == nil {
322
+ return
323
+ }
324
+ // Concise arrow body returns the value implicitly.
325
+ if arrow.Body.Kind != shimast.KindBlock {
326
+ ctx.Report(arrow.Body, "Return values from promise executor functions cannot be read.")
327
+ }
328
+ }
329
+
330
+ // no-control-regex: `/\x00/` — control characters in regex are usually
331
+ // the result of accidentally typing the escape rather than the printable
332
+ // counterpart.
333
+ type noControlRegex struct{}
334
+
335
+ func (noControlRegex) Name() string { return "no-control-regex" }
336
+ func (noControlRegex) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindRegularExpressionLiteral} }
337
+ func (noControlRegex) Check(ctx *Context, node *shimast.Node) {
338
+ src := nodeText(ctx.File, node)
339
+ if regexContainsControl(src) {
340
+ ctx.Report(node, "Unexpected control character(s) in regular expression.")
341
+ }
342
+ }
343
+
344
+ func regexContainsControl(src string) bool {
345
+ for i := 0; i < len(src); i++ {
346
+ c := src[i]
347
+ if c == '\\' && i+1 < len(src) {
348
+ next := src[i+1]
349
+ if next == 'x' && i+3 < len(src) {
350
+ value := hexDigit(src[i+2])*16 + hexDigit(src[i+3])
351
+ if value >= 0 && value < 0x20 {
352
+ return true
353
+ }
354
+ i += 3
355
+ continue
356
+ }
357
+ if next == 'u' && i+5 < len(src) {
358
+ value := hexDigit(src[i+2])*4096 + hexDigit(src[i+3])*256 + hexDigit(src[i+4])*16 + hexDigit(src[i+5])
359
+ if value >= 0 && value < 0x20 {
360
+ return true
361
+ }
362
+ i += 5
363
+ continue
364
+ }
365
+ i++
366
+ continue
367
+ }
368
+ if c < 0x20 && c != '\t' && c != '\n' && c != '\r' {
369
+ return true
370
+ }
371
+ }
372
+ return false
373
+ }
374
+
375
+ func hexDigit(b byte) int {
376
+ switch {
377
+ case b >= '0' && b <= '9':
378
+ return int(b - '0')
379
+ case b >= 'a' && b <= 'f':
380
+ return int(b-'a') + 10
381
+ case b >= 'A' && b <= 'F':
382
+ return int(b-'A') + 10
383
+ }
384
+ return -1
385
+ }
386
+
387
+ // no-irregular-whitespace: zero-width spaces, NBSP, etc. The TS parser
388
+ // accepts them but copy-paste into source is almost always a mistake.
389
+ type noIrregularWhitespace struct{}
390
+
391
+ func (noIrregularWhitespace) Name() string { return "no-irregular-whitespace" }
392
+ func (noIrregularWhitespace) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
393
+ func (noIrregularWhitespace) Check(ctx *Context, node *shimast.Node) {
394
+ if ctx.File == nil {
395
+ return
396
+ }
397
+ text := ctx.File.Text()
398
+ for i, r := range text {
399
+ if isIrregularWhitespace(r) {
400
+ ctx.ReportRange(i, i+len(string(r)), "Irregular whitespace not allowed.")
401
+ }
402
+ }
403
+ }
404
+
405
+ func isIrregularWhitespace(r rune) bool {
406
+ switch r {
407
+ case '\v', '\f',
408
+ 0x00A0, 0x1680,
409
+ 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005,
410
+ 0x2006, 0x2007, 0x2008, 0x2009, 0x200A,
411
+ 0x200B, 0x202F, 0x205F,
412
+ 0x3000,
413
+ 0x2028, 0x2029,
414
+ 0xFEFF:
415
+ return true
416
+ }
417
+ return false
418
+ }
419
+
420
+ // no-fallthrough: `switch` cases that fall through to the next label
421
+ // without an explicit `break` / `return` / `throw` / `continue`.
422
+ type noFallthrough struct{}
423
+
424
+ func (noFallthrough) Name() string { return "no-fallthrough" }
425
+ func (noFallthrough) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSwitchStatement} }
426
+ func (noFallthrough) Check(ctx *Context, node *shimast.Node) {
427
+ sw := node.AsSwitchStatement()
428
+ if sw == nil || sw.CaseBlock == nil {
429
+ return
430
+ }
431
+ block := sw.CaseBlock.AsCaseBlock()
432
+ if block == nil || block.Clauses == nil {
433
+ return
434
+ }
435
+ clauses := block.Clauses.Nodes
436
+ for i := 0; i+1 < len(clauses); i++ {
437
+ clause := clauses[i].AsCaseOrDefaultClause()
438
+ if clause == nil || clause.Statements == nil {
439
+ continue
440
+ }
441
+ stmts := clause.Statements.Nodes
442
+ if len(stmts) == 0 {
443
+ continue // empty case is intentional, never a fallthrough.
444
+ }
445
+ if !isTerminating(stmts[len(stmts)-1]) {
446
+ ctx.Report(clauses[i+1], "Expected a 'break' statement before this case.")
447
+ }
448
+ }
449
+ }
450
+
451
+ func isTerminating(stmt *shimast.Node) bool {
452
+ if stmt == nil {
453
+ return false
454
+ }
455
+ switch stmt.Kind {
456
+ case shimast.KindBreakStatement,
457
+ shimast.KindContinueStatement,
458
+ shimast.KindReturnStatement,
459
+ shimast.KindThrowStatement:
460
+ return true
461
+ case shimast.KindBlock:
462
+ block := stmt.AsBlock()
463
+ if block == nil || block.Statements == nil {
464
+ return false
465
+ }
466
+ nodes := block.Statements.Nodes
467
+ if len(nodes) == 0 {
468
+ return false
469
+ }
470
+ return isTerminating(nodes[len(nodes)-1])
471
+ }
472
+ return false
473
+ }
474
+
475
+ // no-inner-declarations: `function foo() { if (x) { function bar() {} } }`
476
+ // — inner function declarations are hoisted differently in strict mode
477
+ // vs sloppy and are confusing.
478
+ type noInnerDeclarations struct{}
479
+
480
+ func (noInnerDeclarations) Name() string { return "no-inner-declarations" }
481
+ func (noInnerDeclarations) Visits() []shimast.Kind {
482
+ return []shimast.Kind{shimast.KindFunctionDeclaration, shimast.KindVariableStatement}
483
+ }
484
+ func (noInnerDeclarations) Check(ctx *Context, node *shimast.Node) {
485
+ if node.Kind == shimast.KindVariableStatement {
486
+ stmt := node.AsVariableStatement()
487
+ if stmt == nil || stmt.DeclarationList == nil {
488
+ return
489
+ }
490
+ // Only `var` is hoisted oddly.
491
+ if !shimast.IsVar(stmt.DeclarationList) {
492
+ return
493
+ }
494
+ }
495
+ parent := node.Parent
496
+ if parent == nil {
497
+ return
498
+ }
499
+ switch parent.Kind {
500
+ case shimast.KindSourceFile, shimast.KindModuleBlock:
501
+ return
502
+ case shimast.KindBlock:
503
+ grand := parent.Parent
504
+ if grand == nil {
505
+ return
506
+ }
507
+ if isFunctionLikeKind(grand) {
508
+ return
509
+ }
510
+ }
511
+ what := "function"
512
+ if node.Kind == shimast.KindVariableStatement {
513
+ what = "variable"
514
+ }
515
+ ctx.Report(node, "Move "+what+" declaration to the function scope.")
516
+ }
517
+
518
+ // no-obj-calls: `Math()`, `JSON()` — these globals are objects, not
519
+ // callables. ESLint catches a small list.
520
+ type noObjCalls struct{}
521
+
522
+ func (noObjCalls) Name() string { return "no-obj-calls" }
523
+ func (noObjCalls) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCallExpression, shimast.KindNewExpression} }
524
+ func (noObjCalls) Check(ctx *Context, node *shimast.Node) {
525
+ var callee *shimast.Node
526
+ if node.Kind == shimast.KindCallExpression {
527
+ callee = node.AsCallExpression().Expression
528
+ } else {
529
+ callee = node.AsNewExpression().Expression
530
+ }
531
+ switch identifierText(callee) {
532
+ case "Math", "JSON", "Reflect", "Atomics", "Intl":
533
+ ctx.Report(node, "'"+identifierText(callee)+"' is not a function.")
534
+ }
535
+ }
536
+
537
+ // hasAsyncModifier returns whether a function-like node carries the
538
+ // `async` keyword. Used by no-async-promise-executor.
539
+ func hasAsyncModifier(node *shimast.Node) bool {
540
+ if node == nil {
541
+ return false
542
+ }
543
+ mods := node.Modifiers()
544
+ if mods == nil {
545
+ return false
546
+ }
547
+ for _, m := range mods.Nodes {
548
+ if m != nil && m.Kind == shimast.KindAsyncKeyword {
549
+ return true
550
+ }
551
+ }
552
+ return false
553
+ }
554
+
555
+ func init() {
556
+ Register(noDupeElseIf{})
557
+ Register(noExAssign{})
558
+ Register(noEmptyCharacterClass{})
559
+ Register(noMisleadingCharacterClass{})
560
+ Register(noLossOfPrecision{})
561
+ Register(noClassAssign{})
562
+ Register(noFuncAssign{})
563
+ Register(noPrototypeBuiltins{})
564
+ Register(noAsyncPromiseExecutor{})
565
+ Register(noPromiseExecutorReturn{})
566
+ Register(noControlRegex{})
567
+ Register(noIrregularWhitespace{})
568
+ Register(noFallthrough{})
569
+ Register(noInnerDeclarations{})
570
+ Register(noObjCalls{})
571
+ }
@@ -0,0 +1,42 @@
1
+ package lint
2
+
3
+ import shimast "github.com/microsoft/typescript-go/shim/ast"
4
+
5
+ // no-iterator: `obj.__iterator__` is a non-standard SpiderMonkey-era
6
+ // access. Use `Symbol.iterator` instead.
7
+ // https://eslint.org/docs/latest/rules/no-iterator
8
+ type noIterator struct{}
9
+
10
+ func (noIterator) Name() string { return "no-iterator" }
11
+ func (noIterator) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindPropertyAccessExpression} }
12
+ func (noIterator) Check(ctx *Context, node *shimast.Node) {
13
+ access := node.AsPropertyAccessExpression()
14
+ if access == nil {
15
+ return
16
+ }
17
+ if identifierText(access.Name()) == "__iterator__" {
18
+ ctx.Report(node, "Reserved name '__iterator__'.")
19
+ }
20
+ }
21
+
22
+ // no-proto: `obj.__proto__` access is legacy. Use `Object.getPrototypeOf`
23
+ // / `Object.setPrototypeOf`.
24
+ // https://eslint.org/docs/latest/rules/no-proto
25
+ type noProto struct{}
26
+
27
+ func (noProto) Name() string { return "no-proto" }
28
+ func (noProto) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindPropertyAccessExpression} }
29
+ func (noProto) Check(ctx *Context, node *shimast.Node) {
30
+ access := node.AsPropertyAccessExpression()
31
+ if access == nil {
32
+ return
33
+ }
34
+ if identifierText(access.Name()) == "__proto__" {
35
+ ctx.Report(node, "The '__proto__' property is deprecated.")
36
+ }
37
+ }
38
+
39
+ func init() {
40
+ Register(noIterator{})
41
+ Register(noProto{})
42
+ }
@@ -0,0 +1,89 @@
1
+ package lint
2
+
3
+ import shimast "github.com/microsoft/typescript-go/shim/ast"
4
+
5
+ // no-self-assign: detect `x = x` / `obj.foo = obj.foo`. Limited to the
6
+ // cheap textual identity match — sufficient for the canonical cases and
7
+ // matches the `no-self-assign` ESLint behavior on simple identifiers.
8
+ // https://eslint.org/docs/latest/rules/no-self-assign
9
+ type noSelfAssign struct{}
10
+
11
+ func (noSelfAssign) Name() string { return "no-self-assign" }
12
+ func (noSelfAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
13
+ func (noSelfAssign) Check(ctx *Context, node *shimast.Node) {
14
+ expr := node.AsBinaryExpression()
15
+ if expr == nil || expr.OperatorToken == nil {
16
+ return
17
+ }
18
+ if expr.OperatorToken.Kind != shimast.KindEqualsToken {
19
+ return
20
+ }
21
+ left := stripParens(expr.Left)
22
+ right := stripParens(expr.Right)
23
+ if left == nil || right == nil {
24
+ return
25
+ }
26
+ if !isAssignableLeftHand(left) {
27
+ return
28
+ }
29
+ if nodeText(ctx.File, left) == nodeText(ctx.File, right) {
30
+ ctx.Report(node, "Self-assignment of a variable.")
31
+ }
32
+ }
33
+
34
+ func isAssignableLeftHand(node *shimast.Node) bool {
35
+ if node == nil {
36
+ return false
37
+ }
38
+ switch node.Kind {
39
+ case shimast.KindIdentifier, shimast.KindPropertyAccessExpression, shimast.KindElementAccessExpression:
40
+ return true
41
+ }
42
+ return false
43
+ }
44
+
45
+ // no-self-compare: `x === x`, `x !== x`, etc. Useful for catching typos
46
+ // where the developer meant to compare against a different value.
47
+ // https://eslint.org/docs/latest/rules/no-self-compare
48
+ type noSelfCompare struct{}
49
+
50
+ func (noSelfCompare) Name() string { return "no-self-compare" }
51
+ func (noSelfCompare) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
52
+ func (noSelfCompare) Check(ctx *Context, node *shimast.Node) {
53
+ expr := node.AsBinaryExpression()
54
+ if expr == nil || expr.OperatorToken == nil {
55
+ return
56
+ }
57
+ if !isComparisonOperator(expr.OperatorToken.Kind) {
58
+ return
59
+ }
60
+ left := stripParens(expr.Left)
61
+ right := stripParens(expr.Right)
62
+ if left == nil || right == nil {
63
+ return
64
+ }
65
+ if nodeText(ctx.File, left) == nodeText(ctx.File, right) {
66
+ ctx.Report(node, "Comparing to itself is potentially pointless.")
67
+ }
68
+ }
69
+
70
+ func isComparisonOperator(kind shimast.Kind) bool {
71
+ switch kind {
72
+ case
73
+ shimast.KindEqualsEqualsToken,
74
+ shimast.KindEqualsEqualsEqualsToken,
75
+ shimast.KindExclamationEqualsToken,
76
+ shimast.KindExclamationEqualsEqualsToken,
77
+ shimast.KindLessThanToken,
78
+ shimast.KindGreaterThanToken,
79
+ shimast.KindLessThanEqualsToken,
80
+ shimast.KindGreaterThanEqualsToken:
81
+ return true
82
+ }
83
+ return false
84
+ }
85
+
86
+ func init() {
87
+ Register(noSelfAssign{})
88
+ Register(noSelfCompare{})
89
+ }