From df9c83bfcf33608a3e27719be715222a1ea55355 Mon Sep 17 00:00:00 2001 From: ota Date: Thu, 14 May 2020 11:28:41 +0900 Subject: [PATCH] Add `vue/return-in-emits-validator` rule --- docs/rules/README.md | 1 + docs/rules/return-in-emits-validator.md | 63 +++++ lib/configs/vue3-essential.js | 1 + lib/index.js | 1 + lib/rules/return-in-emits-validator.js | 109 ++++++++ tests/lib/rules/return-in-emits-validator.js | 275 +++++++++++++++++++ 6 files changed, 450 insertions(+) create mode 100644 docs/rules/return-in-emits-validator.md create mode 100644 lib/rules/return-in-emits-validator.js create mode 100644 tests/lib/rules/return-in-emits-validator.js diff --git a/docs/rules/README.md b/docs/rules/README.md index 11a347738..d022dd67f 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -73,6 +73,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi | [vue/require-v-for-key](./require-v-for-key.md) | require `v-bind:key` with `v-for` directives | | | [vue/require-valid-default-prop](./require-valid-default-prop.md) | enforce props default values to be valid | | | [vue/return-in-computed-property](./return-in-computed-property.md) | enforce that a return statement is present in computed property | | +| [vue/return-in-emits-validator](./return-in-emits-validator.md) | enforce that a return statement is present in emits validator | | | [vue/use-v-on-exact](./use-v-on-exact.md) | enforce usage of `exact` modifier on `v-on` | | | [vue/valid-template-root](./valid-template-root.md) | enforce valid template root | | | [vue/valid-v-bind](./valid-v-bind.md) | enforce valid `v-bind` directives | | diff --git a/docs/rules/return-in-emits-validator.md b/docs/rules/return-in-emits-validator.md new file mode 100644 index 000000000..1122af378 --- /dev/null +++ b/docs/rules/return-in-emits-validator.md @@ -0,0 +1,63 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/return-in-emits-validator +description: enforce that a return statement is present in emits validator +--- +# vue/return-in-emits-validator +> enforce that a return statement is present in emits validator + +- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`. + +## :book: Rule Details + +This rule enforces that a `return` statement is present in `emits` validators. + + + +```vue + +``` + + + +## :wrench: Options + +Nothing. + +## :books: Further reading + +- [Vue RFCs - 0030-emits-option](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0030-emits-option.md) + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/return-in-emits-validator.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/return-in-emits-validator.js) diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js index c76edfdc4..651ff096b 100644 --- a/lib/configs/vue3-essential.js +++ b/lib/configs/vue3-essential.js @@ -41,6 +41,7 @@ module.exports = { 'vue/require-v-for-key': 'error', 'vue/require-valid-default-prop': 'error', 'vue/return-in-computed-property': 'error', + 'vue/return-in-emits-validator': 'error', 'vue/use-v-on-exact': 'error', 'vue/valid-template-root': 'error', 'vue/valid-v-bind': 'error', diff --git a/lib/index.js b/lib/index.js index c4dbcb87c..fa2bc4347 100644 --- a/lib/index.js +++ b/lib/index.js @@ -97,6 +97,7 @@ module.exports = { 'require-v-for-key': require('./rules/require-v-for-key'), 'require-valid-default-prop': require('./rules/require-valid-default-prop'), 'return-in-computed-property': require('./rules/return-in-computed-property'), + 'return-in-emits-validator': require('./rules/return-in-emits-validator'), 'script-indent': require('./rules/script-indent'), 'singleline-html-element-content-newline': require('./rules/singleline-html-element-content-newline'), 'sort-keys': require('./rules/sort-keys'), diff --git a/lib/rules/return-in-emits-validator.js b/lib/rules/return-in-emits-validator.js new file mode 100644 index 000000000..dd5e551f4 --- /dev/null +++ b/lib/rules/return-in-emits-validator.js @@ -0,0 +1,109 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +const utils = require('../utils') + +/** + * @typedef {import('vue-eslint-parser').AST.ESLintExpression} Expression + */ + +/** + * Checks if the given node value is falsy. + * @param {Expression} node The node to check + * @returns {boolean} If `true`, the given node value is falsy. + */ +function isFalsy (node) { + if (node.type === 'Literal') { + if (node.bigint) { + return node.bigint === '0' + } else if (!node.value) { + return true + } + } else if (node.type === 'Identifier') { + if (node.name === 'undefined' || node.name === 'NaN') { + return true + } + } + return false +} +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'enforce that a return statement is present in emits validator', + categories: ['vue3-essential'], + url: 'https://eslint.vuejs.org/rules/return-in-emits-validator.html' + }, + fixable: null, // or "code" or "whitespace" + schema: [] + }, + + create (context) { + const emitsValidators = [] + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + let scopeStack = null + + return Object.assign({}, + utils.defineVueVisitor(context, + { + ObjectExpression (obj, { node: vueNode }) { + if (obj !== vueNode) { + return + } + for (const emits of utils.getComponentEmits(obj)) { + const emitsValue = emits.value + if (!emitsValue) { + continue + } + if (emitsValue.type !== 'FunctionExpression' && emitsValue.type !== 'ArrowFunctionExpression') { + continue + } + emitsValidators.push(emits) + } + }, + ':function' (node) { + scopeStack = { upper: scopeStack, functionNode: node, hasReturnValue: false, possibleOfReturnTrue: false } + }, + ReturnStatement (node) { + if (node.argument) { + scopeStack.hasReturnValue = true + + if (!isFalsy(node.argument)) { + scopeStack.possibleOfReturnTrue = true + } + } + }, + ':function:exit' (node) { + if (!scopeStack.possibleOfReturnTrue) { + const emits = emitsValidators.find(e => e.value === node) + if (emits) { + context.report({ + node, + message: scopeStack.hasReturnValue + ? 'Expected to return a true value in "{{name}}" emits validator.' + : 'Expected to return a boolean value in "{{name}}" emits validator.', + data: { + name: emits.emitName + } + }) + } + } + + scopeStack = scopeStack.upper + } + } + ), + ) + } +} diff --git a/tests/lib/rules/return-in-emits-validator.js b/tests/lib/rules/return-in-emits-validator.js new file mode 100644 index 000000000..8ee6b75e5 --- /dev/null +++ b/tests/lib/rules/return-in-emits-validator.js @@ -0,0 +1,275 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/return-in-emits-validator') + +const RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parser: require.resolve('vue-eslint-parser'), + parserOptions: { ecmaVersion: 2020, sourceType: 'module' } +}) +ruleTester.run('return-in-emits-validator', rule, { + + valid: [ + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + errors: [{ + message: 'Expected to return a boolean value in "foo" emits validator.', + line: 5 + }] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [{ + message: 'Expected to return a boolean value in "foo" emits validator.', + line: 5 + }] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [{ + message: 'Expected to return a boolean value in "foo" emits validator.', + line: 5 + }] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [{ + message: 'Expected to return a boolean value in "foo" emits validator.', + line: 5 + }] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Expected to return a boolean value in "foo" emits validator.', + line: 5 + }, + { + message: 'Expected to return a boolean value in "bar" emits validator.', + line: 7 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [{ + message: 'Expected to return a boolean value in "foo" emits validator.', + line: 5 + }] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [{ + message: 'Expected to return a true value in "foo" emits validator.', + line: 5 + }] + } + ] +})