From 9989d2dc6af4eb084a88e3f28c16ab9f56c4525b Mon Sep 17 00:00:00 2001 From: Severin Date: Tue, 30 Jun 2020 09:46:25 +0200 Subject: [PATCH] Add `no-object-as-default-parameter` rule (#633) Co-authored-by: Sindre Sorhus Co-authored-by: fisker --- docs/rules/no-object-as-default-parameter.md | 29 +++ index.js | 1 + readme.md | 2 + rules/no-object-as-default-parameter.js | 36 ++++ test/no-object-as-default-parameter.js | 177 +++++++++++++++++++ 5 files changed, 245 insertions(+) create mode 100644 docs/rules/no-object-as-default-parameter.md create mode 100644 rules/no-object-as-default-parameter.js create mode 100644 test/no-object-as-default-parameter.js diff --git a/docs/rules/no-object-as-default-parameter.md b/docs/rules/no-object-as-default-parameter.md new file mode 100644 index 0000000000..d1c66b2a03 --- /dev/null +++ b/docs/rules/no-object-as-default-parameter.md @@ -0,0 +1,29 @@ +# Disallow the use of objects as default parameters + +Default parameters should not be passed to a function through an object literal. The `foo = {a: false}` parameter works fine if only used with one option. As soon as additional options are added, you risk replacing the whole `foo = {a: false, b: true}` object when passing only one option: `{a: true}`. For this reason, object destructuring should be used instead. + + +## Fail + +```js +const abc = (foo = {a: false}) => {}; +``` + +```js +const abc = (foo = {a: false, b: 123}) => {}; +``` + + +## Pass + +```js +const abc = (foo = {}) => {}; +``` + +```js +const abc = (foo = false) => {}; +``` + +```js +const foo = ({a = false, b = 123}) => {}; +``` diff --git a/index.js b/index.js index 21ef42c3f6..1c0b88e4b1 100644 --- a/index.js +++ b/index.js @@ -39,6 +39,7 @@ module.exports = { 'unicorn/no-nested-ternary': 'error', 'unicorn/no-new-buffer': 'error', 'unicorn/no-null': 'error', + 'unicorn/no-object-as-default-parameter': 'error', 'unicorn/no-process-exit': 'error', 'unicorn/no-reduce': 'error', 'unicorn/no-unreadable-array-destructuring': 'error', diff --git a/readme.md b/readme.md index 2a0503dd0d..37d1a14fb1 100644 --- a/readme.md +++ b/readme.md @@ -55,6 +55,7 @@ Configure it in `package.json`. "unicorn/no-nested-ternary": "error", "unicorn/no-new-buffer": "error", "unicorn/no-null": "error", + "unicorn/no-object-as-default-parameter": "error", "unicorn/no-process-exit": "error", "unicorn/no-reduce": "error", "unicorn/no-unreadable-array-destructuring": "error", @@ -116,6 +117,7 @@ Configure it in `package.json`. - [no-nested-ternary](docs/rules/no-nested-ternary.md) - Disallow nested ternary expressions. *(partly fixable)* - [no-new-buffer](docs/rules/no-new-buffer.md) - Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`. *(fixable)* - [no-null](docs/rules/no-null.md) - Disallow the use of the `null` literal. +- [no-object-as-default-parameter](docs/rules/no-object-as-default-parameter.md) - Disallow the use of objects as default parameters. - [no-process-exit](docs/rules/no-process-exit.md) - Disallow `process.exit()`. - [no-reduce](docs/rules/no-reduce.md) - Disallow `Array#reduce()` and `Array#reduceRight()`. - [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) - Disallow unreadable array destructuring. diff --git a/rules/no-object-as-default-parameter.js b/rules/no-object-as-default-parameter.js new file mode 100644 index 0000000000..d0a90e6ebc --- /dev/null +++ b/rules/no-object-as-default-parameter.js @@ -0,0 +1,36 @@ +'use strict'; +const getDocumentationUrl = require('./utils/get-documentation-url'); + +const MESSAGE_ID = 'noObjectAsDefaultParameter'; + +const objectParameterSelector = [ + ':function > AssignmentPattern.params', + '[left.type="Identifier"]', + '[right.type="ObjectExpression"]', + '[right.properties.length>0]' +].join(''); + +const create = context => { + return { + [objectParameterSelector]: node => { + context.report({ + node: node.left, + messageId: MESSAGE_ID, + data: {parameter: node.left.name} + }); + } + }; +}; + +module.exports = { + create, + meta: { + type: 'problem', + docs: { + url: getDocumentationUrl(__filename) + }, + messages: { + [MESSAGE_ID]: 'Do not use an object literal as default for parameter `{{parameter}}`.' + } + } +}; diff --git a/test/no-object-as-default-parameter.js b/test/no-object-as-default-parameter.js new file mode 100644 index 0000000000..7f86354a17 --- /dev/null +++ b/test/no-object-as-default-parameter.js @@ -0,0 +1,177 @@ +import test from 'ava'; +import avaRuleTester from 'eslint-ava-rule-tester'; +import {outdent} from 'outdent'; +import rule from '../rules/no-object-as-default-parameter'; + +const ruleTester = avaRuleTester(test, { + parserOptions: { + ecmaVersion: 2020 + } +}); + +const error = { + messageId: 'noObjectAsDefaultParameter', + data: {parameter: 'foo'} +}; + +ruleTester.run('no-object-as-default-parameter', rule, { + valid: [ + 'const abc = {};', + 'const abc = {foo: 123};', + 'function abc(foo) {}', + 'function abc(foo = null) {}', + 'function abc(foo = undefined) {}', + 'function abc(foo = 123) {}', + 'function abc(foo = true) {}', + 'function abc(foo = "bar") {}', + 'function abc(foo = 123, bar = "foo") {}', + 'function abc(foo = {}) {}', + 'function abc({foo = 123} = {}) {}', + '(function abc() {})(foo = {a: 123})', + 'const abc = foo => {};', + 'const abc = (foo = null) => {};', + 'const abc = (foo = undefined) => {};', + 'const abc = (foo = 123) => {};', + 'const abc = (foo = true) => {};', + 'const abc = (foo = "bar") => {};', + 'const abc = (foo = 123, bar = "foo") => {};', + 'const abc = (foo = {}) => {};', + 'const abc = ({a = true, b = "foo"}) => {};', + 'const abc = function(foo = 123) {}', + 'const {abc = {foo: 123}} = bar;', + 'const {abc = {null: "baz"}} = bar;', + 'const {abc = {foo: undefined}} = undefined;', + 'const abc = ([{foo = false, bar = 123}]) => {};', + 'const abc = ({foo = {a: 123}}) => {};', + 'const abc = ([foo = {a: 123}]) => {};', + 'const abc = ({foo: bar = {a: 123}}) => {};', + 'const abc = () => (foo = {a: 123});', + outdent` + class A { + [foo = {a: 123}]() {} + } + `, + outdent` + class A extends (foo = {a: 123}) { + a() {} + } + ` + ], + invalid: [ + { + code: 'function abc(foo = {a: 123}) {}', + errors: [error] + }, + { + code: 'async function * abc(foo = {a: 123}) {}', + errors: [error] + }, + { + code: 'function abc(foo = {a: false}) {}', + errors: [error] + }, + { + code: 'function abc(foo = {a: "bar"}) {}', + errors: [error] + }, + { + code: 'function abc(foo = {a: "bar", b: {c: true}}) {}', + errors: [error] + }, + { + code: 'const abc = (foo = {a: false}) => {};', + errors: [error] + }, + { + code: 'const abc = (foo = {a: 123, b: false}) => {};', + errors: [error] + }, + { + code: 'const abc = (foo = {a: false, b: 1, c: "test", d: null}) => {};', + errors: [error] + }, + { + code: 'const abc = function(foo = {a: 123}) {}', + errors: [error] + }, + { + code: outdent` + class A { + abc(foo = {a: 123}) {} + } + `, + errors: [error] + }, + { + code: outdent` + class A { + constructor(foo = {a: 123}) {} + } + `, + errors: [error] + }, + { + code: outdent` + class A { + set abc(foo = {a: 123}) {} + } + `, + errors: [error] + }, + { + code: outdent` + class A { + static abc(foo = {a: 123}) {} + } + `, + errors: [error] + }, + { + code: outdent` + class A { + * abc(foo = {a: 123}) {} + } + `, + errors: [error] + }, + { + code: outdent` + class A { + static async * abc(foo = {a: 123}) {} + } + `, + errors: [error] + }, + { + code: outdent` + class A { + [foo = {a: 123}](foo = {a: 123}) {} + } + `, + errors: [error] + }, + { + code: outdent` + const A = class { + abc(foo = {a: 123}) {} + } + `, + errors: [error] + }, + { + code: outdent` + object = { + abc(foo = {a: 123}) {} + }; + `, + errors: [error] + }, + // Actual message + { + code: 'function abc(foo = {a: 123}) {}', + errors: [{ + message: 'Do not use an object literal as default for parameter `foo`.' + }] + } + ] +});