Repository: vuejs/eslint-plugin-vue
Pull request #1333 (2020-10-13 14:04:46)

Mention that .js files are linted as well
This will make the tool more interesting for those of us not using `.vue` files yet.


Patch:
diff --git a/README.md b/README.md
index c34728c4..c8951f0a 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ Since single file components in Vue are not plain JavaScript, the default parser
 
 To know more about certain nodes in produced ASTs, go here:
 - [ESTree docs](https://github.com/estree/estree)
-- [vue-eslint-parser AST docs](https://github.com/mysticatea/vue-eslint-parser/blob/master/docs/ast.md)
+- [vue-eslint-parser AST docs](https://github.com/vuejs/vue-eslint-parser/blob/master/docs/ast.md)
 
 The `vue-eslint-parser` provides a few useful parser services that help traverse the produced AST and access tokens of the template:
 - `context.parserServices.defineTemplateBodyVisitor(visitor, scriptVisitor)`
diff --git a/docs/.vuepress/components/eslint-code-block.vue b/docs/.vuepress/components/eslint-code-block.vue
index 46504375..00b69482 100644
--- a/docs/.vuepress/components/eslint-code-block.vue
+++ b/docs/.vuepress/components/eslint-code-block.vue
@@ -124,11 +124,11 @@ export default {
     // Load linter.
     const [
       { default: Linter },
-      { default: noUndefRule },
+      { default: coreRules },
       { parseForESLint }
     ] = await Promise.all([
       import('eslint4b/dist/linter'),
-      import('eslint/lib/rules/no-undef'),
+      import('eslint4b/dist/core-rules'),
       import('espree').then(() => import('vue-eslint-parser'))
     ])
 
@@ -137,7 +137,7 @@ export default {
     for (const ruleId of Object.keys(rules)) {
       linter.defineRule(`vue/${ruleId}`, rules[ruleId])
     }
-    linter.defineRule('no-undef', noUndefRule)
+    linter.defineRule('no-undef', coreRules['no-undef'])
 
     linter.defineParser('vue-eslint-parser', { parseForESLint })
   }
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index d8b8b292..fb7719ff 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -5,6 +5,7 @@
 'use strict'
 
 const rules = require('../../tools/lib/rules')
+const path = require('path')
 
 const uncategorizedRules = rules.filter(
   (rule) =>
@@ -122,7 +123,8 @@ module.exports = {
     return {
       resolve: {
         alias: {
-          module: require.resolve('./shim/module')
+          module: require.resolve('./shim/module'),
+          eslint: path.resolve(__dirname, './shim/eslint')
         }
       }
     }
diff --git a/docs/.vuepress/shim/eslint/index.js b/docs/.vuepress/shim/eslint/index.js
new file mode 100644
index 00000000..e32cb598
--- /dev/null
+++ b/docs/.vuepress/shim/eslint/index.js
@@ -0,0 +1,2 @@
+const Linter = require('eslint4b')
+module.exports = { Linter }
diff --git a/docs/.vuepress/shim/eslint/lib/rules/index.js b/docs/.vuepress/shim/eslint/lib/rules/index.js
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/README.md b/docs/README.md
index fe739cc2..e1925804 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -6,7 +6,7 @@ sidebarDepth: 0
 
 Official ESLint plugin for Vue.js.
 
-This plugin allows us to check the `<template>` and `<script>` of `.vue` files with ESLint.
+This plugin allows us to check the `<template>` and `<script>` of `.vue` files with ESLint, as well as Vue code in `.js` files.
 
 - Finds syntax errors.
 - Finds the wrong use of [Vue.js Directives](https://v3.vuejs.org/api/directives.html).
diff --git a/docs/developer-guide/README.md b/docs/developer-guide/README.md
index d216984b..f08ecc3a 100644
--- a/docs/developer-guide/README.md
+++ b/docs/developer-guide/README.md
@@ -39,7 +39,7 @@ Since single file components in Vue are not plain JavaScript, we can't use the d
 
 To know more about certain nodes in produced ASTs, go here:
 - [ESTree docs](https://github.com/estree/estree)
-- [vue-eslint-parser AST docs](https://github.com/mysticatea/vue-eslint-parser/blob/master/docs/ast.md)
+- [vue-eslint-parser AST docs](https://github.com/vuejs/vue-eslint-parser/blob/master/docs/ast.md)
 
 The `vue-eslint-parser` provides few useful parser services, to help traverse the produced AST and access tokens of the template:
 - `context.parserServices.defineTemplateBodyVisitor(visitor, scriptVisitor)`
diff --git a/docs/rules/README.md b/docs/rules/README.md
index 1ffb102f..f2d9b847 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -281,13 +281,14 @@ For example:
 ```json
 {
   "rules": {
-    "vue/component-name-in-template-casing": "error"
+    "vue/block-tag-newline": "error"
   }
 }
 ```
 
 | Rule ID | Description |    |
 |:--------|:------------|:---|
+| [vue/block-tag-newline](./block-tag-newline.md) | enforce line breaks after opening and before closing block-level tags | :wrench: |
 | [vue/component-name-in-template-casing](./component-name-in-template-casing.md) | enforce specific casing for the component naming style in template | :wrench: |
 | [vue/html-comment-content-newline](./html-comment-content-newline.md) | enforce unified line brake in HTML comments | :wrench: |
 | [vue/html-comment-content-spacing](./html-comment-content-spacing.md) | enforce unified spacing in HTML comments | :wrench: |
@@ -325,6 +326,7 @@ The following rules extend the rules provided by ESLint itself and apply them to
 
 | Rule ID | Description |    |
 |:--------|:------------|:---|
+| [vue/array-bracket-newline](./array-bracket-newline.md) | enforce linebreaks after opening and before closing array brackets | :wrench: |
 | [vue/array-bracket-spacing](./array-bracket-spacing.md) | enforce consistent spacing inside array brackets | :wrench: |
 | [vue/arrow-spacing](./arrow-spacing.md) | enforce consistent spacing before and after the arrow in arrow functions | :wrench: |
 | [vue/block-spacing](./block-spacing.md) | disallow or enforce spaces inside of blocks after opening block and before closing block | :wrench: |
diff --git a/docs/rules/array-bracket-newline.md b/docs/rules/array-bracket-newline.md
new file mode 100644
index 00000000..2bc2a088
--- /dev/null
+++ b/docs/rules/array-bracket-newline.md
@@ -0,0 +1,25 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/array-bracket-newline
+description: enforce linebreaks after opening and before closing array brackets
+---
+# vue/array-bracket-newline
+> enforce linebreaks after opening and before closing array brackets
+
+- :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.
+
+This rule is the same rule as core [array-bracket-newline] rule but it applies to the expressions in `<template>`.
+
+## :books: Further Reading
+
+- [array-bracket-newline]
+
+[array-bracket-newline]: https://eslint.org/docs/rules/array-bracket-newline
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/array-bracket-newline.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/array-bracket-newline.js)
+
+<sup>Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/array-bracket-newline)</sup>
diff --git a/docs/rules/block-tag-newline.md b/docs/rules/block-tag-newline.md
new file mode 100644
index 00000000..a3ab5f96
--- /dev/null
+++ b/docs/rules/block-tag-newline.md
@@ -0,0 +1,161 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/block-tag-newline
+description: enforce line breaks after opening and before closing block-level tags
+---
+# vue/block-tag-newline
+> enforce line breaks after opening and before closing block-level tags
+
+- :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 enforces a line break (or no line break) after opening and before closing block tags.
+
+<eslint-code-block fix :rules="{'vue/block-tag-newline': ['error']}">
+
+```vue
+<!-- ✓ GOOD -->
+<template><input></template>
+
+<script>
+export default {}
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block fix :rules="{'vue/block-tag-newline': ['error']}">
+
+```vue
+<!-- ✗ BAD -->
+<template>
+<input></template>
+
+<script>
+export default {}</script>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+```json
+{
+  "vue/block-tag-newline": ["error", {
+    "singleline": "always" | "never" | "consistent" | "ignore",
+    "multiline": "always" | "never" | "consistent" | "ignore",
+    "maxEmptyLines": 0,
+    "blocks": {
+      "template": {
+        "singleline": "always" | "never" | "consistent" | "ignore",
+        "multiline": "always" | "never" | "consistent" | "ignore",
+        "maxEmptyLines": 0,
+      },
+      "script": {
+        "singleline": "always" | "never" | "consistent" | "ignore",
+        "multiline": "always" | "never" | "consistent" | "ignore",
+        "maxEmptyLines": 0,
+      },
+      "my-block": {
+        "singleline": "always" | "never" | "consistent" | "ignore",
+        "multiline": "always" | "never" | "consistent" | "ignore",
+        "maxEmptyLines": 0,
+      }
+    }
+  }]
+}
+```
+
+- `singleline` ... the configuration for single-line blocks.
+  - `"consistent"` ... (default) requires consistent usage of line breaks for each pair of tags. It reports an error if one tag in the pair has a linebreak inside it and the other tag does not.
+  - `"always"` ... require one line break after opening and before closing block tags.
+  - `"never"` ... disallow line breaks after opening and before closing block tags.
+- `multiline` ... the configuration for multi-line blocks.
+  - `"consistent"` ... requires consistent usage of line breaks for each pair of tags. It reports an error if one tag in the pair has a linebreak inside it and the other tag does not.
+  - `"always"` ... (default) require one line break after opening and before closing block tags.
+  - `"never"` ... disallow line breaks after opening and before closing block tags.
+- `maxEmptyLines` ... specifies the maximum number of empty lines allowed. default 0.
+- `blocks` ... specifies for each block name.
+
+### `{ "singleline": "never", "multiline": "always" }`
+
+<eslint-code-block fix :rules="{'vue/block-tag-newline': ['error', { 'singleline': 'never', 'multiline': 'always' }]}">
+
+```vue
+<!-- ✓ GOOD -->
+<template><input></template>
+
+<script>
+export default {
+}
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block fix :rules="{'vue/block-tag-newline': ['error', { 'singleline': 'never', 'multiline': 'always' }]}">
+
+```vue
+<!-- ✗ BAD -->
+<template>
+<input>
+</template>
+
+<script>export default {
+}</script>
+```
+
+</eslint-code-block>
+
+### `{ "singleline": "always", "multiline": "always", "maxEmptyLines": 1 }`
+
+<eslint-code-block fix :rules="{'vue/block-tag-newline': ['error', { 'singleline': 'always', 'multiline': 'always', 'maxEmptyLines': 1 }]}">
+
+```vue
+<!-- ✓ GOOD -->
+<template>
+
+<input>
+
+</template>
+
+<script>
+
+export default {
+}
+
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block fix :rules="{'vue/block-tag-newline': ['error', { 'singleline': 'always', 'multiline': 'always', 'maxEmptyLines': 1 }]}">
+
+```vue
+<!-- ✗ BAD -->
+<template>
+
+
+<input>
+
+
+</template>
+
+<script>
+
+
+export default {
+}
+
+
+</script>
+```
+
+</eslint-code-block>
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/block-tag-newline.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/block-tag-newline.js)
diff --git a/docs/rules/custom-event-name-casing.md b/docs/rules/custom-event-name-casing.md
index b4faacfb..6890c771 100644
--- a/docs/rules/custom-event-name-casing.md
+++ b/docs/rules/custom-event-name-casing.md
@@ -49,7 +49,46 @@ export default {
 
 ## :wrench: Options
 
-Nothing.
+```json
+{
+  "vue/custom-event-name-casing": ["error", {
+    "ignores": []
+  }]
+}
+```
+
+- `ignores` (`string[]`) ... The event names to ignore. Sets the event name to allow. For example, custom event names, Vue components event with special name, or Vue library component event name. You can set the regexp by writing it like `"/^name/"` or `click:row` or `fooBar`.
+
+### `"ignores": ["fooBar", "/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u"]`
+
+<eslint-code-block :rules="{'vue/custom-event-name-casing': ['error', { ignores: ['fooBar', '/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u'] }]}">
+
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <button @click="$emit('click:row')" />
+  <button @click="$emit('fooBar')" />
+
+  <!-- ✗ BAD -->
+  <button @click="$emit('myEvent')" />
+</template>
+<script>
+export default {
+  methods: {
+    onClick () {
+      /* ✓ GOOD */
+      this.$emit('click:row')
+      this.$emit('fooBar')
+
+      /* ✗ BAD */
+      this.$emit('myEvent')
+    }
+  }
+}
+</script>
+```
+
+</eslint-code-block>
 
 ## :books: Further Reading
 
diff --git a/docs/rules/html-indent.md b/docs/rules/html-indent.md
index c3a45f27..ab6e144d 100644
--- a/docs/rules/html-indent.md
+++ b/docs/rules/html-indent.md
@@ -82,7 +82,7 @@ This rule enforces a consistent indentation style in `<template>`. The default s
   - `closeBracket.endTag` (`integer`) ... The multiplier of indentation for right brackets of end tags (`</div>`). Default is `0`.
   - `closeBracket.selfClosingTag` (`integer`) ... The multiplier of indentation for right brackets of start tags (`<div/>`). Default is `0`.
 - `alignAttributesVertically` (`boolean`) ... Condition for whether attributes should be vertically aligned to the first attribute in multiline case or not. Default is `true`
-- `ignores` (`string[]`) ... The selector to ignore nodes. The AST spec is [here](https://github.com/mysticatea/vue-eslint-parser/blob/master/docs/ast.md). You can use [esquery](https://github.com/estools/esquery#readme) to select nodes. Default is an empty array.
+- `ignores` (`string[]`) ... The selector to ignore nodes. The AST spec is [here](https://github.com/vuejs/vue-eslint-parser/blob/master/docs/ast.md). You can use [esquery](https://github.com/estools/esquery#readme) to select nodes. Default is an empty array.
 
 ### `2, {"attribute": 1, "closeBracket": 1}`
 
diff --git a/docs/rules/no-restricted-syntax.md b/docs/rules/no-restricted-syntax.md
index 1ddde7d9..c28bd5ba 100644
--- a/docs/rules/no-restricted-syntax.md
+++ b/docs/rules/no-restricted-syntax.md
@@ -46,8 +46,8 @@ Forbids call expressions inside mustache interpolation.
 
 [no-restricted-syntax]: https://eslint.org/docs/rules/no-restricted-syntax
 [ESTree]: https://github.com/estree/estree
-[vue-eslint-parser]: https://github.com/mysticatea/vue-eslint-parser
-[vue-eslint-parser - AST docs]: https://github.com/mysticatea/vue-eslint-parser/blob/master/docs/ast.md
+[vue-eslint-parser]: https://github.com/vuejs/vue-eslint-parser
+[vue-eslint-parser - AST docs]: https://github.com/vuejs/vue-eslint-parser/blob/master/docs/ast.md
 
 ## :mag: Implementation
 
diff --git a/docs/rules/no-textarea-mustache.md b/docs/rules/no-textarea-mustache.md
index 6bd242a4..c424f201 100644
--- a/docs/rules/no-textarea-mustache.md
+++ b/docs/rules/no-textarea-mustache.md
@@ -27,11 +27,15 @@ This rule reports mustaches in `<textarea>`.
 
 </eslint-code-block>
 
+<div v-pre>
+
 ::: warning Note
 Interpolation on textareas (`<textarea>{{text}}</textarea>`) won't work. Use `v-model` instead.
 [https://v3.vuejs.org/guide/forms.html#multiline-text](https://v3.vuejs.org/guide/forms.html#multiline-text)
 :::
 
+</div>
+
 ## :wrench: Options
 
 Nothing.
diff --git a/docs/rules/no-unused-vars.md b/docs/rules/no-unused-vars.md
index 4d5d9ee2..2def5d56 100644
--- a/docs/rules/no-unused-vars.md
+++ b/docs/rules/no-unused-vars.md
@@ -33,7 +33,7 @@ This rule report variable definitions of v-for directives or scope attributes if
 
 ## :wrench: Options
 
-```js
+```json
 {
     "vue/no-unused-vars": ["error", {
         "ignorePattern": "^_"
diff --git a/docs/rules/script-indent.md b/docs/rules/script-indent.md
index 6efa786a..ac3dfd11 100644
--- a/docs/rules/script-indent.md
+++ b/docs/rules/script-indent.md
@@ -59,7 +59,7 @@ This rule has some options.
 - `TYPE` (`number | "tab"`) ... The type of indentation. Default is `2`. If this is a number, it's the number of spaces for one indent. If this is `"tab"`, it uses one tab for one indent.
 - `baseIndent` (`integer`) ... The multiplier of indentation for top-level statements. Default is `0`.
 - `switchCase` (`integer`) ... The multiplier of indentation for `case`/`default` clauses. Default is `0`.
-- `ignores` (`string[]`) ... The selector to ignore nodes. The AST spec is [here](https://github.com/mysticatea/vue-eslint-parser/blob/master/docs/ast.md). You can use [esquery](https://github.com/estools/esquery#readme) to select nodes. Default is an empty array.
+- `ignores` (`string[]`) ... The selector to ignore nodes. The AST spec is [here](https://github.com/vuejs/vue-eslint-parser/blob/master/docs/ast.md). You can use [esquery](https://github.com/estools/esquery#readme) to select nodes. Default is an empty array.
 
 ::: warning Note
 This rule only checks `.vue` files and does not interfere with other `.js` files. Unfortunately the default `indent` rule when turned on will try to lint both, so in order to make them complementary you can use `overrides` setting and disable `indent` rule on `.vue` files:
diff --git a/docs/rules/valid-v-slot.md b/docs/rules/valid-v-slot.md
index 2a918392..f0810cc7 100644
--- a/docs/rules/valid-v-slot.md
+++ b/docs/rules/valid-v-slot.md
@@ -100,7 +100,43 @@ This rule does not check syntax errors in directives because it's checked by [vu
 
 ## :wrench: Options
 
-Nothing.
+```json
+{
+  "vue/valid-v-slot": ["error", {
+    "allowModifiers": false
+  }]
+}
+```
+
+- `allowModifiers` (`boolean`) ... allows having modifiers in the argument of `v-slot` directives. Modifiers just after `v-slot` are still disallowed. E.g. `<template v-slot.foo>` default `false`.
+
+### `allowModifiers: true`
+
+<eslint-code-block :rules="{'vue/valid-v-slot': ['error', {allowModifiers: true}]}">
+
+```vue
+<template>
+  <!-- ignore -->
+  <MyComponent>
+    <template v-slot:foo></template>
+    <template v-slot:foo.bar></template>
+    <template v-slot:foo.baz></template>
+    <template v-slot:foo.bar.baz></template>
+  </MyComponent>
+
+  <!--  ✗ BAD */ -->
+  <MyComponent v-slot.foo="{data}">
+    {{ data }}
+  </MyComponent>
+  <MyComponent>
+    <template v-slot.foo>
+      bar
+    </template>
+  </MyComponent>
+</template>
+```
+
+</eslint-code-block>
 
 ## :couple: Related Rules
 
diff --git a/lib/configs/no-layout-rules.js b/lib/configs/no-layout-rules.js
index be534fd3..59c56af8 100644
--- a/lib/configs/no-layout-rules.js
+++ b/lib/configs/no-layout-rules.js
@@ -5,9 +5,11 @@
  */
 module.exports = {
   rules: {
+    'vue/array-bracket-newline': 'off',
     'vue/array-bracket-spacing': 'off',
     'vue/arrow-spacing': 'off',
     'vue/block-spacing': 'off',
+    'vue/block-tag-newline': 'off',
     'vue/brace-style': 'off',
     'vue/comma-dangle': 'off',
     'vue/comma-spacing': 'off',
diff --git a/lib/index.js b/lib/index.js
index eb6a4291..7755e9f1 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -7,11 +7,13 @@
 
 module.exports = {
   rules: {
+    'array-bracket-newline': require('./rules/array-bracket-newline'),
     'array-bracket-spacing': require('./rules/array-bracket-spacing'),
     'arrow-spacing': require('./rules/arrow-spacing'),
     'attribute-hyphenation': require('./rules/attribute-hyphenation'),
     'attributes-order': require('./rules/attributes-order'),
     'block-spacing': require('./rules/block-spacing'),
+    'block-tag-newline': require('./rules/block-tag-newline'),
     'brace-style': require('./rules/brace-style'),
     camelcase: require('./rules/camelcase'),
     'comma-dangle': require('./rules/comma-dangle'),
diff --git a/lib/rules/array-bracket-newline.js b/lib/rules/array-bracket-newline.js
new file mode 100644
index 00000000..c667fc7e
--- /dev/null
+++ b/lib/rules/array-bracket-newline.js
@@ -0,0 +1,11 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const { wrapCoreRule } = require('../utils')
+
+// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+module.exports = wrapCoreRule('array-bracket-newline', {
+  skipDynamicArguments: true
+})
diff --git a/lib/rules/array-bracket-spacing.js b/lib/rules/array-bracket-spacing.js
index a08215e2..0541d058 100644
--- a/lib/rules/array-bracket-spacing.js
+++ b/lib/rules/array-bracket-spacing.js
@@ -6,7 +6,6 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(
-  require('eslint/lib/rules/array-bracket-spacing'),
-  { skipDynamicArguments: true }
-)
+module.exports = wrapCoreRule('array-bracket-spacing', {
+  skipDynamicArguments: true
+})
diff --git a/lib/rules/arrow-spacing.js b/lib/rules/arrow-spacing.js
index 3d626c61..f31ab3c3 100644
--- a/lib/rules/arrow-spacing.js
+++ b/lib/rules/arrow-spacing.js
@@ -6,4 +6,4 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/arrow-spacing'))
+module.exports = wrapCoreRule('arrow-spacing')
diff --git a/lib/rules/block-spacing.js b/lib/rules/block-spacing.js
index d12ad7da..0634f2b6 100644
--- a/lib/rules/block-spacing.js
+++ b/lib/rules/block-spacing.js
@@ -6,6 +6,6 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/block-spacing'), {
+module.exports = wrapCoreRule('block-spacing', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/block-tag-newline.js b/lib/rules/block-tag-newline.js
new file mode 100644
index 00000000..c88a36c2
--- /dev/null
+++ b/lib/rules/block-tag-newline.js
@@ -0,0 +1,375 @@
+/**
+ * @fileoverview Enforce line breaks style after opening and before closing block-level tags.
+ * @author Yosuke Ota
+ */
+'use strict'
+const utils = require('../utils')
+
+/**
+ * @typedef { 'always' | 'never' | 'consistent' | 'ignore' } OptionType
+ * @typedef { { singleline?: OptionType, multiline?: OptionType, maxEmptyLines?: number } } ContentsOptions
+ * @typedef { ContentsOptions & { blocks?: { [element: string]: ContentsOptions } } } Options
+ * @typedef { Required<ContentsOptions> } ArgsOptions
+ */
+
+/**
+ * @param {string} text Source code as a string.
+ * @returns {number}
+ */
+function getLinebreakCount(text) {
+  return text.split(/\r\n|[\r\n\u2028\u2029]/gu).length - 1
+}
+
+/**
+ * @param {number} lineBreaks
+ */
+function getPhrase(lineBreaks) {
+  switch (lineBreaks) {
+    case 1:
+      return '1 line break'
+    default:
+      return `${lineBreaks} line breaks`
+  }
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const ENUM_OPTIONS = { enum: ['always', 'never', 'consistent', 'ignore'] }
+module.exports = {
+  meta: {
+    type: 'layout',
+    docs: {
+      description:
+        'enforce line breaks after opening and before closing block-level tags',
+      categories: undefined,
+      url: 'https://eslint.vuejs.org/rules/block-tag-newline.html'
+    },
+    fixable: 'whitespace',
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          singleline: ENUM_OPTIONS,
+          multiline: ENUM_OPTIONS,
+          maxEmptyLines: { type: 'number', minimum: 0 },
+          blocks: {
+            type: 'object',
+            patternProperties: {
+              '^(?:\\S+)$': {
+                type: 'object',
+                properties: {
+                  singleline: ENUM_OPTIONS,
+                  multiline: ENUM_OPTIONS,
+                  maxEmptyLines: { type: 'number', minimum: 0 }
+                },
+                additionalProperties: false
+              }
+            },
+            additionalProperties: false
+          }
+        },
+        additionalProperties: false
+      }
+    ],
+    messages: {
+      unexpectedOpeningLinebreak:
+        "There should be no line break after '<{{tag}}>'.",
+      unexpectedClosingLinebreak:
+        "There should be no line break before '</{{tag}}>'.",
+      expectedOpeningLinebreak:
+        "Expected {{expected}} after '<{{tag}}>', but {{actual}} found.",
+      expectedClosingLinebreak:
+        "Expected {{expected}}  before '</{{tag}}>', but {{actual}} found.",
+      missingOpeningLinebreak: "A line break is required after '<{{tag}}>'.",
+      missingClosingLinebreak: "A line break is required before '</{{tag}}>'."
+    }
+  },
+  /** @param {RuleContext} context */
+  create(context) {
+    const df =
+      context.parserServices.getDocumentFragment &&
+      context.parserServices.getDocumentFragment()
+    if (!df) {
+      return {}
+    }
+
+    const sourceCode = context.getSourceCode()
+
+    /**
+     * @param {VStartTag} startTag
+     * @param {string} beforeText
+     * @param {number} beforeLinebreakCount
+     * @param {'always' | 'never'} beforeOption
+     * @param {number} maxEmptyLines
+     * @returns {void}
+     */
+    function verifyBeforeSpaces(
+      startTag,
+      beforeText,
+      beforeLinebreakCount,
+      beforeOption,
+      maxEmptyLines
+    ) {
+      if (beforeOption === 'always') {
+        if (beforeLinebreakCount === 0) {
+          context.report({
+            loc: {
+              start: startTag.loc.end,
+              end: startTag.loc.end
+            },
+            messageId: 'missingOpeningLinebreak',
+            data: { tag: startTag.parent.name },
+            fix(fixer) {
+              return fixer.insertTextAfter(startTag, '\n')
+            }
+          })
+        } else if (maxEmptyLines < beforeLinebreakCount - 1) {
+          context.report({
+            loc: {
+              start: startTag.loc.end,
+              end: sourceCode.getLocFromIndex(
+                startTag.range[1] + beforeText.length
+              )
+            },
+            messageId: 'expectedOpeningLinebreak',
+            data: {
+              tag: startTag.parent.name,
+              expected: getPhrase(maxEmptyLines + 1),
+              actual: getPhrase(beforeLinebreakCount)
+            },
+            fix(fixer) {
+              return fixer.replaceTextRange(
+                [startTag.range[1], startTag.range[1] + beforeText.length],
+                '\n'.repeat(maxEmptyLines + 1)
+              )
+            }
+          })
+        }
+      } else {
+        if (beforeLinebreakCount > 0) {
+          context.report({
+            loc: {
+              start: startTag.loc.end,
+              end: sourceCode.getLocFromIndex(
+                startTag.range[1] + beforeText.length
+              )
+            },
+            messageId: 'unexpectedOpeningLinebreak',
+            data: { tag: startTag.parent.name },
+            fix(fixer) {
+              return fixer.removeRange([
+                startTag.range[1],
+                startTag.range[1] + beforeText.length
+              ])
+            }
+          })
+        }
+      }
+    }
+    /**
+     * @param {VEndTag} endTag
+     * @param {string} afterText
+     * @param {number} afterLinebreakCount
+     * @param {'always' | 'never'} afterOption
+     * @param {number} maxEmptyLines
+     * @returns {void}
+     */
+    function verifyAfterSpaces(
+      endTag,
+      afterText,
+      afterLinebreakCount,
+      afterOption,
+      maxEmptyLines
+    ) {
+      if (afterOption === 'always') {
+        if (afterLinebreakCount === 0) {
+          context.report({
+            loc: {
+              start: endTag.loc.start,
+              end: endTag.loc.start
+            },
+            messageId: 'missingClosingLinebreak',
+            data: { tag: endTag.parent.name },
+            fix(fixer) {
+              return fixer.insertTextBefore(endTag, '\n')
+            }
+          })
+        } else if (maxEmptyLines < afterLinebreakCount - 1) {
+          context.report({
+            loc: {
+              start: sourceCode.getLocFromIndex(
+                endTag.range[0] - afterText.length
+              ),
+              end: endTag.loc.start
+            },
+            messageId: 'expectedClosingLinebreak',
+            data: {
+              tag: endTag.parent.name,
+              expected: getPhrase(maxEmptyLines + 1),
+              actual: getPhrase(afterLinebreakCount)
+            },
+            fix(fixer) {
+              return fixer.replaceTextRange(
+                [endTag.range[0] - afterText.length, endTag.range[0]],
+                '\n'.repeat(maxEmptyLines + 1)
+              )
+            }
+          })
+        }
+      } else {
+        if (afterLinebreakCount > 0) {
+          context.report({
+            loc: {
+              start: sourceCode.getLocFromIndex(
+                endTag.range[0] - afterText.length
+              ),
+              end: endTag.loc.start
+            },
+            messageId: 'unexpectedOpeningLinebreak',
+            data: { tag: endTag.parent.name },
+            fix(fixer) {
+              return fixer.removeRange([
+                endTag.range[0] - afterText.length,
+                endTag.range[0]
+              ])
+            }
+          })
+        }
+      }
+    }
+    /**
+     * @param {VElement} element
+     * @param {ArgsOptions} options
+     * @returns {void}
+     */
+    function verifyElement(element, options) {
+      const { startTag, endTag } = element
+      if (startTag.selfClosing || endTag == null) {
+        return
+      }
+      const text = sourceCode.text.slice(startTag.range[1], endTag.range[0])
+
+      const trimText = text.trim()
+      if (!trimText) {
+        return
+      }
+
+      const option =
+        options.multiline === options.singleline
+          ? options.singleline
+          : /[\n\r\u2028\u2029]/u.test(text.trim())
+          ? options.multiline
+          : options.singleline
+      if (option === 'ignore') {
+        return
+      }
+      const beforeText = /** @type {RegExpExecArray} */ (/^\s*/u.exec(text))[0]
+      const afterText = /** @type {RegExpExecArray} */ (/\s*$/u.exec(text))[0]
+      const beforeLinebreakCount = getLinebreakCount(beforeText)
+      const afterLinebreakCount = getLinebreakCount(afterText)
+
+      /** @type {'always' | 'never'} */
+      let beforeOption
+      /** @type {'always' | 'never'} */
+      let afterOption
+      if (option === 'always' || option === 'never') {
+        beforeOption = option
+        afterOption = option
+      } else {
+        // consistent
+        if (beforeLinebreakCount > 0 === afterLinebreakCount > 0) {
+          return
+        }
+        beforeOption = 'always'
+        afterOption = 'always'
+      }
+
+      verifyBeforeSpaces(
+        startTag,
+        beforeText,
+        beforeLinebreakCount,
+        beforeOption,
+        options.maxEmptyLines
+      )
+
+      verifyAfterSpaces(
+        endTag,
+        afterText,
+        afterLinebreakCount,
+        afterOption,
+        options.maxEmptyLines
+      )
+    }
+
+    /**
+     * Normalizes a given option value.
+     * @param { Options | undefined } option An option value to parse.
+     * @returns { (element: VElement) => void } Verify function.
+     */
+    function normalizeOptionValue(option) {
+      if (!option) {
+        return normalizeOptionValue({})
+      }
+
+      /** @type {ContentsOptions} */
+      const contentsOptions = option
+      /** @type {ArgsOptions} */
+      const options = {
+        singleline: contentsOptions.singleline || 'consistent',
+        multiline: contentsOptions.multiline || 'always',
+        maxEmptyLines: contentsOptions.maxEmptyLines || 0
+      }
+      const { blocks } = option
+      if (!blocks) {
+        return (element) => verifyElement(element, options)
+      }
+
+      return (element) => {
+        const { name } = element
+        const elementsOptions = blocks[name]
+        if (!elementsOptions) {
+          verifyElement(element, options)
+        } else {
+          normalizeOptionValue({
+            singleline: elementsOptions.singleline || options.singleline,
+            multiline: elementsOptions.multiline || options.multiline,
+            maxEmptyLines:
+              elementsOptions.maxEmptyLines != null
+                ? elementsOptions.maxEmptyLines
+                : options.maxEmptyLines
+          })(element)
+        }
+      }
+    }
+
+    const documentFragment = df
+
+    const verify = normalizeOptionValue(context.options[0])
+
+    /**
+     * @returns {VElement[]}
+     */
+    function getTopLevelHTMLElements() {
+      return documentFragment.children.filter(utils.isVElement)
+    }
+
+    return utils.defineTemplateBodyVisitor(
+      context,
+      {},
+      {
+        /** @param {Program} node */
+        Program(node) {
+          if (utils.hasInvalidEOF(node)) {
+            return
+          }
+
+          for (const element of getTopLevelHTMLElements()) {
+            verify(element)
+          }
+        }
+      }
+    )
+  }
+}
diff --git a/lib/rules/brace-style.js b/lib/rules/brace-style.js
index 66680948..5dad2c29 100644
--- a/lib/rules/brace-style.js
+++ b/lib/rules/brace-style.js
@@ -6,6 +6,6 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/brace-style'), {
+module.exports = wrapCoreRule('brace-style', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js
index 4aebfaba..4b0898fe 100644
--- a/lib/rules/camelcase.js
+++ b/lib/rules/camelcase.js
@@ -6,4 +6,4 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/camelcase'))
+module.exports = wrapCoreRule('camelcase')
diff --git a/lib/rules/comma-dangle.js b/lib/rules/comma-dangle.js
index aa6c83c5..6fb48b79 100644
--- a/lib/rules/comma-dangle.js
+++ b/lib/rules/comma-dangle.js
@@ -6,4 +6,4 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/comma-dangle'))
+module.exports = wrapCoreRule('comma-dangle')
diff --git a/lib/rules/comma-spacing.js b/lib/rules/comma-spacing.js
index 2a68be8f..7ac0245e 100644
--- a/lib/rules/comma-spacing.js
+++ b/lib/rules/comma-spacing.js
@@ -6,7 +6,7 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/comma-spacing'), {
+module.exports = wrapCoreRule('comma-spacing', {
   skipDynamicArguments: true,
   skipDynamicArgumentsReport: true
 })
diff --git a/lib/rules/comma-style.js b/lib/rules/comma-style.js
index e7313b8a..960e6d6d 100644
--- a/lib/rules/comma-style.js
+++ b/lib/rules/comma-style.js
@@ -6,7 +6,7 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/comma-style'), {
+module.exports = wrapCoreRule('comma-style', {
   create(_context, { coreHandlers }) {
     return {
       VSlotScopeExpression(node) {
diff --git a/lib/rules/custom-event-name-casing.js b/lib/rules/custom-event-name-casing.js
index 6c5f072c..7ed9335a 100644
--- a/lib/rules/custom-event-name-casing.js
+++ b/lib/rules/custom-event-name-casing.js
@@ -11,6 +11,7 @@
 const { findVariable } = require('eslint-utils')
 const utils = require('../utils')
 const { isKebabCase } = require('../utils/casing')
+const { toRegExp } = require('../utils/regexp')
 
 // ------------------------------------------------------------------------------
 // Helpers
@@ -72,7 +73,20 @@ module.exports = {
       url: 'https://eslint.vuejs.org/rules/custom-event-name-casing.html'
     },
     fixable: null,
-    schema: [],
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          ignores: {
+            type: 'array',
+            items: { type: 'string' },
+            uniqueItems: true,
+            additionalItems: false
+          }
+        },
+        additionalProperties: false
+      }
+    ],
     messages: {
       unexpected: "Custom event name '{{name}}' must be kebab-case."
     }
@@ -80,13 +94,16 @@ module.exports = {
   /** @param {RuleContext} context */
   create(context) {
     const setupContexts = new Map()
+    const options = context.options[0] || {}
+    /** @type {RegExp[]} */
+    const ignores = (options.ignores || []).map(toRegExp)
 
     /**
      * @param { Literal & { value: string } } nameLiteralNode
      */
     function verify(nameLiteralNode) {
       const name = nameLiteralNode.value
-      if (isValidEventName(name)) {
+      if (ignores.some((re) => re.test(name)) || isValidEventName(name)) {
         return
       }
       context.report({
diff --git a/lib/rules/dot-location.js b/lib/rules/dot-location.js
index 14ab075c..08f96127 100644
--- a/lib/rules/dot-location.js
+++ b/lib/rules/dot-location.js
@@ -6,4 +6,4 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/dot-location'))
+module.exports = wrapCoreRule('dot-location')
diff --git a/lib/rules/dot-notation.js b/lib/rules/dot-notation.js
index 68633b30..71265896 100644
--- a/lib/rules/dot-notation.js
+++ b/lib/rules/dot-notation.js
@@ -6,4 +6,4 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/dot-notation'))
+module.exports = wrapCoreRule('dot-notation')
diff --git a/lib/rules/eqeqeq.js b/lib/rules/eqeqeq.js
index e749e41f..39894e1f 100644
--- a/lib/rules/eqeqeq.js
+++ b/lib/rules/eqeqeq.js
@@ -6,4 +6,4 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/eqeqeq'))
+module.exports = wrapCoreRule('eqeqeq')
diff --git a/lib/rules/func-call-spacing.js b/lib/rules/func-call-spacing.js
index da076f9c..9fb96ab3 100644
--- a/lib/rules/func-call-spacing.js
+++ b/lib/rules/func-call-spacing.js
@@ -6,6 +6,6 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/func-call-spacing'), {
+module.exports = wrapCoreRule('func-call-spacing', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/key-spacing.js b/lib/rules/key-spacing.js
index 27e62451..a949c18a 100644
--- a/lib/rules/key-spacing.js
+++ b/lib/rules/key-spacing.js
@@ -6,6 +6,6 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/key-spacing'), {
+module.exports = wrapCoreRule('key-spacing', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/keyword-spacing.js b/lib/rules/keyword-spacing.js
index 0b0da627..af1b2848 100644
--- a/lib/rules/keyword-spacing.js
+++ b/lib/rules/keyword-spacing.js
@@ -6,6 +6,6 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/keyword-spacing'), {
+module.exports = wrapCoreRule('keyword-spacing', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/no-empty-pattern.js b/lib/rules/no-empty-pattern.js
index 2d822387..91c7472d 100644
--- a/lib/rules/no-empty-pattern.js
+++ b/lib/rules/no-empty-pattern.js
@@ -6,4 +6,4 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/no-empty-pattern'))
+module.exports = wrapCoreRule('no-empty-pattern')
diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js
index 3e04f193..3f202fa9 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -7,7 +7,7 @@ const { isParenthesized } = require('eslint-utils')
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/no-extra-parens'), {
+module.exports = wrapCoreRule('no-extra-parens', {
   skipDynamicArguments: true,
   create: createForVueSyntax
 })
diff --git a/lib/rules/no-restricted-syntax.js b/lib/rules/no-restricted-syntax.js
index 279f400c..a09ff666 100644
--- a/lib/rules/no-restricted-syntax.js
+++ b/lib/rules/no-restricted-syntax.js
@@ -6,4 +6,4 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/no-restricted-syntax'))
+module.exports = wrapCoreRule('no-restricted-syntax')
diff --git a/lib/rules/no-sparse-arrays.js b/lib/rules/no-sparse-arrays.js
index 89e30b36..48f7390e 100644
--- a/lib/rules/no-sparse-arrays.js
+++ b/lib/rules/no-sparse-arrays.js
@@ -6,4 +6,4 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/no-sparse-arrays'))
+module.exports = wrapCoreRule('no-sparse-arrays')
diff --git a/lib/rules/no-unregistered-components.js b/lib/rules/no-unregistered-components.js
index be892a6e..3ae28328 100644
--- a/lib/rules/no-unregistered-components.js
+++ b/lib/rules/no-unregistered-components.js
@@ -181,6 +181,17 @@ module.exports = {
       },
       utils.executeOnVue(context, (obj) => {
         registeredComponents.push(...utils.getRegisteredComponents(obj))
+
+        const nameProperty = utils.findProperty(obj, 'name')
+
+        if (nameProperty) {
+          if (nameProperty.value.type === 'Literal') {
+            registeredComponents.push({
+              node: nameProperty,
+              name: `${nameProperty.value.value}`
+            })
+          }
+        }
       })
     )
   }
diff --git a/lib/rules/no-unused-properties.js b/lib/rules/no-unused-properties.js
index 23fab442..75b56a4e 100644
--- a/lib/rules/no-unused-properties.js
+++ b/lib/rules/no-unused-properties.js
@@ -578,6 +578,41 @@ module.exports = {
       }
     }
 
+    /**
+     * @param {VueComponentPropertiesContainer} container
+     * @param {UsedProps} baseUseProps
+     */
+    function processUsed(container, baseUseProps) {
+      for (const { usedNames, unknown } of iterateUsedProps(baseUseProps)) {
+        if (unknown) {
+          container.unknown = true
+          return
+        }
+        for (const name of usedNames.names()) {
+          container.usedNames.add(name)
+        }
+      }
+    }
+
+    /**
+     * @param {Expression} node
+     * @returns {Property|null}
+     */
+    function getParentProperty(node) {
+      if (
+        !node.parent ||
+        node.parent.type !== 'Property' ||
+        node.parent.value !== node
+      ) {
+        return null
+      }
+      const property = node.parent
+      if (!property.parent || property.parent.type !== 'ObjectExpression') {
+        return null
+      }
+      return /** @type {Property} */ (property)
+    }
+
     const scriptVisitor = Object.assign(
       {},
       utils.defineVueVisitor(context, {
@@ -629,6 +664,61 @@ module.exports = {
             container.properties.push(prop)
           }
         },
+        /** @param { (FunctionExpression | ArrowFunctionExpression) & { parent: Property }} node */
+        'ObjectExpression > Property > :function[params.length>0]'(
+          node,
+          vueData
+        ) {
+          const property = getParentProperty(node)
+          if (!property) {
+            return
+          }
+          if (property.parent === vueData.node) {
+            if (utils.getStaticPropertyName(property) !== 'data') {
+              return
+            }
+            // check { data: (vm) => vm.prop }
+          } else {
+            const parentProperty = getParentProperty(property.parent)
+            if (!parentProperty) {
+              return
+            }
+            if (parentProperty.parent === vueData.node) {
+              if (utils.getStaticPropertyName(parentProperty) !== 'computed') {
+                return
+              }
+              // check { computed: { foo: (vm) => vm.prop } }
+            } else {
+              const parentParentProperty = getParentProperty(
+                parentProperty.parent
+              )
+              if (!parentParentProperty) {
+                return
+              }
+              if (parentParentProperty.parent === vueData.node) {
+                if (
+                  utils.getStaticPropertyName(parentParentProperty) !==
+                    'computed' ||
+                  utils.getStaticPropertyName(property) !== 'handler'
+                ) {
+                  return
+                }
+                // check { computed: { foo: { handler: (vm) => vm.prop } } }
+              } else {
+                return
+              }
+            }
+          }
+
+          const paramsUsedProps = getParamsUsedProps(node)
+          const usedProps = /** @type {ParamUsedProps} */ (paramsUsedProps.getParam(
+            0
+          ))
+          processUsed(
+            getVueComponentPropertiesContainer(vueData.node),
+            usedProps
+          )
+        },
         onSetupFunctionEnter(node, vueData) {
           const container = getVueComponentPropertiesContainer(vueData.node)
           if (node.params[0]) {
@@ -690,15 +780,7 @@ module.exports = {
           const container = getVueComponentPropertiesContainer(vueData.node)
           const usedProps = extractPatternOrThisProperties(node, context)
 
-          for (const { usedNames, unknown } of iterateUsedProps(usedProps)) {
-            if (unknown) {
-              container.unknown = true
-              return
-            }
-            for (const name of usedNames.names()) {
-              container.usedNames.add(name)
-            }
-          }
+          processUsed(container, usedProps)
         }
       }),
       {
diff --git a/lib/rules/no-useless-concat.js b/lib/rules/no-useless-concat.js
index 9038b9b9..7379a1ee 100644
--- a/lib/rules/no-useless-concat.js
+++ b/lib/rules/no-useless-concat.js
@@ -6,4 +6,4 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/no-useless-concat'))
+module.exports = wrapCoreRule('no-useless-concat')
diff --git a/lib/rules/object-curly-newline.js b/lib/rules/object-curly-newline.js
index 296abe57..5aa99411 100644
--- a/lib/rules/object-curly-newline.js
+++ b/lib/rules/object-curly-newline.js
@@ -6,7 +6,6 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(
-  require('eslint/lib/rules/object-curly-newline'),
-  { skipDynamicArguments: true }
-)
+module.exports = wrapCoreRule('object-curly-newline', {
+  skipDynamicArguments: true
+})
diff --git a/lib/rules/object-curly-spacing.js b/lib/rules/object-curly-spacing.js
index 9830d2d4..b7c669c4 100644
--- a/lib/rules/object-curly-spacing.js
+++ b/lib/rules/object-curly-spacing.js
@@ -6,7 +6,6 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(
-  require('eslint/lib/rules/object-curly-spacing'),
-  { skipDynamicArguments: true }
-)
+module.exports = wrapCoreRule('object-curly-spacing', {
+  skipDynamicArguments: true
+})
diff --git a/lib/rules/object-property-newline.js b/lib/rules/object-property-newline.js
index d58aeb84..dacd8244 100644
--- a/lib/rules/object-property-newline.js
+++ b/lib/rules/object-property-newline.js
@@ -6,7 +6,6 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(
-  require('eslint/lib/rules/object-property-newline'),
-  { skipDynamicArguments: true }
-)
+module.exports = wrapCoreRule('object-property-newline', {
+  skipDynamicArguments: true
+})
diff --git a/lib/rules/operator-linebreak.js b/lib/rules/operator-linebreak.js
index 2546693d..ad92aa74 100644
--- a/lib/rules/operator-linebreak.js
+++ b/lib/rules/operator-linebreak.js
@@ -6,4 +6,4 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/operator-linebreak'))
+module.exports = wrapCoreRule('operator-linebreak')
diff --git a/lib/rules/prefer-template.js b/lib/rules/prefer-template.js
index b78d1eb7..5cc11e2a 100644
--- a/lib/rules/prefer-template.js
+++ b/lib/rules/prefer-template.js
@@ -6,4 +6,4 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/prefer-template'))
+module.exports = wrapCoreRule('prefer-template')
diff --git a/lib/rules/space-in-parens.js b/lib/rules/space-in-parens.js
index 518c6200..29ba0403 100644
--- a/lib/rules/space-in-parens.js
+++ b/lib/rules/space-in-parens.js
@@ -6,7 +6,7 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/space-in-parens'), {
+module.exports = wrapCoreRule('space-in-parens', {
   skipDynamicArguments: true,
   skipDynamicArgumentsReport: true
 })
diff --git a/lib/rules/space-infix-ops.js b/lib/rules/space-infix-ops.js
index fe29d9ec..650b215e 100644
--- a/lib/rules/space-infix-ops.js
+++ b/lib/rules/space-infix-ops.js
@@ -6,6 +6,6 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/space-infix-ops'), {
+module.exports = wrapCoreRule('space-infix-ops', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/space-unary-ops.js b/lib/rules/space-unary-ops.js
index a45265c3..4920ca9e 100644
--- a/lib/rules/space-unary-ops.js
+++ b/lib/rules/space-unary-ops.js
@@ -6,6 +6,6 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(require('eslint/lib/rules/space-unary-ops'), {
+module.exports = wrapCoreRule('space-unary-ops', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/template-curly-spacing.js b/lib/rules/template-curly-spacing.js
index 777c5d82..acefea6b 100644
--- a/lib/rules/template-curly-spacing.js
+++ b/lib/rules/template-curly-spacing.js
@@ -6,7 +6,6 @@
 const { wrapCoreRule } = require('../utils')
 
 // eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule(
-  require('eslint/lib/rules/template-curly-spacing'),
-  { skipDynamicArguments: true }
-)
+module.exports = wrapCoreRule('template-curly-spacing', {
+  skipDynamicArguments: true
+})
diff --git a/lib/rules/valid-v-slot.js b/lib/rules/valid-v-slot.js
index a94d8c9d..07025f5e 100644
--- a/lib/rules/valid-v-slot.js
+++ b/lib/rules/valid-v-slot.js
@@ -75,15 +75,18 @@ function getSlotDirectivesOnChildren(node) {
 }
 
 /**
- * Get the normalized name of a given `v-slot` directive node.
+ * Get the normalized name of a given `v-slot` directive node with modifiers after `v-slot:` directive.
  * @param {VDirective} node The `v-slot` directive node.
  * @param {SourceCode} sourceCode The source code.
  * @returns {string} The normalized name.
  */
 function getNormalizedName(node, sourceCode) {
-  return node.key.argument == null
-    ? 'default'
-    : sourceCode.getText(node.key.argument)
+  if (node.key.argument == null) {
+    return 'default'
+  }
+  return node.key.modifiers.length === 0
+    ? sourceCode.getText(node.key.argument)
+    : sourceCode.text.slice(node.key.argument.range[0], node.key.range[1])
 }
 
 /**
@@ -150,6 +153,19 @@ function isUsingScopeVar(vSlot) {
   return false
 }
 
+/**
+ * If `allowModifiers` option is set to `true`, check whether a given argument node has invalid modifiers like `v-slot.foo`.
+ * Otherwise, check whether a given argument node has at least one modifier.
+ * @param {VDirective} vSlot The `v-slot` directive to check.
+ * @param {boolean} allowModifiers `allowModifiers` option in context.
+ * @return {boolean} `true` if that argument node has invalid modifiers like `v-slot.foo`.
+ */
+function hasInvalidModifiers(vSlot, allowModifiers) {
+  return allowModifiers
+    ? vSlot.key.argument == null && vSlot.key.modifiers.length >= 1
+    : vSlot.key.modifiers.length >= 1
+}
+
 module.exports = {
   meta: {
     type: 'problem',
@@ -159,7 +175,16 @@ module.exports = {
       url: 'https://eslint.vuejs.org/rules/valid-v-slot.html'
     },
     fixable: null,
-    schema: [],
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          allowModifiers: {
+            type: 'boolean'
+          }
+        }
+      }
+    ],
     messages: {
       ownerMustBeCustomElement:
         "'v-slot' directive must be owned by a custom element, but '{{name}}' is not.",
@@ -181,6 +206,8 @@ module.exports = {
   /** @param {RuleContext} context */
   create(context) {
     const sourceCode = context.getSourceCode()
+    const options = context.options[0] || {}
+    const allowModifiers = options.allowModifiers === true
 
     return utils.defineTemplateBodyVisitor(context, {
       /** @param {VDirective} node */
@@ -264,7 +291,8 @@ module.exports = {
         }
 
         // Verify modifiers.
-        if (node.key.modifiers.length >= 1) {
+        if (hasInvalidModifiers(node, allowModifiers)) {
+          // E.g., <template v-slot.foo>
           context.report({
             node,
             messageId: 'disallowAnyModifier'
diff --git a/lib/utils/index.js b/lib/utils/index.js
index 814517e3..24b9a1bb 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -108,6 +108,18 @@ const { findVariable } = require('eslint-utils')
  */
 const componentComments = new WeakMap()
 
+/** @type { Map<string, RuleModule> | null } */
+let ruleMap = null
+/**
+ * Get the core rule implementation from the rule name
+ * @param {string} name
+ * @returns {RuleModule}
+ */
+function getCoreRule(name) {
+  const map = ruleMap || (ruleMap = new (require('eslint').Linter)().getRules())
+  return map.get(name) || require(`eslint/lib/rules/${name}`)
+}
+
 /**
  * Wrap the rule context object to override methods which access to tokens (such as getTokenAfter).
  * @param {RuleContext} context The rule context object.
@@ -225,7 +237,7 @@ module.exports = {
 
   /**
    * Wrap a given core rule to apply it to Vue.js template.
-   * @param {RuleModule} coreRule The core rule implementation to wrap.
+   * @param {string} coreRuleName The name of the core rule implementation to wrap.
    * @param {Object} [options] The option of this rule.
    * @param {string[]} [options.categories] The categories of this rule.
    * @param {boolean} [options.skipDynamicArguments] If `true`, skip validation within dynamic arguments.
@@ -233,7 +245,8 @@ module.exports = {
    * @param { (context: RuleContext, options: { coreHandlers: RuleListener }) => TemplateListener } [options.create] If define, extend core rule.
    * @returns {RuleModule} The wrapped rule implementation.
    */
-  wrapCoreRule(coreRule, options) {
+  wrapCoreRule(coreRuleName, options) {
+    const coreRule = getCoreRule(coreRuleName)
     const {
       categories,
       skipDynamicArguments,
diff --git a/package.json b/package.json
index ef4847db..eb2ab025 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-vue",
-  "version": "7.0.1",
+  "version": "7.1.0",
   "description": "Official ESLint plugin for Vue.js",
   "main": "lib/index.js",
   "scripts": {
@@ -13,7 +13,6 @@
     "cover:report": "nyc report --reporter=html",
     "lint": "eslint . --rulesdir eslint-internal-rules",
     "lint:fix": "eslint . --rulesdir eslint-internal-rules --fix",
-    "pretsc": "node ./tools/setup-eslint-rule-types.js",
     "tsc": "tsc",
     "preversion": "npm test && npm run update && git add .",
     "version": "npm run lint -- --fix && git add .",
@@ -56,7 +55,7 @@
     "eslint-utils": "^2.1.0",
     "natural-compare": "^1.4.0",
     "semver": "^7.3.2",
-    "vue-eslint-parser": "^7.1.0"
+    "vue-eslint-parser": "^7.1.1"
   },
   "devDependencies": {
     "@types/eslint": "^7.2.0",
diff --git a/tools/setup-eslint-rule-types.js b/tools/setup-eslint-rule-types.js
deleted file mode 100644
index d836d025..00000000
--- a/tools/setup-eslint-rule-types.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * @author Yosuke Ota
- * See LICENSE file in root directory for full license.
- */
-'use strict'
-
-const fs = require('fs')
-const path = require('path')
-const eslint = require('eslint')
-const ruleNames = new Set(new eslint.Linter().getRules().keys())
-
-const TYPINGS_ESLINT_RULES_ROOT = path.resolve(
-  __dirname,
-  '../typings/eslint/lib/rules'
-)
-
-mkdirIfNotExists(TYPINGS_ESLINT_RULES_ROOT)
-
-for (const ruleName of ruleNames) {
-  const filePath = path.join(TYPINGS_ESLINT_RULES_ROOT, `${ruleName}.d.ts`)
-  fs.writeFileSync(
-    filePath,
-    `import { Rule } from '../../index'
-declare const rule: Rule.RuleModule
-export = rule
-`
-  )
-}
-
-fs.readdirSync(TYPINGS_ESLINT_RULES_ROOT)
-  .filter((file) => file.endsWith('.d.ts'))
-  .filter((file) => !ruleNames.has(file.slice(0, -5)))
-  .map((file) => path.join(TYPINGS_ESLINT_RULES_ROOT, file))
-  .forEach((filePath) => fs.unlinkSync(filePath))
-
-function exists(file) {
-  try {
-    fs.statSync(file)
-    return true
-  } catch (err) {
-    if (err.code === 'ENOENT') return false
-  }
-}
-
-function mkdirIfNotExists(dir) {
-  if (exists(dir)) {
-    return
-  }
-  const p = path.dirname(dir)
-  mkdirIfNotExists(p)
-  fs.mkdirSync(dir)
-}
diff --git a/typings/eslint-plugin-vue/util-types/ast/es-ast.ts b/typings/eslint-plugin-vue/util-types/ast/es-ast.ts
index 035c113f..9af1e7d0 100644
--- a/typings/eslint-plugin-vue/util-types/ast/es-ast.ts
+++ b/typings/eslint-plugin-vue/util-types/ast/es-ast.ts
@@ -351,68 +351,76 @@ export interface SequenceExpression extends HasParentNode {
   type: 'SequenceExpression'
   expressions: Expression[]
 }
+export type UnaryOperator = '-' | '+' | '!' | '~' | 'typeof' | 'void' | 'delete'
 export interface UnaryExpression extends HasParentNode {
   type: 'UnaryExpression'
-  operator: '-' | '+' | '!' | '~' | 'typeof' | 'void' | 'delete'
+  operator: UnaryOperator
   prefix: boolean
   argument: Expression
 }
+export type BinaryOperator =
+  | '=='
+  | '!='
+  | '==='
+  | '!=='
+  | '<'
+  | '<='
+  | '>'
+  | '>='
+  | '<<'
+  | '>>'
+  | '>>>'
+  | '+'
+  | '-'
+  | '*'
+  | '/'
+  | '%'
+  | '|'
+  | '^'
+  | '&'
+  | 'in'
+  | 'instanceof'
+  | '**'
 export interface BinaryExpression extends HasParentNode {
   type: 'BinaryExpression'
-  operator:
-    | '=='
-    | '!='
-    | '==='
-    | '!=='
-    | '<'
-    | '<='
-    | '>'
-    | '>='
-    | '<<'
-    | '>>'
-    | '>>>'
-    | '+'
-    | '-'
-    | '*'
-    | '/'
-    | '%'
-    | '**'
-    | '|'
-    | '^'
-    | '&'
-    | 'in'
-    | 'instanceof'
+  operator: BinaryOperator
   left: Expression
   right: Expression
 }
+export type AssignmentOperator =
+  | '='
+  | '+='
+  | '-='
+  | '*='
+  | '/='
+  | '%='
+  | '<<='
+  | '>>='
+  | '>>>='
+  | '|='
+  | '^='
+  | '&='
+  | '**='
+  | '||='
+  | '&&='
+  | '??='
 export interface AssignmentExpression extends HasParentNode {
   type: 'AssignmentExpression'
-  operator:
-    | '='
-    | '+='
-    | '-='
-    | '*='
-    | '/='
-    | '%='
-    | '**='
-    | '<<='
-    | '>>='
-    | '>>>='
-    | '|='
-    | '^='
-    | '&='
+  operator: AssignmentOperator
   left: Pattern
   right: Expression
 }
+export type UpdateOperator = '++' | '--'
 export interface UpdateExpression extends HasParentNode {
   type: 'UpdateExpression'
-  operator: '++' | '--'
+  operator: UpdateOperator
   argument: Expression
   prefix: boolean
 }
+export type LogicalOperator = '||' | '&&' | '??'
 export interface LogicalExpression extends HasParentNode {
   type: 'LogicalExpression'
-  operator: '||' | '&&' | '??'
+  operator: LogicalOperator
   left: Expression
   right: Expression
 }
diff --git a/typings/eslint-plugin-vue/util-types/ast/v-ast.ts b/typings/eslint-plugin-vue/util-types/ast/v-ast.ts
index 64149ac5..fe0ac637 100644
--- a/typings/eslint-plugin-vue/util-types/ast/v-ast.ts
+++ b/typings/eslint-plugin-vue/util-types/ast/v-ast.ts
@@ -1,5 +1,5 @@
 /**
- * @see https://github.com/mysticatea/vue-eslint-parser/blob/master/docs/ast.md
+ * @see https://github.com/vuejs/vue-eslint-parser/blob/master/docs/ast.md
  */
 import { HasParentNode, BaseNode } from '../node'
 import { Token, HTMLComment, HTMLBogusComment, Comment } from '../node'
diff --git a/typings/eslint/index.d.ts b/typings/eslint/index.d.ts
index 1d793d24..0c99161f 100644
--- a/typings/eslint/index.d.ts
+++ b/typings/eslint/index.d.ts
@@ -357,6 +357,9 @@ export namespace Rule {
 }
 
 export class RuleTester extends ESLintRuleTester {}
+export class Linter {
+  getRules(): Map<string, Rule.RuleModule>
+}
 
 export namespace Linter {
   type LintMessage = ESLintLinter.LintMessage


Test patch:
diff --git a/tests/lib/rules/array-bracket-newline.js b/tests/lib/rules/array-bracket-newline.js
new file mode 100644
index 00000000..06e1890c
--- /dev/null
+++ b/tests/lib/rules/array-bracket-newline.js
@@ -0,0 +1,108 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/array-bracket-newline')
+
+const tester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2015 }
+})
+
+tester.run('array-bracket-newline', rule, {
+  valid: [
+    '<template><div :attr="[a]" /></template>',
+    '<template><div :attr="[\na,\nb,\nc\n]" /></template>',
+    {
+      code: '<template><div :attr="[a]" /></template>',
+      options: ['never']
+    },
+    {
+      code: '<template><div :attr="[\na\n]" /></template>',
+      options: ['always']
+    },
+    '<template><div :[attr]="a" /></template>',
+    {
+      code: '<template><div :[attr]="a" /></template>',
+      options: ['always']
+    },
+    '<template><div :[[attr]]="a" /></template>',
+    {
+      code: '<template><div :[[attr]]="a" /></template>',
+      options: ['always']
+    }
+  ],
+  invalid: [
+    {
+      code: '<template><div :attr="[\na]" /></template>',
+      output: '<template><div :attr="[a]" /></template>',
+      errors: ["There should be no linebreak after '['."]
+    },
+    {
+      code: '<template><div :attr="[a\n]" /></template>',
+      output: '<template><div :attr="[a]" /></template>',
+      errors: ["There should be no linebreak before ']'."]
+    },
+    {
+      code: '<template><div :attr="[\na\n]" /></template>',
+      output: '<template><div :attr="[a]" /></template>',
+      errors: [
+        "There should be no linebreak after '['.",
+        "There should be no linebreak before ']'."
+      ]
+    },
+    {
+      code: '<template><div :attr="[\na]" /></template>',
+      options: ['never'],
+      output: '<template><div :attr="[a]" /></template>',
+      errors: ["There should be no linebreak after '['."]
+    },
+    {
+      code: '<template><div :attr="[a\n]" /></template>',
+      options: ['never'],
+      output: '<template><div :attr="[a]" /></template>',
+      errors: ["There should be no linebreak before ']'."]
+    },
+    {
+      code: '<template><div :attr="[\na\n]" /></template>',
+      options: ['never'],
+      output: '<template><div :attr="[a]" /></template>',
+      errors: [
+        "There should be no linebreak after '['.",
+        "There should be no linebreak before ']'."
+      ]
+    },
+    {
+      code: '<template><div :attr="[\na]" /></template>',
+      options: ['always'],
+      output: '<template><div :attr="[\na\n]" /></template>',
+      errors: ["A linebreak is required before ']'."]
+    },
+    {
+      code: '<template><div :attr="[a\n]" /></template>',
+      options: ['always'],
+      output: '<template><div :attr="[\na\n]" /></template>',
+      errors: ["A linebreak is required after '['."]
+    },
+    {
+      code: '<template><div :attr="[a]" /></template>',
+      options: ['always'],
+      output: '<template><div :attr="[\na\n]" /></template>',
+      errors: [
+        "A linebreak is required after '['.",
+        "A linebreak is required before ']'."
+      ]
+    },
+    {
+      code: '<template><div :[[attr]]="[a]" /></template>',
+      options: ['always'],
+      output: '<template><div :[[attr]]="[\na\n]" /></template>',
+      errors: [
+        "A linebreak is required after '['.",
+        "A linebreak is required before ']'."
+      ]
+    }
+  ]
+})
diff --git a/tests/lib/rules/block-tag-newline.js b/tests/lib/rules/block-tag-newline.js
new file mode 100644
index 00000000..92427879
--- /dev/null
+++ b/tests/lib/rules/block-tag-newline.js
@@ -0,0 +1,247 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/block-tag-newline')
+
+const tester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2015 }
+})
+
+tester.run('block-tag-newline', rule, {
+  valid: [
+    '<template><input></template>\n<script>let a</script>',
+    '<template>\n<input>\n</template>\n<script>\nlet a\n</script>',
+    '<template>\n<div>\n</div>\n</template>\n<script>\nlet a\nlet b\n</script>',
+    '<template></template>\n<script></script>',
+    '<template>\n\n</template>\n<script>\n\n</script>',
+    '<template />\n<script />',
+    {
+      code: '<template><div>\n</div></template>\n<script>let a</script>',
+      options: [{ singleline: 'never', multiline: 'never' }]
+    },
+    {
+      code:
+        '<template>\n<input>\n</template>\n<script>\nlet a\nlet b\n</script>',
+      options: [{ singleline: 'always', multiline: 'always' }]
+    },
+    {
+      code:
+        '<template>\n\n<input>\n\n</template>\n<script>\n\nlet a\nlet b\n\n</script>',
+      options: [{ singleline: 'always', multiline: 'always', maxEmptyLines: 1 }]
+    },
+    {
+      code: '<template>\n<input></template>\n<script>let a\nlet b\n</script>',
+      options: [{ singleline: 'ignore', multiline: 'ignore' }]
+    },
+    {
+      code: '<template><input></template>\n<script>\nlet a\n</script>',
+      options: [{ multiline: 'never' }]
+    },
+    {
+      code:
+        '<template>\n<div>\n</div>\n</template>\n<script>\nlet a\nlet b\n</script>',
+      options: [{ singleline: 'never' }]
+    },
+    // invalid
+    '<template><div a="></div>\n</template>\n<script>\nlet a</script>'
+  ],
+  invalid: [
+    {
+      code: '<template><input>\n</template>\n<script>let a\n</script>',
+      output: '<template>\n<input>\n</template>\n<script>\nlet a\n</script>',
+      errors: [
+        {
+          message: "A line break is required after '<template>'.",
+          line: 1,
+          column: 11
+        },
+        {
+          message: "A line break is required after '<script>'.",
+          line: 3,
+          column: 9
+        }
+      ]
+    },
+    {
+      code: '<template>\n<input></template>\n<script>\nlet a</script>',
+      output: '<template>\n<input>\n</template>\n<script>\nlet a\n</script>',
+      errors: [
+        {
+          message: "A line break is required before '</template>'.",
+          line: 2,
+          column: 8
+        },
+        {
+          message: "A line break is required before '</script>'.",
+          line: 4,
+          column: 6
+        }
+      ]
+    },
+    {
+      code: '<template><div>\n</div></template>\n<script>let a\nlet b</script>',
+      output:
+        '<template>\n<div>\n</div>\n</template>\n<script>\nlet a\nlet b\n</script>',
+      errors: [
+        {
+          message: "A line break is required after '<template>'.",
+          line: 1,
+          column: 11
+        },
+        {
+          message: "A line break is required before '</template>'.",
+          line: 2,
+          column: 7
+        },
+        {
+          message: "A line break is required after '<script>'.",
+          line: 3,
+          column: 9
+        },
+        {
+          message: "A line break is required before '</script>'.",
+          line: 4,
+          column: 6
+        }
+      ]
+    },
+    {
+      code:
+        '<template>\n<div>\n</div>\n</template>\n<script>\nlet a\n</script>',
+      output: '<template><div>\n</div></template>\n<script>let a</script>',
+      options: [{ singleline: 'never', multiline: 'never' }],
+      errors: [
+        {
+          message: "There should be no line break after '<template>'.",
+          line: 1,
+          column: 11
+        },
+        {
+          message: "There should be no line break after '<template>'.",
+          line: 3,
+          column: 7
+        },
+        {
+          message: "There should be no line break after '<script>'.",
+          line: 5,
+          column: 9
+        },
+        {
+          message: "There should be no line break after '<script>'.",
+          line: 6,
+          column: 6
+        }
+      ]
+    },
+    {
+      code: '<template><div>\n</div></template>\n<script>let a</script>',
+      output:
+        '<template>\n<div>\n</div>\n</template>\n<script>\nlet a\n</script>',
+      options: [{ singleline: 'always', multiline: 'always' }],
+      errors: [
+        {
+          message: "A line break is required after '<template>'.",
+          line: 1,
+          column: 11
+        },
+        {
+          message: "A line break is required before '</template>'.",
+          line: 2,
+          column: 7
+        },
+        {
+          message: "A line break is required after '<script>'.",
+          line: 3,
+          column: 9
+        },
+        {
+          message: "A line break is required before '</script>'.",
+          line: 3,
+          column: 14
+        }
+      ]
+    },
+    {
+      code:
+        '<template>\n\n<input>\n\n</template>\n<script>\n\nlet a\nlet b\n\n</script>',
+      output:
+        '<template>\n<input>\n</template>\n<script>\nlet a\nlet b\n</script>',
+      options: [{ singleline: 'always', multiline: 'always' }],
+      errors: [
+        {
+          message:
+            "Expected 1 line break after '<template>', but 2 line breaks found.",
+          line: 1,
+          column: 11
+        },
+        {
+          message:
+            "Expected 1 line break  before '</template>', but 2 line breaks found.",
+          line: 3,
+          column: 8
+        },
+        {
+          message:
+            "Expected 1 line break after '<script>', but 2 line breaks found.",
+          line: 6,
+          column: 9
+        },
+        {
+          message:
+            "Expected 1 line break  before '</script>', but 2 line breaks found.",
+          line: 9,
+          column: 6
+        }
+      ]
+    },
+    {
+      code:
+        '<template>\n\n\n<input>\n\n</template>\n<script>\n\nlet a\nlet b\n\n\n</script>',
+      output:
+        '<template>\n\n<input>\n\n</template>\n<script>\n\nlet a\nlet b\n\n</script>',
+      options: [
+        { singleline: 'always', multiline: 'always', maxEmptyLines: 1 }
+      ],
+      errors: [
+        {
+          message:
+            "Expected 2 line breaks after '<template>', but 3 line breaks found.",
+          line: 1,
+          column: 11
+        },
+        {
+          message:
+            "Expected 2 line breaks  before '</script>', but 3 line breaks found.",
+          line: 10,
+          column: 6
+        }
+      ]
+    },
+    {
+      code:
+        '<template><input>\n\n</template>\n<script>let a\nlet b\n\n\n</script><docs>\n#</docs>',
+      output:
+        '<template><input>\n\n</template>\n<script>let a\nlet b</script><docs>\n#\n</docs>',
+      options: [
+        {
+          blocks: {
+            template: {
+              singleline: 'ignore'
+            },
+            script: {
+              multiline: 'never'
+            }
+          }
+        }
+      ],
+      errors: [
+        "There should be no line break after '<script>'.",
+        "A line break is required before '</docs>'."
+      ]
+    }
+  ]
+})
diff --git a/tests/lib/rules/custom-event-name-casing.js b/tests/lib/rules/custom-event-name-casing.js
index 934bb079..ed0ba0eb 100644
--- a/tests/lib/rules/custom-event-name-casing.js
+++ b/tests/lib/rules/custom-event-name-casing.js
@@ -166,6 +166,66 @@ tester.run('custom-event-name-casing', rule, {
       }
       </script>
       `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <input
+          @click="$emit('fooBar')">
+      </template>
+      <script>
+      export default {
+        setup(props, context) {
+          return {
+            onInput(value) {
+              context.emit('barBaz')
+            }
+          }
+        },
+        methods: {
+          onClick() {
+            this.$emit('bazQux')
+          }
+        }
+      }
+      </script>
+      `,
+      options: [{ ignores: ['fooBar', 'barBaz', 'bazQux'] }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <input
+          @click="$emit('input:update')">
+        <input
+          @click="$emit('search:update')">
+        <input
+          @click="$emit('click:row')">
+      </template>
+      <script>
+      export default {
+        setup(props, context) {
+          return {
+            onInput(value) {
+              context.emit('input:update')
+              context.emit('search:update')
+              context.emit('click:row')
+            }
+          }
+        },
+        methods: {
+          onClick() {
+            this.$emit('input:update')
+            this.$emit('search:update')
+            this.$emit('click:row')
+          }
+        }
+      }
+      </script>
+      `,
+      options: [{ ignores: ['/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u'] }]
     }
   ],
   invalid: [
@@ -276,6 +336,68 @@ tester.run('custom-event-name-casing', rule, {
         "Custom event name 'barBaz' must be kebab-case.",
         "Custom event name 'bazQux' must be kebab-case."
       ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <input
+          @click="$emit('input/update')">
+      </template>
+      <script>
+      export default {
+        setup(props, context) {
+          return {
+            onInput(value) {
+              context.emit('search/update')
+            }
+          }
+        },
+        methods: {
+          onClick() {
+            this.$emit('click/row')
+          }
+        }
+      }
+      </script>
+      `,
+      options: [{ ignores: ['/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u'] }],
+      errors: [
+        "Custom event name 'input/update' must be kebab-case.",
+        "Custom event name 'search/update' must be kebab-case.",
+        "Custom event name 'click/row' must be kebab-case."
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <input
+          @click="$emit('input/update')">
+      </template>
+      <script>
+      export default {
+        setup(props, context) {
+          return {
+            onInput(value) {
+              context.emit('search/update')
+            }
+          }
+        },
+        methods: {
+          onClick() {
+            this.$emit('click/row')
+          }
+        }
+      }
+      </script>
+      `,
+      options: [{ ignores: ['input:update', 'search:update', 'click:row'] }],
+      errors: [
+        "Custom event name 'input/update' must be kebab-case.",
+        "Custom event name 'search/update' must be kebab-case.",
+        "Custom event name 'click/row' must be kebab-case."
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/no-unregistered-components.js b/tests/lib/rules/no-unregistered-components.js
index da9e37ee..a50e3c0f 100644
--- a/tests/lib/rules/no-unregistered-components.js
+++ b/tests/lib/rules/no-unregistered-components.js
@@ -393,6 +393,71 @@ tester.run('no-unregistered-components', rule, {
         }
         </script>
       `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <CustomComponent />
+        </template>
+        <script>
+        export default {
+          name: 'CustomComponent'
+        }
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <custom-component />
+        </template>
+        <script>
+        export default {
+          name: 'CustomComponent'
+        }
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <component :is="'CustomComponent'" />
+        </template>
+        <script>
+        export default {
+          name: 'CustomComponent'
+        }
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <component is="CustomComponent" />
+        </template>
+        <script>
+        export default {
+          name: 'CustomComponent'
+        }
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-is="'CustomComponent'" />
+        </template>
+        <script>
+        export default {
+          name: 'CustomComponent'
+        }
+        </script>
+      `
     }
   ],
   invalid: [
@@ -583,6 +648,26 @@ tester.run('no-unregistered-components', rule, {
           line: 3
         }
       ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <CustomComponent />
+        </template>
+        <script>
+        export default {
+          name: CustomComponent
+        }
+        </script>
+      `,
+      errors: [
+        {
+          message:
+            'The "CustomComponent" component has been used but not registered.',
+          line: 3
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/no-unused-properties.js b/tests/lib/rules/no-unused-properties.js
index dffa036f..78e15c4f 100644
--- a/tests/lib/rules/no-unused-properties.js
+++ b/tests/lib/rules/no-unused-properties.js
@@ -1078,6 +1078,48 @@ tester.run('no-unused-properties', rule, {
       </script>
       `,
       options: [{ groups: ['props', 'data'] }]
+    },
+    // contexts
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          props: ['x'],
+          data: ({ x }) => ({
+              y: x
+          })
+        };
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          props: ['x'],
+          computed: {
+            y: (vm) => vm.x * 2
+          }
+        };
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          props: ['x'],
+          computed: {
+            y: {
+              handler: (vm) => vm.x * 2
+            }
+          }
+        };
+      </script>
+      `
     }
   ],
 
@@ -1574,6 +1616,53 @@ tester.run('no-unused-properties', rule, {
       })
       `,
       errors: ["'foo' of property found, but never used."]
+    },
+
+    // contexts
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          props: ['x'],
+          data: () => ({
+              y: x
+          })
+        };
+      </script>
+      `,
+      errors: ["'x' of property found, but never used."]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          props: ['x'],
+          computed: {
+            [(vm) => vm.x * 2]: y
+          }
+        };
+      </script>
+      `,
+      errors: ["'x' of property found, but never used."]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          props: ['x'],
+          computed: {
+            y: {
+              handler: (vm) => vm.z * 2,
+              deep: (vm) => vm.x * 2
+            }
+          }
+        };
+      </script>
+      `,
+      errors: ["'x' of property found, but never used."]
     }
   ]
 })
diff --git a/tests/lib/rules/v-on-function-call.js b/tests/lib/rules/v-on-function-call.js
index 4ba6f5b0..2dee605d 100644
--- a/tests/lib/rules/v-on-function-call.js
+++ b/tests/lib/rules/v-on-function-call.js
@@ -117,7 +117,7 @@ tester.run('v-on-function-call', rule, {
     {
       filename: 'test.vue',
       code: '<template><div @click="foo"></div></template>',
-      output: `<template><div @click="foo"></div></template>`,
+      output: null,
       errors: [
         "Method calls inside of 'v-on' directives must have parentheses."
       ],
@@ -246,6 +246,15 @@ tester.run('v-on-function-call', rule, {
         "Method calls without arguments inside of 'v-on' directives must not have parentheses."
       ],
       options: ['never']
+    },
+    {
+      filename: 'test.vue',
+      code: '<template>\r\n<div\r\n@click="foo()" /></template>',
+      output: '<template>\r\n<div\r\n@click="foo" /></template>',
+      errors: [
+        "Method calls without arguments inside of 'v-on' directives must not have parentheses."
+      ],
+      options: ['never']
     }
   ]
 })
diff --git a/tests/lib/rules/valid-v-slot.js b/tests/lib/rules/valid-v-slot.js
index 0043756f..08b55224 100644
--- a/tests/lib/rules/valid-v-slot.js
+++ b/tests/lib/rules/valid-v-slot.js
@@ -84,6 +84,29 @@ tester.run('valid-v-slot', rule, {
         <template v-for="(key, value) in xxxx" #[key]>{{value}}</template>
       </MyComponent>
     </template>`,
+    {
+      code: `
+        <template>
+          <MyComponent>
+            <template v-slot:foo.bar></template>
+          </MyComponent>
+        </template>
+      `,
+      options: [{ allowModifiers: true }]
+    },
+    {
+      code: `
+        <template>
+          <MyComponent>
+            <template v-slot:foo></template>
+            <template v-slot:foo.bar></template>
+            <template v-slot:foo.baz></template>
+            <template v-slot:foo.bar.baz></template>
+          </MyComponent>
+        </template>
+      `,
+      options: [{ allowModifiers: true }]
+    },
     // parsing error
     {
       filename: 'parsing-error.vue',
@@ -259,6 +282,18 @@ tester.run('valid-v-slot', rule, {
       `,
       errors: [{ messageId: 'disallowDuplicateSlotsOnChildren' }]
     },
+    {
+      code: `
+        <template>
+          <MyComponent>
+            <template v-slot:foo.bar></template>
+            <template v-slot:foo.bar></template>
+          </MyComponent>
+        </template>
+      `,
+      options: [{ allowModifiers: true }],
+      errors: [{ messageId: 'disallowDuplicateSlotsOnChildren' }]
+    },
 
     // Verify arguments.
     {
@@ -291,6 +326,56 @@ tester.run('valid-v-slot', rule, {
       `,
       errors: [{ messageId: 'disallowAnyModifier' }]
     },
+    {
+      code: `
+        <template>
+          <MyComponent>
+            <template v-slot:foo.bar></template>
+          </MyComponent>
+        </template>
+      `,
+      errors: [{ messageId: 'disallowAnyModifier' }]
+    },
+    {
+      code: `
+        <template>
+          <MyComponent>
+            <template v-slot.foo></template>
+          </MyComponent>
+        </template>
+      `,
+      errors: [{ messageId: 'disallowAnyModifier' }],
+      options: [{ allowModifiers: true }]
+    },
+    {
+      code: `
+        <template>
+          <MyComponent>
+            <template v-slot:foo.bar></template>
+          </MyComponent>
+        </template>
+      `,
+      errors: [{ messageId: 'disallowAnyModifier' }],
+      options: [{ allowModifiers: false }]
+    },
+    {
+      code: `
+        <template>
+          <MyComponent>
+            <template v-slot:foo></template>
+            <template v-slot:foo.bar></template>
+            <template v-slot:foo.baz></template>
+            <template v-slot:foo.bar.baz></template>
+          </MyComponent>
+        </template>
+      `,
+      options: [{ allowModifiers: false }],
+      errors: [
+        { messageId: 'disallowAnyModifier' },
+        { messageId: 'disallowAnyModifier' },
+        { messageId: 'disallowAnyModifier' }
+      ]
+    },
 
     // Verify value.
     {