Skip to content

Commit 28db555

Browse files
authored
Add support for defineOptions and defineSlots to vue/no-unsupported-features rule (#2163)
1 parent a9e0a49 commit 28db555

File tree

6 files changed

+258
-2
lines changed

6 files changed

+258
-2
lines changed

Diff for: docs/rules/no-unsupported-features.md

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ This rule reports unsupported Vue.js syntax on the specified version.
2929
- `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.
3030
- `ignores` ... You can use this `ignores` option to ignore the given features.
3131
The `"ignores"` option accepts an array of the following strings.
32+
- Vue.js 3.3.0+
33+
- `"define-slots"` ... `defineSlots()` macro.
34+
- `"define-options"` ... `defineOptions()` macro.
3235
- Vue.js 3.2.0+
3336
- `"v-memo"` ... [v-memo](https://vuejs.org/api/built-in-directives.html#v-memo) directive.
3437
- `"v-bind-prop-modifier-shorthand"` ... `v-bind` with `.prop` modifier shorthand.
@@ -100,6 +103,8 @@ The `"ignores"` option accepts an array of the following strings.
100103

101104
## :books: Further Reading
102105

106+
- [API - defineOptions()](https://vuejs.org/api/sfc-script-setup.html#defineoptions)
107+
- [API - defineSlots()](https://vuejs.org/api/sfc-script-setup.html#defineslots)
103108
- [API - v-memo](https://vuejs.org/api/built-in-directives.html#v-memo)
104109
- [API - v-is](https://v3.vuejs.org/api/directives.html#v-is)
105110
- [API - v-is (Old)](https://github.com/vuejs/docs-next/blob/008613756c3d781128d96b64a2d27f7598f8f548/src/api/directives.md#v-is)

Diff for: lib/rules/no-unsupported-features.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ const FEATURES = {
3232
// Vue.js 3.2.0+
3333
'v-memo': require('./syntaxes/v-memo'),
3434
'v-bind-prop-modifier-shorthand': require('./syntaxes/v-bind-prop-modifier-shorthand'),
35-
'v-bind-attr-modifier': require('./syntaxes/v-bind-attr-modifier')
35+
'v-bind-attr-modifier': require('./syntaxes/v-bind-attr-modifier'),
36+
// Vue.js 3.3.0+
37+
'define-options': require('./syntaxes/define-options'),
38+
'define-slots': require('./syntaxes/define-slots')
3639
}
3740

3841
const SYNTAX_NAMES = /** @type {(keyof FEATURES)[]} */ (Object.keys(FEATURES))
@@ -115,7 +118,12 @@ module.exports = {
115118
forbiddenVBindPropModifierShorthand:
116119
'`.prop` shorthand are not supported until Vue.js "3.2.0".',
117120
forbiddenVBindAttrModifier:
118-
'`.attr` modifiers on `v-bind` are not supported until Vue.js "3.2.0".'
121+
'`.attr` modifiers on `v-bind` are not supported until Vue.js "3.2.0".',
122+
// Vue.js 3.3.0+
123+
forbiddenDefineOptions:
124+
'`defineOptions()` macros are not supported until Vue.js "3.3.0".',
125+
forbiddenDefineSlots:
126+
'`defineSlots()` macros are not supported until Vue.js "3.3.0".'
119127
}
120128
},
121129
/** @param {RuleContext} context */

Diff for: lib/rules/syntaxes/define-options.js

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../../utils/index')
8+
9+
module.exports = {
10+
supported: '>=3.3.0',
11+
/** @param {RuleContext} context @returns {RuleListener} */
12+
createScriptVisitor(context) {
13+
const sourceCode = context.getSourceCode()
14+
return utils.defineScriptSetupVisitor(context, {
15+
onDefineOptionsEnter(node) {
16+
context.report({
17+
node,
18+
messageId: 'forbiddenDefineOptions',
19+
fix(fixer) {
20+
return fix(fixer, node)
21+
}
22+
})
23+
}
24+
})
25+
26+
/**
27+
* @param {RuleFixer} fixer
28+
* @param {CallExpression} node defineOptions() node
29+
*/
30+
function fix(fixer, node) {
31+
if (node.arguments.length === 0) return null
32+
const scriptSetup = utils.getScriptSetupElement(context)
33+
if (!scriptSetup) return null
34+
if (
35+
scriptSetup.parent.children
36+
.filter(utils.isVElement)
37+
.some(
38+
(node) =>
39+
node.name === 'script' && !utils.hasAttribute(node, 'setup')
40+
)
41+
) {
42+
// has `<script>`
43+
return null
44+
}
45+
46+
// Find defineOptions statement
47+
/** @type {ASTNode} */
48+
let statement = node
49+
while (statement.parent && statement.parent.type !== 'Program') {
50+
statement = statement.parent
51+
}
52+
// Calc remove range
53+
/** @type {Range} */
54+
const removeRange = [...statement.range]
55+
if (
56+
sourceCode.lines[statement.loc.start.line - 1]
57+
.slice(0, statement.loc.start.column)
58+
.trim() === ''
59+
) {
60+
removeRange[0] -= statement.loc.start.column
61+
}
62+
63+
return [
64+
fixer.insertTextBefore(
65+
scriptSetup,
66+
`<script>\nexport default ${sourceCode.getText(
67+
node.arguments[0]
68+
)}\n</script>\n`
69+
),
70+
fixer.removeRange(removeRange)
71+
]
72+
}
73+
}
74+
}

Diff for: lib/rules/syntaxes/define-slots.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../../utils/index')
8+
9+
module.exports = {
10+
supported: '>=3.3.0',
11+
/** @param {RuleContext} context @returns {RuleListener} */
12+
createScriptVisitor(context) {
13+
return utils.defineScriptSetupVisitor(context, {
14+
onDefineSlotsEnter(node) {
15+
context.report({
16+
node,
17+
messageId: 'forbiddenDefineSlots'
18+
})
19+
}
20+
})
21+
}
22+
}
+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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('define-options', '^3.2.0')
12+
const tester = new RuleTester({
13+
parser: require.resolve('vue-eslint-parser'),
14+
parserOptions: {
15+
ecmaVersion: 2019,
16+
sourceType: 'module'
17+
}
18+
})
19+
20+
tester.run('no-unsupported-features/define-options', rule, {
21+
valid: [
22+
{
23+
code: `
24+
<script setup>
25+
defineOptions({})
26+
</script>`,
27+
options: buildOptions({ version: '^3.3.0' })
28+
},
29+
{
30+
code: `
31+
<script setup>
32+
defineProps({})
33+
</script>`,
34+
options: buildOptions()
35+
},
36+
{
37+
code: `
38+
<script setup>
39+
defineOptions({})
40+
</script>`,
41+
options: buildOptions({ version: '^3.0.0', ignores: ['define-options'] })
42+
}
43+
],
44+
invalid: [
45+
{
46+
code: `
47+
<script setup>
48+
defineOptions({ name: 'Foo' })
49+
</script>`,
50+
options: buildOptions(),
51+
output: `
52+
<script>
53+
export default { name: 'Foo' }
54+
</script>
55+
<script setup>
56+
57+
</script>`,
58+
errors: [
59+
{
60+
message:
61+
'`defineOptions()` macros are not supported until Vue.js "3.3.0".',
62+
line: 3
63+
}
64+
]
65+
},
66+
{
67+
code: `
68+
<script setup>
69+
defineOptions({});
70+
</script>`,
71+
options: buildOptions(),
72+
output: `
73+
<script>
74+
export default {}
75+
</script>
76+
<script setup>
77+
78+
</script>`,
79+
errors: [
80+
{
81+
message:
82+
'`defineOptions()` macros are not supported until Vue.js "3.3.0".',
83+
line: 3
84+
}
85+
]
86+
}
87+
]
88+
})
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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('define-slots', '^3.2.0')
12+
const tester = new RuleTester({
13+
parser: require.resolve('vue-eslint-parser'),
14+
parserOptions: {
15+
ecmaVersion: 2019
16+
}
17+
})
18+
19+
tester.run('no-unsupported-features/define-slots', rule, {
20+
valid: [
21+
{
22+
code: `
23+
<script setup>
24+
const slots = defineSlots()
25+
</script>`,
26+
options: buildOptions({ version: '^3.3.0' })
27+
},
28+
{
29+
code: `
30+
<script setup>
31+
defineProps({})
32+
</script>`,
33+
options: buildOptions()
34+
},
35+
{
36+
code: `
37+
<script setup>
38+
const slots = defineSlots()
39+
</script>`,
40+
options: buildOptions({ version: '^3.0.0', ignores: ['define-slots'] })
41+
}
42+
],
43+
invalid: [
44+
{
45+
code: `
46+
<script setup>
47+
const slots = defineSlots()
48+
</script>`,
49+
options: buildOptions(),
50+
errors: [
51+
{
52+
message:
53+
'`defineSlots()` macros are not supported until Vue.js "3.3.0".',
54+
line: 3
55+
}
56+
]
57+
}
58+
]
59+
})

0 commit comments

Comments
 (0)