@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,123 @@
1
+ package lint
2
+
3
+ import (
4
+ "strings"
5
+
6
+ shimast "github.com/microsoft/typescript-go/shim/ast"
7
+ )
8
+
9
+ // no-template-curly-in-string: a regular string contains `${...}`. The
10
+ // developer probably meant a template literal.
11
+ // https://eslint.org/docs/latest/rules/no-template-curly-in-string
12
+ type noTemplateCurlyInString struct{}
13
+
14
+ func (noTemplateCurlyInString) Name() string { return "no-template-curly-in-string" }
15
+ func (noTemplateCurlyInString) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindStringLiteral} }
16
+ func (noTemplateCurlyInString) Check(ctx *Context, node *shimast.Node) {
17
+ text := stringLiteralText(node)
18
+ if hasTemplatePlaceholder(text) {
19
+ ctx.Report(node, "Unexpected template string expression in regular string.")
20
+ }
21
+ }
22
+
23
+ func hasTemplatePlaceholder(text string) bool {
24
+ if !strings.Contains(text, "${") {
25
+ return false
26
+ }
27
+ // Match `${ ... }` style, not just literal "${" with nothing after.
28
+ idx := strings.Index(text, "${")
29
+ for idx >= 0 {
30
+ rest := text[idx+2:]
31
+ if strings.Contains(rest, "}") {
32
+ return true
33
+ }
34
+ next := strings.Index(text[idx+1:], "${")
35
+ if next < 0 {
36
+ return false
37
+ }
38
+ idx += next + 1
39
+ }
40
+ return false
41
+ }
42
+
43
+ // no-multi-str: `"line one \\n line two"` style backslash continuations.
44
+ // TS strips them silently and the result is rarely what the author meant.
45
+ // https://eslint.org/docs/latest/rules/no-multi-str
46
+ type noMultiStr struct{}
47
+
48
+ func (noMultiStr) Name() string { return "no-multi-str" }
49
+ func (noMultiStr) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindStringLiteral} }
50
+ func (noMultiStr) Check(ctx *Context, node *shimast.Node) {
51
+ src := nodeText(ctx.File, node)
52
+ // Look for `\` immediately before a newline in the *raw* source text.
53
+ if hasBackslashLineContinuation(src) {
54
+ ctx.Report(node, "Multiline support is limited to comments.")
55
+ }
56
+ }
57
+
58
+ func hasBackslashLineContinuation(src string) bool {
59
+ for i := 0; i < len(src)-1; i++ {
60
+ if src[i] == '\\' {
61
+ next := src[i+1]
62
+ if next == '\n' || next == '\r' {
63
+ return true
64
+ }
65
+ }
66
+ }
67
+ return false
68
+ }
69
+
70
+ // no-useless-concat: `"a" + "b"` of two literals. The compiler can't
71
+ // constant-fold every case, but this catches the obvious shapes.
72
+ // https://eslint.org/docs/latest/rules/no-useless-concat
73
+ type noUselessConcat struct{}
74
+
75
+ func (noUselessConcat) Name() string { return "no-useless-concat" }
76
+ func (noUselessConcat) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
77
+ func (noUselessConcat) Check(ctx *Context, node *shimast.Node) {
78
+ expr := node.AsBinaryExpression()
79
+ if expr == nil || expr.OperatorToken == nil {
80
+ return
81
+ }
82
+ if expr.OperatorToken.Kind != shimast.KindPlusToken {
83
+ return
84
+ }
85
+ if isStringLikeLiteral(stripParens(expr.Left)) && isStringLikeLiteral(stripParens(expr.Right)) {
86
+ ctx.Report(node, "Unexpected string concatenation of literals.")
87
+ }
88
+ }
89
+
90
+ func isStringLikeLiteral(node *shimast.Node) bool {
91
+ if node == nil {
92
+ return false
93
+ }
94
+ switch node.Kind {
95
+ case shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral:
96
+ return true
97
+ }
98
+ return false
99
+ }
100
+
101
+ // no-octal: `010` octal literal. Has to be opt-in via `0o10` in
102
+ // modern code; the legacy form is silently confusing.
103
+ // https://eslint.org/docs/latest/rules/no-octal
104
+ type noOctal struct{}
105
+
106
+ func (noOctal) Name() string { return "no-octal" }
107
+ func (noOctal) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindNumericLiteral} }
108
+ func (noOctal) Check(ctx *Context, node *shimast.Node) {
109
+ src := nodeText(ctx.File, node)
110
+ src = strings.TrimLeft(src, " \t")
111
+ if len(src) >= 2 && src[0] == '0' && isAsciiDigit(src[1]) {
112
+ ctx.Report(node, "Octal literals should not be used.")
113
+ }
114
+ }
115
+
116
+ func isAsciiDigit(b byte) bool { return b >= '0' && b <= '9' }
117
+
118
+ func init() {
119
+ Register(noTemplateCurlyInString{})
120
+ Register(noMultiStr{})
121
+ Register(noUselessConcat{})
122
+ Register(noOctal{})
123
+ }