@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,179 @@
|
|
|
1
|
+
package lint
|
|
2
|
+
|
|
3
|
+
import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
4
|
+
|
|
5
|
+
// no-duplicate-case: duplicated `case` labels in a single switch.
|
|
6
|
+
// https://eslint.org/docs/latest/rules/no-duplicate-case
|
|
7
|
+
type noDuplicateCase struct{}
|
|
8
|
+
|
|
9
|
+
func (noDuplicateCase) Name() string { return "no-duplicate-case" }
|
|
10
|
+
func (noDuplicateCase) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSwitchStatement} }
|
|
11
|
+
func (noDuplicateCase) Check(ctx *Context, node *shimast.Node) {
|
|
12
|
+
sw := node.AsSwitchStatement()
|
|
13
|
+
if sw == nil || sw.CaseBlock == nil {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
block := sw.CaseBlock.AsCaseBlock()
|
|
17
|
+
if block == nil || block.Clauses == nil {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
seen := make(map[string]bool, len(block.Clauses.Nodes))
|
|
21
|
+
for _, clause := range block.Clauses.Nodes {
|
|
22
|
+
if clause == nil || clause.Kind != shimast.KindCaseClause {
|
|
23
|
+
continue
|
|
24
|
+
}
|
|
25
|
+
caseClause := clause.AsCaseOrDefaultClause()
|
|
26
|
+
if caseClause == nil || caseClause.Expression == nil {
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
key := nodeText(ctx.File, caseClause.Expression)
|
|
30
|
+
if key == "" {
|
|
31
|
+
continue
|
|
32
|
+
}
|
|
33
|
+
if seen[key] {
|
|
34
|
+
ctx.Report(clause, "Duplicate case label.")
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
seen[key] = true
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// no-dupe-keys: duplicated property names in a single object literal.
|
|
42
|
+
// https://eslint.org/docs/latest/rules/no-dupe-keys
|
|
43
|
+
type noDupeKeys struct{}
|
|
44
|
+
|
|
45
|
+
func (noDupeKeys) Name() string { return "no-dupe-keys" }
|
|
46
|
+
func (noDupeKeys) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindObjectLiteralExpression} }
|
|
47
|
+
func (noDupeKeys) Check(ctx *Context, node *shimast.Node) {
|
|
48
|
+
obj := node.AsObjectLiteralExpression()
|
|
49
|
+
if obj == nil || obj.Properties == nil {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
seen := make(map[string]*shimast.Node, len(obj.Properties.Nodes))
|
|
53
|
+
for _, prop := range obj.Properties.Nodes {
|
|
54
|
+
key := propertyKey(ctx.File, prop)
|
|
55
|
+
if key == "" {
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
if first, ok := seen[key]; ok {
|
|
59
|
+
ctx.Report(prop, "Duplicate key '"+key+"'.")
|
|
60
|
+
_ = first
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
seen[key] = prop
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// no-dupe-args: duplicated parameter names in a single function head.
|
|
68
|
+
// https://eslint.org/docs/latest/rules/no-dupe-args
|
|
69
|
+
type noDupeArgs struct{}
|
|
70
|
+
|
|
71
|
+
func (noDupeArgs) Name() string { return "no-dupe-args" }
|
|
72
|
+
func (noDupeArgs) Visits() []shimast.Kind {
|
|
73
|
+
return []shimast.Kind{
|
|
74
|
+
shimast.KindFunctionDeclaration,
|
|
75
|
+
shimast.KindFunctionExpression,
|
|
76
|
+
shimast.KindArrowFunction,
|
|
77
|
+
shimast.KindMethodDeclaration,
|
|
78
|
+
shimast.KindConstructor,
|
|
79
|
+
shimast.KindGetAccessor,
|
|
80
|
+
shimast.KindSetAccessor,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
func (noDupeArgs) Check(ctx *Context, node *shimast.Node) {
|
|
84
|
+
params := node.Parameters()
|
|
85
|
+
seen := make(map[string]bool, len(params))
|
|
86
|
+
for _, param := range params {
|
|
87
|
+
paramDecl := param.AsParameterDeclaration()
|
|
88
|
+
if paramDecl == nil {
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
name := identifierText(paramDecl.Name())
|
|
92
|
+
if name == "" {
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
if seen[name] {
|
|
96
|
+
ctx.Report(param, "Duplicate parameter name '"+name+"'.")
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
seen[name] = true
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// propertyKey returns a stable key for a property in an object literal.
|
|
104
|
+
// Computed names without a literal payload return "" so the dedupe pass
|
|
105
|
+
// can skip them safely (a typed field is fine — duplicate identical
|
|
106
|
+
// expressions still won't cause false positives because the same expr
|
|
107
|
+
// produces the same source text).
|
|
108
|
+
func propertyKey(file *shimast.SourceFile, prop *shimast.Node) string {
|
|
109
|
+
if prop == nil {
|
|
110
|
+
return ""
|
|
111
|
+
}
|
|
112
|
+
switch prop.Kind {
|
|
113
|
+
case shimast.KindPropertyAssignment:
|
|
114
|
+
assignment := prop.AsPropertyAssignment()
|
|
115
|
+
if assignment == nil {
|
|
116
|
+
return ""
|
|
117
|
+
}
|
|
118
|
+
return staticPropertyKey(file, assignment.Name())
|
|
119
|
+
case shimast.KindShorthandPropertyAssignment:
|
|
120
|
+
short := prop.AsShorthandPropertyAssignment()
|
|
121
|
+
if short == nil {
|
|
122
|
+
return ""
|
|
123
|
+
}
|
|
124
|
+
return staticPropertyKey(file, short.Name())
|
|
125
|
+
case shimast.KindMethodDeclaration:
|
|
126
|
+
method := prop.AsMethodDeclaration()
|
|
127
|
+
if method == nil {
|
|
128
|
+
return ""
|
|
129
|
+
}
|
|
130
|
+
return staticPropertyKey(file, method.Name())
|
|
131
|
+
case shimast.KindGetAccessor, shimast.KindSetAccessor:
|
|
132
|
+
// Getter and setter pairs share a name but are not duplicates.
|
|
133
|
+
// Add a kind suffix so the dedupe key separates them.
|
|
134
|
+
switch prop.Kind {
|
|
135
|
+
case shimast.KindGetAccessor:
|
|
136
|
+
get := prop.AsGetAccessorDeclaration()
|
|
137
|
+
if get == nil {
|
|
138
|
+
return ""
|
|
139
|
+
}
|
|
140
|
+
if k := staticPropertyKey(file, get.Name()); k != "" {
|
|
141
|
+
return "get:" + k
|
|
142
|
+
}
|
|
143
|
+
case shimast.KindSetAccessor:
|
|
144
|
+
set := prop.AsSetAccessorDeclaration()
|
|
145
|
+
if set == nil {
|
|
146
|
+
return ""
|
|
147
|
+
}
|
|
148
|
+
if k := staticPropertyKey(file, set.Name()); k != "" {
|
|
149
|
+
return "set:" + k
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return ""
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
func staticPropertyKey(file *shimast.SourceFile, name *shimast.Node) string {
|
|
157
|
+
if name == nil {
|
|
158
|
+
return ""
|
|
159
|
+
}
|
|
160
|
+
switch name.Kind {
|
|
161
|
+
case shimast.KindIdentifier:
|
|
162
|
+
return identifierText(name)
|
|
163
|
+
case shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral:
|
|
164
|
+
return stringLiteralText(name)
|
|
165
|
+
case shimast.KindNumericLiteral, shimast.KindBigIntLiteral:
|
|
166
|
+
return numericLiteralText(name)
|
|
167
|
+
case shimast.KindComputedPropertyName:
|
|
168
|
+
// fall back to source text so a computed `[`foo`]` still compares
|
|
169
|
+
// against the literal `foo` form when both appear together.
|
|
170
|
+
return nodeText(file, name)
|
|
171
|
+
}
|
|
172
|
+
return ""
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
func init() {
|
|
176
|
+
Register(noDuplicateCase{})
|
|
177
|
+
Register(noDupeKeys{})
|
|
178
|
+
Register(noDupeArgs{})
|
|
179
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
package lint
|
|
2
|
+
|
|
3
|
+
import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
4
|
+
|
|
5
|
+
// no-empty: empty block statements (`{}`), but allow empty catch
|
|
6
|
+
// clauses since they're idiomatic for "ignore the error".
|
|
7
|
+
// https://eslint.org/docs/latest/rules/no-empty
|
|
8
|
+
type noEmpty struct{}
|
|
9
|
+
|
|
10
|
+
func (noEmpty) Name() string { return "no-empty" }
|
|
11
|
+
func (noEmpty) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBlock} }
|
|
12
|
+
func (noEmpty) Check(ctx *Context, node *shimast.Node) {
|
|
13
|
+
block := node.AsBlock()
|
|
14
|
+
if block == nil || block.Statements == nil {
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
if len(block.Statements.Nodes) > 0 {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
parent := node.Parent
|
|
21
|
+
if parent != nil && parent.Kind == shimast.KindCatchClause {
|
|
22
|
+
return // tolerated — see ESLint default options
|
|
23
|
+
}
|
|
24
|
+
if isFunctionLikeKind(parent) {
|
|
25
|
+
return // empty function body is `no-empty-function`'s job
|
|
26
|
+
}
|
|
27
|
+
ctx.Report(node, "Empty block statement.")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// no-empty-function: empty function / method / arrow / accessor bodies.
|
|
31
|
+
// https://eslint.org/docs/latest/rules/no-empty-function
|
|
32
|
+
type noEmptyFunction struct{}
|
|
33
|
+
|
|
34
|
+
func (noEmptyFunction) Name() string { return "no-empty-function" }
|
|
35
|
+
func (noEmptyFunction) Visits() []shimast.Kind {
|
|
36
|
+
return []shimast.Kind{
|
|
37
|
+
shimast.KindFunctionDeclaration,
|
|
38
|
+
shimast.KindFunctionExpression,
|
|
39
|
+
shimast.KindArrowFunction,
|
|
40
|
+
shimast.KindMethodDeclaration,
|
|
41
|
+
shimast.KindGetAccessor,
|
|
42
|
+
shimast.KindSetAccessor,
|
|
43
|
+
shimast.KindConstructor,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
func (noEmptyFunction) Check(ctx *Context, node *shimast.Node) {
|
|
47
|
+
body := node.Body()
|
|
48
|
+
if body == nil {
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
if body.Kind != shimast.KindBlock {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
block := body.AsBlock()
|
|
55
|
+
if block == nil || block.Statements == nil {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
if len(block.Statements.Nodes) == 0 {
|
|
59
|
+
ctx.Report(node, "Unexpected empty function.")
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// no-empty-pattern: `({}) => x` or `function ({}) {}` — destructuring
|
|
64
|
+
// patterns with no bindings are usually a bug.
|
|
65
|
+
// https://eslint.org/docs/latest/rules/no-empty-pattern
|
|
66
|
+
type noEmptyPattern struct{}
|
|
67
|
+
|
|
68
|
+
func (noEmptyPattern) Name() string { return "no-empty-pattern" }
|
|
69
|
+
func (noEmptyPattern) Visits() []shimast.Kind {
|
|
70
|
+
return []shimast.Kind{shimast.KindObjectBindingPattern, shimast.KindArrayBindingPattern}
|
|
71
|
+
}
|
|
72
|
+
func (noEmptyPattern) Check(ctx *Context, node *shimast.Node) {
|
|
73
|
+
pattern := node.AsBindingPattern()
|
|
74
|
+
if pattern == nil || pattern.Elements == nil {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
if len(pattern.Elements.Nodes) == 0 {
|
|
78
|
+
shape := "object"
|
|
79
|
+
if node.Kind == shimast.KindArrayBindingPattern {
|
|
80
|
+
shape = "array"
|
|
81
|
+
}
|
|
82
|
+
ctx.Report(node, "Unexpected empty "+shape+" pattern.")
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// isFunctionLikeKind reports whether the node represents a function-like
|
|
87
|
+
// host whose body is the relevant scope for the empty check.
|
|
88
|
+
func isFunctionLikeKind(n *shimast.Node) bool {
|
|
89
|
+
if n == nil {
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
switch n.Kind {
|
|
93
|
+
case
|
|
94
|
+
shimast.KindFunctionDeclaration,
|
|
95
|
+
shimast.KindFunctionExpression,
|
|
96
|
+
shimast.KindArrowFunction,
|
|
97
|
+
shimast.KindMethodDeclaration,
|
|
98
|
+
shimast.KindGetAccessor,
|
|
99
|
+
shimast.KindSetAccessor,
|
|
100
|
+
shimast.KindConstructor:
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
return false
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func init() {
|
|
107
|
+
Register(noEmpty{})
|
|
108
|
+
Register(noEmptyFunction{})
|
|
109
|
+
Register(noEmptyPattern{})
|
|
110
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package lint
|
|
2
|
+
|
|
3
|
+
import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
4
|
+
|
|
5
|
+
// no-eval: forbid `eval(...)` calls. Members of nested namespaces (e.g.
|
|
6
|
+
// `globalThis.eval(...)`) are not flagged here — that's
|
|
7
|
+
// `no-implied-eval`, which we don't ship in v0.
|
|
8
|
+
// https://eslint.org/docs/latest/rules/no-eval
|
|
9
|
+
type noEval struct{}
|
|
10
|
+
|
|
11
|
+
func (noEval) Name() string { return "no-eval" }
|
|
12
|
+
func (noEval) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCallExpression} }
|
|
13
|
+
func (noEval) Check(ctx *Context, node *shimast.Node) {
|
|
14
|
+
call := node.AsCallExpression()
|
|
15
|
+
if call == nil {
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
if callCalleeName(call) == "eval" {
|
|
19
|
+
ctx.Report(node, "eval can be harmful.")
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// no-script-url: forbid `"javascript:..."` literals. Often used to inject
|
|
24
|
+
// inline JS via DOM `href`/`src`; legacy and dangerous.
|
|
25
|
+
// https://eslint.org/docs/latest/rules/no-script-url
|
|
26
|
+
type noScriptURL struct{}
|
|
27
|
+
|
|
28
|
+
func (noScriptURL) Name() string { return "no-script-url" }
|
|
29
|
+
func (noScriptURL) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral} }
|
|
30
|
+
func (noScriptURL) Check(ctx *Context, node *shimast.Node) {
|
|
31
|
+
text := stringLiteralText(node)
|
|
32
|
+
if isJavaScriptURL(text) {
|
|
33
|
+
ctx.Report(node, "Script URL is a form of eval.")
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func isJavaScriptURL(text string) bool {
|
|
38
|
+
const prefix = "javascript:"
|
|
39
|
+
if len(text) < len(prefix) {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
for i := 0; i < len(prefix); i++ {
|
|
43
|
+
c := text[i]
|
|
44
|
+
if c >= 'A' && c <= 'Z' {
|
|
45
|
+
c += 'a' - 'A'
|
|
46
|
+
}
|
|
47
|
+
if c != prefix[i] {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return true
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func init() {
|
|
55
|
+
Register(noEval{})
|
|
56
|
+
Register(noScriptURL{})
|
|
57
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
package lint
|
|
2
|
+
|
|
3
|
+
import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
4
|
+
|
|
5
|
+
// no-unsafe-finally: `return` / `break` / `continue` / `throw` inside a
|
|
6
|
+
// `finally` clause silently overrides the in-flight exception or value.
|
|
7
|
+
// https://eslint.org/docs/latest/rules/no-unsafe-finally
|
|
8
|
+
type noUnsafeFinally struct{}
|
|
9
|
+
|
|
10
|
+
func (noUnsafeFinally) Name() string { return "no-unsafe-finally" }
|
|
11
|
+
func (noUnsafeFinally) Visits() []shimast.Kind {
|
|
12
|
+
return []shimast.Kind{
|
|
13
|
+
shimast.KindReturnStatement,
|
|
14
|
+
shimast.KindBreakStatement,
|
|
15
|
+
shimast.KindContinueStatement,
|
|
16
|
+
shimast.KindThrowStatement,
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
func (noUnsafeFinally) Check(ctx *Context, node *shimast.Node) {
|
|
20
|
+
finallyAncestor := walkToFinally(node)
|
|
21
|
+
if finallyAncestor == nil {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
keyword := keywordOfControl(node)
|
|
25
|
+
ctx.Report(node, "Unsafe usage of "+keyword+".")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func walkToFinally(node *shimast.Node) *shimast.Node {
|
|
29
|
+
cur := node.Parent
|
|
30
|
+
for cur != nil {
|
|
31
|
+
if isFunctionLikeKind(cur) || cur.Kind == shimast.KindSourceFile {
|
|
32
|
+
return nil
|
|
33
|
+
}
|
|
34
|
+
if cur.Kind == shimast.KindBlock {
|
|
35
|
+
grand := cur.Parent
|
|
36
|
+
if grand != nil && grand.Kind == shimast.KindTryStatement {
|
|
37
|
+
try := grand.AsTryStatement()
|
|
38
|
+
if try != nil && try.FinallyBlock == cur {
|
|
39
|
+
return cur
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// `break` / `continue` inside an inner loop within finally
|
|
44
|
+
// targets that loop and is therefore safe.
|
|
45
|
+
switch cur.Kind {
|
|
46
|
+
case shimast.KindForStatement,
|
|
47
|
+
shimast.KindForInStatement,
|
|
48
|
+
shimast.KindForOfStatement,
|
|
49
|
+
shimast.KindWhileStatement,
|
|
50
|
+
shimast.KindDoStatement,
|
|
51
|
+
shimast.KindSwitchStatement:
|
|
52
|
+
if node.Kind == shimast.KindBreakStatement || node.Kind == shimast.KindContinueStatement {
|
|
53
|
+
return nil
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
cur = cur.Parent
|
|
57
|
+
}
|
|
58
|
+
return nil
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func keywordOfControl(node *shimast.Node) string {
|
|
62
|
+
switch node.Kind {
|
|
63
|
+
case shimast.KindReturnStatement:
|
|
64
|
+
return "return"
|
|
65
|
+
case shimast.KindBreakStatement:
|
|
66
|
+
return "break"
|
|
67
|
+
case shimast.KindContinueStatement:
|
|
68
|
+
return "continue"
|
|
69
|
+
case shimast.KindThrowStatement:
|
|
70
|
+
return "throw"
|
|
71
|
+
}
|
|
72
|
+
return "control flow"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// no-useless-catch: `catch (err) { throw err; }` adds no behavior.
|
|
76
|
+
// https://eslint.org/docs/latest/rules/no-useless-catch
|
|
77
|
+
type noUselessCatch struct{}
|
|
78
|
+
|
|
79
|
+
func (noUselessCatch) Name() string { return "no-useless-catch" }
|
|
80
|
+
func (noUselessCatch) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCatchClause} }
|
|
81
|
+
func (noUselessCatch) Check(ctx *Context, node *shimast.Node) {
|
|
82
|
+
clause := node.AsCatchClause()
|
|
83
|
+
if clause == nil || clause.VariableDeclaration == nil || clause.Block == nil {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
binding := clause.VariableDeclaration.AsVariableDeclaration()
|
|
87
|
+
if binding == nil {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
bindingName := identifierText(binding.Name())
|
|
91
|
+
if bindingName == "" {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
block := clause.Block.AsBlock()
|
|
95
|
+
if block == nil || block.Statements == nil || len(block.Statements.Nodes) != 1 {
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
stmt := block.Statements.Nodes[0]
|
|
99
|
+
if stmt == nil || stmt.Kind != shimast.KindThrowStatement {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
throw := stmt.AsThrowStatement()
|
|
103
|
+
if throw == nil {
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
if identifierText(throw.Expression) != bindingName {
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
// Ignore when the surrounding try-catch has a `finally` block — the
|
|
110
|
+
// catch may exist solely to keep the finally semantics intact.
|
|
111
|
+
if try := node.Parent; try != nil && try.Kind == shimast.KindTryStatement {
|
|
112
|
+
tryStmt := try.AsTryStatement()
|
|
113
|
+
if tryStmt != nil && tryStmt.FinallyBlock != nil {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
ctx.Report(node, "Unnecessary try/catch wrapper.")
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
func init() {
|
|
121
|
+
Register(noUnsafeFinally{})
|
|
122
|
+
Register(noUselessCatch{})
|
|
123
|
+
}
|