From c5b688e5350d16b330222f13749ab8b6ead517f8 Mon Sep 17 00:00:00 2001 From: Oscar Barrett Date: Wed, 5 Sep 2018 09:32:34 +0800 Subject: [PATCH] Update: Added generators option to func-names (fixes #9511) (#10697) --- docs/rules/func-names.md | 77 ++++++++++ lib/rules/func-names.js | 97 +++++++++--- tests/lib/rules/func-names.js | 272 ++++++++++++++++++++++++++++++++++ 3 files changed, 426 insertions(+), 20 deletions(-) diff --git a/docs/rules/func-names.md b/docs/rules/func-names.md index 31b70198e29..60a0910e555 100644 --- a/docs/rules/func-names.md +++ b/docs/rules/func-names.md @@ -20,6 +20,15 @@ This rule has a string option: * `"as-needed"` requires function expressions to have a name, if the name cannot be assigned automatically in an ES6 environment * `"never"` disallows named function expressions, except in recursive functions, where a name is needed +This rule has an object option: + +* `"generators": "always" | "as-needed" | "never"` + * `"always"` require named generators + * `"as-needed"` require named generators if the name cannot be assigned automatically in an ES6 environment. + * `"never"` disallow named generators where possible. + +When a value for `generators` is not provided the behavior for generator functions falls back to the base option. + ### always Examples of **incorrect** code for this rule with the default `"always"` option: @@ -100,6 +109,74 @@ Foo.prototype.bar = function() {}; }()) ``` +### generators + +Examples of **incorrect** code for this rule with the `"always", { "generators": "as-needed" }` options: + +```js +/*eslint func-names: ["error", "always", { "generators": "as-needed" }]*/ + +(function*() { + // ... +}()) +``` + +Examples of **correct** code for this rule with the `"always", { "generators": "as-needed" }` options: + +```js +/*eslint func-names: ["error", "always", { "generators": "as-needed" }]*/ + +var foo = function*() {}; +``` + +Examples of **incorrect** code for this rule with the `"always", { "generators": "never" }` options: + +```js +/*eslint func-names: ["error", "always", { "generators": "never" }]*/ + +var foo = bar(function *baz() {}); +``` + +Examples of **correct** code for this rule with the `"always", { "generators": "never" }` options: + +```js +/*eslint func-names: ["error", "always", { "generators": "never" }]*/ + +var foo = bar(function *() {}); +``` + +Examples of **incorrect** code for this rule with the `"as-needed", { "generators": "never" }` options: + +```js +/*eslint func-names: ["error", "as-needed", { "generators": "never" }]*/ + +var foo = bar(function *baz() {}); +``` + +Examples of **correct** code for this rule with the `"as-needed", { "generators": "never" }` options: + +```js +/*eslint func-names: ["error", "as-needed", { "generators": "never" }]*/ + +var foo = bar(function *() {}); +``` + +Examples of **incorrect** code for this rule with the `"never", { "generators": "always" }` options: + +```js +/*eslint func-names: ["error", "never", { "generators": "always" }]*/ + +var foo = bar(function *() {}); +``` + +Examples of **correct** code for this rule with the `"never", { "generators": "always" }` options: + +```js +/*eslint func-names: ["error", "never", { "generators": "always" }]*/ + +var foo = bar(function *baz() {}); +``` + ## Further Reading * [Functions Explained](http://markdaggett.com/blog/2013/02/15/functions-explained/) diff --git a/lib/rules/func-names.js b/lib/rules/func-names.js index a4f5d105d89..31f30291811 100644 --- a/lib/rules/func-names.js +++ b/lib/rules/func-names.js @@ -33,11 +33,31 @@ module.exports = { url: "https://eslint.org/docs/rules/func-names" }, - schema: [ - { - enum: ["always", "as-needed", "never"] - } - ], + schema: { + definitions: { + value: { + enum: [ + "always", + "as-needed", + "never" + ] + } + }, + items: [ + { + $ref: "#/definitions/value" + }, + { + type: "object", + properties: { + generators: { + $ref: "#/definitions/value" + } + }, + additionalProperties: false + } + ] + }, messages: { unnamed: "Unexpected unnamed {{name}}.", named: "Unexpected named {{name}}." @@ -45,8 +65,23 @@ module.exports = { }, create(context) { - const never = context.options[0] === "never"; - const asNeeded = context.options[0] === "as-needed"; + + /** + * Returns the config option for the given node. + * @param {ASTNode} node - A node to get the config for. + * @returns {string} The config option. + */ + function getConfigForNode(node) { + if ( + node.generator && + context.options.length > 1 && + context.options[1].generators + ) { + return context.options[1].generators; + } + + return context.options[0] || "always"; + } /** * Determines whether the current FunctionExpression node is a get, set, or @@ -83,6 +118,32 @@ module.exports = { (parent.type === "AssignmentPattern" && parent.right === node); } + /** + * Reports that an unnamed function should be named + * @param {ASTNode} node - The node to report in the event of an error. + * @returns {void} + */ + function reportUnexpectedUnnamedFunction(node) { + context.report({ + node, + messageId: "unnamed", + data: { name: astUtils.getFunctionNameWithKind(node) } + }); + } + + /** + * Reports that a named function should be unnamed + * @param {ASTNode} node - The node to report in the event of an error. + * @returns {void} + */ + function reportUnexpectedNamedFunction(node) { + context.report({ + node, + messageId: "named", + data: { name: astUtils.getFunctionNameWithKind(node) } + }); + } + return { "FunctionExpression:exit"(node) { @@ -94,23 +155,19 @@ module.exports = { } const hasName = Boolean(node.id && node.id.name); - const name = astUtils.getFunctionNameWithKind(node); + const config = getConfigForNode(node); - if (never) { + if (config === "never") { if (hasName) { - context.report({ - node, - messageId: "named", - data: { name } - }); + reportUnexpectedNamedFunction(node); + } + } else if (config === "as-needed") { + if (!hasName && !hasInferredName(node)) { + reportUnexpectedUnnamedFunction(node); } } else { - if (!hasName && (asNeeded ? !hasInferredName(node) : !isObjectOrClassMethod(node))) { - context.report({ - node, - messageId: "unnamed", - data: { name } - }); + if (!hasName && !isObjectOrClassMethod(node)) { + reportUnexpectedUnnamedFunction(node); } } } diff --git a/tests/lib/rules/func-names.js b/tests/lib/rules/func-names.js index 318ef8a3c6e..0dfc00dd551 100644 --- a/tests/lib/rules/func-names.js +++ b/tests/lib/rules/func-names.js @@ -126,6 +126,129 @@ ruleTester.run("func-names", rule, { code: "({ foo() {} });", options: ["never"], parserOptions: { ecmaVersion: 6 } + }, + + // generators + { + code: "var foo = bar(function *baz() {});", + options: ["always"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = bar(function *baz() {});", + options: ["always", { generators: "always" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = bar(function *baz() {});", + options: ["always", { generators: "as-needed" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = function*() {};", + options: ["always", { generators: "as-needed" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = bar(function *baz() {});", + options: ["as-needed"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = function*() {};", + options: ["as-needed"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = bar(function *baz() {});", + options: ["as-needed", { generators: "always" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = bar(function *baz() {});", + options: ["as-needed", { generators: "as-needed" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = function*() {};", + options: ["as-needed", { generators: "as-needed" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = bar(function *baz() {});", + options: ["never", { generators: "always" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = bar(function *baz() {});", + options: ["never", { generators: "as-needed" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = function*() {};", + options: ["never", { generators: "as-needed" }], + parserOptions: { ecmaVersion: 6 } + }, + + { + code: "var foo = bar(function *() {});", + options: ["never"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = function*() {};", + options: ["never"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "(function*() {}())", + options: ["never"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = bar(function *() {});", + options: ["never", { generators: "never" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = function*() {};", + options: ["never", { generators: "never" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "(function*() {}())", + options: ["never", { generators: "never" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = bar(function *() {});", + options: ["always", { generators: "never" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = function*() {};", + options: ["always", { generators: "never" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "(function*() {}())", + options: ["always", { generators: "never" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = bar(function *() {});", + options: ["as-needed", { generators: "never" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var foo = function*() {};", + options: ["as-needed", { generators: "never" }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "(function*() {}())", + options: ["as-needed", { generators: "never" }], + parserOptions: { ecmaVersion: 6 } } ], invalid: [ @@ -198,6 +321,155 @@ ruleTester.run("func-names", rule, { code: "({foo: function foo() {}})", options: ["never"], errors: [{ messageId: "named", data: { name: "method 'foo'" }, type: "FunctionExpression" }] + }, + + // generators + { + code: "var foo = bar(function *() {});", + options: ["always"], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "var foo = function*() {};", + options: ["always"], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "(function*() {}())", + options: ["always"], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "var foo = bar(function *() {});", + options: ["always", { generators: "always" }], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "var foo = function*() {};", + options: ["always", { generators: "always" }], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "(function*() {}())", + options: ["always", { generators: "always" }], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "var foo = bar(function *() {});", + options: ["always", { generators: "as-needed" }], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "(function*() {}())", + options: ["always", { generators: "as-needed" }], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "var foo = bar(function *() {});", + options: ["as-needed"], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "(function*() {}())", + options: ["as-needed"], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "var foo = bar(function *() {});", + options: ["as-needed", { generators: "always" }], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "var foo = function*() {};", + options: ["as-needed", { generators: "always" }], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "(function*() {}())", + options: ["as-needed", { generators: "always" }], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "var foo = bar(function *() {});", + options: ["as-needed", { generators: "as-needed" }], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "(function*() {}())", + options: ["as-needed", { generators: "as-needed" }], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "var foo = bar(function *() {});", + options: ["never", { generators: "always" }], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "var foo = function*() {};", + options: ["never", { generators: "always" }], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "(function*() {}())", + options: ["never", { generators: "always" }], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "var foo = bar(function *() {});", + options: ["never", { generators: "as-needed" }], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + { + code: "(function*() {}())", + options: ["never", { generators: "as-needed" }], + parserOptions: { ecmaVersion: 6 }, + errors: [unnamedError] + }, + + { + code: "var foo = bar(function *baz() {});", + options: ["never"], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + message: "Unexpected named generator function 'baz'." + }] + }, + { + code: "var foo = bar(function *baz() {});", + options: ["never", { generators: "never" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ messageId: "named", data: { name: "generator function 'baz'" }, type: "FunctionExpression" }] + }, + { + code: "var foo = bar(function *baz() {});", + options: ["always", { generators: "never" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ messageId: "named", data: { name: "generator function 'baz'" }, type: "FunctionExpression" }] + }, + { + code: "var foo = bar(function *baz() {});", + options: ["as-needed", { generators: "never" }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ messageId: "named", data: { name: "generator function 'baz'" }, type: "FunctionExpression" }] } ] });