From e8ff837c7dd9d9b5f6cdc5b5a412929e49d0e848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sajn=C3=B3g?= Date: Sun, 6 Aug 2017 17:07:07 +0200 Subject: [PATCH 1/2] Add "require-default-prop" rule --- docs/rules/require-default-prop.md | 45 ++++++++++++ lib/rules/require-default-prop.js | 98 +++++++++++++++++++++++++ tests/lib/rules/require-default-prop.js | 84 +++++++++++++++++++++ 3 files changed, 227 insertions(+) create mode 100644 docs/rules/require-default-prop.md create mode 100644 lib/rules/require-default-prop.js create mode 100644 tests/lib/rules/require-default-prop.js diff --git a/docs/rules/require-default-prop.md b/docs/rules/require-default-prop.md new file mode 100644 index 000000000..e7b9446ac --- /dev/null +++ b/docs/rules/require-default-prop.md @@ -0,0 +1,45 @@ +# Require default value for props (require-default-prop) + +This rule requires default value to be set for each props that are not marked as `required`. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```js +export default { + props: { + a: Number, + b: [Number, String], + c: { + type: Number + }, + d: { + type: Number, + required: false + } + } +} +``` + +Examples of **correct** code for this rule: + +```js +export default { + props: { + a: { + type: Number, + required: true + }, + b: { + type: Number, + default: 0 + }, + c: { + type: Number, + default: 0, + required: false + } + } +} +``` diff --git a/lib/rules/require-default-prop.js b/lib/rules/require-default-prop.js new file mode 100644 index 000000000..952478678 --- /dev/null +++ b/lib/rules/require-default-prop.js @@ -0,0 +1,98 @@ +/** + * @fileoverview Require default value for props + * @author Michał Sajnóg (http://github.com/michalsnik) + */ +'use strict' + +const utils = require('../utils') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'Require default value for props', + category: 'Best Practices', + recommended: false + }, + fixable: null, // or "code" or "whitespace" + schema: [] + }, + + create: function (context) { + // ---------------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------------- + + /** + * Checks if the passed prop is required + * @param {Property} prop - Property AST node for a single prop + * @return {boolean} + */ + function propIsRequired (prop) { + const propRequiredNode = prop.value.properties + .find(p => + p.key.name === 'required' && + p.value.type === 'Literal' && + p.value.value === true + ) + + return Boolean(propRequiredNode) + } + + /** + * Checks if the passed prop has a defualt value + * @param {Property} prop - Property AST node for a single prop + * @return {boolean} + */ + function propHasDefault (prop) { + const propDefaultNode = prop.value.properties + .find(p => p.key.name === 'default') + + return Boolean(propDefaultNode) + } + + /** + * Finds all props that don't have a default value set + * @param {Property} propsNode - Vue component's "props" node + * @return {boolean} + */ + function findPropsWithoutDefaultValue (propsNode) { + return propsNode.value.properties + .filter(prop => { + if (prop.value.type !== 'ObjectExpression') return true + + return !propIsRequired(prop) && !propHasDefault(prop) + }) + } + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + return utils.executeOnVue(context, (obj) => { + const propsNode = obj.properties + .find(p => + p.type === 'Property' && + p.key.type === 'Identifier' && + p.key.name === 'props' + ) + + if (!propsNode || propsNode.value.type !== 'ObjectExpression') return + + const propsWithoutDefault = findPropsWithoutDefaultValue(propsNode) + + propsWithoutDefault.forEach(prop => { + context.report({ + node: prop, + message: `Prop '{{propName}}' requires default value to be set.`, + data: { + propName: prop.key.name + } + }) + }) + }) + } +} diff --git a/tests/lib/rules/require-default-prop.js b/tests/lib/rules/require-default-prop.js new file mode 100644 index 000000000..4a8211a89 --- /dev/null +++ b/tests/lib/rules/require-default-prop.js @@ -0,0 +1,84 @@ +/** + * @fileoverview Require default value for props + * @author Michał Sajnóg (http://github.com/michalsnik) + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/require-default-prop') +const RuleTester = require('eslint').RuleTester +const parserOptions = { ecmaVersion: 6, sourceType: 'module' } + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester() +ruleTester.run('require-default-prop', rule, { + + valid: [ + { + filename: 'test.vue', + code: ` + export default { + props: { + a: { + type: Number, + required: true + }, + b: { + type: Number, + default: 0 + }, + c: { + type: Number, + default: 0, + required: false + }, + // eslint-disable-next-line require-default-prop + d: Number + } + } + `, + parserOptions + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + export default { + props: { + a: Number, + b: [Number, String], + c: { + type: Number + }, + d: { + type: Number, + required: false + } + } + } + `, + parserOptions, + errors: [{ + message: `Prop 'a' requires default value to be set.`, + line: 4 + }, { + message: `Prop 'b' requires default value to be set.`, + line: 5 + }, { + message: `Prop 'c' requires default value to be set.`, + line: 6 + }, { + message: `Prop 'd' requires default value to be set.`, + line: 9 + }] + } + ] +}) From 624e8cd61f717cafee12237f2ebf28e528e310f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sajn=C3=B3g?= Date: Tue, 15 Aug 2017 11:57:54 +0200 Subject: [PATCH 2/2] Handle spread operator cases --- lib/rules/require-default-prop.js | 11 ++++++++--- tests/lib/rules/require-default-prop.js | 26 ++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/lib/rules/require-default-prop.js b/lib/rules/require-default-prop.js index 952478678..b3010d4c9 100644 --- a/lib/rules/require-default-prop.js +++ b/lib/rules/require-default-prop.js @@ -34,6 +34,7 @@ module.exports = { function propIsRequired (prop) { const propRequiredNode = prop.value.properties .find(p => + p.type === 'Property' && p.key.name === 'required' && p.value.type === 'Literal' && p.value.value === true @@ -61,8 +62,11 @@ module.exports = { */ function findPropsWithoutDefaultValue (propsNode) { return propsNode.value.properties + .filter(prop => prop.type === 'Property') .filter(prop => { - if (prop.value.type !== 'ObjectExpression') return true + if (prop.value.type !== 'ObjectExpression') { + return true + } return !propIsRequired(prop) && !propHasDefault(prop) }) @@ -77,10 +81,11 @@ module.exports = { .find(p => p.type === 'Property' && p.key.type === 'Identifier' && - p.key.name === 'props' + p.key.name === 'props' && + p.value.type === 'ObjectExpression' ) - if (!propsNode || propsNode.value.type !== 'ObjectExpression') return + if (!propsNode) return const propsWithoutDefault = findPropsWithoutDefaultValue(propsNode) diff --git a/tests/lib/rules/require-default-prop.js b/tests/lib/rules/require-default-prop.js index 4a8211a89..153e80e1b 100644 --- a/tests/lib/rules/require-default-prop.js +++ b/tests/lib/rules/require-default-prop.js @@ -10,7 +10,11 @@ const rule = require('../../../lib/rules/require-default-prop') const RuleTester = require('eslint').RuleTester -const parserOptions = { ecmaVersion: 6, sourceType: 'module' } +const parserOptions = { + ecmaVersion: 6, + ecmaFeatures: { experimentalObjectRestSpread: true }, + sourceType: 'module' +} // ------------------------------------------------------------------------------ // Tests @@ -44,6 +48,26 @@ ruleTester.run('require-default-prop', rule, { } `, parserOptions + }, + { + filename: 'test.vue', + code: ` + export default { + props: { + ...x, + a: { + ...y, + type: Number, + required: true + }, + b: { + type: Number, + default: 0 + } + } + } + `, + parserOptions } ],