10
10
//------------------------------------------------------------------------------
11
11
12
12
const { findVariable } = require ( "@eslint-community/eslint-utils" ) ;
13
+ const astUtils = require ( "./utils/ast-utils" ) ;
13
14
14
15
//------------------------------------------------------------------------------
15
16
// Helpers
@@ -59,6 +60,78 @@ function isPromiseExecutor(node, scope) {
59
60
isGlobalReference ( parent . callee , getOuterScope ( scope ) ) ;
60
61
}
61
62
63
+ /**
64
+ * Checks if the given node is a void expression.
65
+ * @param {ASTNode } node The node to check.
66
+ * @returns {boolean } - `true` if the node is a void expression
67
+ */
68
+ function expressionIsVoid ( node ) {
69
+ return node . type === "UnaryExpression" && node . operator === "void" ;
70
+ }
71
+
72
+ /**
73
+ * Fixes the linting error by prepending "void " to the given node
74
+ * @param {Object } sourceCode context given by context.sourceCode
75
+ * @param {ASTNode } node The node to fix.
76
+ * @param {Object } fixer The fixer object provided by ESLint.
77
+ * @returns {Array<Object> } - An array of fix objects to apply to the node.
78
+ */
79
+ function voidPrependFixer ( sourceCode , node , fixer ) {
80
+
81
+ const requiresParens =
82
+
83
+ // prepending `void ` will fail if the node has a lower precedence than void
84
+ astUtils . getPrecedence ( node ) < astUtils . getPrecedence ( { type : "UnaryExpression" , operator : "void" } ) &&
85
+
86
+ // check if there are parentheses around the node to avoid redundant parentheses
87
+ ! astUtils . isParenthesised ( sourceCode , node ) ;
88
+
89
+ // avoid parentheses issues
90
+ const returnOrArrowToken = sourceCode . getTokenBefore (
91
+ node ,
92
+ node . parent . type === "ArrowFunctionExpression"
93
+ ? astUtils . isArrowToken
94
+
95
+ // isReturnToken
96
+ : token => token . type === "Keyword" && token . value === "return"
97
+ ) ;
98
+
99
+ const firstToken = sourceCode . getTokenAfter ( returnOrArrowToken ) ;
100
+
101
+ const prependSpace =
102
+
103
+ // is return token, as => allows void to be adjacent
104
+ returnOrArrowToken . value === "return" &&
105
+
106
+ // If two tokens (return and "(") are adjacent
107
+ returnOrArrowToken . range [ 1 ] === firstToken . range [ 0 ] ;
108
+
109
+ return [
110
+ fixer . insertTextBefore ( firstToken , `${ prependSpace ? " " : "" } void ${ requiresParens ? "(" : "" } ` ) ,
111
+ fixer . insertTextAfter ( node , requiresParens ? ")" : "" )
112
+ ] ;
113
+ }
114
+
115
+ /**
116
+ * Fixes the linting error by `wrapping {}` around the given node's body.
117
+ * @param {Object } sourceCode context given by context.sourceCode
118
+ * @param {ASTNode } node The node to fix.
119
+ * @param {Object } fixer The fixer object provided by ESLint.
120
+ * @returns {Array<Object> } - An array of fix objects to apply to the node.
121
+ */
122
+ function curlyWrapFixer ( sourceCode , node , fixer ) {
123
+
124
+ // https://github.com/eslint/eslint/pull/17282#issuecomment-1592795923
125
+ const arrowToken = sourceCode . getTokenBefore ( node . body , astUtils . isArrowToken ) ;
126
+ const firstToken = sourceCode . getTokenAfter ( arrowToken ) ;
127
+ const lastToken = sourceCode . getLastToken ( node ) ;
128
+
129
+ return [
130
+ fixer . insertTextBefore ( firstToken , "{" ) ,
131
+ fixer . insertTextAfter ( lastToken , "}" )
132
+ ] ;
133
+ }
134
+
62
135
//------------------------------------------------------------------------------
63
136
// Rule Definition
64
137
//------------------------------------------------------------------------------
@@ -74,37 +147,80 @@ module.exports = {
74
147
url : "https://eslint.org/docs/latest/rules/no-promise-executor-return"
75
148
} ,
76
149
77
- schema : [ ] ,
150
+ hasSuggestions : true ,
151
+
152
+ schema : [ {
153
+ type : "object" ,
154
+ properties : {
155
+ allowVoid : {
156
+ type : "boolean" ,
157
+ default : false
158
+ }
159
+ } ,
160
+ additionalProperties : false
161
+ } ] ,
78
162
79
163
messages : {
80
- returnsValue : "Return values from promise executor functions cannot be read."
164
+ returnsValue : "Return values from promise executor functions cannot be read." ,
165
+
166
+ // arrow and function suggestions
167
+ prependVoid : "Prepend `void` to the expression." ,
168
+
169
+ // only arrow suggestions
170
+ wrapBraces : "Wrap the expression in `{}`."
81
171
}
82
172
} ,
83
173
84
174
create ( context ) {
85
175
86
176
let funcInfo = null ;
87
177
const sourceCode = context . sourceCode ;
88
-
89
- /**
90
- * Reports the given node.
91
- * @param {ASTNode } node Node to report.
92
- * @returns {void }
93
- */
94
- function report ( node ) {
95
- context . report ( { node, messageId : "returnsValue" } ) ;
96
- }
178
+ const {
179
+ allowVoid = false
180
+ } = context . options [ 0 ] || { } ;
97
181
98
182
return {
99
183
100
184
onCodePathStart ( _ , node ) {
101
185
funcInfo = {
102
186
upper : funcInfo ,
103
- shouldCheck : functionTypesToCheck . has ( node . type ) && isPromiseExecutor ( node , sourceCode . getScope ( node ) )
187
+ shouldCheck :
188
+ functionTypesToCheck . has ( node . type ) &&
189
+ isPromiseExecutor ( node , sourceCode . getScope ( node ) )
104
190
} ;
105
191
106
- if ( funcInfo . shouldCheck && node . type === "ArrowFunctionExpression" && node . expression ) {
107
- report ( node . body ) ;
192
+ if ( // Is a Promise executor
193
+ funcInfo . shouldCheck &&
194
+ node . type === "ArrowFunctionExpression" &&
195
+ node . expression &&
196
+
197
+ // Except void
198
+ ! ( allowVoid && expressionIsVoid ( node . body ) )
199
+ ) {
200
+ const suggest = [ ] ;
201
+
202
+ // prevent useless refactors
203
+ if ( allowVoid ) {
204
+ suggest . push ( {
205
+ messageId : "prependVoid" ,
206
+ fix ( fixer ) {
207
+ return voidPrependFixer ( sourceCode , node . body , fixer ) ;
208
+ }
209
+ } ) ;
210
+ }
211
+
212
+ suggest . push ( {
213
+ messageId : "wrapBraces" ,
214
+ fix ( fixer ) {
215
+ return curlyWrapFixer ( sourceCode , node , fixer ) ;
216
+ }
217
+ } ) ;
218
+
219
+ context . report ( {
220
+ node : node . body ,
221
+ messageId : "returnsValue" ,
222
+ suggest
223
+ } ) ;
108
224
}
109
225
} ,
110
226
@@ -113,9 +229,31 @@ module.exports = {
113
229
} ,
114
230
115
231
ReturnStatement ( node ) {
116
- if ( funcInfo . shouldCheck && node . argument ) {
117
- report ( node ) ;
232
+ if ( ! ( funcInfo . shouldCheck && node . argument ) ) {
233
+ return ;
118
234
}
235
+
236
+ // node is `return <expression>`
237
+ if ( ! allowVoid ) {
238
+ context . report ( { node, messageId : "returnsValue" } ) ;
239
+ return ;
240
+ }
241
+
242
+ if ( expressionIsVoid ( node . argument ) ) {
243
+ return ;
244
+ }
245
+
246
+ // allowVoid && !expressionIsVoid
247
+ context . report ( {
248
+ node,
249
+ messageId : "returnsValue" ,
250
+ suggest : [ {
251
+ messageId : "prependVoid" ,
252
+ fix ( fixer ) {
253
+ return voidPrependFixer ( sourceCode , node . argument , fixer ) ;
254
+ }
255
+ } ]
256
+ } ) ;
119
257
}
120
258
} ;
121
259
}
0 commit comments