Skip to content

Commit

Permalink
Update: 'requireForBlockBody' modifier for 'arrow-parens' (fixes #6557)…
Browse files Browse the repository at this point in the history
… (#6558)
  • Loading branch information
nfroidure authored and kaicataldo committed Aug 3, 2016
1 parent cdded07 commit 30d71d6
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 15 deletions.
58 changes: 52 additions & 6 deletions docs/rules/arrow-parens.md
Expand Up @@ -49,13 +49,16 @@ a => {}

## Options

The rule takes one option, a string, which could be either `"always"` or `"as-needed"`. The default is `"always"`.
This rule has a string option and an object one.

You can set the option in configuration like this:
String options are:

```json
"arrow-parens": ["error", "always"]
```
* `"always"` (default) requires parens around arguments in all cases.
* `"as-needed"` allows omitting parens when there is only one argument.

Object properties for variants of the `"as-needed"` option:

* `"requireForBlockBody": true` modifies the as-needed rule in order to require parens if the function body is in an intructions block (surrounded by braces).

### always

Expand Down Expand Up @@ -176,4 +179,47 @@ a.then(foo => { if (true) {}; });
(a = 10) => a;
([a, b]) => a;
({a, b}) => a;
```
```

### requireForBlockBody

Examples of **incorrect** code for the `{ "requireForBlockBody": true }` option:

```js
/*eslint arrow-parens: [2, "as-needed", { "requireForBlockBody": true }]*/
/*eslint-env es6*/

(a) => a;
a => {};
a => {'\n'};
a.map((x) => x * x);
a.map(x => {
return x * x;
});
a.then(foo => {});
```

Examples of **correct** code for the `{ "requireForBlockBody": true }` option:

```js
/*eslint arrow-parens: [2, "as-needed", { "requireForBlockBody": true }]*/
/*eslint-env es6*/

(a) => {};
(a) => {'\n'};
a => ({});
() => {};
a => a;
a.then((foo) => {});
a.then((foo) => { if (true) {}; });
a((foo) => { if (true) {}; });
(a, b, c) => a;
(a = 10) => a;
([a, b]) => a;
({a, b}) => a;
```

## Further Reading

* The `"as-needed", { "requireForBlockBody": true }` rule is directly inspired by the Airbnb
[JS Style Guide](https://github.com/airbnb/javascript#arrows--one-arg-parens).
56 changes: 55 additions & 1 deletion lib/rules/arrow-parens.js
Expand Up @@ -21,6 +21,15 @@ module.exports = {
schema: [
{
enum: ["always", "as-needed"]
},
{
type: "object",
properties: {
requireForBlockBody: {
type: "boolean"
}
},
additionalProperties: false
}
]
},
Expand All @@ -29,9 +38,13 @@ module.exports = {
const message = "Expected parentheses around arrow function argument.";
const asNeededMessage = "Unexpected parentheses around single function argument.";
const asNeeded = context.options[0] === "as-needed";
const requireForBlockBodyMessage = "Unexpected parentheses around single function argument having a body with no curly braces";
const requireForBlockBodyNoParensMessage = "Expected parentheses around arrow function argument having a body with curly braces.";
const requireForBlockBody = asNeeded && context.options[1] && context.options[1].requireForBlockBody === true;

const sourceCode = context.getSourceCode();


/**
* Determines whether a arrow function argument end with `)`
* @param {ASTNode} node The arrow function node.
Expand All @@ -40,7 +53,48 @@ module.exports = {
function parens(node) {
const token = sourceCode.getFirstToken(node);

// as-needed: x => x
// "as-needed", { "requireForBlockBody": true }: x => x
if (
requireForBlockBody &&
node.params.length === 1 &&
node.params[0].type === "Identifier" &&
node.body.type !== "BlockStatement"
) {
if (token.type === "Punctuator" && token.value === "(") {
context.report({
node: node,
message: requireForBlockBodyMessage,
fix: function(fixer) {
const paramToken = context.getTokenAfter(token);
const closingParenToken = context.getTokenAfter(paramToken);

return fixer.replaceTextRange([
token.range[0],
closingParenToken.range[1]
], paramToken.value);
}
});
}
return;
}

if (
requireForBlockBody &&
node.body.type === "BlockStatement"
) {
if (token.type !== "Punctuator" || token.value !== "(") {
context.report({
node: node,
message: requireForBlockBodyNoParensMessage,
fix: function(fixer) {
return fixer.replaceText(token, "(" + token.value + ")");
}
});
}
return;
}

// "as-needed": x => x
if (asNeeded && node.params.length === 1 && node.params[0].type === "Identifier") {
if (token.type === "Punctuator" && token.value === "(") {
context.report({
Expand Down
55 changes: 47 additions & 8 deletions tests/lib/rules/arrow-parens.js
Expand Up @@ -18,30 +18,56 @@ const rule = require("../../../lib/rules/arrow-parens"),
const ruleTester = new RuleTester();

const valid = [

// "always" (by default)
{ code: "() => {}", parserOptions: { ecmaVersion: 6 } },
{ code: "(a) => {}", parserOptions: { ecmaVersion: 6 } },
{ code: "(a) => a", parserOptions: { ecmaVersion: 6 } },
{ code: "(a) => {\n}", parserOptions: { ecmaVersion: 6 } },
{ code: "a.then((foo) => {});", parserOptions: { ecmaVersion: 6 } },
{ code: "a.then((foo) => { if (true) {}; });", parserOptions: { ecmaVersion: 6 } },

// as-needed
// "always" (explicit)
{ code: "() => {}", options: ["always"], parserOptions: { ecmaVersion: 6 } },
{ code: "(a) => {}", options: ["always"], parserOptions: { ecmaVersion: 6 } },
{ code: "(a) => a", options: ["always"], parserOptions: { ecmaVersion: 6 } },
{ code: "(a) => {\n}", options: ["always"], parserOptions: { ecmaVersion: 6 } },
{ code: "a.then((foo) => {});", options: ["always"], parserOptions: { ecmaVersion: 6 } },
{ code: "a.then((foo) => { if (true) {}; });", options: ["always"], parserOptions: { ecmaVersion: 6 } },

// "as-needed"
{ code: "() => {}", options: ["as-needed"], parserOptions: { ecmaVersion: 6 } },
{ code: "a => {}", options: ["as-needed"], parserOptions: { ecmaVersion: 6 } },
{ code: "a => a", options: ["as-needed"], parserOptions: { ecmaVersion: 6 } },
{ code: "([a, b]) => {}", options: ["as-needed"], parserOptions: { ecmaVersion: 6 } },
{ code: "({ a, b }) => {}", options: ["as-needed"], parserOptions: { ecmaVersion: 6 } },
{ code: "(a = 10) => {}", options: ["as-needed"], parserOptions: { ecmaVersion: 6 } },
{ code: "(...a) => a[0]", options: ["as-needed"], parserOptions: { ecmaVersion: 6 } },
{ code: "(a, b) => {}", options: ["as-needed"], parserOptions: { ecmaVersion: 6 } }
{ code: "(a, b) => {}", options: ["as-needed"], parserOptions: { ecmaVersion: 6 } },

// "as-needed", { "requireForBlockBody": true }
{ code: "() => {}", options: ["as-needed", {requireForBlockBody: true}], parserOptions: { ecmaVersion: 6 } },
{ code: "a => a", options: ["as-needed", {requireForBlockBody: true}], parserOptions: { ecmaVersion: 6 } },
{ code: "([a, b]) => {}", options: ["as-needed", {requireForBlockBody: true}], parserOptions: { ecmaVersion: 6 } },
{ code: "([a, b]) => a", options: ["as-needed", {requireForBlockBody: true}], parserOptions: { ecmaVersion: 6 } },
{ code: "({ a, b }) => {}", options: ["as-needed", {requireForBlockBody: true}], parserOptions: { ecmaVersion: 6 } },
{ code: "({ a, b }) => a + b", options: ["as-needed", {requireForBlockBody: true}], parserOptions: { ecmaVersion: 6 } },
{ code: "(a = 10) => {}", options: ["as-needed", {requireForBlockBody: true}], parserOptions: { ecmaVersion: 6 } },
{ code: "(...a) => a[0]", options: ["as-needed", {requireForBlockBody: true}], parserOptions: { ecmaVersion: 6 } },
{ code: "(a, b) => {}", options: ["as-needed", {requireForBlockBody: true}], parserOptions: { ecmaVersion: 6 } },
{ code: "a => ({})", options: ["as-needed", {requireForBlockBody: true}], parserOptions: { ecmaVersion: 6 } }

];

const message = "Expected parentheses around arrow function argument.";
const asNeededMessage = "Unexpected parentheses around single function argument.";
const requireForBlockBodyMessage = "Unexpected parentheses around single function argument having a body with no curly braces";
const requireForBlockBodyNoParensMessage = "Expected parentheses around arrow function argument having a body with curly braces.";
const type = "ArrowFunctionExpression";

const invalid = [

// "always" (by default)
{
code: "a => {}",
output: "(a) => {}",
Expand Down Expand Up @@ -109,7 +135,7 @@ const invalid = [
}]
},

// as-needed
// "as-needed"
{
code: "(a) => a",
output: "a => a",
Expand All @@ -122,19 +148,32 @@ const invalid = [
type: type
}]
},

// "as-needed", { "requireForBlockBody": true }
{
code: "(b) => b",
output: "b => b",
options: ["as-needed"],
code: "a => {}",
output: "(a) => {}",
options: ["as-needed", {requireForBlockBody: true}],
parserOptions: { ecmaVersion: 6 },
errors: [{
line: 1,
column: 1,
message: asNeededMessage,
message: requireForBlockBodyNoParensMessage,
type: type
}]
},
{
code: "(a) => a",
output: "a => a",
options: ["as-needed", {requireForBlockBody: true}],
parserOptions: { ecmaVersion: 6 },
errors: [{
line: 1,
column: 1,
message: requireForBlockBodyMessage,
type: type
}]
}

];

ruleTester.run("arrow-parens", rule, {
Expand Down

0 comments on commit 30d71d6

Please sign in to comment.