Skip to content

Commit

Permalink
Add vue/no-multiple-slot-args rule. (#1179)
Browse files Browse the repository at this point in the history
* Add `vue/no-multiple-slot-args` rule.

* Fixed testcase
  • Loading branch information
ota-meshi committed Jun 5, 2020
1 parent ffe9ece commit 52f34e9
Show file tree
Hide file tree
Showing 7 changed files with 345 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/rules/README.md
Expand Up @@ -147,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 | |
Expand Down Expand Up @@ -254,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 | |
Expand Down
49 changes: 49 additions & 0 deletions 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)
1 change: 1 addition & 0 deletions lib/configs/recommended.js
Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions lib/configs/vue3-recommended.js
Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -71,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'),
Expand Down
127 changes: 127 additions & 0 deletions 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)
}
})
}
}
164 changes: 164 additions & 0 deletions 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.'
]
}
]
})

0 comments on commit 52f34e9

Please sign in to comment.