From 0313441d016c8aa0674c135f9da67a676e766ec5 Mon Sep 17 00:00:00 2001 From: Chiawen Chen Date: Sun, 8 Sep 2019 00:06:44 +0800 Subject: [PATCH] New: add rule default-param-last (fixes #11361) (#12188) * New: add rule default-param-last (fixes #11361) * Chore: fix typos * Chore: add test cases * Apply suggestion: add column for a test case * Update: change error message * Docs: add example in opening section * Add test cases about parameter destructuring --- docs/rules/default-param-last.md | 35 +++++++++ lib/rules/default-param-last.js | 61 +++++++++++++++ lib/rules/index.js | 1 + tests/lib/rules/default-param-last.js | 106 ++++++++++++++++++++++++++ tools/rule-types.json | 1 + 5 files changed, 204 insertions(+) create mode 100644 docs/rules/default-param-last.md create mode 100644 lib/rules/default-param-last.js create mode 100644 tests/lib/rules/default-param-last.js diff --git a/docs/rules/default-param-last.md b/docs/rules/default-param-last.md new file mode 100644 index 00000000000..97bba70fa8c --- /dev/null +++ b/docs/rules/default-param-last.md @@ -0,0 +1,35 @@ +# enforce default parameters to be last (default-param-last) + +Putting default parameter at last allows function calls to omit optional tail arguments. + +```js +// Correct: optional argument can be omitted +function createUser(id, isAdmin = false) {} +createUser("tabby") + +// Incorrect: optional argument can **not** be omitted +function createUser(isAdmin = false, id) {} +createUser(undefined, "tabby") +``` + +## Rule Details + +This rule enforces default parameters to be the last of parameters. + +Examples of **incorrect** code for this rule: + +```js +/* eslint default-param-last: ["error"] */ + +function f(a = 0, b) {} + +function f(a, b = 0, c) {} +``` + +Examples of **correct** code for this rule: + +```js +/* eslint default-param-last: ["error"] */ + +function f(a, b = 0) {} +``` diff --git a/lib/rules/default-param-last.js b/lib/rules/default-param-last.js new file mode 100644 index 00000000000..ee73aaf3215 --- /dev/null +++ b/lib/rules/default-param-last.js @@ -0,0 +1,61 @@ +/** + * @fileoverview enforce default parameters to be last + * @author Chiawen Chen + */ + +"use strict"; + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce default parameters to be last", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/default-param-last" + }, + + schema: [], + + messages: { + shouldBeLast: "Default parameters should be last." + } + }, + + create(context) { + + /** + * @param {ASTNode} node function node + * @returns {void} + */ + function handleFunction(node) { + let hasSeenPlainParam = false; + + for (let i = node.params.length - 1; i >= 0; i -= 1) { + const param = node.params[i]; + + if ( + param.type !== "AssignmentPattern" && + param.type !== "RestElement" + ) { + hasSeenPlainParam = true; + continue; + } + + if (hasSeenPlainParam && param.type === "AssignmentPattern") { + context.report({ + node: param, + messageId: "shouldBeLast" + }); + } + } + } + + return { + FunctionDeclaration: handleFunction, + FunctionExpression: handleFunction, + ArrowFunctionExpression: handleFunction + }; + } +}; diff --git a/lib/rules/index.js b/lib/rules/index.js index c42ae41d6cb..51d224d219f 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -37,6 +37,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "constructor-super": () => require("./constructor-super"), curly: () => require("./curly"), "default-case": () => require("./default-case"), + "default-param-last": () => require("./default-param-last"), "dot-location": () => require("./dot-location"), "dot-notation": () => require("./dot-notation"), "eol-last": () => require("./eol-last"), diff --git a/tests/lib/rules/default-param-last.js b/tests/lib/rules/default-param-last.js new file mode 100644 index 00000000000..05321f5e265 --- /dev/null +++ b/tests/lib/rules/default-param-last.js @@ -0,0 +1,106 @@ +/** + * @fileoverview Test file for default-param-last + * @author Chiawen Chen + */ +"use strict"; + +const rule = require("../../../lib/rules/default-param-last"); +const { RuleTester } = require("../../../lib/rule-tester"); + +const SHOULD_BE_LAST = "shouldBeLast"; + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 8 } +}); + +const cannedError = { + messageId: SHOULD_BE_LAST, + type: "AssignmentPattern" +}; + +ruleTester.run("default-param-last", rule, { + valid: [ + "function f() {}", + "function f(a) {}", + "function f(a = 5) {}", + "function f(a, b) {}", + "function f(a, b = 5) {}", + "function f(a, b = 5, c = 5) {}", + "function f(a, b = 5, ...c) {}", + "const f = () => {}", + "const f = (a) => {}", + "const f = (a = 5) => {}", + "const f = function f() {}", + "const f = function f(a) {}", + "const f = function f(a = 5) {}" + ], + invalid: [ + { + code: "function f(a = 5, b) {}", + errors: [ + { + messageId: SHOULD_BE_LAST, + column: 12, + endColumn: 17 + } + ] + }, + { + code: "function f(a = 5, b = 6, c) {}", + errors: [ + { + messageId: SHOULD_BE_LAST, + column: 12, + endColumn: 17 + }, + { + messageId: SHOULD_BE_LAST, + column: 19, + endColumn: 24 + } + ] + }, + { + code: "function f (a = 5, b, c = 6, d) {}", + errors: [cannedError, cannedError] + }, + { + code: "function f(a = 5, b, c = 5) {}", + errors: [ + { + messageId: SHOULD_BE_LAST, + column: 12, + endColumn: 17 + } + ] + }, + { + code: "const f = (a = 5, b, ...c) => {}", + errors: [cannedError] + }, + { + code: "const f = function f (a, b = 5, c) {}", + errors: [cannedError] + }, + { + code: "const f = (a = 5, { b }) => {}", + errors: [cannedError] + }, + { + code: "const f = ({ a } = {}, b) => {}", + errors: [cannedError] + }, + { + code: "const f = ({ a, b } = { a: 1, b: 2 }, c) => {}", + errors: [cannedError] + }, + { + code: "const f = ([a] = [], b) => {}", + errors: [cannedError] + }, + { + code: "const f = ([a, b] = [1, 2], c) => {}", + errors: [cannedError] + } + ] +}); diff --git a/tools/rule-types.json b/tools/rule-types.json index eef001a20d5..74cae9391cb 100644 --- a/tools/rule-types.json +++ b/tools/rule-types.json @@ -24,6 +24,7 @@ "constructor-super": "problem", "curly": "suggestion", "default-case": "suggestion", + "default-param-last": "suggestion", "dot-location": "layout", "dot-notation": "suggestion", "eol-last": "layout",