Skip to content

Commit

Permalink
feat: add ignoreTags option (#2609)
Browse files Browse the repository at this point in the history
Co-authored-by: Flo Edelmann <[email protected]>
  • Loading branch information
waynzh and FloEdelmann authored Nov 27, 2024
1 parent bea53c0 commit 9ddf3e5
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 11 deletions.
26 changes: 22 additions & 4 deletions docs/rules/attribute-hyphenation.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,19 @@ This rule enforces using hyphenated attribute names on custom components in Vue
```json
{
"vue/attribute-hyphenation": ["error", "always" | "never", {
"ignore": []
"ignore": [],
"ignoreTags": []
}]
}
```

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"`

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

</eslint-code-block>

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

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

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

</eslint-code-block>

## :couple: Related Rules

- [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md)
Expand Down
26 changes: 22 additions & 4 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": [],
"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"`
Expand Down Expand Up @@ -104,6 +106,22 @@ Don't use hyphenated name but allow custom event names

</eslint-code-block>

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

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

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

</eslint-code-block>

## :couple: Related Rules

- [vue/custom-event-name-casing](./custom-event-name-casing.md)
Expand Down
22 changes: 20 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
},
ignoreTags: {
type: 'array',
items: { type: 'string' },
uniqueItems: true,
additionalItems: false
}
},
additionalProperties: false
Expand All @@ -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) {
Expand Down Expand Up @@ -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

Expand Down
25 changes: 24 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
},
ignoreTags: {
type: 'array',
items: { type: 'string' },
uniqueItems: true,
additionalItems: false
}
},
additionalProperties: false
Expand All @@ -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(
Expand Down Expand Up @@ -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
}
Expand Down
66 changes: 66 additions & 0 deletions tests/lib/rules/attribute-hyphenation.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,26 @@ 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', { ignoreTags: ['VueComponent', '/^custom-/'] }]
},
{
filename: 'test.vue',
code: `
<template>
<VueComponent myProp="prop"></VueComponent>
<custom-component myProp="prop"></custom-component>
</template>
`,
options: ['always', { ignoreTags: ['VueComponent', '/^custom-/'] }]
}
],

Expand Down Expand Up @@ -450,6 +470,52 @@ 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', { ignoreTags: ['CustomComponent'] }],
errors: [
{
message: "Attribute 'my-prop' can't be hyphenated.",
type: 'VIdentifier',
line: 3,
column: 17
}
]
},
{
code: `
<template>
<custom myProp/>
<CustomComponent myProp/>
</template>
`,
output: `
<template>
<custom my-prop/>
<CustomComponent myProp/>
</template>
`,
options: ['always', { ignoreTags: ['CustomComponent'] }],
errors: [
{
message: "Attribute 'myProp' must be hyphenated.",
type: 'VIdentifier',
line: 3,
column: 17
}
]
}
]
})
70 changes: 70 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,32 @@ 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', { ignoreTags: ['/^Vue/', 'custom-component'] }]
},
{
code: `
<template>
<VueComponent v-on:customEvent="events"/>
<custom-component v-on:customEvent="events"/>
</template>
`,
options: ['always', { ignoreTags: ['/^Vue/', 'custom-component'] }]
}
],
invalid: [
Expand Down Expand Up @@ -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: `
<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, ignoreTags: ['CustomComponent'] }],
errors: [
{
message: "v-on event 'v-on:custom-event' can't be hyphenated.",
line: 3,
column: 23
}
]
},
{
code: `
<template>
<VueComponent v-on:customEvent="events"/>
<CustomComponent v-on:customEvent="events"/>
</template>
`,
output: `
<template>
<VueComponent v-on:custom-event="events"/>
<CustomComponent v-on:customEvent="events"/>
</template>
`,
options: ['always', { autofix: true, ignoreTags: ['CustomComponent'] }],
errors: [
{
message: "v-on event 'v-on:customEvent' must be hyphenated.",
line: 3,
column: 23
}
]
}
]
})

0 comments on commit 9ddf3e5

Please sign in to comment.