From 0c80259ac96a65695b0668adbf2a577316ff7307 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Thu, 26 Dec 2019 14:07:28 +0900 Subject: [PATCH] =?UTF-8?q?=E2=AD=90=EF=B8=8FNew:=20Add=20vue/no-unsupport?= =?UTF-8?q?ed-features=20rule=20(#841)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⭐️New: Add vue/no-unsupported-features rule * Change to autofix --- docs/rules/README.md | 1 + docs/rules/no-unsupported-features.md | 82 +++++ lib/index.js | 1 + lib/rules/no-unsupported-features.js | 143 ++++++++ .../syntaxes/dynamic-directive-arguments.js | 25 ++ .../v-bind-prop-modifier-shorthand.js | 33 ++ lib/rules/syntaxes/v-slot.js | 83 +++++ tests/lib/rules/no-unsupported-features.js | 72 ++++ .../dynamic-directive-arguments.js | 86 +++++ .../slot-scope-attribute.js | 92 +++++ .../rules/no-unsupported-features/utils.js | 38 ++ .../v-bind-prop-modifier-shorthand.js | 87 +++++ .../rules/no-unsupported-features/v-slot.js | 342 ++++++++++++++++++ 13 files changed, 1085 insertions(+) create mode 100644 docs/rules/no-unsupported-features.md create mode 100644 lib/rules/no-unsupported-features.js create mode 100644 lib/rules/syntaxes/dynamic-directive-arguments.js create mode 100644 lib/rules/syntaxes/v-bind-prop-modifier-shorthand.js create mode 100644 lib/rules/syntaxes/v-slot.js create mode 100644 tests/lib/rules/no-unsupported-features.js create mode 100644 tests/lib/rules/no-unsupported-features/dynamic-directive-arguments.js create mode 100644 tests/lib/rules/no-unsupported-features/slot-scope-attribute.js create mode 100644 tests/lib/rules/no-unsupported-features/utils.js create mode 100644 tests/lib/rules/no-unsupported-features/v-bind-prop-modifier-shorthand.js create mode 100644 tests/lib/rules/no-unsupported-features/v-slot.js diff --git a/docs/rules/README.md b/docs/rules/README.md index addfcb28c..cbef00777 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -159,6 +159,7 @@ For example: | [vue/no-empty-pattern](./no-empty-pattern.md) | disallow empty destructuring patterns | | | [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | | | [vue/no-restricted-syntax](./no-restricted-syntax.md) | disallow specified syntax | | +| [vue/no-unsupported-features](./no-unsupported-features.md) | disallow unsupported Vue.js syntax on the specified version | :wrench: | | [vue/object-curly-spacing](./object-curly-spacing.md) | enforce consistent spacing inside braces | :wrench: | | [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | | | [vue/require-name-property](./require-name-property.md) | require a name property in Vue components | | diff --git a/docs/rules/no-unsupported-features.md b/docs/rules/no-unsupported-features.md new file mode 100644 index 000000000..5bb39148f --- /dev/null +++ b/docs/rules/no-unsupported-features.md @@ -0,0 +1,82 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/no-unsupported-features +description: disallow unsupported Vue.js syntax on the specified version +--- +# vue/no-unsupported-features +> disallow unsupported Vue.js syntax on the specified version + +- :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. + +## :book: Rule Details + +This rule reports unsupported Vue.js syntax on the specified version. + +## :wrench: Options + +```json +{ + "vue/no-unsupported-features": ["error", { + "version": "^2.6.0", + "ignores": [] + }] +} +``` + +- `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 2.6.0+ + - `"dynamic-directive-arguments"` ... [dynamic directive arguments](https://vuejs.org/v2/guide/syntax.html#Dynamic-Arguments). + - `"v-slot"` ... [v-slot](https://vuejs.org/v2/api/#v-slot) directive. + - Vue.js 2.5.0+ + - `"slot-scope-attribute"` ... [slot-scope](https://vuejs.org/v2/api/#slot-scope-deprecated) attributes. + - Vue.js `">=2.6.0-beta.1 <=2.6.0-beta.3"` or 2.6 custom build + - `"v-bind-prop-modifier-shorthand"` ... [v-bind](https://vuejs.org/v2/api/#v-bind) with `.prop` modifier shorthand. + +### `{"version": "^2.5.0"}` + + + +```vue + +``` + + + +## :books: Further reading + +- [Guide - Dynamic Arguments](https://vuejs.org/v2/guide/syntax.html#Dynamic-Arguments) +- [API - v-slot](https://vuejs.org/v2/api/#v-slot) +- [API - slot-scope](https://vuejs.org/v2/api/#slot-scope-deprecated) +- [Vue RFCs - 0001-new-slot-syntax](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0001-new-slot-syntax.md) +- [Vue RFCs - 0002-slot-syntax-shorthand](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0002-slot-syntax-shorthand.md) +- [Vue RFCs - 0003-dynamic-directive-arguments](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0003-dynamic-directive-arguments.md) +- [Vue RFCs - v-bind .prop shorthand proposal](https://github.com/vuejs/rfcs/pull/18) + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-unsupported-features.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-unsupported-features.js) diff --git a/lib/index.js b/lib/index.js index 3fc0f2c92..5fa52ddd5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -54,6 +54,7 @@ module.exports = { 'no-template-key': require('./rules/no-template-key'), 'no-template-shadow': require('./rules/no-template-shadow'), 'no-textarea-mustache': require('./rules/no-textarea-mustache'), + 'no-unsupported-features': require('./rules/no-unsupported-features'), 'no-unused-components': require('./rules/no-unused-components'), 'no-unused-vars': require('./rules/no-unused-vars'), 'no-use-v-if-with-v-for': require('./rules/no-use-v-if-with-v-for'), diff --git a/lib/rules/no-unsupported-features.js b/lib/rules/no-unsupported-features.js new file mode 100644 index 000000000..c4c361b3a --- /dev/null +++ b/lib/rules/no-unsupported-features.js @@ -0,0 +1,143 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +const { Range } = require('semver') +const utils = require('../utils') + +const FEATURES = { + // Vue.js 2.5.0+ + 'slot-scope-attribute': require('./syntaxes/slot-scope-attribute'), + // Vue.js 2.6.0+ + 'dynamic-directive-arguments': require('./syntaxes/dynamic-directive-arguments'), + 'v-slot': require('./syntaxes/v-slot'), + + // >=2.6.0-beta.1 <=2.6.0-beta.3 + 'v-bind-prop-modifier-shorthand': require('./syntaxes/v-bind-prop-modifier-shorthand') +} + +const cache = new Map() +/** + * Get the `semver.Range` object of a given range text. + * @param {string} x The text expression for a semver range. + * @returns {Range|null} The range object of a given range text. + * It's null if the `x` is not a valid range text. + */ +function getSemverRange (x) { + const s = String(x) + let ret = cache.get(s) || null + + if (!ret) { + try { + ret = new Range(s) + } catch (_error) { + // Ignore parsing error. + } + cache.set(s, ret) + } + + return ret +} + +/** + * Merge two visitors. + * @param {Visitor} x The visitor which is assigned. + * @param {Visitor} y The visitor which is assigning. + * @returns {Visitor} `x`. + */ +function merge (x, y) { + for (const key of Object.keys(y)) { + if (typeof x[key] === 'function') { + if (x[key]._handlers == null) { + const fs = [x[key], y[key]] + x[key] = node => fs.forEach(h => h(node)) + x[key]._handlers = fs + } else { + x[key]._handlers.push(y[key]) + } + } else { + x[key] = y[key] + } + } + return x +} + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow unsupported Vue.js syntax on the specified version', + category: undefined, + url: 'https://eslint.vuejs.org/rules/no-unsupported-features.html' + }, + fixable: 'code', + schema: [ + { + type: 'object', + properties: { + version: { + type: 'string' + }, + ignores: { + type: 'array', + items: { + enum: Object.keys(FEATURES) + }, + uniqueItems: true + } + }, + additionalProperties: false + } + ], + messages: { + // Vue.js 2.5.0+ + forbiddenSlotScopeAttribute: '`slot-scope` are not supported until Vue.js "2.5.0".', + // Vue.js 2.6.0+ + forbiddenDynamicDirectiveArguments: 'Dynamic arguments are not supported until Vue.js "2.6.0".', + forbiddenVSlot: '`v-slot` are not supported until Vue.js "2.6.0".', + + // >=2.6.0-beta.1 <=2.6.0-beta.3 + forbiddenVBindPropModifierShorthand: '`.prop` shorthand are not supported except Vue.js ">=2.6.0-beta.1 <=2.6.0-beta.3".' + } + }, + create (context) { + const { version, ignores } = Object.assign( + { + version: null, + ignores: [] + }, + context.options[0] || {} + ) + if (!version) { + // version is not set. + return {} + } + const versionRange = getSemverRange(version) + + /** + * Check whether a given case object is full-supported on the configured node version. + * @param {{supported:string}} aCase The case object to check. + * @returns {boolean} `true` if it's supporting. + */ + function isNotSupportingVersion (aCase) { + if (typeof aCase.supported === 'function') { + return !aCase.supported(versionRange) + } + return versionRange.intersects(getSemverRange(`<${aCase.supported}`)) + } + const templateBodyVisitor = Object.keys(FEATURES) + .filter(syntaxName => !ignores.includes(syntaxName)) + .filter(syntaxName => isNotSupportingVersion(FEATURES[syntaxName])) + .reduce((result, syntaxName) => { + const visitor = FEATURES[syntaxName].createTemplateBodyVisitor(context) + if (visitor) { + merge(result, visitor) + } + return result + }, {}) + + return utils.defineTemplateBodyVisitor(context, templateBodyVisitor) + } +} diff --git a/lib/rules/syntaxes/dynamic-directive-arguments.js b/lib/rules/syntaxes/dynamic-directive-arguments.js new file mode 100644 index 000000000..4c5a5a7f1 --- /dev/null +++ b/lib/rules/syntaxes/dynamic-directive-arguments.js @@ -0,0 +1,25 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' +module.exports = { + supported: '2.6.0', + createTemplateBodyVisitor (context) { + /** + * Reports dynamic argument node + * @param {VExpressionContainer} dinamicArgument node of dynamic argument + * @returns {void} + */ + function reportDynamicArgument (dinamicArgument) { + context.report({ + node: dinamicArgument, + messageId: 'forbiddenDynamicDirectiveArguments' + }) + } + + return { + 'VAttribute[directive=true] > VDirectiveKey > VExpressionContainer': reportDynamicArgument + } + } +} diff --git a/lib/rules/syntaxes/v-bind-prop-modifier-shorthand.js b/lib/rules/syntaxes/v-bind-prop-modifier-shorthand.js new file mode 100644 index 000000000..db5076cf2 --- /dev/null +++ b/lib/rules/syntaxes/v-bind-prop-modifier-shorthand.js @@ -0,0 +1,33 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' +const { Range } = require('semver') +const unsupported = new Range('<=2.5 || >=2.6.0') + +module.exports = { + // >=2.6.0-beta.1 <=2.6.0-beta.3 + supported: (versionRange) => { + return !versionRange.intersects(unsupported) + }, + createTemplateBodyVisitor (context) { + /** + * Reports `.prop` shorthand node + * @param {VDirectiveKey} bindPropKey node of `.prop` shorthand + * @returns {void} + */ + function reportPropModifierShorthand (bindPropKey) { + context.report({ + node: bindPropKey, + messageId: 'forbiddenVBindPropModifierShorthand', + // fix to use `:x.prop` (downgrade) + fix: fixer => fixer.replaceText(bindPropKey, `:${bindPropKey.argument.rawName}.prop`) + }) + } + + return { + "VAttribute[directive=true] > VDirectiveKey[name.name='bind'][name.rawName='.']": reportPropModifierShorthand + } + } +} diff --git a/lib/rules/syntaxes/v-slot.js b/lib/rules/syntaxes/v-slot.js new file mode 100644 index 000000000..17b1018a1 --- /dev/null +++ b/lib/rules/syntaxes/v-slot.js @@ -0,0 +1,83 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' +module.exports = { + supported: '2.6.0', + createTemplateBodyVisitor (context) { + const sourceCode = context.getSourceCode() + + /** + * Checks whether the given node can convert to the `slot`. + * @param {VAttribute} vSlotAttr node of `v-slot` + * @returns {boolean} `true` if the given node can convert to the `slot` + */ + function canConvertToSlot (vSlotAttr) { + if (vSlotAttr.parent.parent.name !== 'template') { + return false + } + return true + } + /** + * Convert to `slot` and `slot-scope`. + * @param {object} fixer fixer + * @param {VAttribute} vSlotAttr node of `v-slot` + * @returns {*} fix data + */ + function fixVSlotToSlot (fixer, vSlotAttr) { + const key = vSlotAttr.key + if (key.modifiers.length) { + // unknown modifiers + return null + } + + const attrs = [] + const argument = key.argument + if (argument) { + if (argument.type === 'VIdentifier') { + const name = argument.rawName + attrs.push(`slot="${name}"`) + } else if (argument.type === 'VExpressionContainer' && argument.expression) { + const expression = sourceCode.getText(argument.expression) + attrs.push(`:slot="${expression}"`) + } else { + // unknown or syntax error + return null + } + } + const scopedValueNode = vSlotAttr.value + if (scopedValueNode) { + attrs.push( + `slot-scope=${sourceCode.getText(scopedValueNode)}` + ) + } + if (!attrs.length) { + attrs.push('slot') // useless + } + return fixer.replaceText(vSlotAttr, attrs.join(' ')) + } + /** + * Reports `v-slot` node + * @param {VAttribute} vSlotAttr node of `v-slot` + * @returns {void} + */ + function reportVSlot (vSlotAttr) { + context.report({ + node: vSlotAttr.key, + messageId: 'forbiddenVSlot', + // fix to use `slot` (downgrade) + fix: fixer => { + if (!canConvertToSlot(vSlotAttr)) { + return null + } + return fixVSlotToSlot(fixer, vSlotAttr) + } + }) + } + + return { + "VAttribute[directive=true][key.name.name='slot']": reportVSlot + } + } +} diff --git a/tests/lib/rules/no-unsupported-features.js b/tests/lib/rules/no-unsupported-features.js new file mode 100644 index 000000000..ced993c05 --- /dev/null +++ b/tests/lib/rules/no-unsupported-features.js @@ -0,0 +1,72 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +/** + * See to testcases in `./no-unsupported-features` directory for testcases of each features. + */ +'use strict' + +const RuleTester = require('eslint').RuleTester +const rule = require('../../../lib/rules/no-unsupported-features') + +const tester = new RuleTester({ + parser: 'vue-eslint-parser', + parserOptions: { + ecmaVersion: 2019 + } +}) + +tester.run('no-unsupported-features', rule, { + valid: [ + { + code: ` + `, + options: [{ version: '^2.6.0' }] + } + ], + invalid: [ + { + code: ` + `, + options: [{ version: '^2.5.0' }], + output: ` + `, + errors: [ + { + message: '`v-slot` are not supported until Vue.js "2.6.0".', + line: 4 + }, + { + message: 'Dynamic arguments are not supported until Vue.js "2.6.0".', + line: 6 + } + ] + } + ] +}) + diff --git a/tests/lib/rules/no-unsupported-features/dynamic-directive-arguments.js b/tests/lib/rules/no-unsupported-features/dynamic-directive-arguments.js new file mode 100644 index 000000000..46c0725d5 --- /dev/null +++ b/tests/lib/rules/no-unsupported-features/dynamic-directive-arguments.js @@ -0,0 +1,86 @@ +/** + * @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('dynamic-directive-arguments', '^2.5.0') +const tester = new RuleTester({ + parser: 'vue-eslint-parser', + parserOptions: { + ecmaVersion: 2019 + } +}) + +tester.run('no-unsupported-features/dynamic-directive-arguments', rule, { + valid: [ + { + code: ` + `, + options: buildOptions({ version: '^2.6.0' }) + }, + { + code: ` + `, + options: buildOptions({ version: '^2.6.0' }) + }, + { + code: ` + `, + options: buildOptions() + }, + { + code: ` + `, + options: buildOptions() + }, + { + code: ` + `, + options: buildOptions({ version: '2.6.0-beta.2' }) + } + ], + invalid: [ + { + code: ` + `, + options: buildOptions(), + errors: [ + { + message: 'Dynamic arguments are not supported until Vue.js "2.6.0".', + line: 3 + } + ] + }, + { + code: ` + `, + options: buildOptions(), + errors: [ + { + message: 'Dynamic arguments are not supported until Vue.js "2.6.0".', + line: 3 + } + ] + } + ] +}) + diff --git a/tests/lib/rules/no-unsupported-features/slot-scope-attribute.js b/tests/lib/rules/no-unsupported-features/slot-scope-attribute.js new file mode 100644 index 000000000..675eea30c --- /dev/null +++ b/tests/lib/rules/no-unsupported-features/slot-scope-attribute.js @@ -0,0 +1,92 @@ +/** + * @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('slot-scope-attribute', '^2.4.0') +const tester = new RuleTester({ + parser: 'vue-eslint-parser', + parserOptions: { + ecmaVersion: 2019 + } +}) + +tester.run('no-unsupported-features/slot-scope-attribute', rule, { + valid: [ + { + code: ` + `, + options: buildOptions({ version: '^2.5.0' }) + }, + { + code: ` + `, + options: buildOptions() + }, + { + code: ` + `, + options: buildOptions() + }, + { + code: ` + `, + options: buildOptions({ version: '^2.4.0', ignores: ['slot-scope-attribute'] }) + } + ], + invalid: [ + { + code: ` + `, + options: buildOptions(), + output: null, + errors: [ + { + message: '`slot-scope` are not supported until Vue.js "2.5.0".', + line: 4 + } + ] + }, + { + code: ` + `, + options: buildOptions(), + output: null, + errors: [ + { + message: '`slot-scope` are not supported until Vue.js "2.5.0".', + line: 4 + } + ] + } + ] +}) diff --git a/tests/lib/rules/no-unsupported-features/utils.js b/tests/lib/rules/no-unsupported-features/utils.js new file mode 100644 index 000000000..9c94f062c --- /dev/null +++ b/tests/lib/rules/no-unsupported-features/utils.js @@ -0,0 +1,38 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +const rule = require('../../../../lib/rules/no-unsupported-features') + +const SYNTAXES = rule.meta.schema[0].properties.ignores.items.enum + +module.exports = { + SYNTAXES, + /** + * Define the options builder to exclude anything other than the given syntax. + * @param {string} targetSyntax syntax for given + * @param {string} defaultVersion default Vue.js version + * @returns {function} the options builder + */ + optionsBuilder (targetSyntax, defaultVersion) { + const baseIgnores = SYNTAXES.filter(s => s !== targetSyntax) + return (option) => { + const ignores = [...baseIgnores] + let version = defaultVersion + if (!option) { + option = {} + } + if (option.ignores) { + ignores.push(...option.ignores) + } + if (option.version) { + version = option.version + } + option.ignores = ignores + option.version = version + return [option] + } + } +} diff --git a/tests/lib/rules/no-unsupported-features/v-bind-prop-modifier-shorthand.js b/tests/lib/rules/no-unsupported-features/v-bind-prop-modifier-shorthand.js new file mode 100644 index 000000000..0dace1ac7 --- /dev/null +++ b/tests/lib/rules/no-unsupported-features/v-bind-prop-modifier-shorthand.js @@ -0,0 +1,87 @@ +/** + * @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('v-bind-prop-modifier-shorthand', '^2.6.0') +const tester = new RuleTester({ + parser: 'vue-eslint-parser', + parserOptions: { + ecmaVersion: 2019 + } +}) + +tester.run('no-unsupported-features/v-bind-prop-modifier-shorthand', rule, { + valid: [ + { + code: ` + `, + options: buildOptions({ version: '2.6.0-beta.1' }) + }, + { + code: ` + `, + options: buildOptions({ version: '2.6.0-beta.2' }) + }, + { + code: ` + `, + options: buildOptions({ version: '2.6.0-beta.3' }) + }, + { + code: ` + `, + options: buildOptions() + } + ], + invalid: [ + { + code: ` + `, + options: buildOptions(), + output: ` + `, + errors: [ + { + message: '`.prop` shorthand are not supported except Vue.js ">=2.6.0-beta.1 <=2.6.0-beta.3".', + line: 3 + } + ] + }, + { + code: ` + `, + options: buildOptions({ version: '2.5.99' }), + output: ` + `, + errors: [ + { + message: '`.prop` shorthand are not supported except Vue.js ">=2.6.0-beta.1 <=2.6.0-beta.3".', + line: 3 + } + ] + } + ] +}) + diff --git a/tests/lib/rules/no-unsupported-features/v-slot.js b/tests/lib/rules/no-unsupported-features/v-slot.js new file mode 100644 index 000000000..fbde61e60 --- /dev/null +++ b/tests/lib/rules/no-unsupported-features/v-slot.js @@ -0,0 +1,342 @@ +/** + * @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('v-slot', '^2.5.0') +const tester = new RuleTester({ + parser: 'vue-eslint-parser', + parserOptions: { + ecmaVersion: 2019 + } +}) + +tester.run('no-unsupported-features/v-slot', rule, { + valid: [ + { + code: ` + `, + options: buildOptions({ version: '^2.6.0' }) + }, + { + code: ` + `, + options: buildOptions() + }, + { + code: ` + `, + options: buildOptions() + }, + { + code: ` + `, + options: buildOptions({ version: '^2.5.0', ignores: ['v-slot'] }) + }, + { + code: ` + `, + options: buildOptions({ version: '>=2.0.0' }) + }, + { + code: ` + `, + options: buildOptions({ version: '2.6.0-beta.2' }) + } + ], + invalid: [ + + { + code: ` + `, + options: buildOptions(), + output: ` + `, + errors: [ + { + message: '`v-slot` are not supported until Vue.js "2.6.0".', + line: 4 + } + ] + }, + { + code: ` + `, + options: buildOptions(), + output: ` + `, + errors: [ + { + message: '`v-slot` are not supported until Vue.js "2.6.0".', + line: 4 + } + ] + }, + { + code: ` + `, + options: buildOptions(), + output: ` + `, + errors: [ + { + message: '`v-slot` are not supported until Vue.js "2.6.0".', + line: 4 + } + ] + }, + { + code: ` + `, + options: buildOptions(), + output: ` + `, + errors: [ + { + message: '`v-slot` are not supported until Vue.js "2.6.0".', + line: 4 + } + ] + }, + { + code: ` + `, + options: buildOptions(), + output: ` + `, + errors: [ + { + message: '`v-slot` are not supported until Vue.js "2.6.0".', + line: 4 + } + ] + }, + { + code: ` + `, + options: buildOptions(), + output: ` + `, + errors: [ + { + message: '`v-slot` are not supported until Vue.js "2.6.0".', + line: 4 + } + ] + }, + { + code: ` + `, + options: buildOptions(), + output: ` + `, + errors: [ + { + message: '`v-slot` are not supported until Vue.js "2.6.0".', + line: 4 + } + ] + }, + { + code: ` + `, + options: buildOptions(), + output: ` + `, + errors: [ + { + message: '`v-slot` are not supported until Vue.js "2.6.0".', + line: 4 + } + ] + }, + { + code: ` + `, + options: buildOptions(), + output: ` + `, + errors: [ + { + message: '`v-slot` are not supported until Vue.js "2.6.0".', + line: 4 + } + ] + }, + // syntax error + { + code: ` + `, + options: buildOptions(), + output: ` + `, + errors: [ + { + message: '`v-slot` are not supported until Vue.js "2.6.0".', + line: 4 + } + ] + }, + { + code: ` + `, + options: buildOptions(), + output: null, + errors: [ + { + message: '`v-slot` are not supported until Vue.js "2.6.0".', + line: 4 + } + ] + }, + + // unknown modifiers + { + code: ` + `, + options: buildOptions(), + output: null, + errors: [ + { + message: '`v-slot` are not supported until Vue.js "2.6.0".', + line: 4 + } + ] + }, + + // cannot fix + { + code: ` + `, + options: buildOptions(), + output: null, + errors: [ + { + message: '`v-slot` are not supported until Vue.js "2.6.0".', + line: 3 + } + ] + } + ] +})