Repository: vuejs/eslint-plugin-vue
Pull request #1183 (2020-05-31 08:44:00)

Improved autofix of `vue/order-in-components` rule to understand "Nullish Coalescing".


Patch:
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/order-in-components.js b/lib/rules/order-in-components.js
index d26fa7d1..2366cabb 100644
--- a/lib/rules/order-in-components.js
+++ b/lib/rules/order-in-components.js
@@ -64,7 +64,7 @@ function isComma (node) {
   return node.type === 'Punctuator' && node.value === ','
 }
 
-const ARITHMETIC_OPERATORS = ['+', '-', '*', '/', '%', '**']
+const ARITHMETIC_OPERATORS = ['+', '-', '*', '/', '%', '**'/* es2016 */]
 const BITWISE_OPERATORS = ['&', '|', '^', '~', '<<', '>>', '>>>']
 const COMPARISON_OPERATORS = ['==', '!=', '===', '!==', '>', '>=', '<', '<=']
 const RELATIONAL_OPERATORS = ['in', 'instanceof']
@@ -74,7 +74,7 @@ const ALL_BINARY_OPERATORS = [].concat(
   COMPARISON_OPERATORS,
   RELATIONAL_OPERATORS
 )
-const LOGICAL_OPERATORS = ['&&', '||']
+const LOGICAL_OPERATORS = ['&&', '||', '??'/* es2020 */]
 
 /*
  * Result `true` if the node is sure that there are no side effects
@@ -94,17 +94,15 @@ const LOGICAL_OPERATORS = ['&&', '||']
  */
 function isNotSideEffectsNode (node, visitorKeys) {
   let result = true
-  const noSideEffectsNodes = new Set()
+  let skipNode = false
   traverseNodes(node, {
     visitorKeys,
-    enterNode (node, parent) {
-      if (!result) {
+    enterNode (node) {
+      if (!result || skipNode) {
         return
       }
 
       if (
-        // parent has no side effects
-        noSideEffectsNodes.has(parent) ||
         // no side effects node
         node.type === 'FunctionExpression' ||
         node.type === 'Identifier' ||
@@ -113,7 +111,7 @@ function isNotSideEffectsNode (node, visitorKeys) {
         node.type === 'ArrowFunctionExpression' ||
         node.type === 'TemplateElement'
       ) {
-        noSideEffectsNodes.add(node)
+        skipNode = node
       } else if (
         node.type !== 'Property' &&
         node.type !== 'ObjectExpression' &&
@@ -131,7 +129,11 @@ function isNotSideEffectsNode (node, visitorKeys) {
         result = false
       }
     },
-    leaveNode () {}
+    leaveNode (node) {
+      if (skipNode === node) {
+        skipNode = null
+      }
+    }
   })
 
   return result
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)
+      }
+    })
+  }
+}


Test patch:
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/order-in-components.js b/tests/lib/rules/order-in-components.js
index f94b4ec6..53823494 100644
--- a/tests/lib/rules/order-in-components.js
+++ b/tests/lib/rules/order-in-components.js
@@ -10,7 +10,7 @@ const RuleTester = require('eslint').RuleTester
 const ruleTester = new RuleTester()
 
 const parserOptions = {
-  ecmaVersion: 2018,
+  ecmaVersion: 2020,
   sourceType: 'module'
 }
 
@@ -751,6 +751,7 @@ ruleTester.run('order-in-components', rule, {
           testConditional: a ? b : c,
           testYield: function* () {},
           testTemplate: \`a:\${a},b:\${b},c:\${c}.\`,
+          testNullish: a ?? b,
           name: 'burger',
         };
       `,
@@ -768,11 +769,12 @@ ruleTester.run('order-in-components', rule, {
           testConditional: a ? b : c,
           testYield: function* () {},
           testTemplate: \`a:\${a},b:\${b},c:\${c}.\`,
+          testNullish: a ?? b,
         };
       `,
       errors: [{
         message: 'The "name" property should be above the "data" property on line 3.',
-        line: 13
+        line: 14
       }]
     }
   ]
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.'
+      ]
+    }
+  ]
+})