Repository: vuejs/eslint-plugin-vue
Pull request #1180 (2020-05-31 02:59:51)

Improved `vue/no-ref-as-operand` rule.
- Changed `vue/no-ref-as-operand` rule to additionally track variables generated by `computed`, `toRef`, `customRef` and `shallowRef`.
- Changed `vue/no-ref-as-operand` rule to report incorrect use of TemplateLiteral and MemberExpression.


Patch:
diff --git a/.eslintrc.js b/.eslintrc.js
index f45e3ef2..97e2e03f 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -48,10 +48,6 @@ module.exports = {
         'tests/lib/rules/require-direct-export.js',
         'lib/utils/index.js',
         'tests/lib/utils/vue-component.js',
-        // https://github.com/vuejs/eslint-plugin-vue/pull/1017
-        'lib/utils/indent-common.js',
-        'tests/lib/rules/html-indent.js',
-        'tests/lib/rules/script-indent.js',
         // https://github.com/vuejs/eslint-plugin-vue/pull/982
         'lib/rules/attributes-order.js',
         'tests/lib/rules/attributes-order.js',
diff --git a/docs/rules/README.md b/docs/rules/README.md
index 8d3e9fb3..f3e2bfdf 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -43,6 +43,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
 | [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties |  |
 | [vue/no-deprecated-data-object-declaration](./no-deprecated-data-object-declaration.md) | disallow using deprecated object declaration on data (in Vue.js 3.0.0+) | :wrench: |
 | [vue/no-deprecated-dollar-listeners-api](./no-deprecated-dollar-listeners-api.md) | disallow using deprecated `$listeners` (in Vue.js 3.0.0+) |  |
+| [vue/no-deprecated-dollar-scopedslots-api](./no-deprecated-dollar-scopedslots-api.md) | disallow using deprecated `$scopedSlots` (in Vue.js 3.0.0+) | :wrench: |
 | [vue/no-deprecated-events-api](./no-deprecated-events-api.md) | disallow using deprecated events api (in Vue.js 3.0.0+) |  |
 | [vue/no-deprecated-filter](./no-deprecated-filter.md) | disallow using deprecated filters syntax (in Vue.js 3.0.0+) |  |
 | [vue/no-deprecated-functional-template](./no-deprecated-functional-template.md) | disallow using deprecated the `functional` template (in Vue.js 3.0.0+) |  |
@@ -74,6 +75,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
 | [vue/require-component-is](./require-component-is.md) | require `v-bind:is` of `<component>` elements |  |
 | [vue/require-prop-type-constructor](./require-prop-type-constructor.md) | require prop type to be a constructor | :wrench: |
 | [vue/require-render-return](./require-render-return.md) | enforce render function to always return value |  |
+| [vue/require-slots-as-functions](./require-slots-as-functions.md) | enforce properties of `$slots` to be used as a function |  |
 | [vue/require-toggle-inside-transition](./require-toggle-inside-transition.md) | require control the display of the content inside `<transition>` |  |
 | [vue/require-v-for-key](./require-v-for-key.md) | require `v-bind:key` with `v-for` directives |  |
 | [vue/require-valid-default-prop](./require-valid-default-prop.md) | enforce props default values to be valid |  |
@@ -145,6 +147,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
 |:--------|:------------|:---|
 | [vue/attributes-order](./attributes-order.md) | enforce order of attributes | :wrench: |
 | [vue/component-tags-order](./component-tags-order.md) | enforce order of component top-level elements |  |
+| [vue/no-multiple-slot-args](./no-multiple-slot-args.md) | disallow to pass multiple arguments to scoped slots |  |
 | [vue/no-v-html](./no-v-html.md) | disallow use of v-html to prevent XSS attack |  |
 | [vue/order-in-components](./order-in-components.md) | enforce order of properties in components | :wrench: |
 | [vue/this-in-template](./this-in-template.md) | disallow usage of `this` in template |  |
@@ -252,6 +255,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
 |:--------|:------------|:---|
 | [vue/attributes-order](./attributes-order.md) | enforce order of attributes | :wrench: |
 | [vue/component-tags-order](./component-tags-order.md) | enforce order of component top-level elements |  |
+| [vue/no-multiple-slot-args](./no-multiple-slot-args.md) | disallow to pass multiple arguments to scoped slots |  |
 | [vue/no-v-html](./no-v-html.md) | disallow use of v-html to prevent XSS attack |  |
 | [vue/order-in-components](./order-in-components.md) | enforce order of properties in components | :wrench: |
 | [vue/this-in-template](./this-in-template.md) | disallow usage of `this` in template |  |
diff --git a/docs/rules/no-deprecated-dollar-scopedslots-api.md b/docs/rules/no-deprecated-dollar-scopedslots-api.md
new file mode 100644
index 00000000..9b1d577c
--- /dev/null
+++ b/docs/rules/no-deprecated-dollar-scopedslots-api.md
@@ -0,0 +1,47 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-dollar-scopedslots-api
+description: disallow using deprecated `$scopedSlots` (in Vue.js 3.0.0+)
+---
+# vue/no-deprecated-dollar-scopedslots-api
+> disallow using deprecated `$scopedSlots` (in Vue.js 3.0.0+)
+
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+
+## :book: Rule Details
+
+This rule reports use of deprecated `$scopedSlots`. (in Vue.js 3.0.0+).
+
+<eslint-code-block fix :rules="{'vue/no-deprecated-dollar-scopedslots-api': ['error']}">
+
+```vue
+<template>
+  <!-- ✗ BAD -->
+  <div v-if="$scopedSlots.default"><slot /></div>
+</template>
+<script>
+export default {
+  render() {
+    /* ✗ BAD */
+    return this.$scopedSlots.default()
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further reading
+
+- [Vue RFCs - 0006-slots-unification](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0006-slots-unification.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-dollar-scopedslots-api.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-dollar-scopedslots-api.js)
diff --git a/docs/rules/no-multiple-slot-args.md b/docs/rules/no-multiple-slot-args.md
new file mode 100644
index 00000000..d0d31945
--- /dev/null
+++ b/docs/rules/no-multiple-slot-args.md
@@ -0,0 +1,49 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-multiple-slot-args
+description: disallow to pass multiple arguments to scoped slots
+---
+# vue/no-multiple-slot-args
+> disallow to pass multiple arguments to scoped slots
+
+- :gear: This rule is included in `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+
+## :book: Rule Details
+
+This rule disallows to pass multiple arguments to scoped slots.  
+In details, it reports call expressions if a call of `this.$scopedSlots` members has 2 or more arguments.
+
+<eslint-code-block :rules="{'vue/no-multiple-slot-args': ['error']}">
+
+```vue
+<script>
+export default {
+  render(h) {
+    /* ✓ GOOD */
+    var children = this.$scopedSlots.default()
+    var children = this.$scopedSlots.default(foo)
+    var children = this.$scopedSlots.default({ foo, bar })
+
+    /* ✗ BAD */
+    var children = this.$scopedSlots.default(foo, bar)
+    var children = this.$scopedSlots.default(...foo)
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further reading
+
+- [vuejs/vue#9468](https://github.com/vuejs/vue/issues/9468#issuecomment-462210146)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-multiple-slot-args.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-multiple-slot-args.js)
diff --git a/docs/rules/no-ref-as-operand.md b/docs/rules/no-ref-as-operand.md
index c983d669..609a0727 100644
--- a/docs/rules/no-ref-as-operand.md
+++ b/docs/rules/no-ref-as-operand.md
@@ -11,7 +11,8 @@ description: disallow use of value wrapped by `ref()` (Composition API) as an op
 
 ## :book: Rule Details
 
-This rule reports cases where a ref is used incorrectly as an operand.
+This rule reports cases where a ref is used incorrectly as an operand.  
+You must use `.value` to access the `Ref` value.
 
 <eslint-code-block :rules="{'vue/no-ref-as-operand': ['error']}">
 
diff --git a/docs/rules/require-slots-as-functions.md b/docs/rules/require-slots-as-functions.md
new file mode 100644
index 00000000..8b4c01f9
--- /dev/null
+++ b/docs/rules/require-slots-as-functions.md
@@ -0,0 +1,48 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-slots-as-functions
+description: enforce properties of `$slots` to be used as a function
+---
+# vue/require-slots-as-functions
+> enforce properties of `$slots` to be used as a function
+
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+
+## :book: Rule Details
+
+This rule enforces the properties of `$slots` to be used as a function.  
+`this.$slots.default` was an array of VNode in Vue.js 2.x, but changed to a function that returns an array of VNode in Vue.js 3.x.
+
+<eslint-code-block :rules="{'vue/require-slots-as-functions': ['error']}">
+
+```vue
+<script>
+export default {
+  render(h) {
+    /* ✓ GOOD */
+    var children = this.$slots.default()
+    var children = this.$slots.default && this.$slots.default()
+
+    /* ✗ BAD */
+    var children = [...this.$slots.default]
+    var children = this.$slots.default.filter(test)
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further reading
+
+- [Vue RFCs - 0006-slots-unification](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0006-slots-unification.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-slots-as-functions.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-slots-as-functions.js)
diff --git a/lib/configs/recommended.js b/lib/configs/recommended.js
index e3ca8eba..e6388e15 100644
--- a/lib/configs/recommended.js
+++ b/lib/configs/recommended.js
@@ -8,6 +8,7 @@ module.exports = {
   rules: {
     'vue/attributes-order': 'warn',
     'vue/component-tags-order': 'warn',
+    'vue/no-multiple-slot-args': 'warn',
     'vue/no-v-html': 'warn',
     'vue/order-in-components': 'warn',
     'vue/this-in-template': 'warn'
diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js
index b2aec42d..b802623d 100644
--- a/lib/configs/vue3-essential.js
+++ b/lib/configs/vue3-essential.js
@@ -11,6 +11,7 @@ module.exports = {
     'vue/no-async-in-computed-properties': 'error',
     'vue/no-deprecated-data-object-declaration': 'error',
     'vue/no-deprecated-dollar-listeners-api': 'error',
+    'vue/no-deprecated-dollar-scopedslots-api': 'error',
     'vue/no-deprecated-events-api': 'error',
     'vue/no-deprecated-filter': 'error',
     'vue/no-deprecated-functional-template': 'error',
@@ -42,6 +43,7 @@ module.exports = {
     'vue/require-component-is': 'error',
     'vue/require-prop-type-constructor': 'error',
     'vue/require-render-return': 'error',
+    'vue/require-slots-as-functions': 'error',
     'vue/require-toggle-inside-transition': 'error',
     'vue/require-v-for-key': 'error',
     'vue/require-valid-default-prop': 'error',
diff --git a/lib/configs/vue3-recommended.js b/lib/configs/vue3-recommended.js
index 700f0f8f..318690c3 100644
--- a/lib/configs/vue3-recommended.js
+++ b/lib/configs/vue3-recommended.js
@@ -8,6 +8,7 @@ module.exports = {
   rules: {
     'vue/attributes-order': 'warn',
     'vue/component-tags-order': 'warn',
+    'vue/no-multiple-slot-args': 'warn',
     'vue/no-v-html': 'warn',
     'vue/order-in-components': 'warn',
     'vue/this-in-template': 'warn'
diff --git a/lib/index.js b/lib/index.js
index d5fc2122..c0041525 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -50,6 +50,7 @@ module.exports = {
     'no-custom-modifiers-on-v-model': require('./rules/no-custom-modifiers-on-v-model'),
     'no-deprecated-data-object-declaration': require('./rules/no-deprecated-data-object-declaration'),
     'no-deprecated-dollar-listeners-api': require('./rules/no-deprecated-dollar-listeners-api'),
+    'no-deprecated-dollar-scopedslots-api': require('./rules/no-deprecated-dollar-scopedslots-api'),
     'no-deprecated-events-api': require('./rules/no-deprecated-events-api'),
     'no-deprecated-filter': require('./rules/no-deprecated-filter'),
     'no-deprecated-functional-template': require('./rules/no-deprecated-functional-template'),
@@ -70,6 +71,7 @@ module.exports = {
     'no-irregular-whitespace': require('./rules/no-irregular-whitespace'),
     'no-lifecycle-after-await': require('./rules/no-lifecycle-after-await'),
     'no-multi-spaces': require('./rules/no-multi-spaces'),
+    'no-multiple-slot-args': require('./rules/no-multiple-slot-args'),
     'no-multiple-template-root': require('./rules/no-multiple-template-root'),
     'no-mutating-props': require('./rules/no-mutating-props'),
     'no-parsing-error': require('./rules/no-parsing-error'),
@@ -111,6 +113,7 @@ module.exports = {
     'require-prop-type-constructor': require('./rules/require-prop-type-constructor'),
     'require-prop-types': require('./rules/require-prop-types'),
     'require-render-return': require('./rules/require-render-return'),
+    'require-slots-as-functions': require('./rules/require-slots-as-functions'),
     'require-toggle-inside-transition': require('./rules/require-toggle-inside-transition'),
     'require-v-for-key': require('./rules/require-v-for-key'),
     'require-valid-default-prop': require('./rules/require-valid-default-prop'),
diff --git a/lib/rules/no-deprecated-dollar-scopedslots-api.js b/lib/rules/no-deprecated-dollar-scopedslots-api.js
new file mode 100644
index 00000000..f369c70f
--- /dev/null
+++ b/lib/rules/no-deprecated-dollar-scopedslots-api.js
@@ -0,0 +1,79 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description:
+        'disallow using deprecated `$scopedSlots` (in Vue.js 3.0.0+)',
+      categories: ['vue3-essential'],
+      url:
+        'https://eslint.vuejs.org/rules/no-deprecated-dollar-scopedslots-api.html'
+    },
+    fixable: 'code',
+    schema: [],
+    messages: {
+      deprecated: 'The `$scopedSlots` is deprecated.'
+    }
+  },
+
+  create(context) {
+    return utils.defineTemplateBodyVisitor(
+      context,
+      {
+        VExpressionContainer(node) {
+          for (const reference of node.references) {
+            if (reference.variable != null) {
+              // Not vm reference
+              continue
+            }
+            if (reference.id.name === '$scopedSlots') {
+              context.report({
+                node: reference.id,
+                messageId: 'deprecated',
+                fix(fixer) {
+                  return fixer.replaceText(reference.id, '$slots')
+                }
+              })
+            }
+          }
+        }
+      },
+      utils.defineVueVisitor(context, {
+        MemberExpression(node) {
+          if (
+            node.property.type !== 'Identifier' ||
+            node.property.name !== '$scopedSlots'
+          ) {
+            return
+          }
+          if (!utils.isThis(node.object, context)) {
+            return
+          }
+
+          context.report({
+            node: node.property,
+            messageId: 'deprecated',
+            fix(fixer) {
+              return fixer.replaceText(node.property, '$slots')
+            }
+          })
+        }
+      })
+    )
+  }
+}
diff --git a/lib/rules/no-multiple-slot-args.js b/lib/rules/no-multiple-slot-args.js
new file mode 100644
index 00000000..e8bcae96
--- /dev/null
+++ b/lib/rules/no-multiple-slot-args.js
@@ -0,0 +1,127 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+const { findVariable } = require('eslint-utils')
+
+/**
+ * @typedef {import('vue-eslint-parser').AST.ESLintMemberExpression} MemberExpression
+ * @typedef {import('vue-eslint-parser').AST.ESLintIdentifier} Identifier
+ */
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description: 'disallow to pass multiple arguments to scoped slots',
+      categories: ['vue3-recommended', 'recommended'],
+      url: 'https://eslint.vuejs.org/rules/no-multiple-slot-args.html'
+    },
+    fixable: null,
+    schema: [],
+    messages: {
+      unexpected: 'Unexpected multiple arguments.',
+      unexpectedSpread: 'Unexpected spread argument.'
+    }
+  },
+
+  create(context) {
+    /**
+     * Verify the given node
+     * @param {MemberExpression | Identifier} node The node to verify
+     */
+    function verify(node) {
+      const parent = node.parent
+
+      if (
+        parent.type === 'VariableDeclarator' &&
+        parent.id.type === 'Identifier'
+      ) {
+        // const foo = this.$scopedSlots.foo
+        verifyReferences(parent.id)
+        return
+      }
+
+      if (
+        parent.type === 'AssignmentExpression' &&
+        parent.right === node &&
+        parent.left.type === 'Identifier'
+      ) {
+        // foo = this.$scopedSlots.foo
+        verifyReferences(parent.left)
+        return
+      }
+
+      if (parent.type !== 'CallExpression' || parent.arguments.includes(node)) {
+        return
+      }
+
+      if (!parent.arguments.length) {
+        return
+      }
+      if (parent.arguments.length > 1) {
+        context.report({
+          node: parent.arguments[1],
+          messageId: 'unexpected'
+        })
+      }
+      if (parent.arguments[0].type === 'SpreadElement') {
+        context.report({
+          node: parent.arguments[0],
+          messageId: 'unexpectedSpread'
+        })
+      }
+    }
+    /**
+     * Verify the references of the given node.
+     * @param {Identifier} node The node to verify
+     */
+    function verifyReferences(node) {
+      // @ts-ignore
+      const variable = findVariable(context.getScope(), node)
+      if (!variable) {
+        return
+      }
+      for (const reference of variable.references) {
+        if (!reference.isRead()) {
+          continue
+        }
+        /** @type {Identifier} */
+        const id = reference.identifier
+        verify(id)
+      }
+    }
+
+    return utils.defineVueVisitor(context, {
+      /** @param {MemberExpression} node */
+      MemberExpression(node) {
+        const object = node.object
+        if (object.type !== 'MemberExpression') {
+          return
+        }
+        if (
+          object.property.type !== 'Identifier' ||
+          (object.property.name !== '$slots' &&
+            object.property.name !== '$scopedSlots')
+        ) {
+          return
+        }
+        if (!utils.isThis(object.object, context)) {
+          return
+        }
+        verify(node)
+      }
+    })
+  }
+}
diff --git a/lib/rules/no-ref-as-operand.js b/lib/rules/no-ref-as-operand.js
index 11873edf..cfd09921 100644
--- a/lib/rules/no-ref-as-operand.js
+++ b/lib/rules/no-ref-as-operand.js
@@ -4,6 +4,7 @@
  */
 'use strict'
 const { ReferenceTracker, findVariable } = require('eslint-utils')
+const utils = require('../utils')
 
 module.exports = {
   meta: {
@@ -18,19 +19,23 @@ module.exports = {
     schema: [],
     messages: {
       requireDotValue:
-        'Must use `.value` to read or write the value wrapped by `ref()`.'
+        'Must use `.value` to read or write the value wrapped by `{{method}}()`.'
     }
   },
   create(context) {
     const refReferenceIds = new Map()
 
     function reportIfRefWrapped(node) {
-      if (!refReferenceIds.has(node)) {
+      const data = refReferenceIds.get(node)
+      if (!data) {
         return
       }
       context.report({
         node,
-        messageId: 'requireDotValue'
+        messageId: 'requireDotValue',
+        data: {
+          method: data.method
+        }
       })
     }
     return {
@@ -41,11 +46,23 @@ module.exports = {
             [ReferenceTracker.ESM]: true,
             ref: {
               [ReferenceTracker.CALL]: true
+            },
+            computed: {
+              [ReferenceTracker.CALL]: true
+            },
+            toRef: {
+              [ReferenceTracker.CALL]: true
+            },
+            customRef: {
+              [ReferenceTracker.CALL]: true
+            },
+            shallowRef: {
+              [ReferenceTracker.CALL]: true
             }
           }
         }
 
-        for (const { node } of tracker.iterateEsmReferences(traceMap)) {
+        for (const { node, path } of tracker.iterateEsmReferences(traceMap)) {
           const variableDeclarator = node.parent
           if (
             !variableDeclarator ||
@@ -73,7 +90,8 @@ module.exports = {
 
             refReferenceIds.set(reference.identifier, {
               variableDeclarator,
-              variableDeclaration
+              variableDeclaration,
+              method: path[1]
             })
           }
         }
@@ -108,13 +126,13 @@ module.exports = {
           return
         }
         // Report only constants.
-        const info = refReferenceIds.get(node)
-        if (!info) {
+        const data = refReferenceIds.get(node)
+        if (!data) {
           return
         }
         if (
-          !info.variableDeclaration ||
-          info.variableDeclaration.kind !== 'const'
+          !data.variableDeclaration ||
+          data.variableDeclaration.kind !== 'const'
         ) {
           return
         }
@@ -126,6 +144,26 @@ module.exports = {
           return
         }
         reportIfRefWrapped(node)
+      },
+      // `${refValue}`
+      'TemplateLiteral>Identifier'(node) {
+        reportIfRefWrapped(node)
+      },
+      // refValue.x
+      'MemberExpression>Identifier'(node) {
+        if (node.parent.object !== node) {
+          return
+        }
+        const name = utils.getStaticPropertyName(node.parent)
+        if (
+          name === 'value' ||
+          name == null ||
+          // WritableComputedRef
+          name === 'effect'
+        ) {
+          return
+        }
+        reportIfRefWrapped(node)
       }
     }
   }
diff --git a/lib/rules/require-slots-as-functions.js b/lib/rules/require-slots-as-functions.js
new file mode 100644
index 00000000..07493ddb
--- /dev/null
+++ b/lib/rules/require-slots-as-functions.js
@@ -0,0 +1,124 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+const { findVariable } = require('eslint-utils')
+
+/**
+ * @typedef {import('vue-eslint-parser').AST.ESLintMemberExpression} MemberExpression
+ * @typedef {import('vue-eslint-parser').AST.ESLintIdentifier} Identifier
+ * @typedef {import('vue-eslint-parser').AST.ESLintExpression} Expression
+ */
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description: 'enforce properties of `$slots` to be used as a function',
+      categories: ['vue3-essential'],
+      url: 'https://eslint.vuejs.org/rules/require-slots-as-functions.html'
+    },
+    fixable: null,
+    schema: [],
+    messages: {
+      unexpected: 'Property in `$slots` should be used as function.'
+    }
+  },
+
+  create(context) {
+    /**
+     * Verify the given node
+     * @param {MemberExpression | Identifier} node The node to verify
+     * @param {Expression} reportNode The node to report
+     */
+    function verify(node, reportNode) {
+      const parent = node.parent
+
+      if (
+        parent.type === 'VariableDeclarator' &&
+        parent.id.type === 'Identifier'
+      ) {
+        // const children = this.$slots.foo
+        verifyReferences(parent.id, reportNode)
+        return
+      }
+
+      if (
+        parent.type === 'AssignmentExpression' &&
+        parent.right === node &&
+        parent.left.type === 'Identifier'
+      ) {
+        // children = this.$slots.foo
+        verifyReferences(parent.left, reportNode)
+        return
+      }
+
+      if (
+        // this.$slots.foo.xxx
+        parent.type === 'MemberExpression' ||
+        // var [foo] = this.$slots.foo
+        parent.type === 'VariableDeclarator' ||
+        // [...this.$slots.foo]
+        parent.type === 'SpreadElement' ||
+        // [this.$slots.foo]
+        parent.type === 'ArrayExpression'
+      ) {
+        context.report({
+          node: reportNode,
+          messageId: 'unexpected'
+        })
+      }
+    }
+    /**
+     * Verify the references of the given node.
+     * @param {Identifier} node The node to verify
+     * @param {Expression} reportNode The node to report
+     */
+    function verifyReferences(node, reportNode) {
+      // @ts-ignore
+      const variable = findVariable(context.getScope(), node)
+      if (!variable) {
+        return
+      }
+      for (const reference of variable.references) {
+        if (!reference.isRead()) {
+          continue
+        }
+        /** @type {Identifier} */
+        const id = reference.identifier
+        verify(id, reportNode)
+      }
+    }
+
+    return utils.defineVueVisitor(context, {
+      /** @param {MemberExpression} node */
+      MemberExpression(node) {
+        const object = node.object
+        if (object.type !== 'MemberExpression') {
+          return
+        }
+        if (
+          object.property.type !== 'Identifier' ||
+          object.property.name !== '$slots'
+        ) {
+          return
+        }
+        if (!utils.isThis(object.object, context)) {
+          return
+        }
+        verify(node, node.property)
+      }
+    })
+  }
+}
diff --git a/lib/utils/indent-common.js b/lib/utils/indent-common.js
index a69d7a5d..97d28527 100644
--- a/lib/utils/indent-common.js
+++ b/lib/utils/indent-common.js
@@ -14,11 +14,98 @@ const assert = require('assert')
 // Helpers
 // ------------------------------------------------------------------------------
 
-const KNOWN_NODES = new Set(['ArrayExpression', 'ArrayPattern', 'ArrowFunctionExpression', 'AssignmentExpression', 'AssignmentPattern', 'AwaitExpression', 'BinaryExpression', 'BlockStatement', 'BreakStatement', 'CallExpression', 'CatchClause', 'ClassBody', 'ClassDeclaration', 'ClassExpression', 'ConditionalExpression', 'ContinueStatement', 'DebuggerStatement', 'DoWhileStatement', 'EmptyStatement', 'ExperimentalRestProperty', 'ExperimentalSpreadProperty', 'ExportAllDeclaration', 'ExportDefaultDeclaration', 'ExportNamedDeclaration', 'ExportSpecifier', 'ExpressionStatement', 'ForInStatement', 'ForOfStatement', 'ForStatement', 'FunctionDeclaration', 'FunctionExpression', 'Identifier', 'IfStatement', 'ImportDeclaration', 'ImportDefaultSpecifier', 'ImportNamespaceSpecifier', 'ImportSpecifier', 'LabeledStatement', 'Literal', 'LogicalExpression', 'MemberExpression', 'MetaProperty', 'MethodDefinition', 'NewExpression', 'ObjectExpression', 'ObjectPattern', 'Program', 'Property', 'RestElement', 'ReturnStatement', 'SequenceExpression', 'SpreadElement', 'Super', 'SwitchCase', 'SwitchStatement', 'TaggedTemplateExpression', 'TemplateElement', 'TemplateLiteral', 'ThisExpression', 'ThrowStatement', 'TryStatement', 'UnaryExpression', 'UpdateExpression', 'VariableDeclaration', 'VariableDeclarator', 'WhileStatement', 'WithStatement', 'YieldExpression', 'VAttribute', 'VDirectiveKey', 'VDocumentFragment', 'VElement', 'VEndTag', 'VExpressionContainer', 'VFilter', 'VFilterSequenceExpression', 'VForExpression', 'VIdentifier', 'VLiteral', 'VOnExpression', 'VSlotScopeExpression', 'VStartTag', 'VText'])
+const KNOWN_NODES = new Set([
+  'ArrayExpression',
+  'ArrayPattern',
+  'ArrowFunctionExpression',
+  'AssignmentExpression',
+  'AssignmentPattern',
+  'AwaitExpression',
+  'BinaryExpression',
+  'BlockStatement',
+  'BreakStatement',
+  'CallExpression',
+  'CatchClause',
+  'ClassBody',
+  'ClassDeclaration',
+  'ClassExpression',
+  'ConditionalExpression',
+  'ContinueStatement',
+  'DebuggerStatement',
+  'DoWhileStatement',
+  'EmptyStatement',
+  'ExperimentalRestProperty',
+  'ExperimentalSpreadProperty',
+  'ExportAllDeclaration',
+  'ExportDefaultDeclaration',
+  'ExportNamedDeclaration',
+  'ExportSpecifier',
+  'ExpressionStatement',
+  'ForInStatement',
+  'ForOfStatement',
+  'ForStatement',
+  'FunctionDeclaration',
+  'FunctionExpression',
+  'Identifier',
+  'IfStatement',
+  'ImportDeclaration',
+  'ImportDefaultSpecifier',
+  'ImportNamespaceSpecifier',
+  'ImportSpecifier',
+  'LabeledStatement',
+  'Literal',
+  'LogicalExpression',
+  'MemberExpression',
+  'MetaProperty',
+  'MethodDefinition',
+  'NewExpression',
+  'ObjectExpression',
+  'ObjectPattern',
+  'Program',
+  'Property',
+  'RestElement',
+  'ReturnStatement',
+  'SequenceExpression',
+  'SpreadElement',
+  'Super',
+  'SwitchCase',
+  'SwitchStatement',
+  'TaggedTemplateExpression',
+  'TemplateElement',
+  'TemplateLiteral',
+  'ThisExpression',
+  'ThrowStatement',
+  'TryStatement',
+  'UnaryExpression',
+  'UpdateExpression',
+  'VariableDeclaration',
+  'VariableDeclarator',
+  'WhileStatement',
+  'WithStatement',
+  'YieldExpression',
+  'VAttribute',
+  'VDirectiveKey',
+  'VDocumentFragment',
+  'VElement',
+  'VEndTag',
+  'VExpressionContainer',
+  'VFilter',
+  'VFilterSequenceExpression',
+  'VForExpression',
+  'VIdentifier',
+  'VLiteral',
+  'VOnExpression',
+  'VSlotScopeExpression',
+  'VStartTag',
+  'VText'
+])
 const LT_CHAR = /[\r\n\u2028\u2029]/
 const LINES = /[^\r\n\u2028\u2029]+(?:$|\r\n|[\r\n\u2028\u2029])/g
 const BLOCK_COMMENT_PREFIX = /^\s*\*/
-const ITERATION_OPTS = Object.freeze({ includeComments: true, filter: isNotWhitespace })
+const ITERATION_OPTS = Object.freeze({
+  includeComments: true,
+  filter: isNotWhitespace
+})
 const PREFORMATTED_ELEMENT_NAMES = ['pre', 'textarea']
 
 /**
@@ -42,21 +129,24 @@ const PREFORMATTED_ELEMENT_NAMES = ['pre', 'textarea']
  * @param {Object} defaultOptions The default value of options.
  * @returns {IndentOptions} Normalized options.
  */
-function parseOptions (type, options, defaultOptions) {
-  const ret = Object.assign({
-    indentChar: ' ',
-    indentSize: 2,
-    baseIndent: 0,
-    attribute: 1,
-    closeBracket: {
-      startTag: 0,
-      endTag: 0,
-      selfClosingTag: 0
+function parseOptions(type, options, defaultOptions) {
+  const ret = Object.assign(
+    {
+      indentChar: ' ',
+      indentSize: 2,
+      baseIndent: 0,
+      attribute: 1,
+      closeBracket: {
+        startTag: 0,
+        endTag: 0,
+        selfClosingTag: 0
+      },
+      switchCase: 0,
+      alignAttributesVertically: true,
+      ignores: []
     },
-    switchCase: 0,
-    alignAttributesVertically: true,
-    ignores: []
-  }, defaultOptions)
+    defaultOptions
+  )
 
   if (Number.isSafeInteger(type)) {
     ret.indentSize = type
@@ -107,7 +197,7 @@ function parseOptions (type, options, defaultOptions) {
  * @param {Token} token The token to check.
  * @returns {boolean} `true` if the token is an arrow.
  */
-function isArrow (token) {
+function isArrow(token) {
   return token != null && token.type === 'Punctuator' && token.value === '=>'
 }
 
@@ -116,7 +206,7 @@ function isArrow (token) {
  * @param {Token} token The token to check.
  * @returns {boolean} `true` if the token is a left parenthesis.
  */
-function isLeftParen (token) {
+function isLeftParen(token) {
   return token != null && token.type === 'Punctuator' && token.value === '('
 }
 
@@ -125,7 +215,7 @@ function isLeftParen (token) {
  * @param {Token} token The token to check.
  * @returns {boolean} `false` if the token is a left parenthesis.
  */
-function isNotLeftParen (token) {
+function isNotLeftParen(token) {
   return token != null && (token.type !== 'Punctuator' || token.value !== '(')
 }
 
@@ -134,7 +224,7 @@ function isNotLeftParen (token) {
  * @param {Token} token The token to check.
  * @returns {boolean} `true` if the token is a right parenthesis.
  */
-function isRightParen (token) {
+function isRightParen(token) {
   return token != null && token.type === 'Punctuator' && token.value === ')'
 }
 
@@ -143,7 +233,7 @@ function isRightParen (token) {
  * @param {Token} token The token to check.
  * @returns {boolean} `false` if the token is a right parenthesis.
  */
-function isNotRightParen (token) {
+function isNotRightParen(token) {
   return token != null && (token.type !== 'Punctuator' || token.value !== ')')
 }
 
@@ -152,7 +242,7 @@ function isNotRightParen (token) {
  * @param {Token} token The token to check.
  * @returns {boolean} `true` if the token is a left brace.
  */
-function isLeftBrace (token) {
+function isLeftBrace(token) {
   return token != null && token.type === 'Punctuator' && token.value === '{'
 }
 
@@ -161,7 +251,7 @@ function isLeftBrace (token) {
  * @param {Token} token The token to check.
  * @returns {boolean} `true` if the token is a right brace.
  */
-function isRightBrace (token) {
+function isRightBrace(token) {
   return token != null && token.type === 'Punctuator' && token.value === '}'
 }
 
@@ -170,7 +260,7 @@ function isRightBrace (token) {
  * @param {Token} token The token to check.
  * @returns {boolean} `true` if the token is a left bracket.
  */
-function isLeftBracket (token) {
+function isLeftBracket(token) {
   return token != null && token.type === 'Punctuator' && token.value === '['
 }
 
@@ -179,7 +269,7 @@ function isLeftBracket (token) {
  * @param {Token} token The token to check.
  * @returns {boolean} `true` if the token is a right bracket.
  */
-function isRightBracket (token) {
+function isRightBracket(token) {
   return token != null && token.type === 'Punctuator' && token.value === ']'
 }
 
@@ -188,7 +278,7 @@ function isRightBracket (token) {
  * @param {Token} token The token to check.
  * @returns {boolean} `true` if the token is a semicolon.
  */
-function isSemicolon (token) {
+function isSemicolon(token) {
   return token != null && token.type === 'Punctuator' && token.value === ';'
 }
 
@@ -197,7 +287,7 @@ function isSemicolon (token) {
  * @param {Token} token The token to check.
  * @returns {boolean} `true` if the token is a comma.
  */
-function isComma (token) {
+function isComma(token) {
   return token != null && token.type === 'Punctuator' && token.value === ','
 }
 
@@ -206,7 +296,7 @@ function isComma (token) {
  * @param {Token} token The token to check.
  * @returns {boolean} `true` if the token is a whitespace.
  */
-function isNotWhitespace (token) {
+function isNotWhitespace(token) {
   return token != null && token.type !== 'HTMLWhitespace'
 }
 
@@ -215,8 +305,14 @@ function isNotWhitespace (token) {
  * @param {Token} token The token to check.
  * @returns {boolean} `true` if the token is a comment.
  */
-function isComment (token) {
-  return token != null && (token.type === 'Block' || token.type === 'Line' || token.type === 'Shebang' || token.type.endsWith('Comment'))
+function isComment(token) {
+  return (
+    token != null &&
+    (token.type === 'Block' ||
+      token.type === 'Line' ||
+      token.type === 'Shebang' ||
+      token.type.endsWith('Comment'))
+  )
 }
 
 /**
@@ -224,8 +320,14 @@ function isComment (token) {
  * @param {Token} token The token to check.
  * @returns {boolean} `false` if the token is a comment.
  */
-function isNotComment (token) {
-  return token != null && token.type !== 'Block' && token.type !== 'Line' && token.type !== 'Shebang' && !token.type.endsWith('Comment')
+function isNotComment(token) {
+  return (
+    token != null &&
+    token.type !== 'Block' &&
+    token.type !== 'Line' &&
+    token.type !== 'Shebang' &&
+    !token.type.endsWith('Comment')
+  )
 }
 
 /**
@@ -233,7 +335,7 @@ function isNotComment (token) {
  * @param {Node} node The node to check.
  * @returns {boolean} `false` if the token is empty text node.
  */
-function isNotEmptyTextNode (node) {
+function isNotEmptyTextNode(node) {
   return !(node.type === 'VText' && node.value.trim() === '')
 }
 
@@ -242,7 +344,7 @@ function isNotEmptyTextNode (node) {
  * @param {Token} token The token to check.
  * @returns {boolean} `true` if the token is a pipe operator.
  */
-function isPipeOperator (token) {
+function isPipeOperator(token) {
   return token != null && token.type === 'Punctuator' && token.value === '|'
 }
 
@@ -251,7 +353,7 @@ function isPipeOperator (token) {
  * @param {Array} xs The array to get the last element.
  * @returns {any|undefined} The last element or undefined.
  */
-function last (xs) {
+function last(xs) {
   return xs.length === 0 ? undefined : xs[xs.length - 1]
 }
 
@@ -262,7 +364,7 @@ function last (xs) {
  * @param {Node[]} nodes The array of nodes.
  * @returns {boolean} `true` if the node is at the beginning of line.
  */
-function isBeginningOfLine (node, index, nodes) {
+function isBeginningOfLine(node, index, nodes) {
   if (node != null) {
     for (let i = index - 1; i >= 0; --i) {
       const prevNode = nodes[i]
@@ -281,18 +383,13 @@ function isBeginningOfLine (node, index, nodes) {
  * @param {Token} token The token to check.
  * @returns {boolean} `true` if the token is a closing token.
  */
-function isClosingToken (token) {
-  return token != null && (
-    token.type === 'HTMLEndTagOpen' ||
-    token.type === 'VExpressionEnd' ||
-    (
-      token.type === 'Punctuator' &&
-      (
-        token.value === ')' ||
-        token.value === '}' ||
-        token.value === ']'
-      )
-    )
+function isClosingToken(token) {
+  return (
+    token != null &&
+    (token.type === 'HTMLEndTagOpen' ||
+      token.type === 'VExpressionEnd' ||
+      (token.type === 'Punctuator' &&
+        (token.value === ')' || token.value === '}' || token.value === ']')))
   )
 }
 
@@ -304,10 +401,18 @@ function isClosingToken (token) {
  * @param {Object} defaultOptions The default value of options.
  * @returns {object} AST event handlers.
  */
-module.exports.defineVisitor = function create (context, tokenStore, defaultOptions) {
+module.exports.defineVisitor = function create(
+  context,
+  tokenStore,
+  defaultOptions
+) {
   if (!context.getFilename().endsWith('.vue')) return {}
 
-  const options = parseOptions(context.options[0], context.options[1] || {}, defaultOptions)
+  const options = parseOptions(
+    context.options[0],
+    context.options[1] || {},
+    defaultOptions
+  )
   const sourceCode = context.getSourceCode()
   const offsets = new Map()
   const ignoreTokens = new Set()
@@ -320,7 +425,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {boolean} [trivial=false] The flag for trivial tokens.
    * @returns {void}
    */
-  function setOffset (token, offset, baseToken) {
+  function setOffset(token, offset, baseToken) {
     assert(baseToken != null, "'baseToken' should not be null or undefined.")
 
     if (Array.isArray(token)) {
@@ -347,7 +452,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {Token} token The token to set.
    * @returns {void}
    */
-  function setBaseline (token) {
+  function setBaseline(token) {
     const offsetInfo = offsets.get(token)
     if (offsetInfo != null) {
       offsetInfo.baseline = true
@@ -359,20 +464,26 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {Node} node The node to set.
    * @returns {void}
    */
-  function setPreformattedTokens (node) {
-    const endToken = (node.endTag && tokenStore.getFirstToken(node.endTag)) || tokenStore.getTokenAfter(node)
+  function setPreformattedTokens(node) {
+    const endToken =
+      (node.endTag && tokenStore.getFirstToken(node.endTag)) ||
+      tokenStore.getTokenAfter(node)
 
     const option = {
       includeComments: true,
-      filter: token => token != null && (
-        token.type === 'HTMLText' ||
-        token.type === 'HTMLRCDataText' ||
-        token.type === 'HTMLTagOpen' ||
-        token.type === 'HTMLEndTagOpen' ||
-        token.type === 'HTMLComment'
-      )
+      filter: (token) =>
+        token != null &&
+        (token.type === 'HTMLText' ||
+          token.type === 'HTMLRCDataText' ||
+          token.type === 'HTMLTagOpen' ||
+          token.type === 'HTMLEndTagOpen' ||
+          token.type === 'HTMLComment')
     }
-    for (const token of tokenStore.getTokensBetween(node.startTag, endToken, option)) {
+    for (const token of tokenStore.getTokensBetween(
+      node.startTag,
+      endToken,
+      option
+    )) {
       ignoreTokens.add(token)
     }
     ignoreTokens.add(endToken)
@@ -385,7 +496,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {number} [borderOffset] The least offset of the first token. Defailt is 0. This value is used to prevent false positive in the following case: `(a) => {}` The parentheses are enclosing the whole parameter part rather than the first parameter, but this offset parameter is needed to distinguish.
    * @returns {{firstToken:Token,lastToken:Token}} The gotten tokens.
    */
-  function getFirstAndLastTokens (node, borderOffset) {
+  function getFirstAndLastTokens(node, borderOffset) {
     borderOffset |= 0
 
     let firstToken = tokenStore.getFirstToken(node)
@@ -393,7 +504,13 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
 
     // Get the outermost left parenthesis if it's parenthesized.
     let t, u
-    while ((t = tokenStore.getTokenBefore(firstToken)) != null && (u = tokenStore.getTokenAfter(lastToken)) != null && isLeftParen(t) && isRightParen(u) && t.range[0] >= borderOffset) {
+    while (
+      (t = tokenStore.getTokenBefore(firstToken)) != null &&
+      (u = tokenStore.getTokenAfter(lastToken)) != null &&
+      isLeftParen(t) &&
+      isRightParen(u) &&
+      t.range[0] >= borderOffset
+    ) {
       firstToken = t
       lastToken = u
     }
@@ -412,7 +529,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {boolean} [alignVertically=true] The flag to align vertically. If `false`, this doesn't align vertically even if the first node is not at beginning of line.
    * @returns {void}
    */
-  function processNodeList (nodeList, left, right, offset, alignVertically) {
+  function processNodeList(nodeList, left, right, offset, alignVertically) {
     let t
     const leftToken = (left && tokenStore.getFirstToken(left)) || left
     const rightToken = (right && tokenStore.getFirstToken(right)) || right
@@ -429,7 +546,10 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
           // Holes of an array.
           continue
         }
-        const elementTokens = getFirstAndLastTokens(node, lastToken != null ? lastToken.range[1] : 0)
+        const elementTokens = getFirstAndLastTokens(
+          node,
+          lastToken != null ? lastToken.range[1] : 0
+        )
 
         // Collect comma/comment tokens between the last token of the previous node and the first token of this node.
         if (lastToken != null) {
@@ -508,7 +628,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {Token} baseToken The base token.
    * @returns {void}
    */
-  function processMaybeBlock (node, baseToken) {
+  function processMaybeBlock(node, baseToken) {
     const firstToken = getFirstAndLastTokens(node).firstToken
     setOffset(firstToken, isLeftBrace(firstToken) ? 0 : 1, baseToken)
   }
@@ -518,7 +638,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * The prefix includes `async`, `get`, `set`, `static`, and `*`.
    * @param {Property|MethodDefinition} node The property node to collect prefix tokens.
    */
-  function getPrefixTokens (node) {
+  function getPrefixTokens(node) {
     const prefixes = []
 
     let token = tokenStore.getFirstToken(node)
@@ -538,7 +658,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {Node} node The start node to find the head.
    * @returns {Token} The head token of the chain.
    */
-  function getChainHeadToken (node) {
+  function getChainHeadToken(node) {
     const type = node.type
     while (node.parent.type === type) {
       const prevToken = tokenStore.getTokenBefore(node)
@@ -564,7 +684,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {Node} belongingNode The node that the token is belonging to.
    * @returns {boolean} `true` if the token is the first token of an element.
    */
-  function isBeginningOfElement (token, belongingNode) {
+  function isBeginningOfElement(token, belongingNode) {
     let node = belongingNode
 
     while (node != null) {
@@ -585,20 +705,28 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
         return true
       }
       if (t === 'CallExpression' || t === 'NewExpression') {
-        const openParen = tokenStore.getTokenAfter(parent.callee, isNotRightParen)
-        return parent.arguments.some(param =>
-          getFirstAndLastTokens(param, openParen.range[1]).firstToken.range[0] === token.range[0]
+        const openParen = tokenStore.getTokenAfter(
+          parent.callee,
+          isNotRightParen
+        )
+        return parent.arguments.some(
+          (param) =>
+            getFirstAndLastTokens(param, openParen.range[1]).firstToken
+              .range[0] === token.range[0]
         )
       }
       if (t === 'ArrayExpression') {
-        return parent.elements.some(element =>
-          element != null &&
-          getFirstAndLastTokens(element).firstToken.range[0] === token.range[0]
+        return parent.elements.some(
+          (element) =>
+            element != null &&
+            getFirstAndLastTokens(element).firstToken.range[0] ===
+              token.range[0]
         )
       }
       if (t === 'SequenceExpression') {
-        return parent.expressions.some(expr =>
-          getFirstAndLastTokens(expr).firstToken.range[0] === token.range[0]
+        return parent.expressions.some(
+          (expr) =>
+            getFirstAndLastTokens(expr).firstToken.range[0] === token.range[0]
         )
       }
 
@@ -614,13 +742,18 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {number} expectedIndent The number of expected indent.
    * @returns {void}
    */
-  function processTopLevelNode (node, expectedIndent) {
+  function processTopLevelNode(node, expectedIndent) {
     const token = tokenStore.getFirstToken(node)
     const offsetInfo = offsets.get(token)
     if (offsetInfo != null) {
       offsetInfo.expectedIndent = expectedIndent
     } else {
-      offsets.set(token, { baseToken: null, offset: 0, baseline: false, expectedIndent })
+      offsets.set(token, {
+        baseToken: null,
+        offset: 0,
+        baseline: false,
+        expectedIndent
+      })
     }
   }
 
@@ -629,7 +762,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {Node} node The node to ignore.
    * @returns {void}
    */
-  function ignore (node) {
+  function ignore(node) {
     for (const token of tokenStore.getTokens(node)) {
       offsets.delete(token)
       ignoreTokens.add(token)
@@ -641,7 +774,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {Object} visitor The visitor to define functions to ignore nodes.
    * @returns {Object} The visitor.
    */
-  function processIgnores (visitor) {
+  function processIgnores(visitor) {
     for (const ignorePattern of options.ignores) {
       const key = `${ignorePattern}:exit`
 
@@ -665,7 +798,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {Token[]} tokens Tokens which are on the same line.
    * @returns {object|null} Correct indentation. If it failed to calculate then `null`.
    */
-  function getExpectedIndents (tokens) {
+  function getExpectedIndents(tokens) {
     const expectedIndents = []
 
     for (let i = 0; i < tokens.length; ++i) {
@@ -677,8 +810,15 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
           expectedIndents.push(offsetInfo.expectedIndent)
         } else {
           const baseOffsetInfo = offsets.get(offsetInfo.baseToken)
-          if (baseOffsetInfo != null && baseOffsetInfo.expectedIndent != null && (i === 0 || !baseOffsetInfo.baseline)) {
-            expectedIndents.push(baseOffsetInfo.expectedIndent + (offsetInfo.offset * options.indentSize))
+          if (
+            baseOffsetInfo != null &&
+            baseOffsetInfo.expectedIndent != null &&
+            (i === 0 || !baseOffsetInfo.baseline)
+          ) {
+            expectedIndents.push(
+              baseOffsetInfo.expectedIndent +
+                offsetInfo.offset * options.indentSize
+            )
             if (baseOffsetInfo.baseline) {
               break
             }
@@ -701,7 +841,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {Token} firstToken The first token on a line.
    * @returns {string} The text of indentation part.
    */
-  function getIndentText (firstToken) {
+  function getIndentText(firstToken) {
     const text = sourceCode.text
     let i = firstToken.range[0] - 1
 
@@ -719,25 +859,27 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {number} expectedIndent The number of expected indentation.
    * @returns {Function} The defined function.
    */
-  function defineFix (token, actualIndent, expectedIndent) {
+  function defineFix(token, actualIndent, expectedIndent) {
     if (token.type === 'Block' && token.loc.start.line !== token.loc.end.line) {
       // Fix indentation in multiline block comments.
       const lines = sourceCode.getText(token).match(LINES)
       const firstLine = lines.shift()
-      if (lines.every(l => BLOCK_COMMENT_PREFIX.test(l))) {
-        return fixer => {
+      if (lines.every((l) => BLOCK_COMMENT_PREFIX.test(l))) {
+        return (fixer) => {
           const range = [token.range[0] - actualIndent, token.range[1]]
           const indent = options.indentChar.repeat(expectedIndent)
 
           return fixer.replaceTextRange(
             range,
-            `${indent}${firstLine}${lines.map(l => l.replace(BLOCK_COMMENT_PREFIX, `${indent} *`)).join('')}`
+            `${indent}${firstLine}${lines
+              .map((l) => l.replace(BLOCK_COMMENT_PREFIX, `${indent} *`))
+              .join('')}`
           )
         }
       }
     }
 
-    return fixer => {
+    return (fixer) => {
       const range = [token.range[0] - actualIndent, token.range[0]]
       const indent = options.indentChar.repeat(expectedIndent)
       return fixer.replaceTextRange(range, indent)
@@ -751,7 +893,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {number[]|undefined} optionalExpectedIndents The optional expected indentation.
    * @returns {void}
    */
-  function validateCore (token, expectedIndent, optionalExpectedIndents) {
+  function validateCore(token, expectedIndent, optionalExpectedIndents) {
     const line = token.loc.start.line
     const indentText = getIndentText(token)
 
@@ -763,7 +905,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
     }
 
     const actualIndent = token.loc.start.column
-    const unit = (options.indentChar === '\t' ? 'tab' : 'space')
+    const unit = options.indentChar === '\t' ? 'tab' : 'space'
 
     for (let i = 0; i < indentText.length; ++i) {
       if (indentText[i] !== options.indentChar) {
@@ -772,7 +914,8 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
             start: { line, column: i },
             end: { line, column: i + 1 }
           },
-          message: 'Expected {{expected}} character, but found {{actual}} character.',
+          message:
+            'Expected {{expected}} character, but found {{actual}} character.',
           data: {
             expected: JSON.stringify(options.indentChar),
             actual: JSON.stringify(indentText[i])
@@ -783,19 +926,24 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     }
 
-    if (actualIndent !== expectedIndent && (optionalExpectedIndents == null || !optionalExpectedIndents.includes(actualIndent))) {
+    if (
+      actualIndent !== expectedIndent &&
+      (optionalExpectedIndents == null ||
+        !optionalExpectedIndents.includes(actualIndent))
+    ) {
       context.report({
         loc: {
           start: { line, column: 0 },
           end: { line, column: actualIndent }
         },
-        message: 'Expected indentation of {{expectedIndent}} {{unit}}{{expectedIndentPlural}} but found {{actualIndent}} {{unit}}{{actualIndentPlural}}.',
+        message:
+          'Expected indentation of {{expectedIndent}} {{unit}}{{expectedIndentPlural}} but found {{actualIndent}} {{unit}}{{actualIndentPlural}}.',
         data: {
           expectedIndent,
           actualIndent,
           unit,
-          expectedIndentPlural: (expectedIndent === 1) ? '' : 's',
-          actualIndentPlural: (actualIndent === 1) ? '' : 's'
+          expectedIndentPlural: expectedIndent === 1 ? '' : 's',
+          actualIndentPlural: actualIndent === 1 ? '' : 's'
         },
         fix: defineFix(token, actualIndent, expectedIndent)
       })
@@ -809,7 +957,11 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {number|undefined} lastExpectedIndent The expected indent of the last token.
    * @returns {number[]}
    */
-  function getCommentExpectedIndents (nextToken, nextExpectedIndent, lastExpectedIndent) {
+  function getCommentExpectedIndents(
+    nextToken,
+    nextExpectedIndent,
+    lastExpectedIndent
+  ) {
     if (typeof lastExpectedIndent === 'number' && isClosingToken(nextToken)) {
       if (nextExpectedIndent === lastExpectedIndent) {
         // For solo comment. E.g.,
@@ -842,7 +994,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
    * @param {Token|null} lastToken The last validated token. Comments can adjust to the token.
    * @returns {void}
    */
-  function validate (tokens, comments, lastToken) {
+  function validate(tokens, comments, lastToken) {
     // Calculate and save expected indentation.
     const firstToken = tokens[0]
     const actualIndent = firstToken.loc.start.column
@@ -876,16 +1028,22 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
         if (offsetInfo.baseline) {
           // This is a baseline token, so the expected indent is the column of this token.
           if (options.indentChar === ' ') {
-            offsetInfo.expectedIndent = Math.max(0, token.loc.start.column + expectedBaseIndent - actualIndent)
+            offsetInfo.expectedIndent = Math.max(
+              0,
+              token.loc.start.column + expectedBaseIndent - actualIndent
+            )
           } else {
             // In hard-tabs mode, it cannot align tokens strictly, so use one additional offset.
             // But the additional offset isn't needed if it's at the beginning of the line.
-            offsetInfo.expectedIndent = expectedBaseIndent + (token === tokens[0] ? 0 : 1)
+            offsetInfo.expectedIndent =
+              expectedBaseIndent + (token === tokens[0] ? 0 : 1)
           }
           baseline.add(token)
         } else if (baseline.has(offsetInfo.baseToken)) {
           // The base token is a baseline token on this line, so inherit it.
-          offsetInfo.expectedIndent = offsets.get(offsetInfo.baseToken).expectedIndent
+          offsetInfo.expectedIndent = offsets.get(
+            offsetInfo.baseToken
+          ).expectedIndent
           baseline.add(token)
         } else {
           // Otherwise, set the expected indent of this line.
@@ -903,17 +1061,24 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
     // It allows the same indent level with the previous line.
     const lastOffsetInfo = offsets.get(lastToken)
     const lastExpectedIndent = lastOffsetInfo && lastOffsetInfo.expectedIndent
-    const commentOptionalExpectedIndents = getCommentExpectedIndents(firstToken, expectedIndent, lastExpectedIndent)
+    const commentOptionalExpectedIndents = getCommentExpectedIndents(
+      firstToken,
+      expectedIndent,
+      lastExpectedIndent
+    )
 
     // Validate.
     for (const comment of comments) {
       const commentExpectedIndents = getExpectedIndents([comment])
-      const commentExpectedIndent =
-        commentExpectedIndents
-          ? commentExpectedIndents.expectedIndent
-          : commentOptionalExpectedIndents[0]
-
-      validateCore(comment, commentExpectedIndent, commentOptionalExpectedIndents)
+      const commentExpectedIndent = commentExpectedIndents
+        ? commentExpectedIndents.expectedIndent
+        : commentOptionalExpectedIndents[0]
+
+      validateCore(
+        comment,
+        commentExpectedIndent,
+        commentOptionalExpectedIndents
+      )
     }
     validateCore(firstToken, expectedIndent)
   }
@@ -923,7 +1088,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
   // ------------------------------------------------------------------------------
 
   return processIgnores({
-    VAttribute (node) {
+    VAttribute(node) {
       const keyToken = tokenStore.getFirstToken(node)
       const eqToken = tokenStore.getTokenAfter(node.key)
 
@@ -937,11 +1102,17 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    VElement (node) {
+    VElement(node) {
       if (!PREFORMATTED_ELEMENT_NAMES.includes(node.name)) {
         const isTopLevel = node.parent.type !== 'VElement'
         const offset = isTopLevel ? options.baseIndent : 1
-        processNodeList(node.children.filter(isNotEmptyTextNode), node.startTag, node.endTag, offset, false)
+        processNodeList(
+          node.children.filter(isNotEmptyTextNode),
+          node.startTag,
+          node.endTag,
+          offset,
+          false
+        )
       } else {
         const startTagToken = tokenStore.getFirstToken(node)
         const endTagToken = node.endTag && tokenStore.getFirstToken(node.endTag)
@@ -950,7 +1121,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    VEndTag (node) {
+    VEndTag(node) {
       const element = node.parent
       const startTagOpenToken = tokenStore.getFirstToken(element.startTag)
       const closeToken = tokenStore.getLastToken(node)
@@ -960,8 +1131,11 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    VExpressionContainer (node) {
-      if (node.expression != null && node.range[0] !== node.expression.range[0]) {
+    VExpressionContainer(node) {
+      if (
+        node.expression != null &&
+        node.range[0] !== node.expression.range[0]
+      ) {
         const startQuoteToken = tokenStore.getFirstToken(node)
         const endQuoteToken = tokenStore.getLastToken(node)
         const childToken = tokenStore.getTokenAfter(startQuoteToken)
@@ -971,7 +1145,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    VFilter (node) {
+    VFilter(node) {
       const idToken = tokenStore.getFirstToken(node)
       const lastToken = tokenStore.getLastToken(node)
       if (isRightParen(lastToken)) {
@@ -981,7 +1155,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    VFilterSequenceExpression (node) {
+    VFilterSequenceExpression(node) {
       if (node.filters.length === 0) {
         return
       }
@@ -999,7 +1173,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       setOffset(tokens, 1, firstToken)
     },
 
-    VForExpression (node) {
+    VForExpression(node) {
       const firstToken = tokenStore.getFirstToken(node)
       const lastOfLeft = last(node.left) || firstToken
       const inToken = tokenStore.getTokenAfter(lastOfLeft, isNotRightParen)
@@ -1013,11 +1187,11 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       setOffset(rightToken, 1, inToken)
     },
 
-    VOnExpression (node) {
+    VOnExpression(node) {
       processNodeList(node.body, null, null, 0)
     },
 
-    VStartTag (node) {
+    VStartTag(node) {
       const openToken = tokenStore.getFirstToken(node)
       const closeToken = tokenStore.getLastToken(node)
 
@@ -1029,14 +1203,15 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
         options.alignAttributesVertically
       )
       if (closeToken != null && closeToken.type.endsWith('TagClose')) {
-        const offset = closeToken.type !== 'HTMLSelfClosingTagClose'
-          ? options.closeBracket.startTag
-          : options.closeBracket.selfClosingTag
+        const offset =
+          closeToken.type !== 'HTMLSelfClosingTagClose'
+            ? options.closeBracket.startTag
+            : options.closeBracket.selfClosingTag
         setOffset(closeToken, offset, openToken)
       }
     },
 
-    VText (node) {
+    VText(node) {
       const tokens = tokenStore.getTokens(node, isNotWhitespace)
       const firstTokenInfo = offsets.get(tokenStore.getFirstToken(node))
 
@@ -1045,11 +1220,16 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    'ArrayExpression, ArrayPattern' (node) {
-      processNodeList(node.elements, tokenStore.getFirstToken(node), tokenStore.getLastToken(node), 1)
+    'ArrayExpression, ArrayPattern'(node) {
+      processNodeList(
+        node.elements,
+        tokenStore.getFirstToken(node),
+        tokenStore.getLastToken(node),
+        1
+      )
     },
 
-    ArrowFunctionExpression (node) {
+    ArrowFunctionExpression(node) {
       const firstToken = tokenStore.getFirstToken(node)
       const secondToken = tokenStore.getTokenAfter(firstToken)
       const leftToken = node.async ? secondToken : firstToken
@@ -1059,7 +1239,10 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
         setOffset(secondToken, 1, firstToken)
       }
       if (isLeftParen(leftToken)) {
-        const rightToken = tokenStore.getTokenAfter(last(node.params) || leftToken, isRightParen)
+        const rightToken = tokenStore.getTokenAfter(
+          last(node.params) || leftToken,
+          isRightParen
+        )
         processNodeList(node.params, leftToken, rightToken, 1)
       }
 
@@ -1067,32 +1250,38 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       processMaybeBlock(node.body, firstToken)
     },
 
-    'AssignmentExpression, AssignmentPattern, BinaryExpression, LogicalExpression' (node) {
+    'AssignmentExpression, AssignmentPattern, BinaryExpression, LogicalExpression'(
+      node
+    ) {
       const leftToken = getChainHeadToken(node)
       const opToken = tokenStore.getTokenAfter(node.left, isNotRightParen)
       const rightToken = tokenStore.getTokenAfter(opToken)
       const prevToken = tokenStore.getTokenBefore(leftToken)
-      const shouldIndent = (
+      const shouldIndent =
         prevToken == null ||
         prevToken.loc.end.line === leftToken.loc.start.line ||
         isBeginningOfElement(leftToken, node)
-      )
 
       setOffset([opToken, rightToken], shouldIndent ? 1 : 0, leftToken)
     },
 
-    'AwaitExpression, RestElement, SpreadElement, UnaryExpression' (node) {
+    'AwaitExpression, RestElement, SpreadElement, UnaryExpression'(node) {
       const firstToken = tokenStore.getFirstToken(node)
       const nextToken = tokenStore.getTokenAfter(firstToken)
 
       setOffset(nextToken, 1, firstToken)
     },
 
-    'BlockStatement, ClassBody' (node) {
-      processNodeList(node.body, tokenStore.getFirstToken(node), tokenStore.getLastToken(node), 1)
+    'BlockStatement, ClassBody'(node) {
+      processNodeList(
+        node.body,
+        tokenStore.getFirstToken(node),
+        tokenStore.getLastToken(node),
+        1
+      )
     },
 
-    'BreakStatement, ContinueStatement, ReturnStatement, ThrowStatement' (node) {
+    'BreakStatement, ContinueStatement, ReturnStatement, ThrowStatement'(node) {
       if (node.argument != null || node.label != null) {
         const firstToken = tokenStore.getFirstToken(node)
         const nextToken = tokenStore.getTokenAfter(firstToken)
@@ -1101,7 +1290,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    CallExpression (node) {
+    CallExpression(node) {
       const firstToken = tokenStore.getFirstToken(node)
       const rightToken = tokenStore.getLastToken(node)
       const leftToken = tokenStore.getTokenAfter(node.callee, isLeftParen)
@@ -1110,7 +1299,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       processNodeList(node.arguments, leftToken, rightToken, 1)
     },
 
-    CatchClause (node) {
+    CatchClause(node) {
       const firstToken = tokenStore.getFirstToken(node)
       const bodyToken = tokenStore.getFirstToken(node.body)
 
@@ -1124,7 +1313,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       setOffset(bodyToken, 0, firstToken)
     },
 
-    'ClassDeclaration, ClassExpression' (node) {
+    'ClassDeclaration, ClassExpression'(node) {
       const firstToken = tokenStore.getFirstToken(node)
       const bodyToken = tokenStore.getFirstToken(node.body)
 
@@ -1140,12 +1329,15 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       setOffset(bodyToken, 0, firstToken)
     },
 
-    ConditionalExpression (node) {
+    ConditionalExpression(node) {
       const prevToken = tokenStore.getTokenBefore(node)
       const firstToken = tokenStore.getFirstToken(node)
       const questionToken = tokenStore.getTokenAfter(node.test, isNotRightParen)
       const consequentToken = tokenStore.getTokenAfter(questionToken)
-      const colonToken = tokenStore.getTokenAfter(node.consequent, isNotRightParen)
+      const colonToken = tokenStore.getTokenAfter(
+        node.consequent,
+        isNotRightParen
+      )
       const alternateToken = tokenStore.getTokenAfter(colonToken)
       const isFlat =
         prevToken &&
@@ -1153,20 +1345,26 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
         node.test.loc.end.line === node.consequent.loc.start.line
 
       if (isFlat) {
-        setOffset([questionToken, consequentToken, colonToken, alternateToken], 0, firstToken)
+        setOffset(
+          [questionToken, consequentToken, colonToken, alternateToken],
+          0,
+          firstToken
+        )
       } else {
         setOffset([questionToken, colonToken], 1, firstToken)
         setOffset([consequentToken, alternateToken], 1, questionToken)
       }
     },
 
-    DoWhileStatement (node) {
+    DoWhileStatement(node) {
       const doToken = tokenStore.getFirstToken(node)
       const whileToken = tokenStore.getTokenAfter(node.body, isNotRightParen)
       const leftToken = tokenStore.getTokenAfter(whileToken)
       const testToken = tokenStore.getTokenAfter(leftToken)
       const lastToken = tokenStore.getLastToken(node)
-      const rightToken = isSemicolon(lastToken) ? tokenStore.getTokenBefore(lastToken) : lastToken
+      const rightToken = isSemicolon(lastToken)
+        ? tokenStore.getTokenBefore(lastToken)
+        : lastToken
 
       processMaybeBlock(node.body, doToken)
       setOffset(whileToken, 0, doToken)
@@ -1175,7 +1373,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       setOffset(rightToken, 0, leftToken)
     },
 
-    ExportAllDeclaration (node) {
+    ExportAllDeclaration(node) {
       const tokens = tokenStore.getTokens(node)
       const firstToken = tokens.shift()
       if (isSemicolon(last(tokens))) {
@@ -1184,14 +1382,15 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       setOffset(tokens, 1, firstToken)
     },
 
-    ExportDefaultDeclaration (node) {
+    ExportDefaultDeclaration(node) {
       const exportToken = tokenStore.getFirstToken(node)
       const defaultToken = tokenStore.getFirstToken(node, 1)
-      const declarationToken = getFirstAndLastTokens(node.declaration).firstToken
+      const declarationToken = getFirstAndLastTokens(node.declaration)
+        .firstToken
       setOffset([defaultToken, declarationToken], 1, exportToken)
     },
 
-    ExportNamedDeclaration (node) {
+    ExportNamedDeclaration(node) {
       const exportToken = tokenStore.getFirstToken(node)
       if (node.declaration) {
         // export var foo = 1;
@@ -1205,7 +1404,10 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
         processNodeList(node.specifiers, leftParenToken, rightParenToken, 1)
 
         const maybeFromToken = tokenStore.getTokenAfter(rightParenToken)
-        if (maybeFromToken != null && sourceCode.getText(maybeFromToken) === 'from') {
+        if (
+          maybeFromToken != null &&
+          sourceCode.getText(maybeFromToken) === 'from'
+        ) {
           const fromToken = maybeFromToken
           const nameToken = tokenStore.getTokenAfter(fromToken)
           setOffset([fromToken, nameToken], 1, exportToken)
@@ -1213,19 +1415,22 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    ExportSpecifier (node) {
+    ExportSpecifier(node) {
       const tokens = tokenStore.getTokens(node)
       const firstToken = tokens.shift()
       setOffset(tokens, 1, firstToken)
     },
 
-    'ForInStatement, ForOfStatement' (node) {
+    'ForInStatement, ForOfStatement'(node) {
       const forToken = tokenStore.getFirstToken(node)
       const leftParenToken = tokenStore.getTokenAfter(forToken)
       const leftToken = tokenStore.getTokenAfter(leftParenToken)
       const inToken = tokenStore.getTokenAfter(leftToken, isNotRightParen)
       const rightToken = tokenStore.getTokenAfter(inToken)
-      const rightParenToken = tokenStore.getTokenBefore(node.body, isNotLeftParen)
+      const rightParenToken = tokenStore.getTokenBefore(
+        node.body,
+        isNotLeftParen
+      )
 
       setOffset(leftParenToken, 1, forToken)
       setOffset(leftToken, 1, leftParenToken)
@@ -1235,33 +1440,53 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       processMaybeBlock(node.body, forToken)
     },
 
-    ForStatement (node) {
+    ForStatement(node) {
       const forToken = tokenStore.getFirstToken(node)
       const leftParenToken = tokenStore.getTokenAfter(forToken)
-      const rightParenToken = tokenStore.getTokenBefore(node.body, isNotLeftParen)
+      const rightParenToken = tokenStore.getTokenBefore(
+        node.body,
+        isNotLeftParen
+      )
 
       setOffset(leftParenToken, 1, forToken)
-      processNodeList([node.init, node.test, node.update], leftParenToken, rightParenToken, 1)
+      processNodeList(
+        [node.init, node.test, node.update],
+        leftParenToken,
+        rightParenToken,
+        1
+      )
       processMaybeBlock(node.body, forToken)
     },
 
-    'FunctionDeclaration, FunctionExpression' (node) {
+    'FunctionDeclaration, FunctionExpression'(node) {
       const firstToken = tokenStore.getFirstToken(node)
       if (isLeftParen(firstToken)) {
         // Methods.
         const leftToken = firstToken
-        const rightToken = tokenStore.getTokenAfter(last(node.params) || leftToken, isRightParen)
+        const rightToken = tokenStore.getTokenAfter(
+          last(node.params) || leftToken,
+          isRightParen
+        )
         const bodyToken = tokenStore.getFirstToken(node.body)
 
         processNodeList(node.params, leftToken, rightToken, 1)
         setOffset(bodyToken, 0, tokenStore.getFirstToken(node.parent))
       } else {
         // Normal functions.
-        const functionToken = node.async ? tokenStore.getTokenAfter(firstToken) : firstToken
-        const starToken = node.generator ? tokenStore.getTokenAfter(functionToken) : null
+        const functionToken = node.async
+          ? tokenStore.getTokenAfter(firstToken)
+          : firstToken
+        const starToken = node.generator
+          ? tokenStore.getTokenAfter(functionToken)
+          : null
         const idToken = node.id && tokenStore.getFirstToken(node.id)
-        const leftToken = tokenStore.getTokenAfter(idToken || starToken || functionToken)
-        const rightToken = tokenStore.getTokenAfter(last(node.params) || leftToken, isRightParen)
+        const leftToken = tokenStore.getTokenAfter(
+          idToken || starToken || functionToken
+        )
+        const rightToken = tokenStore.getTokenAfter(
+          last(node.params) || leftToken,
+          isRightParen
+        )
         const bodyToken = tokenStore.getFirstToken(node.body)
 
         if (node.async) {
@@ -1279,24 +1504,30 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    IfStatement (node) {
+    IfStatement(node) {
       const ifToken = tokenStore.getFirstToken(node)
       const ifLeftParenToken = tokenStore.getTokenAfter(ifToken)
-      const ifRightParenToken = tokenStore.getTokenBefore(node.consequent, isRightParen)
+      const ifRightParenToken = tokenStore.getTokenBefore(
+        node.consequent,
+        isRightParen
+      )
 
       setOffset(ifLeftParenToken, 1, ifToken)
       setOffset(ifRightParenToken, 0, ifLeftParenToken)
       processMaybeBlock(node.consequent, ifToken)
 
       if (node.alternate != null) {
-        const elseToken = tokenStore.getTokenAfter(node.consequent, isNotRightParen)
+        const elseToken = tokenStore.getTokenAfter(
+          node.consequent,
+          isNotRightParen
+        )
 
         setOffset(elseToken, 0, ifToken)
         processMaybeBlock(node.alternate, elseToken)
       }
     },
 
-    ImportDeclaration (node) {
+    ImportDeclaration(node) {
       const firstSpecifier = node.specifiers[0]
       const secondSpecifier = node.specifiers[1]
       const importToken = tokenStore.getFirstToken(node)
@@ -1322,7 +1553,10 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
           tokens.push(tokenStore.getLastToken(node, hasSemi ? 1 : 0))
         }
       } else if (firstSpecifier.type === 'ImportDefaultSpecifier') {
-        if (secondSpecifier && secondSpecifier.type === 'ImportNamespaceSpecifier') {
+        if (
+          secondSpecifier &&
+          secondSpecifier.type === 'ImportNamespaceSpecifier'
+        ) {
           // There is a pattern:
           //     import Foo, * as foo from "foo"
           tokens.push(
@@ -1381,7 +1615,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       setOffset(tokens, 1, importToken)
     },
 
-    ImportSpecifier (node) {
+    ImportSpecifier(node) {
       if (node.local.range[0] !== node.imported.range[0]) {
         const tokens = tokenStore.getTokens(node)
         const firstToken = tokens.shift()
@@ -1389,13 +1623,13 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    ImportNamespaceSpecifier (node) {
+    ImportNamespaceSpecifier(node) {
       const tokens = tokenStore.getTokens(node)
       const firstToken = tokens.shift()
       setOffset(tokens, 1, firstToken)
     },
 
-    LabeledStatement (node) {
+    LabeledStatement(node) {
       const labelToken = tokenStore.getFirstToken(node)
       const colonToken = tokenStore.getTokenAfter(labelToken)
       const bodyToken = tokenStore.getTokenAfter(colonToken)
@@ -1403,12 +1637,18 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       setOffset([colonToken, bodyToken], 1, labelToken)
     },
 
-    'MemberExpression, MetaProperty' (node) {
+    'MemberExpression, MetaProperty'(node) {
       const objectToken = tokenStore.getFirstToken(node)
       if (node.computed) {
-        const leftBracketToken = tokenStore.getTokenBefore(node.property, isLeftBracket)
+        const leftBracketToken = tokenStore.getTokenBefore(
+          node.property,
+          isLeftBracket
+        )
         const propertyToken = tokenStore.getTokenAfter(leftBracketToken)
-        const rightBracketToken = tokenStore.getTokenAfter(node.property, isRightBracket)
+        const rightBracketToken = tokenStore.getTokenAfter(
+          node.property,
+          isRightBracket
+        )
 
         setOffset(leftBracketToken, 1, objectToken)
         setOffset(propertyToken, 1, leftBracketToken)
@@ -1421,8 +1661,8 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    'MethodDefinition, Property' (node) {
-      const isMethod = (node.type === 'MethodDefinition' || node.method === true)
+    'MethodDefinition, Property'(node) {
+      const isMethod = node.type === 'MethodDefinition' || node.method === true
       const prefixTokens = getPrefixTokens(node)
       const hasPrefix = prefixTokens.length >= 1
 
@@ -1434,7 +1674,10 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       if (node.computed) {
         const keyLeftToken = tokenStore.getFirstToken(node, isLeftBracket)
         const keyToken = tokenStore.getTokenAfter(keyLeftToken)
-        const keyRightToken = lastKeyToken = tokenStore.getTokenAfter(node.key, isRightBracket)
+        const keyRightToken = (lastKeyToken = tokenStore.getTokenAfter(
+          node.key,
+          isRightBracket
+        ))
 
         if (hasPrefix) {
           setOffset(keyLeftToken, 0, last(prefixTokens))
@@ -1442,7 +1685,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
         setOffset(keyToken, 1, keyLeftToken)
         setOffset(keyRightToken, 0, keyLeftToken)
       } else {
-        const idToken = lastKeyToken = tokenStore.getFirstToken(node.key)
+        const idToken = (lastKeyToken = tokenStore.getFirstToken(node.key))
 
         if (hasPrefix) {
           setOffset(idToken, 0, last(prefixTokens))
@@ -1461,7 +1704,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    NewExpression (node) {
+    NewExpression(node) {
       const newToken = tokenStore.getFirstToken(node)
       const calleeToken = tokenStore.getTokenAfter(newToken)
       const rightToken = tokenStore.getLastToken(node)
@@ -1476,15 +1719,20 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    'ObjectExpression, ObjectPattern' (node) {
-      processNodeList(node.properties, tokenStore.getFirstToken(node), tokenStore.getLastToken(node), 1)
+    'ObjectExpression, ObjectPattern'(node) {
+      processNodeList(
+        node.properties,
+        tokenStore.getFirstToken(node),
+        tokenStore.getLastToken(node),
+        1
+      )
     },
 
-    SequenceExpression (node) {
+    SequenceExpression(node) {
       processNodeList(node.expressions, null, null, 0)
     },
 
-    SwitchCase (node) {
+    SwitchCase(node) {
       const caseToken = tokenStore.getFirstToken(node)
 
       if (node.test != null) {
@@ -1498,7 +1746,10 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
         setOffset(colonToken, 1, caseToken)
       }
 
-      if (node.consequent.length === 1 && node.consequent[0].type === 'BlockStatement') {
+      if (
+        node.consequent.length === 1 &&
+        node.consequent[0].type === 'BlockStatement'
+      ) {
         setOffset(tokenStore.getFirstToken(node.consequent[0]), 0, caseToken)
       } else if (node.consequent.length >= 1) {
         setOffset(tokenStore.getFirstToken(node.consequent[0]), 1, caseToken)
@@ -1506,11 +1757,14 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    SwitchStatement (node) {
+    SwitchStatement(node) {
       const switchToken = tokenStore.getFirstToken(node)
       const leftParenToken = tokenStore.getTokenAfter(switchToken)
       const discriminantToken = tokenStore.getTokenAfter(leftParenToken)
-      const leftBraceToken = tokenStore.getTokenAfter(node.discriminant, isLeftBrace)
+      const leftBraceToken = tokenStore.getTokenAfter(
+        node.discriminant,
+        isLeftBrace
+      )
       const rightParenToken = tokenStore.getTokenBefore(leftBraceToken)
       const rightBraceToken = tokenStore.getLastToken(node)
 
@@ -1518,26 +1772,35 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       setOffset(discriminantToken, 1, leftParenToken)
       setOffset(rightParenToken, 0, leftParenToken)
       setOffset(leftBraceToken, 0, switchToken)
-      processNodeList(node.cases, leftBraceToken, rightBraceToken, options.switchCase)
+      processNodeList(
+        node.cases,
+        leftBraceToken,
+        rightBraceToken,
+        options.switchCase
+      )
     },
 
-    TaggedTemplateExpression (node) {
+    TaggedTemplateExpression(node) {
       const tagTokens = getFirstAndLastTokens(node.tag, node.range[0])
       const quasiToken = tokenStore.getTokenAfter(tagTokens.lastToken)
 
       setOffset(quasiToken, 1, tagTokens.firstToken)
     },
 
-    TemplateLiteral (node) {
+    TemplateLiteral(node) {
       const firstToken = tokenStore.getFirstToken(node)
-      const quasiTokens = node.quasis.slice(1).map(n => tokenStore.getFirstToken(n))
-      const expressionToken = node.quasis.slice(0, -1).map(n => tokenStore.getTokenAfter(n))
+      const quasiTokens = node.quasis
+        .slice(1)
+        .map((n) => tokenStore.getFirstToken(n))
+      const expressionToken = node.quasis
+        .slice(0, -1)
+        .map((n) => tokenStore.getTokenAfter(n))
 
       setOffset(quasiTokens, 0, firstToken)
       setOffset(expressionToken, 1, firstToken)
     },
 
-    TryStatement (node) {
+    TryStatement(node) {
       const tryToken = tokenStore.getFirstToken(node)
       const tryBlockToken = tokenStore.getFirstToken(node.block)
 
@@ -1557,18 +1820,23 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    UpdateExpression (node) {
+    UpdateExpression(node) {
       const firstToken = tokenStore.getFirstToken(node)
       const nextToken = tokenStore.getTokenAfter(firstToken)
 
       setOffset(nextToken, 1, firstToken)
     },
 
-    VariableDeclaration (node) {
-      processNodeList(node.declarations, tokenStore.getFirstToken(node), null, 1)
+    VariableDeclaration(node) {
+      processNodeList(
+        node.declarations,
+        tokenStore.getFirstToken(node),
+        null,
+        1
+      )
     },
 
-    VariableDeclarator (node) {
+    VariableDeclarator(node) {
       if (node.init != null) {
         const idToken = tokenStore.getFirstToken(node)
         const eqToken = tokenStore.getTokenAfter(node.id)
@@ -1578,7 +1846,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     },
 
-    'WhileStatement, WithStatement' (node) {
+    'WhileStatement, WithStatement'(node) {
       const firstToken = tokenStore.getFirstToken(node)
       const leftParenToken = tokenStore.getTokenAfter(firstToken)
       const rightParenToken = tokenStore.getTokenBefore(node.body, isRightParen)
@@ -1588,7 +1856,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       processMaybeBlock(node.body, firstToken)
     },
 
-    YieldExpression (node) {
+    YieldExpression(node) {
       if (node.argument != null) {
         const yieldToken = tokenStore.getFirstToken(node)
 
@@ -1600,7 +1868,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
     },
 
     // Process semicolons.
-    ':statement' (node) {
+    ':statement'(node) {
       const firstToken = tokenStore.getFirstToken(node)
       const lastToken = tokenStore.getLastToken(node)
       if (isSemicolon(lastToken) && firstToken !== lastToken) {
@@ -1613,14 +1881,18 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       //   ;[1,2,3].forEach(f)
       const info = offsets.get(firstToken)
       const prevToken = tokenStore.getTokenBefore(firstToken)
-      if (info != null && isSemicolon(prevToken) && prevToken.loc.end.line === firstToken.loc.start.line) {
+      if (
+        info != null &&
+        isSemicolon(prevToken) &&
+        prevToken.loc.end.line === firstToken.loc.start.line
+      ) {
         offsets.set(prevToken, info)
       }
     },
 
     // Process parentheses.
     // `:expression` does not match with MetaProperty and TemplateLiteral as a bug: https://github.com/estools/esquery/pull/59
-    ':expression, MetaProperty, TemplateLiteral' (node) {
+    ':expression, MetaProperty, TemplateLiteral'(node) {
       let leftToken = tokenStore.getTokenBefore(node)
       let rightToken = tokenStore.getTokenAfter(node)
       let firstToken = tokenStore.getFirstToken(node)
@@ -1636,33 +1908,33 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
     },
 
     // Ignore tokens of unknown nodes.
-    '*:exit' (node) {
+    '*:exit'(node) {
       if (!KNOWN_NODES.has(node.type)) {
         ignore(node)
       }
     },
 
     // Top-level process.
-    Program (node) {
+    Program(node) {
       const firstToken = node.tokens[0]
-      const isScriptTag = (
+      const isScriptTag =
         firstToken != null &&
         firstToken.type === 'Punctuator' &&
         firstToken.value === '<script>'
-      )
-      const baseIndent =
-        isScriptTag ? (options.indentSize * options.baseIndent) : 0
+      const baseIndent = isScriptTag
+        ? options.indentSize * options.baseIndent
+        : 0
 
       for (const statement of node.body) {
         processTopLevelNode(statement, baseIndent)
       }
     },
-    "VElement[parent.type!='VElement']" (node) {
+    "VElement[parent.type!='VElement']"(node) {
       processTopLevelNode(node, 0)
     },
 
     // Do validation.
-    ":matches(Program, VElement[parent.type!='VElement']):exit" (node) {
+    ":matches(Program, VElement[parent.type!='VElement']):exit"(node) {
       let comments = []
       let tokensOnSameLine = []
       let isBesideMultilineToken = false
@@ -1670,14 +1942,18 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
 
       // Validate indentation of tokens.
       for (const token of tokenStore.getTokens(node, ITERATION_OPTS)) {
-        if (tokensOnSameLine.length === 0 || tokensOnSameLine[0].loc.start.line === token.loc.start.line) {
+        if (
+          tokensOnSameLine.length === 0 ||
+          tokensOnSameLine[0].loc.start.line === token.loc.start.line
+        ) {
           // This is on the same line (or the first token).
           tokensOnSameLine.push(token)
         } else if (tokensOnSameLine.every(isComment)) {
           // New line is detected, but the all tokens of the previous line are comment.
           // Comment lines are adjusted to the next code line.
           comments.push(tokensOnSameLine[0])
-          isBesideMultilineToken = last(tokensOnSameLine).loc.end.line === token.loc.start.line
+          isBesideMultilineToken =
+            last(tokensOnSameLine).loc.end.line === token.loc.start.line
           tokensOnSameLine = [token]
         } else {
           // New line is detected, so validate the tokens.
@@ -1685,7 +1961,8 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
             validate(tokensOnSameLine, comments, lastValidatedToken)
             lastValidatedToken = tokensOnSameLine[0]
           }
-          isBesideMultilineToken = last(tokensOnSameLine).loc.end.line === token.loc.start.line
+          isBesideMultilineToken =
+            last(tokensOnSameLine).loc.end.line === token.loc.start.line
           tokensOnSameLine = [token]
           comments = []
         }


Test patch:
diff --git a/tests/lib/rules/html-indent.js b/tests/lib/rules/html-indent.js
index 81a77340..875b5df7 100644
--- a/tests/lib/rules/html-indent.js
+++ b/tests/lib/rules/html-indent.js
@@ -35,34 +35,36 @@ const FIXTURE_ROOT = path.resolve(__dirname, '../../fixtures/html-indent/')
  * @param {object[]} additionalInvalid The array of additional invalid patterns.
  * @returns {object} The loaded patterns.
  */
-function loadPatterns (additionalValid, additionalInvalid) {
-  const valid = fs
-    .readdirSync(FIXTURE_ROOT)
-    .map(filename => {
-      const code0 = fs.readFileSync(path.join(FIXTURE_ROOT, filename), 'utf8')
-      const code = code0.replace(/^<!--(.+?)-->/, `<!--${filename}-->`)
-      const baseObj = JSON.parse(/^<!--(.+?)-->/.exec(code0)[1])
-      return Object.assign(baseObj, { code, filename })
-    })
+function loadPatterns(additionalValid, additionalInvalid) {
+  const valid = fs.readdirSync(FIXTURE_ROOT).map((filename) => {
+    const code0 = fs.readFileSync(path.join(FIXTURE_ROOT, filename), 'utf8')
+    const code = code0.replace(/^<!--(.+?)-->/, `<!--${filename}-->`)
+    const baseObj = JSON.parse(/^<!--(.+?)-->/.exec(code0)[1])
+    return Object.assign(baseObj, { code, filename })
+  })
   const invalid = valid
-    .map(pattern => {
-      const kind = ((pattern.options && pattern.options[0]) === 'tab') ? 'tab' : 'space'
+    .map((pattern) => {
+      const kind =
+        (pattern.options && pattern.options[0]) === 'tab' ? 'tab' : 'space'
       const output = pattern.code
-      const lines = output
-        .split('\n')
-        .map((text, number) => ({
-          number,
-          text,
-          indentSize: (/^[ \t]+/.exec(text) || [''])[0].length
-        }))
+      const lines = output.split('\n').map((text, number) => ({
+        number,
+        text,
+        indentSize: (/^[ \t]+/.exec(text) || [''])[0].length
+      }))
       const code = lines
-        .map(line => line.text.replace(/^[ \t]+/, ''))
+        .map((line) => line.text.replace(/^[ \t]+/, ''))
         .join('\n')
       const errors = lines
-        .map(line =>
+        .map((line) =>
           line.indentSize === 0
             ? null
-            : { message: `Expected indentation of ${line.indentSize} ${kind}${line.indentSize === 1 ? '' : 's'} but found 0 ${kind}s.`, line: line.number + 1 }
+            : {
+                message: `Expected indentation of ${line.indentSize} ${kind}${
+                  line.indentSize === 1 ? '' : 's'
+                } but found 0 ${kind}s.`,
+                line: line.number + 1
+              }
         )
         .filter(Boolean)
 
@@ -81,13 +83,18 @@ function loadPatterns (additionalValid, additionalInvalid) {
  * @param {string[]} strings The strings in the template literal
  * @returns {string} The template literal, with spaces removed from all lines
  */
-function unIndent (strings) {
+function unIndent(strings) {
   const templateValue = strings[0]
-  const lines = templateValue.replace(/^\n/, '').replace(/\n\s*$/, '').split('\n')
-  const lineIndents = lines.filter(line => line.trim()).map(line => line.match(/ */)[0].length)
+  const lines = templateValue
+    .replace(/^\n/, '')
+    .replace(/\n\s*$/, '')
+    .split('\n')
+  const lineIndents = lines
+    .filter((line) => line.trim())
+    .map((line) => line.match(/ */)[0].length)
   const minLineIndent = Math.min.apply(null, lineIndents)
 
-  return lines.map(line => line.slice(minLineIndent)).join('\n')
+  return lines.map((line) => line.slice(minLineIndent)).join('\n')
 }
 
 // ------------------------------------------------------------------------------
@@ -104,13 +111,16 @@ const tester = new RuleTester({
   }
 })
 
-tester.run('html-indent', rule, loadPatterns(
-  // Valid
-  [
-    // TemplateLiteral
-    {
-      filename: 'test.vue',
-      code: unIndent`
+tester.run(
+  'html-indent',
+  rule,
+  loadPatterns(
+    // Valid
+    [
+      // TemplateLiteral
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <div
             v-bind:b="
@@ -123,23 +133,23 @@ tester.run('html-indent', rule, loadPatterns(
           ></div>
         </template>
       `
-    },
+      },
 
-    // VAttribute
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      // VAttribute
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <div a="
           a" b="b"></div>
         </template>
       `
-    },
+      },
 
-    // Comments
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      // Comments
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <!-- comment -->
           {{
@@ -149,10 +159,10 @@ tester.run('html-indent', rule, loadPatterns(
           }}
         </template>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           {{
             /*
@@ -162,10 +172,10 @@ tester.run('html-indent', rule, loadPatterns(
           }}
         </template>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           {{
             message
@@ -175,10 +185,10 @@ tester.run('html-indent', rule, loadPatterns(
           <!-- comment -->
         </template>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           {{
             message
@@ -188,10 +198,10 @@ tester.run('html-indent', rule, loadPatterns(
           }}
         </template>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           {{
             message
@@ -201,10 +211,10 @@ tester.run('html-indent', rule, loadPatterns(
           <!-- comment -->
         </template>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           {{
             message
@@ -214,46 +224,46 @@ tester.run('html-indent', rule, loadPatterns(
           }}
         </template>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <div>
             <!-- this comment is ignored because the next token doesn't exist. -->
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <div>
             <div></div>
             <!-- this comment is ignored because the next token doesn't exist. -->
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <div>
         <!-- this comment is ignored because the next token doesn't exist. -->
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <div>
             <div></div>
         <!-- this comment is ignored because the next token doesn't exist. -->
       `
-    },
+      },
 
-    // Ignores
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      // Ignores
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
               <div
             id
@@ -263,16 +273,19 @@ tester.run('html-indent', rule, loadPatterns(
         <span>
         </template>
       `,
-      options: [4, {
-        // Ignore all :D
-        ignores: ['*']
-      }]
-    },
+        options: [
+          4,
+          {
+            // Ignore all :D
+            ignores: ['*']
+          }
+        ]
+      },
 
-    // Pre, Textarea
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      // Pre, Textarea
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <pre>
         aaa
@@ -281,10 +294,10 @@ tester.run('html-indent', rule, loadPatterns(
           </pre>
         </template>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <textarea>
         aaa
@@ -293,10 +306,10 @@ tester.run('html-indent', rule, loadPatterns(
           </textarea>
         </template>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <pre>
         <span>aaa</span>
@@ -305,10 +318,10 @@ tester.run('html-indent', rule, loadPatterns(
           </pre>
         </template>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <pre>aaa
         bbb ccc
@@ -316,10 +329,10 @@ tester.run('html-indent', rule, loadPatterns(
         fff</pre>
         </template>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <pre><span>aaa</span>
         <span>bbb</span> <span>ccc</span>
@@ -327,10 +340,10 @@ tester.run('html-indent', rule, loadPatterns(
         <span>fff</span></pre>
         </template>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <div><pre>aaa
         bbb ccc
@@ -338,10 +351,10 @@ tester.run('html-indent', rule, loadPatterns(
         fff</pre></div>
         </template>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <div><textarea>aaa
         bbb ccc
@@ -349,10 +362,10 @@ tester.run('html-indent', rule, loadPatterns(
         fff</textarea></div>
         </template>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <pre>
         <!-- comment -->
@@ -360,10 +373,10 @@ tester.run('html-indent', rule, loadPatterns(
         <!-- comment --></pre>
         </template>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <pre>
         <!-- comment --> text <span />
@@ -381,15 +394,15 @@ tester.run('html-indent', rule, loadPatterns(
         text <span /> <!-- comment --></pre>
         </template>
       `
-    }
-  ],
+      }
+    ],
 
-  // Invalid
-  [
-    // TemplateLiteral
-    {
-      filename: 'test.vue',
-      code: unIndent`
+    // Invalid
+    [
+      // TemplateLiteral
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
             <div
                 v-bind:b="
@@ -402,7 +415,7 @@ tester.run('html-indent', rule, loadPatterns(
             ></div>
         </template>
       `,
-      output: unIndent`
+        output: unIndent`
         <template>
             <div
                 v-bind:b="
@@ -415,59 +428,68 @@ tester.run('html-indent', rule, loadPatterns(
             ></div>
         </template>
       `,
-      options: [4],
-      errors: [
-        { message: 'Expected indentation of 12 spaces but found 10 spaces.', line: 4 }
-      ]
-    },
+        options: [4],
+        errors: [
+          {
+            message: 'Expected indentation of 12 spaces but found 10 spaces.',
+            line: 4
+          }
+        ]
+      },
 
-    // A mix of spaces and tabs.
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      // A mix of spaces and tabs.
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           <div>
           \tHello
           </div>
         </template>
       `,
-      output: unIndent`
+        output: unIndent`
         <template>
           <div>
             Hello
           </div>
         </template>
       `,
-      errors: [
-        { message: 'Expected " " character, but found "\\t" character.', line: 3 }
-      ]
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+        errors: [
+          {
+            message: 'Expected " " character, but found "\\t" character.',
+            line: 3
+          }
+        ]
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
         \t<div>
         \t    Hello
         \t</div>
         </template>
       `,
-      output: unIndent`
+        output: unIndent`
         <template>
         \t<div>
         \t\tHello
         \t</div>
         </template>
       `,
-      options: ['tab'],
-      errors: [
-        { message: 'Expected "\\t" character, but found " " character.', line: 3 }
-      ]
-    },
+        options: ['tab'],
+        errors: [
+          {
+            message: 'Expected "\\t" character, but found " " character.',
+            line: 3
+          }
+        ]
+      },
 
-    // Comments
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      // Comments
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
         <!-- comment -->
         {{
@@ -477,7 +499,7 @@ tester.run('html-indent', rule, loadPatterns(
         }}
         </template>
       `,
-      output: unIndent`
+        output: unIndent`
         <template>
           <!-- comment -->
           {{
@@ -487,18 +509,36 @@ tester.run('html-indent', rule, loadPatterns(
           }}
         </template>
       `,
-      errors: [
-        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 2 },
-        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 3 },
-        { message: 'Expected indentation of 4 spaces but found 0 spaces.', line: 4 },
-        { message: 'Expected indentation of 4 spaces but found 0 spaces.', line: 5 },
-        { message: 'Expected indentation of 4 spaces but found 0 spaces.', line: 6 },
-        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 7 }
-      ]
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+        errors: [
+          {
+            message: 'Expected indentation of 2 spaces but found 0 spaces.',
+            line: 2
+          },
+          {
+            message: 'Expected indentation of 2 spaces but found 0 spaces.',
+            line: 3
+          },
+          {
+            message: 'Expected indentation of 4 spaces but found 0 spaces.',
+            line: 4
+          },
+          {
+            message: 'Expected indentation of 4 spaces but found 0 spaces.',
+            line: 5
+          },
+          {
+            message: 'Expected indentation of 4 spaces but found 0 spaces.',
+            line: 6
+          },
+          {
+            message: 'Expected indentation of 2 spaces but found 0 spaces.',
+            line: 7
+          }
+        ]
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
           {{
           /*
@@ -508,7 +548,7 @@ tester.run('html-indent', rule, loadPatterns(
           }}
         </template>
       `,
-      output: unIndent`
+        output: unIndent`
         <template>
           {{
             /*
@@ -518,14 +558,20 @@ tester.run('html-indent', rule, loadPatterns(
           }}
         </template>
       `,
-      errors: [
-        { message: 'Expected indentation of 4 spaces but found 2 spaces.', line: 3 },
-        { message: 'Expected indentation of 4 spaces but found 2 spaces.', line: 6 }
-      ]
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+        errors: [
+          {
+            message: 'Expected indentation of 4 spaces but found 2 spaces.',
+            line: 3
+          },
+          {
+            message: 'Expected indentation of 4 spaces but found 2 spaces.',
+            line: 6
+          }
+        ]
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
         {{
         message
@@ -535,7 +581,7 @@ tester.run('html-indent', rule, loadPatterns(
         <!-- comment -->
         </template>
       `,
-      output: unIndent`
+        output: unIndent`
         <template>
           {{
             message
@@ -545,17 +591,32 @@ tester.run('html-indent', rule, loadPatterns(
         <!-- comment -->
         </template>
       `,
-      errors: [
-        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 2 },
-        { message: 'Expected indentation of 4 spaces but found 0 spaces.', line: 3 },
-        { message: 'Expected indentation of 4 spaces but found 0 spaces.', line: 4 },
-        { message: 'Expected indentation of 4 spaces but found 0 spaces.', line: 5 },
-        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 6 }
-      ]
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+        errors: [
+          {
+            message: 'Expected indentation of 2 spaces but found 0 spaces.',
+            line: 2
+          },
+          {
+            message: 'Expected indentation of 4 spaces but found 0 spaces.',
+            line: 3
+          },
+          {
+            message: 'Expected indentation of 4 spaces but found 0 spaces.',
+            line: 4
+          },
+          {
+            message: 'Expected indentation of 4 spaces but found 0 spaces.',
+            line: 5
+          },
+          {
+            message: 'Expected indentation of 2 spaces but found 0 spaces.',
+            line: 6
+          }
+        ]
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
         {{
         message
@@ -565,7 +626,7 @@ tester.run('html-indent', rule, loadPatterns(
         }}
         </template>
       `,
-      output: unIndent`
+        output: unIndent`
         <template>
           {{
             message
@@ -575,18 +636,30 @@ tester.run('html-indent', rule, loadPatterns(
           }}
         </template>
       `,
-      errors: [
-        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 2 },
-        { message: 'Expected indentation of 4 spaces but found 0 spaces.', line: 3 },
-        { message: 'Expected indentation of 4 spaces but found 0 spaces.', line: 4 },
-        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 7 }
-      ]
-    },
+        errors: [
+          {
+            message: 'Expected indentation of 2 spaces but found 0 spaces.',
+            line: 2
+          },
+          {
+            message: 'Expected indentation of 4 spaces but found 0 spaces.',
+            line: 3
+          },
+          {
+            message: 'Expected indentation of 4 spaces but found 0 spaces.',
+            line: 4
+          },
+          {
+            message: 'Expected indentation of 2 spaces but found 0 spaces.',
+            line: 7
+          }
+        ]
+      },
 
-    // Ignores
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      // Ignores
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
             <div
             id=""
@@ -594,7 +667,7 @@ tester.run('html-indent', rule, loadPatterns(
                 />
         </template>
       `,
-      output: unIndent`
+        output: unIndent`
         <template>
             <div
             id=""
@@ -602,16 +675,22 @@ tester.run('html-indent', rule, loadPatterns(
             />
         </template>
       `,
-      options: [4, {
-        ignores: ['VAttribute']
-      }],
-      errors: [
-        { message: 'Expected indentation of 4 spaces but found 8 spaces.', line: 5 }
-      ]
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+        options: [
+          4,
+          {
+            ignores: ['VAttribute']
+          }
+        ],
+        errors: [
+          {
+            message: 'Expected indentation of 4 spaces but found 8 spaces.',
+            line: 5
+          }
+        ]
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
             {{
               obj
@@ -621,7 +700,7 @@ tester.run('html-indent', rule, loadPatterns(
             }}
         </template>
       `,
-      output: unIndent`
+        output: unIndent`
         <template>
             {{
                 obj
@@ -631,21 +710,33 @@ tester.run('html-indent', rule, loadPatterns(
             }}
         </template>
       `,
-      options: [4, {
-        // Ignore inside of computed properties.
-        ignores: ['MemberExpression[computed=true] *.property']
-      }],
-      errors: [
-        { message: 'Expected indentation of 8 spaces but found 6 spaces.', line: 3 },
-        { message: 'Expected indentation of 12 spaces but found 8 spaces.', line: 4 },
-        { message: 'Expected indentation of 12 spaces but found 8 spaces.', line: 6 }
-      ]
-    },
+        options: [
+          4,
+          {
+            // Ignore inside of computed properties.
+            ignores: ['MemberExpression[computed=true] *.property']
+          }
+        ],
+        errors: [
+          {
+            message: 'Expected indentation of 8 spaces but found 6 spaces.',
+            line: 3
+          },
+          {
+            message: 'Expected indentation of 12 spaces but found 8 spaces.',
+            line: 4
+          },
+          {
+            message: 'Expected indentation of 12 spaces but found 8 spaces.',
+            line: 6
+          }
+        ]
+      },
 
-    // Pre, Textarea
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      // Pre, Textarea
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
         <pre
         style=""
@@ -656,7 +747,7 @@ tester.run('html-indent', rule, loadPatterns(
         </pre>
         </template>
       `,
-      output: unIndent`
+        output: unIndent`
         <template>
           <pre
             style=""
@@ -667,15 +758,24 @@ tester.run('html-indent', rule, loadPatterns(
         </pre>
         </template>
       `,
-      errors: [
-        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 2 },
-        { message: 'Expected indentation of 4 spaces but found 0 spaces.', line: 3 },
-        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 4 }
-      ]
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+        errors: [
+          {
+            message: 'Expected indentation of 2 spaces but found 0 spaces.',
+            line: 2
+          },
+          {
+            message: 'Expected indentation of 4 spaces but found 0 spaces.',
+            line: 3
+          },
+          {
+            message: 'Expected indentation of 2 spaces but found 0 spaces.',
+            line: 4
+          }
+        ]
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
         <pre
         :class="[
@@ -690,7 +790,7 @@ tester.run('html-indent', rule, loadPatterns(
         </pre>
         </template>
       `,
-      output: unIndent`
+        output: unIndent`
         <template>
           <pre
             :class="[
@@ -705,19 +805,40 @@ tester.run('html-indent', rule, loadPatterns(
         </pre>
         </template>
       `,
-      errors: [
-        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 2 },
-        { message: 'Expected indentation of 4 spaces but found 0 spaces.', line: 3 },
-        { message: 'Expected indentation of 6 spaces but found 0 spaces.', line: 4 },
-        { message: 'Expected indentation of 6 spaces but found 0 spaces.', line: 5 },
-        { message: 'Expected indentation of 6 spaces but found 0 spaces.', line: 6 },
-        { message: 'Expected indentation of 4 spaces but found 0 spaces.', line: 7 },
-        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 8 }
-      ]
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+        errors: [
+          {
+            message: 'Expected indentation of 2 spaces but found 0 spaces.',
+            line: 2
+          },
+          {
+            message: 'Expected indentation of 4 spaces but found 0 spaces.',
+            line: 3
+          },
+          {
+            message: 'Expected indentation of 6 spaces but found 0 spaces.',
+            line: 4
+          },
+          {
+            message: 'Expected indentation of 6 spaces but found 0 spaces.',
+            line: 5
+          },
+          {
+            message: 'Expected indentation of 6 spaces but found 0 spaces.',
+            line: 6
+          },
+          {
+            message: 'Expected indentation of 4 spaces but found 0 spaces.',
+            line: 7
+          },
+          {
+            message: 'Expected indentation of 2 spaces but found 0 spaces.',
+            line: 8
+          }
+        ]
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
         <textarea
         :class="[
@@ -732,7 +853,7 @@ tester.run('html-indent', rule, loadPatterns(
         </textarea>
         </template>
       `,
-      output: unIndent`
+        output: unIndent`
         <template>
           <textarea
             :class="[
@@ -747,34 +868,59 @@ tester.run('html-indent', rule, loadPatterns(
         </textarea>
         </template>
       `,
-      errors: [
-        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 2 },
-        { message: 'Expected indentation of 4 spaces but found 0 spaces.', line: 3 },
-        { message: 'Expected indentation of 6 spaces but found 0 spaces.', line: 4 },
-        { message: 'Expected indentation of 6 spaces but found 0 spaces.', line: 5 },
-        { message: 'Expected indentation of 6 spaces but found 0 spaces.', line: 6 },
-        { message: 'Expected indentation of 4 spaces but found 0 spaces.', line: 7 },
-        { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 8 }
-      ]
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+        errors: [
+          {
+            message: 'Expected indentation of 2 spaces but found 0 spaces.',
+            line: 2
+          },
+          {
+            message: 'Expected indentation of 4 spaces but found 0 spaces.',
+            line: 3
+          },
+          {
+            message: 'Expected indentation of 6 spaces but found 0 spaces.',
+            line: 4
+          },
+          {
+            message: 'Expected indentation of 6 spaces but found 0 spaces.',
+            line: 5
+          },
+          {
+            message: 'Expected indentation of 6 spaces but found 0 spaces.',
+            line: 6
+          },
+          {
+            message: 'Expected indentation of 4 spaces but found 0 spaces.',
+            line: 7
+          },
+          {
+            message: 'Expected indentation of 2 spaces but found 0 spaces.',
+            line: 8
+          }
+        ]
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <template>
         \t <div attr1
         \t\t attr2/>
         </template>
       `,
-      output: unIndent`
+        output: unIndent`
         <template>
         \t<div attr1
         \t\t attr2/>
         </template>
       `,
-      options: ['tab', { 'ignores': ['VAttribute'] }],
-      errors: [
-        { message: 'Expected "\\t" character, but found " " character.', line: 2 }
-      ]
-    }
-  ]
-))
+        options: ['tab', { ignores: ['VAttribute'] }],
+        errors: [
+          {
+            message: 'Expected "\\t" character, but found " " character.',
+            line: 2
+          }
+        ]
+      }
+    ]
+  )
+)
diff --git a/tests/lib/rules/no-deprecated-dollar-scopedslots-api.js b/tests/lib/rules/no-deprecated-dollar-scopedslots-api.js
new file mode 100644
index 00000000..303e5de9
--- /dev/null
+++ b/tests/lib/rules/no-deprecated-dollar-scopedslots-api.js
@@ -0,0 +1,288 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-deprecated-dollar-scopedslots-api')
+
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2018, sourceType: 'module' }
+})
+ruleTester.run('no-deprecated-dollar-scopedslots-api', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-bind="$attrs"/>
+        </template>
+        <script>
+        export default {
+          mounted () {
+            this.$emit('start')
+          }
+        }
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script>
+        export default {
+          methods: {
+            click () {
+              this.$emit('click')
+            }
+          }
+        }
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script>
+        export default {
+        }
+        const another = function () {
+          console.log(this.$scopedSlots)
+        }
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div foo="$scopedSlots"/>
+        </template>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-on="() => {
+            function click ($scopedSlots) {
+              fn(foo.$scopedSlots)
+              fn($scopedSlots)
+            }
+          }"/>
+          <div v-for="$scopedSlots in list">
+            <div v-on="$scopedSlots">
+          </div>
+          <VueComp>
+            <template v-slot="{$scopedSlots}">
+              <div v-on="$scopedSlots">
+            </template>
+          </VueComp>
+        </template>
+        <script>
+        export default {
+          methods: {
+            click ($scopedSlots) {
+              foo.$scopedSlots
+            }
+          }
+        }
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script>
+        export default {
+          computed: {
+            foo () {
+              const {vm} = this
+              return vm.$scopedSlots
+            }
+          }
+        }
+        </script>
+      `
+    }
+  ],
+
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-if="$scopedSlots.default"/>
+        </template>
+        <script>
+        export default {
+          render() {
+            return this.$scopedSlots.foo('bar')
+          }
+        }
+        </script>
+      `,
+      output: `
+        <template>
+          <div v-if="$slots.default"/>
+        </template>
+        <script>
+        export default {
+          render() {
+            return this.$slots.foo('bar')
+          }
+        }
+        </script>
+      `,
+      errors: [
+        {
+          line: 3,
+          column: 22,
+          messageId: 'deprecated',
+          endLine: 3,
+          endColumn: 34
+        },
+        {
+          line: 8,
+          column: 25,
+          messageId: 'deprecated',
+          endLine: 8,
+          endColumn: 37
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-for="slot in $scopedSlots"/>
+          <div :foo="$scopedSlots"/>
+        </template>
+        <script>
+        export default {
+          computed: {
+            foo () {
+              fn(this.$scopedSlots)
+            }
+          }
+        }
+        </script>
+      `,
+      output: `
+        <template>
+          <div v-for="slot in $slots"/>
+          <div :foo="$slots"/>
+        </template>
+        <script>
+        export default {
+          computed: {
+            foo () {
+              fn(this.$slots)
+            }
+          }
+        }
+        </script>
+      `,
+      errors: [
+        {
+          line: 3,
+          column: 31,
+          messageId: 'deprecated',
+          endLine: 3,
+          endColumn: 43
+        },
+        {
+          line: 4,
+          column: 22,
+          messageId: 'deprecated',
+          endLine: 4,
+          endColumn: 34
+        },
+        {
+          line: 10,
+          column: 23,
+          messageId: 'deprecated',
+          endLine: 10,
+          endColumn: 35
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script>
+        export default {
+          render() {
+            const vm = this
+            return vm.$scopedSlots.foo('bar')
+          }
+        }
+        </script>
+      `,
+      output: `
+        <script>
+        export default {
+          render() {
+            const vm = this
+            return vm.$slots.foo('bar')
+          }
+        }
+        </script>
+      `,
+      errors: [
+        {
+          line: 6,
+          column: 23,
+          messageId: 'deprecated'
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script>
+        export default {
+          render() {
+            const vm = this
+            function fn() {
+              return vm.$scopedSlots
+            }
+            return fn().foo('bar')
+          }
+        }
+        </script>
+      `,
+      output: `
+        <script>
+        export default {
+          render() {
+            const vm = this
+            function fn() {
+              return vm.$slots
+            }
+            return fn().foo('bar')
+          }
+        }
+        </script>
+      `,
+      errors: [
+        {
+          line: 7,
+          column: 25,
+          messageId: 'deprecated'
+        }
+      ]
+    }
+  ]
+})
diff --git a/tests/lib/rules/no-multiple-slot-args.js b/tests/lib/rules/no-multiple-slot-args.js
new file mode 100644
index 00000000..fb42767b
--- /dev/null
+++ b/tests/lib/rules/no-multiple-slot-args.js
@@ -0,0 +1,164 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-multiple-slot-args')
+
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2018, sourceType: 'module' }
+})
+ruleTester.run('no-multiple-slot-args', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        render (h) {
+          var children = this.$scopedSlots.default()
+          var children = this.$scopedSlots.foo(foo)
+          const bar = this.$scopedSlots.bar
+          bar(foo)
+        }
+      }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        render (h) {
+          unknown.$scopedSlots.default(foo, bar)
+        }
+      }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        render (h) {
+          // for Vue3
+          var children = this.$slots.default()
+          var children = this.$slots.foo(foo)
+          const bar = this.$slots.bar
+          bar(foo)
+        }
+      }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        render (h) {
+          this.$foo.default(foo, bar)
+        }
+      }
+      </script>
+      `
+    }
+  ],
+
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        render (h) {
+          this.$scopedSlots.default(foo, bar)
+          this.$scopedSlots.foo(foo, bar)
+        }
+      }
+      </script>
+      `,
+      errors: [
+        {
+          message: 'Unexpected multiple arguments.',
+          line: 5,
+          column: 42,
+          endLine: 5,
+          endColumn: 45
+        },
+        {
+          message: 'Unexpected multiple arguments.',
+          line: 6,
+          column: 38,
+          endLine: 6,
+          endColumn: 41
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        render (h) {
+          let children
+
+          this.$scopedSlots.default(foo, { bar })
+
+          children = this.$scopedSlots.foo
+          if (children) children(...foo)
+        }
+      }
+      </script>
+      `,
+      errors: [
+        {
+          message: 'Unexpected multiple arguments.',
+          line: 7,
+          column: 42,
+          endLine: 7,
+          endColumn: 49
+        },
+        {
+          message: 'Unexpected spread argument.',
+          line: 10,
+          column: 34,
+          endLine: 10,
+          endColumn: 40
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        render (h) {
+          // for Vue3
+          this.$slots.default(foo, bar)
+          this.$slots.foo(foo, bar)
+        }
+      }
+      </script>
+      `,
+      errors: [
+        'Unexpected multiple arguments.',
+        'Unexpected multiple arguments.'
+      ]
+    }
+  ]
+})
diff --git a/tests/lib/rules/no-ref-as-operand.js b/tests/lib/rules/no-ref-as-operand.js
index 6eeb11b5..5d53a557 100644
--- a/tests/lib/rules/no-ref-as-operand.js
+++ b/tests/lib/rules/no-ref-as-operand.js
@@ -103,7 +103,25 @@ tester.run('no-ref-as-operand', rule, {
     import { ref } from 'vue'
     const count = ref
     count++
-    `
+    `,
+    {
+      code: `
+      <script>
+        import { ref, computed, toRef, customRef, shallowRef } from 'vue'
+        const foo = shallowRef({})
+        foo[bar] = 123
+      </script>
+      `
+    },
+    {
+      code: `
+      <script>
+        import { ref, computed, toRef, customRef, shallowRef } from 'vue'
+        const foo = shallowRef({})
+        const isComp = foo.effect
+      </script>
+      `
+    }
   ],
   invalid: [
     {
@@ -332,6 +350,89 @@ tester.run('no-ref-as-operand', rule, {
           line: 9
         }
       ]
+    },
+    {
+      code: `
+      <script>
+        import { ref, computed, toRef, customRef, shallowRef } from 'vue'
+        let count = ref(0)
+        let cntcnt = computed(()=>count.value+count.value)
+
+        const state = reactive({
+          foo: 1,
+          bar: 2
+        })
+
+        const fooRef = toRef(state, 'foo')
+
+        let value = 'hello'
+        const cref = customRef((track, trigger) => {
+          return {
+            get() {
+              track()
+              return value
+            },
+            set(newValue) {
+              clearTimeout(timeout)
+              timeout = setTimeout(() => {
+                value = newValue
+                trigger()
+              }, delay)
+            }
+          }
+        })
+
+        const foo = shallowRef({})
+
+        count++ // error
+        cntcnt++ // error
+
+        const s = \`\${fooRef} : \${cref}\` // error x 2
+
+        const n = foo + 1 // error
+      </script>
+      `,
+      errors: [
+        {
+          message:
+            'Must use `.value` to read or write the value wrapped by `ref()`.',
+          line: 33
+        },
+        {
+          message:
+            'Must use `.value` to read or write the value wrapped by `computed()`.',
+          line: 34
+        },
+        {
+          message:
+            'Must use `.value` to read or write the value wrapped by `toRef()`.',
+          line: 36
+        },
+        {
+          message:
+            'Must use `.value` to read or write the value wrapped by `customRef()`.',
+          line: 36
+        },
+        {
+          message:
+            'Must use `.value` to read or write the value wrapped by `shallowRef()`.',
+          line: 38
+        }
+      ]
+    },
+    {
+      code: `
+      <script>
+        import { ref, computed, toRef, customRef, shallowRef } from 'vue'
+        const foo = shallowRef({})
+        foo.bar = 123
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'requireDotValue'
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/require-slots-as-functions.js b/tests/lib/rules/require-slots-as-functions.js
new file mode 100644
index 00000000..9d79a1ec
--- /dev/null
+++ b/tests/lib/rules/require-slots-as-functions.js
@@ -0,0 +1,115 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/require-slots-as-functions')
+
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2018, sourceType: 'module' }
+})
+ruleTester.run('require-slots-as-functions', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        render (h) {
+          var children = this.$slots.default()
+          var children = this.$slots.default && this.$slots.default()
+
+          return h('div', this.$slots.default)
+        }
+      }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        render (h) {
+          var children = unknown.$slots.default
+          var children = unknown.$slots.default.filter(test)
+
+          return h('div', [...children])
+        }
+      }
+      </script>
+      `
+    }
+  ],
+
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        render (h) {
+          var children = this.$slots.default
+          var children = this.$slots.default.filter(test)
+
+          return h('div', [...children])
+        }
+      }
+      </script>
+      `,
+      errors: [
+        {
+          message: 'Property in `$slots` should be used as function.',
+          line: 5,
+          column: 38,
+          endLine: 5,
+          endColumn: 45
+        },
+        {
+          message: 'Property in `$slots` should be used as function.',
+          line: 6,
+          column: 38,
+          endLine: 6,
+          endColumn: 45
+        }
+      ]
+    },
+
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        render (h) {
+          let children
+
+          const [node] = this.$slots.foo
+          const bar = [this.$slots[foo]]
+
+          children = this.$slots.foo
+
+          return h('div', children.filter(test))
+        }
+      }
+      </script>
+      `,
+      errors: [
+        'Property in `$slots` should be used as function.',
+        'Property in `$slots` should be used as function.',
+        'Property in `$slots` should be used as function.'
+      ]
+    }
+  ]
+})
diff --git a/tests/lib/rules/script-indent.js b/tests/lib/rules/script-indent.js
index 53750272..d74d41dd 100644
--- a/tests/lib/rules/script-indent.js
+++ b/tests/lib/rules/script-indent.js
@@ -35,35 +35,37 @@ const FIXTURE_ROOT = path.resolve(__dirname, '../../fixtures/script-indent/')
  * @param {object[]} additionalInvalid The array of additional invalid patterns.
  * @returns {object} The loaded patterns.
  */
-function loadPatterns (additionalValid, additionalInvalid) {
-  const valid = fs
-    .readdirSync(FIXTURE_ROOT)
-    .map(filename => {
-      const commentPattern = /^(<!--|\/\*)(.+?)(-->|\*\/)/
-      const code0 = fs.readFileSync(path.join(FIXTURE_ROOT, filename), 'utf8')
-      const code = code0.replace(commentPattern, `$1${filename}$3`)
-      const baseObj = JSON.parse(commentPattern.exec(code0)[2])
-      return Object.assign(baseObj, { code, filename })
-    })
+function loadPatterns(additionalValid, additionalInvalid) {
+  const valid = fs.readdirSync(FIXTURE_ROOT).map((filename) => {
+    const commentPattern = /^(<!--|\/\*)(.+?)(-->|\*\/)/
+    const code0 = fs.readFileSync(path.join(FIXTURE_ROOT, filename), 'utf8')
+    const code = code0.replace(commentPattern, `$1${filename}$3`)
+    const baseObj = JSON.parse(commentPattern.exec(code0)[2])
+    return Object.assign(baseObj, { code, filename })
+  })
   const invalid = valid
-    .map(pattern => {
-      const kind = ((pattern.options && pattern.options[0]) === 'tab') ? 'tab' : 'space'
+    .map((pattern) => {
+      const kind =
+        (pattern.options && pattern.options[0]) === 'tab' ? 'tab' : 'space'
       const output = pattern.code
-      const lines = output
-        .split('\n')
-        .map((text, number) => ({
-          number,
-          text,
-          indentSize: (/^[ \t]+/.exec(text) || [''])[0].length
-        }))
+      const lines = output.split('\n').map((text, number) => ({
+        number,
+        text,
+        indentSize: (/^[ \t]+/.exec(text) || [''])[0].length
+      }))
       const code = lines
-        .map(line => line.text.replace(/^[ \t]+/, ''))
+        .map((line) => line.text.replace(/^[ \t]+/, ''))
         .join('\n')
       const errors = lines
-        .map(line =>
+        .map((line) =>
           line.indentSize === 0
             ? null
-            : { message: `Expected indentation of ${line.indentSize} ${kind}${line.indentSize === 1 ? '' : 's'} but found 0 ${kind}s.`, line: line.number + 1 }
+            : {
+                message: `Expected indentation of ${line.indentSize} ${kind}${
+                  line.indentSize === 1 ? '' : 's'
+                } but found 0 ${kind}s.`,
+                line: line.number + 1
+              }
         )
         .filter(Boolean)
 
@@ -82,13 +84,18 @@ function loadPatterns (additionalValid, additionalInvalid) {
  * @param {string[]} strings The strings in the template literal
  * @returns {string} The template literal, with spaces removed from all lines
  */
-function unIndent (strings) {
+function unIndent(strings) {
   const templateValue = strings[0]
-  const lines = templateValue.replace(/^\n/, '').replace(/\n\s*$/, '').split('\n')
-  const lineIndents = lines.filter(line => line.trim()).map(line => line.match(/ */)[0].length)
+  const lines = templateValue
+    .replace(/^\n/, '')
+    .replace(/\n\s*$/, '')
+    .split('\n')
+  const lineIndents = lines
+    .filter((line) => line.trim())
+    .map((line) => line.match(/ */)[0].length)
   const minLineIndent = Math.min.apply(null, lineIndents)
 
-  return lines.map(line => line.slice(minLineIndent)).join('\n')
+  return lines.map((line) => line.slice(minLineIndent)).join('\n')
 }
 
 // ------------------------------------------------------------------------------
@@ -103,13 +110,16 @@ const tester = new RuleTester({
   }
 })
 
-tester.run('script-indent', rule, loadPatterns(
-  // Valid
-  [
-    // TemplateLiteral
-    {
-      filename: 'test.vue',
-      code: unIndent`
+tester.run(
+  'script-indent',
+  rule,
+  loadPatterns(
+    // Valid
+    [
+      // TemplateLiteral
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <script>
         \`
           test
@@ -118,22 +128,22 @@ tester.run('script-indent', rule, loadPatterns(
           \`
         </script>
       `
-    },
+      },
 
-    // Comments
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      // Comments
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <script>
         // comment
         // comment
         foo
         </script>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <script>
         /*
         * comment
@@ -141,10 +151,10 @@ tester.run('script-indent', rule, loadPatterns(
         message
         </script>
       `
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <script>
         message
         /*
@@ -152,12 +162,12 @@ tester.run('script-indent', rule, loadPatterns(
         */
         </script>
       `
-    },
+      },
 
-    // Ignores files other than .vue
-    {
-      filename: 'test.js',
-      code: unIndent`
+      // Ignores files other than .vue
+      {
+        filename: 'test.js',
+        code: unIndent`
         <script>
           \`
           test
@@ -166,12 +176,12 @@ tester.run('script-indent', rule, loadPatterns(
           \`
         </script>
       `
-    },
+      },
 
-    // Ignores
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      // Ignores
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <script>
         var a
                 =
@@ -179,19 +189,22 @@ tester.run('script-indent', rule, loadPatterns(
         2
         </script>
       `,
-      options: [4, {
-        // Ignore all :D
-        ignores: ['*']
-      }]
-    }
-  ],
+        options: [
+          4,
+          {
+            // Ignore all :D
+            ignores: ['*']
+          }
+        ]
+      }
+    ],
 
-  // Invalid
-  [
-    // TemplateLiteral
-    {
-      filename: 'test.vue',
-      code: unIndent`
+    // Invalid
+    [
+      // TemplateLiteral
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <script>
           \`
           test
@@ -200,7 +213,7 @@ tester.run('script-indent', rule, loadPatterns(
           \`
         </script>
       `,
-      output: unIndent`
+        output: unIndent`
         <script>
         \`
           test
@@ -209,34 +222,40 @@ tester.run('script-indent', rule, loadPatterns(
           \`
         </script>
       `,
-      options: [4],
-      errors: [
-        { message: 'Expected indentation of 0 spaces but found 2 spaces.', line: 2 }
-      ]
-    },
+        options: [4],
+        errors: [
+          {
+            message: 'Expected indentation of 0 spaces but found 2 spaces.',
+            line: 2
+          }
+        ]
+      },
 
-    // A mix of spaces and tabs.
-    {
-      filename: 'test.vue',
-      code: unIndent`
+      // A mix of spaces and tabs.
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <script>
         var a =
         \t1
         </script>
       `,
-      output: unIndent`
+        output: unIndent`
         <script>
         var a =
           1
         </script>
       `,
-      errors: [
-        { message: 'Expected " " character, but found "\\t" character.', line: 3 }
-      ]
-    },
-    {
-      filename: 'test.vue',
-      code: unIndent`
+        errors: [
+          {
+            message: 'Expected " " character, but found "\\t" character.',
+            line: 3
+          }
+        ]
+      },
+      {
+        filename: 'test.vue',
+        code: unIndent`
         <script>
         var obj = {
           a: 1,
@@ -244,7 +263,7 @@ tester.run('script-indent', rule, loadPatterns(
         }
         </script>
       `,
-      output: unIndent`
+        output: unIndent`
         <script>
         var obj = {
         \ta: 1,
@@ -252,11 +271,18 @@ tester.run('script-indent', rule, loadPatterns(
         }
         </script>
       `,
-      options: ['tab'],
-      errors: [
-        { message: 'Expected "\\t" character, but found " " character.', line: 3 },
-        { message: 'Expected "\\t" character, but found " " character.', line: 4 }
-      ]
-    }
-  ]
-))
+        options: ['tab'],
+        errors: [
+          {
+            message: 'Expected "\\t" character, but found " " character.',
+            line: 3
+          },
+          {
+            message: 'Expected "\\t" character, but found " " character.',
+            line: 4
+          }
+        ]
+      }
+    ]
+  )
+)