Skip to content

Commit c51cbe2

Browse files
committed
⭐️New: Add vue/valid-slot-scope rule
1 parent 0fd0f7b commit c51cbe2

File tree

5 files changed

+606
-0
lines changed

5 files changed

+606
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
238238
| :wrench: | [vue/script-indent](./docs/rules/script-indent.md) | enforce consistent indentation in `<script>` |
239239
| :wrench: | [vue/singleline-html-element-content-newline](./docs/rules/singleline-html-element-content-newline.md) | require a line break before and after the contents of a singleline element |
240240
| | [vue/use-v-on-exact](./docs/rules/use-v-on-exact.md) | enforce usage of `exact` modifier on `v-on` |
241+
| | [vue/valid-slot-scope](./docs/rules/valid-slot-scope.md) | enforce valid `slot-scope` attributes |
241242

242243
### Deprecated
243244

docs/rules/valid-slot-scope.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# enforce valid `slot-scope` attributes (vue/valid-slot-scope)
2+
3+
This rule checks whether every `slot-scope` (or `scope`) attributes is valid.
4+
5+
## :book: Rule Details
6+
7+
This rule reports `slot-scope` attributes in the following cases:
8+
9+
- The `slot-scope` attribute does not have that attribute value. E.g. `<div slot-scope></div>`
10+
- The `slot-scope` attribute have the attribute value which is extra access to slot data. E.g. `<div slot-scope="prop, extra"></div>`
11+
- The `slot-scope` attribute have the attribute value which is rest parameter. E.g. `<div slot-scope="...props"></div>`
12+
13+
This rule does not check syntax errors in directives because it's checked by [no-parsing-error] rule.
14+
15+
:-1: Examples of **incorrect** code for this rule:
16+
17+
```vue
18+
<template>
19+
<TheComponent>
20+
<template slot-scope>
21+
...
22+
</template>
23+
</TheComponent>
24+
<TheComponent>
25+
<template slot-scope="">
26+
...
27+
</template>
28+
</TheComponent>
29+
<TheComponent>
30+
<template slot-scope="a, b, c">
31+
<!-- `b` and `c` are extra access. -->
32+
...
33+
</template>
34+
</TheComponent>
35+
<TheComponent>
36+
<template slot-scope="...props">
37+
...
38+
</template>
39+
</TheComponent>
40+
</template>
41+
```
42+
43+
:+1: Examples of **correct** code for this rule:
44+
45+
```vue
46+
<template>
47+
<TheComponent>
48+
<template slot-scope="prop">
49+
...
50+
</template>
51+
</TheComponent>
52+
<TheComponent>
53+
<template slot-scope="{ a, b, c }">
54+
...
55+
</template>
56+
</TheComponent>
57+
<TheComponent>
58+
<template slot-scope="[ a, b, c ]">
59+
...
60+
</template>
61+
</TheComponent>
62+
</template>
63+
```
64+
65+
## :wrench: Options
66+
67+
Nothing.
68+
69+
## :couple: Related rules
70+
71+
- [no-parsing-error]
72+
73+
74+
[no-parsing-error]: no-parsing-error.md

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ module.exports = {
5555
'use-v-on-exact': require('./rules/use-v-on-exact'),
5656
'v-bind-style': require('./rules/v-bind-style'),
5757
'v-on-style': require('./rules/v-on-style'),
58+
'valid-slot-scope': require('./rules/valid-slot-scope'),
5859
'valid-template-root': require('./rules/valid-template-root'),
5960
'valid-v-bind': require('./rules/valid-v-bind'),
6061
'valid-v-cloak': require('./rules/valid-v-cloak'),

lib/rules/valid-slot-scope.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/**
2+
* @fileoverview enforce valid `slot-scope` attributes
3+
* @author Yosuke Ota
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
13+
// ------------------------------------------------------------------------------
14+
// Helpers
15+
// ------------------------------------------------------------------------------
16+
17+
/**
18+
* Check whether the given token is a comma.
19+
* @param {Token} token The token to check.
20+
* @returns {boolean} `true` if the token is a comma.
21+
*/
22+
function isComma (token) {
23+
return token != null && token.type === 'Punctuator' && token.value === ','
24+
}
25+
26+
/**
27+
* Check whether the given token is a quote.
28+
* @param {Token} token The token to check.
29+
* @returns {boolean} `true` if the token is a quote.
30+
*/
31+
function isQuote (token) {
32+
return token != null && token.type === 'Punctuator' && (token.value === '"' || token.value === "'")
33+
}
34+
35+
/**
36+
* Gets the extra access parameter tokens of the given slot-scope node.
37+
* @param {ASTNode} node The slot-scope node to check.
38+
* @param {TokenStore} tokenStore The TokenStore.
39+
* @returns {Array} the extra tokens.
40+
*/
41+
function getExtraAccessParameterTokens (node, tokenStore) {
42+
const valueNode = node.value
43+
const result = []
44+
const valueFirstToken = tokenStore.getFirstToken(valueNode)
45+
const valueLastToken = tokenStore.getLastToken(valueNode)
46+
const exprLastToken = isQuote(valueFirstToken) && isQuote(valueLastToken) && valueFirstToken.value === valueLastToken.value
47+
? tokenStore.getTokenBefore(valueLastToken)
48+
: valueLastToken
49+
const idLastToken = tokenStore.getLastToken(valueNode.expression.id)
50+
if (idLastToken !== exprLastToken) {
51+
// e.g. `<div slot-scope="a, b">`
52+
// ^^^ Invalid
53+
result.push(...tokenStore.getTokensBetween(idLastToken, exprLastToken))
54+
result.push(exprLastToken)
55+
}
56+
57+
return result
58+
}
59+
60+
// ------------------------------------------------------------------------------
61+
// Rule Definition
62+
// ------------------------------------------------------------------------------
63+
64+
module.exports = {
65+
meta: {
66+
docs: {
67+
description: 'enforce valid `slot-scope` attributes',
68+
category: undefined,
69+
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-slot-scope.md'
70+
},
71+
fixable: null,
72+
schema: [],
73+
messages: {
74+
expectedValue: "'{{attrName}}' attributes require a value.",
75+
unexpectedRestParameter: 'The top level rest parameter is useless.',
76+
unexpectedExtraAccessParams: 'Unexpected extra access parameters `{{value}}`.',
77+
unexpectedTrailingComma: 'Unexpected trailing comma.'
78+
}
79+
},
80+
81+
create (context) {
82+
const tokenStore =
83+
context.parserServices.getTemplateBodyTokenStore &&
84+
context.parserServices.getTemplateBodyTokenStore()
85+
return utils.defineTemplateBodyVisitor(context, {
86+
'VAttribute[directive=true][key.name=/^(slot-)?scope$/]' (node) {
87+
if (!utils.hasAttributeValue(node)) {
88+
context.report({
89+
node,
90+
loc: node.loc,
91+
messageId: 'expectedValue',
92+
data: { attrName: node.key.name }
93+
})
94+
return
95+
}
96+
97+
const idNode = node.value.expression.id
98+
if (idNode.type === 'RestElement') {
99+
// e.g. `<div slot-scope="...a">`
100+
context.report({
101+
node: idNode,
102+
loc: idNode.loc,
103+
messageId: 'unexpectedRestParameter'
104+
})
105+
}
106+
107+
const extraAccessParameterTokens = getExtraAccessParameterTokens(node, tokenStore)
108+
if (extraAccessParameterTokens.length) {
109+
const startToken = extraAccessParameterTokens[0]
110+
if (extraAccessParameterTokens.length === 1 && isComma(startToken)) {
111+
context.report({
112+
node: startToken,
113+
loc: startToken.loc,
114+
messageId: 'unexpectedTrailingComma'
115+
})
116+
} else {
117+
const endToken = extraAccessParameterTokens[extraAccessParameterTokens.length - 1]
118+
const value = context.getSourceCode().text.slice(
119+
extraAccessParameterTokens.length > 1 && isComma(startToken)
120+
? extraAccessParameterTokens[1].range[0]
121+
: startToken.range[0],
122+
endToken.range[1]
123+
)
124+
context.report({
125+
loc: {
126+
start: startToken.loc.start,
127+
end: endToken.loc.end
128+
},
129+
messageId: 'unexpectedExtraAccessParams',
130+
data: { value }
131+
})
132+
}
133+
}
134+
}
135+
})
136+
}
137+
}

0 commit comments

Comments
 (0)