Skip to content

Commit 16d025f

Browse files
committed
feat: add await option for next-tick-style rule
1 parent f5cb93d commit 16d025f

File tree

3 files changed

+248
-5
lines changed

3 files changed

+248
-5
lines changed

docs/rules/next-tick-style.md

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
pageClass: rule-details
33
sidebarDepth: 0
44
title: vue/next-tick-style
5-
description: enforce Promise or callback style in `nextTick`
5+
description: enforce Await, Promise or callback style in `nextTick`
66
since: v7.5.0
77
---
88

99
# vue/next-tick-style
1010

11-
> enforce Promise or callback style in `nextTick`
11+
> enforce Promise, Await or callback style in `nextTick`
1212
1313
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
1414

@@ -52,13 +52,45 @@ Default is set to `promise`.
5252

5353
```json
5454
{
55-
"vue/next-tick-style": ["error", "promise" | "callback"]
55+
"vue/next-tick-style": ["error", "promise" | "await" | "callback"]
5656
}
5757
```
5858

5959
- `"promise"` (default) ... requires using the promise version.
60+
- `"await"` ... requires using the await syntax version.
6061
- `"callback"` ... requires using the callback version. Use this if you use a Vue version below v2.1.0.
6162

63+
### `"await"`
64+
65+
<eslint-code-block fix :rules="{'vue/next-tick-style': ['error', 'await']}">
66+
67+
```vue
68+
<script>
69+
import { nextTick as nt } from 'vue';
70+
71+
export default {
72+
async mounted() {
73+
/* ✓ GOOD */
74+
await nt(); callback();
75+
await Vue.nextTick(); this.callback();
76+
await this.$nextTick(); this.callback();
77+
78+
/* ✗ BAD */
79+
nt().then(() => callback());
80+
Vue.nextTick().then(() => callback());
81+
this.$nextTick().then(() => callback());
82+
nt(() => callback());
83+
nt(callback);
84+
Vue.nextTick(() => callback());
85+
Vue.nextTick(callback);
86+
this.$nextTick(() => callback());
87+
this.$nextTick(callback);
88+
}
89+
}
90+
</script>
91+
```
92+
93+
</eslint-code-block>
6294
### `"callback"`
6395

6496
<eslint-code-block fix :rules="{'vue/next-tick-style': ['error', 'callback']}">

lib/rules/next-tick-style.js

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,53 @@ function isAwaitedPromise(callExpression) {
7979
)
8080
}
8181

