Skip to content
24 changes: 22 additions & 2 deletions docs/rules/attribute-hyphenation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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": [],
"exclude": []
}]
}
```
Expand All @@ -46,7 +47,8 @@ and all the [SVG attributes](https://developer.mozilla.org/en-US/docs/Web/SVG/At

- `"always"` (default) ... Use hyphenated name.
- `"never"` ... Don't use hyphenated name except the ones that are ignored.
- `"ignore"` ... Array of ignored names
- `"ignore"` ... Array of ignored names.
- `"exclude"` ... Array of exclude tag names.

### `"always"`

Expand Down Expand Up @@ -109,6 +111,24 @@ Don't use hyphenated name but allow custom attributes

</eslint-code-block>

### `"never", { "exclude": ["/^custom-/"] }`

Exclude tags from applying this rule.

<eslint-code-block fix :rules="{'vue/attribute-hyphenation': ['error', 'never', { exclude: ['/^custom-/'] }]}">

```vue
<template>
<!-- ✓ GOOD -->
<custom-component custom-prop="prop" />

<!-- ✗ BAD -->
<my-component custom-prop="prop" />
</template>
```

</eslint-code-block>

## :couple: Related Rules

- [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md)
Expand Down
24 changes: 22 additions & 2 deletions docs/rules/v-on-event-hyphenation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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": [],
"exclude": []
}]
}
```

- `"always"` (default) ... Use hyphenated name.
- `"never"` ... Don't use hyphenated name.
- `"ignore"` ... Array of ignored names
- `"ignore"` ... Array of ignored names.
- `"exclude"` ... Array of exclude tag 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"`
Expand Down Expand Up @@ -104,6 +106,24 @@ Don't use hyphenated name but allow custom event names

</eslint-code-block>

### `"never", { "exclude": ["/^custom-/"] }`

Exclude tags from applying this rule.

<eslint-code-block fix :rules="{'vue/v-on-event-hyphenation': ['error', 'never', { exclude: ['/^custom-/'], autofix: true }]}">

```vue
<template>
<!-- ✓ GOOD -->
<custom-component v-on:custom-event="handleEvent" />

<!-- ✗ BAD -->
<my-component v-on:custom-event="handleEvent" />
</template>
```

</eslint-code-block>

## :couple: Related Rules

- [vue/custom-event-name-casing](./custom-event-name-casing.md)
Expand Down
23 changes: 21 additions & 2 deletions lib/rules/attribute-hyphenation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')

/**
Expand Down Expand Up @@ -56,6 +57,12 @@ module.exports = {
},
uniqueItems: true,
additionalItems: false
},
exclude: {
type: 'array',
items: { type: 'string' },
uniqueItems: true,
additionalItems: false
}
},
additionalProperties: false
Expand All @@ -72,6 +79,10 @@ module.exports = {
const option = context.options[0]
const optionsPayload = context.options[1]
const useHyphenated = option !== 'never'
/** @type {RegExp[]} */
const excludedTags = ((optionsPayload && optionsPayload.exclude) || []).map(
toRegExp
)
const ignoredAttributes = ['data-', 'aria-', 'slot-scope', ...svgAttributes]

if (optionsPayload && optionsPayload.ignore) {
Expand Down Expand Up @@ -130,11 +141,19 @@ module.exports = {
return useHyphenated ? value.toLowerCase() === value : !/-/.test(value)
}

/**
* @param {string} name
*/
function isExcludedTags(name) {
return excludedTags.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') ||
isExcludedTags(element.rawName)
)
return

Expand Down
26 changes: 25 additions & 1 deletion lib/rules/v-on-event-hyphenation.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const utils = require('../utils')
const casing = require('../utils/casing')
const { toRegExp } = require('../utils/regexp')

module.exports = {
meta: {
Expand Down Expand Up @@ -35,6 +36,12 @@ module.exports = {
},
uniqueItems: true,
additionalItems: false
},
exclude: {
type: 'array',
items: { type: 'string' },
uniqueItems: true,
additionalItems: false
}
},
additionalProperties: false
Expand All @@ -56,6 +63,10 @@ module.exports = {
const useHyphenated = option !== 'never'
/** @type {string[]} */
const ignoredAttributes = (optionsPayload && optionsPayload.ignore) || []
/** @type {RegExp[]} */
const excludedTags = ((optionsPayload && optionsPayload.exclude) || []).map(
toRegExp
)
const autofix = Boolean(optionsPayload && optionsPayload.autofix)

const caseConverter = casing.getConverter(
Expand Down Expand Up @@ -99,9 +110,22 @@ module.exports = {
return useHyphenated ? value.toLowerCase() === value : !/-/.test(value)
}

/**
* @param {string} name
*/
function isExcludedTags(name) {
return excludedTags.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) ||
isExcludedTags(element.rawName)
) {
return
}
if (!node.key.argument || node.key.argument.type !== 'VIdentifier') {
return
}
Expand Down
32 changes: 32 additions & 0 deletions tests/lib/rules/attribute-hyphenation.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ ruleTester.run('attribute-hyphenation', rule, {
filename: 'test.vue',
code: '<template><div><custom :myName.sync="prop"></custom></div></template>',
options: ['never']
},
{
filename: 'test.vue',
code: `
<template>
<VueComponent my-prop></VueComponent>
<custom-component my-prop></custom-component>
</template>
`,
options: ['never', { exclude: ['VueComponent', '/^custom-/'] }]
}
],

Expand Down Expand Up @@ -450,6 +460,28 @@ ruleTester.run('attribute-hyphenation', rule, {
line: 1
}
]
},
{
code: `
<template>
<custom my-prop/>
<CustomComponent my-prop/>
</template>
`,
output: `
<template>
<custom myProp/>
<CustomComponent my-prop/>
</template>
`,
options: ['never', { exclude: ['CustomComponent'] }],
errors: [
{
message: "Attribute 'my-prop' can't be hyphenated.",
type: 'VIdentifier',
line: 3
}
]
}
]
})
33 changes: 33 additions & 0 deletions tests/lib/rules/v-on-event-hyphenation.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,23 @@ tester.run('v-on-event-hyphenation', rule, {
</template>
`,
options: ['never', { ignore: ['custom'] }]
},
{
code: `
<template>
<VueComponent v-on:custom-event="events"/>
</template>
`,
options: ['never', { ignore: ['custom-event'] }]
},
{
code: `
<template>
<VueComponent v-on:custom-event="events"/>
<custom-component v-on:custom-event="events"/>
</template>
`,
options: ['never', { exclude: ['/^Vue/', 'custom-component'] }]
}
],
invalid: [
Expand Down Expand Up @@ -179,6 +196,22 @@ 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: `
<template>
<VueComponent v-on:custom-event="events"/>
<CustomComponent v-on:custom-event="events"/>
</template>
`,
output: `
<template>
<VueComponent v-on:customEvent="events"/>
<CustomComponent v-on:custom-event="events"/>
</template>
`,
options: ['never', { autofix: true, exclude: ['CustomComponent'] }],
errors: ["v-on event 'v-on:custom-event' can't be hyphenated."]
}
]
})
Loading