Skip to content

Commit

Permalink
Add support for defineOptions and defineSlots to `vue/no-unsuppor…
Browse files Browse the repository at this point in the history
…ted-features` rule (#2163)
  • Loading branch information
ota-meshi committed May 13, 2023
1 parent a9e0a49 commit 28db555
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 2 deletions.
5 changes: 5 additions & 0 deletions docs/rules/no-unsupported-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ This rule reports unsupported Vue.js syntax on the specified version.
- `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.
- `ignores` ... You can use this `ignores` option to ignore the given features.
The `"ignores"` option accepts an array of the following strings.
- Vue.js 3.3.0+
- `"define-slots"` ... `defineSlots()` macro.
- `"define-options"` ... `defineOptions()` macro.
- Vue.js 3.2.0+
- `"v-memo"` ... [v-memo](https://vuejs.org/api/built-in-directives.html#v-memo) directive.
- `"v-bind-prop-modifier-shorthand"` ... `v-bind` with `.prop` modifier shorthand.
Expand Down Expand Up @@ -100,6 +103,8 @@ The `"ignores"` option accepts an array of the following strings.

## :books: Further Reading

- [API - defineOptions()](https://vuejs.org/api/sfc-script-setup.html#defineoptions)
- [API - defineSlots()](https://vuejs.org/api/sfc-script-setup.html#defineslots)
- [API - v-memo](https://vuejs.org/api/built-in-directives.html#v-memo)
- [API - v-is](https://v3.vuejs.org/api/directives.html#v-is)
- [API - v-is (Old)](https://github.com/vuejs/docs-next/blob/008613756c3d781128d96b64a2d27f7598f8f548/src/api/directives.md#v-is)
Expand Down
12 changes: 10 additions & 2 deletions lib/rules/no-unsupported-features.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ const FEATURES = {
// Vue.js 3.2.0+
'v-memo': require('./syntaxes/v-memo'),
'v-bind-prop-modifier-shorthand': require('./syntaxes/v-bind-prop-modifier-shorthand'),
'v-bind-attr-modifier': require('./syntaxes/v-bind-attr-modifier')
'v-bind-attr-modifier': require('./syntaxes/v-bind-attr-modifier'),
// Vue.js 3.3.0+
'define-options': require('./syntaxes/define-options'),
'define-slots': require('./syntaxes/define-slots')
}

const SYNTAX_NAMES = /** @type {(keyof FEATURES)[]} */ (Object.keys(FEATURES))
Expand Down Expand Up @@ -115,7 +118,12 @@ module.exports = {
forbiddenVBindPropModifierShorthand:
'`.prop` shorthand are not supported until Vue.js "3.2.0".',
forbiddenVBindAttrModifier:
'`.attr` modifiers on `v-bind` are not supported until Vue.js "3.2.0".'
'`.attr` modifiers on `v-bind` are not supported until Vue.js "3.2.0".',
// Vue.js 3.3.0+
forbiddenDefineOptions:
'`defineOptions()` macros are not supported until Vue.js "3.3.0".',
forbiddenDefineSlots:
'`defineSlots()` macros are not supported until Vue.js "3.3.0".'
}
},
/** @param {RuleContext} context */
Expand Down
74 changes: 74 additions & 0 deletions lib/rules/syntaxes/define-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'

const utils = require('../../utils/index')

module.exports = {
supported: '>=3.3.0',
/** @param {RuleContext} context @returns {RuleListener} */
createScriptVisitor(context) {
const sourceCode = context.getSourceCode()
return utils.defineScriptSetupVisitor(context, {
onDefineOptionsEnter(node) {
context.report({
node,
messageId: 'forbiddenDefineOptions',
fix(fixer) {
return fix(fixer, node)
}
})
}
})

/**
* @param {RuleFixer} fixer
* @param {CallExpression} node defineOptions() node
*/
function fix(fixer, node) {
if (node.arguments.length === 0) return null
const scriptSetup = utils.getScriptSetupElement(context)
if (!scriptSetup) return null
if (
scriptSetup.parent.children
.filter(utils.isVElement)
.some(
(node) =>
node.name === 'script' && !utils.hasAttribute(node, 'setup')
)
) {
// has `<script>`
return null
}

// Find defineOptions statement
/** @type {ASTNode} */
let statement = node
while (statement.parent && statement.parent.type !== 'Program') {
statement = statement.parent
}
// Calc remove range
/** @type {Range} */
const removeRange = [...statement.range]
if (
sourceCode.lines[statement.loc.start.line - 1]
.slice(0, statement.loc.start.column)
.trim() === ''
) {
removeRange[0] -= statement.loc.start.column
}

return [
fixer.insertTextBefore(
scriptSetup,
`<script>\nexport default ${sourceCode.getText(
node.arguments[0]
)}\n</script>\n`
),
fixer.removeRange(removeRange)
]
}
}
}
22 changes: 22 additions & 0 deletions lib/rules/syntaxes/define-slots.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'

const utils = require('../../utils/index')

module.exports = {
supported: '>=3.3.0',
/** @param {RuleContext} context @returns {RuleListener} */
createScriptVisitor(context) {
return utils.defineScriptSetupVisitor(context, {
onDefineSlotsEnter(node) {
context.report({
node,
messageId: 'forbiddenDefineSlots'
})
}
})
}
}
88 changes: 88 additions & 0 deletions tests/lib/rules/no-unsupported-features/define-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'

const RuleTester = require('eslint').RuleTester
const rule = require('../../../../lib/rules/no-unsupported-features')
const utils = require('./utils')

const buildOptions = utils.optionsBuilder('define-options', '^3.2.0')
const tester = new RuleTester({
parser: require.resolve('vue-eslint-parser'),
parserOptions: {
ecmaVersion: 2019,
sourceType: 'module'
}
})

tester.run('no-unsupported-features/define-options', rule, {
valid: [
{
code: `
<script setup>
defineOptions({})
</script>`,
options: buildOptions({ version: '^3.3.0' })
},
{
code: `
<script setup>
defineProps({})
</script>`,
options: buildOptions()
},
{
code: `
<script setup>
defineOptions({})
</script>`,
options: buildOptions({ version: '^3.0.0', ignores: ['define-options'] })
}
],
invalid: [
{
code: `
<script setup>
defineOptions({ name: 'Foo' })
</script>`,
options: buildOptions(),
output: `
<script>
export default { name: 'Foo' }
</script>
<script setup>
</script>`,
errors: [
{
message:
'`defineOptions()` macros are not supported until Vue.js "3.3.0".',
line: 3
}
]
},
{
code: `
<script setup>
defineOptions({});
</script>`,
options: buildOptions(),
output: `
<script>
export default {}
</script>
<script setup>
</script>`,
errors: [
{
message:
'`defineOptions()` macros are not supported until Vue.js "3.3.0".',
line: 3
}
]
}
]
})
59 changes: 59 additions & 0 deletions tests/lib/rules/no-unsupported-features/define-slots.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'

const RuleTester = require('eslint').RuleTester
const rule = require('../../../../lib/rules/no-unsupported-features')
const utils = require('./utils')

const buildOptions = utils.optionsBuilder('define-slots', '^3.2.0')
const tester = new RuleTester({
parser: require.resolve('vue-eslint-parser'),
parserOptions: {
ecmaVersion: 2019
}
})

tester.run('no-unsupported-features/define-slots', rule, {
valid: [
{
code: `
<script setup>
const slots = defineSlots()
</script>`,
options: buildOptions({ version: '^3.3.0' })
},
{
code: `
<script setup>
defineProps({})
</script>`,
options: buildOptions()
},
{
code: `
<script setup>
const slots = defineSlots()
</script>`,
options: buildOptions({ version: '^3.0.0', ignores: ['define-slots'] })
}
],
invalid: [
{
code: `
<script setup>
const slots = defineSlots()
</script>`,
options: buildOptions(),
errors: [
{
message:
'`defineSlots()` macros are not supported until Vue.js "3.3.0".',
line: 3
}
]
}
]
})

0 comments on commit 28db555

Please sign in to comment.