diff --git a/packages/eslint-plugin/docs/rules/member-delimiter-style.md b/packages/eslint-plugin/docs/rules/member-delimiter-style.md index ed1b0957756..c6f7ca47000 100644 --- a/packages/eslint-plugin/docs/rules/member-delimiter-style.md +++ b/packages/eslint-plugin/docs/rules/member-delimiter-style.md @@ -72,6 +72,7 @@ type Config = BaseConfig & { interface?: BaseConfig; typeLiteral?: BaseConfig; }; + multilineDetection?: 'brackets' | 'last-member'; }; ``` @@ -86,7 +87,8 @@ Default config: "singleline": { "delimiter": "semi", "requireLast": false - } + }, + "multilineDetection": "brackets" } ``` @@ -94,6 +96,11 @@ Default config: `singleline` config only applies to single line `interface`/`type` definitions. The two configs are entirely separate, and do not effect one another. +`multilineDetection` determines what counts as multiline + +- `"brackets"` (default) any newlines in the type or interface make it multiline. +- `"last-member"` if the last member of the interface is on the same line as the last bracket, it is counted as a single line. + ### `delimiter` Accepts three values (or two for `singleline`): diff --git a/packages/eslint-plugin/src/rules/member-delimiter-style.ts b/packages/eslint-plugin/src/rules/member-delimiter-style.ts index 75381a8df2c..64147b37cf2 100644 --- a/packages/eslint-plugin/src/rules/member-delimiter-style.ts +++ b/packages/eslint-plugin/src/rules/member-delimiter-style.ts @@ -21,6 +21,7 @@ type Config = BaseOptions & { typeLiteral?: BaseOptions; interface?: BaseOptions; }; + multilineDetection?: 'brackets' | 'last-member'; }; type Options = [Config]; type MessageIds = @@ -82,6 +83,9 @@ export default util.createRule({ }, additionalProperties: false, }, + multilineDetection: { + enum: ['brackets', 'last-member'], + }, }), additionalProperties: false, }, @@ -97,6 +101,7 @@ export default util.createRule({ delimiter: 'semi', requireLast: false, }, + multilineDetection: 'brackets', }, ], create(context, [options]) { @@ -215,11 +220,21 @@ export default util.createRule({ function checkMemberSeparatorStyle( node: TSESTree.TSInterfaceBody | TSESTree.TSTypeLiteral, ): void { - const isSingleLine = node.loc.start.line === node.loc.end.line; - const members = node.type === AST_NODE_TYPES.TSInterfaceBody ? node.body : node.members; + let isSingleLine = node.loc.start.line === node.loc.end.line; + if ( + options.multilineDetection === 'last-member' && + !isSingleLine && + members.length > 0 + ) { + const lastMember = members[members.length - 1]; + if (lastMember.loc.end.line === node.loc.end.line) { + isSingleLine = true; + } + } + const typeOpts = node.type === AST_NODE_TYPES.TSInterfaceBody ? interfaceOptions diff --git a/packages/eslint-plugin/tests/rules/member-delimiter-style.test.ts b/packages/eslint-plugin/tests/rules/member-delimiter-style.test.ts index d0fa7895b9c..c69e54e2af0 100644 --- a/packages/eslint-plugin/tests/rules/member-delimiter-style.test.ts +++ b/packages/eslint-plugin/tests/rules/member-delimiter-style.test.ts @@ -705,6 +705,75 @@ type Bar = { }, ], }, + { + code: ` +type Foo = {a: { + b: true; +}}; + `, + options: [ + { + multilineDetection: 'last-member', + }, + ], + }, + ` +type Foo = {a: { + b: true; +};}; + `, + { + code: ` +type Foo = {a: { + b: true; +};}; + `, + options: [ + { + multilineDetection: 'brackets', + }, + ], + }, + { + code: ` +type Foo = { + a: { + b: true; + }; +}; + `, + options: [ + { + multilineDetection: 'last-member', + }, + ], + }, + { + code: ` +type Foo = {a: { + b: true; +};}; + `, + options: [ + { + singleline: { delimiter: 'semi', requireLast: true }, + multilineDetection: 'last-member', + }, + ], + }, + { + code: ` +type Foo = { + +}; + `, + options: [ + { + singleline: { delimiter: 'semi', requireLast: true }, + multilineDetection: 'last-member', + }, + ], + }, { code: ` @@ -3365,5 +3434,101 @@ type Foo = { }, ], }, + { + code: ` +type Foo = {a: { + b: true; +};}; + `, + output: ` +type Foo = {a: { + b: true; +}}; + `, + options: [ + { + multilineDetection: 'last-member', + }, + ], + errors: [ + { + messageId: 'unexpectedSemi', + line: 4, + column: 3, + }, + ], + }, + { + code: ` +type Foo = {a: { + b: true; +}}; + `, + output: ` +type Foo = {a: { + b: true; +};}; + `, + errors: [ + { + messageId: 'expectedSemi', + line: 4, + column: 2, + }, + ], + }, + { + code: ` +type Foo = { + a: { + b: true; + } +}; + `, + output: ` +type Foo = { + a: { + b: true; + }; +}; + `, + options: [ + { + multilineDetection: 'last-member', + }, + ], + errors: [ + { + messageId: 'expectedSemi', + line: 5, + column: 4, + }, + ], + }, + { + code: ` +type Foo = {a: { + b: true; +}}; + `, + output: ` +type Foo = {a: { + b: true; +};}; + `, + options: [ + { + singleline: { delimiter: 'semi', requireLast: true }, + multilineDetection: 'last-member', + }, + ], + errors: [ + { + messageId: 'expectedSemi', + line: 4, + column: 2, + }, + ], + }, ], });