Skip to content

Commit

Permalink
Add fork of brace-style rule to support one line arrow functions
Browse files Browse the repository at this point in the history
The base brace-style rule in eslint does not have an option to support
one line arrow functions like:

```
myFunction((b) => { doSomething(b); });
```

The only option available is `allowSingleLine`, which would allow the
above code to pass, but would also allow one line if statements.

This commit forks the brace-style rule and adds the option
`allowSingleLineArrowFunctions`.
  • Loading branch information
jimjenkins5 committed Dec 28, 2018
1 parent fdb4381 commit 696a63a
Show file tree
Hide file tree
Showing 3 changed files with 291 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/index.js
Expand Up @@ -16,6 +16,7 @@ var fluentChaining = require('./rules/fluent-chaining'),
noArrowProperties = require('./rules/no-arrow-for-class-property'),
modulesOnly = require('./rules/module-files-only'),
blockScopeCase = require('./rules/block-scope-case'),
braceStyle = require('./rules/brace-style'),
indent = require('./rules/indent');

module.exports.rules = {
Expand All @@ -24,6 +25,7 @@ module.exports.rules = {
'call-indentation': callIndentation,
'no-multiple-inline-functions': noMultipleInlineFunctions,
'no-multiline-var-declarations': noMultilineVarDeclaration,
'brace-style': braceStyle,
'indent': indent,
'no-multiline-conditionals': noMultilineConditionals,
'empty-object-spacing': emptyObjectSpacing,
Expand Down
193 changes: 193 additions & 0 deletions lib/rules/brace-style.js
@@ -0,0 +1,193 @@
/**
* @fileoverview Rule to flag block statements that do not use the one true brace style
* @author Ian Christian Myers
*/

'use strict';

const astUtils = require('eslint/lib/util/ast-utils');

module.exports = {
meta: {
type: 'layout',

docs: {
description: 'enforce consistent brace style for blocks',
category: 'Stylistic Issues',
recommended: false,
url: 'https://eslint.org/docs/rules/brace-style',
},

schema: [
{
'enum': [ '1tbs', 'stroustrup', 'allman' ],
},
{
type: 'object',
properties: {
allowSingleLine: {
type: 'boolean',
},
allowSingleLineArrow: {
type: 'boolean',
},
},
additionalProperties: false,
},
],

fixable: 'whitespace',

messages: {
nextLineOpen: 'Opening curly brace does not appear on the same line as controlling statement.',
sameLineOpen: 'Opening curly brace appears on the same line as controlling statement.',
blockSameLine: 'Statement inside of curly braces should be on next line.',
nextLineClose: 'Closing curly brace does not appear on the same line as the subsequent block.',
singleLineClose: 'Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.',
sameLineClose: 'Closing curly brace appears on the same line as the subsequent block.',
},
},

create(context) {
const style = context.options[0] || '1tbs',
params = context.options[1] || {},
sourceCode = context.getSourceCode();

// --------------------------------------------------------------------------
// Helpers
// --------------------------------------------------------------------------

/**
* Fixes a place where a newline unexpectedly appears
* @param {Token} firstToken The token before the unexpected newline
* @param {Token} secondToken The token after the unexpected newline
* @returns {Function} A fixer function to remove the newlines between the tokens
*/
function removeNewlineBetween(firstToken, secondToken) {
const textRange = [ firstToken.range[1], secondToken.range[0] ],
textBetween = sourceCode.text.slice(textRange[0], textRange[1]);

// Don't do a fix if there is a comment between the tokens
if (textBetween.trim()) {
return null;
}
return (fixer) => fixer.replaceTextRange(textRange, ' ');
}

/**
* Validates a pair of curly brackets based on the user's config
* @param {Token} openingCurly The opening curly bracket
* @param {Token} closingCurly The closing curly bracket
* @returns {void}
*/
function validateCurlyPair(openingCurly, closingCurly) {
const tokenBeforeOpeningCurly = sourceCode.getTokenBefore(openingCurly),
tokenAfterOpeningCurly = sourceCode.getTokenAfter(openingCurly),
tokenBeforeClosingCurly = sourceCode.getTokenBefore(closingCurly),
singleLineException = params.allowSingleLine && astUtils.isTokenOnSameLine(openingCurly, closingCurly),
isOpeningCurlyOnSameLine = astUtils.isTokenOnSameLine(openingCurly, tokenAfterOpeningCurly),
isClosingCurlyOnSameLine = astUtils.isTokenOnSameLine(tokenBeforeClosingCurly, closingCurly);

if (style !== 'allman' && !astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly)) {
context.report({
node: openingCurly,
messageId: 'nextLineOpen',
fix: removeNewlineBetween(tokenBeforeOpeningCurly, openingCurly),
});
}

if (style === 'allman' && astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly) && !singleLineException) {
context.report({
node: openingCurly,
messageId: 'sameLineOpen',
fix: (fixer) => fixer.insertTextBefore(openingCurly, '\n'),
});
}


if (isOpeningCurlyOnSameLine && tokenAfterOpeningCurly !== closingCurly && !singleLineException) {
context.report({
node: openingCurly,
messageId: 'blockSameLine',
fix: (fixer) => fixer.insertTextAfter(openingCurly, '\n'),
});
}

if (tokenBeforeClosingCurly !== openingCurly && !singleLineException && isClosingCurlyOnSameLine) {
context.report({
node: closingCurly,
messageId: 'singleLineClose',
fix: (fixer) => fixer.insertTextBefore(closingCurly, '\n'),
});
}
}

/**
* Validates the location of a token that appears before a keyword (e.g. a newline
* before `else`)
* @param {Token} curlyToken The closing curly token. This is assumed to precede a
* keyword token (such as `else` or `finally`).
* @returns {void}
*/
function validateCurlyBeforeKeyword(curlyToken) {
const keywordToken = sourceCode.getTokenAfter(curlyToken);

if (style === '1tbs' && !astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
context.report({
node: curlyToken,
messageId: 'nextLineClose',
fix: removeNewlineBetween(curlyToken, keywordToken),
});
}

if (style !== '1tbs' && astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
context.report({
node: curlyToken,
messageId: 'sameLineClose',
fix: (fixer) => fixer.insertTextAfter(curlyToken, '\n'),
});
}
}

return {
BlockStatement(node) {
const isOneLine = astUtils.isTokenOnSameLine(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));

if (params.allowSingleLineArrow && node.parent.type === 'ArrowFunctionExpression' && isOneLine) {
return;
}

if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
}
},
ClassBody(node) {
validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
},
SwitchStatement(node) {
const closingCurly = sourceCode.getLastToken(node),
openingCurly = sourceCode.getTokenBefore(node.cases.length ? node.cases[0] : closingCurly);

validateCurlyPair(openingCurly, closingCurly);
},
IfStatement(node) {
if (node.consequent.type === 'BlockStatement' && node.alternate) {

// Handle the keyword after the `if` block (before `else`)
validateCurlyBeforeKeyword(sourceCode.getLastToken(node.consequent));
}
},
TryStatement(node) {

// Handle the keyword after the `try` block (before `catch` or `finally`)
validateCurlyBeforeKeyword(sourceCode.getLastToken(node.block));

if (node.handler && node.finalizer) {

// Handle the keyword after the `catch` block (before `finally`)
validateCurlyBeforeKeyword(sourceCode.getLastToken(node.handler.body));
}
},
};
},
};
96 changes: 96 additions & 0 deletions tests/lib/rules/brace-style.test.js
@@ -0,0 +1,96 @@
/**
* @fileoverview Check indentation at the beginning and end of a function call
*/
'use strict';

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

