diff --git a/docs/rules/README.md b/docs/rules/README.md
index 85f6e661b..2f89e3a53 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -310,6 +310,7 @@ For example:
| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | |
| [vue/no-restricted-block](./no-restricted-block.md) | disallow specific block | |
| [vue/no-restricted-call-after-await](./no-restricted-call-after-await.md) | disallow asynchronously called restricted methods | |
+| [vue/no-restricted-class](./no-restricted-class.md) | disallow specific classes in Vue components | |
| [vue/no-restricted-component-options](./no-restricted-component-options.md) | disallow specific component option | |
| [vue/no-restricted-custom-event](./no-restricted-custom-event.md) | disallow specific custom event | |
| [vue/no-restricted-props](./no-restricted-props.md) | disallow specific props | |
diff --git a/docs/rules/no-restricted-class.md b/docs/rules/no-restricted-class.md
new file mode 100644
index 000000000..c7aebce6b
--- /dev/null
+++ b/docs/rules/no-restricted-class.md
@@ -0,0 +1,79 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-restricted-class
+description: disallow specific classes in Vue components
+---
+# vue/no-restricted-class
+
+> disallow specific classes in Vue components
+
+- :exclamation: ***This rule has not been released yet.***
+
+## :book: Rule Details
+
+This rule lets you specify a list of classes that you don't want to allow in your templates.
+
+## :wrench: Options
+
+The simplest way to specify a list of forbidden classes is to pass it directly
+in the rule configuration.
+
+```json
+{
+ "vue/no-restricted-props": ["error", "forbidden", "forbidden-two", "forbidden-three"]
+}
+```
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+::: warning Note
+This rule will only detect classes that are used as strings in your templates. Passing classes via
+variables, like below, will not be detected by this rule.
+
+```vue
+
+
+
+
+
+```
+:::
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-restricted-class.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-restricted-class.js)
diff --git a/lib/index.js b/lib/index.js
index 0015fb579..ef111732d 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -101,6 +101,7 @@ module.exports = {
'no-reserved-keys': require('./rules/no-reserved-keys'),
'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-options': require('./rules/no-restricted-component-options'),
'no-restricted-custom-event': require('./rules/no-restricted-custom-event'),
'no-restricted-props': require('./rules/no-restricted-props'),
diff --git a/lib/rules/no-restricted-class.js b/lib/rules/no-restricted-class.js
new file mode 100644
index 000000000..b1dcccb07
--- /dev/null
+++ b/lib/rules/no-restricted-class.js
@@ -0,0 +1,154 @@
+/**
+ * @fileoverview Forbid certain classes from being used
+ * @author Tao Bojlen
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+const utils = require('../utils')
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+/**
+ * Report a forbidden class
+ * @param {string} className
+ * @param {*} node
+ * @param {RuleContext} context
+ * @param {Set} forbiddenClasses
+ */
+const reportForbiddenClass = (className, node, context, forbiddenClasses) => {
+ if (forbiddenClasses.has(className)) {
+ const loc = node.value ? node.value.loc : node.loc
+ context.report({
+ node,
+ loc,
+ messageId: 'forbiddenClass',
+ data: {
+ class: className
+ }
+ })
+ }
+}
+
+/**
+ * @param {Expression} node
+ * @param {boolean} [textOnly]
+ * @returns {IterableIterator<{ className:string, reportNode: ESNode }>}
+ */
+function* extractClassNames(node, textOnly) {
+ if (node.type === 'Literal') {
+ yield* `${node.value}`
+ .split(/\s+/)
+ .map((className) => ({ className, reportNode: node }))
+ return
+ }
+ if (node.type === 'TemplateLiteral') {
+ for (const templateElement of node.quasis) {
+ yield* templateElement.value.cooked
+ .split(/\s+/)
+ .map((className) => ({ className, reportNode: templateElement }))
+ }
+ for (const expr of node.expressions) {
+ yield* extractClassNames(expr, true)
+ }
+ return
+ }
+ if (node.type === 'BinaryExpression') {
+ if (node.operator !== '+') {
+ return
+ }
+ yield* extractClassNames(node.left, true)
+ yield* extractClassNames(node.right, true)
+ return
+ }
+ if (textOnly) {
+ return
+ }
+ if (node.type === 'ObjectExpression') {
+ for (const prop of node.properties) {
+ if (prop.type !== 'Property') {
+ continue
+ }
+ const classNames = utils.getStaticPropertyName(prop)
+ if (!classNames) {
+ continue
+ }
+ yield* classNames
+ .split(/\s+/)
+ .map((className) => ({ className, reportNode: prop.key }))
+ }
+ return
+ }
+ if (node.type === 'ArrayExpression') {
+ for (const element of node.elements) {
+ if (element == null) {
+ continue
+ }
+ if (element.type === 'SpreadElement') {
+ continue
+ }
+ yield* extractClassNames(element)
+ }
+ return
+ }
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'disallow specific classes in Vue components',
+ url: 'https://eslint.vuejs.org/rules/no-restricted-class.html',
+ categories: undefined
+ },
+ fixable: null,
+ messages: {
+ forbiddenClass: "'{{class}}' class is not allowed."
+ },
+ schema: {
+ type: 'array',
+ items: {
+ type: 'string'
+ }
+ }
+ },
+
+ /** @param {RuleContext} context */
+ create(context) {
+ const forbiddenClasses = new Set(context.options || [])
+
+ return utils.defineTemplateBodyVisitor(context, {
+ /**
+ * @param {VAttribute & { value: VLiteral } } node
+ */
+ 'VAttribute[directive=false][key.name="class"]'(node) {
+ node.value.value
+ .split(/\s+/)
+ .forEach((className) =>
+ reportForbiddenClass(className, node, context, forbiddenClasses)
+ )
+ },
+
+ /** @param {VExpressionContainer} node */
+ "VAttribute[directive=true][key.name.name='bind'][key.argument.name='class'] > VExpressionContainer.value"(
+ node
+ ) {
+ if (!node.expression) {
+ return
+ }
+
+ for (const { className, reportNode } of extractClassNames(
+ /** @type {Expression} */ (node.expression)
+ )) {
+ reportForbiddenClass(className, reportNode, context, forbiddenClasses)
+ }
+ }
+ })
+ }
+}
diff --git a/tests/lib/rules/no-restricted-class.js b/tests/lib/rules/no-restricted-class.js
new file mode 100644
index 000000000..baf39778f
--- /dev/null
+++ b/tests/lib/rules/no-restricted-class.js
@@ -0,0 +1,118 @@
+/**
+ * @author Tao Bojlen
+ */
+
+'use strict'
+
+const rule = require('../../../lib/rules/no-restricted-class')
+const RuleTester = require('eslint').RuleTester
+
+const ruleTester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: { ecmaVersion: 2020, sourceType: 'module' }
+})
+
+ruleTester.run('no-restricted-class', rule, {
+ valid: [
+ { code: `Content
` },
+ {
+ code: `Content
`,
+ options: ['forbidden']
+ },
+ {
+ code: `Content
`,
+ options: ['forbidden']
+ },
+ {
+ code: `Content
`,
+ options: ['forbidden']
+ },
+ {
+ code: `Content
`,
+ options: ['forbidden']
+ }
+ ],
+
+ invalid: [
+ {
+ code: ``,
+ errors: [
+ {
+ message: "'forbidden' class is not allowed.",
+ type: 'VAttribute'
+ }
+ ],
+ options: ['forbidden']
+ },
+ {
+ code: ``,
+ errors: [
+ {
+ message: "'forbidden' class is not allowed.",
+ type: 'Literal'
+ }
+ ],
+ options: ['forbidden']
+ },
+ {
+ code: ``,
+ errors: [
+ {
+ message: "'forbidden' class is not allowed.",
+ type: 'Literal'
+ }
+ ],
+ options: ['forbidden']
+ },
+ {
+ code: ``,
+ errors: [
+ {
+ message: "'forbidden' class is not allowed.",
+ type: 'Identifier'
+ }
+ ],
+ options: ['forbidden']
+ },
+ {
+ code: '',
+ errors: [
+ {
+ message: "'forbidden' class is not allowed.",
+ type: 'TemplateElement'
+ }
+ ],
+ options: ['forbidden']
+ },
+ {
+ code: ``,
+ errors: [
+ {
+ message: "'forbidden' class is not allowed.",
+ type: 'Literal'
+ }
+ ],
+ options: ['forbidden']
+ },
+ {
+ code: ``,
+ errors: [
+ {
+ message: "'forbidden' class is not allowed.",
+ type: 'Literal'
+ }
+ ],
+ options: ['forbidden']
+ },
+ {
+ code: ``,
+ errors: [
+ {
+ message: "'forbidden' class is not allowed.",
+ type: 'Literal'
+ }
+ ],
+ options: ['forbidden']
+ }
+ ]
+})