From 9ddf3e5a6d8d38ee3e7c07ee692740218bf1be8e Mon Sep 17 00:00:00 2001 From: Wayne Zhang Date: Wed, 27 Nov 2024 14:06:14 +0800 Subject: [PATCH] feat: add `ignoreTags` option (#2609) Co-authored-by: Flo Edelmann --- docs/rules/attribute-hyphenation.md | 26 +++++++-- docs/rules/v-on-event-hyphenation.md | 26 +++++++-- lib/rules/attribute-hyphenation.js | 22 ++++++- lib/rules/v-on-event-hyphenation.js | 25 +++++++- tests/lib/rules/attribute-hyphenation.js | 66 +++++++++++++++++++++ tests/lib/rules/v-on-event-hyphenation.js | 70 +++++++++++++++++++++++ 6 files changed, 224 insertions(+), 11 deletions(-) diff --git a/docs/rules/attribute-hyphenation.md b/docs/rules/attribute-hyphenation.md index d5fba2e31..89442fceb 100644 --- a/docs/rules/attribute-hyphenation.md +++ b/docs/rules/attribute-hyphenation.md @@ -36,7 +36,8 @@ This rule enforces using hyphenated attribute names on custom components in Vue ```json { "vue/attribute-hyphenation": ["error", "always" | "never", { - "ignore": [] + "ignore": [], + "ignoreTags": [] }] } ``` @@ -44,9 +45,10 @@ This rule enforces using hyphenated attribute names on custom components in Vue Default casing is set to `always`. By default the following attributes are ignored: `data-`, `aria-`, `slot-scope`, and all the [SVG attributes](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute) with either an upper case letter or an hyphen. -- `"always"` (default) ... Use hyphenated name. -- `"never"` ... Don't use hyphenated name except the ones that are ignored. -- `"ignore"` ... Array of ignored names +- `"always"` (default) ... Use hyphenated attribute name. +- `"never"` ... Don't use hyphenated attribute name. +- `"ignore"` ... Array of attribute names that don't need to follow the specified casing. +- `"ignoreTags"` ... Array of tag names whose attributes don't need to follow the specified casing. ### `"always"` @@ -109,6 +111,22 @@ Don't use hyphenated name but allow custom attributes +### `"never", { "ignoreTags": ["/^custom-/"] }` + + + +```vue + +``` + + + ## :couple: Related Rules - [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md) diff --git a/docs/rules/v-on-event-hyphenation.md b/docs/rules/v-on-event-hyphenation.md index 811b37437..493a9dac9 100644 --- a/docs/rules/v-on-event-hyphenation.md +++ b/docs/rules/v-on-event-hyphenation.md @@ -39,14 +39,16 @@ This rule enforces using hyphenated v-on event names on custom components in Vue { "vue/v-on-event-hyphenation": ["error", "always" | "never", { "autofix": false, - "ignore": [] + "ignore": [], + "ignoreTags": [] }] } ``` -- `"always"` (default) ... Use hyphenated name. -- `"never"` ... Don't use hyphenated name. -- `"ignore"` ... Array of ignored names +- `"always"` (default) ... Use hyphenated event name. +- `"never"` ... Don't use hyphenated event name. +- `"ignore"` ... Array of event names that don't need to follow the specified casing. +- `"ignoreTags"` ... Array of tag names whose events don't need to follow the specified casing. - `"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"` @@ -104,6 +106,22 @@ Don't use hyphenated name but allow custom event names +### `"never", { "ignoreTags": ["/^custom-/"] }` + + + +```vue + +``` + + + ## :couple: Related Rules - [vue/custom-event-name-casing](./custom-event-name-casing.md) diff --git a/lib/rules/attribute-hyphenation.js b/lib/rules/attribute-hyphenation.js index 35519d231..65d096cd4 100644 --- a/lib/rules/attribute-hyphenation.js +++ b/lib/rules/attribute-hyphenation.js @@ -6,6 +6,7 @@ const utils = require('../utils') const casing = require('../utils/casing') +const { toRegExp } = require('../utils/regexp') const svgAttributes = require('../utils/svg-attributes-weird-case.json') /** @@ -56,6 +57,12 @@ module.exports = { }, uniqueItems: true, additionalItems: false + }, + ignoreTags: { + type: 'array', + items: { type: 'string' }, + uniqueItems: true, + additionalItems: false } }, additionalProperties: false @@ -72,6 +79,11 @@ module.exports = { const option = context.options[0] const optionsPayload = context.options[1] const useHyphenated = option !== 'never' + /** @type {RegExp[]} */ + const ignoredTagsRegexps = ( + (optionsPayload && optionsPayload.ignoreTags) || + [] + ).map(toRegExp) const ignoredAttributes = ['data-', 'aria-', 'slot-scope', ...svgAttributes] if (optionsPayload && optionsPayload.ignore) { @@ -130,11 +142,17 @@ module.exports = { return useHyphenated ? value.toLowerCase() === value : !/-/.test(value) } + /** @param {string} name */ + function isIgnoredTagName(name) { + return ignoredTagsRegexps.some((re) => re.test(name)) + } + return utils.defineTemplateBodyVisitor(context, { VAttribute(node) { + const element = node.parent.parent if ( - !utils.isCustomComponent(node.parent.parent) && - node.parent.parent.name !== 'slot' + (!utils.isCustomComponent(element) && element.name !== 'slot') || + isIgnoredTagName(element.rawName) ) return diff --git a/lib/rules/v-on-event-hyphenation.js b/lib/rules/v-on-event-hyphenation.js index f99a45fdc..c9fac76e8 100644 --- a/lib/rules/v-on-event-hyphenation.js +++ b/lib/rules/v-on-event-hyphenation.js @@ -2,6 +2,7 @@ const utils = require('../utils') const casing = require('../utils/casing') +const { toRegExp } = require('../utils/regexp') module.exports = { meta: { @@ -35,6 +36,12 @@ module.exports = { }, uniqueItems: true, additionalItems: false + }, + ignoreTags: { + type: 'array', + items: { type: 'string' }, + uniqueItems: true, + additionalItems: false } }, additionalProperties: false @@ -56,6 +63,11 @@ module.exports = { const useHyphenated = option !== 'never' /** @type {string[]} */ const ignoredAttributes = (optionsPayload && optionsPayload.ignore) || [] + /** @type {RegExp[]} */ + const ignoredTagsRegexps = ( + (optionsPayload && optionsPayload.ignoreTags) || + [] + ).map(toRegExp) const autofix = Boolean(optionsPayload && optionsPayload.autofix) const caseConverter = casing.getConverter( @@ -99,9 +111,20 @@ module.exports = { return useHyphenated ? value.toLowerCase() === value : !/-/.test(value) } + /** @param {string} name */ + function isIgnoredTagName(name) { + return ignoredTagsRegexps.some((re) => re.test(name)) + } + return utils.defineTemplateBodyVisitor(context, { "VAttribute[directive=true][key.name.name='on']"(node) { - if (!utils.isCustomComponent(node.parent.parent)) return + const element = node.parent.parent + if ( + !utils.isCustomComponent(element) || + isIgnoredTagName(element.rawName) + ) { + return + } if (!node.key.argument || node.key.argument.type !== 'VIdentifier') { return } diff --git a/tests/lib/rules/attribute-hyphenation.js b/tests/lib/rules/attribute-hyphenation.js index 18d60e19c..738d59ae9 100644 --- a/tests/lib/rules/attribute-hyphenation.js +++ b/tests/lib/rules/attribute-hyphenation.js @@ -85,6 +85,26 @@ ruleTester.run('attribute-hyphenation', rule, { filename: 'test.vue', code: '', options: ['never'] + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['never', { ignoreTags: ['VueComponent', '/^custom-/'] }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['always', { ignoreTags: ['VueComponent', '/^custom-/'] }] } ], @@ -450,6 +470,52 @@ ruleTester.run('attribute-hyphenation', rule, { line: 1 } ] + }, + { + code: ` + + `, + output: ` + + `, + options: ['never', { ignoreTags: ['CustomComponent'] }], + errors: [ + { + message: "Attribute 'my-prop' can't be hyphenated.", + type: 'VIdentifier', + line: 3, + column: 17 + } + ] + }, + { + code: ` + + `, + output: ` + + `, + options: ['always', { ignoreTags: ['CustomComponent'] }], + errors: [ + { + message: "Attribute 'myProp' must be hyphenated.", + type: 'VIdentifier', + line: 3, + column: 17 + } + ] } ] }) diff --git a/tests/lib/rules/v-on-event-hyphenation.js b/tests/lib/rules/v-on-event-hyphenation.js index 54d2ec435..3f58ce1f0 100644 --- a/tests/lib/rules/v-on-event-hyphenation.js +++ b/tests/lib/rules/v-on-event-hyphenation.js @@ -44,6 +44,32 @@ tester.run('v-on-event-hyphenation', rule, { `, options: ['never', { ignore: ['custom'] }] + }, + { + code: ` + + `, + options: ['never', { ignore: ['custom-event'] }] + }, + { + code: ` + + `, + options: ['never', { ignoreTags: ['/^Vue/', 'custom-component'] }] + }, + { + code: ` + + `, + options: ['always', { ignoreTags: ['/^Vue/', 'custom-component'] }] } ], invalid: [ @@ -179,6 +205,50 @@ tester.run('v-on-event-hyphenation', rule, { "v-on event '@upDate:model-value' can't be hyphenated.", "v-on event '@up-date:model-value' can't be hyphenated." ] + }, + { + code: ` + + `, + output: ` + + `, + options: ['never', { autofix: true, ignoreTags: ['CustomComponent'] }], + errors: [ + { + message: "v-on event 'v-on:custom-event' can't be hyphenated.", + line: 3, + column: 23 + } + ] + }, + { + code: ` + + `, + output: ` + + `, + options: ['always', { autofix: true, ignoreTags: ['CustomComponent'] }], + errors: [ + { + message: "v-on event 'v-on:customEvent' must be hyphenated.", + line: 3, + column: 23 + } + ] } ] })