var rule = require('../../../lib/rules/brace-style'),
formatCode = require('../../code-helper'),
ruleTester = require('../../ruleTesters').es6(),
invalidExamples, validExample;

invalidExamples = [
{
code: formatCode(
'if (a === b) { doSomething() }',
'if (a === b) {',
' doSomething(); }',
'if (a === b )',
'{',
' doSomething();',
'}',
'let badArrow = (b) => ',
'{',
' doSomething(b);',
'};'
),
errors: [
{
message: 'Statement inside of curly braces should be on next line.',
type: 'Punctuator',
},
{
message: 'Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.',
type: 'Punctuator',
},
{
message: 'Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.',
type: 'Punctuator',
},
{
message: 'Opening curly brace does not appear on the same line as controlling statement.',
type: 'Punctuator',
},
{
message: 'Opening curly brace does not appear on the same line as controlling statement.',
type: 'Punctuator',
},
],
options: [ '1tbs', { allowSingleLine: false, allowSingleLineArrow: true } ],
},
];

validExample = formatCode(
'if (a === b) {',
' doSomething();',
'}',
'if (a === b) {',
' doSomething();',
'} else {',
' doSomethingElse();',
'}',
'while (a === b) {',
' doSomething();',
'}',
'function myFunc(a) {',
' doSomething(a);',
'}',
'let func = (a) => {',
' doSomething(a);',
'};',
'try {',
' doSomething(a);',
'} catch(e) {',
' console.log(e);',
'}',
'myOtherFunc((a) => { doSomething(); });',
'let func2 = (b) => { doSomething(); };'
);

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

ruleTester.run('brace-style', rule, {
valid: [
{
code: validExample,
options: [ '1tbs', { allowSingleLine: false, allowSingleLineArrow: true } ],
},
],

invalid: invalidExamples,
});

0 comments on commit 696a63a

Please sign in to comment.