From fb1fb7946a338173908471a2e4be4dab4c403568 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Fri, 5 Jun 2020 11:13:16 +0900 Subject: [PATCH 001/910] Add `vue/no-deprecated-dollar-scopedslots-api` rule. (#1177) --- docs/rules/README.md | 1 + .../no-deprecated-dollar-scopedslots-api.md | 47 +++ lib/configs/vue3-essential.js | 1 + lib/index.js | 1 + .../no-deprecated-dollar-scopedslots-api.js | 79 +++++ .../no-deprecated-dollar-scopedslots-api.js | 288 ++++++++++++++++++ 6 files changed, 417 insertions(+) create mode 100644 docs/rules/no-deprecated-dollar-scopedslots-api.md create mode 100644 lib/rules/no-deprecated-dollar-scopedslots-api.js create mode 100644 tests/lib/rules/no-deprecated-dollar-scopedslots-api.js diff --git a/docs/rules/README.md b/docs/rules/README.md index 8d3e9fb32..40548ec13 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -43,6 +43,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi | [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties | | | [vue/no-deprecated-data-object-declaration](./no-deprecated-data-object-declaration.md) | disallow using deprecated object declaration on data (in Vue.js 3.0.0+) | :wrench: | | [vue/no-deprecated-dollar-listeners-api](./no-deprecated-dollar-listeners-api.md) | disallow using deprecated `$listeners` (in Vue.js 3.0.0+) | | +| [vue/no-deprecated-dollar-scopedslots-api](./no-deprecated-dollar-scopedslots-api.md) | disallow using deprecated `$scopedSlots` (in Vue.js 3.0.0+) | :wrench: | | [vue/no-deprecated-events-api](./no-deprecated-events-api.md) | disallow using deprecated events api (in Vue.js 3.0.0+) | | | [vue/no-deprecated-filter](./no-deprecated-filter.md) | disallow using deprecated filters syntax (in Vue.js 3.0.0+) | | | [vue/no-deprecated-functional-template](./no-deprecated-functional-template.md) | disallow using deprecated the `functional` template (in Vue.js 3.0.0+) | | diff --git a/docs/rules/no-deprecated-dollar-scopedslots-api.md b/docs/rules/no-deprecated-dollar-scopedslots-api.md new file mode 100644 index 000000000..9b1d577c7 --- /dev/null +++ b/docs/rules/no-deprecated-dollar-scopedslots-api.md @@ -0,0 +1,47 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/no-deprecated-dollar-scopedslots-api +description: disallow using deprecated `$scopedSlots` (in Vue.js 3.0.0+) +--- +# vue/no-deprecated-dollar-scopedslots-api +> disallow using deprecated `$scopedSlots` (in Vue.js 3.0.0+) + +- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`. +- :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 use of deprecated `$scopedSlots`. (in Vue.js 3.0.0+). + + + +```vue + + +``` + + + +## :wrench: Options + +Nothing. + +## :books: Further reading + +- [Vue RFCs - 0006-slots-unification](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0006-slots-unification.md) + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-dollar-scopedslots-api.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-dollar-scopedslots-api.js) diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js index b2aec42d8..0e3e02f74 100644 --- a/lib/configs/vue3-essential.js +++ b/lib/configs/vue3-essential.js @@ -11,6 +11,7 @@ module.exports = { 'vue/no-async-in-computed-properties': 'error', 'vue/no-deprecated-data-object-declaration': 'error', 'vue/no-deprecated-dollar-listeners-api': 'error', + 'vue/no-deprecated-dollar-scopedslots-api': 'error', 'vue/no-deprecated-events-api': 'error', 'vue/no-deprecated-filter': 'error', 'vue/no-deprecated-functional-template': 'error', diff --git a/lib/index.js b/lib/index.js index d5fc21229..6eb4dc340 100644 --- a/lib/index.js +++ b/lib/index.js @@ -50,6 +50,7 @@ module.exports = { 'no-custom-modifiers-on-v-model': require('./rules/no-custom-modifiers-on-v-model'), 'no-deprecated-data-object-declaration': require('./rules/no-deprecated-data-object-declaration'), 'no-deprecated-dollar-listeners-api': require('./rules/no-deprecated-dollar-listeners-api'), + 'no-deprecated-dollar-scopedslots-api': require('./rules/no-deprecated-dollar-scopedslots-api'), 'no-deprecated-events-api': require('./rules/no-deprecated-events-api'), 'no-deprecated-filter': require('./rules/no-deprecated-filter'), 'no-deprecated-functional-template': require('./rules/no-deprecated-functional-template'), diff --git a/lib/rules/no-deprecated-dollar-scopedslots-api.js b/lib/rules/no-deprecated-dollar-scopedslots-api.js new file mode 100644 index 000000000..f369c70ff --- /dev/null +++ b/lib/rules/no-deprecated-dollar-scopedslots-api.js @@ -0,0 +1,79 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const utils = require('../utils') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'problem', + docs: { + description: + 'disallow using deprecated `$scopedSlots` (in Vue.js 3.0.0+)', + categories: ['vue3-essential'], + url: + 'https://eslint.vuejs.org/rules/no-deprecated-dollar-scopedslots-api.html' + }, + fixable: 'code', + schema: [], + messages: { + deprecated: 'The `$scopedSlots` is deprecated.' + } + }, + + create(context) { + return utils.defineTemplateBodyVisitor( + context, + { + VExpressionContainer(node) { + for (const reference of node.references) { + if (reference.variable != null) { + // Not vm reference + continue + } + if (reference.id.name === '$scopedSlots') { + context.report({ + node: reference.id, + messageId: 'deprecated', + fix(fixer) { + return fixer.replaceText(reference.id, '$slots') + } + }) + } + } + } + }, + utils.defineVueVisitor(context, { + MemberExpression(node) { + if ( + node.property.type !== 'Identifier' || + node.property.name !== '$scopedSlots' + ) { + return + } + if (!utils.isThis(node.object, context)) { + return + } + + context.report({ + node: node.property, + messageId: 'deprecated', + fix(fixer) { + return fixer.replaceText(node.property, '$slots') + } + }) + } + }) + ) + } +} diff --git a/tests/lib/rules/no-deprecated-dollar-scopedslots-api.js b/tests/lib/rules/no-deprecated-dollar-scopedslots-api.js new file mode 100644 index 000000000..303e5de99 --- /dev/null +++ b/tests/lib/rules/no-deprecated-dollar-scopedslots-api.js @@ -0,0 +1,288 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/no-deprecated-dollar-scopedslots-api') + +const RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parser: require.resolve('vue-eslint-parser'), + parserOptions: { ecmaVersion: 2018, sourceType: 'module' } +}) +ruleTester.run('no-deprecated-dollar-scopedslots-api', rule, { + valid: [ + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + + + `, + output: ` + + + `, + errors: [ + { + line: 3, + column: 22, + messageId: 'deprecated', + endLine: 3, + endColumn: 34 + }, + { + line: 8, + column: 25, + messageId: 'deprecated', + endLine: 8, + endColumn: 37 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + output: ` + + + `, + errors: [ + { + line: 3, + column: 31, + messageId: 'deprecated', + endLine: 3, + endColumn: 43 + }, + { + line: 4, + column: 22, + messageId: 'deprecated', + endLine: 4, + endColumn: 34 + }, + { + line: 10, + column: 23, + messageId: 'deprecated', + endLine: 10, + endColumn: 35 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + line: 6, + column: 23, + messageId: 'deprecated' + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + line: 7, + column: 25, + messageId: 'deprecated' + } + ] + } + ] +}) From ffe9ecea8895fbfa2fb1566898f8a5c828107c83 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Fri, 5 Jun 2020 11:14:24 +0900 Subject: [PATCH 002/910] Add `vue/require-slots-as-functions` rule. (#1178) * Add `vue/require-slots-as-functions` rule. * Update require-slots-as-functions.js --- docs/rules/README.md | 1 + docs/rules/require-slots-as-functions.md | 48 +++++++ lib/configs/vue3-essential.js | 1 + lib/index.js | 1 + lib/rules/require-slots-as-functions.js | 124 ++++++++++++++++++ tests/lib/rules/require-slots-as-functions.js | 115 ++++++++++++++++ 6 files changed, 290 insertions(+) create mode 100644 docs/rules/require-slots-as-functions.md create mode 100644 lib/rules/require-slots-as-functions.js create mode 100644 tests/lib/rules/require-slots-as-functions.js diff --git a/docs/rules/README.md b/docs/rules/README.md index 40548ec13..dd5718b08 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -75,6 +75,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi | [vue/require-component-is](./require-component-is.md) | require `v-bind:is` of `` elements | | | [vue/require-prop-type-constructor](./require-prop-type-constructor.md) | require prop type to be a constructor | :wrench: | | [vue/require-render-return](./require-render-return.md) | enforce render function to always return value | | +| [vue/require-slots-as-functions](./require-slots-as-functions.md) | enforce properties of `$slots` to be used as a function | | | [vue/require-toggle-inside-transition](./require-toggle-inside-transition.md) | require control the display of the content inside `` | | | [vue/require-v-for-key](./require-v-for-key.md) | require `v-bind:key` with `v-for` directives | | | [vue/require-valid-default-prop](./require-valid-default-prop.md) | enforce props default values to be valid | | diff --git a/docs/rules/require-slots-as-functions.md b/docs/rules/require-slots-as-functions.md new file mode 100644 index 000000000..8b4c01f91 --- /dev/null +++ b/docs/rules/require-slots-as-functions.md @@ -0,0 +1,48 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/require-slots-as-functions +description: enforce properties of `$slots` to be used as a function +--- +# vue/require-slots-as-functions +> enforce properties of `$slots` to be used as a function + +- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`. + +## :book: Rule Details + +This rule enforces the properties of `$slots` to be used as a function. +`this.$slots.default` was an array of VNode in Vue.js 2.x, but changed to a function that returns an array of VNode in Vue.js 3.x. + + + +```vue + +``` + + + +## :wrench: Options + +Nothing. + +## :books: Further reading + +- [Vue RFCs - 0006-slots-unification](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0006-slots-unification.md) + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-slots-as-functions.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-slots-as-functions.js) diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js index 0e3e02f74..b802623da 100644 --- a/lib/configs/vue3-essential.js +++ b/lib/configs/vue3-essential.js @@ -43,6 +43,7 @@ module.exports = { 'vue/require-component-is': 'error', 'vue/require-prop-type-constructor': 'error', 'vue/require-render-return': 'error', + 'vue/require-slots-as-functions': 'error', 'vue/require-toggle-inside-transition': 'error', 'vue/require-v-for-key': 'error', 'vue/require-valid-default-prop': 'error', diff --git a/lib/index.js b/lib/index.js index 6eb4dc340..f41be8ba7 100644 --- a/lib/index.js +++ b/lib/index.js @@ -112,6 +112,7 @@ module.exports = { 'require-prop-type-constructor': require('./rules/require-prop-type-constructor'), 'require-prop-types': require('./rules/require-prop-types'), 'require-render-return': require('./rules/require-render-return'), + 'require-slots-as-functions': require('./rules/require-slots-as-functions'), 'require-toggle-inside-transition': require('./rules/require-toggle-inside-transition'), 'require-v-for-key': require('./rules/require-v-for-key'), 'require-valid-default-prop': require('./rules/require-valid-default-prop'), diff --git a/lib/rules/require-slots-as-functions.js b/lib/rules/require-slots-as-functions.js new file mode 100644 index 000000000..07493ddbb --- /dev/null +++ b/lib/rules/require-slots-as-functions.js @@ -0,0 +1,124 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const utils = require('../utils') +const { findVariable } = require('eslint-utils') + +/** + * @typedef {import('vue-eslint-parser').AST.ESLintMemberExpression} MemberExpression + * @typedef {import('vue-eslint-parser').AST.ESLintIdentifier} Identifier + * @typedef {import('vue-eslint-parser').AST.ESLintExpression} Expression + */ + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'enforce properties of `$slots` to be used as a function', + categories: ['vue3-essential'], + url: 'https://eslint.vuejs.org/rules/require-slots-as-functions.html' + }, + fixable: null, + schema: [], + messages: { + unexpected: 'Property in `$slots` should be used as function.' + } + }, + + create(context) { + /** + * Verify the given node + * @param {MemberExpression | Identifier} node The node to verify + * @param {Expression} reportNode The node to report + */ + function verify(node, reportNode) { + const parent = node.parent + + if ( + parent.type === 'VariableDeclarator' && + parent.id.type === 'Identifier' + ) { + // const children = this.$slots.foo + verifyReferences(parent.id, reportNode) + return + } + + if ( + parent.type === 'AssignmentExpression' && + parent.right === node && + parent.left.type === 'Identifier' + ) { + // children = this.$slots.foo + verifyReferences(parent.left, reportNode) + return + } + + if ( + // this.$slots.foo.xxx + parent.type === 'MemberExpression' || + // var [foo] = this.$slots.foo + parent.type === 'VariableDeclarator' || + // [...this.$slots.foo] + parent.type === 'SpreadElement' || + // [this.$slots.foo] + parent.type === 'ArrayExpression' + ) { + context.report({ + node: reportNode, + messageId: 'unexpected' + }) + } + } + /** + * Verify the references of the given node. + * @param {Identifier} node The node to verify + * @param {Expression} reportNode The node to report + */ + function verifyReferences(node, reportNode) { + // @ts-ignore + const variable = findVariable(context.getScope(), node) + if (!variable) { + return + } + for (const reference of variable.references) { + if (!reference.isRead()) { + continue + } + /** @type {Identifier} */ + const id = reference.identifier + verify(id, reportNode) + } + } + + return utils.defineVueVisitor(context, { + /** @param {MemberExpression} node */ + MemberExpression(node) { + const object = node.object + if (object.type !== 'MemberExpression') { + return + } + if ( + object.property.type !== 'Identifier' || + object.property.name !== '$slots' + ) { + return + } + if (!utils.isThis(object.object, context)) { + return + } + verify(node, node.property) + } + }) + } +} diff --git a/tests/lib/rules/require-slots-as-functions.js b/tests/lib/rules/require-slots-as-functions.js new file mode 100644 index 000000000..9d79a1ec7 --- /dev/null +++ b/tests/lib/rules/require-slots-as-functions.js @@ -0,0 +1,115 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/require-slots-as-functions') + +const RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parser: require.resolve('vue-eslint-parser'), + parserOptions: { ecmaVersion: 2018, sourceType: 'module' } +}) +ruleTester.run('require-slots-as-functions', rule, { + valid: [ + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Property in `$slots` should be used as function.', + line: 5, + column: 38, + endLine: 5, + endColumn: 45 + }, + { + message: 'Property in `$slots` should be used as function.', + line: 6, + column: 38, + endLine: 6, + endColumn: 45 + } + ] + }, + + { + filename: 'test.vue', + code: ` + + `, + errors: [ + 'Property in `$slots` should be used as function.', + 'Property in `$slots` should be used as function.', + 'Property in `$slots` should be used as function.' + ] + } + ] +}) From 52f34e93bcc06c6ba3c341e351b251e0f6721e87 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Fri, 5 Jun 2020 11:17:06 +0900 Subject: [PATCH 003/910] Add `vue/no-multiple-slot-args` rule. (#1179) * Add `vue/no-multiple-slot-args` rule. * Fixed testcase --- docs/rules/README.md | 2 + docs/rules/no-multiple-slot-args.md | 49 +++++++ lib/configs/recommended.js | 1 + lib/configs/vue3-recommended.js | 1 + lib/index.js | 1 + lib/rules/no-multiple-slot-args.js | 127 ++++++++++++++++++ tests/lib/rules/no-multiple-slot-args.js | 164 +++++++++++++++++++++++ 7 files changed, 345 insertions(+) create mode 100644 docs/rules/no-multiple-slot-args.md create mode 100644 lib/rules/no-multiple-slot-args.js create mode 100644 tests/lib/rules/no-multiple-slot-args.js diff --git a/docs/rules/README.md b/docs/rules/README.md index dd5718b08..f3e2bfdf6 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -147,6 +147,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi |:--------|:------------|:---| | [vue/attributes-order](./attributes-order.md) | enforce order of attributes | :wrench: | | [vue/component-tags-order](./component-tags-order.md) | enforce order of component top-level elements | | +| [vue/no-multiple-slot-args](./no-multiple-slot-args.md) | disallow to pass multiple arguments to scoped slots | | | [vue/no-v-html](./no-v-html.md) | disallow use of v-html to prevent XSS attack | | | [vue/order-in-components](./order-in-components.md) | enforce order of properties in components | :wrench: | | [vue/this-in-template](./this-in-template.md) | disallow usage of `this` in template | | @@ -254,6 +255,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi |:--------|:------------|:---| | [vue/attributes-order](./attributes-order.md) | enforce order of attributes | :wrench: | | [vue/component-tags-order](./component-tags-order.md) | enforce order of component top-level elements | | +| [vue/no-multiple-slot-args](./no-multiple-slot-args.md) | disallow to pass multiple arguments to scoped slots | | | [vue/no-v-html](./no-v-html.md) | disallow use of v-html to prevent XSS attack | | | [vue/order-in-components](./order-in-components.md) | enforce order of properties in components | :wrench: | | [vue/this-in-template](./this-in-template.md) | disallow usage of `this` in template | | diff --git a/docs/rules/no-multiple-slot-args.md b/docs/rules/no-multiple-slot-args.md new file mode 100644 index 000000000..d0d319458 --- /dev/null +++ b/docs/rules/no-multiple-slot-args.md @@ -0,0 +1,49 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/no-multiple-slot-args +description: disallow to pass multiple arguments to scoped slots +--- +# vue/no-multiple-slot-args +> disallow to pass multiple arguments to scoped slots + +- :gear: This rule is included in `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`. + +## :book: Rule Details + +This rule disallows to pass multiple arguments to scoped slots. +In details, it reports call expressions if a call of `this.$scopedSlots` members has 2 or more arguments. + + + +```vue + +``` + + + +## :wrench: Options + +Nothing. + +## :books: Further reading + +- [vuejs/vue#9468](https://github.com/vuejs/vue/issues/9468#issuecomment-462210146) + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-multiple-slot-args.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-multiple-slot-args.js) diff --git a/lib/configs/recommended.js b/lib/configs/recommended.js index e3ca8eba7..e6388e158 100644 --- a/lib/configs/recommended.js +++ b/lib/configs/recommended.js @@ -8,6 +8,7 @@ module.exports = { rules: { 'vue/attributes-order': 'warn', 'vue/component-tags-order': 'warn', + 'vue/no-multiple-slot-args': 'warn', 'vue/no-v-html': 'warn', 'vue/order-in-components': 'warn', 'vue/this-in-template': 'warn' diff --git a/lib/configs/vue3-recommended.js b/lib/configs/vue3-recommended.js index 700f0f8f5..318690c33 100644 --- a/lib/configs/vue3-recommended.js +++ b/lib/configs/vue3-recommended.js @@ -8,6 +8,7 @@ module.exports = { rules: { 'vue/attributes-order': 'warn', 'vue/component-tags-order': 'warn', + 'vue/no-multiple-slot-args': 'warn', 'vue/no-v-html': 'warn', 'vue/order-in-components': 'warn', 'vue/this-in-template': 'warn' diff --git a/lib/index.js b/lib/index.js index f41be8ba7..c0041525f 100644 --- a/lib/index.js +++ b/lib/index.js @@ -71,6 +71,7 @@ module.exports = { 'no-irregular-whitespace': require('./rules/no-irregular-whitespace'), 'no-lifecycle-after-await': require('./rules/no-lifecycle-after-await'), 'no-multi-spaces': require('./rules/no-multi-spaces'), + 'no-multiple-slot-args': require('./rules/no-multiple-slot-args'), 'no-multiple-template-root': require('./rules/no-multiple-template-root'), 'no-mutating-props': require('./rules/no-mutating-props'), 'no-parsing-error': require('./rules/no-parsing-error'), diff --git a/lib/rules/no-multiple-slot-args.js b/lib/rules/no-multiple-slot-args.js new file mode 100644 index 000000000..e8bcae96c --- /dev/null +++ b/lib/rules/no-multiple-slot-args.js @@ -0,0 +1,127 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const utils = require('../utils') +const { findVariable } = require('eslint-utils') + +/** + * @typedef {import('vue-eslint-parser').AST.ESLintMemberExpression} MemberExpression + * @typedef {import('vue-eslint-parser').AST.ESLintIdentifier} Identifier + */ + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow to pass multiple arguments to scoped slots', + categories: ['vue3-recommended', 'recommended'], + url: 'https://eslint.vuejs.org/rules/no-multiple-slot-args.html' + }, + fixable: null, + schema: [], + messages: { + unexpected: 'Unexpected multiple arguments.', + unexpectedSpread: 'Unexpected spread argument.' + } + }, + + create(context) { + /** + * Verify the given node + * @param {MemberExpression | Identifier} node The node to verify + */ + function verify(node) { + const parent = node.parent + + if ( + parent.type === 'VariableDeclarator' && + parent.id.type === 'Identifier' + ) { + // const foo = this.$scopedSlots.foo + verifyReferences(parent.id) + return + } + + if ( + parent.type === 'AssignmentExpression' && + parent.right === node && + parent.left.type === 'Identifier' + ) { + // foo = this.$scopedSlots.foo + verifyReferences(parent.left) + return + } + + if (parent.type !== 'CallExpression' || parent.arguments.includes(node)) { + return + } + + if (!parent.arguments.length) { + return + } + if (parent.arguments.length > 1) { + context.report({ + node: parent.arguments[1], + messageId: 'unexpected' + }) + } + if (parent.arguments[0].type === 'SpreadElement') { + context.report({ + node: parent.arguments[0], + messageId: 'unexpectedSpread' + }) + } + } + /** + * Verify the references of the given node. + * @param {Identifier} node The node to verify + */ + function verifyReferences(node) { + // @ts-ignore + const variable = findVariable(context.getScope(), node) + if (!variable) { + return + } + for (const reference of variable.references) { + if (!reference.isRead()) { + continue + } + /** @type {Identifier} */ + const id = reference.identifier + verify(id) + } + } + + return utils.defineVueVisitor(context, { + /** @param {MemberExpression} node */ + MemberExpression(node) { + const object = node.object + if (object.type !== 'MemberExpression') { + return + } + if ( + object.property.type !== 'Identifier' || + (object.property.name !== '$slots' && + object.property.name !== '$scopedSlots') + ) { + return + } + if (!utils.isThis(object.object, context)) { + return + } + verify(node) + } + }) + } +} diff --git a/tests/lib/rules/no-multiple-slot-args.js b/tests/lib/rules/no-multiple-slot-args.js new file mode 100644 index 000000000..fb42767b9 --- /dev/null +++ b/tests/lib/rules/no-multiple-slot-args.js @@ -0,0 +1,164 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/no-multiple-slot-args') + +const RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parser: require.resolve('vue-eslint-parser'), + parserOptions: { ecmaVersion: 2018, sourceType: 'module' } +}) +ruleTester.run('no-multiple-slot-args', rule, { + valid: [ + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Unexpected multiple arguments.', + line: 5, + column: 42, + endLine: 5, + endColumn: 45 + }, + { + message: 'Unexpected multiple arguments.', + line: 6, + column: 38, + endLine: 6, + endColumn: 41 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Unexpected multiple arguments.', + line: 7, + column: 42, + endLine: 7, + endColumn: 49 + }, + { + message: 'Unexpected spread argument.', + line: 10, + column: 34, + endLine: 10, + endColumn: 40 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + 'Unexpected multiple arguments.', + 'Unexpected multiple arguments.' + ] + } + ] +}) From 083ea82d88b93714eca99c0669a80006f812a1d7 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Fri, 5 Jun 2020 11:18:08 +0900 Subject: [PATCH 004/910] Improved `vue/no-ref-as-operand` rule. (#1180) - Changed `vue/no-ref-as-operand` rule to additionally track variables generated by `computed`, `toRef`, `customRef` and `shallowRef`. - Changed `vue/no-ref-as-operand` rule to report incorrect use of TemplateLiteral and MemberExpression. --- docs/rules/no-ref-as-operand.md | 3 +- lib/rules/no-ref-as-operand.js | 56 ++++++++++++--- tests/lib/rules/no-ref-as-operand.js | 103 ++++++++++++++++++++++++++- 3 files changed, 151 insertions(+), 11 deletions(-) diff --git a/docs/rules/no-ref-as-operand.md b/docs/rules/no-ref-as-operand.md index c983d669d..609a07279 100644 --- a/docs/rules/no-ref-as-operand.md +++ b/docs/rules/no-ref-as-operand.md @@ -11,7 +11,8 @@ description: disallow use of value wrapped by `ref()` (Composition API) as an op ## :book: Rule Details -This rule reports cases where a ref is used incorrectly as an operand. +This rule reports cases where a ref is used incorrectly as an operand. +You must use `.value` to access the `Ref` value. diff --git a/lib/rules/no-ref-as-operand.js b/lib/rules/no-ref-as-operand.js index 11873edf0..cfd09921c 100644 --- a/lib/rules/no-ref-as-operand.js +++ b/lib/rules/no-ref-as-operand.js @@ -4,6 +4,7 @@ */ 'use strict' const { ReferenceTracker, findVariable } = require('eslint-utils') +const utils = require('../utils') module.exports = { meta: { @@ -18,19 +19,23 @@ module.exports = { schema: [], messages: { requireDotValue: - 'Must use `.value` to read or write the value wrapped by `ref()`.' + 'Must use `.value` to read or write the value wrapped by `{{method}}()`.' } }, create(context) { const refReferenceIds = new Map() function reportIfRefWrapped(node) { - if (!refReferenceIds.has(node)) { + const data = refReferenceIds.get(node) + if (!data) { return } context.report({ node, - messageId: 'requireDotValue' + messageId: 'requireDotValue', + data: { + method: data.method + } }) } return { @@ -41,11 +46,23 @@ module.exports = { [ReferenceTracker.ESM]: true, ref: { [ReferenceTracker.CALL]: true + }, + computed: { + [ReferenceTracker.CALL]: true + }, + toRef: { + [ReferenceTracker.CALL]: true + }, + customRef: { + [ReferenceTracker.CALL]: true + }, + shallowRef: { + [ReferenceTracker.CALL]: true } } } - for (const { node } of tracker.iterateEsmReferences(traceMap)) { + for (const { node, path } of tracker.iterateEsmReferences(traceMap)) { const variableDeclarator = node.parent if ( !variableDeclarator || @@ -73,7 +90,8 @@ module.exports = { refReferenceIds.set(reference.identifier, { variableDeclarator, - variableDeclaration + variableDeclaration, + method: path[1] }) } } @@ -108,13 +126,13 @@ module.exports = { return } // Report only constants. - const info = refReferenceIds.get(node) - if (!info) { + const data = refReferenceIds.get(node) + if (!data) { return } if ( - !info.variableDeclaration || - info.variableDeclaration.kind !== 'const' + !data.variableDeclaration || + data.variableDeclaration.kind !== 'const' ) { return } @@ -126,6 +144,26 @@ module.exports = { return } reportIfRefWrapped(node) + }, + // `${refValue}` + 'TemplateLiteral>Identifier'(node) { + reportIfRefWrapped(node) + }, + // refValue.x + 'MemberExpression>Identifier'(node) { + if (node.parent.object !== node) { + return + } + const name = utils.getStaticPropertyName(node.parent) + if ( + name === 'value' || + name == null || + // WritableComputedRef + name === 'effect' + ) { + return + } + reportIfRefWrapped(node) } } } diff --git a/tests/lib/rules/no-ref-as-operand.js b/tests/lib/rules/no-ref-as-operand.js index 6eeb11b54..5d53a5571 100644 --- a/tests/lib/rules/no-ref-as-operand.js +++ b/tests/lib/rules/no-ref-as-operand.js @@ -103,7 +103,25 @@ tester.run('no-ref-as-operand', rule, { import { ref } from 'vue' const count = ref count++ - ` + `, + { + code: ` + + ` + }, + { + code: ` + + ` + } ], invalid: [ { @@ -332,6 +350,89 @@ tester.run('no-ref-as-operand', rule, { line: 9 } ] + }, + { + code: ` + + `, + errors: [ + { + message: + 'Must use `.value` to read or write the value wrapped by `ref()`.', + line: 33 + }, + { + message: + 'Must use `.value` to read or write the value wrapped by `computed()`.', + line: 34 + }, + { + message: + 'Must use `.value` to read or write the value wrapped by `toRef()`.', + line: 36 + }, + { + message: + 'Must use `.value` to read or write the value wrapped by `customRef()`.', + line: 36 + }, + { + message: + 'Must use `.value` to read or write the value wrapped by `shallowRef()`.', + line: 38 + } + ] + }, + { + code: ` + + `, + errors: [ + { + messageId: 'requireDotValue' + } + ] } ] }) From 50bf17a748a39b2d6865774ae82131c4f40ecd25 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Fri, 5 Jun 2020 11:19:22 +0900 Subject: [PATCH 005/910] Improved autofix of `vue/order-in-components` rule to understand "Nullish Coalescing". (#1183) --- lib/rules/order-in-components.js | 20 +++++++++++--------- tests/lib/rules/order-in-components.js | 6 ++++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/rules/order-in-components.js b/lib/rules/order-in-components.js index d26fa7d10..2366cabbf 100644 --- a/lib/rules/order-in-components.js +++ b/lib/rules/order-in-components.js @@ -64,7 +64,7 @@ function isComma (node) { return node.type === 'Punctuator' && node.value === ',' } -const ARITHMETIC_OPERATORS = ['+', '-', '*', '/', '%', '**'] +const ARITHMETIC_OPERATORS = ['+', '-', '*', '/', '%', '**'/* es2016 */] const BITWISE_OPERATORS = ['&', '|', '^', '~', '<<', '>>', '>>>'] const COMPARISON_OPERATORS = ['==', '!=', '===', '!==', '>', '>=', '<', '<='] const RELATIONAL_OPERATORS = ['in', 'instanceof'] @@ -74,7 +74,7 @@ const ALL_BINARY_OPERATORS = [].concat( COMPARISON_OPERATORS, RELATIONAL_OPERATORS ) -const LOGICAL_OPERATORS = ['&&', '||'] +const LOGICAL_OPERATORS = ['&&', '||', '??'/* es2020 */] /* * Result `true` if the node is sure that there are no side effects @@ -94,17 +94,15 @@ const LOGICAL_OPERATORS = ['&&', '||'] */ function isNotSideEffectsNode (node, visitorKeys) { let result = true - const noSideEffectsNodes = new Set() + let skipNode = false traverseNodes(node, { visitorKeys, - enterNode (node, parent) { - if (!result) { + enterNode (node) { + if (!result || skipNode) { return } if ( - // parent has no side effects - noSideEffectsNodes.has(parent) || // no side effects node node.type === 'FunctionExpression' || node.type === 'Identifier' || @@ -113,7 +111,7 @@ function isNotSideEffectsNode (node, visitorKeys) { node.type === 'ArrowFunctionExpression' || node.type === 'TemplateElement' ) { - noSideEffectsNodes.add(node) + skipNode = node } else if ( node.type !== 'Property' && node.type !== 'ObjectExpression' && @@ -131,7 +129,11 @@ function isNotSideEffectsNode (node, visitorKeys) { result = false } }, - leaveNode () {} + leaveNode (node) { + if (skipNode === node) { + skipNode = null + } + } }) return result diff --git a/tests/lib/rules/order-in-components.js b/tests/lib/rules/order-in-components.js index f94b4ec67..538234944 100644 --- a/tests/lib/rules/order-in-components.js +++ b/tests/lib/rules/order-in-components.js @@ -10,7 +10,7 @@ const RuleTester = require('eslint').RuleTester const ruleTester = new RuleTester() const parserOptions = { - ecmaVersion: 2018, + ecmaVersion: 2020, sourceType: 'module' } @@ -751,6 +751,7 @@ ruleTester.run('order-in-components', rule, { testConditional: a ? b : c, testYield: function* () {}, testTemplate: \`a:\${a},b:\${b},c:\${c}.\`, + testNullish: a ?? b, name: 'burger', }; `, @@ -768,11 +769,12 @@ ruleTester.run('order-in-components', rule, { testConditional: a ? b : c, testYield: function* () {}, testTemplate: \`a:\${a},b:\${b},c:\${c}.\`, + testNullish: a ?? b, }; `, errors: [{ message: 'The "name" property should be above the "data" property on line 3.', - line: 13 + line: 14 }] } ] From af90fed4dfe1dbd618164b2d5ae1272e060604b5 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Fri, 5 Jun 2020 11:20:20 +0900 Subject: [PATCH 006/910] Improved to not report that a value is required when parsing error, for `vue/valid-v-bind-sync`, `vue/valid-v-bind`, `vue/valid-v-else-if`, `vue/valid-v-for`, `vue/valid-v-html`, `vue/valid-v-if`, `vue/valid-v-model`, `vue/valid-v-on`, `vue/valid-v-show`, `vue/valid-v-slot` and `vue/valid-v-text` rules. (#1184) --- lib/rules/valid-v-bind-sync.js | 45 +++++++++-------- lib/rules/valid-v-bind.js | 2 +- lib/rules/valid-v-else-if.js | 2 +- lib/rules/valid-v-else.js | 2 +- lib/rules/valid-v-for.js | 2 +- lib/rules/valid-v-html.js | 2 +- lib/rules/valid-v-if.js | 2 +- lib/rules/valid-v-model.js | 52 ++++++++++---------- lib/rules/valid-v-on.js | 30 ++++++++---- lib/rules/valid-v-show.js | 2 +- lib/rules/valid-v-slot.js | 4 +- lib/rules/valid-v-text.js | 2 +- lib/utils/index.js | 73 +++++++++++++++++++++++++--- tests/lib/rules/valid-v-bind-sync.js | 15 ++++++ tests/lib/rules/valid-v-bind.js | 16 ++++++ tests/lib/rules/valid-v-cloak.js | 18 +++++++ tests/lib/rules/valid-v-else-if.js | 19 ++++++++ tests/lib/rules/valid-v-else.js | 19 ++++++++ tests/lib/rules/valid-v-for.js | 27 +++++++--- tests/lib/rules/valid-v-html.js | 16 ++++++ tests/lib/rules/valid-v-if.js | 16 ++++++ tests/lib/rules/valid-v-model.js | 16 ++++++ tests/lib/rules/valid-v-on.js | 35 +++++++++++++ tests/lib/rules/valid-v-once.js | 18 +++++++ tests/lib/rules/valid-v-pre.js | 18 +++++++ tests/lib/rules/valid-v-show.js | 13 ++++- tests/lib/rules/valid-v-slot.js | 28 ++++++++++- tests/lib/rules/valid-v-text.js | 16 ++++++ 28 files changed, 426 insertions(+), 84 deletions(-) diff --git a/lib/rules/valid-v-bind-sync.js b/lib/rules/valid-v-bind-sync.js index 9d24442ac..6b9225308 100644 --- a/lib/rules/valid-v-bind-sync.js +++ b/lib/rules/valid-v-bind-sync.js @@ -37,10 +37,7 @@ function isValidElement(node) { * @returns {boolean} `true` if the node can be LHS. */ function isLhs(node) { - return ( - Boolean(node) && - (node.type === 'Identifier' || node.type === 'MemberExpression') - ) + return node.type === 'Identifier' || node.type === 'MemberExpression' } // ------------------------------------------------------------------------------ @@ -85,30 +82,32 @@ module.exports = { }) } - if (node.value) { - if (!isLhs(node.value.expression)) { + if (!node.value || !node.value.expression) { + return + } + + if (!isLhs(node.value.expression)) { + context.report({ + node, + loc: node.loc, + messageId: 'unexpectedNonLhsExpression' + }) + } + + for (const reference of node.value.references) { + const id = reference.id + if (id.parent.type !== 'VExpressionContainer') { + continue + } + const variable = reference.variable + if (variable) { context.report({ node, loc: node.loc, - messageId: 'unexpectedNonLhsExpression' + messageId: 'unexpectedUpdateIterationVariable', + data: { varName: id.name } }) } - - for (const reference of node.value.references) { - const id = reference.id - if (id.parent.type !== 'VExpressionContainer') { - continue - } - const variable = reference.variable - if (variable) { - context.report({ - node, - loc: node.loc, - messageId: 'unexpectedUpdateIterationVariable', - data: { varName: id.name } - }) - } - } } } }) diff --git a/lib/rules/valid-v-bind.js b/lib/rules/valid-v-bind.js index dfb1f97e2..7d88bad38 100644 --- a/lib/rules/valid-v-bind.js +++ b/lib/rules/valid-v-bind.js @@ -48,7 +48,7 @@ module.exports = { } } - if (!utils.hasAttributeValue(node)) { + if (!node.value || utils.isEmptyValueDirective(node, context)) { context.report({ node, loc: node.loc, diff --git a/lib/rules/valid-v-else-if.js b/lib/rules/valid-v-else-if.js index b68caa746..59252855b 100644 --- a/lib/rules/valid-v-else-if.js +++ b/lib/rules/valid-v-else-if.js @@ -70,7 +70,7 @@ module.exports = { message: "'v-else-if' directives require no modifier." }) } - if (!utils.hasAttributeValue(node)) { + if (!node.value || utils.isEmptyValueDirective(node, context)) { context.report({ node, loc: node.loc, diff --git a/lib/rules/valid-v-else.js b/lib/rules/valid-v-else.js index 3379fa326..2479f467e 100644 --- a/lib/rules/valid-v-else.js +++ b/lib/rules/valid-v-else.js @@ -70,7 +70,7 @@ module.exports = { message: "'v-else' directives require no modifier." }) } - if (utils.hasAttributeValue(node)) { + if (node.value) { context.report({ node, loc: node.loc, diff --git a/lib/rules/valid-v-for.js b/lib/rules/valid-v-for.js index 681a5dcf1..443afc2d0 100644 --- a/lib/rules/valid-v-for.js +++ b/lib/rules/valid-v-for.js @@ -137,7 +137,7 @@ module.exports = { message: "'v-for' directives require no modifier." }) } - if (!utils.hasAttributeValue(node)) { + if (!node.value || utils.isEmptyValueDirective(node, context)) { context.report({ node, loc: node.loc, diff --git a/lib/rules/valid-v-html.js b/lib/rules/valid-v-html.js index 1bc1fcb2b..575939a2a 100644 --- a/lib/rules/valid-v-html.js +++ b/lib/rules/valid-v-html.js @@ -44,7 +44,7 @@ module.exports = { message: "'v-html' directives require no modifier." }) } - if (!utils.hasAttributeValue(node)) { + if (!node.value || utils.isEmptyValueDirective(node, context)) { context.report({ node, loc: node.loc, diff --git a/lib/rules/valid-v-if.js b/lib/rules/valid-v-if.js index 6c24cabc9..c07ae2c81 100644 --- a/lib/rules/valid-v-if.js +++ b/lib/rules/valid-v-if.js @@ -62,7 +62,7 @@ module.exports = { message: "'v-if' directives require no modifier." }) } - if (!utils.hasAttributeValue(node)) { + if (!node.value || utils.isEmptyValueDirective(node, context)) { context.report({ node, loc: node.loc, diff --git a/lib/rules/valid-v-model.js b/lib/rules/valid-v-model.js index 7dc22a4d7..0c7431c05 100644 --- a/lib/rules/valid-v-model.js +++ b/lib/rules/valid-v-model.js @@ -42,10 +42,7 @@ function isValidElement(node) { * @returns {boolean} `true` if the node can be LHS. */ function isLhs(node) { - return ( - node != null && - (node.type === 'Identifier' || node.type === 'MemberExpression') - ) + return node.type === 'Identifier' || node.type === 'MemberExpression' } /** @@ -133,40 +130,43 @@ module.exports = { } } - if (!utils.hasAttributeValue(node)) { + if (!node.value || utils.isEmptyValueDirective(node, context)) { context.report({ node, loc: node.loc, message: "'v-model' directives require that attribute value." }) + return + } + if (!node.value.expression) { + // Parsing error + return } - if (node.value) { - if (!isLhs(node.value.expression)) { + if (!isLhs(node.value.expression)) { + context.report({ + node, + loc: node.loc, + message: + "'v-model' directives require the attribute value which is valid as LHS." + }) + } + + for (const reference of node.value.references) { + const id = reference.id + if (id.parent.type !== 'VExpressionContainer') { + continue + } + + const variable = getVariable(id.name, element) + if (variable != null) { context.report({ node, loc: node.loc, message: - "'v-model' directives require the attribute value which is valid as LHS." + "'v-model' directives cannot update the iteration variable '{{varName}}' itself.", + data: { varName: id.name } }) } - - for (const reference of node.value.references) { - const id = reference.id - if (id.parent.type !== 'VExpressionContainer') { - continue - } - - const variable = getVariable(id.name, element) - if (variable != null) { - context.report({ - node, - loc: node.loc, - message: - "'v-model' directives cannot update the iteration variable '{{varName}}' itself.", - data: { varName: id.name } - }) - } - } } } }) diff --git a/lib/rules/valid-v-on.js b/lib/rules/valid-v-on.js index 4e014e00c..bcdb76db0 100644 --- a/lib/rules/valid-v-on.js +++ b/lib/rules/valid-v-on.js @@ -108,20 +108,30 @@ module.exports = { } if ( - !utils.hasAttributeValue(node) && + (!node.value || !node.value.expression) && !node.key.modifiers.some((modifier) => VERB_MODIFIERS.has(modifier.name) ) ) { - if (node.value && sourceCode.getText(node.value.expression)) { - const value = sourceCode.getText(node.value) - context.report({ - node, - loc: node.loc, - message: - 'Avoid using JavaScript keyword as "v-on" value: {{value}}.', - data: { value } - }) + if (node.value && !utils.isEmptyValueDirective(node, context)) { + const valueText = sourceCode.getText(node.value) + let innerText = valueText + if ( + (valueText[0] === '"' || valueText[0] === "'") && + valueText[0] === valueText[valueText.length - 1] + ) { + // quoted + innerText = valueText.slice(1, -1) + } + if (/^\w+$/.test(innerText)) { + context.report({ + node, + loc: node.loc, + message: + 'Avoid using JavaScript keyword as "v-on" value: {{value}}.', + data: { value: valueText } + }) + } } else { context.report({ node, diff --git a/lib/rules/valid-v-show.js b/lib/rules/valid-v-show.js index 51e4014fd..71553722c 100644 --- a/lib/rules/valid-v-show.js +++ b/lib/rules/valid-v-show.js @@ -44,7 +44,7 @@ module.exports = { message: "'v-show' directives require no modifier." }) } - if (!utils.hasAttributeValue(node)) { + if (!node.value || utils.isEmptyValueDirective(node, context)) { context.report({ node, loc: node.loc, diff --git a/lib/rules/valid-v-slot.js b/lib/rules/valid-v-slot.js index 79b052269..118dc8463 100644 --- a/lib/rules/valid-v-slot.js +++ b/lib/rules/valid-v-slot.js @@ -264,7 +264,9 @@ module.exports = { if ( ownerElement === element && isDefaultSlot && - !utils.hasAttributeValue(node) + (!node.value || + utils.isEmptyValueDirective(node, context) || + utils.isEmptyExpressionValueDirective(node, context)) ) { context.report({ node, diff --git a/lib/rules/valid-v-text.js b/lib/rules/valid-v-text.js index 2e1881ce3..f0a3f09fa 100644 --- a/lib/rules/valid-v-text.js +++ b/lib/rules/valid-v-text.js @@ -44,7 +44,7 @@ module.exports = { message: "'v-text' directives require no modifier." }) } - if (!utils.hasAttributeValue(node)) { + if (!node.value || utils.isEmptyValueDirective(node, context)) { context.report({ node, loc: node.loc, diff --git a/lib/utils/index.js b/lib/utils/index.js index 43c919f88..b61b52926 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -290,16 +290,73 @@ module.exports = { }, /** - * Check whether the given attribute has their attribute value. - * @param {ASTNode} node The attribute node to check. - * @returns {boolean} `true` if the attribute has their value. + * Check whether the given directive attribute has their empty value (`=""`). + * @param {ASTNode} node The directive attribute node to check. + * @param {RuleContext} context The rule context to use parser services. + * @returns {boolean} `true` if the directive attribute has their empty value (`=""`). */ - hasAttributeValue (node) { + isEmptyValueDirective (node, context) { assert(node && node.type === 'VAttribute') - return ( - node.value != null && - (node.value.expression != null || node.value.syntaxError != null) - ) + if (node.value == null) { + return false + } + if (node.value.expression != null) { + return false + } + + let valueText = context.getSourceCode().getText(node.value) + if ((valueText[0] === '"' || valueText[0] === "'") && valueText[0] === valueText[valueText.length - 1]) { + // quoted + valueText = valueText.slice(1, -1) + } + if (!valueText) { + // empty + return true + } + return false + }, + + /** + * Check whether the given directive attribute has their empty expression value (e.g. `=" "`, `="/* */"`). + * @param {ASTNode} node The directive attribute node to check. + * @param {RuleContext} context The rule context to use parser services. + * @returns {boolean} `true` if the directive attribute has their empty expression value. + */ + isEmptyExpressionValueDirective (node, context) { + assert(node && node.type === 'VAttribute') + if (node.value == null) { + return false + } + if (node.value.expression != null) { + return false + } + + const valueNode = node.value + const tokenStore = context.parserServices.getTemplateBodyTokenStore() + let quote1 = null + let quote2 = null + // `node.value` may be only comments, so cannot get the correct tokens with `tokenStore.getTokens(node.value)`. + for (const token of tokenStore.getTokens(node)) { + if (token.range[1] <= valueNode.range[0]) { + continue + } + if (valueNode.range[1] <= token.range[0]) { + // empty + return true + } + if (!quote1 && token.type === 'Punctuator' && (token.value === '"' || token.value === "'")) { + quote1 = token + continue + } + if (!quote2 && quote1 && token.type === 'Punctuator' && (token.value === quote1.value)) { + quote2 = token + continue + } + // not empty + return false + } + // empty + return true }, /** diff --git a/tests/lib/rules/valid-v-bind-sync.js b/tests/lib/rules/valid-v-bind-sync.js index 385f5d991..ea8bdf333 100644 --- a/tests/lib/rules/valid-v-bind-sync.js +++ b/tests/lib/rules/valid-v-bind-sync.js @@ -130,6 +130,21 @@ tester.run('valid-v-bind-sync', rule, { { filename: 'test.vue', code: '' + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '' + }, + // comment value (parsing error) + { + filename: 'comment-value.vue', + code: '' + }, + // empty value (valid-v-bind) + { + filename: 'empty-value.vue', + code: '' } ], invalid: [ diff --git a/tests/lib/rules/valid-v-bind.js b/tests/lib/rules/valid-v-bind.js index d98ff34d7..b5e71af7c 100644 --- a/tests/lib/rules/valid-v-bind.js +++ b/tests/lib/rules/valid-v-bind.js @@ -74,6 +74,16 @@ tester.run('valid-v-bind', rule, { { filename: 'test.vue', code: "" + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '' + }, + // comment value (parsing error) + { + filename: 'comment-value.vue', + code: '' } ], invalid: [ @@ -91,6 +101,12 @@ tester.run('valid-v-bind', rule, { filename: 'test.vue', code: "", errors: ["'v-bind' directives don't support the modifier 'unknown'."] + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: ["'v-bind' directives require an attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-cloak.js b/tests/lib/rules/valid-v-cloak.js index 8a8a0acad..abecacba4 100644 --- a/tests/lib/rules/valid-v-cloak.js +++ b/tests/lib/rules/valid-v-cloak.js @@ -47,6 +47,24 @@ tester.run('valid-v-cloak', rule, { filename: 'test.vue', code: '', errors: ["'v-cloak' directives require no attribute value."] + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '', + errors: ["'v-cloak' directives require no attribute value."] + }, + // comment value + { + filename: 'comment-value.vue', + code: '', + errors: ["'v-cloak' directives require no attribute value."] + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: ["'v-cloak' directives require no attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-else-if.js b/tests/lib/rules/valid-v-else-if.js index 778d07339..e9f05d1e2 100644 --- a/tests/lib/rules/valid-v-else-if.js +++ b/tests/lib/rules/valid-v-else-if.js @@ -40,6 +40,18 @@ tester.run('valid-v-else-if', rule, { { filename: 'test.vue', code: `` + }, + // parsing error + { + filename: 'parsing-error.vue', + code: + '' + }, + // comment value (parsing error) + { + filename: 'comment-value.vue', + code: + '' } ], invalid: [ @@ -122,6 +134,13 @@ tester.run('valid-v-else-if', rule, { code: '', errors: ["'v-else-if' directives require that attribute value."] + }, + // empty value + { + filename: 'empty-value.vue', + code: + '', + errors: ["'v-else-if' directives require that attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-else.js b/tests/lib/rules/valid-v-else.js index 1e235f9b5..31b067861 100644 --- a/tests/lib/rules/valid-v-else.js +++ b/tests/lib/rules/valid-v-else.js @@ -120,6 +120,25 @@ tester.run('valid-v-else', rule, { code: '', errors: ["'v-else' directives require no attribute value."] + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '', + errors: ["'v-else' directives require no attribute value."] + }, + // comment value + { + filename: 'comment-value.vue', + code: + '', + errors: ["'v-else' directives require no attribute value."] + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: ["'v-else' directives require no attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-for.js b/tests/lib/rules/valid-v-for.js index 74a1ea614..94fa43467 100644 --- a/tests/lib/rules/valid-v-for.js +++ b/tests/lib/rules/valid-v-for.js @@ -127,6 +127,21 @@ tester.run('valid-v-for', rule, { ` + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '' + }, + { + filename: 'test.vue', + code: + '' + }, + // comment value (parsing error) + { + filename: 'comment-value.vue', + code: '' } ], invalid: [ @@ -245,12 +260,6 @@ tester.run('valid-v-for', rule, { '', errors: ["Custom elements in iteration require 'v-bind:key' directives."] }, - { - filename: 'test.vue', - code: - '', - errors: ["'v-for' directives require that attribute value."] - }, { filename: 'test.vue', code: @@ -309,6 +318,12 @@ tester.run('valid-v-for', rule, { ` + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: ["'v-for' directives require that attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-html.js b/tests/lib/rules/valid-v-html.js index 9a30bcf48..afb312f74 100644 --- a/tests/lib/rules/valid-v-html.js +++ b/tests/lib/rules/valid-v-html.js @@ -30,6 +30,16 @@ tester.run('valid-v-html', rule, { { filename: 'test.vue', code: '' + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '' + }, + // comment value (parsing error) + { + filename: 'comment-value.vue', + code: '' } ], invalid: [ @@ -47,6 +57,12 @@ tester.run('valid-v-html', rule, { filename: 'test.vue', code: '', errors: ["'v-html' directives require that attribute value."] + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: ["'v-html' directives require that attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-if.js b/tests/lib/rules/valid-v-if.js index effe04836..d3afe5780 100644 --- a/tests/lib/rules/valid-v-if.js +++ b/tests/lib/rules/valid-v-if.js @@ -30,6 +30,16 @@ tester.run('valid-v-if', rule, { { filename: 'test.vue', code: '' + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '' + }, + // comment value (parsing error) + { + filename: 'comment-value.vue', + code: '' } ], invalid: [ @@ -62,6 +72,12 @@ tester.run('valid-v-if', rule, { filename: 'test.vue', code: '', errors: ["'v-if' directives require that attribute value."] + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: ["'v-if' directives require that attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-model.js b/tests/lib/rules/valid-v-model.js index c9c28585a..9e78ef47a 100644 --- a/tests/lib/rules/valid-v-model.js +++ b/tests/lib/rules/valid-v-model.js @@ -150,6 +150,16 @@ tester.run('valid-v-model', rule, { filename: 'test.vue', code: '' + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '' + }, + // comment value (parsing error) + { + filename: 'comment-value.vue', + code: '' } ], invalid: [ @@ -216,6 +226,12 @@ tester.run('valid-v-model', rule, { errors: [ "'v-model' directives cannot update the iteration variable 'e' itself." ] + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: ["'v-model' directives require that attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-on.js b/tests/lib/rules/valid-v-on.js index 911039ea5..6f162e1df 100644 --- a/tests/lib/rules/valid-v-on.js +++ b/tests/lib/rules/valid-v-on.js @@ -96,6 +96,33 @@ tester.run('valid-v-on', rule, { filename: 'test.vue', code: '', options: [{ modifiers: ['bar', 'aaa'] }] + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '' + }, + // comment value (valid) + { + filename: 'comment-value.vue', + code: '' + }, + { + filename: 'comment-value.vue', + code: '' + }, + { + filename: 'comment-value.vue', + code: '' + }, + { + filename: 'comment-value.vue', + code: '' + }, + // empty value + { + filename: 'empty-value.vue', + code: '' } ], invalid: [ @@ -139,6 +166,14 @@ tester.run('valid-v-on', rule, { filename: 'test.vue', code: '', errors: ['Avoid using JavaScript keyword as "v-on" value: "delete".'] + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: [ + "'v-on' directives require a value or verb modifier (like 'stop' or 'prevent')." + ] } ] }) diff --git a/tests/lib/rules/valid-v-once.js b/tests/lib/rules/valid-v-once.js index 0c6a71a20..c9f3750c3 100644 --- a/tests/lib/rules/valid-v-once.js +++ b/tests/lib/rules/valid-v-once.js @@ -47,6 +47,24 @@ tester.run('valid-v-once', rule, { filename: 'test.vue', code: '', errors: ["'v-once' directives require no attribute value."] + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '', + errors: ["'v-once' directives require no attribute value."] + }, + // comment value + { + filename: 'comment-value.vue', + code: '', + errors: ["'v-once' directives require no attribute value."] + }, + // empty value + { + filename: 'comment-value.vue', + code: '', + errors: ["'v-once' directives require no attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-pre.js b/tests/lib/rules/valid-v-pre.js index ae9d38e6f..3d9c9d883 100644 --- a/tests/lib/rules/valid-v-pre.js +++ b/tests/lib/rules/valid-v-pre.js @@ -47,6 +47,24 @@ tester.run('valid-v-pre', rule, { filename: 'test.vue', code: '', errors: ["'v-pre' directives require no attribute value."] + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '', + errors: ["'v-pre' directives require no attribute value."] + }, + // comment value + { + filename: 'comment-value.vue', + code: '', + errors: ["'v-pre' directives require no attribute value."] + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: ["'v-pre' directives require no attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-show.js b/tests/lib/rules/valid-v-show.js index 0471ccd9f..4220e8749 100644 --- a/tests/lib/rules/valid-v-show.js +++ b/tests/lib/rules/valid-v-show.js @@ -30,6 +30,16 @@ tester.run('valid-v-show', rule, { { filename: 'test.vue', code: '' + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '' + }, + // comment value (parsing error) + { + filename: 'comment-value.vue', + code: '' } ], invalid: [ @@ -48,8 +58,9 @@ tester.run('valid-v-show', rule, { code: '', errors: ["'v-show' directives require that attribute value."] }, + // empty value { - filename: 'test.vue', + filename: 'empty-value.vue', code: '', errors: ["'v-show' directives require that attribute value."] } diff --git a/tests/lib/rules/valid-v-slot.js b/tests/lib/rules/valid-v-slot.js index ab8307fed..0043756f1 100644 --- a/tests/lib/rules/valid-v-slot.js +++ b/tests/lib/rules/valid-v-slot.js @@ -83,7 +83,13 @@ tester.run('valid-v-slot', rule, { - ` + `, + // parsing error + { + filename: 'parsing-error.vue', + code: + '' + } ], invalid: [ // Verify location. @@ -294,6 +300,26 @@ tester.run('valid-v-slot', rule, { `, errors: [{ messageId: 'requireAttributeValue' }] + }, + // comment value + { + filename: 'comment-value1.vue', + code: + '', + errors: [{ messageId: 'requireAttributeValue' }] + }, + { + filename: 'comment-value2.vue', + code: + '', + errors: [{ messageId: 'requireAttributeValue' }] + }, + // empty value + { + filename: 'empty-value.vue', + code: + '', + errors: [{ messageId: 'requireAttributeValue' }] } ] }) diff --git a/tests/lib/rules/valid-v-text.js b/tests/lib/rules/valid-v-text.js index 3956462ce..611b1a31a 100644 --- a/tests/lib/rules/valid-v-text.js +++ b/tests/lib/rules/valid-v-text.js @@ -34,6 +34,16 @@ tester.run('valid-v-text', rule, { { filename: 'test.vue', code: '' + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '' + }, + // comment value (parsing error) + { + filename: 'parsing-error.vue', + code: '' } ], invalid: [ @@ -51,6 +61,12 @@ tester.run('valid-v-text', rule, { filename: 'test.vue', code: '', errors: ["'v-text' directives require that attribute value."] + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: ["'v-text' directives require that attribute value."] } ] }) From e5c835eb957340b153ea2047e3ba274b88ba01bb Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Fri, 5 Jun 2020 14:29:35 +0900 Subject: [PATCH 007/910] Add `vue/no-bare-strings-in-template` rule (#1185) --- docs/rules/README.md | 1 + docs/rules/no-bare-strings-in-template.md | 88 ++++++ lib/index.js | 1 + lib/rules/no-bare-strings-in-template.js | 279 +++++++++++++++++ lib/utils/regexp.js | 14 +- package.json | 2 + .../lib/rules/no-bare-strings-in-template.js | 290 ++++++++++++++++++ 7 files changed, 673 insertions(+), 2 deletions(-) create mode 100644 docs/rules/no-bare-strings-in-template.md create mode 100644 lib/rules/no-bare-strings-in-template.js create mode 100644 tests/lib/rules/no-bare-strings-in-template.js diff --git a/docs/rules/README.md b/docs/rules/README.md index f3e2bfdf6..55b3c4e0d 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -282,6 +282,7 @@ For example: | [vue/html-comment-content-spacing](./html-comment-content-spacing.md) | enforce unified spacing in HTML comments | :wrench: | | [vue/html-comment-indent](./html-comment-indent.md) | enforce consistent indentation in HTML comments | :wrench: | | [vue/match-component-file-name](./match-component-file-name.md) | require component name property to match its file name | | +| [vue/no-bare-strings-in-template](./no-bare-strings-in-template.md) | disallow the use of bare strings in ` `, - options: [{ ignoreWhenEmpty: false, ignoreWhenNoAttributes: false }], output: ` `, + options: [{ ignoreWhenEmpty: false, ignoreWhenNoAttributes: false }], errors: [ 'Expected 1 line break after opening tag (`
`), but no line breaks found.' ] @@ -473,13 +473,13 @@ singleline element
`, - options: [{ ignoreWhenEmpty: false, ignoreWhenNoAttributes: false }], output: ` `, + options: [{ ignoreWhenEmpty: false, ignoreWhenNoAttributes: false }], errors: [ 'Expected 1 line break after opening tag (`
`), but no line breaks found.' ] diff --git a/tests/lib/rules/space-in-parens.js b/tests/lib/rules/space-in-parens.js index 8791652ed..ef6fe628e 100644 --- a/tests/lib/rules/space-in-parens.js +++ b/tests/lib/rules/space-in-parens.js @@ -94,13 +94,13 @@ tester.run('space-in-parens', rule, { @click="foo(arg)" /> `, - options: ['always'], output: ` `, + options: ['always'], errors: [ errorMessage({ messageId: 'missingOpeningSpace', @@ -143,13 +143,13 @@ tester.run('space-in-parens', rule, { :value="(1 + 2) + 3" > `, - options: ['always'], output: ` `, + options: ['always'], errors: [ errorMessage({ messageId: 'missingOpeningSpace', @@ -192,13 +192,13 @@ tester.run('space-in-parens', rule, { :[(1+2)]="(1 + 2) + 3" > `, - options: ['always'], output: ` `, + options: ['always'], errors: [ errorMessage({ messageId: 'missingOpeningSpace', diff --git a/tests/lib/rules/space-unary-ops.js b/tests/lib/rules/space-unary-ops.js index 6b3e7c25f..63539a5a7 100644 --- a/tests/lib/rules/space-unary-ops.js +++ b/tests/lib/rules/space-unary-ops.js @@ -50,8 +50,8 @@ tester.run('space-unary-ops', rule, { }, { code: '', - options: [{ nonwords: true }], output: '', + options: [{ nonwords: true }], errors: ["Unary operator '!' must be followed by whitespace."] }, diff --git a/tests/lib/rules/template-curly-spacing.js b/tests/lib/rules/template-curly-spacing.js index 6c4376969..0fe530511 100644 --- a/tests/lib/rules/template-curly-spacing.js +++ b/tests/lib/rules/template-curly-spacing.js @@ -41,14 +41,13 @@ tester.run('template-curly-spacing', rule, { }, // CSS vars injection - { - code: ` + ` ` - } + + ` ], invalid: [ { @@ -79,12 +78,12 @@ tester.run('template-curly-spacing', rule, {
`, - options: ['always'], output: ` `, + options: ['always'], errors: [ { message: "Expected space(s) after '${'.", diff --git a/tests/lib/rules/this-in-template.js b/tests/lib/rules/this-in-template.js index 904aa00c4..5686bf137 100644 --- a/tests/lib/rules/this-in-template.js +++ b/tests/lib/rules/this-in-template.js @@ -248,14 +248,14 @@ ruleTester.run('this-in-template', rule, { { code: ``, output: ``, - errors: ["Unexpected usage of 'this'."], - options: ['never'] + options: ['never'], + errors: ["Unexpected usage of 'this'."] }, { code: ``, output: ``, - errors: ["Unexpected usage of 'this'."], - options: ['never'] + options: ['never'], + errors: ["Unexpected usage of 'this'."] } ] }) diff --git a/tests/lib/rules/use-v-on-exact.js b/tests/lib/rules/use-v-on-exact.js index 73ac2408b..929fd746a 100644 --- a/tests/lib/rules/use-v-on-exact.js +++ b/tests/lib/rules/use-v-on-exact.js @@ -15,92 +15,48 @@ const ruleTester = new RuleTester({ ruleTester.run('use-v-on-exact', rule, { valid: [ - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: ` + ` ], invalid: [ diff --git a/tests/lib/rules/v-bind-style.js b/tests/lib/rules/v-bind-style.js index 2fd1ce7c2..0cb67f62f 100644 --- a/tests/lib/rules/v-bind-style.js +++ b/tests/lib/rules/v-bind-style.js @@ -67,44 +67,44 @@ tester.run('v-bind-style', rule, { }, { filename: 'test.vue', - options: ['shorthand'], code: '', output: '', + options: ['shorthand'], errors: ["Unexpected 'v-bind' before ':'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-bind' before ':'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-bind:' instead of '.'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-bind:' instead of '.'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-bind:' instead of '.'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-bind:' instead of '.'."] } ] diff --git a/tests/lib/rules/v-for-delimiter-style.js b/tests/lib/rules/v-for-delimiter-style.js index b1500af50..23665a32c 100644 --- a/tests/lib/rules/v-for-delimiter-style.js +++ b/tests/lib/rules/v-for-delimiter-style.js @@ -94,9 +94,9 @@ tester.run('v-for-delimiter-style', rule, { }, { filename: 'test.vue', - options: ['in'], code: '', output: '', + options: ['in'], errors: [ { message: "Expected 'in' instead of 'of' in 'v-for'.", @@ -106,9 +106,9 @@ tester.run('v-for-delimiter-style', rule, { }, { filename: 'test.vue', - options: ['of'], code: '', output: '', + options: ['of'], errors: [ { message: "Expected 'of' instead of 'in' in 'v-for'.", diff --git a/tests/lib/rules/v-on-event-hyphenation.js b/tests/lib/rules/v-on-event-hyphenation.js index 087e5d00a..beaa5e3fa 100644 --- a/tests/lib/rules/v-on-event-hyphenation.js +++ b/tests/lib/rules/v-on-event-hyphenation.js @@ -73,12 +73,12 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['always', { autofix: true }], output: ` `, + options: ['always', { autofix: true }], errors: [ { message: "v-on event '@customEvent' must be hyphenated.", @@ -95,12 +95,12 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['never', { autofix: true }], output: ` `, + options: ['never', { autofix: true }], errors: ["v-on event 'v-on:custom-event' can't be hyphenated."] }, { @@ -110,13 +110,13 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['always', { autofix: true }], output: ` `, + options: ['always', { autofix: true }], errors: ["v-on event '@update:modelValue' must be hyphenated."] }, { @@ -126,13 +126,13 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['never', { autofix: true }], output: ` `, + options: ['never', { autofix: true }], errors: ["v-on event '@update:model-value' can't be hyphenated."] }, { @@ -144,7 +144,6 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['always', { autofix: true }], output: ` `, + options: ['always', { autofix: true }], errors: [ "v-on event '@upDate:modelValue' must be hyphenated.", "v-on event '@up-date:modelValue' must be hyphenated.", @@ -168,7 +168,6 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['never', { autofix: true }], output: ` `, + options: ['never', { autofix: true }], errors: [ "v-on event '@up-date:modelValue' can't be hyphenated.", "v-on event '@upDate:model-value' can't be hyphenated.", diff --git a/tests/lib/rules/v-on-function-call.js b/tests/lib/rules/v-on-function-call.js index d8486a448..d06bbe231 100644 --- a/tests/lib/rules/v-on-function-call.js +++ b/tests/lib/rules/v-on-function-call.js @@ -161,37 +161,37 @@ tester.run('v-on-function-call', rule, { filename: 'test.vue', code: '', output: null, + options: ['always'], errors: [ "Method calls inside of 'v-on' directives must have parentheses." - ], - options: ['always'] + ] }, { filename: 'test.vue', code: '', output: ``, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', code: '', output: ``, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', code: '', output: null, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -204,13 +204,13 @@ tester.run('v-on-function-call', rule, { ">
`, output: null, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses.", "Method calls without arguments inside of 'v-on' directives must not have parentheses.", "Method calls without arguments inside of 'v-on' directives must not have parentheses.", "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -222,10 +222,10 @@ tester.run('v-on-function-call', rule, { `, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -237,10 +237,10 @@ tester.run('v-on-function-call', rule, { `, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -254,11 +254,11 @@ tester.run('v-on-function-call', rule, {
`, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses.", "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -270,10 +270,10 @@ tester.run('v-on-function-call', rule, { `, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -285,19 +285,19 @@ tester.run('v-on-function-call', rule, { `, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', code: '', output: '', + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -319,10 +319,10 @@ tester.run('v-on-function-call', rule, { } } `, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -344,10 +344,10 @@ tester.run('v-on-function-call', rule, { } } `, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] } ] }) diff --git a/tests/lib/rules/v-on-handler-style.js b/tests/lib/rules/v-on-handler-style.js index e3afd05d5..314bbf8c8 100644 --- a/tests/lib/rules/v-on-handler-style.js +++ b/tests/lib/rules/v-on-handler-style.js @@ -78,12 +78,12 @@ tester.run('v-on-handler-style', rule, {