diff --git a/docs/rules/index.md b/docs/rules/index.md
index 605cd4ef4..d7033432a 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -234,6 +234,7 @@ For example:
| [vue/no-restricted-block](./no-restricted-block.md) | disallow specific block | | :hammer: |
| [vue/no-restricted-call-after-await](./no-restricted-call-after-await.md) | disallow asynchronously called restricted methods | | :hammer: |
| [vue/no-restricted-class](./no-restricted-class.md) | disallow specific classes in Vue components | | :warning: |
+| [vue/no-restricted-component-names](./no-restricted-component-names.md) | disallow specific component names | :bulb: | :hammer: |
| [vue/no-restricted-component-options](./no-restricted-component-options.md) | disallow specific component option | | :hammer: |
| [vue/no-restricted-custom-event](./no-restricted-custom-event.md) | disallow specific custom event | :bulb: | :hammer: |
| [vue/no-restricted-html-elements](./no-restricted-html-elements.md) | disallow specific HTML elements | | :hammer: |
diff --git a/docs/rules/no-restricted-component-names.md b/docs/rules/no-restricted-component-names.md
new file mode 100644
index 000000000..ccd7b24bc
--- /dev/null
+++ b/docs/rules/no-restricted-component-names.md
@@ -0,0 +1,89 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-restricted-component-names
+description: disallow specific component names
+---
+# vue/no-restricted-component-names
+
+> disallow specific component names
+
+- :exclamation: ***This rule has not been released yet.***
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
+## :book: Rule Details
+
+This rule allows you to specify component names that you don't want to use in your application.
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+## :wrench: Options
+
+This rule takes a list of strings, where each string is a component name or pattern to be restricted:
+
+```json
+{
+ "vue/no-restricted-component-names": ["error", "foo", "/^Disallow/"]
+}
+```
+
+Alternatively, you can specify an object with a `name` property and an optional `message` and `suggest` property:
+
+```json
+ {
+ "vue/no-restricted-component-names": [
+ "error",
+ {
+ "name": "Disallow",
+ "message": "Please do not use `Disallow` as a component name",
+ "suggest": "allow"
+ },
+ {
+ "name": "/^custom/",
+ "message": "Please do not use component names starting with 'custom'"
+ }
+ ]
+ }
+ ```
+
+
+
+```vue
+
+
+```
+
+
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-restricted-component-names.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-restricted-component-names.js)
diff --git a/lib/index.js b/lib/index.js
index 41c5b6b84..4329a29a2 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -118,6 +118,7 @@ module.exports = {
'no-restricted-block': require('./rules/no-restricted-block'),
'no-restricted-call-after-await': require('./rules/no-restricted-call-after-await'),
'no-restricted-class': require('./rules/no-restricted-class'),
+ 'no-restricted-component-names': require('./rules/no-restricted-component-names'),
'no-restricted-component-options': require('./rules/no-restricted-component-options'),
'no-restricted-custom-event': require('./rules/no-restricted-custom-event'),
'no-restricted-html-elements': require('./rules/no-restricted-html-elements'),
diff --git a/lib/rules/no-restricted-component-names.js b/lib/rules/no-restricted-component-names.js
new file mode 100644
index 000000000..e7fc28608
--- /dev/null
+++ b/lib/rules/no-restricted-component-names.js
@@ -0,0 +1,157 @@
+/**
+ * @author ItMaga
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const casing = require('../utils/casing')
+const { isRegExp, toRegExp } = require('../utils/regexp')
+
+/**
+ * @typedef {object} OptionParsed
+ * @property { (name: string) => boolean } test
+ * @property {string|undefined} [message]
+ * @property {string|undefined} [suggest]
+ */
+
+/**
+ * @param {string} str
+ * @returns {(str: string) => boolean}
+ * @private
+ */
+function buildMatcher(str) {
+ if (isRegExp(str)) {
+ const regex = toRegExp(str)
+ return (s) => regex.test(s)
+ }
+ return (s) => s === casing.pascalCase(str) || s === casing.kebabCase(str)
+}
+
+/**
+ * @param {string|{name: string, message?: string, suggest?: string}} option
+ * @returns {OptionParsed}
+ * @private
+ * */
+function parseOption(option) {
+ if (typeof option === 'string') {
+ const matcher = buildMatcher(option)
+ return { test: matcher }
+ }
+ const parsed = parseOption(option.name)
+ parsed.message = option.message
+ parsed.suggest = option.suggest
+ return parsed
+}
+
+/**
+ * @param {Property | AssignmentProperty} property
+ * @param {string | undefined} suggest
+ * @returns {Rule.SuggestionReportDescriptor[]}
+ * @private
+ * */
+function createSuggest(property, suggest) {
+ if (!suggest) {
+ return []
+ }
+
+ return [
+ {
+ fix(fixer) {
+ return fixer.replaceText(property.value, JSON.stringify(suggest))
+ },
+ messageId: 'suggest',
+ data: { suggest }
+ }
+ ]
+}
+
+module.exports = {
+ meta: {
+ hasSuggestions: true,
+ type: 'suggestion',
+ docs: {
+ description: 'disallow specific component names',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/no-restricted-component-names.html'
+ },
+ fixable: null,
+ schema: {
+ type: 'array',
+ items: {
+ oneOf: [
+ { type: 'string' },
+ {
+ type: 'object',
+ properties: {
+ name: { type: 'string' },
+ message: { type: 'string', minLength: 1 },
+ suggest: { type: 'string' }
+ },
+ required: ['name'],
+ additionalProperties: false
+ }
+ ]
+ },
+ uniqueItems: true,
+ minItems: 0
+ },
+ messages: {
+ // eslint-disable-next-line eslint-plugin/report-message-format
+ disallow: '{{message}}',
+ suggest: 'Instead, change to `{{suggest}}`.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ /** @type {OptionParsed[]} */
+ const options = context.options.map(parseOption)
+
+ /**
+ * @param {ObjectExpression} node
+ */
+ function verify(node) {
+ const property = utils.findProperty(node, 'name')
+ if (!property) return
+
+ const propertyName = utils.getStaticPropertyName(property)
+ if (propertyName === 'name' && property.value.type === 'Literal') {
+ const componentName = property.value.value?.toString()
+ if (!componentName) {
+ return
+ }
+
+ for (const option of options) {
+ if (option.test(componentName)) {
+ context.report({
+ node: property.value,
+ messageId: 'disallow',
+ data: {
+ message:
+ option.message ||
+ `Using component name \`${componentName}\` is not allowed.`
+ },
+ suggest: createSuggest(property, option.suggest)
+ })
+ }
+ }
+ }
+ }
+
+ return utils.compositingVisitors(
+ utils.defineVueVisitor(context, {
+ onVueObjectEnter(node) {
+ verify(node)
+ }
+ }),
+ utils.defineScriptSetupVisitor(context, {
+ onDefineOptionsEnter(node) {
+ const expression = node.arguments[0]
+ if (expression.type === 'ObjectExpression') {
+ verify(expression)
+ }
+ }
+ })
+ )
+ }
+}
diff --git a/tests/lib/rules/no-restricted-component-names.js b/tests/lib/rules/no-restricted-component-names.js
new file mode 100644
index 000000000..07030c4bb
--- /dev/null
+++ b/tests/lib/rules/no-restricted-component-names.js
@@ -0,0 +1,262 @@
+/**
+ * @author ItMaga
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/no-restricted-component-names')
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: 'module'
+ }
+})
+
+tester.run('no-restricted-component-names', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: ['Disallow', 'Disallow2'],
+ errors: [
+ {
+ message: 'Using component name `Disallow` is not allowed.',
+ line: 4,
+ column: 15
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: ['Disallow'],
+ errors: [
+ {
+ message: 'Using component name `Disallow` is not allowed.',
+ line: 4,
+ column: 15
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: ['Disallow'],
+ errors: [
+ {
+ message: 'Using component name `Disallow` is not allowed.',
+ line: 4,
+ column: 15
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: ['/^Foo(Bar|Baz)/'],
+ errors: [
+ {
+ message: 'Using component name `FooBar` is not allowed.',
+ line: 4,
+ column: 15
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [
+ { name: 'Disallow', message: 'Custom message', suggest: 'Allow' }
+ ],
+ errors: [
+ {
+ message: 'Custom message',
+ line: 4,
+ column: 15,
+ suggestions: [
+ {
+ desc: 'Instead, change to `Allow`.',
+ output: `
+
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [{ name: 'Disallow', suggest: 'Allow' }],
+ errors: [
+ {
+ message: 'Using component name `Disallow` is not allowed.',
+ line: 4,
+ column: 15,
+ suggestions: [
+ {
+ desc: 'Instead, change to `Allow`.',
+ output: `
+
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [{ name: 'Disallow', message: 'Custom message' }],
+ errors: [
+ {
+ message: 'Custom message',
+ line: 4,
+ column: 15
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: ['1'],
+ errors: [
+ {
+ message: 'Using component name `1` is not allowed.',
+ line: 4,
+ column: 15
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: ['DisallowedComponent'],
+ errors: [
+ {
+ message:
+ 'Using component name `disallowed-component` is not allowed.',
+ line: 4,
+ column: 15
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: ['disallowed-component'],
+ errors: [
+ {
+ message: 'Using component name `DisallowedComponent` is not allowed.',
+ line: 4,
+ column: 15
+ }
+ ]
+ }
+ ]
+})