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
5 changes: 5 additions & 0 deletions .changeset/rude-heads-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'eslint-plugin-vue': minor
---

Changed vue/component-name-in-template-casing `globals` option to support regex patterns
12 changes: 9 additions & 3 deletions docs/rules/component-name-in-template-casing.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ This rule aims to warn the tag names other than the configured casing in Vue.js
- `registeredComponentsOnly` ... If `true`, only registered components (in PascalCase) are checked. If `false`, check all.
default `true`
- `ignores` (`string[]`) ... The element names to ignore. Sets the element name to allow. For example, custom elements or Vue components with special name. You can set the regexp by writing it like `"/^name/"`.
- `globals` (`string[]`) ... Globally registered component names to check. For example, `RouterView` and `RouterLink` are globally registered by `vue-router` and can't be detected as registered in a SFC file.
- `globals` (`string[]`) ... Globally registered component names to check. For example, `RouterView` and `RouterLink` are globally registered by `vue-router` and can't be detected as registered in a SFC file. You can set the regexp by writing it like `"/^c-/"` to match component names with patterns.

### `"PascalCase", { registeredComponentsOnly: true }` (default)

Expand Down Expand Up @@ -143,17 +143,23 @@ export default {

</eslint-code-block>

### `"PascalCase", { globals: ["RouterView"] }`
### `"PascalCase", { globals: ["RouterView", "/^c-/"] }`

<eslint-code-block fix :rules="{'vue/component-name-in-template-casing': ['error', 'PascalCase', {globals: ['RouterView']}]}">
<eslint-code-block fix :rules="{'vue/component-name-in-template-casing': ['error', 'PascalCase', {globals: ['RouterView', '/^c-/']}]}">

```vue
<template>
<!-- ✓ GOOD -->
<RouterView></RouterView>
<CButton />
<CCard />
<CInput />

<!-- ✗ BAD -->
<router-view></router-view>
<c-button />
<c-card />
<c-input />
</template>
```

Expand Down
24 changes: 18 additions & 6 deletions lib/rules/component-name-in-template-casing.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

const utils = require('../utils')
const casing = require('../utils/casing')
const { toRegExpGroupMatcher } = require('../utils/regexp')
const { toRegExpGroupMatcher, isRegExp } = require('../utils/regexp')

const allowedCaseOptions = ['PascalCase', 'kebab-case']
const defaultCase = 'PascalCase'
Expand Down Expand Up @@ -82,16 +82,26 @@ module.exports = {
? caseOption
: defaultCase
const isIgnored = toRegExpGroupMatcher(options.ignores)
/** @type {string[]} */
const globals = (options.globals || []).map(casing.pascalCase)

const globalStrings = []
const globalPatterns = []
for (const global of options.globals || []) {
if (isRegExp(global)) {
globalPatterns.push(global)
} else {
globalStrings.push(global)
}
}

const isGlobalPattern = toRegExpGroupMatcher(globalPatterns)
const registeredComponentsOnly = options.registeredComponentsOnly !== false
const sourceCode = context.getSourceCode()
const tokens =
sourceCode.parserServices.getTemplateBodyTokenStore &&
sourceCode.parserServices.getTemplateBodyTokenStore()

/** @type { Set<string> } */
const registeredComponents = new Set(globals)
const registeredComponents = new Set(globalStrings.map(casing.pascalCase))

if (utils.isScriptSetup(context)) {
// For <script setup>
Expand Down Expand Up @@ -137,8 +147,10 @@ module.exports = {
return true
}

// We only verify the registered components.
return registeredComponents.has(casing.pascalCase(node.rawName))
return (
registeredComponents.has(casing.pascalCase(node.rawName)) ||
isGlobalPattern(node.rawName)
)
}

let hasInvalidEOF = false
Expand Down
95 changes: 95 additions & 0 deletions tests/lib/rules/component-name-in-template-casing.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,21 @@ tester.run('component-name-in-template-casing', rule, {
options: ['kebab-case', { globals: ['RouterView', 'router-link'] }]
},

// globals with regex patterns
{
code: `
<template>
<div>
<c-button />
<c-card />
<c-input />
<other-component />
</div>
</template>
`,
options: ['kebab-case', { globals: ['/^c-/', 'other-component'] }]
},

// type-only imports
...(semver.gte(
require('@typescript-eslint/parser/package.json').version,
Expand Down Expand Up @@ -1223,6 +1238,86 @@ tester.run('component-name-in-template-casing', rule, {
}
]
},
{
code: `
<template>
<c-button />
<c-card />
<c-input />
</template>
`,
output: `
<template>
<CButton />
<CCard />
<CInput />
</template>
`,
options: ['PascalCase', { globals: ['/^c-/'] }],
errors: [
{
message: 'Component name "c-button" is not PascalCase.',
line: 3,
column: 11,
endLine: 3,
endColumn: 20
},
{
message: 'Component name "c-card" is not PascalCase.',
line: 4,
column: 11,
endLine: 4,
endColumn: 18
},
{
message: 'Component name "c-input" is not PascalCase.',
line: 5,
column: 11,
endLine: 5,
endColumn: 19
}
]
},
{
code: `
<template>
<CButton />
<CCard />
<CInput />
</template>
`,
output: `
<template>
<c-button />
<c-card />
<c-input />
</template>
`,
options: ['kebab-case', { globals: ['/^C[A-Z]/', '/^c-/'] }],
errors: [
{
message: 'Component name "CButton" is not kebab-case.',
line: 3,
column: 11,
endLine: 3,
endColumn: 19
},
{
message: 'Component name "CCard" is not kebab-case.',
line: 4,
column: 11,
endLine: 4,
endColumn: 17
},
{
message: 'Component name "CInput" is not kebab-case.',
line: 5,
column: 11,
endLine: 5,
endColumn: 18
}
]
},
// type-only imports
...(semver.gte(
require('@typescript-eslint/parser/package.json').version,
Expand Down