Skip to content

Commit 9ddf3e5

Browse files
waynzhFloEdelmann
andauthored
feat: add ignoreTags option (#2609)
Co-authored-by: Flo Edelmann <[email protected]>
1 parent bea53c0 commit 9ddf3e5

6 files changed

+224
-11
lines changed

docs/rules/attribute-hyphenation.md

+22-4
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,19 @@ This rule enforces using hyphenated attribute names on custom components in Vue
3636
```json
3737
{
3838
"vue/attribute-hyphenation": ["error", "always" | "never", {
39-
"ignore": []
39+
"ignore": [],
40+
"ignoreTags": []
4041
}]
4142
}
4243
```
4344

4445
Default casing is set to `always`. By default the following attributes are ignored: `data-`, `aria-`, `slot-scope`,
4546
and all the [SVG attributes](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute) with either an upper case letter or an hyphen.
4647

47-
- `"always"` (default) ... Use hyphenated name.
48-
- `"never"` ... Don't use hyphenated name except the ones that are ignored.
49-
- `"ignore"` ... Array of ignored names
48+
- `"always"` (default) ... Use hyphenated attribute name.
49+
- `"never"` ... Don't use hyphenated attribute name.
50+
- `"ignore"` ... Array of attribute names that don't need to follow the specified casing.
51+
- `"ignoreTags"` ... Array of tag names whose attributes don't need to follow the specified casing.
5052

5153
### `"always"`
5254

@@ -109,6 +111,22 @@ Don't use hyphenated name but allow custom attributes
109111

110112
</eslint-code-block>
111113

114+
### `"never", { "ignoreTags": ["/^custom-/"] }`
115+
116+
<eslint-code-block fix :rules="{'vue/attribute-hyphenation': ['error', 'never', { ignoreTags: ['/^custom-/'] }]}">
117+
118+
```vue
119+
<template>
120+
<!-- ✓ GOOD -->
121+
<custom-component my-prop="prop" />
122+
123+
<!-- ✗ BAD -->
124+
<my-component my-prop="prop" />
125+
</template>
126+
```
127+
128+
</eslint-code-block>
129+
112130
## :couple: Related Rules
113131

114132
- [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md)

docs/rules/v-on-event-hyphenation.md

+22-4
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,16 @@ This rule enforces using hyphenated v-on event names on custom components in Vue
3939
{
4040
"vue/v-on-event-hyphenation": ["error", "always" | "never", {
4141
"autofix": false,
42-
"ignore": []
42+
"ignore": [],
43+
"ignoreTags": []
4344
}]
4445
}
4546
```
4647

47-
- `"always"` (default) ... Use hyphenated name.
48-
- `"never"` ... Don't use hyphenated name.
49-
- `"ignore"` ... Array of ignored names
48+
- `"always"` (default) ... Use hyphenated event name.
49+
- `"never"` ... Don't use hyphenated event name.
50+
- `"ignore"` ... Array of event names that don't need to follow the specified casing.
51+
- `"ignoreTags"` ... Array of tag names whose events don't need to follow the specified casing.
5052
- `"autofix"` ... If `true`, enable autofix. If you are using Vue 2, we recommend that you do not use it due to its side effects.
5153

5254
### `"always"`
@@ -104,6 +106,22 @@ Don't use hyphenated name but allow custom event names
104106

105107
</eslint-code-block>
106108

109+
### `"never", { "ignoreTags": ["/^custom-/"] }`
110+
111+
<eslint-code-block fix :rules="{'vue/v-on-event-hyphenation': ['error', 'never', { ignoreTags: ['/^custom-/'], autofix: true }]}">
112+
113+
```vue
114+
<template>
115+
<!-- ✓ GOOD -->
116+
<custom-component v-on:my-event="handleEvent" />
117+
118+
<!-- ✗ BAD -->
119+
<my-component v-on:my-event="handleEvent" />
120+
</template>
121+
```
122+
123+
</eslint-code-block>
124+
107125
## :couple: Related Rules
108126

109127
- [vue/custom-event-name-casing](./custom-event-name-casing.md)

lib/rules/attribute-hyphenation.js

+20-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
const utils = require('../utils')
88
const casing = require('../utils/casing')
9+
const { toRegExp } = require('../utils/regexp')
910
const svgAttributes = require('../utils/svg-attributes-weird-case.json')
1011

1112
/**
@@ -56,6 +57,12 @@ module.exports = {
5657
},
5758
uniqueItems: true,
5859
additionalItems: false
60+
},
61+
ignoreTags: {
62+
type: 'array',
63+
items: { type: 'string' },
64+
uniqueItems: true,
65+
additionalItems: false
5966
}
6067
},
6168
additionalProperties: false
@@ -72,6 +79,11 @@ module.exports = {
7279
const option = context.options[0]
7380
const optionsPayload = context.options[1]
7481
const useHyphenated = option !== 'never'
82+
/** @type {RegExp[]} */
83+
const ignoredTagsRegexps = (
84+
(optionsPayload && optionsPayload.ignoreTags) ||
85+
[]
86+
).map(toRegExp)
7587
const ignoredAttributes = ['data-', 'aria-', 'slot-scope', ...svgAttributes]
7688

7789
if (optionsPayload && optionsPayload.ignore) {
@@ -130,11 +142,17 @@ module.exports = {
130142
return useHyphenated ? value.toLowerCase() === value : !/-/.test(value)
131143
}
132144

145+
/** @param {string} name */
146+
function isIgnoredTagName(name) {
147+
return ignoredTagsRegexps.some((re) => re.test(name))
148+
}
149+
133150
return utils.defineTemplateBodyVisitor(context, {
134151
VAttribute(node) {
152+
const element = node.parent.parent
135153
if (
136-
!utils.isCustomComponent(node.parent.parent) &&
137-
node.parent.parent.name !== 'slot'
154+
(!utils.isCustomComponent(element) && element.name !== 'slot') ||
155+
isIgnoredTagName(element.rawName)
138156
)
139157
return
140158

lib/rules/v-on-event-hyphenation.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const utils = require('../utils')
44
const casing = require('../utils/casing')
5+
const { toRegExp } = require('../utils/regexp')
56

67
module.exports = {
78
meta: {
@@ -35,6 +36,12 @@ module.exports = {
3536
},
3637
uniqueItems: true,
3738
additionalItems: false
39+
},
40+
ignoreTags: {
41+
type: 'array',
42+
items: { type: 'string' },
43+
uniqueItems: true,
44+
additionalItems: false
3845
}
3946
},
4047
additionalProperties: false
@@ -56,6 +63,11 @@ module.exports = {
5663
const useHyphenated = option !== 'never'
5764
/** @type {string[]} */
5865
const ignoredAttributes = (optionsPayload && optionsPayload.ignore) || []
66+
/** @type {RegExp[]} */
67+
const ignoredTagsRegexps = (
68+
(optionsPayload && optionsPayload.ignoreTags) ||
69+
[]
70+
).map(toRegExp)
5971
const autofix = Boolean(optionsPayload && optionsPayload.autofix)
6072

6173
const caseConverter = casing.getConverter(
@@ -99,9 +111,20 @@ module.exports = {
99111
return useHyphenated ? value.toLowerCase() === value : !/-/.test(value)
100112
}
101113

114+
/** @param {string} name */
115+
function isIgnoredTagName(name) {
116+
return ignoredTagsRegexps.some((re) => re.test(name))
117+
}
118+
102119
return utils.defineTemplateBodyVisitor(context, {
103120
"VAttribute[directive=true][key.name.name='on']"(node) {
104-
if (!utils.isCustomComponent(node.parent.parent)) return
121+
const element = node.parent.parent
122+
if (
123+
!utils.isCustomComponent(element) ||
124+
isIgnoredTagName(element.rawName)
125+
) {
126+
return
127+
}
105128
if (!node.key.argument || node.key.argument.type !== 'VIdentifier') {
106129
return
107130
}

tests/lib/rules/attribute-hyphenation.js

+66
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,26 @@ ruleTester.run('attribute-hyphenation', rule, {
8585
filename: 'test.vue',
8686
code: '<template><div><custom :myName.sync="prop"></custom></div></template>',
8787
options: ['never']
88+
},
89+
{
90+
filename: 'test.vue',
91+
code: `
92+
<template>
93+
<VueComponent my-prop></VueComponent>
94+
<custom-component my-prop></custom-component>
95+
</template>
96+
`,
97+
options: ['never', { ignoreTags: ['VueComponent', '/^custom-/'] }]
98+
},
99+
{
100+
filename: 'test.vue',
101+
code: `
102+
<template>
103+
<VueComponent myProp="prop"></VueComponent>
104+
<custom-component myProp="prop"></custom-component>
105+
</template>
106+
`,
107+
options: ['always', { ignoreTags: ['VueComponent', '/^custom-/'] }]
88108
}
89109
],
90110

@@ -450,6 +470,52 @@ ruleTester.run('attribute-hyphenation', rule, {
450470
line: 1
451471
}
452472
]
473+
},
474+
{
475+
code: `
476+
<template>
477+
<custom my-prop/>
478+
<CustomComponent my-prop/>
479+
</template>
480+
`,
481+
output: `
482+
<template>
483+
<custom myProp/>
484+
<CustomComponent my-prop/>
485+
</template>
486+
`,
487+
options: ['never', { ignoreTags: ['CustomComponent'] }],
488+
errors: [
489+
{
490+
message: "Attribute 'my-prop' can't be hyphenated.",
491+
type: 'VIdentifier',
492+
line: 3,
493+
column: 17
494+
}
495+
]
496+
},
497+
{
498+
code: `
499+
<template>
500+
<custom myProp/>
501+
<CustomComponent myProp/>
502+
</template>
503+
`,
504+
output: `
505+
<template>
506+
<custom my-prop/>
507+
<CustomComponent myProp/>
508+
</template>
509+
`,
510+
options: ['always', { ignoreTags: ['CustomComponent'] }],
511+
errors: [
512+
{
513+
message: "Attribute 'myProp' must be hyphenated.",
514+
type: 'VIdentifier',
515+
line: 3,
516+
column: 17
517+
}
518+
]
453519
}
454520
]
455521
})

tests/lib/rules/v-on-event-hyphenation.js

+70
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,32 @@ tester.run('v-on-event-hyphenation', rule, {
4444
</template>
4545
`,
4646
options: ['never', { ignore: ['custom'] }]
47+
},
48+
{
49+
code: `
50+
<template>
51+
<VueComponent v-on:custom-event="events"/>
52+
</template>
53+
`,
54+
options: ['never', { ignore: ['custom-event'] }]
55+
},
56+
{
57+
code: `
58+
<template>
59+
<VueComponent v-on:custom-event="events"/>
60+
<custom-component v-on:custom-event="events"/>
61+
</template>
62+
`,
63+
options: ['never', { ignoreTags: ['/^Vue/', 'custom-component'] }]
64+
},
65+
{
66+
code: `
67+
<template>
68+
<VueComponent v-on:customEvent="events"/>
69+
<custom-component v-on:customEvent="events"/>
70+
</template>
71+
`,
72+
options: ['always', { ignoreTags: ['/^Vue/', 'custom-component'] }]
4773
}
4874
],
4975
invalid: [
@@ -179,6 +205,50 @@ tester.run('v-on-event-hyphenation', rule, {
179205
"v-on event '@upDate:model-value' can't be hyphenated.",
180206
"v-on event '@up-date:model-value' can't be hyphenated."
181207
]
208+
},
209+
{
210+
code: `
211+
<template>
212+
<VueComponent v-on:custom-event="events"/>
213+
<CustomComponent v-on:custom-event="events"/>
214+
</template>
215+
`,
216+
output: `
217+
<template>
218+
<VueComponent v-on:customEvent="events"/>
219+
<CustomComponent v-on:custom-event="events"/>
220+
</template>
221+
`,
222+
options: ['never', { autofix: true, ignoreTags: ['CustomComponent'] }],
223+
errors: [
224+
{
225+
message: "v-on event 'v-on:custom-event' can't be hyphenated.",
226+
line: 3,
227+
column: 23
228+
}
229+
]
230+
},
231+
{
232+
code: `
233+
<template>
234+
<VueComponent v-on:customEvent="events"/>
235+
<CustomComponent v-on:customEvent="events"/>
236+
</template>
237+
`,
238+
output: `
239+
<template>
240+
<VueComponent v-on:custom-event="events"/>
241+
<CustomComponent v-on:customEvent="events"/>
242+
</template>
243+
`,
244+
options: ['always', { autofix: true, ignoreTags: ['CustomComponent'] }],
245+
errors: [
246+
{
247+
message: "v-on event 'v-on:customEvent' must be hyphenated.",
248+
line: 3,
249+
column: 23
250+
}
251+
]
182252
}
183253
]
184254
})

0 commit comments

Comments
 (0)