Repository: vuejs/eslint-plugin-vue (js)

Issue:
Rule Proposal: template-no-target-blank
<!--
  Before proposing new rule, please make sure it hasn't been posted already.
  You can see all open propositions here:
  https://github.com/vuejs/eslint-plugin-vue/issues?q=is%3Aopen+is%3Aissue+label%3A%22new+rule+proposition%22
-->

**Please describe what the rule should do:**
<!-- A clear and concise description of what the rule should do. -->
This rule should warn when `rel='noreferrer noopener'` does not exist in attributes of an anchor tag with `target="_blank"`.

**What category should the rule belong to?**
<!-- (place an "X" next to just one item) -->
- [ ] Enforces code style
- [x] Warns about a potential error
- [ ] Suggests an alternate way of doing something
- [ ] Other (please specify:)

**Provide 2-3 code examples that this rule should warn about:**
```vue
<template>
  <a href="http://example.com" target="_blank">link</a>
</template>
```

**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
ref: https://developers.google.com/web/tools/lighthouse/audits/noopener?hl=en
A rule does exist for this problem in [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-target-blank.md)

Pull request description:
New: add `vue/no-template-no-target-blank` rule
Fixes #1054 

Patch:
diff --git a/docs/.vuepress/components/eslint-code-block.vue b/docs/.vuepress/components/eslint-code-block.vue
index ab33abdf..fed685d9 100644
--- a/docs/.vuepress/components/eslint-code-block.vue
+++ b/docs/.vuepress/components/eslint-code-block.vue
@@ -18,8 +18,7 @@
 </template>
 
 <script>
