Skip to content

Commit

Permalink
New: Adds implicit-arrow-linebreak rule (refs #9510) (#9629)
Browse files Browse the repository at this point in the history
* New: Adds implicit-arrow-linebreak rule (refs #9510)

Adds a new rule that enforces consistency of arrow function bodies
that contain an implicit return.

* Docs: Use "implicit-arrow-linebreak" consistently

* Chore: Use name "implicit-arrow-linebreak"
  • Loading branch information
sharmilajesupaul authored and not-an-aardvark committed Nov 26, 2017
1 parent 208fb0f commit 4118f14
Show file tree
Hide file tree
Showing 4 changed files with 361 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/eslint-recommended.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ module.exports = {
"id-blacklist": "off",
"id-length": "off",
"id-match": "off",
"implicit-arrow-linebreak": "off",
indent: "off",
"indent-legacy": "off",
"init-declarations": "off",
Expand Down
101 changes: 101 additions & 0 deletions docs/rules/implicit-arrow-linebreak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Enforce the location of arrow function bodies with implicit returns (implicit-arrow-linebreak)

An arrow function body can contain an implicit return as an expression instead of a block body. It can be useful to enforce a consistent location for the implicitly returned expression.

## Rule Details

This rule aims to enforce a consistent location for an arrow function containing an implicit return.

See Also:

- [`brace-style`](https://eslint.org/docs/rules/brace-style) which enforces this behavior for arrow functions with block bodies.

### Options

This rule accepts a string option:

- `"beside"` (default) disallows a newline before an arrow function body.
- `"below"` requires a newline before an arrow function body.

Examples of **incorrect** code for this rule with the default `"beside"` option:

```js
/* eslint implicit-arrow-linebreak: ["error", "beside"] */

(foo) =>
bar;

(foo) =>
(bar);

(foo) =>
bar =>
baz;

(foo) =>
(
bar()
);
```

Examples of **correct** code for this rule with the default `"beside"` option:

```js
/* eslint implicit-arrow-linebreak: ["error", "beside"] */

(foo) => bar;

(foo) => (bar);

(foo) => bar => baz;

(foo) => (
bar()
);

// functions with block bodies allowed with this rule using any style
// to enforce a consistent location for this case, see the rule: `brace-style`
(foo) => {
return bar();
}

(foo) =>
{
return bar();
}
```

Examples of **incorrect** code for this rule with the `"below"` option:

```js
/* eslint implicit-arrow-linebreak: ["error", "below"] */

(foo) => bar;

(foo) => (bar);

(foo) => bar => baz;
```

Examples of **correct** code for this rule with the `"below"` option:

```js
/* eslint implicit-arrow-linebreak: ["error", "below"] */


(foo) =>
bar;

(foo) =>
(bar);

(foo) =>
bar =>
baz;
```

## When Not To Use It

If you're not concerned about consistent locations of implicitly returned arrow function expressions, you should not turn on this rule.

You can also disable this rule if you are using the `"always"` option for the [`arrow-body-style`](https://eslint.org/docs/rules/arrow-body-style), since this will disable the use of implicit returns in arrow functions.
86 changes: 86 additions & 0 deletions lib/rules/implicit-arrow-linebreak.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* @fileoverview enforce the location of arrow function bodies
* @author Sharmila Jesupaul
*/
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "enforce the location of arrow function bodies",
category: "Stylistic Issues",
recommended: false
},
fixable: "whitespace",
schema: [
{
enum: ["beside", "below"]
}
]
},

create(context) {
const sourceCode = context.getSourceCode();

//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------
/**
* Gets the applicable preference for a particular keyword
* @returns {string} The applicable option for the keyword, e.g. 'beside'
*/
function getOption() {
return context.options[0] || "beside";
}

/**
* Validates the location of an arrow function body
* @param {ASTNode} node The arrow function body
* @param {string} keywordName The applicable keyword name for the arrow function body
* @returns {void}
*/
function validateExpression(node) {
const option = getOption();

let tokenBefore = sourceCode.getTokenBefore(node.body);
const hasParens = tokenBefore.value === "(";

if (node.type === "BlockStatement") {
return;
}

let fixerTarget = node.body;

if (hasParens) {

// Gets the first token before the function body that is not an open paren
tokenBefore = sourceCode.getTokenBefore(node.body, token => token.value !== "(");
fixerTarget = sourceCode.getTokenAfter(tokenBefore);
}

if (tokenBefore.loc.end.line === fixerTarget.loc.start.line && option === "below") {
context.report({
node: fixerTarget,
message: "Expected a linebreak before this expression.",
fix: fixer => fixer.insertTextBefore(fixerTarget, "\n")
});
} else if (tokenBefore.loc.end.line !== fixerTarget.loc.start.line && option === "beside") {
context.report({
node: fixerTarget,
message: "Expected no linebreak before this expression.",
fix: fixer => fixer.replaceTextRange([tokenBefore.range[1], fixerTarget.range[0]], " ")
});
}
}

//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
return {
ArrowFunctionExpression: node => validateExpression(node)
};
}
};
173 changes: 173 additions & 0 deletions tests/lib/rules/implicit-arrow-linebreak.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/**
* @fileoverview enforce the location of arrow function bodies
* @author Sharmila Jesupaul
*/
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require("../../../lib/rules/implicit-arrow-linebreak");
const RuleTester = require("../../../lib/testers/rule-tester");

const EXPECTED_LINEBREAK = { message: "Expected a linebreak before this expression." };
const UNEXPECTED_LINEBREAK = { message: "Expected no linebreak before this expression." };

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });

