-
-
Notifications
You must be signed in to change notification settings - Fork 666
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
vue/no-multiple-slot-args
rule. (#1179)
* Add `vue/no-multiple-slot-args` rule. * Fixed testcase
- Loading branch information
Showing
7 changed files
with
345 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.' | ||
] | ||
} | ||
] | ||
}) |