Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Breaking: check unnamed default export in func-names (fixes #12194) #12195

Merged
merged 4 commits into from Feb 17, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 10 additions & 2 deletions docs/rules/func-names.md
Expand Up @@ -16,8 +16,8 @@ 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
* `"always"` (default) requires function expressions and default export functions to have a name
* `"as-needed"` requires function expressions and default export functions to have a name, if the name cannot be assigned automatically in an ES6 environment
golopot marked this conversation as resolved.
Show resolved Hide resolved
* `"never"` disallows named function expressions, except in recursive functions, where a name is needed

This rule has an object option:
Expand Down Expand Up @@ -45,6 +45,8 @@ const cat = {
(function() {
// ...
}())

export default function() {}
```

Examples of **correct** code for this rule with the default `"always"` option:
Expand All @@ -61,6 +63,8 @@ const cat = {
(function bar() {
// ...
}())

export default function foo() {}
```

### as-needed
Expand All @@ -77,6 +81,8 @@ Foo.prototype.bar = function() {};
(function() {
// ...
}())

export default function() {}
```

Examples of **correct** code for this rule with the `"as-needed"` option:
Expand All @@ -93,6 +99,8 @@ const cat = {
(function bar() {
// ...
}())

export default function foo() {}
```

### never
Expand Down
53 changes: 30 additions & 23 deletions lib/rules/func-names.js
Expand Up @@ -119,7 +119,6 @@ module.exports = {
(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);
}

Expand Down Expand Up @@ -151,33 +150,41 @@ module.exports = {
});
}

return {
"FunctionExpression:exit"(node) {
/**
* The listener for function nodes.
* @param {ASTNode} node function node
* @returns {void}
*/
function handleFunction(node) {

// Skip recursive functions.
const nameVar = context.getDeclaredVariables(node)[0];
// Skip recursive functions.
const nameVar = context.getDeclaredVariables(node)[0];

if (isFunctionName(nameVar) && nameVar.references.length > 0) {
return;
}
if (isFunctionName(nameVar) && nameVar.references.length > 0) {
return;
}

const hasName = Boolean(node.id && node.id.name);
const config = getConfigForNode(node);

if (config === "never") {
if (hasName) {
reportUnexpectedNamedFunction(node);
}
} else if (config === "as-needed") {
if (!hasName && !hasInferredName(node)) {
reportUnexpectedUnnamedFunction(node);
}
} else {
if (!hasName && !isObjectOrClassMethod(node)) {
reportUnexpectedUnnamedFunction(node);
}
const hasName = Boolean(node.id && node.id.name);
const config = getConfigForNode(node);

if (config === "never") {
if (hasName && node.type !== "FunctionDeclaration") {
reportUnexpectedNamedFunction(node);
}
} else if (config === "as-needed") {
if (!hasName && !hasInferredName(node)) {
reportUnexpectedUnnamedFunction(node);
}
} else {
if (!hasName && !isObjectOrClassMethod(node)) {
reportUnexpectedUnnamedFunction(node);
}
}
}

return {
"FunctionExpression:exit": handleFunction,
"ExportDefaultDeclaration > FunctionDeclaration": handleFunction
};
}
};
65 changes: 57 additions & 8 deletions tests/lib/rules/func-names.js
Expand Up @@ -64,14 +64,6 @@ ruleTester.run("func-names", rule, {
code: "(foo = function(){});",
options: ["as-needed"]
},
{
code: "export default (function(){});",
options: ["as-needed"],
parserOptions: {
ecmaVersion: 6,
sourceType: "module"
}
},
{
code: "({foo = function(){}} = {});",
options: ["as-needed"],
Expand Down Expand Up @@ -127,6 +119,28 @@ ruleTester.run("func-names", rule, {
parserOptions: { ecmaVersion: 6 }
},

// export default
{
code: "export default function foo() {}",
options: ["always"],
parserOptions: { sourceType: "module", ecmaVersion: 6 }
},
{
code: "export default function foo() {}",
options: ["as-needed"],
parserOptions: { sourceType: "module", ecmaVersion: 6 }
},
{
code: "export default function foo() {}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this valid? The option is "never" but the function has a name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would require more checks, foo is here a declaration and can be used from within the module. And even if that's not the case, I believe this would break some builds

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reasoning is that, under option never, function f() is allowed, export function f() is allowed, so likewise export default function f() should be allowed.

I understand in some perspective this should be reported, so this case is controvesial. Not reporting in this case would make both sides happy.

options: ["never"],
parserOptions: { sourceType: "module", ecmaVersion: 6 }
},
{
code: "export default function() {}",
options: ["never"],
parserOptions: { sourceType: "module", ecmaVersion: 6 }
},

// generators
{
code: "var foo = bar(function *baz() {});",
Expand Down Expand Up @@ -415,6 +429,41 @@ ruleTester.run("func-names", rule, {
}]
},

// export default
{
code: "export default function() {}",
options: ["always"],
parserOptions: { sourceType: "module", ecmaVersion: 6 },
errors: [{
messageId: "unnamed",
type: "FunctionDeclaration",
column: 16,
endColumn: 24
}]
},
{
code: "export default function() {}",
options: ["as-needed"],
parserOptions: { sourceType: "module", ecmaVersion: 6 },
errors: [{
messageId: "unnamed",
type: "FunctionDeclaration",
column: 16,
endColumn: 24
}]
},
{
code: "export default (function(){});",
options: ["as-needed"],
parserOptions: { sourceType: "module", ecmaVersion: 6 },
errors: [{
messageId: "unnamed",
type: "FunctionExpression",
column: 17,
endColumn: 25
}]
},

// generators
{
code: "var foo = bar(function *() {});",
Expand Down