Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ For example:
| [vue/require-typed-ref](./require-typed-ref.md) | require `ref` and `shallowRef` functions to be strongly typed | | :hammer: |
| [vue/restricted-component-names](./restricted-component-names.md) | enforce using only specific in component names | | :warning: |
| [vue/script-indent](./script-indent.md) | enforce consistent indentation in `<script>` | :wrench: | :lipstick: |
| [vue/slot-name-casing](./slot-name-casing.md) | enforce specific casing for the slot name | | :hammer: |
| [vue/sort-keys](./sort-keys.md) | enforce sort-keys in a manner that is compatible with order-in-components | | :hammer: |
| [vue/static-class-names-order](./static-class-names-order.md) | enforce static class names order | :wrench: | :hammer: |
| [vue/v-for-delimiter-style](./v-for-delimiter-style.md) | enforce `v-for` directive's delimiter style | :wrench: | :lipstick: |
Expand Down
88 changes: 88 additions & 0 deletions docs/rules/slot-name-casing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/slot-name-casing
description: enforce specific casing for the slot name
---

# vue/slot-name-casing

> enforce specific casing for the slot name

- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> _**This rule has not been released yet.**_ </badge>

## :book: Rule Details

This rule enforce proper casing of slot names in vue components.

<eslint-code-block :rules="{'vue/slot-name-casing': ['error']}">

```vue
<template>
<!-- ✓ GOOD -->
<slot name="foo" />
<slot name="fooBar" />

<!-- ✗ BAD -->
<slot name="foo-bar" />
<slot name="foo_bar" />
<slot name="foo:bar" />
</template>
```

</eslint-code-block>

## :wrench: Options

```json
{
"vue/slot-name-casing": ["error", "camelCase" | "kebab-case" | "singleword"]
}
```

- `"camelCase"` (default) ... Enforce slot name to be in camel case.
- `"kebab-case"` ... Enforce slot name to be in kebab case.
- `"singleword"` ... Enforce slot name to be a single word.

### `"kebab-case"`

<eslint-code-block :rules="{'vue/prop-name-casing': ['error', 'kebab-case']}">

```vue
<template>
<!-- ✓ GOOD -->
<slot name="foo" />
<slot name="foo-bar" />

<!-- ✗ BAD -->
<slot name="fooBar" />
<slot name="foo_bar" />
<slot name="foo:bar" />
</template>
```

</eslint-code-block>

### `"singleword"`

<eslint-code-block :rules="{'vue/prop-name-casing': ['error', 'singleword']}">

```vue
<template>
<!-- ✓ GOOD -->
<slot name="foo" />

<!-- ✗ BAD -->
<slot name="foo-bar" />
<slot name="fooBar" />
<slot name="foo_bar" />
<slot name="foo:bar" />
</template>
```

