Repository: vuejs/eslint-plugin-vue
Pull request #1820 (2022-03-18 04:55:44)

Add new rule: no-restricted-html-elements
Hi there! I would like to add a new rule to the plugin inspired by the [React forbid-elements rule](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-elements.md). I'm trying to solve a problem with a large project we're working on at work where developers aren't using our Button component from our component library, but are instead are using the button html element. I would like to forbid use of the button html tag altogether to encourage adoption, so I would like to add:

```
{
  "vue/html-forbid-elements": ["error", { "forbid": ["button"] }]
}
```

to our eslintrc file and then emit linting errors when new button tags are added. I will likely also use this for anchor tags to encourage use of our Link component.

Another use case might be to restrict use of deprecated html elements, like marquee.


Patch:
diff --git a/.markdownlint.yml b/.markdownlint.yml
new file mode 100644
index 00000000..cb76e21d
--- /dev/null
+++ b/.markdownlint.yml
@@ -0,0 +1,23 @@
+line-length: false
+single-title: false
+no-inline-html:
+  allowed_elements:
+    - badge
+    - eslint-code-block
+    - sup
+
+# enforce consistency
+code-block-style:
+  style: fenced
+code-fence-style:
+  style: backtick
+emphasis-style:
+  style: asterisk
+heading-style:
+  style: atx
+hr-style:
+  style: ---
+strong-style:
+  style: asterisk
+ul-style:
+  style: dash
diff --git a/.markdownlintignore b/.markdownlintignore
new file mode 100644
index 00000000..3c3629e6
--- /dev/null
+++ b/.markdownlintignore
@@ -0,0 +1,1 @@
+node_modules
diff --git a/docs/rules/README.md b/docs/rules/README.md
index 3d4a9b2e..b4ebfefd 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -12,7 +12,6 @@ sidebarDepth: 0
   :bulb: Indicates that some problems reported by the rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
 :::
 
-
 ## Base Rules (Enabling Correct ESLint Parsing)
 
 Enforce all the rules in this category, as well as all higher priority rules, with:
@@ -335,6 +334,7 @@ For example:
 | [vue/no-restricted-class](./no-restricted-class.md) | disallow specific classes in Vue components |  |
 | [vue/no-restricted-component-options](./no-restricted-component-options.md) | disallow specific component option |  |
 | [vue/no-restricted-custom-event](./no-restricted-custom-event.md) | disallow specific custom event | :bulb: |
+| [vue/no-restricted-html-elements](./no-restricted-html-elements.md) | disallow specific HTML elements |  |
 | [vue/no-restricted-props](./no-restricted-props.md) | disallow specific props | :bulb: |
 | [vue/no-restricted-static-attribute](./no-restricted-static-attribute.md) | disallow specific attribute |  |
 | [vue/no-restricted-v-bind](./no-restricted-v-bind.md) | disallow specific argument in `v-bind` |  |
