@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.
- package/LICENSE +21 -0
- package/README.md +204 -0
- package/go-plugin/go.mod +38 -0
- package/go-plugin/lint/ast_helpers.go +170 -0
- package/go-plugin/lint/compile.go +393 -0
- package/go-plugin/lint/config.go +134 -0
- package/go-plugin/lint/engine.go +259 -0
- package/go-plugin/lint/host.go +190 -0
- package/go-plugin/lint/rules_arrays.go +78 -0
- package/go-plugin/lint/rules_console.go +37 -0
- package/go-plugin/lint/rules_debugger.go +29 -0
- package/go-plugin/lint/rules_dupes.go +179 -0
- package/go-plugin/lint/rules_empty.go +110 -0
- package/go-plugin/lint/rules_eval.go +57 -0
- package/go-plugin/lint/rules_finally.go +123 -0
- package/go-plugin/lint/rules_logic.go +368 -0
- package/go-plugin/lint/rules_loops.go +107 -0
- package/go-plugin/lint/rules_misc.go +69 -0
- package/go-plugin/lint/rules_problems.go +571 -0
- package/go-plugin/lint/rules_protos.go +42 -0
- package/go-plugin/lint/rules_self.go +89 -0
- package/go-plugin/lint/rules_strings.go +123 -0
- package/go-plugin/lint/rules_suggestions.go +1236 -0
- package/go-plugin/lint/rules_throw.go +31 -0
- package/go-plugin/lint/rules_ts.go +250 -0
- package/go-plugin/lint/rules_ts_extra.go +654 -0
- package/go-plugin/lint/rules_var.go +40 -0
- package/go-plugin/main.go +50 -0
- package/index.cjs +28 -0
- package/package.json +41 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package lint
|
|
2
|
+
|
|
3
|
+
import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
4
|
+
|
|
5
|
+
// no-throw-literal: throw should always pass an Error subclass.
|
|
6
|
+
// https://eslint.org/docs/latest/rules/no-throw-literal
|
|
7
|
+
type noThrowLiteral struct{}
|
|
8
|
+
|
|
9
|
+
func (noThrowLiteral) Name() string { return "no-throw-literal" }
|
|
10
|
+
func (noThrowLiteral) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindThrowStatement} }
|
|
11
|
+
func (noThrowLiteral) Check(ctx *Context, node *shimast.Node) {
|
|
12
|
+
throw := node.AsThrowStatement()
|
|
13
|
+
if throw == nil {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
expr := stripParens(throw.Expression)
|
|
17
|
+
if expr == nil {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
if isLiteralExpression(expr) || expr.Kind == shimast.KindUndefinedKeyword {
|
|
21
|
+
ctx.Report(throw.Expression, "Expected an error object to be thrown.")
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
if id := identifierText(expr); id == "undefined" {
|
|
25
|
+
ctx.Report(throw.Expression, "Expected an error object to be thrown.")
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func init() {
|
|
30
|
+
Register(noThrowLiteral{})
|
|
31
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
package lint
|
|
2
|
+
|
|
3
|
+
import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
4
|
+
|
|
5
|
+
// no-explicit-any: ban `: any` annotations. Loud equivalent of
|
|
6
|
+
// `@typescript-eslint/no-explicit-any`.
|
|
7
|
+
type noExplicitAny struct{}
|
|
8
|
+
|
|
9
|
+
func (noExplicitAny) Name() string { return "no-explicit-any" }
|
|
10
|
+
func (noExplicitAny) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindAnyKeyword} }
|
|
11
|
+
func (noExplicitAny) Check(ctx *Context, node *shimast.Node) {
|
|
12
|
+
ctx.Report(node, "Unexpected any. Specify a different type.")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// no-non-null-assertion: ban the postfix `!` non-null assertion.
|
|
16
|
+
type noNonNullAssertion struct{}
|
|
17
|
+
|
|
18
|
+
func (noNonNullAssertion) Name() string { return "no-non-null-assertion" }
|
|
19
|
+
func (noNonNullAssertion) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindNonNullExpression} }
|
|
20
|
+
func (noNonNullAssertion) Check(ctx *Context, node *shimast.Node) {
|
|
21
|
+
ctx.Report(node, "Forbidden non-null assertion.")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// no-empty-interface: empty `interface { }` declarations are an alias
|
|
25
|
+
// for the supertype with extra ceremony.
|
|
26
|
+
type noEmptyInterface struct{}
|
|
27
|
+
|
|
28
|
+
func (noEmptyInterface) Name() string { return "no-empty-interface" }
|
|
29
|
+
func (noEmptyInterface) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindInterfaceDeclaration} }
|
|
30
|
+
func (noEmptyInterface) Check(ctx *Context, node *shimast.Node) {
|
|
31
|
+
decl := node.AsInterfaceDeclaration()
|
|
32
|
+
if decl == nil || decl.Members == nil {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
if len(decl.Members.Nodes) == 0 {
|
|
36
|
+
ctx.Report(node, "An empty interface is equivalent to '{}'.")
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// no-inferrable-types: `let x: number = 0` — the annotation is what TS
|
|
41
|
+
// would have inferred anyway.
|
|
42
|
+
type noInferrableTypes struct{}
|
|
43
|
+
|
|
44
|
+
func (noInferrableTypes) Name() string { return "no-inferrable-types" }
|
|
45
|
+
func (noInferrableTypes) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindVariableDeclaration, shimast.KindParameter, shimast.KindPropertyDeclaration} }
|
|
46
|
+
func (noInferrableTypes) Check(ctx *Context, node *shimast.Node) {
|
|
47
|
+
var typeNode, init *shimast.Node
|
|
48
|
+
switch node.Kind {
|
|
49
|
+
case shimast.KindVariableDeclaration:
|
|
50
|
+
decl := node.AsVariableDeclaration()
|
|
51
|
+
if decl == nil {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
typeNode = decl.Type
|
|
55
|
+
init = decl.Initializer
|
|
56
|
+
case shimast.KindParameter:
|
|
57
|
+
decl := node.AsParameterDeclaration()
|
|
58
|
+
if decl == nil {
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
typeNode = decl.Type
|
|
62
|
+
init = decl.Initializer
|
|
63
|
+
case shimast.KindPropertyDeclaration:
|
|
64
|
+
decl := node.AsPropertyDeclaration()
|
|
65
|
+
if decl == nil {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
typeNode = decl.Type
|
|
69
|
+
init = decl.Initializer
|
|
70
|
+
}
|
|
71
|
+
if typeNode == nil || init == nil {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
if !isInferrablePair(typeNode, init) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
ctx.Report(typeNode, "Type annotation here is unnecessary.")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
func isInferrablePair(typeNode, init *shimast.Node) bool {
|
|
81
|
+
switch typeNode.Kind {
|
|
82
|
+
case shimast.KindStringKeyword:
|
|
83
|
+
return init.Kind == shimast.KindStringLiteral || init.Kind == shimast.KindNoSubstitutionTemplateLiteral || init.Kind == shimast.KindTemplateExpression
|
|
84
|
+
case shimast.KindNumberKeyword:
|
|
85
|
+
return init.Kind == shimast.KindNumericLiteral || isUnaryNumeric(init)
|
|
86
|
+
case shimast.KindBooleanKeyword:
|
|
87
|
+
return init.Kind == shimast.KindTrueKeyword || init.Kind == shimast.KindFalseKeyword
|
|
88
|
+
case shimast.KindBigIntKeyword:
|
|
89
|
+
return init.Kind == shimast.KindBigIntLiteral
|
|
90
|
+
case shimast.KindNullKeyword:
|
|
91
|
+
return init.Kind == shimast.KindNullKeyword
|
|
92
|
+
case shimast.KindUndefinedKeyword:
|
|
93
|
+
return identifierText(init) == "undefined" || init.Kind == shimast.KindVoidExpression
|
|
94
|
+
}
|
|
95
|
+
return false
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
func isUnaryNumeric(node *shimast.Node) bool {
|
|
99
|
+
if node == nil || node.Kind != shimast.KindPrefixUnaryExpression {
|
|
100
|
+
return false
|
|
101
|
+
}
|
|
102
|
+
prefix := node.AsPrefixUnaryExpression()
|
|
103
|
+
if prefix == nil {
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
switch prefix.Operator {
|
|
107
|
+
case shimast.KindPlusToken, shimast.KindMinusToken:
|
|
108
|
+
return prefix.Operand != nil && prefix.Operand.Kind == shimast.KindNumericLiteral
|
|
109
|
+
}
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// no-namespace: TypeScript-only `namespace`/`module` declarations. They
|
|
114
|
+
// exist for legacy reasons; modern TS uses ES modules.
|
|
115
|
+
type noNamespace struct{}
|
|
116
|
+
|
|
117
|
+
func (noNamespace) Name() string { return "no-namespace" }
|
|
118
|
+
func (noNamespace) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindModuleDeclaration} }
|
|
119
|
+
func (noNamespace) Check(ctx *Context, node *shimast.Node) {
|
|
120
|
+
decl := node.AsModuleDeclaration()
|
|
121
|
+
if decl == nil || decl.Name() == nil {
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
// Skip `declare module "fs"` ambient module declarations — those are
|
|
125
|
+
// the legitimate use case.
|
|
126
|
+
if decl.Name().Kind == shimast.KindStringLiteral {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
ctx.Report(node, "ES2015 module syntax is preferred over namespaces.")
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// no-this-alias: `const self = this;` reassigns `this` to a local. Use
|
|
133
|
+
// arrow functions or `.bind(this)` instead.
|
|
134
|
+
type noThisAlias struct{}
|
|
135
|
+
|
|
136
|
+
func (noThisAlias) Name() string { return "no-this-alias" }
|
|
137
|
+
func (noThisAlias) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindVariableDeclaration} }
|
|
138
|
+
func (noThisAlias) Check(ctx *Context, node *shimast.Node) {
|
|
139
|
+
decl := node.AsVariableDeclaration()
|
|
140
|
+
if decl == nil || decl.Initializer == nil {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
if decl.Initializer.Kind == shimast.KindThisKeyword {
|
|
144
|
+
ctx.Report(node, "Unexpected aliasing of 'this' to local variable.")
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// prefer-as-const: `as 'foo'` / `as 1` should be `as const`.
|
|
149
|
+
type preferAsConst struct{}
|
|
150
|
+
|
|
151
|
+
func (preferAsConst) Name() string { return "prefer-as-const" }
|
|
152
|
+
func (preferAsConst) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindAsExpression, shimast.KindTypeAssertionExpression} }
|
|
153
|
+
func (preferAsConst) Check(ctx *Context, node *shimast.Node) {
|
|
154
|
+
var expr, typeNode *shimast.Node
|
|
155
|
+
switch node.Kind {
|
|
156
|
+
case shimast.KindAsExpression:
|
|
157
|
+
as := node.AsAsExpression()
|
|
158
|
+
if as == nil {
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
expr = as.Expression
|
|
162
|
+
typeNode = as.Type
|
|
163
|
+
case shimast.KindTypeAssertionExpression:
|
|
164
|
+
ta := node.AsTypeAssertion()
|
|
165
|
+
if ta == nil {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
expr = ta.Expression
|
|
169
|
+
typeNode = ta.Type
|
|
170
|
+
}
|
|
171
|
+
if expr == nil || typeNode == nil {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
if typeNode.Kind != shimast.KindLiteralType {
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
literalType := typeNode.AsLiteralTypeNode()
|
|
178
|
+
if literalType == nil || literalType.Literal == nil {
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
if !literalsMatchSourceText(ctx.File, expr, literalType.Literal) {
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
ctx.Report(node, "Expected `as const` instead of `as` literal type.")
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
func literalsMatchSourceText(file *shimast.SourceFile, lhs, rhs *shimast.Node) bool {
|
|
188
|
+
if lhs == nil || rhs == nil {
|
|
189
|
+
return false
|
|
190
|
+
}
|
|
191
|
+
if !isLiteralExpression(lhs) {
|
|
192
|
+
return false
|
|
193
|
+
}
|
|
194
|
+
return nodeText(file, lhs) == nodeText(file, rhs)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// no-require-imports: ban `require(...)` calls in TS source. Use
|
|
198
|
+
// ES `import` instead.
|
|
199
|
+
type noRequireImports struct{}
|
|
200
|
+
|
|
201
|
+
func (noRequireImports) Name() string { return "no-require-imports" }
|
|
202
|
+
func (noRequireImports) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCallExpression, shimast.KindImportEqualsDeclaration} }
|
|
203
|
+
func (noRequireImports) Check(ctx *Context, node *shimast.Node) {
|
|
204
|
+
switch node.Kind {
|
|
205
|
+
case shimast.KindCallExpression:
|
|
206
|
+
call := node.AsCallExpression()
|
|
207
|
+
if call == nil {
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
if callCalleeName(call) != "require" {
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
// Ignore `import("...")` — that's a different node kind.
|
|
214
|
+
ctx.Report(node, "A `require()` style import is forbidden.")
|
|
215
|
+
case shimast.KindImportEqualsDeclaration:
|
|
216
|
+
ctx.Report(node, "An `import = require()` style import is forbidden.")
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ban-ts-comment: `// @ts-ignore` / `// @ts-nocheck` / `// @ts-expect-error`
|
|
221
|
+
// silence the type checker. Default mode flags every variant.
|
|
222
|
+
type banTsComment struct{}
|
|
223
|
+
|
|
224
|
+
func (banTsComment) Name() string { return "ban-ts-comment" }
|
|
225
|
+
func (banTsComment) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
226
|
+
func (banTsComment) Check(ctx *Context, node *shimast.Node) {
|
|
227
|
+
if ctx.File == nil {
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
for _, directive := range ctx.File.CommentDirectives {
|
|
231
|
+
switch directive.Kind {
|
|
232
|
+
case shimast.CommentDirectiveKindIgnore:
|
|
233
|
+
ctx.ReportRange(directive.Loc.Pos(), directive.Loc.End(), "Do not use `@ts-ignore` because it alters compilation errors.")
|
|
234
|
+
case shimast.CommentDirectiveKindExpectError:
|
|
235
|
+
ctx.ReportRange(directive.Loc.Pos(), directive.Loc.End(), "Do not use `@ts-expect-error` because it alters compilation errors.")
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
func init() {
|
|
241
|
+
Register(noExplicitAny{})
|
|
242
|
+
Register(noNonNullAssertion{})
|
|
243
|
+
Register(noEmptyInterface{})
|
|
244
|
+
Register(noInferrableTypes{})
|
|
245
|
+
Register(noNamespace{})
|
|
246
|
+
Register(noThisAlias{})
|
|
247
|
+
Register(preferAsConst{})
|
|
248
|
+
Register(noRequireImports{})
|
|
249
|
+
Register(banTsComment{})
|
|
250
|
+
}
|