-// https://github.com/vuejs/vuepress/issues/451
-import EslintEditor from '../../../node_modules/vue-eslint-editor'
+import EslintEditor from 'vue-eslint-editor'
 import { rules, processors } from '../../../'
 
 export default {
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index 0b78e7eb..a6756baa 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -66,12 +66,18 @@ module.exports = {
   base: '/',
   title: 'eslint-plugin-vue',
   description: 'Official ESLint plugin for Vue.js',
-  serviceWorker: true,
   evergreen: true,
   head: [
     ['link', { rel: 'icon', href: '/favicon.png' }]
   ],
 
+  plugins: {
+    '@vuepress/pwa': {
+      serviceWorker: true,
+      updatePopup: true
+    }
+  },
+
   themeConfig: {
     repo: 'vuejs/eslint-plugin-vue',
     docsRepo: 'vuejs/eslint-plugin-vue',
@@ -79,9 +85,6 @@ module.exports = {
     docsBranch: 'master',
     editLinks: true,
     lastUpdated: true,
-    serviceWorker: {
-      updatePopup: true
-    },
 
     nav: [
       { text: 'User Guide', link: '/user-guide/' },
diff --git a/docs/.vuepress/style.styl b/docs/.vuepress/styles/index.styl
similarity index 81%
rename from docs/.vuepress/style.styl
rename to docs/.vuepress/styles/index.styl
index 564ebd26..48680bbf 100644
--- a/docs/.vuepress/style.styl
+++ b/docs/.vuepress/styles/index.styl
@@ -1,4 +1,4 @@
-.theme-container.rule-details .content > h1 {
+.theme-container.rule-details .theme-default-content > h1 {
   font-size: 1.8rem;
 
   + blockquote {
diff --git a/docs/.vuepress/theme/index.js b/docs/.vuepress/theme/index.js
new file mode 100644
index 00000000..b91b8a57
--- /dev/null
+++ b/docs/.vuepress/theme/index.js
@@ -0,0 +1,3 @@
+module.exports = {
+  extend: '@vuepress/theme-default'
+}
diff --git a/docs/.vuepress/theme/Layout.vue b/docs/.vuepress/theme/layouts/Layout.vue
similarity index 83%
rename from docs/.vuepress/theme/Layout.vue
rename to docs/.vuepress/theme/layouts/Layout.vue
index 78dab76f..b88c846c 100644
--- a/docs/.vuepress/theme/Layout.vue
+++ b/docs/.vuepress/theme/layouts/Layout.vue
@@ -12,7 +12,7 @@
     />
     <template
       slot="page-top">
-      <div class="content beta-doc-description">
+      <div class="theme-default-content beta-doc-description">
         <div class="warning custom-block">
           <p class="custom-block-title">Note</p>
           <p>This is a documentation for version <code>{{docVersion}}</code>.<template v-if="hasNotYetBeenReleased"> Also, this documentation may contain content that has not yet been released.</template><br>
@@ -30,14 +30,16 @@
     />
   </BaseLayout>
 </template>
+
 <script>
 /**
  * Layout definition to navigate to older versions of the document.
  */
-import BaseLayout from 'vuepress/lib/default-theme/Layout.vue'
+import BaseLayout from '@vuepress/theme-default/layouts/Layout.vue'
 import semver from 'semver'
-const version = semver.parse(require('../../../package.json').version)
+const version = semver.parse(require('../../../../package.json').version)
 export default {
+  name: 'MyLayout',
   components: {
     BaseLayout
   },
@@ -63,14 +65,15 @@ export default {
   }
 }
 </script>
+
 <style scoped>
 .beta-doc-description {
   padding-bottom: 0;
 }
-* ::v-deep .content ~ .content {
+* ::v-deep .theme-default-content ~ .theme-default-content {
   padding-top: 0;
 }
-* ::v-deep .content:not(.custom) h1{
+* ::v-deep .theme-default-content:not(.custom) h1{
   margin-top: -3.1rem;
 }
 </style>
diff --git a/docs/rules/README.md b/docs/rules/README.md
index e184873e..e229c19f 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -2,10 +2,10 @@
 sidebarDepth: 0
 ---
 
-<!-- This file is automatically generated in tools/update-docs-rules-index.js, do not change! -->
-
 # Available rules
 
+<!-- This file is automatically generated in tools/update-docs-rules-index.js, do not change! -->
+
 ::: tip Legend
   :wrench: Indicates that the rule is fixable, and using `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the reported problems.
 :::
@@ -39,11 +39,13 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
 | Rule ID | Description |    |
 |:--------|:------------|:---|
 | [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties |  |
+| [vue/no-deprecated-data-object-declaration](./no-deprecated-data-object-declaration.md) | disallow using deprecated object declaration on data | :wrench: |
 | [vue/no-deprecated-filter](./no-deprecated-filter.md) | disallow using deprecated filters syntax |  |
 | [vue/no-deprecated-scope-attribute](./no-deprecated-scope-attribute.md) | disallow deprecated `scope` attribute (in Vue.js 2.5.0+) | :wrench: |
 | [vue/no-deprecated-slot-attribute](./no-deprecated-slot-attribute.md) | disallow deprecated `slot` attribute (in Vue.js 2.6.0+) | :wrench: |
 | [vue/no-deprecated-slot-scope-attribute](./no-deprecated-slot-scope-attribute.md) | disallow deprecated `slot-scope` attribute (in Vue.js 2.6.0+) | :wrench: |
 | [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-deprecated-v-on-number-modifiers](./no-deprecated-v-on-number-modifiers.md) | disallow using deprecated number (keycode) modifiers | :wrench: |
 | [vue/no-dupe-keys](./no-dupe-keys.md) | disallow duplication of field names |  |
 | [vue/no-duplicate-attributes](./no-duplicate-attributes.md) | disallow duplication of attributes |  |
 | [vue/no-lifecycle-after-await](./no-lifecycle-after-await.md) | disallow asynchronously registered lifecycle hooks |  |
@@ -272,6 +274,7 @@ For example:
 | [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 |  |
 | [vue/no-static-inline-styles](./no-static-inline-styles.md) | disallow static inline `style` attributes |  |
+| [vue/no-template-target-blank](./no-template-target-blank.md) | disallow target="_blank" attribute without rel="noopener noreferrer" |  |
 | [vue/no-unsupported-features](./no-unsupported-features.md) | disallow unsupported Vue.js syntax on the specified version | :wrench: |
 | [vue/object-curly-spacing](./object-curly-spacing.md) | enforce consistent spacing inside braces | :wrench: |
 | [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: |
diff --git a/docs/rules/no-deprecated-data-object-declaration.md b/docs/rules/no-deprecated-data-object-declaration.md
new file mode 100644
index 00000000..c095fadb
--- /dev/null
+++ b/docs/rules/no-deprecated-data-object-declaration.md
@@ -0,0 +1,65 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-data-object-declaration
+description: disallow using deprecated object declaration on data
+---
+# vue/no-deprecated-data-object-declaration
+> disallow using deprecated object declaration on data
+
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :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.
+
+## :book: Rule Details
+
+This rule reports use of deprecated object declaration on `data` property (in Vue.js 3.0.0+).
+The different from `vue/no-shared-component-data` is the root instance being also disallowed.
+
+<eslint-code-block fix :rules="{'vue/no-deprecated-data-object-declaration': ['error']}">
+
+```vue
+<script>
+/* ✗ BAD */
+createApp({
+  data: {
+    foo: null
+  }
+}).mount('#app')
+export default {
+  data: {
+    foo: null
+  }
+}
+
+/* ✓ GOOD */
+export default {
+  data () {
+    return {
+      foo: null
+    }
+  }
+}
+createApp({
+  data () {
+    return {
+      foo: null
+    }
+  }
+}).mount('#app')
+</script>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further reading
+
+- [RFC: remove data object declaration](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0019-remove-data-object-declaration.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-data-object-declaration.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-data-object-declaration.js)
diff --git a/docs/rules/no-deprecated-v-on-number-modifiers.md b/docs/rules/no-deprecated-v-on-number-modifiers.md
new file mode 100644
index 00000000..5ecba24a
--- /dev/null
+++ b/docs/rules/no-deprecated-v-on-number-modifiers.md
@@ -0,0 +1,52 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-v-on-number-modifiers
+description: disallow using deprecated number (keycode) modifiers (in Vue.js 3.0.0+)
+---
+# vue/no-deprecated-v-on-number-modifiers
+> disallow using deprecated number (keycode) modifiers (in Vue.js 3.0.0+)
+
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :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.
+
+## :book: Rule Details
+
+This rule reports use of deprecated `KeyboardEvent.keyCode` modifier on `v-on` directive (in Vue.js 3.0.0+)
+
+<eslint-code-block fix :rules="{'vue/no-deprecated-v-on-number-modifiers': ['error']}">
+
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <input v-on:keyup.page-down="onArrowUp">
+  <input @keyup.page-down="onArrowUp">
+  <input @keyup.9="onArrowUp"> <!-- 9 is KeyboardEvent.key -->
+
+
+  <!-- ✗ BAD -->
+  <input v-on:keyup.34="onArrowUp">
+  <input @keyup.34="onArrowUp">
+</template>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related rules
+
+- [valid-v-on]
+
+[valid-v-on]: valid-v-on.md
+
+## :books: Further reading
+
+- [RFC: drop keycode support](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0014-drop-keycode-support.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-v-on-number-modifiers.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-v-on-number-modifiers.js)
diff --git a/docs/rules/no-template-target-blank.md b/docs/rules/no-template-target-blank.md
new file mode 100644
index 00000000..67dfcc99
--- /dev/null
+++ b/docs/rules/no-template-target-blank.md
@@ -0,0 +1,99 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-template-target-blank
+description: disallow target="_blank" attribute without rel="noopener noreferrer"
+---
+# vue/no-template-target-blank
+> disallow target="_blank" attribute without rel="noopener noreferrer"
+
+## :book: Rule Details
+
+This rule disallows using `target="_blank"` attribute without `rel="noopener noreferrer"` to avoid a security vulnerability([see here for more details](https://mathiasbynens.github.io/rel-noopener/)).
+
+<eslint-code-block :rules="{'vue/no-template-target-blank': ['error']}">
+
+```vue
+<template>
+  <!-- ✓ Good -->
+  <a link="http://example.com" target="_blank" rel="noopener noreferrer">link</a>
+
+  <!-- ✗ BAD -->
+  <a link="http://example.com" target="_blank" >link</a>
+</temlate>
+```
+
+## :wrench: Options
+
+```json
+{
+  "vue/no-template-target-blank": ["error", {
+    "allowReferrer": true,
+    "enforceDynamicLinks": "always"
+  }]
+}
+```
+
+- `allowReferrer` ... If `true`, does not require noreferrer.default `false`
+- `enforceDynamicLinks ("always" | "never")` ... If `always`, enforces the rule if the href is a dynamic link. default `always`.
+
+### `{ allowReferrer: false }` (default)
+
+<eslint-code-block :rules="{'vue/no-template-target-blank': ['error', { allowReferrer: false }]}">
+
+```vue
+<template>
+  <!-- ✓ Good -->
+  <a link="http://example.com" target="_blank" rel="noopener noreferrer">link</a>
+
+  <!-- ✗ BAD -->
+  <a link="http://example.com" target="_blank" rel="noopener">link</a>
+</temlate>
+```
+
+### `{ allowReferrer: true }`
+
+<eslint-code-block :rules="{'vue/no-template-target-blank': ['error', { allowReferrer: true }]}">
+
+```vue
+<template>
+  <!-- ✓ Good -->
+  <a link="http://example.com" target="_blank" rel="noopener">link</a>
+
+  <!-- ✗ BAD -->
+  <a link="http://example.com" target="_blank" >link</a>
+</temlate>
+```
+
+### `{ "enforceDynamicLinks": "always" }` (default)
+
+<eslint-code-block :rules="{'vue/no-template-target-blank': ['error', { enforceDynamicLinks: 'never' }]}">
+
+```vue
+<template>
+  <!-- ✓ Good -->
+  <a :link="link" target="_blank" rel="noopener noreferrer">link</a>
+
+  <!-- ✗ BAD -->
+  <a :link="link" target="_blank">link</a>
+</temlate>
+```
+
+### `{ "enforceDynamicLinks": "never" }`
+
+<eslint-code-block :rules="{'vue/no-template-target-blank': ['error', { enforceDynamicLinks: 'never' }]}">
+
+```vue
+<template>
+  <!-- ✓ Good -->
+  <a :link="link" target="_blank">link</a>
+
+  <!-- ✗ BAD -->
+  <a link="http://example.com" target="_blank" >link</a>
+</temlate>
+```
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-template-target-blank.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-template-target-blank.js)
diff --git a/docs/rules/no-unused-vars.md b/docs/rules/no-unused-vars.md
index 321aede1..4d5d9ee2 100644
--- a/docs/rules/no-unused-vars.md
+++ b/docs/rules/no-unused-vars.md
@@ -35,14 +35,18 @@ This rule report variable definitions of v-for directives or scope attributes if
 
 ```js
 {
-    "vue/no-unsed-vars": [{
-        "ignorePattern": '^_',
+    "vue/no-unused-vars": ["error", {
+        "ignorePattern": "^_"
     }]
 }
 ```
+
 - `ignorePattern` ... disables reporting when your definitions of v-for directives or scope attributes match your ignorePattern Regular expression. default `null`, will ignore nothing
+
 ## :rocket: Suggestion
+
 - When your ignorePattern set to `^_`, we could provide a suggestion which add a prefix`_` to your variable and no more eslint error
+
 ## :mag: Implementation
 
 - [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-unused-vars.js)
diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js
index 3e45ddd2..3f2865df 100644
--- a/lib/configs/vue3-essential.js
+++ b/lib/configs/vue3-essential.js
@@ -7,11 +7,13 @@ module.exports = {
   extends: require.resolve('./base'),
   rules: {
     'vue/no-async-in-computed-properties': 'error',
+    'vue/no-deprecated-data-object-declaration': 'error',
     'vue/no-deprecated-filter': 'error',
     'vue/no-deprecated-scope-attribute': 'error',
     'vue/no-deprecated-slot-attribute': 'error',
     'vue/no-deprecated-slot-scope-attribute': 'error',
     'vue/no-deprecated-v-bind-sync': 'error',
+    'vue/no-deprecated-v-on-number-modifiers': 'error',
     'vue/no-dupe-keys': 'error',
     'vue/no-duplicate-attributes': 'error',
     'vue/no-lifecycle-after-await': 'error',
diff --git a/lib/index.js b/lib/index.js
index 10e2e876..040965de 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -40,11 +40,13 @@ module.exports = {
     'no-boolean-default': require('./rules/no-boolean-default'),
     'no-confusing-v-for-v-if': require('./rules/no-confusing-v-for-v-if'),
     'no-custom-modifiers-on-v-model': require('./rules/no-custom-modifiers-on-v-model'),
+    'no-deprecated-data-object-declaration': require('./rules/no-deprecated-data-object-declaration'),
     'no-deprecated-filter': require('./rules/no-deprecated-filter'),
     'no-deprecated-scope-attribute': require('./rules/no-deprecated-scope-attribute'),
     'no-deprecated-slot-attribute': require('./rules/no-deprecated-slot-attribute'),
     'no-deprecated-slot-scope-attribute': require('./rules/no-deprecated-slot-scope-attribute'),
     'no-deprecated-v-bind-sync': require('./rules/no-deprecated-v-bind-sync'),
+    'no-deprecated-v-on-number-modifiers': require('./rules/no-deprecated-v-on-number-modifiers'),
     'no-dupe-keys': require('./rules/no-dupe-keys'),
     'no-duplicate-attributes': require('./rules/no-duplicate-attributes'),
     'no-empty-pattern': require('./rules/no-empty-pattern'),
@@ -64,6 +66,7 @@ module.exports = {
     'no-static-inline-styles': require('./rules/no-static-inline-styles'),
     'no-template-key': require('./rules/no-template-key'),
     'no-template-shadow': require('./rules/no-template-shadow'),
+    'no-template-target-blank': require('./rules/no-template-target-blank'),
     'no-textarea-mustache': require('./rules/no-textarea-mustache'),
     'no-unsupported-features': require('./rules/no-unsupported-features'),
     'no-unused-components': require('./rules/no-unused-components'),
diff --git a/lib/rules/no-deprecated-data-object-declaration.js b/lib/rules/no-deprecated-data-object-declaration.js
new file mode 100644
index 00000000..4c8399d7
--- /dev/null
+++ b/lib/rules/no-deprecated-data-object-declaration.js
@@ -0,0 +1,86 @@
+/**
+ * @fileoverview disallow using deprecated object declaration on data
+ * @author yoyo930021
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+
+function isOpenParen (token) {
+  return token.type === 'Punctuator' && token.value === '('
+}
+
+function isCloseParen (token) {
+  return token.type === 'Punctuator' && token.value === ')'
+}
+
+function getFirstAndLastTokens (node, sourceCode) {
+  let first = sourceCode.getFirstToken(node)
+  let last = sourceCode.getLastToken(node)
+
+  // If the value enclosed by parentheses, update the 'first' and 'last' by the parentheses.
+  while (true) {
+    const prev = sourceCode.getTokenBefore(first)
+    const next = sourceCode.getTokenAfter(last)
+    if (isOpenParen(prev) && isCloseParen(next)) {
+      first = prev
+      last = next
+    } else {
+      return { first, last }
+    }
+  }
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description: 'disallow using deprecated object declaration on data',
+      categories: ['vue3-essential'],
+      url: 'https://eslint.vuejs.org/rules/no-deprecated-data-object-declaration.html'
+    },
+    fixable: 'code',
+    schema: [],
+    messages: {
+      objectDeclarationIsDeprecated: "Object declaration on \'data\' property is deprecated. Using function declaration instead."
+    }
+  },
+
+  create (context) {
+    const sourceCode = context.getSourceCode()
+
+    return utils.executeOnVue(context, (obj) => {
+      obj.properties
+        .filter(p =>
+          p.type === 'Property' &&
+          p.key.type === 'Identifier' &&
+          p.key.name === 'data' &&
+          p.value.type !== 'FunctionExpression' &&
+          p.value.type !== 'ArrowFunctionExpression' &&
+          p.value.type !== 'Identifier'
+        )
+        .forEach(p => {
+          context.report({
+            node: p,
+            messageId: 'objectDeclarationIsDeprecated',
+            fix (fixer) {
+              const tokens = getFirstAndLastTokens(p.value, sourceCode)
+
+              return [
+                fixer.insertTextBefore(tokens.first, 'function() {\nreturn '),
+                fixer.insertTextAfter(tokens.last, ';\n}')
+              ]
+            }
+          })
+        })
+    })
+  }
+}
diff --git a/lib/rules/no-deprecated-v-on-number-modifiers.js b/lib/rules/no-deprecated-v-on-number-modifiers.js
new file mode 100644
index 00000000..651991cd
--- /dev/null
+++ b/lib/rules/no-deprecated-v-on-number-modifiers.js
@@ -0,0 +1,58 @@
+/**
+ * @fileoverview disallow using deprecated number (keycode) modifiers
+ * @author yoyo930021
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+const keyCodeToKey = require('../utils/keycode-to-key.json')
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description: 'disallow using deprecated number (keycode) modifiers',
+      categories: ['vue3-essential'],
+      url: 'https://eslint.vuejs.org/rules/no-deprecated-v-on-number-modifiers.html'
+    },
+    fixable: 'code',
+    schema: [],
+    messages: {
+      numberModifierIsDeprecated: "'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."
+    }
+  },
+
+  create (context) {
+    return utils.defineTemplateBodyVisitor(context, {
+      "VAttribute[directive=true][key.name.name='on']" (node) {
+        const modifier = node.key.modifiers.find(mod => Number.isInteger(parseInt(mod.name, 10)))
+        if (!modifier) return
+
+        const keyCodes = parseInt(modifier.name, 10)
+        if (
+          keyCodes > 9 || keyCodes < 0
+        ) {
+          context.report({
+            node,
+            loc: node.loc,
+            messageId: 'numberModifierIsDeprecated',
+            fix: (fixer) => {
+              const key = keyCodeToKey[keyCodes]
+              if (!key) return
+
+              return fixer.replaceTextRange(modifier.range, `${key}`)
+            }
+          })
+        }
+      }
+    })
+  }
+}
diff --git a/lib/rules/no-dupe-keys.js b/lib/rules/no-dupe-keys.js
index 1bee4d94..28018e44 100644
--- a/lib/rules/no-dupe-keys.js
+++ b/lib/rules/no-dupe-keys.js
@@ -10,7 +10,7 @@ const utils = require('../utils')
 // Rule Definition
 // ------------------------------------------------------------------------------
 
-const GROUP_NAMES = ['props', 'computed', 'data', 'methods']
+const GROUP_NAMES = ['props', 'computed', 'data', 'methods', 'setup']
 
 module.exports = {
   meta: {
diff --git a/lib/rules/no-reserved-keys.js b/lib/rules/no-reserved-keys.js
index 80ecada3..249f1054 100644
--- a/lib/rules/no-reserved-keys.js
+++ b/lib/rules/no-reserved-keys.js
@@ -11,7 +11,7 @@ const utils = require('../utils')
 // ------------------------------------------------------------------------------
 
 const RESERVED_KEYS = require('../utils/vue-reserved.json')
-const GROUP_NAMES = ['props', 'computed', 'data', 'methods']
+const GROUP_NAMES = ['props', 'computed', 'data', 'methods', 'setup']
 
 module.exports = {
   meta: {
diff --git a/lib/rules/no-template-target-blank.js b/lib/rules/no-template-target-blank.js
new file mode 100644
index 00000000..bf43e19e
--- /dev/null
+++ b/lib/rules/no-template-target-blank.js
@@ -0,0 +1,111 @@
+/**
+ * @fileoverview disallow target="_blank" attribute without rel="noopener noreferrer"
+ * @author Sosukesuzuki
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+function isTargetBlank (node) {
+  return node.key &&
+    node.key.name === 'target' &&
+    node.value &&
+    node.value.value === '_blank'
+}
+
+function hasSecureRel (node, allowReferrer) {
+  return node.attributes.some(attr => {
+    if (attr.key && attr.key.name === 'rel') {
+      const tags = attr.value && attr.value.value.toLowerCase().split(' ')
+      return tags &&
+        tags.includes('noopener') &&
+        (allowReferrer || tags.includes('noreferrer'))
+    } else {
+      return false
+    }
+  })
+}
+
+function hasExternalLink (node) {
+  return node.attributes.some(attr =>
+    attr.key &&
+    attr.key.name === 'href' &&
+    attr.value && /^(?:\w+:|\/\/)/.test(attr.value.value)
+  )
+}
+
+function hasDynamicLink (node) {
+  return node.attributes.some(attr =>
+    attr.key &&
+    attr.key.type === 'VDirectiveKey' &&
+    attr.key.name &&
+    attr.key.name.name === 'bind' &&
+    attr.key.argument &&
+    attr.key.argument.name === 'href'
+  )
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description:
+        'disallow target="_blank" attribute without rel="noopener noreferrer"',
+      categories: undefined,
+      url: 'https://eslint.vuejs.org/rules/no-template-target-blank.html'
+    },
+    schema: [{
+      type: 'object',
+      properties: {
+        allowReferrer: {
+          type: 'boolean'
+        },
+        enforceDynamicLinks: {
+          enum: ['always', 'never']
+        }
+      },
+      additionalProperties: false
+    }]
+  },
+
+  /**
+   * Creates AST event handlers for no-template-target-blank
+   *
+   * @param {RuleContext} context - The rule context.
+   * @returns {Object} AST event handlers.
+   */
+  create (context) {
+    const configuration = context.options[0] || {}
+    const allowReferrer = configuration.allowReferrer || false
+    const enforceDynamicLinks = configuration.enforceDynamicLinks || 'always'
+
+    return utils.defineTemplateBodyVisitor(context, {
+      'VAttribute' (node) {
+        if (!isTargetBlank(node) || hasSecureRel(node.parent, allowReferrer)) {
+          return
+        }
+
+        const hasDangerHref = hasExternalLink(node.parent) ||
+          (enforceDynamicLinks === 'always' && hasDynamicLink(node.parent))
+
+        if (hasDangerHref) {
+          context.report({
+            node,
+            message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk.'
+          })
+        }
+      }
+    })
+  }
+}
diff --git a/lib/utils/keycode-to-key.json b/lib/utils/keycode-to-key.json
new file mode 100644
index 00000000..43f48030
--- /dev/null
+++ b/lib/utils/keycode-to-key.json
@@ -0,0 +1,100 @@
+{
+  "8": "backspace",
+  "9": "tab",
+  "13": "enter",
+  "16": "shift",
+  "17": "ctrl",
+  "18": "alt",
+  "19": "pause-break",
+  "20": "caps-lock",
+  "27": "escape",
+  "33": "page-up",
+  "34": "page-down",
+  "35": "end",
+  "36": "home",
+  "37": "left-arrow",
+  "38": "up-arrow",
+  "39": "right-arrow",
+  "40": "down-arrow",
+  "45": "insert",
+  "46": "delete",
+  "48": "0",
+  "49": "1",
+  "50": "2",
+  "51": "3",
+  "52": "4",
+  "53": "5",
+  "54": "6",
+  "55": "7",
+  "56": "8",
+  "57": "9",
+  "65": "a",
+  "66": "b",
+  "67": "c",
+  "68": "d",
+  "69": "e",
+  "70": "f",
+  "71": "g",
+  "72": "h",
+  "73": "i",
+  "74": "j",
+  "75": "k",
+  "76": "l",
+  "77": "m",
+  "78": "n",
+  "79": "o",
+  "80": "p",
+  "81": "q",
+  "82": "r",
+  "83": "s",
+  "84": "t",
+  "85": "u",
+  "86": "v",
+  "87": "w",
+  "88": "x",
+  "89": "y",
+  "90": "z",
+  "91": "left-window-key",
+  "92": "right-window-key",
+  "93": "select-key",
+  "96": "numpad-0",
+  "97": "numpad-1",
+  "98": "numpad-2",
+  "99": "numpad-3",
+  "100": "numpad-4",
+  "101": "numpad-5",
+  "102": "numpad-6",
+  "103": "numpad-7",
+  "104": "numpad-8",
+  "105": "numpad-9",
+  "106": "multiply",
+  "107": "add",
+  "109": "subtract",
+  "110": "decimal-point",
+  "111": "divide",
+  "112": "f1",
+  "113": "f2",
+  "114": "f3",
+  "115": "f4",
+  "116": "f5",
+  "117": "f6",
+  "118": "f7",
+  "119": "f8",
+  "120": "f9",
+  "121": "f10",
+  "122": "f11",
+  "123": "f12",
+  "144": "num-lock",
+  "145": "scroll-lock",
+  "186": "semi-colon",
+  "187": "equal-sign",
+  "188": "comma",
+  "189": "dash",
+  "190": "period",
+  "191": "forward-slash",
+  "192": "grave-accent",
+  "219": "open-bracket",
+  "220": "back-slash",
+  "221": "close-braket",
+  "222": "single-quote"
+}
diff --git a/package.json b/package.json
index c6bde732..2d568134 100644
--- a/package.json
+++ b/package.json
@@ -58,6 +58,7 @@
   "devDependencies": {
     "@types/node": "^4.2.16",
     "@typescript-eslint/parser": "^2.6.1",
+    "@vuepress/plugin-pwa": "^1.4.0",
     "acorn": "^7.1.0",
     "babel-eslint": "^10.0.2",
     "chai": "^4.1.0",
@@ -66,12 +67,12 @@
     "eslint-plugin-import": "^2.18.2",
     "eslint-plugin-vue": "file:.",
     "eslint-plugin-vue-libs": "^4.0.0",
-    "eslint4b": "^6.6.0",
+    "eslint4b": "^6.8.0",
     "lodash": "^4.17.4",
     "mocha": "^5.2.0",
     "nyc": "^12.0.2",
     "typescript": "^3.5.2",
-    "vue-eslint-editor": "^0.1.4",
-    "vuepress": "^0.14.5"
+    "vue-eslint-editor": "^1.1.0",
+    "vuepress": "^1.4.0"
   }
 }
diff --git a/tools/update-docs-rules-index.js b/tools/update-docs-rules-index.js
index e2283b3b..23b1172b 100644
--- a/tools/update-docs-rules-index.js
+++ b/tools/update-docs-rules-index.js
@@ -94,10 +94,10 @@ fs.writeFileSync(
 sidebarDepth: 0
 ---
 
-<!-- This file is automatically generated in tools/update-docs-rules-index.js, do not change! -->
-
 # Available rules
 
+<!-- This file is automatically generated in tools/update-docs-rules-index.js, do not change! -->
+
 ::: tip Legend
   :wrench: Indicates that the rule is fixable, and using \`--fix\` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the reported problems.
 :::


Test patch:
diff --git a/tests/lib/rules/no-deprecated-data-object-declaration.js b/tests/lib/rules/no-deprecated-data-object-declaration.js
new file mode 100644
index 00000000..1ced275c
--- /dev/null
+++ b/tests/lib/rules/no-deprecated-data-object-declaration.js
@@ -0,0 +1,289 @@
+/**
+ * @fileoverview disallow using deprecated object declaration on data
+ * @author yoyo930021
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-deprecated-data-object-declaration')
+
+const RuleTester = require('eslint').RuleTester
+
+const parserOptions = {
+  ecmaVersion: 2018,
+  sourceType: 'module'
+}
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester()
+ruleTester.run('no-deprecated-data-object-declaration', rule, {
+
+  valid: [
+    {
+      filename: 'test.js',
+      code: `
+        createApp({
+          data: function () {
+            return {
+              foo: 'bar'
+            }
+          }
+        }).mount('#app')
+      `,
+      parserOptions
+    },
+    {
+      filename: 'test.js',
+      code: `
+        createApp({
+          ...data,
+          data () {
+            return {
+              foo: 'bar'
+            }
+          }
+        }).mount('#app')
+      `,
+      parserOptions
+    },
+    {
+      filename: 'test.js',
+      code: `
+        const app = createApp(App)
+        app.component('some-comp', {
+          data: function () {
+            return {
+              foo: 'bar'
+            }
+          }
+        })
+      `,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          data: function () {
+            return {
+              foo: 'bar'
+            }
+          }
+        }
+      `,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          ...foo
+        }
+      `,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          data
+        }
+      `,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          data: () => {
+
+          }
+        }
+      `,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          data () {
+
+          },
+          methods: {
+
+          }
+        }
+      `,
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          data () {
+
+          },
+          computed: {
+
+          }
+        }
+      `,
+      parserOptions
+    }
+  ],
+
+  invalid: [
+    {
+      filename: 'test.js',
+      code: `
+        const app = createApp(App)
+        app.component('some-comp', {
+          data: {
+            foo: 'bar'
+          }
+        })
+      `,
+      output: `
+        const app = createApp(App)
+        app.component('some-comp', {
+          data: function() {
+return {
+            foo: 'bar'
+          };
+}
+        })
+      `,
+      parserOptions,
+      errors: [{
+        message: "Object declaration on \'data\' property is deprecated. Using function declaration instead.",
+        line: 4
+      }]
+    },
+    {
+      filename: 'test.js',
+      code: `
+        app.component('some-comp', {
+          data: {
+            foo: 'bar'
+          }
+        })
+      `,
+      output: `
+        app.component('some-comp', {
+          data: function() {
+return {
+            foo: 'bar'
+          };
+}
+        })
+      `,
+      parserOptions,
+      errors: [{
+        message: "Object declaration on \'data\' property is deprecated. Using function declaration instead.",
+        line: 3
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          data: {
+            foo: 'bar'
+          }
+        }
+      `,
+      output: `
+        export default {
+          data: function() {
+return {
+            foo: 'bar'
+          };
+}
+        }
+      `,
+      parserOptions,
+      errors: [{
+        message: "Object declaration on \'data\' property is deprecated. Using function declaration instead.",
+        line: 3
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          data: /*a*/ (/*b*/{
+            foo: 'bar'
+          })
+        }
+      `,
+      output: `
+        export default {
+          data: /*a*/ function() {
+return (/*b*/{
+            foo: 'bar'
+          });
+}
+        }
+      `,
+      parserOptions,
+      errors: [{
+        message: "Object declaration on \'data\' property is deprecated. Using function declaration instead.",
+        line: 3
+      }]
+    },
+    {
+      filename: 'test.js',
+      code: `
+        createApp({
+          data: {
+            foo: 'bar'
+          }
+        })
+      `,
+      output: `
+        createApp({
+          data: function() {
+return {
+            foo: 'bar'
+          };
+}
+        })
+      `,
+      parserOptions,
+      errors: [{
+        message: "Object declaration on \'data\' property is deprecated. Using function declaration instead.",
+        line: 3
+      }]
+    },
+    {
+      filename: 'test.js',
+      code: `
+        createApp({
+          data: {
+            foo: 'bar'
+          }
+        }).mount('#app')
+      `,
+      output: `
+        createApp({
+          data: function() {
+return {
+            foo: 'bar'
+          };
+}
+        }).mount('#app')
+      `,
+      parserOptions,
+      errors: [{
+        message: "Object declaration on \'data\' property is deprecated. Using function declaration instead.",
+        line: 3
+      }]
+    }
+  ]
+})
diff --git a/tests/lib/rules/no-deprecated-v-on-number-modifiers.js b/tests/lib/rules/no-deprecated-v-on-number-modifiers.js
new file mode 100644
index 00000000..8c6d7269
--- /dev/null
+++ b/tests/lib/rules/no-deprecated-v-on-number-modifiers.js
@@ -0,0 +1,174 @@
+/**
+ * @fileoverview disallow using deprecated number (keyCodes) modifiers
+ * @author yoyo930021
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-deprecated-v-on-number-modifiers')
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2015 }
+})
+
+ruleTester.run('no-deprecated-v-bind-sync', rule, {
+
+  valid: [
+    {
+      filename: 'test.vue',
+      code: "<template><input v-on:keyup.page-down='onArrowUp'></template>"
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @keyup.page-down='onArrowUp'></template>"
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input v-on:keyup.9='onArrowUp'></template>"
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @keyup.9='onArrowUp'></template>"
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input v-on:keyup.0='onArrowUp'></template>"
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @keyup.0='onArrowUp'></template>"
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input v-on:keyup.4='onArrowUp'></template>"
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @keyup.4='onArrowUp'></template>"
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input v-on:keyup.page-down.native='onArrowUp'></template>"
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @keyup.page-down.native='onArrowUp'></template>"
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input v-on:keyup.0.native='onArrowUp'></template>"
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @keyup.0.native='onArrowUp'></template>"
+    }
+  ],
+
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: "<template><input v-on:keyup.34='onArrowUp'></template>",
+      output: "<template><input v-on:keyup.page-down='onArrowUp'></template>",
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input v-on:keyup.34.native='onArrowUp'></template>",
+      output: "<template><input v-on:keyup.page-down.native='onArrowUp'></template>",
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input v-on:keyup.unknown.34='onArrowUp'></template>",
+      output: "<template><input v-on:keyup.unknown.page-down='onArrowUp'></template>",
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input v-on:[dynamicArg].34='onArrowUp'></template>",
+      output: "<template><input v-on:[dynamicArg].page-down='onArrowUp'></template>",
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input v-on:[dynamicArg].unknown.34='onArrowUp'></template>",
+      output: "<template><input v-on:[dynamicArg].unknown.page-down='onArrowUp'></template>",
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input v-on:[dynamicArg].34.unknown='onArrowUp'></template>",
+      output: "<template><input v-on:[dynamicArg].page-down.unknown='onArrowUp'></template>",
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @keyup.34='onArrowUp'></template>",
+      output: "<template><input @keyup.page-down='onArrowUp'></template>",
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @keyup.34.native='onArrowUp'></template>",
+      output: "<template><input @keyup.page-down.native='onArrowUp'></template>",
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @keyup.unknown.34='onArrowUp'></template>",
+      output: "<template><input @keyup.unknown.page-down='onArrowUp'></template>",
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @[dynamicArg].34='onArrowUp'></template>",
+      output: "<template><input @[dynamicArg].page-down='onArrowUp'></template>",
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @[dynamicArg].unknown.34='onArrowUp'></template>",
+      output: "<template><input @[dynamicArg].unknown.page-down='onArrowUp'></template>",
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @[dynamicArg].34.unknown='onArrowUp'></template>",
+      output: "<template><input @[dynamicArg].page-down.unknown='onArrowUp'></template>",
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @keyup.10='onArrowUp'></template>",
+      output: null,
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @keyup.10.native='onArrowUp'></template>",
+      output: null,
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @keyup.unknown.10='onArrowUp'></template>",
+      output: null,
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @[dynamicArg].unknown.10='onArrowUp'></template>",
+      output: null,
+      errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+    }
+  ]
+})
diff --git a/tests/lib/rules/no-dupe-keys.js b/tests/lib/rules/no-dupe-keys.js
index f1a35568..0844173f 100644
--- a/tests/lib/rules/no-dupe-keys.js
+++ b/tests/lib/rules/no-dupe-keys.js
@@ -44,6 +44,37 @@ ruleTester.run('no-dupe-keys', rule, {
       `,
       parserOptions: { ecmaVersion: 6, sourceType: 'module' }
     },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          props: ['foo'],
+          data () {
+            return {
+              dat: null
+            }
+          },
+          data () {
+            return
+          },
+          methods: {
+            _foo () {},
+            test () {
+            }
+          },
+          setup () {
+            const _foo = () => {}
+            const dat = ref(null)
+            const bar = computed(() => 'bar')
+
+            return {
+              bar
+            }
+          }
+        }
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' }
+    },
 
     {
       filename: 'test.vue',
@@ -134,6 +165,51 @@ ruleTester.run('no-dupe-keys', rule, {
       parserOptions: { ecmaVersion: 2018, sourceType: 'module' }
     },
 
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          ...foo(),
+          props: {
+            ...foo(),
+            foo: String
+          },
+          computed: {
+            ...mapGetters({
+              test: 'getTest'
+            }),
+            bar: {
+              get () {
+              }
+            }
+          },
+          data: {
+            ...foo(),
+            dat: null
+          },
+          methods: {
+            ...foo(),
+            test () {
+            }
+          },
+          data () {
+            return {
+              ...dat
+            }
+          },
+          setup () {
+            const com = computed(() => 1)
+
+            return {
+              ...foo(),
+              com
+            }
+          }
+        }
+      `,
+      parserOptions: { ecmaVersion: 2018, sourceType: 'module' }
+    },
+
     {
       filename: 'test.vue',
       code: `
@@ -224,6 +300,39 @@ ruleTester.run('no-dupe-keys', rule, {
         }
       `,
       parserOptions: { ecmaVersion: 6, sourceType: 'module' }
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        // @vue/component
+        export const compA = {
+          props: {
+            propA: String
+          },
+          setup (props) {
+            const com = computed(() => props.propA)
+
+            return {
+              com
+            }
+          }
+        }
+
+        // @vue/component
+        export const compB = {
+          props: {
+            propA: String
+          },
+          setup (props) {
+            const com = computed(() => props.propA)
+
+            return {
+              com
+            }
+          }
+        }
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' }
     }
   ],
 
@@ -245,6 +354,13 @@ ruleTester.run('no-dupe-keys', rule, {
           methods: {
             foo () {
             }
+          },
+          setup () {
+            const foo = ref(1)
+
+            return {
+              foo
+            }
           }
         }
       `,
@@ -258,6 +374,9 @@ ruleTester.run('no-dupe-keys', rule, {
       }, {
         message: 'Duplicated key \'foo\'.',
         line: 14
+      }, {
+        message: 'Duplicated key \'foo\'.',
+        line: 21
       }]
     },
     {
@@ -277,6 +396,13 @@ ruleTester.run('no-dupe-keys', rule, {
           methods: {
             foo () {
             }
+          },
+          setup: () => {
+            const foo = computed(() => 0)
+
+            return {
+              foo
+            }
           }
         }
       `,
@@ -290,6 +416,9 @@ ruleTester.run('no-dupe-keys', rule, {
       }, {
         message: 'Duplicated key \'foo\'.',
         line: 14
+      }, {
+        message: 'Duplicated key \'foo\'.',
+        line: 21
       }]
     },
     {
@@ -341,6 +470,13 @@ ruleTester.run('no-dupe-keys', rule, {
           methods: {
             foo () {
             }
+          },
+          setup (props) {
+            const foo = computed(() => props.foo)
+
+            return {
+              foo
+            }
           }
         }
       `,
@@ -354,6 +490,9 @@ ruleTester.run('no-dupe-keys', rule, {
       }, {
         message: 'Duplicated key \'foo\'.',
         line: 16
+      }, {
+        message: 'Duplicated key \'foo\'.',
+        line: 23
       }]
     },
     {
@@ -374,6 +513,132 @@ ruleTester.run('no-dupe-keys', rule, {
         message: 'Duplicated key \'bar\'.',
         line: 7
       }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          methods: {
+            foo () {
+              return 0
+            }
+          },
+          setup () {
+            const foo = () => 0
+
+            return {
+              foo
+            }
+          }
+        }
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [{
+        message: 'Duplicated key \'foo\'.',
+        line: 12
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          methods: {
+            foo () {
+              return 0
+            }
+          },
+          setup () {
+            return {
+              foo: () => 0
+            }
+          }
+        }
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [{
+        message: 'Duplicated key \'foo\'.',
+        line: 10
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          methods: {
+            foo () {
+              return 0
+            }
+          },
+          setup: () => ({
+            foo: () => 0
+          })
+        }
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [{
+        message: 'Duplicated key \'foo\'.',
+        line: 9
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          computed: {
+            foo () {
+              return 0
+            }
+          },
+          setup: () => ({
+            foo: computed(() => 0)
+          })
+        }
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [{
+        message: 'Duplicated key \'foo\'.',
+        line: 9
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          data() {
+            return {
+              foo: 0
+            }
+          },
+          setup: () => ({
+            foo: ref(0)
+          })
+        }
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [{
+        message: 'Duplicated key \'foo\'.',
+        line: 9
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          data() {
+            return {
+              foo: 0
+            }
+          },
+          setup: () => ({
+            foo: 0
+          })
+        }
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [{
+        message: 'Duplicated key \'foo\'.',
+        line: 9
+      }]
     }
   ]
 })
diff --git a/tests/lib/rules/no-reserved-keys.js b/tests/lib/rules/no-reserved-keys.js
index 05836bc4..f3bc64ac 100644
--- a/tests/lib/rules/no-reserved-keys.js
+++ b/tests/lib/rules/no-reserved-keys.js
@@ -89,6 +89,32 @@ ruleTester.run('no-reserved-keys', rule, {
         }
       `,
       parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          props: ['foo'],
+          computed: {
+            bar () {
+            }
+          },
+          data: () => ({
+            dat: null
+          }),
+          methods: {
+            _foo () {},
+            test () {
+            }
+          },
+          setup () {
+            return {
+              _bar: () => {}
+            }
+          }
+        }
+      `,
+      parserOptions
     }
   ],
 
@@ -108,6 +134,23 @@ ruleTester.run('no-reserved-keys', rule, {
         line: 4
       }]
     },
+    {
+      filename: 'test.js',
+      code: `
+        new Vue({
+          setup () {
+            return {
+              $el: ''
+            }
+          }
+        })
+      `,
+      parserOptions: { ecmaVersion: 6 },
+      errors: [{
+        message: "Key '$el' is reserved.",
+        line: 5
+      }]
+    },
     {
       filename: 'test.js',
       code: `
diff --git a/tests/lib/rules/no-template-target-blank.js b/tests/lib/rules/no-template-target-blank.js
new file mode 100644
index 00000000..41b5c1c4
--- /dev/null
+++ b/tests/lib/rules/no-template-target-blank.js
@@ -0,0 +1,68 @@
+/**
+ * @fileoverview disallow target="_blank" attribute without rel="noopener noreferrer"
+ * @author Sosukesuzuki
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-template-target-blank')
+
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2015 }
+})
+
+ruleTester.run('no-template-target-blank', rule, {
+  valid: [
+    { code: '<template><a>link</a></template>' },
+    { code: '<template><a attr>link</a></template>' },
+    { code: '<template><a target>link</a></template>' },
+    { code: '<template><a href="https://eslint.vuejs.org">link</a></template>' },
+    { code: '<template><a :href="link">link</a></template>' },
+    { code: '<template><a :href="link" target="_blank" rel="noopener noreferrer">link</a></template>' },
+    { code: '<template><a href="https://eslint.vuejs.org" target="_blank" rel="noopener noreferrer">link</a></template>' },
+    {
+      code: '<template><a href="https://eslint.vuejs.org" target="_blank" rel="noopener">link</a></template>',
+      options: [{ allowReferrer: true }]
+    },
+    { code: '<template><a href="/foo" target="_blank">link</a></template>' },
+    { code: '<template><a href="/foo" target="_blank" rel="noopener noreferrer">link</a></template>' },
+    { code: '<template><a href="foo/bar" target="_blank">link</a></template>' },
+    { code: '<template><a href="foo/bar" target="_blank" rel="noopener noreferrer">link</a></template>' },
+    {
+      code: '<template><a :href="link" target="_blank">link</a></template>',
+      options: [{ enforceDynamicLinks: 'never' }]
+    }
+  ],
+  invalid: [
+    {
+      code: '<template><a href="https://eslint.vuejs.org" target="_blank">link</a></template>',
+      errors: ['Using target="_blank" without rel="noopener noreferrer" is a security risk.']
+    },
+    {
+      code: '<template><a href="https://eslint.vuejs.org" target="_blank" rel="noopenernoreferrer">link</a></template>',
+      errors: ['Using target="_blank" without rel="noopener noreferrer" is a security risk.']
+    },
+    {
+      code: '<template><a :href="link" target="_blank" rel=3>link</a></template>',
+      errors: ['Using target="_blank" without rel="noopener noreferrer" is a security risk.']
+    },
+    {
+      code: '<template><a :href="link" target="_blank">link</a></template>',
+      errors: ['Using target="_blank" without rel="noopener noreferrer" is a security risk.']
+    },
+    {
+      code: '<template><a href="https://eslint.vuejs.org" target="_blank" rel="noopener">link</a></template>',
+      errors: ['Using target="_blank" without rel="noopener noreferrer" is a security risk.']
+    }
+  ]
+})