@@ -353,6 +353,7 @@ For example:
 | [vue/no-v-text](./no-v-text.md) | disallow use of v-text |  |
 | [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: |
 | [vue/prefer-import-from-vue](./prefer-import-from-vue.md) | enforce import from 'vue' instead of import from '@vue/*' | :wrench: |
+| [vue/prefer-prop-type-boolean-first](./prefer-prop-type-boolean-first.md) | enforce `Boolean` comes first in component prop types | :bulb: |
 | [vue/prefer-separate-static-class](./prefer-separate-static-class.md) | require static class names in template to be in a separate `class` attribute | :wrench: |
 | [vue/prefer-true-attribute-shorthand](./prefer-true-attribute-shorthand.md) | require shorthand form attribute when `v-bind` value is `true` | :bulb: |
 | [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported |  |
diff --git a/docs/rules/custom-event-name-casing.md b/docs/rules/custom-event-name-casing.md
index ecc130fb..a2c4d364 100644
--- a/docs/rules/custom-event-name-casing.md
+++ b/docs/rules/custom-event-name-casing.md
@@ -163,6 +163,11 @@ export default {
 
 </eslint-code-block>
 
+## :couple: Related Rules
+
+- [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md)
+- [vue/prop-name-casing](./prop-name-casing.md)
+
 ## :books: Further Reading
 
 - [Guide - Custom Events]
@@ -171,11 +176,6 @@ export default {
 [Guide - Custom Events]: https://vuejs.org/guide/components/events.html
 [Guide (for v2) - Custom Events]: https://v2.vuejs.org/v2/guide/components-custom-events.html
 
-## :couple: Related Rules
-
-- [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md)
-- [vue/prop-name-casing](./prop-name-casing.md)
-
 ## :rocket: Version
 
 This rule was introduced in eslint-plugin-vue v7.0.0
diff --git a/docs/rules/html-closing-bracket-newline.md b/docs/rules/html-closing-bracket-newline.md
index df3a8ac3..ac6c505d 100644
--- a/docs/rules/html-closing-bracket-newline.md
+++ b/docs/rules/html-closing-bracket-newline.md
@@ -66,11 +66,11 @@ This rule aims to warn the right angle brackets which are at the location other
 ```
 
 - `singleline` ... the configuration for single-line elements. It's a single-line element if the element does not have attributes or the last attribute is on the same line as the opening bracket.
-    - `"never"` (default) ... disallow line breaks before the closing bracket.
-    - `"always"` ... require one line break before the closing bracket.
+  - `"never"` (default) ... disallow line breaks before the closing bracket.
+  - `"always"` ... require one line break before the closing bracket.
 - `multiline` ... the configuration for multiline elements. It's a multiline element if the last attribute is not on the same line of the opening bracket.
-    - `"never"` ... disallow line breaks before the closing bracket.
-    - `"always"` (default) ... require one line break before the closing bracket.
+  - `"never"` ... disallow line breaks before the closing bracket.
+  - `"always"` (default) ... require one line break before the closing bracket.
 
 Plus, you can use [`vue/html-indent`](./html-indent.md) rule to enforce indent-level of the closing brackets.
 
diff --git a/docs/rules/html-closing-bracket-spacing.md b/docs/rules/html-closing-bracket-spacing.md
index ee2a608d..6bbbff21 100644
--- a/docs/rules/html-closing-bracket-spacing.md
+++ b/docs/rules/html-closing-bracket-spacing.md
@@ -55,14 +55,14 @@ This rule aims to enforce consistent spacing style before closing brackets `>` o
 ```
 
 - `startTag` (`"always" | "never"`) ... Setting for the `>` of start tags (e.g. `<div>`). Default is `"never"`.
-    - `"always"` ... requires one or more spaces.
-    - `"never"` ... disallows spaces.
+  - `"always"` ... requires one or more spaces.
+  - `"never"` ... disallows spaces.
 - `endTag` (`"always" | "never"`) ... Setting for the `>` of end tags (e.g. `</div>`). Default is `"never"`.
-    - `"always"` ... requires one or more spaces.
-    - `"never"` ... disallows spaces.
+  - `"always"` ... requires one or more spaces.
+  - `"never"` ... disallows spaces.
 - `selfClosingTag` (`"always" | "never"`) ... Setting for the `/>` of self-closing tags (e.g. `<div/>`). Default is `"always"`.
-    - `"always"` ... requires one or more spaces.
-    - `"never"` ... disallows spaces.
+  - `"always"` ... requires one or more spaces.
+  - `"never"` ... disallows spaces.
 
 ### `"startTag": "always", "endTag": "always", "selfClosingTag": "always"`
 
diff --git a/docs/rules/html-comment-content-newline.md b/docs/rules/html-comment-content-newline.md
index eb543a75..807ecb5d 100644
--- a/docs/rules/html-comment-content-newline.md
+++ b/docs/rules/html-comment-content-newline.md
@@ -56,21 +56,21 @@ This rule will enforce consistency of line break after the `<!--` and before the
 ```
 
 - The first option is either an object with `"singleline"` and `"multiline"` keys.
-    - `singleline` ... the configuration for single-line comments.
-        - `"never"` (default) ... disallow line breaks after the `<!--` and before the `-->`.
-        - `"always"` ... require one line break after the `<!--` and before the `-->`.
-    - `multiline` ... the configuration for multiline comments.
-        - `"never"` ... disallow line breaks after the `<!--` and before the `-->`.
-        - `"always"` (default) ... require one line break after the `<!--` and before the `-->`.
+  - `singleline` ... the configuration for single-line comments.
+    - `"never"` (default) ... disallow line breaks after the `<!--` and before the `-->`.
+    - `"always"` ... require one line break after the `<!--` and before the `-->`.
+  - `multiline` ... the configuration for multiline comments.
+    - `"never"` ... disallow line breaks after the `<!--` and before the `-->`.
+    - `"always"` (default) ... require one line break after the `<!--` and before the `-->`.
 
-    You can also set the same value for both `singleline` and `multiline` by specifies a string.
+  You can also set the same value for both `singleline` and `multiline` by specifies a string.
 
 - This rule can also take a 2nd option, an object with the following key: `"exceptions"`.
-    - The `"exceptions"` value is an array of string patterns which are considered exceptions to the rule.
+  - The `"exceptions"` value is an array of string patterns which are considered exceptions to the rule.
 
-    ```json
-    "vue/html-comment-content-newline": ["error", { ... }, { "exceptions": ["*"] }]
-    ```
+  ```json
+  "vue/html-comment-content-newline": ["error", { ... }, { "exceptions": ["*"] }]
+  ```
 
 ### `"always"`
 
@@ -162,7 +162,6 @@ This rule will enforce consistency of line break after the `<!--` and before the
 
 </eslint-code-block>
 
-
 ### `{"singleline": "ignore", "multiline": "always"}`
 
 <eslint-code-block fix :rules="{'vue/html-comment-content-newline': ['error', { 'singleline': 'ignore', 'multiline': 'always' }]}">
diff --git a/docs/rules/html-comment-content-spacing.md b/docs/rules/html-comment-content-spacing.md
index 70d6d78a..e5e5b4cf 100644
--- a/docs/rules/html-comment-content-spacing.md
+++ b/docs/rules/html-comment-content-spacing.md
@@ -48,17 +48,16 @@ Whitespace after the `<!--` and before the `-->` makes it easier to read text in
 ```
 
 - The first is a string which be either `"always"` or `"never"`. The default is `"always"`.
-    - `"always"` (default) ... there must be at least one whitespace at after the `<!--` and before the `-->`.
-    - `"never"` ... there should be no whitespace at after the `<!--` and before the `-->`.
-
+  - `"always"` (default) ... there must be at least one whitespace at after the `<!--` and before the `-->`.
+  - `"never"` ... there should be no whitespace at after the `<!--` and before the `-->`.
 
 - This rule can also take a 2nd option, an object with the following key: `"exceptions"`.
-    - The `"exceptions"` value is an array of string patterns which are considered exceptions to the rule.
-    Please note that exceptions are ignored if the first argument is `"never"`.
+  - The `"exceptions"` value is an array of string patterns which are considered exceptions to the rule.
+  Please note that exceptions are ignored if the first argument is `"never"`.
 
-    ```json
-    "vue/html-comment-content-spacing": ["error", "always", { "exceptions": ["*"] }]
-    ```
+  ```json
+  "vue/html-comment-content-spacing": ["error", "always", { "exceptions": ["*"] }]
+  ```
 
 ### `"always"`
 
diff --git a/docs/rules/max-len.md b/docs/rules/max-len.md
index 19663f16..deca2e72 100644
--- a/docs/rules/max-len.md
+++ b/docs/rules/max-len.md
@@ -112,7 +112,6 @@ var foo = ['line', 'length', 'is', '50', '......']
 
 </eslint-code-block>
 
-
 ### `"template": 120`
 
 <eslint-code-block :rules="{'vue/max-len': ['error', {template: 120}]}">
diff --git a/docs/rules/new-line-between-multi-line-property.md b/docs/rules/new-line-between-multi-line-property.md
index 4af4e59c..c8cee19e 100644
--- a/docs/rules/new-line-between-multi-line-property.md
+++ b/docs/rules/new-line-between-multi-line-property.md
@@ -75,7 +75,7 @@ export default {
 
 </eslint-code-block>
 
-## :wrench: Option
+## :wrench: Options
 
 ```json
 {
diff --git a/docs/rules/next-tick-style.md b/docs/rules/next-tick-style.md
index 683d5d92..077699e8 100644
--- a/docs/rules/next-tick-style.md
+++ b/docs/rules/next-tick-style.md
@@ -46,6 +46,7 @@ export default {
 </eslint-code-block>
 
 ## :wrench: Options
+
 Default is set to `promise`.
 
 ```json
diff --git a/docs/rules/no-bare-strings-in-template.md b/docs/rules/no-bare-strings-in-template.md
index f462ca21..adf12bff 100644
--- a/docs/rules/no-bare-strings-in-template.md
+++ b/docs/rules/no-bare-strings-in-template.md
@@ -16,7 +16,6 @@ In order to be able to internationalize your application, you will need to avoid
 
 This rule was inspired by [no-bare-strings rule in ember-template-lint](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-bare-strings.md).
 
-
 <eslint-code-block :rules="{'vue/no-bare-strings-in-template': ['error']}">
 
 ```vue
diff --git a/docs/rules/no-boolean-default.md b/docs/rules/no-boolean-default.md
index f9571df8..d0782c60 100644
--- a/docs/rules/no-boolean-default.md
+++ b/docs/rules/no-boolean-default.md
@@ -13,8 +13,8 @@ since: v7.0.0
 
 The rule prevents Boolean props from having a default value.
 
-
 ## :book: Rule Details
+
 The rule is to enforce the HTML standard of always defaulting boolean attributes to false.
 
 <eslint-code-block fix :rules="{'vue/no-boolean-default': ['error']}">
@@ -38,6 +38,7 @@ export default {
 </eslint-code-block>
 
 ## :wrench: Options
+
 - `'no-default'` (default) allows a prop definition object, but enforces that the `default` property not be defined.
 - `'default-false'` enforces that the default can be set but must be set to `false`.
 
diff --git a/docs/rules/no-custom-modifiers-on-v-model.md b/docs/rules/no-custom-modifiers-on-v-model.md
index 092ee325..673565ca 100644
--- a/docs/rules/no-custom-modifiers-on-v-model.md
+++ b/docs/rules/no-custom-modifiers-on-v-model.md
@@ -11,7 +11,7 @@ since: v7.0.0
 
 - :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
 
-This rule checks whether `v-model `used on the component do not have custom modifiers.
+This rule checks whether `v-model` used on the component do not have custom modifiers.
 
 ## :book: Rule Details
 
diff --git a/docs/rules/no-empty-component-block.md b/docs/rules/no-empty-component-block.md
index 5a15b6d7..7e6104ae 100644
--- a/docs/rules/no-empty-component-block.md
+++ b/docs/rules/no-empty-component-block.md
@@ -14,7 +14,7 @@ since: v7.0.0
 This rule disallows the `<template>` `<script>` `<style>` block to be empty.
 
 This rule also checks block what has attribute `src`.
-See: https://vue-loader.vuejs.org/spec.html#src-imports
+See [Vue Single-File Component (SFC) Spec](https://vue-loader.vuejs.org/spec.html#src-imports).
 
 <eslint-code-block :rules="{'vue/no-empty-component-block': ['error']}">
 
diff --git a/docs/rules/no-invalid-model-keys.md b/docs/rules/no-invalid-model-keys.md
index 615b6985..1a46e9a4 100644
--- a/docs/rules/no-invalid-model-keys.md
+++ b/docs/rules/no-invalid-model-keys.md
@@ -108,7 +108,6 @@ export default {
 
 </eslint-code-block>
 
-
 ## :rocket: Version
 
 This rule was introduced in eslint-plugin-vue v7.9.0
diff --git a/docs/rules/no-parsing-error.md b/docs/rules/no-parsing-error.md
index 811fd06a..61832037 100644
--- a/docs/rules/no-parsing-error.md
+++ b/docs/rules/no-parsing-error.md
@@ -16,10 +16,10 @@ This rule reports syntax errors in `<template>`. For example:
 - Syntax errors of scripts in directives.
 - Syntax errors of scripts in mustaches.
 - Syntax errors of HTML.
-    - Invalid end tags.
-    - Attributes in end tags.
-    - ...
-    - See also: [WHATWG HTML spec](https://html.spec.whatwg.org/multipage/parsing.html#parse-errors)
+  - Invalid end tags.
+  - Attributes in end tags.
+  - ...
+  - See also: [WHATWG HTML spec](https://html.spec.whatwg.org/multipage/parsing.html#parse-errors)
 
 ## :book: Rule Details
 
diff --git a/docs/rules/no-potential-component-option-typo.md b/docs/rules/no-potential-component-option-typo.md
index bcd2d4ed..871af5de 100644
--- a/docs/rules/no-potential-component-option-typo.md
+++ b/docs/rules/no-potential-component-option-typo.md
@@ -15,7 +15,7 @@ since: v7.0.0
 
 This rule disallow a potential typo in your component options
 
-**Here is the config**
+### Here is the config
 
 ```json
 {
@@ -59,7 +59,7 @@ export default {
 
 > we use edit distance to compare two string similarity, threshold is an option to control upper bound of edit distance to report
 
-**Here is the another example about config option `threshold`**
+### Here is the another example about config option `threshold`
 
 ```json
 {
diff --git a/docs/rules/no-restricted-class.md b/docs/rules/no-restricted-class.md
index c4919ebd..d67e5d8b 100644
--- a/docs/rules/no-restricted-class.md
+++ b/docs/rules/no-restricted-class.md
@@ -70,6 +70,7 @@ export default {
 }
 </script>
 ```
+
 :::
 
 ## :rocket: Version
diff --git a/docs/rules/no-restricted-custom-event.md b/docs/rules/no-restricted-custom-event.md
index f18edc3f..bc48def0 100644
--- a/docs/rules/no-restricted-custom-event.md
+++ b/docs/rules/no-restricted-custom-event.md
@@ -53,7 +53,6 @@ export default {
 
 </eslint-code-block>
 
-
 Alternatively, the rule also accepts objects.
 
 ```json
diff --git a/docs/rules/no-restricted-html-elements.md b/docs/rules/no-restricted-html-elements.md
new file mode 100644
index 00000000..780708d6
--- /dev/null
+++ b/docs/rules/no-restricted-html-elements.md
@@ -0,0 +1,96 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-restricted-html-elements
+description: disallow specific HTML elements
+---
+# vue/no-restricted-html-elements
+
+> disallow specific HTML elements
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+
+## :book: Rule Details
+
+This rule allows you to specify HTML elements that you don't want to use in your application.
+
+<eslint-code-block :rules="{'vue/no-restricted-html-elements': ['error', 'marquee', 'button'] }">
+
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <p></p>
+  <input />
+  <br />
+
+  <!-- ✗ BAD -->
+  <button></button>
+  <marquee></marquee>
+</template>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+This rule takes a list of strings, where each string is an HTML element name to be restricted:
+
+```json
+{
+  "vue/no-restricted-html-elements": ["error", "button", "marquee"]
+}
+```
+
+<eslint-code-block :rules="{'vue/no-restricted-html-elements': ['error', 'button', 'marquee']}">
+
+```vue
+<template>
+  <!-- ✗ BAD -->
+  <button></button>
+  <marquee></marquee>
+</template>
+```
+
+</eslint-code-block>
+
+Alternatively, the rule also accepts objects.
+
+```json
+{
+  "vue/no-restricted-html-elements": [
+    "error",
+    {
+      "element": "button",
+      "message": "Prefer use of our custom <AppButton /> component"
+    },
+    {
+      "element": "marquee",
+      "message": "Do not use deprecated HTML tags"
+    }
+  ]
+}
+```
+
+The following properties can be specified for the object.
+
+- `element` ... Specify the html element.
+- `message` ... Specify an optional custom message.
+
+### `{ "element": "marquee" }, { "element": "button" }`
+
+<eslint-code-block :rules="{'vue/no-restricted-html-elements': ['error', { element: 'marquee' }, { element: 'button' }]}">
+
+```vue
+<template>
+  <!-- ✗ BAD -->
+  <marquee></marquee>
+  <button></button>
+</template>
+```
+
+</eslint-code-block>
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-restricted-html-elements.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-restricted-html-elements.js)
diff --git a/docs/rules/no-restricted-syntax.md b/docs/rules/no-restricted-syntax.md
index 958bbad8..dcde6897 100644
--- a/docs/rules/no-restricted-syntax.md
+++ b/docs/rules/no-restricted-syntax.md
@@ -11,7 +11,6 @@ since: v5.2.0
 
 This rule is the same rule as core [no-restricted-syntax] rule but it applies to the expressions in `<template>`.
 
-
 ## :wrench: Options
 
 Please see [no-restricted-syntax] for detailed options.
diff --git a/docs/rules/no-spaces-around-equal-signs-in-attribute.md b/docs/rules/no-spaces-around-equal-signs-in-attribute.md
index f13feee2..bb3a7ff6 100644
--- a/docs/rules/no-spaces-around-equal-signs-in-attribute.md
+++ b/docs/rules/no-spaces-around-equal-signs-in-attribute.md
@@ -43,7 +43,7 @@ HTML5 allows spaces around equal signs. But space-less is easier to read, and gr
 
 ## :books: Further Reading
 
-* [HTML5 Style Guide - W3Schools *Spaces and Equal Signs*](https://www.w3schools.com/html/html5_syntax.asp)
+- [HTML5 Style Guide - W3Schools *Spaces and Equal Signs*](https://www.w3schools.com/html/html5_syntax.asp)
 
 ## :rocket: Version
 
diff --git a/docs/rules/no-textarea-mustache.md b/docs/rules/no-textarea-mustache.md
index 4c26b6bf..3676d4a0 100644
--- a/docs/rules/no-textarea-mustache.md
+++ b/docs/rules/no-textarea-mustache.md
@@ -29,6 +29,7 @@ This rule reports mustaches in `<textarea>`.
 
 </eslint-code-block>
 
+<!-- markdownlint-disable-next-line no-inline-html -->
 <div v-pre>
 
 ::: warning Note
diff --git a/docs/rules/no-this-in-before-route-enter.md b/docs/rules/no-this-in-before-route-enter.md
index 2871a617..0771bbb9 100644
--- a/docs/rules/no-this-in-before-route-enter.md
+++ b/docs/rules/no-this-in-before-route-enter.md
@@ -11,7 +11,7 @@ since: v7.11.0
 
 ## :book: Rule Details
 
-Because lack of `this` in the `beforeRouteEnter` [(docs)](https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards). This behavior isn't obvious, so it's pretty easy to make a `TypeError`. Especially during some refactor. 
+Because lack of `this` in the `beforeRouteEnter` [(docs)](https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards). This behavior isn't obvious, so it's pretty easy to make a `TypeError`. Especially during some refactor.
 
 <eslint-code-block :rules="{'vue/no-this-in-before-route-enter': ['error']}">
 
diff --git a/docs/rules/no-unregistered-components.md b/docs/rules/no-unregistered-components.md
index 761431cf..16b5c8a1 100644
--- a/docs/rules/no-unregistered-components.md
+++ b/docs/rules/no-unregistered-components.md
@@ -16,8 +16,8 @@ since: v7.0.0
 This rule reports components that haven't been registered and are being used in the template.
 
 ::: warning Note
-This rule cannot check globally registered components and components registered in mixins 
-unless you add them as part of the ignored patterns. `component`, `suspense` and `teleport` 
+This rule cannot check globally registered components and components registered in mixins
+unless you add them as part of the ignored patterns. `component`, `suspense` and `teleport`
 are ignored by default.
 :::
 
diff --git a/docs/rules/no-use-v-if-with-v-for.md b/docs/rules/no-use-v-if-with-v-for.md
index b8de1a17..62656cd2 100644
--- a/docs/rules/no-use-v-if-with-v-for.md
+++ b/docs/rules/no-use-v-if-with-v-for.md
@@ -16,8 +16,9 @@ since: v4.6.0
 This rule is aimed at preventing the use of `v-for` directives together with `v-if` directives on the same element.
 
 There are two common cases where this can be tempting:
- * To filter items in a list (e.g. `v-for="user in users" v-if="user.isActive"`). In these cases, replace `users` with a new computed property that returns your filtered list (e.g. `activeUsers`).
- * To avoid rendering a list if it should be hidden (e.g. `v-for="user in users" v-if="shouldShowUsers"`). In these cases, move the `v-if` to a container element (e.g. `ul`, `ol`).
+
+- To filter items in a list (e.g. `v-for="user in users" v-if="user.isActive"`). In these cases, replace `users` with a new computed property that returns your filtered list (e.g. `activeUsers`).
+- To avoid rendering a list if it should be hidden (e.g. `v-for="user in users" v-if="shouldShowUsers"`). In these cases, move the `v-if` to a container element (e.g. `ul`, `ol`).
 
 <eslint-code-block :rules="{'vue/no-use-v-if-with-v-for': ['error']}">
 
diff --git a/docs/rules/no-v-model-argument.md b/docs/rules/no-v-model-argument.md
index 4571893c..d020bbba 100644
--- a/docs/rules/no-v-model-argument.md
+++ b/docs/rules/no-v-model-argument.md
@@ -34,7 +34,6 @@ This rule reports `v-model` directives in the following cases:
 
 </eslint-code-block>
 
-
 ## :wrench: Options
 
 Nothing.
diff --git a/docs/rules/order-in-components.md b/docs/rules/order-in-components.md
index 99d71421..f8274810 100644
--- a/docs/rules/order-in-components.md
+++ b/docs/rules/order-in-components.md
@@ -110,7 +110,6 @@ export default {
 
   If an element is an array of strings, it means any of those can be placed there unordered. Default is above.
 
-
 ## :books: Further Reading
 
 - [Style guide - Component/instance options order](https://vuejs.org/style-guide/rules-recommended.html#component-instance-options-order)
diff --git a/docs/rules/padding-line-between-blocks.md b/docs/rules/padding-line-between-blocks.md
index 7e1e058e..63630895 100644
--- a/docs/rules/padding-line-between-blocks.md
+++ b/docs/rules/padding-line-between-blocks.md
@@ -134,7 +134,6 @@ export default {}
 [padding-line-between-statements]: https://eslint.org/docs/rules/padding-line-between-statements
 [lines-between-class-members]: https://eslint.org/docs/rules/lines-between-class-members
 
-
 ## :rocket: Version
 
 This rule was introduced in eslint-plugin-vue v6.2.0
diff --git a/docs/rules/prefer-prop-type-boolean-first.md b/docs/rules/prefer-prop-type-boolean-first.md
new file mode 100644
index 00000000..3bf2edb4
--- /dev/null
+++ b/docs/rules/prefer-prop-type-boolean-first.md
@@ -0,0 +1,60 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/prefer-prop-type-boolean-first
+description: enforce `Boolean` comes first in component prop types
+---
+# vue/prefer-prop-type-boolean-first
+
+> enforce `Boolean` comes first in component prop types
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
+## :book: Rule Details
+
+When declaring types of a property in component, we can use array style to accept multiple types.
+
+When using components in template,
+we can use shorthand-style property if its value is `true`.
+
+However, if a property allows `Boolean` or `String` and we use it with shorthand form in somewhere else,
+different types order can introduce different behaviors:
+If `Boolean` comes first, it will be `true`; if `String` comes first, it will be `""` (empty string).
+
+See [this demo](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCBNeUNvbXBvbmVudCBmcm9tICcuL015Q29tcG9uZW50LnZ1ZSdcbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIFNob3J0aGFuZCBmb3JtOlxuICA8TXlDb21wb25lbnQgYm9vbCBib29sLW9yLXN0cmluZyBzdHJpbmctb3ItYm9vbCAvPlxuICBcbiAgTG9uZ2hhbmQgZm9ybTpcbiAgPE15Q29tcG9uZW50IDpib29sPVwidHJ1ZVwiIDpib29sLW9yLXN0cmluZz1cInRydWVcIiA6c3RyaW5nLW9yLWJvb2w9XCJ0cnVlXCIgLz5cbjwvdGVtcGxhdGU+IiwiaW1wb3J0LW1hcC5qc29uIjoie1xuICBcImltcG9ydHNcIjoge1xuICAgIFwidnVlXCI6IFwiaHR0cHM6Ly9zZmMudnVlanMub3JnL3Z1ZS5ydW50aW1lLmVzbS1icm93c2VyLmpzXCJcbiAgfVxufSIsIk15Q29tcG9uZW50LnZ1ZSI6IjxzY3JpcHQ+XG5leHBvcnQgZGVmYXVsdCB7XG4gIHByb3BzOiB7XG4gICAgYm9vbDogQm9vbGVhbixcbiAgICBib29sT3JTdHJpbmc6IFtCb29sZWFuLCBTdHJpbmddLFxuICAgIHN0cmluZ09yQm9vbDogW1N0cmluZywgQm9vbGVhbl0sXG4gIH1cbn1cbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIDxwcmU+XG5ib29sOiB7e2Jvb2x9fSAoe3sgdHlwZW9mIGJvb2wgfX0pXG5ib29sT3JTdHJpbmc6IHt7Ym9vbE9yU3RyaW5nfX0gKHt7IHR5cGVvZiBib29sT3JTdHJpbmcgfX0pXG5zdHJpbmdPckJvb2w6IHt7c3RyaW5nT3JCb29sfX0gKHt7IHR5cGVvZiBzdHJpbmdPckJvb2wgfX0pXG4gIDwvcHJlPlxuPC90ZW1wbGF0ZT4ifQ==).
+
+<eslint-code-block :rules="{'vue/prefer-prop-type-boolean-first': ['error']}">
+
+```vue
+<script>
+export default {
+  props: {
+    // ✓ GOOD
+    a: Boolean,
+    b: String,
+    c: [Boolean, String],
+    d: {
+      type: [Boolean, String]
+    },
+
+    // ✗ BAD
+    e: [String, Boolean],
+    f: {
+      type: [String, Boolean]
+    }
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+## :couple: Related Rules
+
+- [vue/prefer-true-attribute-shorthand](./prefer-true-attribute-shorthand.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prefer-prop-type-boolean-first.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prefer-prop-type-boolean-first.js)
diff --git a/docs/rules/prefer-true-attribute-shorthand.md b/docs/rules/prefer-true-attribute-shorthand.md
index 22c2c4f7..c64f9a8c 100644
--- a/docs/rules/prefer-true-attribute-shorthand.md
+++ b/docs/rules/prefer-true-attribute-shorthand.md
@@ -54,7 +54,7 @@ export default {
 <MyComponent bool bool-or-string string-or-bool />
 ```
 
-```
+```plain
 bool: true (boolean)
 boolOrString: true (boolean)
 stringOrBool: "" (string)
@@ -66,7 +66,7 @@ stringOrBool: "" (string)
 <MyComponent :bool="true" :bool-or-string="true" :string-or-bool="true" />
 ```
 
-```
+```plain
 bool: true (boolean)
 boolOrString: true (boolean)
 stringOrBool: true (boolean)
@@ -107,6 +107,7 @@ Default options is `"always"`.
 ## :couple: Related Rules
 
 - [vue/no-boolean-default](./no-boolean-default.md)
+- [vue/prefer-prop-type-boolean-first](./prefer-prop-type-boolean-first.md)
 
 ## :rocket: Version
 
diff --git a/docs/rules/prop-name-casing.md b/docs/rules/prop-name-casing.md
index 2cdeec52..4f32bfa1 100644
--- a/docs/rules/prop-name-casing.md
+++ b/docs/rules/prop-name-casing.md
@@ -66,15 +66,15 @@ export default {
 
 </eslint-code-block>
 
-## :books: Further Reading
-
-- [Style guide - Prop name casing](https://vuejs.org/style-guide/rules-strongly-recommended.html#prop-name-casing)
-
 ## :couple: Related Rules
 
 - [vue/attribute-hyphenation](./attribute-hyphenation.md)
 - [vue/custom-event-name-casing](./custom-event-name-casing.md)
 
+## :books: Further Reading
+
+- [Style guide - Prop name casing](https://vuejs.org/style-guide/rules-strongly-recommended.html#prop-name-casing)
+
 ## :rocket: Version
 
 This rule was introduced in eslint-plugin-vue v4.3.0
diff --git a/docs/rules/require-component-is.md b/docs/rules/require-component-is.md
index 767e9a93..5ced4d87 100644
--- a/docs/rules/require-component-is.md
+++ b/docs/rules/require-component-is.md
@@ -15,7 +15,6 @@ since: v3.0.0
 
 This rule reports the `<component>` elements which do not have `v-bind:is` attributes.
 
-
 <eslint-code-block :rules="{'vue/require-component-is': ['error']}">
 
 ```vue
@@ -36,7 +35,6 @@ This rule reports the `<component>` elements which do not have `v-bind:is` attri
 You can use the same mount point and dynamically switch between multiple components using the reserved `<component>` element and dynamically bind to its `is` attribute.
 :::
 
-
 ## :wrench: Options
 
 Nothing.
diff --git a/docs/rules/require-default-prop.md b/docs/rules/require-default-prop.md
index 4d97e16c..44fba0f2 100644
--- a/docs/rules/require-default-prop.md
+++ b/docs/rules/require-default-prop.md
@@ -61,14 +61,14 @@ export default {
 
 Nothing.
 
-## :books: Further Reading
-
-- [Style guide - Prop definitions](https://vuejs.org/style-guide/rules-essential.html#use-detailed-prop-definitions)
-
 ## :couple: Related Rules
 
 - [vue/no-boolean-default](./no-boolean-default.md)
 
+## :books: Further Reading
+
+- [Style guide - Prop definitions](https://vuejs.org/style-guide/rules-essential.html#use-detailed-prop-definitions)
+
 ## :rocket: Version
 
 This rule was introduced in eslint-plugin-vue v3.13.0
diff --git a/docs/rules/return-in-computed-property.md b/docs/rules/return-in-computed-property.md
index d18a1b27..01ac6326 100644
--- a/docs/rules/return-in-computed-property.md
+++ b/docs/rules/return-in-computed-property.md
@@ -90,6 +90,7 @@ export default {
 ```
 
 This rule has an object option:
+
 - `"treatUndefinedAsUnspecified"`: `true` (default) disallows implicitly returning undefined with a `return` statement.
 
 ### `treatUndefinedAsUnspecified: false`
diff --git a/docs/rules/script-indent.md b/docs/rules/script-indent.md
index d4e7d54e..4cf86504 100644
--- a/docs/rules/script-indent.md
+++ b/docs/rules/script-indent.md
@@ -84,6 +84,7 @@ This rule only checks `.vue` files and does not interfere with other `.js` files
 ```
 
 ### `2, "baseIndent": 1`
+
 <eslint-code-block fix :rules="{'vue/script-indent': ['error', 2, { 'baseIndent': 1 }]}">
 
 ```vue
diff --git a/docs/rules/singleline-html-element-content-newline.md b/docs/rules/singleline-html-element-content-newline.md
index d9c2e0be..8e6dadf3 100644
--- a/docs/rules/singleline-html-element-content-newline.md
+++ b/docs/rules/singleline-html-element-content-newline.md
@@ -16,7 +16,6 @@ since: v5.0.0
 
 This rule enforces a line break before and after the contents of a singleline element.
 
-
 <eslint-code-block fix :rules="{'vue/singleline-html-element-content-newline': ['error']}">
 
 ```vue
@@ -69,12 +68,10 @@ This rule enforces a line break before and after the contents of a singleline el
 - `ignores` ... the configuration for element names to ignore line breaks style.
     default `["pre", "textarea", ...INLINE_ELEMENTS]`.
 
-
 ::: info
   All inline non void elements can be found [here](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/utils/inline-non-void-elements.json).
 :::
 
-
 ### `"ignoreWhenNoAttributes": true`
 
 <eslint-code-block fix :rules="{'vue/singleline-html-element-content-newline': ['error', {'ignoreWhenNoAttributes': true}]}">
diff --git a/docs/rules/sort-keys.md b/docs/rules/sort-keys.md
index b573e4ea..26e4e683 100644
--- a/docs/rules/sort-keys.md
+++ b/docs/rules/sort-keys.md
@@ -11,35 +11,6 @@ since: v6.2.0
 
 This rule is almost the same rule as core [sort-keys] rule but it will not error on top component properties allowing that order to be enforced with `order-in-components`.
 
-## :wrench: Options
-
-```json
-{
-    "vue/sort-keys": ["error", "asc", {
-        "caseSensitive": true,
-        "ignoreChildrenOf": ["model"],
-        "ignoreGrandchildrenOf": ["computed", "directives", "inject", "props", "watch"],
-        "minKeys": 2,
-        "natural": false
-  }]
-}
-```
-
-The 1st option is `"asc"` or `"desc"`.
-
-* `"asc"` (default) - enforce properties to be in ascending order.
-* `"desc"` - enforce properties to be in descending order.
-
-The 2nd option is an object which has 5 properties.
-
-* `caseSensitive` - if `true`, enforce properties to be in case-sensitive order. Default is `true`.
-* `ignoreChildrenOf` - an array of properties to ignore the children of.  Default is `["model"]`
-* `ignoreGrandchildrenOf` - an array of properties to ignore the grandchildren sort order. Default is `["computed", "directives", "inject", "props", "watch"]`
-* `minKeys` - Specifies the minimum number of keys that an object should have in order for the object's unsorted keys to produce an error. Default is `2`, which means by default all objects with unsorted keys will result in lint errors.
-* `natural` - if `true`, enforce properties to be in natural order. Default is `false`. Natural Order compares strings containing combination of letters and numbers in the way a human being would sort. It basically sorts numerically, instead of sorting alphabetically. So the number 10 comes after the number 3 in Natural Sorting.
-
-While using this rule, you may disable the normal `sort-keys` rule.  This rule will apply to plain js files as well as Vue component scripts.
-
 ## :book: Rule Details
 
 This rule forces many dictionary properties to be in alphabetical order while allowing `order-in-components`, property details,
@@ -99,6 +70,35 @@ export default {
 
 </eslint-code-block>
 
+## :wrench: Options
+
+```json
+{
+    "vue/sort-keys": ["error", "asc", {
+        "caseSensitive": true,
+        "ignoreChildrenOf": ["model"],
+        "ignoreGrandchildrenOf": ["computed", "directives", "inject", "props", "watch"],
+        "minKeys": 2,
+        "natural": false
+  }]
+}
+```
+
+The 1st option is `"asc"` or `"desc"`.
+
+- `"asc"` (default) - enforce properties to be in ascending order.
+- `"desc"` - enforce properties to be in descending order.
+
+The 2nd option is an object which has 5 properties.
+
+- `caseSensitive` - if `true`, enforce properties to be in case-sensitive order. Default is `true`.
+- `ignoreChildrenOf` - an array of properties to ignore the children of.  Default is `["model"]`
+- `ignoreGrandchildrenOf` - an array of properties to ignore the grandchildren sort order. Default is `["computed", "directives", "inject", "props", "watch"]`
+- `minKeys` - Specifies the minimum number of keys that an object should have in order for the object's unsorted keys to produce an error. Default is `2`, which means by default all objects with unsorted keys will result in lint errors.
+- `natural` - if `true`, enforce properties to be in natural order. Default is `false`. Natural Order compares strings containing combination of letters and numbers in the way a human being would sort. It basically sorts numerically, instead of sorting alphabetically. So the number 10 comes after the number 3 in Natural Sorting.
+
+While using this rule, you may disable the normal `sort-keys` rule.  This rule will apply to plain js files as well as Vue component scripts.
+
 ## :books: Further Reading
 
 - [sort-keys]
diff --git a/docs/rules/this-in-template.md b/docs/rules/this-in-template.md
index f193e7e3..50134fe0 100644
--- a/docs/rules/this-in-template.md
+++ b/docs/rules/this-in-template.md
@@ -41,6 +41,7 @@ This rule aims at preventing usage of `this` in Vue templates.
   "vue/this-in-template": ["error", "always" | "never"]
 }
 ```
+
 - `"always"` ... Always use `this` while accessing properties from Vue.
 - `"never"` (default) ... Never use `this` keyword in expressions.
 
diff --git a/docs/rules/v-bind-style.md b/docs/rules/v-bind-style.md
index 2e69bd62..23be4a8c 100644
--- a/docs/rules/v-bind-style.md
+++ b/docs/rules/v-bind-style.md
@@ -31,6 +31,7 @@ This rule enforces `v-bind` directive style which you should use shorthand or lo
 </eslint-code-block>
 
 ## :wrench: Options
+
 Default is set to `shorthand`.
 
 ```json
diff --git a/docs/rules/v-for-delimiter-style.md b/docs/rules/v-for-delimiter-style.md
index 7239ef02..7d9b5c07 100644
--- a/docs/rules/v-for-delimiter-style.md
+++ b/docs/rules/v-for-delimiter-style.md
@@ -30,6 +30,7 @@ This rule enforces which delimiter (`in` or `of`) should be used in `v-for` dire
 </eslint-code-block>
 
 ## :wrench: Options
+
 Default is set to `in`.
 
 ```json
diff --git a/docs/rules/v-on-event-hyphenation.md b/docs/rules/v-on-event-hyphenation.md
index fd8b9f48..a462e414 100644
--- a/docs/rules/v-on-event-hyphenation.md
+++ b/docs/rules/v-on-event-hyphenation.md
@@ -103,17 +103,17 @@ Don't use hyphenated name but allow custom event names
 
 </eslint-code-block>
 
+## :couple: Related Rules
+
+- [vue/custom-event-name-casing](./custom-event-name-casing.md)
+- [vue/attribute-hyphenation](./attribute-hyphenation.md)
+
 ## :books: Further Reading
 
 - [Guide - Custom Events]
 
 [Guide - Custom Events]: https://v3.vuejs.org/guide/component-custom-events.html
 
-## :couple: Related Rules
-
-- [vue/custom-event-name-casing](./custom-event-name-casing.md)
-- [vue/attribute-hyphenation](./attribute-hyphenation.md)
-
 ## :rocket: Version
 
 This rule was introduced in eslint-plugin-vue v7.4.0
diff --git a/docs/rules/v-on-style.md b/docs/rules/v-on-style.md
index 17594be6..73e36ce3 100644
--- a/docs/rules/v-on-style.md
+++ b/docs/rules/v-on-style.md
@@ -31,6 +31,7 @@ This rule enforces `v-on` directive style which you should use shorthand or long
 </eslint-code-block>
 
 ## :wrench: Options
+
 Default is set to `shorthand`.
 
 ```json
diff --git a/lib/index.js b/lib/index.js
index 97ff805d..22fe9a18 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -113,6 +113,7 @@ module.exports = {
     'no-restricted-class': require('./rules/no-restricted-class'),
     'no-restricted-component-options': require('./rules/no-restricted-component-options'),
     'no-restricted-custom-event': require('./rules/no-restricted-custom-event'),
+    'no-restricted-html-elements': require('./rules/no-restricted-html-elements'),
     'no-restricted-props': require('./rules/no-restricted-props'),
     'no-restricted-static-attribute': require('./rules/no-restricted-static-attribute'),
     'no-restricted-syntax': require('./rules/no-restricted-syntax'),
@@ -158,6 +159,7 @@ module.exports = {
     'order-in-components': require('./rules/order-in-components'),
     'padding-line-between-blocks': require('./rules/padding-line-between-blocks'),
     'prefer-import-from-vue': require('./rules/prefer-import-from-vue'),
+    'prefer-prop-type-boolean-first': require('./rules/prefer-prop-type-boolean-first'),
     'prefer-separate-static-class': require('./rules/prefer-separate-static-class'),
     'prefer-template': require('./rules/prefer-template'),
     'prefer-true-attribute-shorthand': require('./rules/prefer-true-attribute-shorthand'),
diff --git a/lib/rules/no-restricted-html-elements.js b/lib/rules/no-restricted-html-elements.js
new file mode 100644
index 00000000..b0874f1c
--- /dev/null
+++ b/lib/rules/no-restricted-html-elements.js
@@ -0,0 +1,68 @@
+/**
+ * @author Doug Wade <douglas.b.wade@gmail.com>
+ */
+
+'use strict'
+
+const utils = require('../utils')
+
+module.exports = {
+  meta: {
+    type: 'suggestion',
+    docs: {
+      description: 'disallow specific HTML elements',
+      categories: undefined,
+      url: 'https://eslint.vuejs.org/rules/no-restricted-html-elements.html'
+    },
+    fixable: null,
+    schema: {
+      type: 'array',
+      items: {
+        oneOf: [
+          { type: 'string' },
+          {
+            type: 'object',
+            properties: {
+              element: { type: 'string' },
+              message: { type: 'string', minLength: 1 }
+            },
+            required: ['element'],
+            additionalProperties: false
+          }
+        ]
+      },
+      uniqueItems: true,
+      minItems: 0
+    }
+  },
+  /**
+   * @param {RuleContext} context - The rule context.
+   * @returns {RuleListener} AST event handlers.
+   */
+  create(context) {
+    return utils.defineTemplateBodyVisitor(context, {
+      /**
+       * @param {VElement} node
+       */
+      VElement(node) {
+        if (!utils.isHtmlElementNode(node)) {
+          return
+        }
+
+        context.options.forEach((option) => {
+          const message =
+            option.message ||
+            `Unexpected use of forbidden HTML element ${node.rawName}.`
+          const element = option.element || option
+
+          if (element === node.rawName) {
+            context.report({
+              message,
+              node: node.startTag
+            })
+          }
+        })
+      }
+    })
+  }
+}
diff --git a/lib/rules/prefer-prop-type-boolean-first.js b/lib/rules/prefer-prop-type-boolean-first.js
new file mode 100644
index 00000000..215262d3
--- /dev/null
+++ b/lib/rules/prefer-prop-type-boolean-first.js
@@ -0,0 +1,115 @@
+/**
+ * @author Pig Fang
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+/**
+ * @param {ArrayExpression} node
+ * @param {RuleContext} context
+ */
+function checkArrayExpression(node, context) {
+  const booleanType = node.elements.find(
+    (element) =>
+      element && element.type === 'Identifier' && element.name === 'Boolean'
+  )
+  if (!booleanType) {
+    return
+  }
+  const booleanTypeIndex = node.elements.indexOf(booleanType)
+  if (booleanTypeIndex > 0) {
+    context.report({
+      node: booleanType,
+      messageId: 'shouldBeFirst',
+      suggest: [
+        {
+          messageId: 'moveToFirst',
+          fix: (fixer) => {
+            const sourceCode = context.getSourceCode()
+
+            const elements = node.elements.slice()
+            elements.splice(booleanTypeIndex, 1)
+            const code = elements
+              .filter(utils.isDef)
+              .map((element) => sourceCode.getText(element))
+            code.unshift('Boolean')
+
+            return fixer.replaceText(node, `[${code.join(', ')}]`)
+          }
+        }
+      ]
+    })
+  }
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description: 'enforce `Boolean` comes first in component prop types',
+      categories: undefined,
+      url: 'https://eslint.vuejs.org/rules/prefer-prop-type-boolean-first.html'
+    },
+    fixable: null,
+    // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- `context.report` with suggestion is not recognized in `checkArrayExpression`
+    hasSuggestions: true,
+    schema: [],
+    messages: {
+      shouldBeFirst: 'Type `Boolean` should be at first in prop types.',
+      moveToFirst: 'Move `Boolean` to be first in prop types.'
+    }
+  },
+  /** @param {RuleContext} context */
+  create(context) {
+    /**
+     * @param {import('../utils').ComponentProp} prop
+     */
+    function checkProperty(prop) {
+      const { value } = prop
+      if (!value) {
+        return
+      }
+
+      if (value.type === 'ArrayExpression') {
+        checkArrayExpression(value, context)
+      } else if (value.type === 'ObjectExpression') {
+        const type = value.properties.find(
+          /** @return {property is Property} */
+          (property) =>
+            property.type === 'Property' &&
+            utils.getStaticPropertyName(property) === 'type'
+        )
+        if (!type || type.value.type !== 'ArrayExpression') {
+          return
+        }
+        checkArrayExpression(type.value, context)
+      }
+    }
+
+    return utils.compositingVisitors(
+      utils.defineScriptSetupVisitor(context, {
+        onDefinePropsEnter(_, props) {
+          props.forEach(checkProperty)
+        }
+      }),
+      utils.executeOnVue(context, (obj) => {
+        const props = utils.getComponentPropsFromOptions(obj)
+        props.forEach(checkProperty)
+      })
+    )
+  }
+}
diff --git a/package.json b/package.json
index 7abecb3f..4b843c06 100644
--- a/package.json
+++ b/package.json
@@ -12,8 +12,8 @@
     "cover": "npm run cover:test && npm run cover:report",
     "cover:test": "nyc npm run test:base -- --timeout 60000",
     "cover:report": "nyc report --reporter=html",
-    "lint": "eslint . --rulesdir eslint-internal-rules",
-    "lint:fix": "eslint . --rulesdir eslint-internal-rules --fix",
+    "lint": "eslint . --rulesdir eslint-internal-rules && markdownlint \"**/*.md\"",
+    "lint:fix": "eslint . --rulesdir eslint-internal-rules --fix && markdownlint \"**/*.md\" --fix",
     "tsc": "tsc",
     "preversion": "npm test && git add .",
     "version": "env-cmd -e version npm run update && npm run lint -- --fix && git add .",
@@ -79,6 +79,7 @@
     "eslint-plugin-vue": "file:.",
     "espree": "^9.0.0",
     "lodash": "^4.17.21",
+    "markdownlint-cli": "^0.31.1",
     "mocha": "^7.1.2",
     "nyc": "^15.1.0",
     "prettier": "^2.4.1",
diff --git a/tools/update-docs.js b/tools/update-docs.js
index a7b847ee..12deca0e 100644
--- a/tools/update-docs.js
+++ b/tools/update-docs.js
@@ -87,6 +87,8 @@ class DocFile {
 
   write() {
     fs.writeFileSync(this.filePath, this.content)
+
+    return this
   }
 
   updateFileIntro() {
@@ -225,6 +227,42 @@ ${
 
     return this
   }
+
+  checkHeadings() {
+    const headingPattern = /^## .*$/gm
+
+    const knownHeadings = [
+      '## :book: Rule Details',
+      '## :wrench: Options',
+      '## :rocket: Suggestion',
+      '## :mute: When Not To Use It',
+      '## :couple: Related Rules',
+      '## :books: Further Reading',
+      '## :rocket: Version',
+      '## :mag: Implementation'
+    ]
+
+    const foundHeadings = [...this.content.matchAll(headingPattern)]
+
+    let previousHeadingIndex = -1
+    for (const [heading] of foundHeadings) {
+      const headingIndex = knownHeadings.indexOf(heading)
+
+      if (headingIndex === -1) {
+        throw new Error(`Unknown heading '${heading}' in '${this.filePath}'`)
+      }
+
+      if (headingIndex < previousHeadingIndex) {
+        throw new Error(
+          `Heading '${heading}' should be above '${knownHeadings[previousHeadingIndex]}' in '${this.filePath}'`
+        )
+      }
+
+      previousHeadingIndex = headingIndex
+    }
+
+    return this
+  }
 }
 
 for (const rule of rules) {
@@ -235,4 +273,5 @@ for (const rule of rules) {
     .updateFileIntro()
     .adjustCodeBlocks()
     .write()
+    .checkHeadings()
 }


Test patch:
diff --git a/tests/lib/rules/no-restricted-html-elements.js b/tests/lib/rules/no-restricted-html-elements.js
new file mode 100644
index 00000000..775d9458
--- /dev/null
+++ b/tests/lib/rules/no-restricted-html-elements.js
@@ -0,0 +1,75 @@
+/**
+ * @author Doug Wade
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/no-restricted-html-elements')
+
+const tester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2015 }
+})
+
+tester.run('no-restricted-html-elements', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: '',
+      options: ['button']
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div class="foo"></div></template>',
+      options: ['button']
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><button type="button"></button></template>',
+      options: ['div']
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div class="foo"><Button type="button"></Button></div></template>',
+      options: ['button']
+    }
+  ],
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: '<template><div><button type="button"></button><div></template>',
+      errors: [
+        {
+          message: 'Unexpected use of forbidden HTML element button.',
+          line: 1,
+          column: 16
+        }
+      ],
+      options: ['button']
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div class="foo"><button type="button"></button></div></template>',
+      errors: [
+        {
+          message: 'Unexpected use of forbidden HTML element div.',
+          line: 1,
+          column: 11
+        }
+      ],
+      options: ['div']
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><marquee>foo</marquee></template>',
+      errors: [
+        {
+          message: 'Custom error',
+          line: 1,
+          column: 11
+        }
+      ],
+      options: [{ element: 'marquee', message: 'Custom error' }]
+    }
+  ]
+})
diff --git a/tests/lib/rules/prefer-prop-type-boolean-first.js b/tests/lib/rules/prefer-prop-type-boolean-first.js
new file mode 100644
index 00000000..9f67e336
--- /dev/null
+++ b/tests/lib/rules/prefer-prop-type-boolean-first.js
@@ -0,0 +1,317 @@
+/**
+ * @author Pig Fang
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/prefer-prop-type-boolean-first')
+
+const tester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: {
+    ecmaVersion: 2020,
+    sourceType: 'module'
+  }
+})
+
+tester.run('prefer-prop-type-boolean-first', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+        <script>
+          export default {
+            props: {
+              a: Boolean,
+              b: String,
+              c: [Boolean],
+              d: [String],
+              e: [Boolean, String],
+              f: { type: Boolean },
+              g: { type: String },
+              h: { type: [Boolean] },
+              i: { type: [String] },
+              j: { type: [Boolean, String] },
+              k: {},
+            }
+          }
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script>
+          export default Vue.extend({
+            props: {
+              a: [Boolean, String],
+              b: { type: [Boolean, String] },
+            }
+          })
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          defineProps({
+            a: [Boolean, String],
+            b: { type: [Boolean, String] },
+          })
+        </script>
+      `
+    }
+  ],
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+        <script>
+          export default {
+            props: {
+              foo: [String, Boolean]
+            }
+          }
+        </script>
+      `,
+      errors: [
+        {
+          messageId: 'shouldBeFirst',
+          line: 5,
+          column: 29,
+          suggestions: [
+            {
+              messageId: 'moveToFirst',
+              output: `
+        <script>
+          export default {
+            props: {
+              foo: [Boolean, String]
+            }
+          }
+        </script>
+      `
+            }
+          ]
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script>
+          export default {
+            props: {
+              foo: { type: [String, Boolean] }
+            }
+          }
+        </script>
+      `,
+      errors: [
+        {
+          messageId: 'shouldBeFirst',
+          line: 5,
+          column: 37,
+          suggestions: [
+            {
+              messageId: 'moveToFirst',
+              output: `
+        <script>
+          export default {
+            props: {
+              foo: { type: [Boolean, String] }
+            }
+          }
+        </script>
+      `
+            }
+          ]
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script>
+          export default Vue.extend({
+            props: {
+              foo: [String, Boolean]
+            }
+          })
+        </script>
+      `,
+      errors: [
+        {
+          messageId: 'shouldBeFirst',
+          line: 5,
+          column: 29,
+          suggestions: [
+            {
+              messageId: 'moveToFirst',
+              output: `
+        <script>
+          export default Vue.extend({
+            props: {
+              foo: [Boolean, String]
+            }
+          })
+        </script>
+      `
+            }
+          ]
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script>
+          export default Vue.extend({
+            props: {
+              foo: { type: [String, Boolean] }
+            }
+          })
+        </script>
+      `,
+      errors: [
+        {
+          messageId: 'shouldBeFirst',
+          line: 5,
+          column: 37,
+          suggestions: [
+            {
+              messageId: 'moveToFirst',
+              output: `
+        <script>
+          export default Vue.extend({
+            props: {
+              foo: { type: [Boolean, String] }
+            }
+          })
+        </script>
+      `
+            }
+          ]
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          defineProps({
+            foo: [String, Boolean]
+          })
+        </script>
+      `,
+      errors: [
+        {
+          messageId: 'shouldBeFirst',
+          line: 4,
+          column: 27,
+          suggestions: [
+            {
+              messageId: 'moveToFirst',
+              output: `
+        <script setup>
+          defineProps({
+            foo: [Boolean, String]
+          })
+        </script>
+      `
+            }
+          ]
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          defineProps({
+            foo: { type: [String, Boolean] }
+          })
+        </script>
+      `,
+      errors: [
+        {
+          messageId: 'shouldBeFirst',
+          line: 4,
+          column: 35,
+          suggestions: [
+            {
+              messageId: 'moveToFirst',
+              output: `
+        <script setup>
+          defineProps({
+            foo: { type: [Boolean, String] }
+          })
+        </script>
+      `
+            }
+          ]
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          defineProps({
+            foo: { type: [String, Boolean, Number] }
+          })
+        </script>
+      `,
+      errors: [
+        {
+          messageId: 'shouldBeFirst',
+          line: 4,
+          column: 35,
+          suggestions: [
+            {
+              messageId: 'moveToFirst',
+              output: `
+        <script setup>
+          defineProps({
+            foo: { type: [Boolean, String, Number] }
+          })
+        </script>
+      `
+            }
+          ]
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          defineProps({
+            foo: { type: [String, Number, Boolean] }
+          })
+        </script>
+      `,
+      errors: [
+        {
+          messageId: 'shouldBeFirst',
+          line: 4,
+          column: 43,
+          suggestions: [
+            {
+              messageId: 'moveToFirst',
+              output: `
+        <script setup>
+          defineProps({
+            foo: { type: [Boolean, String, Number] }
+          })
+        </script>
+      `
+            }
+          ]
+        }
+      ]
+    }
+  ]
+})