Skip to content

Commit 3ddf1a2

Browse files
a-tarasyukbradzacher
authored andcommitted
fix(eslint-plugin): [brace-style] handle enum declarations (#1281)
1 parent b5a52a3 commit 3ddf1a2

File tree

3 files changed

+195
-25
lines changed

3 files changed

+195
-25
lines changed

Diff for: packages/eslint-plugin/src/rules/brace-style.ts

+117-21
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import {
2-
TSESTree,
3-
AST_NODE_TYPES,
4-
} from '@typescript-eslint/experimental-utils';
1+
import { TSESTree } from '@typescript-eslint/experimental-utils';
52
import baseRule from 'eslint/lib/rules/brace-style';
6-
import * as util from '../util';
3+
import {
4+
InferOptionsTypeFromRule,
5+
InferMessageIdsTypeFromRule,
6+
createRule,
7+
isTokenOnSameLine,
8+
} from '../util';
79

8-
export type Options = util.InferOptionsTypeFromRule<typeof baseRule>;
9-
export type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>;
10+
export type Options = InferOptionsTypeFromRule<typeof baseRule>;
11+
export type MessageIds = InferMessageIdsTypeFromRule<typeof baseRule>;
1012

11-
export default util.createRule<Options, MessageIds>({
13+
export default createRule<Options, MessageIds>({
1214
name: 'brace-style',
1315
meta: {
1416
type: 'layout',
@@ -23,23 +25,117 @@ export default util.createRule<Options, MessageIds>({
2325
},
2426
defaultOptions: ['1tbs'],
2527
create(context) {
28+
const [
29+
style,
30+
{ allowSingleLine } = { allowSingleLine: false },
31+
] = context.options;
32+
33+
const isAllmanStyle = style === 'allman';
34+
const sourceCode = context.getSourceCode();
2635
const rules = baseRule.create(context);
27-
const checkBlockStatement = (
28-
node: TSESTree.TSModuleBlock | TSESTree.TSInterfaceBody,
29-
): void => {
30-
rules.BlockStatement({
31-
type: AST_NODE_TYPES.BlockStatement,
32-
parent: node.parent,
33-
range: node.range,
34-
body: node.body as any, // eslint-disable-line @typescript-eslint/no-explicit-any
35-
loc: node.loc,
36-
});
37-
};
36+
37+
/**
38+
* Checks a pair of curly brackets based on the user's config
39+
*/
40+
function validateCurlyPair(
41+
openingCurlyToken: TSESTree.Token,
42+
closingCurlyToken: TSESTree.Token,
43+
): void {
44+
if (
45+
allowSingleLine &&
46+
isTokenOnSameLine(openingCurlyToken, closingCurlyToken)
47+
) {
48+
return;
49+
}
50+
51+
const tokenBeforeOpeningCurly = sourceCode.getTokenBefore(
52+
openingCurlyToken,
53+
)!;
54+
const tokenBeforeClosingCurly = sourceCode.getTokenBefore(
55+
closingCurlyToken,
56+
)!;
57+
const tokenAfterOpeningCurly = sourceCode.getTokenAfter(
58+
openingCurlyToken,
59+
)!;
60+
61+
if (
62+
!isAllmanStyle &&
63+
!isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurlyToken)
64+
) {
65+
context.report({
66+
node: openingCurlyToken,
67+
messageId: 'nextLineOpen',
68+
fix: fixer => {
69+
const textRange: TSESTree.Range = [
70+
tokenBeforeOpeningCurly.range[1],
71+
openingCurlyToken.range[0],
72+
];
73+
const textBetween = sourceCode.text.slice(
74+
textRange[0],
75+
textRange[1],
76+
);
77+
78+
if (textBetween.trim()) {
79+
return null;
80+
}
81+
82+
return fixer.replaceTextRange(textRange, ' ');
83+
},
84+
});
85+
}
86+
87+
if (
88+
isAllmanStyle &&
89+
isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurlyToken)
90+
) {
91+
context.report({
92+
node: openingCurlyToken,
93+
messageId: 'sameLineOpen',
94+
fix: fixer => fixer.insertTextBefore(openingCurlyToken, '\n'),
95+
});
96+
}
97+
98+
if (
99+
isTokenOnSameLine(openingCurlyToken, tokenAfterOpeningCurly) &&
100+
tokenAfterOpeningCurly !== closingCurlyToken
101+
) {
102+
context.report({
103+
node: openingCurlyToken,
104+
messageId: 'blockSameLine',
105+
fix: fixer => fixer.insertTextAfter(openingCurlyToken, '\n'),
106+
});
107+
}
108+
109+
if (
110+
isTokenOnSameLine(tokenBeforeClosingCurly, closingCurlyToken) &&
111+
tokenBeforeClosingCurly !== openingCurlyToken
112+
) {
113+
context.report({
114+
node: closingCurlyToken,
115+
messageId: 'singleLineClose',
116+
fix: fixer => fixer.insertTextBefore(closingCurlyToken, '\n'),
117+
});
118+
}
119+
}
38120

39121
return {
40122
...rules,
41-
TSInterfaceBody: checkBlockStatement,
42-
TSModuleBlock: checkBlockStatement,
123+
'TSInterfaceBody, TSModuleBlock'(
124+
node: TSESTree.TSModuleBlock | TSESTree.TSInterfaceBody,
125+
): void {
126+
const openingCurly = sourceCode.getFirstToken(node)!;
127+
const closingCurly = sourceCode.getLastToken(node)!;
128+
129+
validateCurlyPair(openingCurly, closingCurly);
130+
},
131+
TSEnumDeclaration(node): void {
132+
const closingCurly = sourceCode.getLastToken(node)!;
133+
const openingCurly = sourceCode.getTokenBefore(
134+
node.members.length ? node.members[0] : closingCurly,
135+
)!;
136+
137+
validateCurlyPair(openingCurly, closingCurly);
138+
},
43139
};
44140
},
45141
});

Diff for: packages/eslint-plugin/src/util/astUtils.ts

+11
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,22 @@ function isOptionalOptionalChain(
4242
);
4343
}
4444

45+
/**
46+
* Determines whether two adjacent tokens are on the same line
47+
*/
48+
function isTokenOnSameLine(
49+
left: TSESTree.Token,
50+
right: TSESTree.Token,
51+
): boolean {
52+
return left.loc.end.line === right.loc.start.line;
53+
}
54+
4555
export {
4656
isNonNullAssertionPunctuator,
4757
isNotNonNullAssertionPunctuator,
4858
isNotOptionalChainPunctuator,
4959
isOptionalChainPunctuator,
5060
isOptionalOptionalChain,
61+
isTokenOnSameLine,
5162
LINEBREAK_MATCHER,
5263
};

Diff for: packages/eslint-plugin/tests/rules/brace-style.test.ts

+67-4
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,38 @@ namespace Foo
539539
`,
540540
options: ['allman'],
541541
},
542+
{
543+
code: `
544+
enum Foo
545+
{
546+
A,
547+
B
548+
}
549+
`,
550+
options: ['allman'],
551+
},
552+
{
553+
code: `
554+
enum Foo {
555+
A,
556+
B
557+
}
558+
`,
559+
options: ['1tbs'],
560+
},
561+
{
562+
code: `
563+
enum Foo {
564+
A,
565+
B
566+
}
567+
`,
568+
options: ['stroustrup'],
569+
},
570+
{
571+
code: `enum Foo { A, B }`,
572+
options: ['1tbs', { allowSingleLine: true }],
573+
},
542574
],
543575

544576
invalid: [
@@ -760,7 +792,6 @@ if (f) {
760792
options: ['allman'],
761793
errors: [{ messageId: 'sameLineClose' }],
762794
},
763-
764795
// allowSingleLine: true
765796
{
766797
code: `function foo() { return; \n}`,
@@ -912,14 +943,12 @@ if (f) {
912943
{ messageId: 'sameLineOpen' },
913944
],
914945
},
915-
916946
// Comment interferes with fix
917947
{
918948
code: `if (foo) // comment \n{\nbar();\n}`,
919949
output: null,
920950
errors: [{ messageId: 'nextLineOpen' }],
921951
},
922-
923952
// https://github.com/eslint/eslint/issues/7493
924953
{
925954
code: `if (foo) {\n bar\n.baz }`,
@@ -995,7 +1024,6 @@ if (f) {
9951024
options: ['allman'],
9961025
errors: [{ messageId: 'sameLineOpen' }],
9971026
},
998-
9991027
// https://github.com/eslint/eslint/issues/7621
10001028
{
10011029
code: `
@@ -1109,5 +1137,40 @@ namespace Foo {
11091137
options: ['allman'],
11101138
errors: [{ messageId: 'sameLineOpen' }],
11111139
},
1140+
{
1141+
code: `
1142+
enum Foo
1143+
{
1144+
}
1145+
`,
1146+
output: `
1147+
enum Foo {
1148+
}
1149+
`,
1150+
errors: [{ messageId: 'nextLineOpen' }],
1151+
},
1152+
{
1153+
code: `
1154+
enum Foo
1155+
{
1156+
}
1157+
`,
1158+
output: `
1159+
enum Foo {
1160+
}
1161+
`,
1162+
options: ['stroustrup'],
1163+
errors: [{ messageId: 'nextLineOpen' }],
1164+
},
1165+
{
1166+
code: `enum Foo { A }`,
1167+
output: `enum Foo \n{\n A \n}`,
1168+
options: ['allman'],
1169+
errors: [
1170+
{ messageId: 'sameLineOpen' },
1171+
{ messageId: 'blockSameLine' },
1172+
{ messageId: 'singleLineClose' },
1173+
],
1174+
},
11121175
],
11131176
});

0 commit comments

Comments
 (0)