Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit f00ccea

Browse files
committedFeb 28, 2019
⭐️New: Add vue/no-unsupported-features rule
1 parent 66a252d commit f00ccea

14 files changed

+1054
-0
lines changed
 

‎docs/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ For example:
153153
| [vue/no-boolean-default](./no-boolean-default.md) | disallow boolean defaults | :wrench: |
154154
| [vue/no-empty-pattern](./no-empty-pattern.md) | disallow empty destructuring patterns | |
155155
| [vue/no-restricted-syntax](./no-restricted-syntax.md) | disallow specified syntax | |
156+
| [vue/no-unsupported-features](./no-unsupported-features.md) | disallow unsupported Vue.js syntax on the specified version | :wrench: |
156157
| [vue/object-curly-spacing](./object-curly-spacing.md) | enforce consistent spacing inside braces | :wrench: |
157158
| [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | |
158159
| [vue/script-indent](./script-indent.md) | enforce consistent indentation in `<script>` | :wrench: |

‎docs/rules/no-unsupported-features.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-unsupported-features
5+
description: disallow unsupported Vue.js syntax on the specified version
6+
---
7+
# vue/no-unsupported-features
8+
> disallow unsupported Vue.js syntax on the specified version
9+
10+
- :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.
11+
12+
## :book: Rule Details
13+
14+
This rule reports unsupported Vue.js syntax on the specified version.
15+
16+
## :wrench: Options
17+
18+
```json
19+
{
20+
"vue/no-unsupported-features": ["error", {
21+
"version": "^2.6.0",
22+
"ignores": []
23+
}]
24+
}
25+
```
26+
27+
- `version` ... The `version` option accepts [the valid version range of `node-semver`](https://github.com/npm/node-semver#range-grammar). Set the version of Vue.js you are using. This option is required.
28+
- `ignores` ... You can use this `ignores` option to ignore the given features.
29+
The `"ignores"` option accepts an array of the following strings.
30+
- Vue.js 2.6.0+
31+
- `"dynamic-directive-arguments"` ... [dynamic directive arguments](https://vuejs.org/v2/guide/syntax.html#Dynamic-Arguments).
32+
- `"v-slot"` ... [v-slot](https://vuejs.org/v2/api/#v-slot) directive.
33+
- Vue.js 2.5.0+
34+
- `"slot-scope-attribute"` ... [slot-scope](https://vuejs.org/v2/api/#slot-scope-deprecated) attributes.
35+
- Vue.js `">=2.6.0-beta.1 <=2.6.0-beta.3"` or 2.6 custom build
36+
- `"v-bind-prop-modifier-shorthand"` ... [v-bind](https://vuejs.org/v2/api/#v-bind) with `.prop` modifier shorthand.
37+
38+
### `{"version": "^2.5.0"}`
39+
40+
<eslint-code-block fix :rules="{'vue/no-unsupported-features': ['error', {'version': '^2.5.0'}]}">
41+
42+
```vue
43+
<template>
44+
<!-- ✓ GOOD -->
45+
<CustomComponent :foo="val" />
46+
<ListComponent>
47+
<template slot="name" slot-scope="props">
48+
{{ props.title }}
49+
</template>
50+
</ListComponent>
51+
52+
<!-- ✗ BAD -->
53+
<!-- dynamic directive arguments -->
54+
<CustomComponent :[foo]="val" />
55+
<ListComponent>
56+
<!-- v-slot -->
57+
<template v-slot:name="props">
58+
{{ props.title }}
59+
</template>
60+
<template #name="props">
61+
{{ props.title }}
62+
</template>
63+
</ListComponent>
64+
</template>
65+
```
66+
67+
</eslint-code-block>
68+
69+
## :books: Further reading
70+
71+
- [Guide - Dynamic Arguments](https://vuejs.org/v2/guide/syntax.html#Dynamic-Arguments)
72+
- [API - v-slot](https://vuejs.org/v2/api/#v-slot)
73+
- [API - slot-scope](https://vuejs.org/v2/api/#slot-scope-deprecated)
74+
- [Vue RFCs - 0001-new-slot-syntax](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0001-new-slot-syntax.md)
75+
- [Vue RFCs - 0002-slot-syntax-shorthand](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0002-slot-syntax-shorthand.md)
76+
- [Vue RFCs - 0003-dynamic-directive-arguments](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0003-dynamic-directive-arguments.md)
77+
78+
## :mag: Implementation
79+
80+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-unsupported-features.js)
81+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-unsupported-features.js)

‎lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ module.exports = {
4848
'no-template-key': require('./rules/no-template-key'),
4949
'no-template-shadow': require('./rules/no-template-shadow'),
5050
'no-textarea-mustache': require('./rules/no-textarea-mustache'),
51+
'no-unsupported-features': require('./rules/no-unsupported-features'),
5152
'no-unused-components': require('./rules/no-unused-components'),
5253
'no-unused-vars': require('./rules/no-unused-vars'),
5354
'no-use-v-if-with-v-for': require('./rules/no-use-v-if-with-v-for'),

‎lib/rules/no-unsupported-features.js

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const { Range } = require('semver')
8+
const utils = require('../utils')
9+
10+
const FEATURES = {
11+
// Vue.js 2.5.0+
12+
'slot-scope-attribute': require('./syntaxes/slot-scope-attribute'),
13+
// Vue.js 2.6.0+
14+
'dynamic-directive-arguments': require('./syntaxes/dynamic-directive-arguments'),
15+
'v-slot': require('./syntaxes/v-slot'),
16+
17+
// >=2.6.0-beta.1 <=2.6.0-beta.3
18+
'v-bind-prop-modifier-shorthand': require('./syntaxes/v-bind-prop-modifier-shorthand')
19+
}
20+
21+
const cache = new Map()
22+
/**
23+
* Get the `semver.Range` object of a given range text.
24+
* @param {string} x The text expression for a semver range.
25+
* @returns {Range|null} The range object of a given range text.
26+
* It's null if the `x` is not a valid range text.
27+
*/
28+
function getSemverRange (x) {
29+
const s = String(x)
30+
let ret = cache.get(s) || null
31+
32+
if (!ret) {
33+
try {
34+
ret = new Range(s)
35+
} catch (_error) {
36+
// Ignore parsing error.
37+
}
38+
cache.set(s, ret)
39+
}
40+
41+
return ret
42+
}
43+
44+
/**
45+
* Merge two visitors.
46+
* @param {Visitor} x The visitor which is assigned.
47+
* @param {Visitor} y The visitor which is assigning.
48+
* @returns {Visitor} `x`.
49+
*/
50+
function merge (x, y) {
51+
for (const key of Object.keys(y)) {
52+
if (typeof x[key] === 'function') {
53+
if (x[key]._handlers == null) {
54+
const fs = [x[key], y[key]]
55+
x[key] = node => fs.forEach(h => h(node))
56+
x[key]._handlers = fs
57+
} else {
58+
x[key]._handlers.push(y[key])
59+
}
60+
} else {
61+
x[key] = y[key]
62+
}
63+
}
64+
return x
65+
}
66+
67+
module.exports = {
68+
meta: {
69+
type: 'suggestion',
70+
docs: {
71+
description: 'disallow unsupported Vue.js syntax on the specified version',
72+
category: undefined,
73+
url: 'https://eslint.vuejs.org/rules/no-unsupported-features.html'
74+
},
75+
fixable: 'code',
76+
schema: [
77+
{
78+
type: 'object',
79+
properties: {
80+
version: {
81+
type: 'string'
82+
},
83+
ignores: {
84+
type: 'array',
85+
items: {
86+
enum: Object.keys(FEATURES)
87+
},
88+
uniqueItems: true
89+
}
90+
},
91+
additionalProperties: false
92+
}
93+
],
94+
messages: {
95+
// Vue.js 2.5.0+
96+
forbiddenSlotScopeAttribute: '`slot-scope` are not supported until Vue.js "2.5.0".',
97+
// Vue.js 2.6.0+
98+
forbiddenDynamicDirectiveArguments: 'Dynamic arguments are not supported until Vue.js "2.6.0".',
99+
forbiddenVSlot: '`v-slot` are not supported until Vue.js "2.6.0".',
100+
101+
// >=2.6.0-beta.1 <=2.6.0-beta.3
102+
forbiddenVBindPropModifierShorthand: '`.prop` shorthand are not supported except Vue.js ">=2.6.0-beta.1 <=2.6.0-beta.3".'
103+
}
104+
},
105+
create (context) {
106+
const { version, ignores } = Object.assign(
107+
{
108+
version: null,
109+
ignores: []
110+
},
111+
context.options[0] || {}
112+
)
113+
if (!version) {
114+
// version is not set.
115+
return {}
116+
}
117+
const versionRange = getSemverRange(version)
118+
119+
/**
120+
* Check whether a given case object is full-supported on the configured node version.
121+
* @param {{supported:string}} aCase The case object to check.
122+
* @returns {boolean} `true` if it's supporting.
123+
*/
124+
function isNotSupportingVersion (aCase) {
125+
if (typeof aCase.supported === 'function') {
126+
return !aCase.supported(versionRange)
127+
}
128+
return versionRange.intersects(getSemverRange(`<${aCase.supported}`))
129+
}
130+
const templateBodyVisitor = Object.keys(FEATURES)
131+
.filter(syntaxName => !ignores.includes(syntaxName))
132+
.filter(syntaxName => isNotSupportingVersion(FEATURES[syntaxName]))
133+
.reduce((result, syntaxName) => {
134+
const visitor = FEATURES[syntaxName].createTemplateBodyVisitor(context)
135+
if (visitor) {
136+
merge(result, visitor)
137+
}
138+
return result
139+
}, {})
140+
141+
return utils.defineTemplateBodyVisitor(context, templateBodyVisitor)
142+
}
143+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
module.exports = {
7+
supported: '2.6.0',
8+
createTemplateBodyVisitor (context) {
9+
/**
10+
* Reports dynamic argument node
11+
* @param {VExpressionContainer} dinamicArgument node of dynamic argument
12+
* @returns {void}
13+
*/
14+
function reportDynamicArgument (dinamicArgument) {
15+
context.report({
16+
node: dinamicArgument,
17+
messageId: 'forbiddenDynamicDirectiveArguments'
18+
})
19+
}
20+
21+
return {
22+
'VAttribute[directive=true] > VDirectiveKey > VExpressionContainer': reportDynamicArgument
23+
}
24+
}
25+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
module.exports = {
7+
deprecated: '2.6.0',
8+
supported: '2.5.0',
9+
createTemplateBodyVisitor (context, { fixToUpgrade } = {}) {
10+
const sourceCode = context.getSourceCode()
11+
/**
12+
* Convert to `v-slot`.
13+
* @param {object} fixer fixer
14+
* @param {VAttribute | null} slotAttr node of `slot`
15+
* @param {VAttribute | null} scopeAttr node of `slot-scope`
16+
* @returns {*} fix data
17+
*/
18+
function fixSlotToVSlot (fixer, slotAttr, scopeAttr) {
19+
const nameArgument = slotAttr && slotAttr.value && slotAttr && slotAttr.value.value
20+
? `:${slotAttr.value.value}`
21+
: ''
22+
const scopeValue = scopeAttr && scopeAttr.value
23+
? `=${sourceCode.getText(scopeAttr.value)}`
24+
: ''
25+
26+
const replaceText = `v-slot${nameArgument}${scopeValue}`
27+
const fixers = [
28+
fixer.replaceText(slotAttr || scopeAttr, replaceText)
29+
]
30+
if (slotAttr && scopeAttr) {
31+
fixers.push(fixer.remove(scopeAttr))
32+
}
33+
return fixers
34+
}
35+
/**
36+
* Reports `slot-scope` node
37+
* @param {VAttribute} scopeAttr node of `slot-scope`
38+
* @returns {void}
39+
*/
40+
function reportSlotScope (scopeAttr) {
41+
context.report({
42+
node: scopeAttr.key,
43+
messageId: 'forbiddenSlotScopeAttribute',
44+
fix: fixToUpgrade
45+
// fix to use `v-slot`
46+
? (fixer) => {
47+
const element = scopeAttr.parent
48+
const slotAttr = element.attributes
49+
.find(attr => attr.directive === false && attr.key.name === 'slot')
50+
return fixSlotToVSlot(fixer, slotAttr, scopeAttr)
51+
}
52+
: null
53+
})
54+
}
55+
56+
return {
57+
"VAttribute[directive=true][key.name.name='slot-scope']": reportSlotScope
58+
}
59+
}
60+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
const { Range } = require('semver')
7+
const unsupported = new Range('<=2.5 || >=2.6.0')
8+
9+
module.exports = {
10+
// >=2.6.0-beta.1 <=2.6.0-beta.3
11+
supported: (versionRange) => {
12+
return !versionRange.intersects(unsupported)
13+
},
14+
createTemplateBodyVisitor (context) {
15+
/**
16+
* Reports `.prop` shorthand node
17+
* @param {VDirectiveKey} bindPropKey node of `.prop` shorthand
18+
* @returns {void}
19+
*/
20+
function reportPropModifierShorthand (bindPropKey) {
21+
context.report({
22+
node: bindPropKey,
23+
messageId: 'forbiddenVBindPropModifierShorthand',
24+
// fix to use `:x.prop` (downgrade)
25+
fix: fixer => fixer.replaceText(bindPropKey, `:${bindPropKey.argument.rawName}.prop`)
26+
})
27+
}
28+
29+
return {
30+
"VAttribute[directive=true] > VDirectiveKey[name.name='bind'][name.rawName='.']": reportPropModifierShorthand
31+
}
32+
}
33+
}

‎lib/rules/syntaxes/v-slot.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
module.exports = {
7+
supported: '2.6.0',
8+
createTemplateBodyVisitor (context) {
9+
const sourceCode = context.getSourceCode()
10+
/**
11+
* Convert to `slot` and `slot-scope`.
12+
* @param {object} fixer fixer
13+
* @param {VAttribute} vSlotAttr node of `v-slot`
14+
* @returns {*} fix data
15+
*/
16+
function fixVSlotToSlot (fixer, vSlotAttr) {
17+
const key = vSlotAttr.key
18+
if (key.modifiers.length) {
19+
// unknown modifiers
20+
return null
21+
}
22+
const name = key.argument ? key.argument.rawName : null
23+
const scopedValueNode = vSlotAttr.value
24+
25+
const attrs = []
26+
if (name) {
27+
attrs.push(`slot="${name}"`)
28+
}
29+
if (scopedValueNode) {
30+
attrs.push(
31+
`slot-scope=${sourceCode.getText(scopedValueNode)}`
32+
)
33+
}
34+
if (!attrs.length) {
35+
attrs.push('slot') // useless
36+
}
37+
return fixer.replaceText(vSlotAttr, attrs.join(' '))
38+
}
39+
/**
40+
* Reports `v-slot` node
41+
* @param {VAttribute} vSlotAttr node of `v-slot`
42+
* @returns {void}
43+
*/
44+
function reportVSlot (vSlotAttr) {
45+
context.report({
46+
node: vSlotAttr.key,
47+
messageId: 'forbiddenVSlot',
48+
// fix to use `slot` (downgrade)
49+
fix: fixer => fixVSlotToSlot(fixer, vSlotAttr)
50+
})
51+
}
52+
53+
return {
54+
"VAttribute[directive=true][key.name.name='slot']": reportVSlot
55+
}
56+
}
57+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
/**
6+
* See to testcases in `./no-unsupported-features` directory for testcases of each features.
7+
*/
8+
'use strict'
9+
10+
const RuleTester = require('eslint').RuleTester
11+
const rule = require('../../../lib/rules/no-unsupported-features')
12+
13+
const tester = new RuleTester({
14+
parser: 'vue-eslint-parser',
15+
parserOptions: {
16+
ecmaVersion: 2019
17+
}
18+
})
19+
20+
tester.run('no-unsupported-features', rule, {
21+
valid: [
22+
{
23+
code: `
24+
<template>
25+
<a
26+
v-slot:name
27+
:[href]="'/xxx'"
28+
/>
29+
</template>`,
30+
options: [{ version: '^2.6.0' }]
31+
}
32+
],
33+
invalid: [
34+
{
35+
code: `
36+
<template>
37+
<a
38+
v-slot:name
39+
:[href]="'/xxx'"
40+
/>
41+
</template>`,
42+
options: [{ version: '^2.5.0' }],
43+
output: `
44+
<template>
45+
<a
46+
slot="name"
47+
:[href]="'/xxx'"
48+
/>
49+
</template>`,
50+
errors: [
51+
{
52+
message: '`v-slot` are not supported until Vue.js "2.6.0".',
53+
line: 4
54+
},
55+
{
56+
message: 'Dynamic arguments are not supported until Vue.js "2.6.0".',
57+
line: 5
58+
}
59+
]
60+
}
61+
]
62+
})
63+
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const RuleTester = require('eslint').RuleTester
8+
const rule = require('../../../../lib/rules/no-unsupported-features')
9+
const utils = require('./utils')
10+
11+
const buildOptions = utils.optionsBuilder('dynamic-directive-arguments', '^2.5.0')
12+
const tester = new RuleTester({
13+
parser: 'vue-eslint-parser',
14+
parserOptions: {
15+
ecmaVersion: 2019
16+
}
17+
})
18+
19+
tester.run('no-unsupported-features/dynamic-directive-arguments', rule, {
20+
valid: [
21+
{
22+
code: `
23+
<template>
24+
<a :[href]="'/xxx'" />
25+
</template>`,
26+
options: buildOptions({ version: '^2.6.0' })
27+
},
28+
{
29+
code: `
30+
<template>
31+
<a @[click]="onClick" />
32+
</template>`,
33+
options: buildOptions({ version: '^2.6.0' })
34+
},
35+
{
36+
code: `
37+
<template>
38+
<a :href="'/xxx'" />
39+
</template>`,
40+
options: buildOptions()
41+
},
42+
{
43+
code: `
44+
<template>
45+
<a @click="onClick" />
46+
</template>`,
47+
options: buildOptions()
48+
},
49+
{
50+
code: `
51+
<template>
52+
<a :[href]="'/xxx'" />
53+
</template>`,
54+
options: buildOptions({ version: '2.6.0-beta.2' })
55+
}
56+
],
57+
invalid: [
58+
{
59+
code: `
60+
<template>
61+
<a :[href]="'/xxx'" />
62+
</template>`,
63+
options: buildOptions(),
64+
errors: [
65+
{
66+
message: 'Dynamic arguments are not supported until Vue.js "2.6.0".',
67+
line: 3
68+
}
69+
]
70+
},
71+
{
72+
code: `
73+
<template>
74+
<a @[click]="onClick" />
75+
</template>`,
76+
options: buildOptions(),
77+
errors: [
78+
{
79+
message: 'Dynamic arguments are not supported until Vue.js "2.6.0".',
80+
line: 3
81+
}
82+
]
83+
}
84+
]
85+
})
86+
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const RuleTester = require('eslint').RuleTester
8+
const rule = require('../../../../lib/rules/no-unsupported-features')
9+
const utils = require('./utils')
10+
11+
const buildOptions = utils.optionsBuilder('slot-scope-attribute', '^2.4.0')
12+
const tester = new RuleTester({
13+
parser: 'vue-eslint-parser',
14+
parserOptions: {
15+
ecmaVersion: 2019
16+
}
17+
})
18+
19+
tester.run('no-unsupported-features/slot-scope-attribute', rule, {
20+
valid: [
21+
{
22+
code: `
23+
<template>
24+
<LinkList>
25+
<a slot-scope="{a}" />
26+
</LinkList>
27+
</template>`,
28+
options: buildOptions({ version: '^2.5.0' })
29+
},
30+
{
31+
code: `
32+
<template>
33+
<LinkList>
34+
<a slot=name />
35+
</LinkList>
36+
</template>`,
37+
options: buildOptions()
38+
},
39+
{
40+
code: `
41+
<template>
42+
<LinkList>
43+
<a v-slot="{a}" />
44+
</LinkList>
45+
</template>`,
46+
options: buildOptions()
47+
},
48+
{
49+
code: `
50+
<template>
51+
<LinkList>
52+
<a slot-scope="{a}" />
53+
</LinkList>
54+
</template>`,
55+
options: buildOptions({ version: '^2.4.0', ignores: ['slot-scope-attribute'] })
56+
}
57+
],
58+
invalid: [
59+
{
60+
code: `
61+
<template>
62+
<LinkList>
63+
<a slot-scope />
64+
</LinkList>
65+
</template>`,
66+
options: buildOptions(),
67+
output: null,
68+
errors: [
69+
{
70+
message: '`slot-scope` are not supported until Vue.js "2.5.0".',
71+
line: 4
72+
}
73+
]
74+
},
75+
{
76+
code: `
77+
<template>
78+
<LinkList>
79+
<a slot-scope="{a}" />
80+
</LinkList>
81+
</template>`,
82+
options: buildOptions(),
83+
output: null,
84+
errors: [
85+
{
86+
message: '`slot-scope` are not supported until Vue.js "2.5.0".',
87+
line: 4
88+
}
89+
]
90+
}
91+
]
92+
})
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const rule = require('../../../../lib/rules/no-unsupported-features')
8+
9+
const SYNTAXES = rule.meta.schema[0].properties.ignores.items.enum
10+
11+
module.exports = {
12+
SYNTAXES,
13+
/**
14+
* Define the options builder to exclude anything other than the given syntax.
15+
* @param {string} targetSyntax syntax for given
16+
* @param {string} defaultVersion default Vue.js version
17+
* @returns {function} the options builder
18+
*/
19+
optionsBuilder (targetSyntax, defaultVersion) {
20+
const baseIgnores = SYNTAXES.filter(s => s !== targetSyntax)
21+
return (option) => {
22+
const ignores = [...baseIgnores]
23+
let version = defaultVersion
24+
if (!option) {
25+
option = {}
26+
}
27+
if (option.ignores) {
28+
ignores.push(...option.ignores)
29+
}
30+
if (option.version) {
31+
version = option.version
32+
}
33+
option.ignores = ignores
34+
option.version = version
35+
return [option]
36+
}
37+
}
38+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const RuleTester = require('eslint').RuleTester
8+
const rule = require('../../../../lib/rules/no-unsupported-features')
9+
const utils = require('./utils')
10+
11+
const buildOptions = utils.optionsBuilder('v-bind-prop-modifier-shorthand', '^2.6.0')
12+
const tester = new RuleTester({
13+
parser: 'vue-eslint-parser',
14+
parserOptions: {
15+
ecmaVersion: 2019
16+
}
17+
})
18+
19+
tester.run('no-unsupported-features/v-bind-prop-modifier-shorthand', rule, {
20+
valid: [
21+
{
22+
code: `
23+
<template>
24+
<a .href="'/xxx'" />
25+
</template>`,
26+
options: buildOptions({ version: '2.6.0-beta.1' })
27+
},
28+
{
29+
code: `
30+
<template>
31+
<a .href="'/xxx'" />
32+
</template>`,
33+
options: buildOptions({ version: '2.6.0-beta.2' })
34+
},
35+
{
36+
code: `
37+
<template>
38+
<a .href="'/xxx'" />
39+
</template>`,
40+
options: buildOptions({ version: '2.6.0-beta.3' })
41+
},
42+
{
43+
code: `
44+
<template>
45+
<a :href.prop="'/xxx'" />
46+
</template>`,
47+
options: buildOptions()
48+
}
49+
],
50+
invalid: [
51+
{
52+
code: `
53+
<template>
54+
<a .href="'/xxx'" />
55+
</template>`,
56+
options: buildOptions(),
57+
output: `
58+
<template>
59+
<a :href.prop="'/xxx'" />
60+
</template>`,
61+
errors: [
62+
{
63+
message: '`.prop` shorthand are not supported except Vue.js ">=2.6.0-beta.1 <=2.6.0-beta.3".',
64+
line: 3
65+
}
66+
]
67+
},
68+
{
69+
code: `
70+
<template>
71+
<a .href="'/xxx'" />
72+
</template>`,
73+
options: buildOptions({ version: '2.5.99' }),
74+
output: `
75+
<template>
76+
<a :href.prop="'/xxx'" />
77+
</template>`,
78+
errors: [
79+
{
80+
message: '`.prop` shorthand are not supported except Vue.js ">=2.6.0-beta.1 <=2.6.0-beta.3".',
81+
line: 3
82+
}
83+
]
84+
}
85+
]
86+
})
87+
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const RuleTester = require('eslint').RuleTester
8+
const rule = require('../../../../lib/rules/no-unsupported-features')
9+
const utils = require('./utils')
10+
11+
const buildOptions = utils.optionsBuilder('v-slot', '^2.5.0')
12+
const tester = new RuleTester({
13+
parser: 'vue-eslint-parser',
14+
parserOptions: {
15+
ecmaVersion: 2019
16+
}
17+
})
18+
19+
tester.run('no-unsupported-features/v-slot', rule, {
20+
valid: [
21+
{
22+
code: `
23+
<template>
24+
<LinkList>
25+
<a v-slot:name />
26+
</LinkList>
27+
</template>`,
28+
options: buildOptions({ version: '^2.6.0' })
29+
},
30+
{
31+
code: `
32+
<template>
33+
<LinkList>
34+
<a slot=name />
35+
</LinkList>
36+
</template>`,
37+
options: buildOptions()
38+
},
39+
{
40+
code: `
41+
<template>
42+
<LinkList>
43+
<a slot-scope="{a}" />
44+
</LinkList>
45+
</template>`,
46+
options: buildOptions()
47+
},
48+
{
49+
code: `
50+
<template>
51+
<LinkList>
52+
<a v-slot:name />
53+
</LinkList>
54+
</template>`,
55+
options: buildOptions({ version: '^2.5.0', ignores: ['v-slot'] })
56+
},
57+
{
58+
code: `
59+
<template>
60+
<LinkList>
61+
<a />
62+
</LinkList>
63+
</template>`,
64+
options: buildOptions({ version: '>=2.0.0' })
65+
},
66+
{
67+
code: `
68+
<template>
69+
<LinkList>
70+
<a v-slot:name />
71+
</LinkList>
72+
</template>`,
73+
options: buildOptions({ version: '2.6.0-beta.2' })
74+
}
75+
],
76+
invalid: [
77+
78+
{
79+
code: `
80+
<template>
81+
<LinkList>
82+
<a v-slot />
83+
</LinkList>
84+
</template>`,
85+
options: buildOptions(),
86+
output: `
87+
<template>
88+
<LinkList>
89+
<a slot />
90+
</LinkList>
91+
</template>`,
92+
errors: [
93+
{
94+
message: '`v-slot` are not supported until Vue.js "2.6.0".',
95+
line: 4
96+
}
97+
]
98+
},
99+
{
100+
code: `
101+
<template>
102+
<LinkList>
103+
<a #default />
104+
</LinkList>
105+
</template>`,
106+
options: buildOptions(),
107+
output: `
108+
<template>
109+
<LinkList>
110+
<a slot="default" />
111+
</LinkList>
112+
</template>`,
113+
errors: [
114+
{
115+
message: '`v-slot` are not supported until Vue.js "2.6.0".',
116+
line: 4
117+
}
118+
]
119+
},
120+
{
121+
code: `
122+
<template>
123+
<LinkList>
124+
<a v-slot:name />
125+
</LinkList>
126+
</template>`,
127+
options: buildOptions(),
128+
output: `
129+
<template>
130+
<LinkList>
131+
<a slot="name" />
132+
</LinkList>
133+
</template>`,
134+
errors: [
135+
{
136+
message: '`v-slot` are not supported until Vue.js "2.6.0".',
137+
line: 4
138+
}
139+
]
140+
},
141+
{
142+
code: `
143+
<template>
144+
<LinkList>
145+
<a #name />
146+
</LinkList>
147+
</template>`,
148+
options: buildOptions(),
149+
output: `
150+
<template>
151+
<LinkList>
152+
<a slot="name" />
153+
</LinkList>
154+
</template>`,
155+
errors: [
156+
{
157+
message: '`v-slot` are not supported until Vue.js "2.6.0".',
158+
line: 4
159+
}
160+
]
161+
},
162+
{
163+
code: `
164+
<template>
165+
<LinkList>
166+
<a v-slot="{a}" />
167+
</LinkList>
168+
</template>`,
169+
options: buildOptions(),
170+
output: `
171+
<template>
172+
<LinkList>
173+
<a slot-scope="{a}" />
174+
</LinkList>
175+
</template>`,
176+
errors: [
177+
{
178+
message: '`v-slot` are not supported until Vue.js "2.6.0".',
179+
line: 4
180+
}
181+
]
182+
},
183+
{
184+
code: `
185+
<template>
186+
<LinkList>
187+
<a #default="{a}" />
188+
</LinkList>
189+
</template>`,
190+
options: buildOptions(),
191+
output: `
192+
<template>
193+
<LinkList>
194+
<a slot="default" slot-scope="{a}" />
195+
</LinkList>
196+
</template>`,
197+
errors: [
198+
{
199+
message: '`v-slot` are not supported until Vue.js "2.6.0".',
200+
line: 4
201+
}
202+
]
203+
},
204+
{
205+
code: `
206+
<template>
207+
<LinkList>
208+
<a v-slot:name="{a}" />
209+
</LinkList>
210+
</template>`,
211+
options: buildOptions(),
212+
output: `
213+
<template>
214+
<LinkList>
215+
<a slot="name" slot-scope="{a}" />
216+
</LinkList>
217+
</template>`,
218+
errors: [
219+
{
220+
message: '`v-slot` are not supported until Vue.js "2.6.0".',
221+
line: 4
222+
}
223+
]
224+
},
225+
{
226+
code: `
227+
<template>
228+
<LinkList>
229+
<a #name="{a}" />
230+
</LinkList>
231+
</template>`,
232+
options: buildOptions(),
233+
output: `
234+
<template>
235+
<LinkList>
236+
<a slot="name" slot-scope="{a}" />
237+
</LinkList>
238+
</template>`,
239+
errors: [
240+
{
241+
message: '`v-slot` are not supported until Vue.js "2.6.0".',
242+
line: 4
243+
}
244+
]
245+
},
246+
// syntax error
247+
{
248+
code: `
249+
<template>
250+
<LinkList>
251+
<a v-slot:name="{" />
252+
</LinkList>
253+
</template>`,
254+
options: buildOptions(),
255+
output: `
256+
<template>
257+
<LinkList>
258+
<a slot="name" slot-scope="{" />
259+
</LinkList>
260+
</template>`,
261+
errors: [
262+
{
263+
message: '`v-slot` are not supported until Vue.js "2.6.0".',
264+
line: 4
265+
}
266+
]
267+
},
268+
269+
// unknown modifiers
270+
{
271+
code: `
272+
<template>
273+
<LinkList>
274+
<a v-slot.mod="{a}" />
275+
</LinkList>
276+
</template>`,
277+
options: buildOptions(),
278+
output: null,
279+
errors: [
280+
{
281+
message: '`v-slot` are not supported until Vue.js "2.6.0".',
282+
line: 4
283+
}
284+
]
285+
}
286+
]
287+
})

0 commit comments

Comments
 (0)
Please sign in to comment.