diff --git a/docs/rules/README.md b/docs/rules/README.md
index f704c1a61..9ca817e30 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -260,6 +260,7 @@ For example:
| [vue/require-emit-validator](./require-emit-validator.md) | require type definitions in emits | :bulb: | :hammer: |
| [vue/require-expose](./require-expose.md) | require declare public properties using `expose` | :bulb: | :hammer: |
| [vue/require-name-property](./require-name-property.md) | require a name property in Vue components | | :hammer: |
+| [vue/require-prop-comment](./require-prop-comment.md) | require props to have a comment | | :hammer: |
| [vue/script-indent](./script-indent.md) | enforce consistent indentation in `
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/require-prop-comment": ["error", {
+ "type": "JSDoc"
+ }]
+}
+```
+
+- `type` ... Type of comment. Default is `"JSDoc"`
+ - `"JSDoc"` ... Only JSDoc comment are allowed.
+ - `"line"` ... Only line comment are allowed.
+ - `"block"` ... Only block comment are allowed.
+ - `"any"` ... All comment types are allowed.
+
+### `"type": "block"`
+
+
+
+```vue
+
+```
+
+
+
+### `"type": "line"`
+
+
+
+```vue
+
+```
+
+
+
+### `"type": "any"`
+
+
+
+```vue
+
+```
+
+
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-prop-comment.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-prop-comment.js)
diff --git a/lib/index.js b/lib/index.js
index 6f5452cc8..2d14cd404 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -176,6 +176,7 @@ module.exports = {
'require-explicit-emits': require('./rules/require-explicit-emits'),
'require-expose': require('./rules/require-expose'),
'require-name-property': require('./rules/require-name-property'),
+ 'require-prop-comment': require('./rules/require-prop-comment'),
'require-prop-type-constructor': require('./rules/require-prop-type-constructor'),
'require-prop-types': require('./rules/require-prop-types'),
'require-render-return': require('./rules/require-render-return'),
diff --git a/lib/rules/require-prop-comment.js b/lib/rules/require-prop-comment.js
new file mode 100644
index 000000000..9c8ef994d
--- /dev/null
+++ b/lib/rules/require-prop-comment.js
@@ -0,0 +1,123 @@
+/**
+ * @author CZB
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'require props to have a comment',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/require-prop-comment.html'
+ },
+ fixable: null,
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ type: { enum: ['JSDoc', 'line', 'block', 'any'] }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ requireAnyComment: 'The "{{name}}" property should have a comment.',
+ requireLineComment: 'The "{{name}}" property should have a line comment.',
+ requireBlockComment:
+ 'The "{{name}}" property should have a block comment.',
+ requireJSDocComment:
+ 'The "{{name}}" property should have a JSDoc comment.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ /** @type {{type?: "JSDoc" | "line" | "block" | "any"}|undefined} */
+ const schema = context.options[0]
+ const type = (schema && schema.type) || 'JSDoc'
+
+ const sourceCode = context.getSourceCode()
+
+ /** @param {Comment | undefined} comment */
+ const verifyBlock = (comment) =>
+ comment && comment.type === 'Block' && comment.value.charAt(0) !== '*'
+ ? undefined
+ : 'requireBlockComment'
+
+ /** @param {Comment | undefined} comment */
+ const verifyLine = (comment) =>
+ comment && comment.type === 'Line' ? undefined : 'requireLineComment'
+
+ /** @param {Comment | undefined} comment */
+ const verifyAny = (comment) => (comment ? undefined : 'requireAnyComment')
+
+ /** @param {Comment | undefined} comment */
+ const verifyJSDoc = (comment) =>
+ comment && comment.type === 'Block' && comment.value.charAt(0) === '*'
+ ? undefined
+ : 'requireJSDocComment'
+
+ /**
+ * @param {import('../utils').ComponentProp[]} props
+ */
+ function verifyProps(props) {
+ for (const prop of props) {
+ if (!prop.propName) {
+ continue
+ }
+
+ const precedingComments = sourceCode.getCommentsBefore(prop.node)
+ const lastPrecedingComment =
+ precedingComments.length > 0
+ ? precedingComments[precedingComments.length - 1]
+ : undefined
+
+ /** @type {string|undefined} */
+ let messageId
+
+ switch (type) {
+ case 'block':
+ messageId = verifyBlock(lastPrecedingComment)
+ break
+ case 'line':
+ messageId = verifyLine(lastPrecedingComment)
+ break
+ case 'any':
+ messageId = verifyAny(lastPrecedingComment)
+ break
+ default:
+ messageId = verifyJSDoc(lastPrecedingComment)
+ break
+ }
+
+ if (!messageId) {
+ continue
+ }
+
+ context.report({
+ node: prop.node,
+ messageId,
+ data: {
+ name: prop.propName
+ }
+ })
+ }
+ }
+
+ return utils.compositingVisitors(
+ utils.defineScriptSetupVisitor(context, {
+ onDefinePropsEnter(_node, props) {
+ verifyProps(props)
+ }
+ }),
+ utils.defineVueVisitor(context, {
+ onVueObjectEnter(node) {
+ verifyProps(utils.getComponentPropsFromOptions(node))
+ }
+ })
+ )
+ }
+}
diff --git a/tests/lib/rules/require-prop-comment.js b/tests/lib/rules/require-prop-comment.js
new file mode 100644
index 000000000..f72d6905b
--- /dev/null
+++ b/tests/lib/rules/require-prop-comment.js
@@ -0,0 +1,269 @@
+/**
+ * @author CZB
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/require-prop-comment')
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: 'module'
+ }
+})
+
+tester.run('require-prop-comment', rule, {
+ valid: [
+ {
+ code: `
+
+ `
+ },
+ {
+ code: `
+
+ `,
+ options: [{ type: 'block' }]
+ },
+ {
+ code: `
+
+ `,
+ options: [{ type: 'line' }]
+ },
+ {
+ code: `
+
+ `,
+ options: [{ type: 'any' }]
+ },
+ {
+ code: `
+
+ `
+ },
+ {
+ code: `
+
+ `,
+ parserOptions: {
+ parser: require.resolve('@typescript-eslint/parser')
+ }
+ }
+ ],
+ invalid: [
+ {
+ code: `
+ export default defineComponent({
+ props: {
+ // line comment
+ b: Number,
+
+ /* block comment */
+ c: Number,
+
+ d: Number,
+ }
+ })
+ `,
+ errors: [
+ {
+ line: 5,
+ column: 11,
+ message: `The "b" property should have a JSDoc comment.`
+ },
+ {
+ line: 8,
+ column: 11,
+ message: `The "c" property should have a JSDoc comment.`
+ },
+ {
+ line: 10,
+ column: 11,
+ message: `The "d" property should have a JSDoc comment.`
+ }
+ ]
+ },
+ {
+ code: `
+
+ `,
+ options: [{ type: 'block' }],
+ errors: [
+ {
+ line: 5,
+ column: 9,
+ message: 'The "b" property should have a block comment.'
+ },
+ {
+ line: 8,
+ column: 9,
+ message: 'The "c" property should have a block comment.'
+ },
+ {
+ line: 10,
+ column: 9,
+ message: 'The "d" property should have a block comment.'
+ }
+ ]
+ },
+ {
+ code: `
+
+ `,
+ options: [{ type: 'line' }],
+ errors: [
+ {
+ line: 5,
+ column: 9,
+ message: 'The "b" property should have a line comment.'
+ },
+ {
+ line: 8,
+ column: 9,
+ message: 'The "c" property should have a line comment.'
+ },
+ {
+ line: 10,
+ column: 9,
+ message: 'The "d" property should have a line comment.'
+ }
+ ]
+ },
+ {
+ code: `
+
+ `,
+ options: [{ type: 'any' }],
+ errors: [
+ {
+ line: 4,
+ column: 9,
+ message: `The "d" property should have a comment.`
+ }
+ ]
+ },
+ {
+ code: `
+
+ `,
+ errors: [
+ {
+ line: 5,
+ column: 11,
+ message: 'The "a" property should have a JSDoc comment.'
+ }
+ ]
+ },
+ {
+ code: `
+ new Vue({
+ props: {
+ a: Number
+ }
+ })
+ `,
+ errors: [
+ {
+ line: 4,
+ column: 11,
+ message: 'The "a" property should have a JSDoc comment.'
+ }
+ ]
+ },
+ {
+ code: `
+
+ `,
+ errors: [
+ {
+ line: 4,
+ column: 9,
+ message: 'The "a" property should have a JSDoc comment.'
+ }
+ ],
+ parserOptions: {
+ parser: require.resolve('@typescript-eslint/parser')
+ }
+ }
+ ]
+})