8000 fix(eslint-plugin): Support more nodes [no-extra-parens] (#465) · andersk/typescript-eslint@2d15644 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2d15644

Browse files
authored
fix(eslint-plugin): Support more nodes [no-extra-parens] (typescript-eslint#465)
1 parent 2c36325 commit 2d15644

File tree

10 files changed

+452
-162
lines changed

10 files changed

+452
-162
lines changed

packages/eslint-plugin-tslint/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919
},
2020
"license": "MIT",
2121
"scripts": {
22-
"test": "jest --coverage",
23-
"prebuild": "npm run clean",
2422
"build": "tsc -p tsconfig.build.json",
2523
"clean": "rimraf dist/",
24+
"format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore",
25+
"prebuild": "npm run clean",
26+
"test": "jest --coverage",
2627
"typecheck": "tsc --noEmit"
2728
},
2829
"dependencies": {

packages/eslint-plugin/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@
2525
"license": "MIT",
2626
"main": "dist/index.js",
2727
"scripts": {
28+
"build": "tsc -p tsconfig.build.json",
29+
"clean": "rimraf dist/",
2830
"docs": "eslint-docs",
2931
"docs:check": "eslint-docs check",
30-
"test": "jest --coverage",
31-
"recommended:update": "ts-node tools/update-recommended.ts",
32+
"format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore",
3233
"prebuild": "npm run clean",
33-
"build": "tsc -p tsconfig.build.json",
34-
"clean": "rimraf dist/",
34+
"recommended:update": "ts-node tools/update-recommended.ts",
35+
"test": "jest --coverage",
3536
"typecheck": "tsc --noEmit"
3637
},
3738
"dependencies": {

packages/eslint-plugin/src/rules/no-extra-parens.ts

Lines changed: 198 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree';
1+
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree';
22
import baseRule from 'eslint/lib/rules/no-extra-parens';
33
import * as util from '../util';
44

@@ -22,10 +22,204 @@ export default util.createRule<Options, MessageIds>({
2222
create(context) {
2323
const rules = baseRule.create(context);
2424

25+
function binaryExp(
26+
node: TSESTree.BinaryExpression | TSESTree.LogicalExpression,
27+
) {
28+
const rule = rules.BinaryExpression as (n: typeof node) => void;
29+
30+
// makes the rule think it should skip the left or right
31+
if (node.left.type === AST_NODE_TYPES.TSAsExpression) {
32+
return rule({
33+
...node,
34+
left: {
35+
...node.left,
36+
type: AST_NODE_TYPES.BinaryExpression as any,
37+
},
38+
});
39+
}
40+
if (node.right.type === AST_NODE_TYPES.TSAsExpression) {
41+
return rule({
42+
...node,
43+
right: {
44+
...node.right,
45+
type: AST_NODE_TYPES.BinaryExpression as any,
46+
},
47+
});
48+
}
49+
50+
return rule(node);
51+
}
52+
function callExp(node: TSESTree.CallExpression | TSESTree.NewExpression) {
53+
const rule = rules.CallExpression as (n: typeof node) => void;
54+
55+
if (node.callee.type === AST_NODE_TYPES.TSAsExpression) {
56+
// reduces the precedence of the node so the rule thinks it needs to be wrapped
57+
return rule({
58+
...node,
59+
callee: {
60+
...node.callee,
61+
type: AST_NODE_TYPES.SequenceExpression as any,
62+
},
63+
});
64+
}
65+
66+
return rule(node);
67+
}
68+
function unaryUpdateExpression(
69+
node: TSESTree.UnaryExpression | TSESTree.UpdateExpression,
70+
) {
71+
const rule = rules.UnaryExpression as (n: typeof node) => void;
72+
73+
if (node.argument.type === AST_NODE_TYPES.TSAsExpression) {
74+
// reduces the precedence of the node so the rule thinks it needs to be wrapped
75+
return rule({
76+
...node,
77+
argument: {
78+
...node.argument,
79+
type: AST_NODE_TYPES.SequenceExpression as any,
80+
},
81+
});
82+
}
83+
84+
return rule(node);
85+
}
86+
2587
return Object.assign({}, rules, {
26-
MemberExpression(node: TSESTree.MemberExpression) {
27-
if (node.object.type !== AST_NODE_TYPES.TSAsExpression) {
28-
return rules.MemberExpression(node);
88+
// ArrayExpression
89+
ArrowFunctionExpression(node) {
90+
if (node.body.type !== AST_NODE_TYPES.TSAsExpression) {
91+
return rules.ArrowFunctionExpression(node);
92+
}
93+
},
94+
// AssignmentExpression
95+
// AwaitExpression
96+
BinaryExpression: binaryExp,
97+
CallExpression: callExp,
98+
// ClassDeclaration
99+
// ClassExpression
100+
ConditionalExpression(node) {
101+
// reduces the precedence of the node so the rule thinks it needs to be wrapped
102+
if (node.test.type === AST_NODE_TYPES.TSAsExpression) {
103+
return rules.ConditionalExpression({
104+
...node,
105+
test: {
106+
...node.test,
107+
type: AST_NODE_TYPES.SequenceExpression as any,
108+
},
109+
});
110+
}
111+
if (node.consequent.type === AST_NODE_TYPES.TSAsExpression) {
112+
return rules.ConditionalExpression({
113+
...node,
114+
consequent: {
115+
...node.consequent,
116+
type: AST_NODE_TYPES.SequenceExpression as any,
117+
},
118+
});
119+
}
120+
if (node.alternate.type === AST_NODE_TYPES.TSAsExpression) {
121+
// reduces the precedence of the node so the rule thinks it needs to be rapped
122+
return rules.ConditionalExpression({
123+
...node,
124+
alternate: {
125+
...node.alternate,
126+
type: AST_NODE_TYPES.SequenceExpression as any,
127+
},
128+
});
129+
}
130+
return rules.ConditionalExpression(node);
131+
},
132+
// DoWhileStatement
133+
'ForInStatement, ForOfStatement'(
134+
node: TSESTree.ForInStatement | TSESTree.ForOfStatement,
135+
) {
136+
if (node.right.type === AST_NODE_TYPES.TSAsExpression) {
137+
// makes the rule skip checking of the right
138+
return rules['ForInStatement, ForOfStatement']({
139+
...node,
140+
type: AST_NODE_TYPES.ForOfStatement as any,
141+
right: {
142+
...node.right,
143+
type: AST_NODE_TYPES.SequenceExpression as any,
144+
},
145+
});
146+
}
147+
148+
return rules['ForInStatement, ForOfStatement'](node);
149+
},
150+
ForStatement(node) {
151+
// make the rule skip the piece by removing it entirely
152+
if (node.init && node.init.type === AST_NODE_TYPES.TSAsExpression) {
153+
return rules.ForStatement({
154+
...node,
155+
init: null,
156+
});
157+
}
158+
if (node.test && node.test.type === AST_NODE_TYPES.TSAsExpression) {
159+
return rules.ForStatement({
160+
...node,
161+
test: null,
162+
});
163+
}
164+
if (node.update && node.update.type === AST_NODE_TYPES.TSAsExpression) {
165+
return rules.ForStatement({
166+
...node,
167+
update: null,
168+
});
169+
}
170+
171+
return rules.ForStatement(node);
172+
},
173+
// IfStatement
174+
LogicalExpression: binaryExp,
175+
MemberExpression(node) {
176+
if (node.object.type === AST_NODE_TYPES.TSAsExpression) {
177+
// reduces the precedence of the node so the rule thinks it needs to be wrapped
178+
return rules.MemberExpression({
179+
...node,
180+
object: {
181+
...node.object,
182+
type: AST_NODE_TYPES.SequenceExpression as any,
183+
},
184+
});
185+
}
186+
187+
return rules.MemberExpression(node);
188+
},
189+
NewExpression: callExp,
190+
// ObjectExpression
191+
// ReturnStatement
192+
// SequenceExpression
193+
SpreadElement(node) {
194+
if (node.argument.type !== AST_NODE_TYPES.TSAsExpression) {
195+
return rules.SpreadElement(node);
196+
}
197+
},
198+
SwitchCase(node) {
199+
if (node.test.type !== AST_NODE_TYPES.TSAsExpression) {
200+
return rules.SwitchCase(node);
201+
}
202+
},
203+
// SwitchStatement
204+
ThrowStatement(node) {
205+
if (
206+
node.argument &&
207+
node.argument.type !== AST_NODE_TYPES.TSAsExpression
208+
) {
209+
return rules.ThrowStatement(node);
210+
}
211+
},
212+
UnaryExpression: unaryUpdateExpression,
213+
UpdateExpression: unaryUpdateExpression,
214+
// VariableDeclarator
215+
// WhileStatement
216+
// WithStatement - i'm not going to even bother implementing this terrible and never used feature
217+
YieldExpression(node) {
218+
if (
219+
node.argument &&
220+
node.argument.type !== AST_NODE_TYPES.TSAsExpression
221+
) {
222+
return rules.YieldExpression(node);
29223
}
30224
},
31225
});

packages/eslint-plugin/tests/RuleTester.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,68 @@ function getFixturesRootDir() {
6161
return path.join(process.cwd(), 'tests/fixtures/');
6262
}
6363

64+
/**
65+
* Converts a batch of single line tests into a number of separate test cases.
66+
* This makes it easier to write tests which use the same options.
67+
*
68+
* Why wouldn't you just leave them as one test?
69+
* Because it makes the test error messages harder to decipher.
70+
* This way each line will fail separately, instead of them all failing together.
71+
*/
72+
function batchedSingleLineTests<TOptions extends Readonly<any[]>>(
73+
test: ValidTestCase<TOptions>,
74+
): ValidTestCase<TOptions>[];
75+
/**
76+
* Converts a batch of single line tests into a number of separate test cases.
77+
* This makes it easier to write tests which use the same options.
78+
*
79+
* Why wouldn't you just leave them as one test?
80+
* Because it makes the test error messages harder to decipher.
81+
* This way each line will fail separately, instead of them all failing together.
82+
*
83+
* Make sure you have your line numbers correct for error reporting, as it will match
84+
* the line numbers up with the split tests!
85+
*/
86+
function batchedSingleLineTests<
87+
TMessageIds extends string,
88+
TOptions extends Readonly<any[]>
89+
>(
90+
test: InvalidTestCase<TMessageIds, TOptions>,
91+
): InvalidTestCase<TMessageIds, TOptions>[];
92+
function batchedSingleLineTests<
93+
TMessageIds extends string,
94+
TOptions extends Readonly<any[]>
95+
>(
96+
options: ValidTestCase<TOptions> | InvalidTestCase<TMessageIds, TOptions>,
97+
): (ValidTestCase<TOptions> | InvalidTestCase<TMessageIds, TOptions>)[] {
98+
// eslint counts lines from 1
99+
const lineOffset = options.code[0] === '\n' ? 2 : 1;
100+
return options.code
101+
.trim()
102+
.split('\n')
103+
.map((code, i) => {
104+
const lineNum = i + lineOffset;
105+
const errors =
106+
'errors' in options
107+
? options.errors.filter(e => e.line === lineNum)
108+
: [];
109+
return {
110+
...options,
111+
code,
112+
errors: errors.map(e => ({
113+
...e,
114+
line: 1,
115+
})),
116+
};
117+
});
118+
}
119+
64120
export {
65121
RuleTester,
66122
RunTests,
67123
TestCaseError,
68124
InvalidTestCase,
69125
ValidTestCase,
126+
batchedSingleLineTests,
70127
getFixturesRootDir,
71128
};

0 commit comments

Comments
 (0)
0