82+
/**
83+
* @param {CallExpression} callExpression
84+
* @returns {boolean}
85+
*/
86+
function isAwaitedFunction(callExpression) {
87+
return (
88+
callExpression.parent.type === 'AwaitExpression' &&
89+
callExpression.parent.parent.type !== 'MemberExpression'
90+
)
91+
}
92+
93+
/**
94+
* @param {Expression | SpreadElement} callback
95+
* @param {SourceCode} sourceCode
96+
* @returns {string}
97+
*/
98+
function extractCallbackBody(callback, sourceCode) {
99+
if (
100+
callback.type !== 'FunctionExpression' &&
101+
callback.type !== 'ArrowFunctionExpression'
102+
) {
103+
return ''
104+
}
105+
106+
if (callback.body.type === 'BlockStatement') {
107+
return sourceCode
108+
.getText(callback.body)
109+
.slice(1, -1) // Remove curly braces
110+
.trim()
111+
}
112+
113+
return sourceCode.getText(callback.body)
114+
}
115+
82116
module.exports = {
83117
meta: {
84118
type: 'suggestion',
85119
docs: {
86-
description: 'enforce Promise or callback style in `nextTick`',
120+
description: 'enforce Await, Promise or callback style in `nextTick`',
87121
categories: undefined,
88122
url: 'https://eslint.vuejs.org/rules/next-tick-style.html'
89123
},
90124
fixable: 'code',
91-
schema: [{ enum: ['promise', 'callback'] }],
125+
schema: [{ enum: ['await', 'promise', 'callback'] }],
92126
messages: {
127+
useAwait:
128+
'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.',
93129
usePromise:
94130
'Use the Promise returned by `nextTick` instead of passing a callback function.',
95131
useCallback:
@@ -123,6 +159,61 @@ module.exports = {
123159
return
124160
}
125161

162+
if (preferredStyle === 'await') {
163+
if (
164+
callExpression.arguments.length > 0 ||
165+
!isAwaitedFunction(callExpression)
166+
) {
167+
context.report({
168+
node,
169+
messageId: 'useAwait',
170+
fix(fixer) {
171+
const sourceCode = context.getSourceCode()
172+
173+
// Handle callback to await conversion
174+
if (callExpression.arguments.length > 0) {
175+
const [args] = callExpression.arguments
176+
let callbackBody = null
177+
178+
callbackBody =
179+
args.type === 'ArrowFunctionExpression' ||
180+
args.type === 'FunctionExpression'
181+
? extractCallbackBody(args, sourceCode)
182+
: `${sourceCode.getText(args)}()`
183+
184+
const nextTickCaller = sourceCode.getText(
185+
callExpression.callee
186+
)
187+
return fixer.replaceText(
188+
callExpression.parent,
189+
`await ${nextTickCaller}();${callbackBody};`
190+
)
191+
}
192+
193+
// Handle promise to await conversion
194+
if (isAwaitedPromise(callExpression)) {
195+
const thenCall = callExpression.parent.parent
196+
if (thenCall === null || thenCall.type !== 'CallExpression')
197+
return null
198+
const [thenCallback] = thenCall.arguments
199+
if (thenCallback) {
200+
const thenCallbackBody = extractCallbackBody(
201+
thenCallback,
202+
sourceCode
203+
)
204+
return fixer.replaceText(
205+
thenCall,
206+
`await ${sourceCode.getText(callExpression)};${thenCallbackBody}`
207+
)
208+
}
209+
}
210+
return null
211+
}
212+
})
213+
}
214+
215+
return
216+
}
126217
if (
127218
callExpression.arguments.length > 0 ||
128219
!isAwaitedPromise(callExpression)

tests/lib/rules/next-tick-style.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ tester.run('next-tick-style', rule, {
5454
}</script>`,
5555
options: ['promise']
5656
},
57+
{
58+
filename: 'test.vue',
59+
code: `<script>import { nextTick as nt } from 'vue';
60+
export default {
61+
async mounted() {
62+
await this.$nextTick(); callback();
63+
await Vue.nextTick(); callback();
64+
await nt(); callback();
65+
}
66+
}</script>`,
67+
options: ['await']
68+
},
5769
{
5870
filename: 'test.vue',
5971
code: `<script>import { nextTick as nt } from 'vue';
@@ -102,6 +114,22 @@ tester.run('next-tick-style', rule, {
102114
foo.then(this.$nextTick, catchHandler);
103115
}
104116
}</script>`,
117+
options: ['await']
118+
},
119+
{
120+
filename: 'test.vue',
121+
code: `<script>import { nextTick as nt } from 'vue';
122+
export default {
123+
mounted() {
124+
foo.then(this.$nextTick);
125+
foo.then(Vue.nextTick);
126+
foo.then(nt);
127+
128+
foo.then(nt, catchHandler);
129+
foo.then(Vue.nextTick, catchHandler);
130+
foo.then(this.$nextTick, catchHandler);
131+
}
132+
}</script>`,
105133
options: ['callback']
106134
}
107135
],
@@ -237,6 +265,98 @@ tester.run('next-tick-style', rule, {
237265
}
238266
]
239267
},
268+
{
269+
filename: 'test.vue',
270+
code: `<script>import { nextTick as nt } from 'vue';
271+
export default {
272+
async mounted() {
273+
this.$nextTick(() => callback());
274+
Vue.nextTick(() => callback());
275+
nt(() => callback());
276+
277+
this.$nextTick(callback);
278+
Vue.nextTick(callback);
279+
nt(callback);
280+
281+
this.$nextTick().then(() => callback());
282+
Vue.nextTick().then(() => callback());
283+
nt().then(() => callback());
284+
}
285+
}</script>`,
286+
output: `<script>import { nextTick as nt } from 'vue';
287+
export default {
288+
async mounted() {
289+
await this.$nextTick();callback();
290+
await Vue.nextTick();callback();
291+
await nt();callback();
292+
293+
await this.$nextTick();callback();
294+
await Vue.nextTick();callback();
295+
await nt();callback();
296+
297+
await this.$nextTick();callback();
298+
await Vue.nextTick();callback();
299+
await nt();callback();
300+
}
301+
}</script>`,
302+
options: ['await'],
303+
errors: [
304+
{
305+
message:
306+
'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.',
307+
line: 4,
308+
column: 16
309+
},
310+
{
311+
message:
312+
'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.',
313+
line: 5,
314+
column: 15
315+
},
316+
{
317+
message:
318+
'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.',
319+
line: 6,
320+
column: 11
321+
},
322+
{
323+
message:
324+
'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.',
325+
line: 8,
326+
column: 16
327+
},
328+
{
329+
message:
330+
'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.',
331+
line: 9,
332+
column: 15
333+
},
334+
{
335+
message:
336+
'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.',
337+
line: 10,
338+
column: 11
339+
},
340+
{
341+
message:
342+
'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.',
343+
line: 12,
344+
column: 16
345+
},
346+
{
347+
message:
348+
'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.',
349+
line: 13,
350+
column: 15
351+
},
352+
{
353+
message:
354+
'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.',
355+
line: 14,
356+
column: 11
357+
}
358+
]
359+
},
240360
{
241361
filename: 'test.vue',
242362
code: `<script>import { nextTick as nt } from 'vue';

0 commit comments

Comments
 (0)