You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
164 lines
4.8 KiB
164 lines
4.8 KiB
/**
|
|
* @author Yosuke Ota
|
|
* issue https://github.com/vuejs/eslint-plugin-vue/issues/250
|
|
*/
|
|
'use strict'
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Requirements
|
|
// ------------------------------------------------------------------------------
|
|
|
|
const utils = require('../utils')
|
|
const casing = require('../utils/casing')
|
|
const { toRegExp } = require('../utils/regexp')
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Helpers
|
|
// -----------------------------------------------------------------------------
|
|
|
|
const allowedCaseOptions = ['PascalCase', 'kebab-case']
|
|
const defaultCase = 'PascalCase'
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Rule Definition
|
|
// ------------------------------------------------------------------------------
|
|
|
|
module.exports = {
|
|
meta: {
|
|
type: 'suggestion',
|
|
docs: {
|
|
description:
|
|
'enforce specific casing for the component naming style in template',
|
|
categories: undefined,
|
|
url: 'https://eslint.vuejs.org/rules/component-name-in-template-casing.html'
|
|
},
|
|
fixable: 'code',
|
|
schema: [
|
|
{
|
|
enum: allowedCaseOptions
|
|
},
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
ignores: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
uniqueItems: true,
|
|
additionalItems: false
|
|
},
|
|
registeredComponentsOnly: {
|
|
type: 'boolean'
|
|
}
|
|
},
|
|
additionalProperties: false
|
|
}
|
|
]
|
|
},
|
|
/** @param {RuleContext} context */
|
|
create(context) {
|
|
const caseOption = context.options[0]
|
|
const options = context.options[1] || {}
|
|
const caseType =
|
|
allowedCaseOptions.indexOf(caseOption) !== -1 ? caseOption : defaultCase
|
|
/** @type {RegExp[]} */
|
|
const ignores = (options.ignores || []).map(toRegExp)
|
|
const registeredComponentsOnly = options.registeredComponentsOnly !== false
|
|
const tokens =
|
|
context.parserServices.getTemplateBodyTokenStore &&
|
|
context.parserServices.getTemplateBodyTokenStore()
|
|
|
|
/** @type { string[] } */
|
|
const registeredComponents = []
|
|
|
|
/**
|
|
* Checks whether the given node is the verification target node.
|
|
* @param {VElement} node element node
|
|
* @returns {boolean} `true` if the given node is the verification target node.
|
|
*/
|
|
function isVerifyTarget(node) {
|
|
if (ignores.some((re) => re.test(node.rawName))) {
|
|
// ignore
|
|
return false
|
|
}
|
|
|
|
if (!registeredComponentsOnly) {
|
|
// If the user specifies registeredComponentsOnly as false, it checks all component tags.
|
|
if (
|
|
(!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
|
|
utils.isHtmlWellKnownElementName(node.rawName) ||
|
|
utils.isSvgWellKnownElementName(node.rawName)
|
|
) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
// We only verify the components registered in the component.
|
|
if (
|
|
registeredComponents
|
|
.filter((name) => casing.isPascalCase(name)) // When defining a component with PascalCase, you can use either case
|
|
.some(
|
|
(name) =>
|
|
node.rawName === name || casing.pascalCase(node.rawName) === name
|
|
)
|
|
) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
let hasInvalidEOF = false
|
|
|
|
return utils.defineTemplateBodyVisitor(
|
|
context,
|
|
{
|
|
VElement(node) {
|
|
if (hasInvalidEOF) {
|
|
return
|
|
}
|
|
|
|
if (!isVerifyTarget(node)) {
|
|
return
|
|
}
|
|
|
|
const name = node.rawName
|
|
if (!casing.getChecker(caseType)(name)) {
|
|
const startTag = node.startTag
|
|
const open = tokens.getFirstToken(startTag)
|
|
const casingName = casing.getExactConverter(caseType)(name)
|
|
context.report({
|
|
node: open,
|
|
loc: open.loc,
|
|
message: 'Component name "{{name}}" is not {{caseType}}.',
|
|
data: {
|
|
name,
|
|
caseType
|
|
},
|
|
*fix(fixer) {
|
|
yield fixer.replaceText(open, `<${casingName}`)
|
|
const endTag = node.endTag
|
|
if (endTag) {
|
|
const endTagOpen = tokens.getFirstToken(endTag)
|
|
yield fixer.replaceText(endTagOpen, `</${casingName}`)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
},
|
|
{
|
|
Program(node) {
|
|
hasInvalidEOF = utils.hasInvalidEOF(node)
|
|
},
|
|
...(registeredComponentsOnly
|
|
? utils.executeOnVue(context, (obj) => {
|
|
registeredComponents.push(
|
|
...utils.getRegisteredComponents(obj).map((n) => n.name)
|
|
)
|
|
})
|
|
: {})
|
|
}
|
|
)
|
|
}
|
|
}
|