diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index a3ce816616b..178ca903a9f 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -145,6 +145,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@typescript-eslint/await-thenable`](./docs/rules/await-thenable.md) | Disallows awaiting a value that is not a Thenable | :heavy_check_mark: | | :thought_balloon: | | [`@typescript-eslint/ban-ts-ignore`](./docs/rules/ban-ts-ignore.md) | Bans “// @ts-ignore” comments from being used | :heavy_check_mark: | | | | [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Bans specific types from being used | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :wrench: | | | [`@typescript-eslint/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | | | [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names | :heavy_check_mark: | | | | [`@typescript-eslint/consistent-type-assertions`](./docs/rules/consistent-type-assertions.md) | Enforces consistent usage of type assertions. | :heavy_check_mark: | | | diff --git a/packages/eslint-plugin/docs/rules/brace-style.md b/packages/eslint-plugin/docs/rules/brace-style.md new file mode 100644 index 00000000000..908fca707c1 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/brace-style.md @@ -0,0 +1,22 @@ +# Enforce consistent brace style for blocks + +## Rule Details + +This rule extends the base [eslint/brace-style](https://eslint.org/docs/rules/brace-style) rule. +It supports all options and features of the base rule. + +## How to use + +```cjson +{ + // note you must disable the base rule as it can report incorrect errors + "brace-style": "off", + "@typescript-eslint/brace-style": ["error"] +} +``` + +## Options + +See [eslint/brace-style options](https://eslint.org/docs/rules/brace-style#options). + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/brace-style.md) diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index cce896718db..0974b7a7263 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -6,6 +6,8 @@ "@typescript-eslint/await-thenable": "error", "@typescript-eslint/ban-ts-ignore": "error", "@typescript-eslint/ban-types": "error", + "brace-style": "off", + "@typescript-eslint/brace-style": "error", "camelcase": "off", "@typescript-eslint/camelcase": "error", "@typescript-eslint/class-name-casing": "error", diff --git a/packages/eslint-plugin/src/rules/brace-style.ts b/packages/eslint-plugin/src/rules/brace-style.ts new file mode 100644 index 00000000000..0f0f75afb30 --- /dev/null +++ b/packages/eslint-plugin/src/rules/brace-style.ts @@ -0,0 +1,45 @@ +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; +import baseRule from 'eslint/lib/rules/brace-style'; +import * as util from '../util'; + +export type Options = util.InferOptionsTypeFromRule; +export type MessageIds = util.InferMessageIdsTypeFromRule; + +export default util.createRule({ + name: 'brace-style', + meta: { + type: 'layout', + docs: { + description: 'Enforce consistent brace style for blocks', + category: 'Stylistic Issues', + recommended: false, + }, + messages: baseRule.meta.messages, + fixable: baseRule.meta.fixable, + schema: baseRule.meta.schema, + }, + defaultOptions: ['1tbs'], + create(context) { + 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, + }); + }; + + return { + ...rules, + TSInterfaceBody: checkBlockStatement, + TSModuleBlock: checkBlockStatement, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index bd39c7867e3..8ae9d698cca 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -3,6 +3,7 @@ import arrayType from './array-type'; import awaitThenable from './await-thenable'; import banTsIgnore from './ban-ts-ignore'; import banTypes from './ban-types'; +import braceStyle from './brace-style'; import camelcase from './camelcase'; import classNameCasing from './class-name-casing'; import consistentTypeAssertions from './consistent-type-assertions'; @@ -67,6 +68,7 @@ export default { 'await-thenable': awaitThenable, 'ban-ts-ignore': banTsIgnore, 'ban-types': banTypes, + 'brace-style': braceStyle, camelcase: camelcase, 'class-name-casing': classNameCasing, 'consistent-type-assertions': consistentTypeAssertions, diff --git a/packages/eslint-plugin/tests/rules/brace-style.test.ts b/packages/eslint-plugin/tests/rules/brace-style.test.ts new file mode 100644 index 00000000000..d1f586afc2f --- /dev/null +++ b/packages/eslint-plugin/tests/rules/brace-style.test.ts @@ -0,0 +1,1113 @@ +import rule from '../../src/rules/brace-style'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: {}, + }, +}); + +ruleTester.run('brace-style', rule, { + valid: [ + { + code: ` +function f() { + if (true) + return { x: 1 }; + else { + var y = 2; + return y; + } +} + `, + }, + { + code: ` +if (tag === 1) glyph.id = pbf.readVarint(); +else if (tag === 2) glyph.bitmap = pbf.readBytes(); + `, + }, + { + code: ` +function foo () { + return; +} + `, + }, + { + code: ` +function a(b, +c, +d) { } + `, + }, + { + code: ` +!function foo () { + return; +} + `, + }, + { + code: ` +!function a(b, +c, +d) { } + `, + }, + { + code: ` +if (foo) { + bar(); +} + `, + }, + { + code: ` +if (a) { + b(); +} else { + c(); +} + `, + }, + { + code: ` +while (foo) { + bar(); +} + `, + }, + { + code: ` +for (;;) { + bar(); +} + `, + }, + { + code: ` +with (foo) { + bar(); +} + `, + }, + { + code: ` +switch (foo) { + case 'bar': break; +} + `, + }, + { + code: ` +try { + bar(); +} catch (e) { + baz(); +} + `, + }, + { + code: ` +do { + bar(); +} while (true) + `, + }, + { + code: ` +for (foo in bar) { + baz(); +} + `, + }, + { + code: ` +if (a && + b && + c) { + } + `, + }, + { + code: ` +switch(0) { +} + `, + }, + { + code: ` +class Foo { +} + `, + }, + { + code: ` +(class { +}) + `, + }, + { + code: ` +class +Foo { +} + `, + }, + { + code: ` +class Foo { + bar() { + } +} + `, + }, + { + code: ` +if (foo) { +} +else { +} + `, + options: ['stroustrup'], + }, + { + code: ` +if (foo) +{ +} +else +{ +} + `, + options: ['allman'], + }, + { + code: ` +try { + bar(); +} +catch (e) { + baz(); +} + `, + options: ['stroustrup'], + }, + { + code: ` +try +{ + bar(); +} +catch (e) +{ + baz(); +} + `, + options: ['allman'], + }, + { + code: `function foo () { return; }`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `function foo () { a(); b(); return; }`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `function a(b,c,d) { }`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `!function foo () { return; }`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `!function a(b,c,d) { }`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `if (foo) { bar(); }`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `if (a) { b(); } else { c(); }`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `while (foo) { bar(); }`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `for (;;) { bar(); }`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `with (foo) { bar(); }`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `switch (foo) { case 'bar': break; }`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `try { bar(); } catch (e) { baz(); }`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `do { bar(); } while (true)`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `for (foo in bar) { baz(); }`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `if (a && b && c) { }`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `switch(0) {}`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: ` +if (foo) {} +else {} + `, + options: ['stroustrup', { allowSingleLine: true }], + }, + { + code: ` +try { bar(); } +catch (e) { baz(); } + `, + options: ['stroustrup', { allowSingleLine: true }], + }, + { + code: `var foo = () => { return; }`, + options: ['stroustrup', { allowSingleLine: true }], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` +if (foo) {} +else {} + `, + options: ['allman', { allowSingleLine: true }], + }, + { + code: ` +try { bar(); } +catch (e) { baz(); } + `, + options: ['allman', { allowSingleLine: true }], + }, + { + code: `var foo = () => { return; }`, + options: ['allman', { allowSingleLine: true }], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` +if (tag === 1) fontstack.name = pbf.readString(); +else if (tag === 2) fontstack.range = pbf.readString(); +else if (tag === 3) { + var glyph = pbf.readMessage(readGlyph, {}); + fontstack.glyphs[glyph.id] = glyph; +} + `, + options: ['1tbs'], + }, + { + code: ` +if (tag === 1) fontstack.name = pbf.readString(); +else if (tag === 2) fontstack.range = pbf.readString(); +else if (tag === 3) { + var glyph = pbf.readMessage(readGlyph, {}); + fontstack.glyphs[glyph.id] = glyph; +} + `, + options: ['stroustrup'], + }, + { + code: ` +switch(x) +{ + case 1: + bar(); +} + `, + options: ['allman'], + }, + { + code: `switch(x) {}`, + options: ['allman', { allowSingleLine: true }], + }, + { + code: ` +class Foo { +} + `, + options: ['stroustrup'], + }, + { + code: ` +(class { +}) + `, + options: ['stroustrup'], + }, + { + code: ` +class Foo +{ +} + `, + options: ['allman'], + }, + { + code: ` +(class +{ +}) + `, + options: ['allman'], + }, + { + code: ` +class +Foo +{ +} + `, + options: ['allman'], + }, + { + code: `class Foo {}`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `class Foo {}`, + options: ['allman', { allowSingleLine: true }], + }, + { + code: `(class {})`, + options: ['1tbs', { allowSingleLine: true }], + }, + { + code: `(class {})`, + options: ['allman', { allowSingleLine: true }], + }, + + // https://github.com/eslint/eslint/issues/7908 + { + code: `{}`, + }, + { + code: ` +if (foo) { +} +{ +} + `, + }, + { + code: ` +switch (foo) { + case bar: + baz(); + { + qux(); + } +} + `, + }, + { + code: ` +{ +} + `, + }, + { + code: ` +{ + { + } +} + `, + }, + + // https://github.com/eslint/eslint/issues/7974 + { + code: ` +class Ball { + throw() {} + catch() {} +} + `, + }, + { + code: ` +({ + and() {}, + finally() {} +}) + `, + }, + { + code: ` +(class { + or() {} + else() {} +}) + `, + }, + { + code: ` +if (foo) bar = function() {} +else baz() + `, + }, + { + code: ` +interface Foo { +} + `, + options: ['1tbs'], + }, + { + code: ` +interface Foo { +} + `, + options: ['stroustrup'], + }, + { + code: ` +interface Foo +{ +} + `, + options: ['allman'], + }, + { + code: ` +module "Foo" { +} + `, + options: ['1tbs'], + }, + { + code: ` +module "Foo" { +} + `, + options: ['stroustrup'], + }, + { + code: ` +module "Foo" +{ +} + `, + options: ['allman'], + }, + { + code: ` +namespace Foo { +} + `, + options: ['1tbs'], + }, + { + code: ` +namespace Foo { +} + `, + options: ['stroustrup'], + }, + { + code: ` +namespace Foo +{ +} + `, + options: ['allman'], + }, + ], + + invalid: [ + { + code: ` +if (f) { + bar; +} +else + baz; + `, + output: ` +if (f) { + bar; +} else + baz; + `, + errors: [{ messageId: 'nextLineClose' }], + }, + { + code: `var foo = () => { return; }`, + output: `var foo = () => {\n return; \n}`, + parserOptions: { ecmaVersion: 6 }, + errors: [ + { messageId: 'blockSameLine' }, + { messageId: 'singleLineClose' }, + ], + }, + { + code: `function foo() { return; }`, + output: `function foo() {\n return; \n}`, + errors: [ + { messageId: 'blockSameLine' }, + { messageId: 'singleLineClose' }, + ], + }, + { + code: `function foo() \n { \n return; }`, + output: `function foo() { \n return; \n}`, + errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], + }, + { + code: `!function foo() \n { \n return; }`, + output: `!function foo() { \n return; \n}`, + errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], + }, + { + code: `if (foo) \n { \n bar(); }`, + output: `if (foo) { \n bar(); \n}`, + errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], + }, + { + code: `if (a) { \nb();\n } else \n { c(); }`, + output: `if (a) { \nb();\n } else {\n c(); \n}`, + errors: [ + { messageId: 'nextLineOpen' }, + { messageId: 'blockSameLine' }, + { messageId: 'singleLineClose' }, + ], + }, + { + code: `while (foo) \n { \n bar(); }`, + output: `while (foo) { \n bar(); \n}`, + errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], + }, + { + code: `for (;;) \n { \n bar(); }`, + output: `for (;;) { \n bar(); \n}`, + errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], + }, + { + code: `with (foo) \n { \n bar(); }`, + output: `with (foo) { \n bar(); \n}`, + errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], + }, + { + code: `switch (foo) \n { \n case 'bar': break; }`, + output: `switch (foo) { \n case 'bar': break; \n}`, + errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], + }, + { + code: `switch (foo) \n { }`, + output: `switch (foo) { }`, + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `try \n { \n bar(); \n } catch (e) {}`, + output: `try { \n bar(); \n } catch (e) {}`, + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `try { \n bar(); \n } catch (e) \n {}`, + output: `try { \n bar(); \n } catch (e) {}`, + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `do \n { \n bar(); \n} while (true)`, + output: `do { \n bar(); \n} while (true)`, + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `for (foo in bar) \n { \n baz(); \n }`, + output: `for (foo in bar) { \n baz(); \n }`, + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `for (foo of bar) \n { \n baz(); \n }`, + output: `for (foo of bar) { \n baz(); \n }`, + parserOptions: { ecmaVersion: 6 }, + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `try { \n bar(); \n }\ncatch (e) {\n}`, + output: `try { \n bar(); \n } catch (e) {\n}`, + errors: [{ messageId: 'nextLineClose' }], + }, + { + code: `try { \n bar(); \n } catch (e) {\n}\n finally {\n}`, + output: `try { \n bar(); \n } catch (e) {\n} finally {\n}`, + errors: [{ messageId: 'nextLineClose' }], + }, + { + code: `if (a) { \nb();\n } \n else { \nc();\n }`, + output: `if (a) { \nb();\n } else { \nc();\n }`, + errors: [{ messageId: 'nextLineClose' }], + }, + { + code: `try { \n bar(); \n }\ncatch (e) {\n} finally {\n}`, + output: `try { \n bar(); \n }\ncatch (e) {\n}\n finally {\n}`, + options: ['stroustrup'], + errors: [{ messageId: 'sameLineClose' }], + }, + { + code: `try { \n bar(); \n } catch (e) {\n}\n finally {\n}`, + output: `try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}`, + options: ['stroustrup'], + errors: [{ messageId: 'sameLineClose' }], + }, + { + code: `if (a) { \nb();\n } else { \nc();\n }`, + output: `if (a) { \nb();\n }\n else { \nc();\n }`, + options: ['stroustrup'], + errors: [{ messageId: 'sameLineClose' }], + }, + { + code: `if (foo) {\nbaz();\n} else if (bar) {\nbaz();\n}\nelse {\nqux();\n}`, + output: `if (foo) {\nbaz();\n}\n else if (bar) {\nbaz();\n}\nelse {\nqux();\n}`, + options: ['stroustrup'], + errors: [{ messageId: 'sameLineClose' }], + }, + { + code: `if (foo) {\npoop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}`, + output: `if (foo) {\npoop();\n} \nelse if (bar) {\nbaz();\n}\n else if (thing) {\nboom();\n}\nelse {\nqux();\n}`, + options: ['stroustrup'], + errors: [{ messageId: 'sameLineClose' }], + }, + { + code: `try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}`, + output: `try \n{ \n bar(); \n }\n catch (e) \n{\n}\n finally \n{\n}`, + options: ['allman'], + errors: [ + { messageId: 'sameLineOpen', line: 1 }, + { messageId: 'sameLineOpen', line: 4 }, + { messageId: 'sameLineOpen', line: 6 }, + ], + }, + { + code: `switch(x) { case 1: \nbar(); }\n `, + output: `switch(x) \n{\n case 1: \nbar(); \n}\n `, + options: ['allman'], + errors: [ + { messageId: 'sameLineOpen', line: 1 }, + { messageId: 'blockSameLine', line: 1 }, + { messageId: 'singleLineClose', line: 2 }, + ], + }, + { + code: `if (a) { \nb();\n } else { \nc();\n }`, + output: `if (a) \n{ \nb();\n }\n else \n{ \nc();\n }`, + options: ['allman'], + errors: [ + { messageId: 'sameLineOpen' }, + { messageId: 'sameLineClose' }, + { messageId: 'sameLineOpen' }, + ], + }, + { + code: `if (foo) {\nbaz();\n} else if (bar) {\nbaz();\n}\nelse {\nqux();\n}`, + output: `if (foo) \n{\nbaz();\n}\n else if (bar) \n{\nbaz();\n}\nelse \n{\nqux();\n}`, + options: ['allman'], + errors: [ + { messageId: 'sameLineOpen' }, + { messageId: 'sameLineClose' }, + { messageId: 'sameLineOpen' }, + { messageId: 'sameLineOpen' }, + ], + }, + { + code: `if (foo)\n{ poop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}`, + output: `if (foo)\n{\n poop();\n} \nelse if (bar) \n{\nbaz();\n}\n else if (thing) \n{\nboom();\n}\nelse \n{\nqux();\n}`, + options: ['allman'], + errors: [ + { messageId: 'blockSameLine' }, + { messageId: 'sameLineOpen' }, + { messageId: 'sameLineClose' }, + { messageId: 'sameLineOpen' }, + { messageId: 'sameLineOpen' }, + ], + }, + { + code: `if (foo)\n{\n bar(); }`, + output: `if (foo)\n{\n bar(); \n}`, + options: ['allman'], + errors: [{ messageId: 'singleLineClose' }], + }, + { + code: `try\n{\n somethingRisky();\n} catch (e)\n{\n handleError()\n}`, + output: `try\n{\n somethingRisky();\n}\n catch (e)\n{\n handleError()\n}`, + options: ['allman'], + errors: [{ messageId: 'sameLineClose' }], + }, + + // allowSingleLine: true + { + code: `function foo() { return; \n}`, + output: `function foo() {\n return; \n}`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'blockSameLine' }], + }, + { + code: `function foo() { a(); b(); return; \n}`, + output: `function foo() {\n a(); b(); return; \n}`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'blockSameLine' }], + }, + { + code: `function foo() { \n return; }`, + output: `function foo() { \n return; \n}`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'singleLineClose' }], + }, + { + code: `function foo() {\na();\nb();\nreturn; }`, + output: `function foo() {\na();\nb();\nreturn; \n}`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'singleLineClose' }], + }, + { + code: `!function foo() { \n return; }`, + output: `!function foo() { \n return; \n}`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'singleLineClose' }], + }, + { + code: `if (a) { b();\n } else { c(); }`, + output: `if (a) {\n b();\n } else { c(); }`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'blockSameLine' }], + }, + { + code: `if (a) { b(); }\nelse { c(); }`, + output: `if (a) { b(); } else { c(); }`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'nextLineClose' }], + }, + { + code: `while (foo) { \n bar(); }`, + output: `while (foo) { \n bar(); \n}`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'singleLineClose' }], + }, + { + code: `for (;;) { bar(); \n }`, + output: `for (;;) {\n bar(); \n }`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'blockSameLine' }], + }, + { + code: `with (foo) { bar(); \n }`, + output: `with (foo) {\n bar(); \n }`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'blockSameLine' }], + }, + { + code: `switch (foo) \n { \n case \`bar\`: break; }`, + output: `switch (foo) { \n case \`bar\`: break; \n}`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], + }, + { + code: `switch (foo) \n { }`, + output: `switch (foo) { }`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `try { bar(); }\ncatch (e) { baz(); }`, + output: `try { bar(); } catch (e) { baz(); }`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'nextLineClose' }], + }, + { + code: `try \n { \n bar(); \n } catch (e) {}`, + output: `try { \n bar(); \n } catch (e) {}`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `try { \n bar(); \n } catch (e) \n {}`, + output: `try { \n bar(); \n } catch (e) {}`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `do \n { \n bar(); \n} while (true)`, + output: `do { \n bar(); \n} while (true)`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `for (foo in bar) \n { \n baz(); \n }`, + output: `for (foo in bar) { \n baz(); \n }`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `try { \n bar(); \n }\ncatch (e) {\n}`, + output: `try { \n bar(); \n } catch (e) {\n}`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'nextLineClose' }], + }, + { + code: `try { \n bar(); \n } catch (e) {\n}\n finally {\n}`, + output: `try { \n bar(); \n } catch (e) {\n} finally {\n}`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'nextLineClose' }], + }, + { + code: `if (a) { \nb();\n } \n else { \nc();\n }`, + output: `if (a) { \nb();\n } else { \nc();\n }`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'nextLineClose' }], + }, + { + code: `try { \n bar(); \n }\ncatch (e) {\n} finally {\n}`, + output: `try { \n bar(); \n }\ncatch (e) {\n}\n finally {\n}`, + options: ['stroustrup', { allowSingleLine: true }], + errors: [{ messageId: 'sameLineClose' }], + }, + { + code: `try { \n bar(); \n } catch (e) {\n}\n finally {\n}`, + output: `try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}`, + options: ['stroustrup', { allowSingleLine: true }], + errors: [{ messageId: 'sameLineClose' }], + }, + { + code: `if (a) { \nb();\n } else { \nc();\n }`, + output: `if (a) { \nb();\n }\n else { \nc();\n }`, + options: ['stroustrup', { allowSingleLine: true }], + errors: [{ messageId: 'sameLineClose' }], + }, + { + code: `if (foo)\n{ poop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}`, + output: `if (foo)\n{\n poop();\n} \nelse if (bar) \n{\nbaz();\n}\n else if (thing) \n{\nboom();\n}\nelse \n{\nqux();\n}`, + options: ['allman', { allowSingleLine: true }], + errors: [ + { messageId: 'blockSameLine' }, + { messageId: 'sameLineOpen' }, + { messageId: 'sameLineClose' }, + { messageId: 'sameLineOpen' }, + { 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 }`, + output: `if (foo) {\n bar\n.baz \n}`, + errors: [{ messageId: 'singleLineClose' }], + }, + { + code: `if (foo)\n{\n bar\n.baz }`, + output: `if (foo)\n{\n bar\n.baz \n}`, + options: ['allman'], + errors: [{ messageId: 'singleLineClose' }], + }, + { + code: `if (foo) { bar\n.baz }`, + output: `if (foo) {\n bar\n.baz \n}`, + options: ['1tbs', { allowSingleLine: true }], + errors: [ + { messageId: 'blockSameLine' }, + { messageId: 'singleLineClose' }, + ], + }, + { + code: `if (foo) { bar\n.baz }`, + output: `if (foo) \n{\n bar\n.baz \n}`, + options: ['allman', { allowSingleLine: true }], + errors: [ + { messageId: 'sameLineOpen' }, + { messageId: 'blockSameLine' }, + { messageId: 'singleLineClose' }, + ], + }, + { + code: `switch (x) {\n case 1: foo() }`, + output: `switch (x) {\n case 1: foo() \n}`, + options: ['1tbs', { allowSingleLine: true }], + errors: [{ messageId: 'singleLineClose' }], + }, + { + code: `class Foo\n{\n}`, + output: `class Foo {\n}`, + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `(class\n{\n})`, + output: `(class {\n})`, + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `class Foo{\n}`, + output: `class Foo\n{\n}`, + options: ['allman'], + errors: [{ messageId: 'sameLineOpen' }], + }, + { + code: `(class {\n})`, + output: `(class \n{\n})`, + options: ['allman'], + errors: [{ messageId: 'sameLineOpen' }], + }, + { + code: `class Foo {\nbar() {\n}}`, + output: `class Foo {\nbar() {\n}\n}`, + errors: [{ messageId: 'singleLineClose' }], + }, + { + code: `(class Foo {\nbar() {\n}})`, + output: `(class Foo {\nbar() {\n}\n})`, + errors: [{ messageId: 'singleLineClose' }], + }, + { + code: `class\nFoo{}`, + output: `class\nFoo\n{}`, + options: ['allman'], + errors: [{ messageId: 'sameLineOpen' }], + }, + + // https://github.com/eslint/eslint/issues/7621 + { + code: ` +if (foo) +{ + bar +} +else { + baz +} + `, + output: ` +if (foo) { + bar +} else { + baz +} + `, + errors: [{ messageId: 'nextLineOpen' }, { messageId: 'nextLineClose' }], + }, + { + code: ` +interface Foo +{ +} + `, + output: ` +interface Foo { +} + `, + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: ` +interface Foo +{ +} + `, + output: ` +interface Foo { +} + `, + options: ['stroustrup'], + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `interface Foo { \n }`, + output: `interface Foo \n{ \n }`, + options: ['allman'], + errors: [{ messageId: 'sameLineOpen' }], + }, + { + code: ` +module "Foo" +{ +} + `, + output: ` +module "Foo" { +} + `, + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: ` +module "Foo" +{ +} + `, + output: ` +module "Foo" { +} + `, + options: ['stroustrup'], + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `module "Foo" { \n }`, + output: `module "Foo" \n{ \n }`, + options: ['allman'], + errors: [{ messageId: 'sameLineOpen' }], + }, + { + code: ` +namespace Foo +{ +} + `, + output: ` +namespace Foo { +} + `, + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: ` +namespace Foo +{ +} + `, + output: ` +namespace Foo { +} + `, + options: ['stroustrup'], + errors: [{ messageId: 'nextLineOpen' }], + }, + { + code: `namespace Foo { \n }`, + output: `namespace Foo \n{ \n }`, + options: ['allman'], + errors: [{ messageId: 'sameLineOpen' }], + }, + ], +}); diff --git a/packages/eslint-plugin/tools/generate-configs.ts b/packages/eslint-plugin/tools/generate-configs.ts index b400ca05722..664504783f0 100644 --- a/packages/eslint-plugin/tools/generate-configs.ts +++ b/packages/eslint-plugin/tools/generate-configs.ts @@ -22,6 +22,7 @@ const RULE_NAME_PREFIX = '@typescript-eslint/'; const MAX_RULE_NAME_LENGTH = 32; const DEFAULT_RULE_SETTING = 'warn'; const BASE_RULES_TO_BE_OVERRIDDEN = new Set([ + 'brace-style', 'camelcase', 'func-call-spacing', 'indent', diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index 9eb02d5cce0..de6545f744d 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -482,3 +482,30 @@ declare module 'eslint/lib/rules/quotes' { >; export = rule; } + +declare module 'eslint/lib/rules/brace-style' { + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; + + const rule: TSESLint.RuleModule< + | 'nextLineOpen' + | 'sameLineOpen' + | 'blockSameLine' + | 'nextLineClose' + | 'singleLineClose' + | 'sameLineClose', + [ + '1tbs' | 'stroustrup' | 'allman', + { + allowSingleLine?: boolean; + }?, + ], + { + BlockStatement(node: TSESTree.BlockStatement): void; + ClassBody(node: TSESTree.ClassBody): void; + SwitchStatement(node: TSESTree.SwitchStatement): void; + IfStatement(node: TSESTree.IfStatement): void; + TryStatement(node: TSESTree.TryStatement): void; + } + >; + export = rule; +}