From 74d7e537927643393320c48d31363b0ff013e364 Mon Sep 17 00:00:00 2001 From: Florian Adonis Date: Fri, 12 Mar 2021 18:54:34 +0300 Subject: [PATCH 1/7] fix(eslint-plugin): do not fix code when semi creates error in multiline --- .../src/rules/member-delimiter-style.ts | 100 +++++++--- .../rules/member-delimiter-style.test.ts | 183 ++---------------- 2 files changed, 95 insertions(+), 188 deletions(-) diff --git a/packages/eslint-plugin/src/rules/member-delimiter-style.ts b/packages/eslint-plugin/src/rules/member-delimiter-style.ts index 64147b37cf2a..9b04a9aec3e3 100644 --- a/packages/eslint-plugin/src/rules/member-delimiter-style.ts +++ b/packages/eslint-plugin/src/rules/member-delimiter-style.ts @@ -2,6 +2,10 @@ import { TSESTree, AST_NODE_TYPES, } from '@typescript-eslint/experimental-utils'; +import { + RuleFix, + RuleFixer, +} from '@typescript-eslint/experimental-utils/src/ts-eslint'; import * as util from '../util'; type Delimiter = 'comma' | 'none' | 'semi'; @@ -11,6 +15,9 @@ type TypeOptions = { delimiter?: Delimiter; requireLast?: boolean; }; +type TypeOptionsWithType = TypeOptions & { + type: string; +}; // eslint-disable-next-line @typescript-eslint/consistent-type-definitions type BaseOptions = { multiline?: TypeOptions; @@ -21,7 +28,7 @@ type Config = BaseOptions & { typeLiteral?: BaseOptions; interface?: BaseOptions; }; - multilineDetection?: 'brackets' | 'last-member'; + multilineDetection?: string; }; type Options = [Config]; type MessageIds = @@ -29,6 +36,18 @@ type MessageIds = | 'unexpectedSemi' | 'expectedComma' | 'expectedSemi'; +type LastTokenType = TSESTree.Token; + +interface MakeFixFunctionParams { + optsNone: boolean; + optsSemi: boolean; + lastToken: LastTokenType; + missingDelimiter: boolean; + lastTokenLine: string; + isSingleLine: boolean; +} + +type MakeFixFunctionReturnType = ((fixer: RuleFixer) => RuleFix) | null; const definition = { type: 'object', @@ -54,6 +73,47 @@ const definition = { additionalProperties: false, }; +const isLastTokenEndOfLine = (token: string, line: string): boolean => { + const positionInLine = line.indexOf(token); + + return positionInLine === line.length - 1; +}; + +const makeFixFunction = ({ + optsNone, + optsSemi, + lastToken, + missingDelimiter, + lastTokenLine, + isSingleLine, +}: MakeFixFunctionParams): MakeFixFunctionReturnType => { + // if removing is the action but last token is not the end of the line + if ( + optsNone && + !isLastTokenEndOfLine(lastToken.value, lastTokenLine) && + !isSingleLine + ) { + return null; + } + + return (fixer: RuleFixer): RuleFix => { + if (optsNone) { + // remove the unneeded token + return fixer.remove(lastToken); + } + + const token = optsSemi ? ';' : ','; + + if (missingDelimiter) { + // add the missing delimiter + return fixer.insertTextAfter(lastToken, token); + } + + // correct the current delimiter + return fixer.replaceText(lastToken, token); + }; +}; + export default util.createRule({ name: 'member-delimiter-style', meta: { @@ -83,9 +143,6 @@ export default util.createRule({ }, additionalProperties: false, }, - multilineDetection: { - enum: ['brackets', 'last-member'], - }, }), additionalProperties: false, }, @@ -101,7 +158,6 @@ export default util.createRule({ delimiter: 'semi', requireLast: false, }, - multilineDetection: 'brackets', }, ], create(context, [options]) { @@ -127,7 +183,7 @@ export default util.createRule({ */ function checkLastToken( member: TSESTree.TypeElement, - opts: TypeOptions, + opts: TypeOptionsWithType, isLast: boolean, ): void { /** @@ -147,10 +203,14 @@ export default util.createRule({ const lastToken = sourceCode.getLastToken(member, { includeComments: false, }); + if (!lastToken) { return; } + const sourceCodeLines = sourceCode.getLines(); + const lastTokenLine = sourceCodeLines[lastToken?.loc.start.line - 1]; + const optsSemi = getOption('semi'); const optsComma = getOption('comma'); const optsNone = getOption('none'); @@ -193,22 +253,14 @@ export default util.createRule({ }, }, messageId, - fix(fixer) { - if (optsNone) { - // remove the unneeded token - return fixer.remove(lastToken); - } - - const token = optsSemi ? ';' : ','; - - if (missingDelimiter) { - // add the missing delimiter - return fixer.insertTextAfter(lastToken, token); - } - - // correct the current delimiter - return fixer.replaceText(lastToken, token); - }, + fix: makeFixFunction({ + optsNone, + optsSemi, + lastToken, + missingDelimiter, + lastTokenLine, + isSingleLine: opts.type === 'single-line', + }), }); } } @@ -239,7 +291,9 @@ export default util.createRule({ node.type === AST_NODE_TYPES.TSInterfaceBody ? interfaceOptions : typeLiteralOptions; - const opts = isSingleLine ? typeOpts.singleline : typeOpts.multiline; + const opts = isSingleLine + ? { ...typeOpts.singleline, type: 'single-line' } + : { ...typeOpts.multiline, type: 'multi-line' }; members.forEach((member, index) => { checkLastToken(member, opts ?? {}, index === members.length - 1); 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 c69e54e2af00..0858a229c794 100644 --- a/packages/eslint-plugin/tests/rules/member-delimiter-style.test.ts +++ b/packages/eslint-plugin/tests/rules/member-delimiter-style.test.ts @@ -705,75 +705,6 @@ 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: ` @@ -905,6 +836,24 @@ interface Foo { }, { code: ` +type Test = { + a: { + one: 1 + }; b: 2 +}; + `, + output: null, + options: [{ multiline: { delimiter: 'none' } }], + errors: [ + { + messageId: 'unexpectedSemi', + line: 5, + column: 5, + }, + ], + }, + { + code: ` interface Foo { name: string age: number @@ -3434,101 +3383,5 @@ 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, - }, - ], - }, ], }); From 14491bceec7c9514ef325c51fe4146aa636b8385 Mon Sep 17 00:00:00 2001 From: Florian Adonis Date: Fri, 12 Mar 2021 20:33:55 +0300 Subject: [PATCH 2/7] fix(eslint-plugin): change Config multilineDetection type --- packages/eslint-plugin/src/rules/member-delimiter-style.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/member-delimiter-style.ts b/packages/eslint-plugin/src/rules/member-delimiter-style.ts index 9b04a9aec3e3..031d30de93c4 100644 --- a/packages/eslint-plugin/src/rules/member-delimiter-style.ts +++ b/packages/eslint-plugin/src/rules/member-delimiter-style.ts @@ -28,7 +28,7 @@ type Config = BaseOptions & { typeLiteral?: BaseOptions; interface?: BaseOptions; }; - multilineDetection?: string; + multilineDetection?: 'last-member'; }; type Options = [Config]; type MessageIds = From a27e2e38c955f9211c8f223ce966b289f128e779 Mon Sep 17 00:00:00 2001 From: Florian Adonis Date: Tue, 16 Mar 2021 09:31:19 +0300 Subject: [PATCH 3/7] fix(eslint-plugin): fix RuleFix and RuleFixer imports --- .../eslint-plugin/src/rules/member-delimiter-style.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/src/rules/member-delimiter-style.ts b/packages/eslint-plugin/src/rules/member-delimiter-style.ts index 031d30de93c4..c273f9ef62cc 100644 --- a/packages/eslint-plugin/src/rules/member-delimiter-style.ts +++ b/packages/eslint-plugin/src/rules/member-delimiter-style.ts @@ -1,11 +1,8 @@ import { + TSESLint, TSESTree, AST_NODE_TYPES, } from '@typescript-eslint/experimental-utils'; -import { - RuleFix, - RuleFixer, -} from '@typescript-eslint/experimental-utils/src/ts-eslint'; import * as util from '../util'; type Delimiter = 'comma' | 'none' | 'semi'; @@ -47,7 +44,9 @@ interface MakeFixFunctionParams { isSingleLine: boolean; } -type MakeFixFunctionReturnType = ((fixer: RuleFixer) => RuleFix) | null; +type MakeFixFunctionReturnType = + | ((fixer: TSESLint.RuleFixer) => TSESLint.RuleFix) + | null; const definition = { type: 'object', @@ -96,7 +95,7 @@ const makeFixFunction = ({ return null; } - return (fixer: RuleFixer): RuleFix => { + return (fixer: TSESLint.RuleFixer): TSESLint.RuleFix => { if (optsNone) { // remove the unneeded token return fixer.remove(lastToken); From e5353a9ea7bdff443335c057e61f75de893ec901 Mon Sep 17 00:00:00 2001 From: Florian Adonis Date: Tue, 16 Mar 2021 09:34:15 +0300 Subject: [PATCH 4/7] fix(eslint-plugin): put back multilineDetection option in Config type --- packages/eslint-plugin/src/rules/member-delimiter-style.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/member-delimiter-style.ts b/packages/eslint-plugin/src/rules/member-delimiter-style.ts index c273f9ef62cc..5c71d7e5a706 100644 --- a/packages/eslint-plugin/src/rules/member-delimiter-style.ts +++ b/packages/eslint-plugin/src/rules/member-delimiter-style.ts @@ -25,7 +25,7 @@ type Config = BaseOptions & { typeLiteral?: BaseOptions; interface?: BaseOptions; }; - multilineDetection?: 'last-member'; + multilineDetection?: 'brackets' | 'last-member'; }; type Options = [Config]; type MessageIds = From 75e83de73f53175e9d38a6f47ae1ff46c1b0c853 Mon Sep 17 00:00:00 2001 From: Florian Adonis Date: Tue, 16 Mar 2021 09:39:11 +0300 Subject: [PATCH 5/7] fix(eslint-plugin): put back multilineDetection into schema --- packages/eslint-plugin/src/rules/member-delimiter-style.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/eslint-plugin/src/rules/member-delimiter-style.ts b/packages/eslint-plugin/src/rules/member-delimiter-style.ts index 5c71d7e5a706..de0a60f013cf 100644 --- a/packages/eslint-plugin/src/rules/member-delimiter-style.ts +++ b/packages/eslint-plugin/src/rules/member-delimiter-style.ts @@ -142,6 +142,9 @@ export default util.createRule({ }, additionalProperties: false, }, + multilineDetection: { + enum: ['brackets', 'last-member'], + }, }), additionalProperties: false, }, @@ -157,6 +160,7 @@ export default util.createRule({ delimiter: 'semi', requireLast: false, }, + multilineDetection: 'brackets', }, ], create(context, [options]) { From f636acfd94e015ed33298a668ee49fe05680f9d9 Mon Sep 17 00:00:00 2001 From: Florian Adonis Date: Tue, 16 Mar 2021 09:43:10 +0300 Subject: [PATCH 6/7] fix(eslint-plugin): put back tests as they are on master --- .../rules/member-delimiter-style.test.ts | 183 ++++++++++++++++-- 1 file changed, 165 insertions(+), 18 deletions(-) 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 0858a229c794..c69e54e2af00 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: ` @@ -836,24 +905,6 @@ interface Foo { }, { code: ` -type Test = { - a: { - one: 1 - }; b: 2 -}; - `, - output: null, - options: [{ multiline: { delimiter: 'none' } }], - errors: [ - { - messageId: 'unexpectedSemi', - line: 5, - column: 5, - }, - ], - }, - { - code: ` interface Foo { name: string age: number @@ -3383,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, + }, + ], + }, ], }); From 5d2b9c27dd7986cd10349576f9e9e68522d5fd0f Mon Sep 17 00:00:00 2001 From: Florian Adonis Date: Tue, 16 Mar 2021 09:45:34 +0300 Subject: [PATCH 7/7] test(eslint-plugin): test new fix --- .../tests/rules/member-delimiter-style.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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 c69e54e2af00..fa75383df8e3 100644 --- a/packages/eslint-plugin/tests/rules/member-delimiter-style.test.ts +++ b/packages/eslint-plugin/tests/rules/member-delimiter-style.test.ts @@ -851,6 +851,24 @@ interface Foo { }, { code: ` +type Test = { + a: { + one: 1 + }; b: 2 +}; + `, + output: null, + options: [{ multiline: { delimiter: 'none' } }], + errors: [ + { + messageId: 'unexpectedSemi', + line: 5, + column: 5, + }, + ], + }, + { + code: ` interface Foo { name: string age: number