From 16d025f7faa96387d26bffb6cfec186938ec3b83 Mon Sep 17 00:00:00 2001 From: Iniubong Obonguko Date: Thu, 11 Jul 2024 23:07:52 +0100 Subject: [PATCH 1/4] feat: add await option for next-tick-style rule --- docs/rules/next-tick-style.md | 38 ++++++++- lib/rules/next-tick-style.js | 95 ++++++++++++++++++++++- tests/lib/rules/next-tick-style.js | 120 +++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+), 5 deletions(-) diff --git a/docs/rules/next-tick-style.md b/docs/rules/next-tick-style.md index 1224e93b9..d588d9d9c 100644 --- a/docs/rules/next-tick-style.md +++ b/docs/rules/next-tick-style.md @@ -2,13 +2,13 @@ pageClass: rule-details sidebarDepth: 0 title: vue/next-tick-style -description: enforce Promise or callback style in `nextTick` +description: enforce Await, Promise or callback style in `nextTick` since: v7.5.0 --- # vue/next-tick-style -> enforce Promise or callback style in `nextTick` +> enforce Promise, Await or callback style in `nextTick` - :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. @@ -52,13 +52,45 @@ Default is set to `promise`. ```json { - "vue/next-tick-style": ["error", "promise" | "callback"] + "vue/next-tick-style": ["error", "promise" | "await" | "callback"] } ``` - `"promise"` (default) ... requires using the promise version. +- `"await"` ... requires using the await syntax version. - `"callback"` ... requires using the callback version. Use this if you use a Vue version below v2.1.0. +### `"await"` + + + +```vue + +``` + + ### `"callback"` diff --git a/lib/rules/next-tick-style.js b/lib/rules/next-tick-style.js index 08cc85a1b..e3b463abf 100644 --- a/lib/rules/next-tick-style.js +++ b/lib/rules/next-tick-style.js @@ -79,17 +79,53 @@ function isAwaitedPromise(callExpression) { ) } +/** + * @param {CallExpression} callExpression + * @returns {boolean} + */ +function isAwaitedFunction(callExpression) { + return ( + callExpression.parent.type === 'AwaitExpression' && + callExpression.parent.parent.type !== 'MemberExpression' + ) +} + +/** + * @param {Expression | SpreadElement} callback + * @param {SourceCode} sourceCode + * @returns {string} + */ +function extractCallbackBody(callback, sourceCode) { + if ( + callback.type !== 'FunctionExpression' && + callback.type !== 'ArrowFunctionExpression' + ) { + return '' + } + + if (callback.body.type === 'BlockStatement') { + return sourceCode + .getText(callback.body) + .slice(1, -1) // Remove curly braces + .trim() + } + + return sourceCode.getText(callback.body) +} + module.exports = { meta: { type: 'suggestion', docs: { - description: 'enforce Promise or callback style in `nextTick`', + description: 'enforce Await, Promise or callback style in `nextTick`', categories: undefined, url: 'https://eslint.vuejs.org/rules/next-tick-style.html' }, fixable: 'code', - schema: [{ enum: ['promise', 'callback'] }], + schema: [{ enum: ['await', 'promise', 'callback'] }], messages: { + useAwait: + 'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.', usePromise: 'Use the Promise returned by `nextTick` instead of passing a callback function.', useCallback: @@ -123,6 +159,61 @@ module.exports = { return } + if (preferredStyle === 'await') { + if ( + callExpression.arguments.length > 0 || + !isAwaitedFunction(callExpression) + ) { + context.report({ + node, + messageId: 'useAwait', + fix(fixer) { + const sourceCode = context.getSourceCode() + + // Handle callback to await conversion + if (callExpression.arguments.length > 0) { + const [args] = callExpression.arguments + let callbackBody = null + + callbackBody = + args.type === 'ArrowFunctionExpression' || + args.type === 'FunctionExpression' + ? extractCallbackBody(args, sourceCode) + : `${sourceCode.getText(args)}()` + + const nextTickCaller = sourceCode.getText( + callExpression.callee + ) + return fixer.replaceText( + callExpression.parent, + `await ${nextTickCaller}();${callbackBody};` + ) + } + + // Handle promise to await conversion + if (isAwaitedPromise(callExpression)) { + const thenCall = callExpression.parent.parent + if (thenCall === null || thenCall.type !== 'CallExpression') + return null + const [thenCallback] = thenCall.arguments + if (thenCallback) { + const thenCallbackBody = extractCallbackBody( + thenCallback, + sourceCode + ) + return fixer.replaceText( + thenCall, + `await ${sourceCode.getText(callExpression)};${thenCallbackBody}` + ) + } + } + return null + } + }) + } + + return + } if ( callExpression.arguments.length > 0 || !isAwaitedPromise(callExpression) diff --git a/tests/lib/rules/next-tick-style.js b/tests/lib/rules/next-tick-style.js index c143c8a40..899be6730 100644 --- a/tests/lib/rules/next-tick-style.js +++ b/tests/lib/rules/next-tick-style.js @@ -54,6 +54,18 @@ tester.run('next-tick-style', rule, { }`, options: ['promise'] }, + { + filename: 'test.vue', + code: ``, + options: ['await'] + }, { filename: 'test.vue', code: ``, + options: ['await'] + }, + { + filename: 'test.vue', + code: ``, options: ['callback'] } ], @@ -237,6 +265,98 @@ tester.run('next-tick-style', rule, { } ] }, + { + filename: 'test.vue', + code: ``, + output: ``, + options: ['await'], + errors: [ + { + message: + 'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.', + line: 4, + column: 16 + }, + { + message: + 'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.', + line: 5, + column: 15 + }, + { + message: + 'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.', + line: 6, + column: 11 + }, + { + message: + 'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.', + line: 8, + column: 16 + }, + { + message: + 'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.', + line: 9, + column: 15 + }, + { + message: + 'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.', + line: 10, + column: 11 + }, + { + message: + 'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.', + line: 12, + column: 16 + }, + { + message: + 'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.', + line: 13, + column: 15 + }, + { + message: + 'Use the await keyword with the Promise returned by `nextTick` instead of passing a callback function or using `.then()`.', + line: 14, + column: 11 + } + ] + }, { filename: 'test.vue', code: `