Skip to content

Commit 6eb70f2

Browse files
authored
feat: port eslint core rule no-ex-assign (#485)
1 parent 1e5a9c6 commit 6eb70f2

7 files changed

Lines changed: 872 additions & 0 deletions

File tree

internal/config/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import (
7979
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/prefer_namespace_keyword"
8080
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/prefer_promise_reject_errors"
8181
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/prefer_readonly"
82+
8283
// "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/prefer_readonly_parameter_types" // Temporarily disabled - incomplete implementation
8384
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/prefer_reduce_type_parameter"
8485
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/prefer_regexp_exec"
@@ -120,6 +121,7 @@ import (
120121
"github.com/web-infra-dev/rslint/internal/rules/no_duplicate_case"
121122
"github.com/web-infra-dev/rslint/internal/rules/no_empty"
122123
"github.com/web-infra-dev/rslint/internal/rules/no_empty_pattern"
124+
"github.com/web-infra-dev/rslint/internal/rules/no_ex_assign"
123125
"github.com/web-infra-dev/rslint/internal/rules/no_loss_of_precision"
124126
"github.com/web-infra-dev/rslint/internal/rules/no_sparse_arrays"
125127
"github.com/web-infra-dev/rslint/internal/rules/no_template_curly_in_string"
@@ -419,6 +421,7 @@ func registerAllCoreEslintRules() {
419421
GlobalRuleRegistry.Register("no-duplicate-case", no_duplicate_case.NoDuplicateCaseRule)
420422
GlobalRuleRegistry.Register("no-empty", no_empty.NoEmptyRule)
421423
GlobalRuleRegistry.Register("no-empty-pattern", no_empty_pattern.NoEmptyPatternRule)
424+
GlobalRuleRegistry.Register("no-ex-assign", no_ex_assign.NoExAssignRule)
422425
GlobalRuleRegistry.Register("no-loss-of-precision", no_loss_of_precision.NoLossOfPrecisionRule)
423426
GlobalRuleRegistry.Register("no-template-curly-in-string", no_template_curly_in_string.NoTemplateCurlyInString)
424427
GlobalRuleRegistry.Register("no-sparse-arrays", no_sparse_arrays.NoSparseArraysRule)
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
package no_ex_assign
2+
3+
import (
4+
"slices"
5+
6+
"github.com/microsoft/typescript-go/shim/ast"
7+
"github.com/web-infra-dev/rslint/internal/rule"
8+
"github.com/web-infra-dev/rslint/internal/utils"
9+
)
10+
11+
// Message builder
12+
func buildExAssignMessage() rule.RuleMessage {
13+
return rule.RuleMessage{
14+
Id: "unexpected",
15+
Description: "Do not assign to the exception parameter.",
16+
}
17+
}
18+
19+
func collectCatchBindingNamesAndSymbols(node *ast.Node, ctx rule.RuleContext) ([]string, []*ast.Symbol) {
20+
if node == nil {
21+
return nil, nil
22+
}
23+
if ast.IsIdentifier(node) {
24+
return []string{node.Text()}, []*ast.Symbol{ctx.TypeChecker.GetSymbolAtLocation(node)}
25+
}
26+
if ast.IsBindingPattern(node) {
27+
var names []string
28+
var symbols []*ast.Symbol
29+
for _, elem := range node.Elements() {
30+
if elem == nil || !ast.IsBindingElement(elem) {
31+
continue
32+
}
33+
be := elem.AsBindingElement()
34+
if be == nil || be.Name() == nil {
35+
continue
36+
}
37+
utils.CollectBindingNames(be.Name(), func(ident *ast.Node, name string) {
38+
names = append(names, name)
39+
symbols = append(symbols, ctx.TypeChecker.GetSymbolAtLocation(ident))
40+
})
41+
}
42+
return names, symbols
43+
}
44+
return nil, nil
45+
}
46+
47+
func isBindingPatternInAssignment(node *ast.Node) bool {
48+
if node == nil {
49+
return false
50+
}
51+
52+
parent := node.Parent
53+
54+
for parent != nil && parent.Kind == ast.KindParenthesizedExpression {
55+
parent = parent.Parent
56+
}
57+
58+
if parent == nil || parent.Kind != ast.KindBinaryExpression {
59+
return false
60+
}
61+
62+
binary := parent.AsBinaryExpression()
63+
if binary == nil || binary.OperatorToken == nil {
64+
return false
65+
}
66+
67+
switch binary.OperatorToken.Kind {
68+
case ast.KindEqualsToken:
69+
return binary.Left == node
70+
}
71+
72+
return false
73+
}
74+
75+
func isInDestructuringAssignment(node *ast.Node) bool {
76+
current := node
77+
for current != nil {
78+
parent := current.Parent
79+
if parent == nil {
80+
return false
81+
}
82+
83+
switch parent.Kind {
84+
case ast.KindBinaryExpression:
85+
binary := parent.AsBinaryExpression()
86+
if binary != nil && binary.OperatorToken != nil &&
87+
binary.OperatorToken.Kind == ast.KindEqualsToken {
88+
return binary.Left == current
89+
}
90+
return false
91+
case ast.KindParenthesizedExpression,
92+
ast.KindObjectLiteralExpression,
93+
ast.KindArrayLiteralExpression,
94+
ast.KindPropertyAssignment,
95+
ast.KindShorthandPropertyAssignment,
96+
ast.KindSpreadAssignment:
97+
current = parent
98+
default:
99+
return false
100+
}
101+
}
102+
return false
103+
}
104+
105+
func isWriteReference(node *ast.Node) bool {
106+
if node == nil || node.Parent == nil {
107+
return false
108+
}
109+
110+
parent := node.Parent
111+
112+
switch parent.Kind {
113+
case ast.KindBinaryExpression:
114+
binary := parent.AsBinaryExpression()
115+
if binary == nil || binary.OperatorToken == nil {
116+
return false
117+
}
118+
119+
switch binary.OperatorToken.Kind {
120+
case ast.KindEqualsToken,
121+
ast.KindPlusEqualsToken,
122+
ast.KindMinusEqualsToken,
123+
ast.KindAsteriskAsteriskEqualsToken,
124+
ast.KindAsteriskEqualsToken,
125+
ast.KindSlashEqualsToken,
126+
ast.KindPercentEqualsToken,
127+
ast.KindLessThanLessThanEqualsToken,
128+
ast.KindGreaterThanGreaterThanEqualsToken,
129+
ast.KindGreaterThanGreaterThanGreaterThanEqualsToken,
130+
ast.KindAmpersandEqualsToken,
131+
ast.KindBarEqualsToken,
132+
ast.KindCaretEqualsToken,
133+
ast.KindBarBarEqualsToken,
134+
ast.KindAmpersandAmpersandEqualsToken,
135+
ast.KindQuestionQuestionEqualsToken:
136+
return binary.Left == node
137+
}
138+
case ast.KindPostfixUnaryExpression:
139+
postfix := parent.AsPostfixUnaryExpression()
140+
if postfix == nil {
141+
return false
142+
}
143+
switch postfix.Operator {
144+
case ast.KindPlusPlusToken, ast.KindMinusMinusToken:
145+
return postfix.Operand == node
146+
}
147+
case ast.KindPrefixUnaryExpression:
148+
prefix := parent.AsPrefixUnaryExpression()
149+
if prefix == nil {
150+
return false
151+
}
152+
switch prefix.Operator {
153+
case ast.KindPlusPlusToken, ast.KindMinusMinusToken:
154+
return prefix.Operand == node
155+
}
156+
case ast.KindForInStatement:
157+
forIn := parent.AsForInOrOfStatement()
158+
if forIn == nil {
159+
return false
160+
}
161+
return forIn.Initializer == node
162+
case ast.KindForOfStatement:
163+
forOf := parent.AsForInOrOfStatement()
164+
if forOf == nil {
165+
return false
166+
}
167+
return forOf.Initializer == node
168+
case ast.KindObjectBindingPattern:
169+
return isBindingPatternInAssignment(parent)
170+
case ast.KindArrayBindingPattern:
171+
return isBindingPatternInAssignment(parent)
172+
case ast.KindBindingElement:
173+
return isWriteReference(parent)
174+
case ast.KindShorthandPropertyAssignment:
175+
shorthand := parent.AsShorthandPropertyAssignment()
176+
if shorthand != nil && shorthand.Name() == node {
177+
return isInDestructuringAssignment(parent)
178+
}
179+
case ast.KindPropertyAssignment:
180+
propAssignment := parent.AsPropertyAssignment()
181+
if propAssignment != nil && propAssignment.Initializer == node {
182+
return isInDestructuringAssignment(parent)
183+
}
184+
case ast.KindObjectLiteralExpression:
185+
return isInDestructuringAssignment(parent)
186+
case ast.KindArrayLiteralExpression:
187+
return isInDestructuringAssignment(parent)
188+
case ast.KindParenthesizedExpression:
189+
return isWriteReference(parent)
190+
case ast.KindAsExpression, ast.KindTypeAssertionExpression:
191+
return isWriteReference(parent)
192+
}
193+
194+
return false
195+
}
196+
197+
func getReferenceSymbol(node *ast.Node, ctx rule.RuleContext) *ast.Symbol {
198+
if node == nil || ctx.TypeChecker == nil {
199+
return nil
200+
}
201+
202+
parent := node.Parent
203+
if parent != nil && parent.Kind == ast.KindShorthandPropertyAssignment {
204+
shorthand := parent.AsShorthandPropertyAssignment()
205+
if shorthand != nil && shorthand.Name() == node {
206+
if symbol := ctx.TypeChecker.GetShorthandAssignmentValueSymbol(parent); symbol != nil {
207+
return symbol
208+
}
209+
}
210+
}
211+
212+
return ctx.TypeChecker.GetSymbolAtLocation(node)
213+
}
214+
215+
func isNameShadowed(node *ast.Node, symbols []*ast.Symbol, ctx rule.RuleContext) bool {
216+
if node == nil || ctx.TypeChecker == nil || len(symbols) == 0 {
217+
return false
218+
}
219+
220+
symbol := getReferenceSymbol(node, ctx)
221+
if symbol == nil {
222+
return false
223+
}
224+
225+
for _, s := range symbols {
226+
if s == symbol {
227+
return false
228+
}
229+
}
230+
return true
231+
}
232+
233+
func getIdentifierName(node *ast.Node) string {
234+
if node == nil || node.Kind != ast.KindIdentifier {
235+
return ""
236+
}
237+
return node.Text()
238+
}
239+
240+
func checkReassignments(block *ast.Node, names []string, symbols []*ast.Symbol, ctx rule.RuleContext) {
241+
if block == nil || ctx.TypeChecker == nil || len(names) == 0 || len(symbols) == 0 {
242+
return
243+
}
244+
245+
var walk func(*ast.Node)
246+
walk = func(block *ast.Node) {
247+
if block == nil {
248+
return
249+
}
250+
251+
block.ForEachChild(func(child *ast.Node) bool {
252+
if child == nil {
253+
return false
254+
}
255+
256+
childName := getIdentifierName(child)
257+
if child.Kind == ast.KindIdentifier && slices.Contains(names, childName) {
258+
if isWriteReference(child) {
259+
shadowed := isNameShadowed(child, symbols, ctx)
260+
if !shadowed {
261+
ctx.ReportNode(child, buildExAssignMessage())
262+
}
263+
}
264+
} else {
265+
walk(child)
266+
}
267+
268+
return false
269+
})
270+
}
271+
272+
walk(block)
273+
}
274+
275+
var NoExAssignRule = rule.Rule{
276+
Name: "no-ex-assign",
277+
Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
278+
return rule.RuleListeners{
279+
ast.KindCatchClause: func(node *ast.Node) {
280+
if node.AsCatchClause().VariableDeclaration == nil {
281+
return
282+
}
283+
284+
varDecl := node.AsCatchClause().VariableDeclaration.AsVariableDeclaration()
285+
if varDecl == nil || varDecl.Name() == nil {
286+
return
287+
}
288+
289+
block := node.AsCatchClause().Block
290+
if block == nil {
291+
return
292+
}
293+
294+
names, symbols := collectCatchBindingNamesAndSymbols(varDecl.Name(), ctx)
295+
checkReassignments(block, names, symbols, ctx)
296+
},
297+
}
298+
},
299+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# no-ex-assign
2+
3+
## Rule Details
4+
5+
Disallow assign another value to the exception parameter a catch clause in a try statement accidentally or purposely. Since there is no arguments object to offer alternative access to this data, assignment of the parameter is absolutely destructive.
6+
7+
Examples of **correct** code for this rule:
8+
9+
```javascript
10+
try {
11+
} catch (e) {
12+
three = 2 + 1;
13+
}
14+
try {
15+
} catch ({ e }) {
16+
this.something = 2;
17+
}
18+
function foo() {
19+
try {
20+
} catch (e) {
21+
return false;
22+
}
23+
}
24+
```
25+
26+
Examples of **incorrect** code for this rule:
27+
28+
```javascript
29+
try {
30+
} catch (e) {
31+
e = 10;
32+
}
33+
try {
34+
} catch (ex) {
35+
ex = 10;
36+
}
37+
try {
38+
} catch (ex) {
39+
[ex] = [];
40+
}
41+
try {
42+
} catch (ex) {
43+
({ x: ex = 0 } = {});
44+
}
45+
try {
46+
} catch ({ message }) {
47+
message = 10;
48+
}
49+
```
50+
51+
## Original Documentation
52+
53+
https://eslint.org/docs/latest/rules/no-ex-assign

0 commit comments

Comments
 (0)