diff --git a/docs/rules/no-reserved-component-names.md b/docs/rules/no-reserved-component-names.md index a5d85cde8..31b74ab06 100644 --- a/docs/rules/no-reserved-component-names.md +++ b/docs/rules/no-reserved-component-names.md @@ -35,13 +35,15 @@ export default { { "vue/no-reserved-component-names": ["error", { "disallowVueBuiltInComponents": false, - "disallowVue3BuiltInComponents": false + "disallowVue3BuiltInComponents": false, + "htmlElementCaseSensitive": false, }] } ``` - `disallowVueBuiltInComponents` (`boolean`) ... If `true`, disallow Vue.js 2.x built-in component names. Default is `false`. - `disallowVue3BuiltInComponents` (`boolean`) ... If `true`, disallow Vue.js 3.x built-in component names. Default is `false`. +- `htmlElementCaseSensitive` (`boolean`) ... If `true`, component names must exactly match the case of an HTML element to be considered conflicting. Default is `false` (i.e. case-insensitve comparison). ### `"disallowVueBuiltInComponents": true` @@ -73,6 +75,34 @@ export default { +### `"htmlElementCaseSensitive": true` + + + +```vue + +``` + + + + + +```vue + +``` + + + ## :couple: Related Rules - [vue/multi-word-component-names](./multi-word-component-names.md) diff --git a/lib/rules/no-reserved-component-names.js b/lib/rules/no-reserved-component-names.js index 90e53604f..56621b424 100644 --- a/lib/rules/no-reserved-component-names.js +++ b/lib/rules/no-reserved-component-names.js @@ -34,19 +34,6 @@ function isLowercase(word) { return /^[a-z]*$/.test(word) } -const RESERVED_NAMES_IN_HTML = new Set([ - ...htmlElements, - ...htmlElements.map(casing.capitalize) -]) -const RESERVED_NAMES_IN_OTHERS = new Set([ - ...deprecatedHtmlElements, - ...deprecatedHtmlElements.map(casing.capitalize), - ...kebabCaseElements, - ...kebabCaseElements.map(casing.pascalCase), - ...svgElements, - ...svgElements.filter(isLowercase).map(casing.capitalize) -]) - /** * @param {Expression | SpreadElement} node * @returns {node is (Literal | TemplateLiteral)} @@ -61,14 +48,14 @@ function canVerify(node) { } /** - * @param {string} name - * @returns {string} + * @template T + * @param {Set} set + * @param {Iterable} iterable */ -function getMessageId(name) { - if (RESERVED_NAMES_IN_HTML.has(name)) return 'reservedInHtml' - if (RESERVED_NAMES_IN_VUE.has(name)) return 'reservedInVue' - if (RESERVED_NAMES_IN_VUE3.has(name)) return 'reservedInVue3' - return 'reserved' +function addAll(set, iterable) { + for (const element of iterable) { + set.add(element) + } } module.exports = { @@ -90,6 +77,9 @@ module.exports = { }, disallowVue3BuiltInComponents: { type: 'boolean' + }, + htmlElementCaseSensitive: { + type: 'boolean' } }, additionalProperties: false @@ -109,6 +99,23 @@ module.exports = { options.disallowVueBuiltInComponents === true const disallowVue3BuiltInComponents = options.disallowVue3BuiltInComponents === true + const htmlElementCaseSensitive = options.htmlElementCaseSensitive === true + + const RESERVED_NAMES_IN_HTML = new Set(htmlElements) + const RESERVED_NAMES_IN_OTHERS = new Set([ + ...deprecatedHtmlElements, + ...kebabCaseElements, + ...svgElements + ]) + + if (!htmlElementCaseSensitive) { + addAll(RESERVED_NAMES_IN_HTML, htmlElements.map(casing.capitalize)) + addAll(RESERVED_NAMES_IN_OTHERS, [ + ...deprecatedHtmlElements.map(casing.capitalize), + ...kebabCaseElements.map(casing.pascalCase), + ...svgElements.filter(isLowercase).map(casing.capitalize) + ]) + } const reservedNames = new Set([ ...RESERVED_NAMES_IN_HTML, @@ -117,6 +124,17 @@ module.exports = { ...RESERVED_NAMES_IN_OTHERS ]) + /** + * @param {string} name + * @returns {string} + */ + function getMessageId(name) { + if (RESERVED_NAMES_IN_HTML.has(name)) return 'reservedInHtml' + if (RESERVED_NAMES_IN_VUE.has(name)) return 'reservedInVue' + if (RESERVED_NAMES_IN_VUE3.has(name)) return 'reservedInVue3' + return 'reserved' + } + /** * @param {Literal | TemplateLiteral} node */ diff --git a/tests/lib/rules/no-reserved-component-names.js b/tests/lib/rules/no-reserved-component-names.js index 41cddcfd5..231e1f98d 100644 --- a/tests/lib/rules/no-reserved-component-names.js +++ b/tests/lib/rules/no-reserved-component-names.js @@ -410,6 +410,16 @@ const invalidElements = [ 'xmp', 'Xmp' ] +const invalidLowerCaseElements = [] +const invalidUpperCaseElements = [] + +for (const element of invalidElements) { + if (element[0] === element[0].toLowerCase()) { + invalidLowerCaseElements.push(element) + } else { + invalidUpperCaseElements.push(element) + } +} const vue2BuiltInComponents = [ 'component', @@ -559,6 +569,16 @@ ruleTester.run('no-reserved-component-names', rule, { languageOptions, options: [{ disallowVueBuiltInComponents: true }] })), + ...invalidUpperCaseElements.map((name) => ({ + filename: `${name}.vue`, + code: ` + export default { + name: '${name}' + } + `, + languageOptions, + options: [{ htmlElementCaseSensitive: true }] + })), { filename: 'test.vue', code: ``, @@ -701,6 +721,24 @@ ruleTester.run('no-reserved-component-names', rule, { } ] })), + ...invalidLowerCaseElements.map((name) => ({ + filename: `${name}.vue`, + code: ``, + languageOptions: { + parser: require('vue-eslint-parser'), + ...languageOptions + }, + options: [{ htmlElementCaseSensitive: true }], + errors: [ + { + messageId: RESERVED_NAMES_IN_HTML.has(name) + ? 'reservedInHtml' + : 'reserved', + data: { name }, + line: 1 + } + ] + })), ...vue2BuiltInComponents.map((name) => ({ filename: `${name}.vue`, code: `