From 9fd6139acfafa73945bbb0777ebcc7d7bf35e2ea Mon Sep 17 00:00:00 2001 From: ota-meshi Date: Fri, 25 Dec 2020 21:21:09 +0900 Subject: [PATCH 1/3] Add vue/v-on-event-hyphenation rule --- docs/rules/README.md | 1 + docs/rules/attribute-hyphenation.md | 3 + docs/rules/v-on-event-hyphenation.md | 108 +++++++++++++++++++++ lib/index.js | 1 + lib/rules/attribute-hyphenation.js | 2 +- lib/rules/v-on-event-hyphenation.js | 113 ++++++++++++++++++++++ tests/lib/rules/v-on-event-hyphenation.js | 107 ++++++++++++++++++++ 7 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 docs/rules/v-on-event-hyphenation.md create mode 100644 lib/rules/v-on-event-hyphenation.js create mode 100644 tests/lib/rules/v-on-event-hyphenation.js diff --git a/docs/rules/README.md b/docs/rules/README.md index 68677d252..805dbd4ec 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -320,6 +320,7 @@ For example: | [vue/sort-keys](./sort-keys.md) | enforce sort-keys in a manner that is compatible with order-in-components | | | [vue/static-class-names-order](./static-class-names-order.md) | enforce static class names order | :wrench: | | [vue/v-for-delimiter-style](./v-for-delimiter-style.md) | enforce `v-for` directive's delimiter style | :wrench: | +| [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md) | enforce v-on event naming style on custom components in template | :wrench: | | [vue/v-on-function-call](./v-on-function-call.md) | enforce or forbid parentheses after method calls without arguments in `v-on` directives | :wrench: | ### Extension Rules diff --git a/docs/rules/attribute-hyphenation.md b/docs/rules/attribute-hyphenation.md index fc7bba756..7dd2feb21 100644 --- a/docs/rules/attribute-hyphenation.md +++ b/docs/rules/attribute-hyphenation.md @@ -47,6 +47,7 @@ Default casing is set to `always` with `['data-', 'aria-', 'slot-scope']` set to - `"ignore"` ... Array of ignored names ### `"always"` + It errors on upper case letters. @@ -64,6 +65,7 @@ It errors on upper case letters. ### `"never"` + It errors on hyphens except `data-`, `aria-` and `slot-scope`. @@ -84,6 +86,7 @@ It errors on hyphens except `data-`, `aria-` and `slot-scope`. ### `"never", { "ignore": ["custom-prop"] }` + Don't use hyphenated name but allow custom attributes diff --git a/docs/rules/v-on-event-hyphenation.md b/docs/rules/v-on-event-hyphenation.md new file mode 100644 index 000000000..3b94b4529 --- /dev/null +++ b/docs/rules/v-on-event-hyphenation.md @@ -0,0 +1,108 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/v-on-event-hyphenation +description: enforce v-on event naming style on custom components in template +--- +# vue/v-on-event-hyphenation + +> enforce v-on event naming style on custom components in template + +- :exclamation: ***This rule has not been released yet.*** +- :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 enforces using hyphenated v-on event names on custom components in Vue templates. + + + +```vue + +``` + + + +## :wrench: Options + +```json +{ + "vue/v-on-event-hyphenation": ["error", "always" | "never", { + "autofix": false, + "ignore": [] + }] +} +``` + +- `"always"` (default) ... Use hyphenated name. +- `"never"` ... Don't use hyphenated name. +- `"ignore"` ... Array of ignored names +- `"autofix"` ... If `true`, enable autofix. If you are using Vue 2, we recommend that you do not use it due to its side effects. + +### `"always"` + +It errors on upper case letters. + + + +```vue + +``` + + + +### `"never"` + +It errors on hyphens. + + + +```vue + +``` + + + +### `"never", { "ignore": ["custom-event"] }` + +Don't use hyphenated name but allow custom event names + + + +```vue + +``` + + + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-on-event-hyphenation.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/v-on-event-hyphenation.js) diff --git a/lib/index.js b/lib/index.js index 95ffd1dd6..35a781e65 100644 --- a/lib/index.js +++ b/lib/index.js @@ -156,6 +156,7 @@ module.exports = { 'use-v-on-exact': require('./rules/use-v-on-exact'), 'v-bind-style': require('./rules/v-bind-style'), 'v-for-delimiter-style': require('./rules/v-for-delimiter-style'), + 'v-on-event-hyphenation': require('./rules/v-on-event-hyphenation'), 'v-on-function-call': require('./rules/v-on-function-call'), 'v-on-style': require('./rules/v-on-style'), 'v-slot-style': require('./rules/v-slot-style'), diff --git a/lib/rules/attribute-hyphenation.js b/lib/rules/attribute-hyphenation.js index 1fd7b5e9b..cba9dc241 100644 --- a/lib/rules/attribute-hyphenation.js +++ b/lib/rules/attribute-hyphenation.js @@ -87,7 +87,7 @@ module.exports = { */ function isIgnoredAttribute(value) { const isIgnored = ignoredAttributes.some((attr) => { - return value.indexOf(attr) !== -1 + return value.includes(attr) }) if (isIgnored) { diff --git a/lib/rules/v-on-event-hyphenation.js b/lib/rules/v-on-event-hyphenation.js new file mode 100644 index 000000000..60823e013 --- /dev/null +++ b/lib/rules/v-on-event-hyphenation.js @@ -0,0 +1,113 @@ +'use strict' + +const utils = require('../utils') +const casing = require('../utils/casing') + +module.exports = { + meta: { + docs: { + description: + 'enforce v-on event naming style on custom components in template', + // TODO Change with major version. + // categories: ['vue3-strongly-recommended'], + categories: undefined, + url: 'https://eslint.vuejs.org/rules/v-on-event-hyphenation.html' + }, + fixable: 'code', + schema: [ + { + enum: ['always', 'never'] + }, + { + type: 'object', + properties: { + autofix: { type: 'boolean' }, + ignore: { + type: 'array', + items: { + allOf: [ + { type: 'string' }, + { not: { type: 'string', pattern: ':exit$' } }, + { not: { type: 'string', pattern: '^\\s*$' } } + ] + }, + uniqueItems: true, + additionalItems: false + } + }, + additionalProperties: false + } + ], + type: 'suggestion' + }, + + /** @param {RuleContext} context */ + create(context) { + const sourceCode = context.getSourceCode() + const option = context.options[0] + const optionsPayload = context.options[1] + const useHyphenated = option !== 'never' + /** @type {string[]} */ + const ignoredAttributes = (optionsPayload && optionsPayload.ignore) || [] + const autofix = Boolean(optionsPayload && optionsPayload.autofix) + + const caseConverter = casing.getExactConverter( + useHyphenated ? 'kebab-case' : 'camelCase' + ) + + /** + * @param {VDirective} node + * @param {string} name + */ + function reportIssue(node, name) { + const text = sourceCode.getText(node.key) + + context.report({ + node: node.key, + loc: node.loc, + message: useHyphenated + ? "v-on event '{{text}}' must be hyphenated." + : "v-on event '{{text}}' can't be hyphenated.", + data: { + text + }, + fix: autofix + ? (fixer) => + fixer.replaceText( + node.key, + text.replace(name, caseConverter(name)) + ) + : null + }) + } + + /** + * @param {string} value + */ + function isIgnoredAttribute(value) { + const isIgnored = ignoredAttributes.some((attr) => { + return value.includes(attr) + }) + + if (isIgnored) { + return true + } + + return useHyphenated ? value.toLowerCase() === value : !/-/.test(value) + } + + return utils.defineTemplateBodyVisitor(context, { + "VAttribute[directive=true][key.name.name='on']"(node) { + if (!utils.isCustomComponent(node.parent.parent)) return + + const name = + node.key.argument && + node.key.argument.type === 'VIdentifier' && + node.key.argument.rawName + if (!name || isIgnoredAttribute(name)) return + + reportIssue(node, name) + } + }) + } +} diff --git a/tests/lib/rules/v-on-event-hyphenation.js b/tests/lib/rules/v-on-event-hyphenation.js new file mode 100644 index 000000000..c77449971 --- /dev/null +++ b/tests/lib/rules/v-on-event-hyphenation.js @@ -0,0 +1,107 @@ +'use strict' + +const RuleTester = require('eslint').RuleTester +const rule = require('../../../lib/rules/v-on-event-hyphenation.js') + +const tester = new RuleTester({ + parser: require.resolve('vue-eslint-parser'), + parserOptions: { + ecmaVersion: 2019 + } +}) + +tester.run('v-on-event-hyphenation', rule, { + valid: [ + ` + + `, + ` + + `, + ` + + `, + ` + + `, + { + code: ` + + `, + options: ['never'] + }, + { + code: ` + + `, + options: ['never', { ignore: ['custom'] }] + } + ], + invalid: [ + { + code: ` + + `, + output: null, + errors: [ + { + message: "v-on event '@customEvent' must be hyphenated.", + line: 3, + column: 25, + endLine: 3, + endColumn: 47 + } + ] + }, + { + code: ` + + `, + options: ['always', { autofix: true }], + output: ` + + `, + errors: [ + { + message: "v-on event '@customEvent' must be hyphenated.", + line: 3, + column: 25, + endLine: 3, + endColumn: 47 + } + ] + }, + { + code: ` + + `, + options: ['never', { autofix: true }], + output: ` + + `, + errors: ["v-on event 'v-on:custom-event' can't be hyphenated."] + } + ] +}) From fd03bd3d184f552895a82cd1063d5baa13d9e1b8 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Sat, 26 Dec 2020 17:32:34 +0900 Subject: [PATCH 2/3] update docs --- docs/rules/attribute-hyphenation.md | 4 ++++ docs/rules/v-on-event-hyphenation.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/rules/attribute-hyphenation.md b/docs/rules/attribute-hyphenation.md index 7dd2feb21..022475c1b 100644 --- a/docs/rules/attribute-hyphenation.md +++ b/docs/rules/attribute-hyphenation.md @@ -107,6 +107,10 @@ Don't use hyphenated name but allow custom attributes +## :couple: Related Rules + +- [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md) + ## :rocket: Version This rule was introduced in eslint-plugin-vue v3.9.0 diff --git a/docs/rules/v-on-event-hyphenation.md b/docs/rules/v-on-event-hyphenation.md index 3b94b4529..7571b4431 100644 --- a/docs/rules/v-on-event-hyphenation.md +++ b/docs/rules/v-on-event-hyphenation.md @@ -102,6 +102,10 @@ Don't use hyphenated name but allow custom event names +## :couple: Related Rules + +- [vue/attribute-hyphenation](./attribute-hyphenation.md) + ## :mag: Implementation - [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-on-event-hyphenation.js) From 6d5b9e30d03dabcc13b71fea818da31d164ac299 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Sat, 26 Dec 2020 17:52:10 +0900 Subject: [PATCH 3/3] Update doc --- docs/rules/attribute-hyphenation.md | 1 + docs/rules/custom-event-name-casing.md | 9 +++++++-- docs/rules/prop-name-casing.md | 5 +++++ docs/rules/v-on-event-hyphenation.md | 7 +++++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/rules/attribute-hyphenation.md b/docs/rules/attribute-hyphenation.md index 022475c1b..cc6391831 100644 --- a/docs/rules/attribute-hyphenation.md +++ b/docs/rules/attribute-hyphenation.md @@ -110,6 +110,7 @@ Don't use hyphenated name but allow custom attributes ## :couple: Related Rules - [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md) +- [vue/prop-name-casing](./prop-name-casing.md) ## :rocket: Version diff --git a/docs/rules/custom-event-name-casing.md b/docs/rules/custom-event-name-casing.md index 16a46dfda..ae08fc25d 100644 --- a/docs/rules/custom-event-name-casing.md +++ b/docs/rules/custom-event-name-casing.md @@ -23,9 +23,9 @@ Vue 2 recommends using kebab-case for custom event names. See [Guide (for v2) - Custom Events] for more details. -Vue 3 recommends using camelCase for custom event names. +In Vue 3, using either camelCase or kebab-case for your custom event name does not limit its use in v-on. However, following JavaScript conventions, camelCase is more natural. -See [vuejs/docs-next#656](https://github.com/vuejs/docs-next/issues/656) for more details. +See [Guide - Custom Events] for more details. This rule enforces kebab-case by default. @@ -171,6 +171,11 @@ export default { [Guide - Custom Events]: https://v3.vuejs.org/guide/component-custom-events.html [Guide (for v2) - Custom Events]: https://vuejs.org/v2/guide/components-custom-events.html +## :couple: Related Rules + +- [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md) +- [vue/prop-name-casing](./prop-name-casing.md) + ## :rocket: Version This rule was introduced in eslint-plugin-vue v7.0.0 diff --git a/docs/rules/prop-name-casing.md b/docs/rules/prop-name-casing.md index 3cfee5b06..81f19518f 100644 --- a/docs/rules/prop-name-casing.md +++ b/docs/rules/prop-name-casing.md @@ -70,6 +70,11 @@ export default { - [Style guide - Prop name casing](https://v3.vuejs.org/style-guide/#prop-name-casing-strongly-recommended) +## :couple: Related Rules + +- [vue/attribute-hyphenation](./attribute-hyphenation.md) +- [vue/custom-event-name-casing](./custom-event-name-casing.md) + ## :rocket: Version This rule was introduced in eslint-plugin-vue v4.3.0 diff --git a/docs/rules/v-on-event-hyphenation.md b/docs/rules/v-on-event-hyphenation.md index 7571b4431..92caed693 100644 --- a/docs/rules/v-on-event-hyphenation.md +++ b/docs/rules/v-on-event-hyphenation.md @@ -102,8 +102,15 @@ Don't use hyphenated name but allow custom event names +## :books: Further Reading + +- [Guide - Custom Events] + +[Guide - Custom Events]: https://v3.vuejs.org/guide/component-custom-events.html + ## :couple: Related Rules +- [vue/custom-event-name-casing](./custom-event-name-casing.md) - [vue/attribute-hyphenation](./attribute-hyphenation.md) ## :mag: Implementation