ruleTester.run("implicit-arrow-linebreak", rule, {

valid: [

// always valid
`(foo) => {
bar
}`,

// 'beside' option
"() => bar;",
"() => (bar);",
"() => bar => baz;",
"() => ((((bar))));",
`(foo) => (
bar
)`,
{
code: "(foo) => bar();",
options: ["beside"]
},

// 'below' option
{
code: `
(foo) =>
(
bar
)
`,
options: ["below"]
}, {
code: `
() =>
((((bar))));
`,
options: ["below"]
}, {
code: `
() =>
bar();
`,
options: ["below"]
}, {
code: `
() =>
(bar);
`,
options: ["below"]
}, {
code: `
() =>
bar =>
baz;
`,
options: ["below"]
}
],

invalid: [

// 'beside' option
{
code: `
(foo) =>
bar();
`,
output: `
(foo) => bar();
`,
errors: [UNEXPECTED_LINEBREAK]
}, {
code: `
() =>
(bar);
`,
output: `
() => (bar);
`,
errors: [UNEXPECTED_LINEBREAK]
}, {
code: `
() =>
bar =>
baz;
`,
output: `
() => bar => baz;
`,
errors: [UNEXPECTED_LINEBREAK, UNEXPECTED_LINEBREAK]
}, {
code: `
() =>
((((bar))));
`,
output: `
() => ((((bar))));
`,
errors: [UNEXPECTED_LINEBREAK]
}, {
code: `
(foo) =>
(
bar
)
`,
output: `
(foo) => (
bar
)
`,
errors: [UNEXPECTED_LINEBREAK]
},

// 'below' option
{
code: "(foo) => bar();",
output: "(foo) => \nbar();",
options: ["below"],
errors: [EXPECTED_LINEBREAK]
}, {
code: "(foo) => bar => baz;",
output: "(foo) => \nbar => \nbaz;",
options: ["below"],
errors: [EXPECTED_LINEBREAK, EXPECTED_LINEBREAK]
}, {
code: "(foo) => (bar);",
output: "(foo) => \n(bar);",
options: ["below"],
errors: [EXPECTED_LINEBREAK]
}, {
code: "(foo) => (((bar)));",
output: "(foo) => \n(((bar)));",
options: ["below"],
errors: [EXPECTED_LINEBREAK]
}, {
code: `
(foo) => (
bar
)
`,
output: `
(foo) => \n(
bar
)
`,
options: ["below"],
errors: [EXPECTED_LINEBREAK]
}
]
});

0 comments on commit 4118f14

Please sign in to comment.