diff --git a/lib/rules/no-undef-components.js b/lib/rules/no-undef-components.js index 4537f3dce..9eb74ad05 100644 --- a/lib/rules/no-undef-components.js +++ b/lib/rules/no-undef-components.js @@ -4,6 +4,7 @@ */ 'use strict' +const path = require('path') const utils = require('../utils') const casing = require('../utils/casing') @@ -17,6 +18,93 @@ function camelize(str) { return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : '')) } +class DefinedInSetupComponents { + constructor() { + /** + * Component names + * @type {Set} + */ + this.names = new Set() + } + + /** + * @param {string[]} names + */ + addName(...names) { + for (const name of names) { + this.names.add(name) + } + } + + /** + * @see https://github.com/vuejs/core/blob/ae4b0783d78670b6e942ae2a4e3ec6efbbffa158/packages/compiler-core/src/transforms/transformElement.ts#L334 + * @param {string} rawName + */ + isDefinedComponent(rawName) { + if (this.names.has(rawName)) { + return true + } + const camelName = camelize(rawName) + if (this.names.has(camelName)) { + return true + } + const pascalName = casing.capitalize(camelName) + if (this.names.has(pascalName)) { + return true + } + // Check namespace + // https://github.com/vuejs/core/blob/ae4b0783d78670b6e942ae2a4e3ec6efbbffa158/packages/compiler-core/src/transforms/transformElement.ts#L305 + const dotIndex = rawName.indexOf('.') + if (dotIndex > 0 && this.isDefinedComponent(rawName.slice(0, dotIndex))) { + return true + } + return false + } +} + +class DefinedInOptionComponents { + constructor() { + /** + * Component names + * @type {Set} + */ + this.names = new Set() + /** + * Component names, transformed to kebab-case + * @type {Set} + */ + this.kebabCaseNames = new Set() + } + + /** + * @param {string[]} names + */ + addName(...names) { + for (const name of names) { + this.names.add(name) + this.kebabCaseNames.add(casing.kebabCase(name)) + } + } + + /** + * @param {string} rawName + */ + isDefinedComponent(rawName) { + if (this.names.has(rawName)) { + return true + } + const kebabCaseName = casing.kebabCase(rawName) + if ( + this.kebabCaseNames.has(kebabCaseName) && + !casing.isPascalCase(rawName) + ) { + // Component registered as `foo-bar` cannot be used as `FooBar` + return true + } + return false + } +} + module.exports = { meta: { type: 'suggestion', @@ -109,13 +197,15 @@ module.exports = { if (utils.isScriptSetup(context)) { // For + + + ` + }, + { + filename: 'FooBar.vue', + code: ` + + + + ` + }, + { + filename: 'FooBar.vue', + code: ` + + + + + ` + }, // options API {