diff --git a/docs/rules/no-unsupported-features.md b/docs/rules/no-unsupported-features.md index cd6c3dcf9..2e7d67501 100644 --- a/docs/rules/no-unsupported-features.md +++ b/docs/rules/no-unsupported-features.md @@ -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. @@ -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) diff --git a/lib/rules/no-unsupported-features.js b/lib/rules/no-unsupported-features.js index 803a62ca5..0bcd61a55 100644 --- a/lib/rules/no-unsupported-features.js +++ b/lib/rules/no-unsupported-features.js @@ -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)) @@ -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 */ diff --git a/lib/rules/syntaxes/define-options.js b/lib/rules/syntaxes/define-options.js new file mode 100644 index 000000000..0ef008ca7 --- /dev/null +++ b/lib/rules/syntaxes/define-options.js @@ -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 `\n` + ), + fixer.removeRange(removeRange) + ] + } + } +} diff --git a/lib/rules/syntaxes/define-slots.js b/lib/rules/syntaxes/define-slots.js new file mode 100644 index 000000000..450ec3e89 --- /dev/null +++ b/lib/rules/syntaxes/define-slots.js @@ -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' + }) + } + }) + } +} diff --git a/tests/lib/rules/no-unsupported-features/define-options.js b/tests/lib/rules/no-unsupported-features/define-options.js new file mode 100644 index 000000000..10b2069b2 --- /dev/null +++ b/tests/lib/rules/no-unsupported-features/define-options.js @@ -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: ` + `, + options: buildOptions({ version: '^3.3.0' }) + }, + { + code: ` + `, + options: buildOptions() + }, + { + code: ` + `, + options: buildOptions({ version: '^3.0.0', ignores: ['define-options'] }) + } + ], + invalid: [ + { + code: ` + `, + options: buildOptions(), + output: ` + +`, + errors: [ + { + message: + '`defineOptions()` macros are not supported until Vue.js "3.3.0".', + line: 3 + } + ] + }, + { + code: ` + `, + options: buildOptions(), + output: ` + +`, + errors: [ + { + message: + '`defineOptions()` macros are not supported until Vue.js "3.3.0".', + line: 3 + } + ] + } + ] +}) diff --git a/tests/lib/rules/no-unsupported-features/define-slots.js b/tests/lib/rules/no-unsupported-features/define-slots.js new file mode 100644 index 000000000..2fef117c7 --- /dev/null +++ b/tests/lib/rules/no-unsupported-features/define-slots.js @@ -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: ` + `, + options: buildOptions({ version: '^3.3.0' }) + }, + { + code: ` + `, + options: buildOptions() + }, + { + code: ` + `, + options: buildOptions({ version: '^3.0.0', ignores: ['define-slots'] }) + } + ], + invalid: [ + { + code: ` + `, + options: buildOptions(), + errors: [ + { + message: + '`defineSlots()` macros are not supported until Vue.js "3.3.0".', + line: 3 + } + ] + } + ] +})