</eslint-code-block>

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/slot-name-casing.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/slot-name-casing.js)
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@
'script-indent': require('./rules/script-indent'),
'script-setup-uses-vars': require('./rules/script-setup-uses-vars'),
'singleline-html-element-content-newline': require('./rules/singleline-html-element-content-newline'),
'slot-name-casing': require('./rules/slot-name-casing'),
'sort-keys': require('./rules/sort-keys'),
'space-in-parens': require('./rules/space-in-parens'),
'space-infix-ops': require('./rules/space-infix-ops'),
Expand Down Expand Up @@ -283,7 +284,7 @@
vue: require('./processor')
},
environments: {
// TODO Remove in the next major version

Check warning on line 287 in lib/index.js

View workflow job for this annotation

GitHub Actions / Lint

Unexpected 'todo' comment: 'TODO Remove in the next major version'
/** @deprecated */
'setup-compiler-macros': {
globals: {
Expand Down
82 changes: 82 additions & 0 deletions lib/rules/slot-name-casing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* @author Wayne Zhang
* See LICENSE file in root directory for full license.
*/
'use strict'

const utils = require('../utils')
const casing = require('../utils/casing')

/**
* @typedef { 'camelCase' | 'kebab-case' | 'singleword' } OptionType
* @typedef { (str: string) => boolean } CheckerType
*/

/**
* Checks whether the given string is a single word.
* @param {string} str
* @return {boolean}
*/
function isSingleWord(str) {
return /^[a-z]+$/u.test(str)
}

/** @type {OptionType[]} */
const allowedCaseOptions = ['camelCase', 'kebab-case', 'singleword']

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'enforce specific casing for the slot name',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/slot-name-casing.html'
},
fixable: null,
schema: [
{
enum: allowedCaseOptions
}
],
messages: {
invalidCase: 'Slot name "{{name}}" is not {{caseType}}.'
}
},
/** @param {RuleContext} context */
create(context) {
const option = context.options[0]

/** @type {OptionType} */
const caseType = allowedCaseOptions.includes(option) ? option : 'camelCase'

/** @type {CheckerType} */
const checker =
caseType === 'singleword' ? isSingleWord : casing.getChecker(caseType)

/** @param {VAttribute} node */
function processSlotNode(node) {
const name = node.value?.value
if (name && !checker(name)) {
context.report({
node,
loc: node.loc,
messageId: 'invalidCase',
data: {
name,
caseType
}
})
}
}

return utils.defineTemplateBodyVisitor(context, {
/** @param {VElement} node */
"VElement[name='slot']"(node) {
const slotName = utils.getAttribute(node, 'name')
if (slotName) {
processSlotNode(slotName)
}
}
})
}
}
143 changes: 143 additions & 0 deletions tests/lib/rules/slot-name-casing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/**
* @author WayneZhang
* See LICENSE file in root directory for full license.
*/
'use strict'

const RuleTester = require('../../eslint-compat').RuleTester
const rule = require('../../../lib/rules/slot-name-casing')

const tester = new RuleTester({
languageOptions: {
parser: require('vue-eslint-parser'),
ecmaVersion: 2020,
sourceType: 'module'
}
})

tester.run('slot-name-casing', rule, {
valid: [
`<template><slot key="foo" /></template>`,
`<template><slot name /></template>`,
`<template><slot name="foo" /></template>`,
`<template><slot name="fooBar" /></template>`,
`<template><slot :name="fooBar" /></template>`,
{
filename: 'test.vue',
code: `
<template>
<slot name="foo" />
<slot name="foo-bar" />
<slot :name="fooBar" />
</template>`,
options: ['kebab-case']
},
{
filename: 'test.vue',
code: `
<template>
<slot name="foo" />
<slot :name="fooBar" />
</template>`,
options: ['singleword']
}
],
invalid: [
{
filename: 'test.vue',
code: `
<template>
<slot name="foo-bar" />
<slot name="foo-Bar_baz" />
</template>`,
errors: [
{
messageId: 'invalidCase',
data: {
name: 'foo-bar',
caseType: 'camelCase'
},
line: 3,
column: 13
},
{
messageId: 'invalidCase',
data: {
name: 'foo-Bar_baz',
caseType: 'camelCase'
},
line: 4,
column: 13
}
]
},
{
filename: 'test.vue',
code: `
<template>
<slot name="fooBar" />
<slot name="foo-Bar_baz" />
</template>`,
options: ['kebab-case'],
errors: [
{
messageId: 'invalidCase',
data: {
name: 'fooBar',
caseType: 'kebab-case'
},
line: 3,
column: 13
},
{
messageId: 'invalidCase',
data: {
name: 'foo-Bar_baz',
caseType: 'kebab-case'
},
line: 4,
column: 13
}
]
},
{
filename: 'test.vue',
code: `
<template>
<slot name="foo-bar" />
<slot name="fooBar" />
<slot name="foo-Bar_baz" />
</template>`,
options: ['singleword'],
errors: [
{
messageId: 'invalidCase',
data: {
name: 'foo-bar',
caseType: 'singleword'
},
line: 3,
column: 13
},
{
messageId: 'invalidCase',
data: {
name: 'fooBar',
caseType: 'singleword'
},
line: 4,
column: 13
},
{
messageId: 'invalidCase',
data: {
name: 'foo-Bar_baz',
caseType: 'singleword'
},
line: 5,
column: 13
}
]
}
]
})
Loading