diff --git a/docs/rules/README.md b/docs/rules/README.md
index 16e0ef983..fa734f4ca 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -288,6 +288,7 @@ For example:
| [vue/no-duplicate-attr-inheritance](./no-duplicate-attr-inheritance.md) | enforce `inheritAttrs` to be set to `false` when using `v-bind="$attrs"` | |
| [vue/no-potential-component-option-typo](./no-potential-component-option-typo.md) | disallow a potential typo in your component property | |
| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | |
+| [vue/no-restricted-component-options](./no-restricted-component-options.md) | disallow specific component option | |
| [vue/no-restricted-static-attribute](./no-restricted-static-attribute.md) | disallow specific attribute | |
| [vue/no-restricted-v-bind](./no-restricted-v-bind.md) | disallow specific argument in `v-bind` | |
| [vue/no-static-inline-styles](./no-static-inline-styles.md) | disallow static inline `style` attributes | |
diff --git a/docs/rules/no-restricted-component-options.md b/docs/rules/no-restricted-component-options.md
new file mode 100644
index 000000000..41292bc67
--- /dev/null
+++ b/docs/rules/no-restricted-component-options.md
@@ -0,0 +1,124 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-restricted-component-options
+description: disallow specific component option
+---
+# vue/no-restricted-component-options
+> disallow specific component option
+
+## :book: Rule Details
+
+This rule allows you to specify component options that you don't want to use in your application.
+
+## :wrench: Options
+
+This rule takes a list of strings, where each string is a component option name or pattern to be restricted:
+
+```json
+{
+ "vue/no-restricted-component-options": ["error", "init", "beforeCompile", "compiled", "activate", "ready", "/^(?:at|de)tached$/"]
+}
+```
+
+
+
+```vue
+
+```
+
+
+
+Also, you can use an array to specify the path of object properties.
+
+e.g. `[ "error", ["props", "/.*/", "twoWay"] ]`
+
+
+
+```vue
+
+```
+
+
+
+You can use `"*"` to match all properties, including computed keys.
+
+e.g. `[ "error", ["props", "*", "twoWay"] ]`
+
+
+
+```vue
+
+```
+
+
+
+Alternatively, the rule also accepts objects.
+
+```json
+{
+ "vue/no-restricted-component-options": ["error",
+ {
+ "name": "init",
+ "message": "Use \"beforeCreate\" instead."
+ },
+ {
+ "name": "/^(?:at|de)tached$/",
+ "message": "\"attached\" and \"detached\" is deprecated."
+ },
+ {
+ "name": ["props", "/.*/", "twoWay"],
+ "message": "\"props.*.twoWay\" cannot be used."
+ }
+ ]
+}
+```
+
+The following properties can be specified for the object.
+
+- `name` ... Specify the component option name or pattern, or the path by its array.
+- `message` ... Specify an optional custom message.
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-restricted-component-options.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-restricted-component-options.js)
diff --git a/lib/index.js b/lib/index.js
index 7eda5423d..81f7dfc72 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -82,6 +82,7 @@ module.exports = {
'no-ref-as-operand': require('./rules/no-ref-as-operand'),
'no-reserved-component-names': require('./rules/no-reserved-component-names'),
'no-reserved-keys': require('./rules/no-reserved-keys'),
+ 'no-restricted-component-options': require('./rules/no-restricted-component-options'),
'no-restricted-static-attribute': require('./rules/no-restricted-static-attribute'),
'no-restricted-syntax': require('./rules/no-restricted-syntax'),
'no-restricted-v-bind': require('./rules/no-restricted-v-bind'),
diff --git a/lib/rules/no-restricted-component-options.js b/lib/rules/no-restricted-component-options.js
new file mode 100644
index 000000000..6d0de9a8d
--- /dev/null
+++ b/lib/rules/no-restricted-component-options.js
@@ -0,0 +1,215 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const regexp = require('../utils/regexp')
+
+/**
+ * @typedef {object} ParsedOption
+ * @property {Tester} test
+ * @property {string|undefined} [message]
+ */
+/**
+ * @typedef {object} MatchResult
+ * @property {Tester | undefined} [next]
+ * @property {boolean} [wildcard]
+ * @property {string} keyName
+ */
+/**
+ * @typedef { (name: string) => boolean } Matcher
+ * @typedef { (node: Property | SpreadElement) => (MatchResult | null) } Tester
+ */
+
+/**
+ * @param {string} str
+ * @returns {Matcher}
+ */
+function buildMatcher(str) {
+ if (regexp.isRegExp(str)) {
+ const re = regexp.toRegExp(str)
+ return (s) => {
+ re.lastIndex = 0
+ return re.test(s)
+ }
+ }
+ return (s) => s === str
+}
+
+/**
+ * @param {string | string[] | { name: string | string[], message?: string } } option
+ * @returns {ParsedOption}
+ */
+function parseOption(option) {
+ if (typeof option === 'string' || Array.isArray(option)) {
+ return parseOption({
+ name: option
+ })
+ }
+
+ /**
+ * @typedef {object} Step
+ * @property {Matcher} [test]
+ * @property {boolean} [wildcard]
+ */
+
+ /** @type {Step[]} */
+ const steps = []
+ for (const name of Array.isArray(option.name) ? option.name : [option.name]) {
+ if (name === '*') {
+ steps.push({ wildcard: true })
+ } else {
+ steps.push({ test: buildMatcher(name) })
+ }
+ }
+ const message = option.message
+
+ return {
+ test: buildTester(0),
+ message
+ }
+
+ /**
+ * @param {number} index
+ * @returns {Tester}
+ */
+ function buildTester(index) {
+ const { wildcard, test } = steps[index]
+ const next = index + 1
+ const needNext = steps.length > next
+ return (node) => {
+ /** @type {string} */
+ let keyName
+ if (wildcard) {
+ keyName = '*'
+ } else {
+ if (node.type !== 'Property') {
+ return null
+ }
+ const name = utils.getStaticPropertyName(node)
+ if (!name || !test(name)) {
+ return null
+ }
+ keyName = name
+ }
+
+ return {
+ next: needNext ? buildTester(next) : undefined,
+ wildcard,
+ keyName
+ }
+ }
+ }
+}
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'disallow specific component option',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/no-restricted-component-options.html'
+ },
+ fixable: null,
+ schema: {
+ type: 'array',
+ items: {
+ oneOf: [
+ { type: 'string' },
+ {
+ type: 'array',
+ items: {
+ type: 'string'
+ }
+ },
+ {
+ type: 'object',
+ properties: {
+ name: {
+ anyOf: [
+ { type: 'string' },
+ {
+ type: 'array',
+ items: {
+ type: 'string'
+ }
+ }
+ ]
+ },
+ message: { type: 'string', minLength: 1 }
+ },
+ required: ['name'],
+ additionalProperties: false
+ }
+ ]
+ },
+ uniqueItems: true,
+ minItems: 0
+ },
+
+ messages: {
+ // eslint-disable-next-line eslint-plugin/report-message-format
+ restrictedOption: '{{message}}'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ if (!context.options || context.options.length === 0) {
+ return {}
+ }
+ /** @type {ParsedOption[]} */
+ const options = context.options.map(parseOption)
+
+ return utils.defineVueVisitor(context, {
+ onVueObjectEnter(node) {
+ for (const option of options) {
+ verify(node, option.test, option.message)
+ }
+ }
+ })
+
+ /**
+ * @param {ObjectExpression} node
+ * @param {Tester} test
+ * @param {string | undefined} customMessage
+ * @param {string[]} path
+ */
+ function verify(node, test, customMessage, path = []) {
+ for (const prop of node.properties) {
+ const result = test(prop)
+ if (!result) {
+ continue
+ }
+ if (result.next) {
+ if (
+ prop.type !== 'Property' ||
+ prop.value.type !== 'ObjectExpression'
+ ) {
+ continue
+ }
+ verify(prop.value, result.next, customMessage, [
+ ...path,
+ result.keyName
+ ])
+ } else {
+ const message =
+ customMessage || defaultMessage([...path, result.keyName])
+ context.report({
+ node: prop.type === 'Property' ? prop.key : prop,
+ messageId: 'restrictedOption',
+ data: { message }
+ })
+ }
+ }
+ }
+
+ /**
+ * @param {string[]} path
+ */
+ function defaultMessage(path) {
+ return `Using \`${path.join('.')}\` is not allowed.`
+ }
+ }
+}
diff --git a/tests/lib/rules/no-restricted-component-options.js b/tests/lib/rules/no-restricted-component-options.js
new file mode 100644
index 000000000..512eb53c5
--- /dev/null
+++ b/tests/lib/rules/no-restricted-component-options.js
@@ -0,0 +1,317 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/no-restricted-component-options')
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: { ecmaVersion: 2020, sourceType: 'module' }
+})
+
+tester.run('no-restricted-component-options', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [
+ 'init',
+ 'beforeCompile',
+ 'compiled',
+ 'activate',
+ 'ready',
+ '/^(?:at|de)tached$/'
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [['foo', '*', 'baz']]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [['foo', 'bar']]
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [
+ 'init',
+ 'beforeCompile',
+ 'compiled',
+ 'activate',
+ 'ready',
+ '/^(?:at|de)tached$/'
+ ],
+ errors: [
+ {
+ message: 'Using `init` is not allowed.',
+ line: 5,
+ column: 9
+ },
+ {
+ message: 'Using `beforeCompile` is not allowed.',
+ line: 6,
+ column: 9
+ },
+ {
+ message: 'Using `compiled` is not allowed.',
+ line: 7,
+ column: 9
+ },
+ {
+ message: 'Using `activate` is not allowed.',
+ line: 8,
+ column: 9
+ },
+ {
+ message: 'Using `ready` is not allowed.',
+ line: 9,
+ column: 9
+ },
+ {
+ message: 'Using `attached` is not allowed.',
+ line: 10,
+ column: 9
+ },
+ {
+ message: 'Using `detached` is not allowed.',
+ line: 11,
+ column: 9
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [['props', '/.*/', 'twoWay']],
+ errors: [
+ {
+ message: 'Using `props.name.twoWay` is not allowed.',
+ line: 10
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [
+ {
+ name: 'init',
+ message: 'Use "beforeCreate" instead.'
+ },
+ {
+ name: '/^(?:at|de)tached$/',
+ message: '"attached" and "detached" is deprecated.'
+ },
+ {
+ name: ['props', '/.*/', 'twoWay'],
+ message: '"props.*.twoWay" cannot be used.'
+ }
+ ],
+ errors: [
+ {
+ message: '"props.*.twoWay" cannot be used.',
+ line: 10,
+ column: 13
+ },
+ {
+ message: 'Use "beforeCreate" instead.',
+ line: 13,
+ column: 9
+ },
+ {
+ message: '"attached" and "detached" is deprecated.',
+ line: 18,
+ column: 9
+ },
+ {
+ message: '"attached" and "detached" is deprecated.',
+ line: 19,
+ column: 9
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [['props', '*', 'twoWay']],
+ errors: [
+ {
+ message: 'Using `props.*.twoWay` is not allowed.',
+ line: 10
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [['foo', '*']],
+ errors: [
+ {
+ message: 'Using `foo.*` is not allowed.',
+ line: 5
+ }
+ ]
+ }
+ ]
+})