Skip to content

Commit

Permalink
Update: Added generators option to func-names (fixes eslint#9511)
Browse files Browse the repository at this point in the history
  • Loading branch information
OscarBarrett committed Jul 29, 2018
1 parent 5984820 commit 109faf2
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 15 deletions.
56 changes: 56 additions & 0 deletions docs/rules/func-names.md
Expand Up @@ -20,6 +20,12 @@ 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": true (default) | false`
* `true`: require generators to follow the func-names rule. `"always"` or `"as-needed"` will require named generator functions, `"never"` will allow unnamed generator functions.
* `false`: require generators to not follow the func-names rule. `"always"` or `"as-needed"` will allow unnamed generator functions, `"never"` will require named generator functions.

### always

Examples of **incorrect** code for this rule with the default `"always"` option:
Expand Down Expand Up @@ -100,6 +106,56 @@ Foo.prototype.bar = function() {};
}())
```

### generators

Examples of **incorrect** code for this rule with the `"always", { "generators": false }` options:

```js
/*eslint func-names: ["error", "always", { "generators": false }]*/

var foo = bar(function *baz() {});
```

Examples of **correct** code for this rule with the `"always", { "generators": false }` options:

```js
/*eslint func-names: ["error", "always", { "generators": false }]*/

var foo = bar(function *() {});
```

Examples of **incorrect** code for this rule with the `"as-needed", { "generators": false }` options:

```js
/*eslint func-names: ["error", "as-needed", { "generators": false }]*/

var foo = bar(function *baz() {});
```

Examples of **correct** code for this rule with the `"as-needed", { "generators": false }` options:

```js
/*eslint func-names: ["error", "as-needed", { "generators": false }]*/

var foo = bar(function *() {});
```

Examples of **incorrect** code for this rule with the `"never", { "generators": false }` options:

```js
/*eslint func-names: ["error", "never", { "generators": false }]*/

var foo = bar(function *() {});
```

Examples of **correct** code for this rule with the `"never", { "generators": false }` options:

```js
/*eslint func-names: ["error", "never", { "generators": false }]*/

var foo = bar(function *baz() {});
```

## Further Reading

* [Functions Explained](http://markdaggett.com/blog/2013/02/15/functions-explained/)
Expand Down
81 changes: 66 additions & 15 deletions lib/rules/func-names.js
Expand Up @@ -36,6 +36,15 @@ module.exports = {
schema: [
{
enum: ["always", "as-needed", "never"]
},
{
type: "object",
properties: {
generators: {
type: "boolean"
}
},
additionalProperties: false
}
]
},
Expand All @@ -44,6 +53,23 @@ module.exports = {
const never = context.options[0] === "never";
const asNeeded = context.options[0] === "as-needed";

// defaults to true (func-name required) for "always" and "as-needed", false (func-name not required) for "never".
let requireNamedGenerators = !never;

if (context.options[1] && context.options[1].hasOwnProperty("generators")) {
switch (context.options[0]) {
case "always":
case "as-needed":
requireNamedGenerators = context.options[1].generators;
break;
case "never":
requireNamedGenerators = !context.options[1].generators;

// no default
}

}

/**
* Determines whether the current FunctionExpression node is a get, set, or
* shorthand method in an object literal or a class.
Expand Down Expand Up @@ -79,6 +105,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,
message: "Unexpected unnamed {{name}}.",
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,
message: "Unexpected named {{name}}.",
data: { name: astUtils.getFunctionNameWithKind(node) }
});
}

return {
"FunctionExpression:exit"(node) {

Expand All @@ -90,23 +142,22 @@ module.exports = {
}

const hasName = Boolean(node.id && node.id.name);
const name = astUtils.getFunctionNameWithKind(node);

if (never) {
if (hasName) {
context.report({
node,
message: "Unexpected named {{name}}.",
data: { name }
});

if (node.generator) {
if (requireNamedGenerators && !hasName) {
reportUnexpectedUnnamedFunction(node);
} else if (!requireNamedGenerators && hasName) {
reportUnexpectedNamedFunction(node);
}
} else {
if (!hasName && (asNeeded ? !hasInferredName(node) : !isObjectOrClassMethod(node))) {
context.report({
node,
message: "Unexpected unnamed {{name}}.",
data: { name }
});
if (never) {
if (hasName) {
reportUnexpectedNamedFunction(node);
}
} else {
if (!hasName && (asNeeded ? !hasInferredName(node) : !isObjectOrClassMethod(node))) {
reportUnexpectedUnnamedFunction(node);
}
}
}
}
Expand Down
82 changes: 82 additions & 0 deletions tests/lib/rules/func-names.js
Expand Up @@ -125,6 +125,38 @@ ruleTester.run("func-names", rule, {
code: "({ foo() {} });",
options: ["never"],
parserOptions: { ecmaVersion: 6 }
},

// generators
{
code: "var foo = bar(function *baz() {});",
options: ["always", { generators: true }],
parserOptions: { ecmaVersion: 6 }
},
{
code: "var foo = bar(function *() {});",
options: ["always", { generators: false }],
parserOptions: { ecmaVersion: 6 }
},
{
code: "var foo = bar(function *baz() {});",
options: ["as-needed", { generators: true }],
parserOptions: { ecmaVersion: 6 }
},
{
code: "var foo = bar(function *() {});",
options: ["as-needed", { generators: false }],
parserOptions: { ecmaVersion: 6 }
},
{
code: "var foo = bar(function *() {});",
options: ["never", { generators: true }],
parserOptions: { ecmaVersion: 6 }
},
{
code: "var foo = bar(function *baz() {});",
options: ["never", { generators: false }],
parserOptions: { ecmaVersion: 6 }
}
],
invalid: [
Expand Down Expand Up @@ -197,6 +229,56 @@ ruleTester.run("func-names", rule, {
code: "({foo: function foo() {}})",
options: ["never"],
errors: [{ message: "Unexpected named method 'foo'.", type: "FunctionExpression" }]
},

// generators
{
code: "var foo = bar(function *() {});",
options: ["always", { generators: true }],
parserOptions: { ecmaVersion: 6 },
errors: [{
message: "Unexpected unnamed generator function."
}]
},
{
code: "var foo = bar(function *baz() {});",
options: ["always", { generators: false }],
parserOptions: { ecmaVersion: 6 },
errors: [{
message: "Unexpected named generator function 'baz'."
}]
},
{
code: "var foo = bar(function *() {});",
options: ["as-needed", { generators: true }],
parserOptions: { ecmaVersion: 6 },
errors: [{
message: "Unexpected unnamed generator function."
}]
},
{
code: "var foo = bar(function *baz() {});",
options: ["as-needed", { generators: false }],
parserOptions: { ecmaVersion: 6 },
errors: [{
message: "Unexpected named generator function 'baz'."
}]
},
{
code: "var foo = bar(function *baz() {});",
options: ["never", { generators: true }],
parserOptions: { ecmaVersion: 6 },
errors: [{
message: "Unexpected named generator function 'baz'."
}]
},
{
code: "var foo = bar(function *() {});",
options: ["never", { generators: false }],
parserOptions: { ecmaVersion: 6 },
errors: [{
message: "Unexpected unnamed generator function."
}]
}
]
});

0 comments on commit 109faf2

Please sign in to comment.