From bf5829ce5e48ce060aa8197031a617a65315b088 Mon Sep 17 00:00:00 2001 From: czb <80880408+czb3279338858@users.noreply.github.com> Date: Wed, 2 Nov 2022 15:47:34 +0800 Subject: [PATCH] Add new `vue/require-prop-comment` rule (#2019) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * require-prop-comment * add type arg * add jsdoc type * edit rule * npm run update运行结果 * npm run update edit * Modify according to the requirements during consolidation * edit test * delete only one check * delete template * edit md * Lint * Improve docs * Only check last preceding comment * Use message IDs * Rename unlimited → any * Fix docs * edit rules * edit schema type * update readme.md * add rules * add rules Co-authored-by: Flo Edelmann --- docs/rules/README.md | 1 + docs/rules/require-prop-comment.md | 144 +++++++++++++ lib/index.js | 1 + lib/rules/require-prop-comment.js | 123 +++++++++++ tests/lib/rules/require-prop-comment.js | 269 ++++++++++++++++++++++++ 5 files changed, 538 insertions(+) create mode 100644 docs/rules/require-prop-comment.md create mode 100644 lib/rules/require-prop-comment.js create mode 100644 tests/lib/rules/require-prop-comment.js 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') + } + } + ] +})