Skip to content

Commit

Permalink
Update: Added generators option to func-names (fixes #9511) (#10697)
Browse files Browse the repository at this point in the history
  • Loading branch information
OscarBarrett authored and not-an-aardvark committed Sep 5, 2018
1 parent 7da36d5 commit c5b688e
Show file tree
Hide file tree
Showing 3 changed files with 426 additions and 20 deletions.
77 changes: 77 additions & 0 deletions docs/rules/func-names.md
Expand Up @@ -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:
Expand Down Expand Up @@ -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/)
Expand Down
97 changes: 77 additions & 20 deletions lib/rules/func-names.js
Expand Up @@ -33,20 +33,55 @@ 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}}."
}
},

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
Expand Down Expand Up @@ -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) {

Expand All @@ -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);
}
}
}
Expand Down

0 comments on commit c5b688e

Please sign in to comment.