Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add fork of brace-style rule to support one line arrow functions
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
1 parent
fdb4381
commit 696a63a
Showing
3 changed files
with
291 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); | ||
} | ||
}, | ||
}; | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
}); |