Skip to content

Commit f237054

Browse files
committed
add ignoreAssignmentVariable
1 parent 1e13293 commit f237054

File tree

3 files changed

+115
-3
lines changed

3 files changed

+115
-3
lines changed

Diff for: __tests__/always-return.js

+33
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ ruleTester.run('always-return', rule, {
105105
.finally(() => console.error('end'))`,
106106
options: [{ ignoreLastCallback: true }],
107107
},
108+
{
109+
code: `hey.then(x => { window.a = x })`,
110+
options: [{ ignoreAssignmentVariable: ['window'] }],
111+
},
112+
{
113+
code: `hey.then(x => { window.a.x = x })`,
114+
options: [{ ignoreAssignmentVariable: ['window.a'] }],
115+
},
108116
],
109117

110118
invalid: [
@@ -230,5 +238,30 @@ ruleTester.run('always-return', rule, {
230238
options: [{ ignoreLastCallback: true }],
231239
errors: [{ message }],
232240
},
241+
{
242+
code: `hey.then(x => { console.log(x) })`,
243+
options: [{ ignoreAssignmentVariable: ['window'] }],
244+
errors: [{ message }],
245+
},
246+
{
247+
code: `hey.then(x => { console.log(x) })`,
248+
options: [{ ignoreAssignmentVariable: ['window.s'] }],
249+
errors: [{ message }],
250+
},
251+
{
252+
code: `hey.then(x => { ++window.a })`,
253+
options: [{ ignoreAssignmentVariable: ['window.a'] }],
254+
errors: [{ message }],
255+
},
256+
{
257+
code: `hey.then(x => { window.a.b.c = x })`,
258+
options: [{ ignoreAssignmentVariable: ['window.a'] }],
259+
errors: [{ message }],
260+
},
261+
{
262+
code: `hey.then(x => { let a = x })`,
263+
options: [{ ignoreAssignmentVariable: ['window.a'] }],
264+
errors: [{ message }],
265+
},
233266
],
234267
})

Diff for: docs/rules/always-return.md

+29-3
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ myPromise.then((b) => {
4949

5050
##### `ignoreLastCallback`
5151

52-
You can pass an `{ ignoreLastCallback: true }` as an option to this rule to the
53-
last `then()` callback in a promise chain does not warn if it does not have a
54-
`return`. Default is `false`.
52+
You can pass an `{ ignoreLastCallback: true }` as an option to this rule so that
53+
the last `then()` callback in a promise chain does not warn if it does not have
54+
a `return`. Default is `false`.
5555

5656
```js
5757
// OK
@@ -92,3 +92,29 @@ function foo() {
9292
})
9393
}
9494
```
95+
96+
##### `ignoreAssignmentVariable`
97+
98+
You can pass an `{ ignoreAssignmentVariable: [] }` as an option to this rule
99+
with a list of variable names so that the last `then()` callback in a promise
100+
chain does not warn if it does an assignment to a global variable. Default is
101+
`[]`.
102+
103+
```js
104+
/* eslint promise/always-return: ["error", { ignoreAssignmentVariable: ["window"] }] */
105+
106+
// OK
107+
promise.then((x) => {
108+
window.x = x
109+
})
110+
111+
promise
112+
// NG
113+
.then((x) => {
114+
window.x.y = x
115+
})
116+
// NG
117+
.then((x) => {
118+
let b = x
119+
})
120+
```

Diff for: rules/always-return.js

+53
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,34 @@ function peek(arr) {
124124
return arr[arr.length - 1]
125125
}
126126

127+
/**
128+
* Checks if the node is an assignment to an ignored variable
129+
* @param {Node} node
130+
* @param {string[]} ignoredVars
131+
* @returns {boolean}
132+
*/
133+
function isIgnoredAssignment(node, ignoredVars) {
134+
if (node.type !== 'ExpressionStatement') return false
135+
const expr = node.expression
136+
if (expr.type !== 'AssignmentExpression') return false
137+
const left = expr.left
138+
// istanbul ignore else
139+
if (left.type === 'MemberExpression') {
140+
if (left.object.type === 'Identifier') {
141+
return ignoredVars.includes(left.object.name)
142+
}
143+
if (
144+
left.object.type === 'MemberExpression' &&
145+
left.object.object.type === 'Identifier'
146+
) {
147+
const fullName = `${left.object.object.name}.${left.object.property.name}`
148+
return ignoredVars.includes(fullName)
149+
}
150+
}
151+
// fallback
152+
return false
153+
}
154+
127155
module.exports = {
128156
meta: {
129157
type: 'problem',
@@ -139,6 +167,13 @@ module.exports = {
139167
ignoreLastCallback: {
140168
type: 'boolean',
141169
},
170+
ignoreAssignmentVariable: {
171+
type: 'array',
172+
items: {
173+
type: 'string',
174+
},
175+
uniqueItems: true,
176+
},
142177
},
143178
additionalProperties: false,
144179
},
@@ -150,6 +185,7 @@ module.exports = {
150185
create(context) {
151186
const options = context.options[0] || {}
152187
const ignoreLastCallback = !!options.ignoreLastCallback
188+
const ignoreAssignmentVariable = options.ignoreAssignmentVariable || []
153189
/**
154190
* @typedef {object} FuncInfo
155191
* @property {string[]} branchIDStack This is a stack representing the currently
@@ -244,6 +280,23 @@ module.exports = {
244280
return
245281
}
246282

283+
if (ignoreAssignmentVariable.length && isLastCallback(node)) {
284+
let hasIgnoredAssignment = false
285+
286+
//istanbul ignore else
287+
if (node.body?.type === 'BlockStatement') {
288+
node.body.body.forEach((statement) => {
289+
if (isIgnoredAssignment(statement, ignoreAssignmentVariable)) {
290+
hasIgnoredAssignment = true
291+
}
292+
})
293+
}
294+
295+
if (hasIgnoredAssignment) {
296+
return
297+
}
298+
}
299+
247300
path.finalSegments.forEach((segment) => {
248301
const id = segment.id
249302
const branch = funcInfo.branchInfoMap[id]

0 commit comments

Comments
 (0)