Skip to content

Commit

Permalink
Update: Allow func-names to recognize inferred ES6 names (fixes #7235
Browse files Browse the repository at this point in the history
…) (#7244)
  • Loading branch information
loganfsmyth authored and btmills committed Nov 11, 2016
1 parent b8d6e48 commit c3f4809
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 4 deletions.
30 changes: 30 additions & 0 deletions docs/rules/func-names.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ This rule can enforce or disallow the use of named function expressions.
This rule has a string option:

* `"always"` (default) requires function expressions to have a name
* `"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

### always
Expand Down Expand Up @@ -45,6 +46,34 @@ Foo.prototype.bar = function bar() {};
}())
```

### as-needed

ECMAScript 6 introduced a `name` property on all functions. The value of `name` is determined by evaluating the code around the function to see if a name can be inferred. For example, a function assigned to a variable will automatically have a `name` property equal to the name of the variable. The value of `name` is then used in stack traces for easier debugging.

Examples of **incorrect** code for this rule with the default `"as-needed"` option:

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

Foo.prototype.bar = function() {};

(function() {
// ...
}())
```

Examples of **correct** code for this rule with the default `"as-needed"` option:

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

var bar = function() {};

(function bar() {
// ...
}())
```

### never

Examples of **incorrect** code for this rule with the `"never"` option:
Expand Down Expand Up @@ -74,6 +103,7 @@ Foo.prototype.bar = function() {};
## Further Reading

* [Functions Explained](http://markdaggett.com/blog/2013/02/15/functions-explained/)
* [Function Names in ES6](http://www.2ality.com/2015/09/function-names-es6.html)

## Compatibility

Expand Down
27 changes: 23 additions & 4 deletions lib/rules/func-names.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,23 @@ module.exports = {

schema: [
{
enum: ["always", "never"]
enum: ["always", "as-needed", "never"]
}
]
},

create(context) {
const never = context.options[0] === "never";
const asNeeded = context.options[0] === "as-needed";

/**
* Determines whether the current FunctionExpression node is a get, set, or
* shorthand method in an object literal or a class.
* @param {ASTNode} node - A node to check.
* @returns {boolean} True if the node is a get, set, or shorthand method.
*/
function isObjectOrClassMethod() {
const parent = context.getAncestors().pop();
function isObjectOrClassMethod(node) {
const parent = node.parent;

return (parent.type === "MethodDefinition" || (
parent.type === "Property" && (
Expand All @@ -53,6 +55,23 @@ module.exports = {
));
}

/**
* Determines whether the current FunctionExpression node has a name that would be
* inferred from context in a conforming ES6 environment.
* @param {ASTNode} node - A node to check.
* @returns {boolean} True if the node would have a name assigned automatically.
*/
function hasInferredName(node) {
const parent = node.parent;

return isObjectOrClassMethod(node) ||
(parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) ||
(parent.type === "Property" && parent.value === node) ||
(parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) ||
(parent.type === "ExportDefaultDeclaration" && parent.declaration === node) ||
(parent.type === "AssignmentPattern" && parent.right === node);
}

return {
"FunctionExpression:exit"(node) {

Expand All @@ -70,7 +89,7 @@ module.exports = {
context.report(node, "Unexpected function expression name.");
}
} else {
if (!name && !isObjectOrClassMethod()) {
if (!name && (asNeeded ? !hasInferredName(node) : !isObjectOrClassMethod(node))) {
context.report(node, "Missing function expression name.");
}
}
Expand Down
81 changes: 81 additions & 0 deletions tests/lib/rules/func-names.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,56 @@ ruleTester.run("func-names", rule, {
code: "var a = function foo() {};",
options: ["always"]
},
{
code: "class A { constructor(){} foo(){} get bar(){} set baz(value){} static qux(){}}",
options: ["as-needed"],
parserOptions: { ecmaVersion: 6 }
},
{
code: "({ foo() {} });",
options: ["as-needed"],
parserOptions: { ecmaVersion: 6 }
},
{
code: "var foo = function(){};",
options: ["as-needed"]
},
{
code: "({foo: function(){}});",
options: ["as-needed"]
},
{
code: "(foo = function(){});",
options: ["as-needed"]
},
{
code: "export default (function(){});",
options: ["as-needed"],
parserOptions: {
ecmaVersion: 6,
sourceType: "module"
}
},
{
code: "({foo = function(){}} = {});",
options: ["as-needed"],
parserOptions: { ecmaVersion: 6 }
},
{
code: "({key: foo = function(){}} = {});",
options: ["as-needed"],
parserOptions: { ecmaVersion: 6 }
},
{
code: "[foo = function(){}] = [];",
options: ["as-needed"],
parserOptions: { ecmaVersion: 6 }
},
{
code: "function fn(foo = function(){}) {}",
options: ["as-needed"],
parserOptions: { ecmaVersion: 6 }
},
{
code: "function foo() {}",
options: ["never"]
Expand Down Expand Up @@ -84,6 +134,37 @@ ruleTester.run("func-names", rule, {
{ code: "var a = new Date(function() {});", errors: [{ message: "Missing function expression name.", type: "FunctionExpression"}] },
{ code: "var test = function(d, e, f) {};", errors: [{ message: "Missing function expression name.", type: "FunctionExpression"}] },
{ code: "new function() {}", errors: [{ message: "Missing function expression name.", type: "FunctionExpression"}] },
{
code: "Foo.prototype.bar = function() {};",
options: ["as-needed"],
errors: [{ message: "Missing function expression name.", type: "FunctionExpression"}]
},
{
code: "(function(){}())",
options: ["as-needed"],
errors: [{ message: "Missing function expression name.", type: "FunctionExpression"}]
},
{
code: "f(function(){})",
options: ["as-needed"],
errors: [{ message: "Missing function expression name.", type: "FunctionExpression"}]
},
{
code: "var a = new Date(function() {});",
options: ["as-needed"],
errors: [{ message: "Missing function expression name.", type: "FunctionExpression"}]
},
{
code: "new function() {}",
options: ["as-needed"],
errors: [{ message: "Missing function expression name.", type: "FunctionExpression"}]
},
{
code: "var {foo} = function(){};",
options: ["as-needed"],
parserOptions: { ecmaVersion: 6 },
errors: [{ message: "Missing function expression name.", type: "FunctionExpression"}]
},
{
code: "var x = function named() {};",
options: ["never"],
Expand Down

0 comments on commit c3f4809

Please sign in to comment.