Skip to content

Commit

Permalink
fix(eslint-plugin): [brace-style] handle enum declarations (#1281)
Browse files Browse the repository at this point in the history
  • Loading branch information
a-tarasyuk authored and bradzacher committed Dec 4, 2019
1 parent b5a52a3 commit 3ddf1a2
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 25 deletions.
138 changes: 117 additions & 21 deletions packages/eslint-plugin/src/rules/brace-style.ts
@@ -1,14 +1,16 @@
import {
TSESTree,
AST_NODE_TYPES,
} from '@typescript-eslint/experimental-utils';
import { TSESTree } from '@typescript-eslint/experimental-utils';
import baseRule from 'eslint/lib/rules/brace-style';
import * as util from '../util';
import {
InferOptionsTypeFromRule,
InferMessageIdsTypeFromRule,
createRule,
isTokenOnSameLine,
} from '../util';

export type Options = util.InferOptionsTypeFromRule<typeof baseRule>;
export type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>;
export type Options = InferOptionsTypeFromRule<typeof baseRule>;
export type MessageIds = InferMessageIdsTypeFromRule<typeof baseRule>;

export default util.createRule<Options, MessageIds>({
export default createRule<Options, MessageIds>({
name: 'brace-style',
meta: {
type: 'layout',
Expand All @@ -23,23 +25,117 @@ export default util.createRule<Options, MessageIds>({
},
defaultOptions: ['1tbs'],
create(context) {
const [
style,
{ allowSingleLine } = { allowSingleLine: false },
] = context.options;

const isAllmanStyle = style === 'allman';
const sourceCode = context.getSourceCode();
const rules = baseRule.create(context);
const checkBlockStatement = (
node: TSESTree.TSModuleBlock | TSESTree.TSInterfaceBody,
): void => {
rules.BlockStatement({
type: AST_NODE_TYPES.BlockStatement,
parent: node.parent,
range: node.range,
body: node.body as any, // eslint-disable-line @typescript-eslint/no-explicit-any
loc: node.loc,
});
};

/**
* Checks a pair of curly brackets based on the user's config
*/
function validateCurlyPair(
openingCurlyToken: TSESTree.Token,
closingCurlyToken: TSESTree.Token,
): void {
if (
allowSingleLine &&
isTokenOnSameLine(openingCurlyToken, closingCurlyToken)
) {
return;
}

const tokenBeforeOpeningCurly = sourceCode.getTokenBefore(
openingCurlyToken,
)!;
const tokenBeforeClosingCurly = sourceCode.getTokenBefore(
closingCurlyToken,
)!;
const tokenAfterOpeningCurly = sourceCode.getTokenAfter(
openingCurlyToken,
)!;

if (
!isAllmanStyle &&
!isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurlyToken)
) {
context.report({
node: openingCurlyToken,
messageId: 'nextLineOpen',
fix: fixer => {
const textRange: TSESTree.Range = [
tokenBeforeOpeningCurly.range[1],
openingCurlyToken.range[0],
];
const textBetween = sourceCode.text.slice(
textRange[0],
textRange[1],
);

if (textBetween.trim()) {
return null;
}

return fixer.replaceTextRange(textRange, ' ');
},
});
}

if (
isAllmanStyle &&
isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurlyToken)
) {
context.report({
node: openingCurlyToken,
messageId: 'sameLineOpen',
fix: fixer => fixer.insertTextBefore(openingCurlyToken, '\n'),
});
}

if (
isTokenOnSameLine(openingCurlyToken, tokenAfterOpeningCurly) &&
tokenAfterOpeningCurly !== closingCurlyToken
) {
context.report({
node: openingCurlyToken,
messageId: 'blockSameLine',
fix: fixer => fixer.insertTextAfter(openingCurlyToken, '\n'),
});
}

if (
isTokenOnSameLine(tokenBeforeClosingCurly, closingCurlyToken) &&
tokenBeforeClosingCurly !== openingCurlyToken
) {
context.report({
node: closingCurlyToken,
messageId: 'singleLineClose',
fix: fixer => fixer.insertTextBefore(closingCurlyToken, '\n'),
});
}
}

return {
...rules,
TSInterfaceBody: checkBlockStatement,
TSModuleBlock: checkBlockStatement,
'TSInterfaceBody, TSModuleBlock'(
node: TSESTree.TSModuleBlock | TSESTree.TSInterfaceBody,
): void {
const openingCurly = sourceCode.getFirstToken(node)!;
const closingCurly = sourceCode.getLastToken(node)!;

validateCurlyPair(openingCurly, closingCurly);
},
TSEnumDeclaration(node): void {
const closingCurly = sourceCode.getLastToken(node)!;
const openingCurly = sourceCode.getTokenBefore(
node.members.length ? node.members[0] : closingCurly,
)!;

validateCurlyPair(openingCurly, closingCurly);
},
};
},
});
11 changes: 11 additions & 0 deletions packages/eslint-plugin/src/util/astUtils.ts
Expand Up @@ -42,11 +42,22 @@ function isOptionalOptionalChain(
);
}

/**
* Determines whether two adjacent tokens are on the same line
*/
function isTokenOnSameLine(
left: TSESTree.Token,
right: TSESTree.Token,
): boolean {
return left.loc.end.line === right.loc.start.line;
}

export {
isNonNullAssertionPunctuator,
isNotNonNullAssertionPunctuator,
isNotOptionalChainPunctuator,
isOptionalChainPunctuator,
isOptionalOptionalChain,
isTokenOnSameLine,
LINEBREAK_MATCHER,
};
71 changes: 67 additions & 4 deletions packages/eslint-plugin/tests/rules/brace-style.test.ts
Expand Up @@ -539,6 +539,38 @@ namespace Foo
`,
options: ['allman'],
},
{
code: `
enum Foo
{
A,
B
}
`,
options: ['allman'],
},
{
code: `
enum Foo {
A,
B
}
`,
options: ['1tbs'],
},
{
code: `
enum Foo {
A,
B
}
`,
options: ['stroustrup'],
},
{
code: `enum Foo { A, B }`,
options: ['1tbs', { allowSingleLine: true }],
},
],

invalid: [
Expand Down Expand Up @@ -760,7 +792,6 @@ if (f) {
options: ['allman'],
errors: [{ messageId: 'sameLineClose' }],
},

// allowSingleLine: true
{
code: `function foo() { return; \n}`,
Expand Down Expand Up @@ -912,14 +943,12 @@ if (f) {
{ messageId: 'sameLineOpen' },
],
},

// Comment interferes with fix
{
code: `if (foo) // comment \n{\nbar();\n}`,
output: null,
errors: [{ messageId: 'nextLineOpen' }],
},

// https://github.com/eslint/eslint/issues/7493
{
code: `if (foo) {\n bar\n.baz }`,
Expand Down Expand Up @@ -995,7 +1024,6 @@ if (f) {
options: ['allman'],
errors: [{ messageId: 'sameLineOpen' }],
},

// https://github.com/eslint/eslint/issues/7621
{
code: `
Expand Down Expand Up @@ -1109,5 +1137,40 @@ namespace Foo {
options: ['allman'],
errors: [{ messageId: 'sameLineOpen' }],
},
{
code: `
enum Foo
{
}
`,
output: `
enum Foo {
}
`,
errors: [{ messageId: 'nextLineOpen' }],
},
{
code: `
enum Foo
{
}
`,
output: `
enum Foo {
}
`,
options: ['stroustrup'],
errors: [{ messageId: 'nextLineOpen' }],
},
{
code: `enum Foo { A }`,
output: `enum Foo \n{\n A \n}`,
options: ['allman'],
errors: [
{ messageId: 'sameLineOpen' },
{ messageId: 'blockSameLine' },
{ messageId: 'singleLineClose' },
],
},
],
});

0 comments on commit 3ddf1a2

Please sign in to comment.