diff --git a/docs/rules/README.md b/docs/rules/README.md
index 86b51d484..5b21a0840 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -167,6 +167,7 @@ For example:
| [vue/no-deprecated-v-bind-sync](./no-deprecated-v-bind-sync.md) | disallow use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+) | :wrench: |
| [vue/no-empty-pattern](./no-empty-pattern.md) | disallow empty destructuring patterns | |
| [vue/no-irregular-whitespace](./no-irregular-whitespace.md) | disallow irregular whitespace | |
+| [vue/no-lifecycle-after-await](./no-lifecycle-after-await.md) | disallow asynchronously registered lifecycle hooks | |
| [vue/no-ref-as-operand](./no-ref-as-operand.md) | disallow use of value wrapped by `ref()` (Composition API) as an operand | |
| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | |
| [vue/no-restricted-syntax](./no-restricted-syntax.md) | disallow specified syntax | |
diff --git a/docs/rules/no-lifecycle-after-await.md b/docs/rules/no-lifecycle-after-await.md
new file mode 100644
index 000000000..aaa521f7c
--- /dev/null
+++ b/docs/rules/no-lifecycle-after-await.md
@@ -0,0 +1,47 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-lifecycle-after-await
+description: disallow asynchronously registered lifecycle hooks
+---
+# vue/no-lifecycle-after-await
+> disallow asynchronously registered lifecycle hooks
+
+## :book: Rule Details
+
+This rule reports the lifecycle hooks after `await` expression.
+In `setup()` function, `onXXX` lifecycle hooks should be registered synchronously.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further reading
+
+- [Vue RFCs - 0013-composition-api](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0013-composition-api.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-lifecycle-after-await.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-lifecycle-after-await.js)
diff --git a/lib/index.js b/lib/index.js
index e772dc590..55d753058 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -49,6 +49,7 @@ module.exports = {
'no-duplicate-attributes': require('./rules/no-duplicate-attributes'),
'no-empty-pattern': require('./rules/no-empty-pattern'),
'no-irregular-whitespace': require('./rules/no-irregular-whitespace'),
+ 'no-lifecycle-after-await': require('./rules/no-lifecycle-after-await'),
'no-multi-spaces': require('./rules/no-multi-spaces'),
'no-multiple-template-root': require('./rules/no-multiple-template-root'),
'no-parsing-error': require('./rules/no-parsing-error'),
diff --git a/lib/rules/no-lifecycle-after-await.js b/lib/rules/no-lifecycle-after-await.js
new file mode 100644
index 000000000..1f78165e5
--- /dev/null
+++ b/lib/rules/no-lifecycle-after-await.js
@@ -0,0 +1,111 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+const { ReferenceTracker } = require('eslint-utils')
+const utils = require('../utils')
+
+const LIFECYCLE_HOOKS = ['onBeforeMount', 'onBeforeUnmount', 'onBeforeUpdate', 'onErrorCaptured', 'onMounted', 'onRenderTracked', 'onRenderTriggered', 'onUnmounted', 'onUpdated', 'onActivated', 'onDeactivated']
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'disallow asynchronously registered lifecycle hooks',
+ category: undefined,
+ url: 'https://eslint.vuejs.org/rules/no-lifecycle-after-await.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ forbidden: 'The lifecycle hooks after `await` expression are forbidden.'
+ }
+ },
+ create (context) {
+ const lifecycleHookCallNodes = new Set()
+ const setupFunctions = new Map()
+ const forbiddenNodes = new Map()
+
+ function addForbiddenNode (property, node) {
+ let list = forbiddenNodes.get(property)
+ if (!list) {
+ list = []
+ forbiddenNodes.set(property, list)
+ }
+ list.push(node)
+ }
+
+ let scopeStack = { upper: null, functionNode: null }
+
+ return Object.assign(
+ {
+ 'Program' () {
+ const tracker = new ReferenceTracker(context.getScope())
+ const traceMap = {
+ vue: {
+ [ReferenceTracker.ESM]: true
+ }
+ }
+ for (const lifecycleHook of LIFECYCLE_HOOKS) {
+ traceMap.vue[lifecycleHook] = {
+ [ReferenceTracker.CALL]: true
+ }
+ }
+
+ for (const { node } of tracker.iterateEsmReferences(traceMap)) {
+ lifecycleHookCallNodes.add(node)
+ }
+ },
+ 'Property[value.type=/^(Arrow)?FunctionExpression$/]' (node) {
+ if (utils.getStaticPropertyName(node) !== 'setup') {
+ return
+ }
+
+ setupFunctions.set(node.value, {
+ setupProperty: node,
+ afterAwait: false
+ })
+ },
+ ':function' (node) {
+ scopeStack = { upper: scopeStack, functionNode: node }
+ },
+ 'AwaitExpression' () {
+ const setupFunctionData = setupFunctions.get(scopeStack.functionNode)
+ if (!setupFunctionData) {
+ return
+ }
+ setupFunctionData.afterAwait = true
+ },
+ 'CallExpression' (node) {
+ const setupFunctionData = setupFunctions.get(scopeStack.functionNode)
+ if (!setupFunctionData || !setupFunctionData.afterAwait) {
+ return
+ }
+
+ if (lifecycleHookCallNodes.has(node)) {
+ addForbiddenNode(setupFunctionData.setupProperty, node)
+ }
+ },
+ ':function:exit' (node) {
+ scopeStack = scopeStack.upper
+
+ setupFunctions.delete(node)
+ }
+ },
+ utils.executeOnVue(context, obj => {
+ const reportsList = obj.properties
+ .map(item => forbiddenNodes.get(item))
+ .filter(reports => !!reports)
+ for (const reports of reportsList) {
+ for (const node of reports) {
+ context.report({
+ node,
+ messageId: 'forbidden'
+ })
+ }
+ }
+ })
+ )
+ }
+}
diff --git a/tests/lib/rules/no-lifecycle-after-await.js b/tests/lib/rules/no-lifecycle-after-await.js
new file mode 100644
index 000000000..eaa99f843
--- /dev/null
+++ b/tests/lib/rules/no-lifecycle-after-await.js
@@ -0,0 +1,180 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/no-lifecycle-after-await')
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: { ecmaVersion: 2019, sourceType: 'module' }
+})
+
+tester.run('no-lifecycle-after-await', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ }, {
+ filename: 'test.vue',
+ code: `
+
+ `
+ }, {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'The lifecycle hooks after `await` expression are forbidden.',
+ line: 8,
+ column: 11,
+ endLine: 8,
+ endColumn: 41
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ messageId: 'forbidden',
+ line: 8
+ },
+ {
+ messageId: 'forbidden',
+ line: 9
+ },
+ {
+ messageId: 'forbidden',
+ line: 10
+ },
+ {
+ messageId: 'forbidden',
+ line: 11
+ },
+ {
+ messageId: 'forbidden',
+ line: 12
+ },
+ {
+ messageId: 'forbidden',
+ line: 13
+ },
+ {
+ messageId: 'forbidden',
+ line: 14
+ },
+ {
+ messageId: 'forbidden',
+ line: 15
+ },
+ {
+ messageId: 'forbidden',
+ line: 16
+ },
+ {
+ messageId: 'forbidden',
+ line: 17
+ },
+ {
+ messageId: 'forbidden',
+ line: 18
+ }
+ ]
+ }
+ ]
+})