From 3ddf1a2a0fb0b7863dee048913053f2c59f8283e Mon Sep 17 00:00:00 2001 From: Alexander T Date: Wed, 4 Dec 2019 21:44:07 +0200 Subject: [PATCH] fix(eslint-plugin): [brace-style] handle enum declarations (#1281) --- .../eslint-plugin/src/rules/brace-style.ts | 138 +++++++++++++++--- packages/eslint-plugin/src/util/astUtils.ts | 11 ++ .../tests/rules/brace-style.test.ts | 71 ++++++++- 3 files changed, 195 insertions(+), 25 deletions(-) diff --git a/packages/eslint-plugin/src/rules/brace-style.ts b/packages/eslint-plugin/src/rules/brace-style.ts index 0f0f75afb30..5be461b8c23 100644 --- a/packages/eslint-plugin/src/rules/brace-style.ts +++ b/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; -export type MessageIds = util.InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; -export default util.createRule({ +export default createRule({ name: 'brace-style', meta: { type: 'layout', @@ -23,23 +25,117 @@ export default util.createRule({ }, 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); + }, }; }, }); diff --git a/packages/eslint-plugin/src/util/astUtils.ts b/packages/eslint-plugin/src/util/astUtils.ts index 30e8231f95e..9398f4eb64b 100644 --- a/packages/eslint-plugin/src/util/astUtils.ts +++ b/packages/eslint-plugin/src/util/astUtils.ts @@ -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, }; diff --git a/packages/eslint-plugin/tests/rules/brace-style.test.ts b/packages/eslint-plugin/tests/rules/brace-style.test.ts index d1f586afc2f..21d10998eac 100644 --- a/packages/eslint-plugin/tests/rules/brace-style.test.ts +++ b/packages/eslint-plugin/tests/rules/brace-style.test.ts @@ -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: [ @@ -760,7 +792,6 @@ if (f) { options: ['allman'], errors: [{ messageId: 'sameLineClose' }], }, - // allowSingleLine: true { code: `function foo() { return; \n}`, @@ -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 }`, @@ -995,7 +1024,6 @@ if (f) { options: ['allman'], errors: [{ messageId: 'sameLineOpen' }], }, - // https://github.com/eslint/eslint/issues/7621 { code: ` @@ -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' }, + ], + }, ], });