From eb02b4d84d8997254955930ff0042699cdac6f9d Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Wed, 14 Dec 2022 01:21:34 +0100 Subject: [PATCH 01/40] =?UTF-8?q?=F0=9F=9A=A7=20key-spacing=20for=20interf?= =?UTF-8?q?ace=20on=20default=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eslint-plugin/src/rules/key-spacing.ts | 90 +++++++++++++++++++ .../src/util/getESLintCoreRule.ts | 1 + .../tests/rules/key-spacing.test.ts | 28 ++++++ .../eslint-plugin/typings/eslint-rules.d.ts | 25 ++++++ 4 files changed, 144 insertions(+) create mode 100644 packages/eslint-plugin/src/rules/key-spacing.ts create mode 100644 packages/eslint-plugin/tests/rules/key-spacing.test.ts diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts new file mode 100644 index 000000000000..8d5c0806ac44 --- /dev/null +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -0,0 +1,90 @@ +/* eslint-disable no-console */ +import type { TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import * as util from '../util'; +import { getESLintCoreRule } from '../util/getESLintCoreRule'; + +const baseRule = getESLintCoreRule('key-spacing'); + +export type Options = util.InferOptionsTypeFromRule; +export type MessageIds = util.InferMessageIdsTypeFromRule; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment +const baseSchema = Array.isArray(baseRule.meta.schema) + ? baseRule.meta.schema[0] + : baseRule.meta.schema; + +export default util.createRule({ + name: 'key-spacing', + meta: { + type: 'layout', + docs: { + description: + 'Enforce consistent spacing between keys and values in types and interfaces', + recommended: false, + extendsBaseRule: true, + }, + fixable: 'whitespace', + hasSuggestions: baseRule.meta.hasSuggestions, + schema: [baseSchema], + messages: baseRule.meta.messages, + }, + defaultOptions: [{}], + + create(context) { + const sourceCode = context.getSourceCode(); + const baseRules = baseRule.create(context); + return { + ...baseRules, + "TSTypeAliasDeclaration[typeAnnotation.type='TSTypeLiteral']"( + node: TSESTree.TSTypeAliasDeclaration, + ): void { + console.log('...'); + // Todo + }, + TSInterfaceDeclaration(node): void { + const interfaceBody = node.body; + + let minStart = 0; + + for (const node of interfaceBody.body) { + if ( + node.type === AST_NODE_TYPES.TSPropertySignature && + node.typeAnnotation + ) { + // In case of single-line interface declaration, skip rule + if (node.loc.start.line === interfaceBody.loc.start.line) { + return; + } + + minStart = Math.max( + minStart, + node.typeAnnotation.loc.start.column + ': '.length, + ); + } + } + + for (const node of interfaceBody.body) { + if ( + node.type === AST_NODE_TYPES.TSPropertySignature && + node.typeAnnotation + ) { + const start = node.typeAnnotation.typeAnnotation.loc.start.column; + + if (start !== minStart) { + context.report({ + node, + messageId: start > minStart ? 'extraValue' : 'missingValue', + data: { + computed: '', + key: sourceCode.getText(node.key), + }, + }); + } + } + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/util/getESLintCoreRule.ts b/packages/eslint-plugin/src/util/getESLintCoreRule.ts index 1678903acd32..80962a677b05 100644 --- a/packages/eslint-plugin/src/util/getESLintCoreRule.ts +++ b/packages/eslint-plugin/src/util/getESLintCoreRule.ts @@ -12,6 +12,7 @@ interface RuleMap { 'dot-notation': typeof import('eslint/lib/rules/dot-notation'); indent: typeof import('eslint/lib/rules/indent'); 'init-declarations': typeof import('eslint/lib/rules/init-declarations'); + 'key-spacing': typeof import('eslint/lib/rules/key-spacing'); 'keyword-spacing': typeof import('eslint/lib/rules/keyword-spacing'); 'lines-between-class-members': typeof import('eslint/lib/rules/lines-between-class-members'); 'no-dupe-args': typeof import('eslint/lib/rules/no-dupe-args'); diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts new file mode 100644 index 000000000000..66a632a78433 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -0,0 +1,28 @@ +/* eslint-disable eslint-comments/no-use */ +// this rule tests the new lines, which prettier will want to fix and break the tests +/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ +/* eslint-enable eslint-comments/no-use */ +import rule from '../../src/rules/key-spacing'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('key-spacing', rule, { + valid: [ + { + code: 'interface X {\n a: number;\n abc: string\n};', + }, + ], + invalid: [ + { + code: 'interface X {\n a: number;\n abc: string\n};', + errors: [{ messageId: 'missingValue' }], + }, + { + code: 'interface X {\n a: number;\n abc: string\n};', + errors: [{ messageId: 'extraValue' }], + }, + ], +}); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index 09b54ae4a516..82825487f86e 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -141,6 +141,31 @@ declare module 'eslint/lib/rules/indent' { export = rule; } +declare module 'eslint/lib/rules/key-spacing' { + import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; + import type { RuleFunction } from '@typescript-eslint/utils/dist/ts-eslint'; + + type Options = [ + { + beforeColon?: boolean; + afterColon?: boolean; + mode?: 'strict' | 'minimum'; + align?: 'value' | 'colon'; + }, + ]; + type MessageIds = 'extraKey' | 'extraValue' | 'missingKey' | 'missingValue'; + + const rule: TSESLint.RuleModule< + MessageIds, + Options, + { + ObjectExpression: RuleFunction; + Property: RuleFunction; + } + >; + export = rule; +} + declare module 'eslint/lib/rules/keyword-spacing' { import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import type { RuleFunction } from '@typescript-eslint/utils/dist/ts-eslint'; From 6d78fe8798b5fe56cf5321e0636cf2148d4c0ec1 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Wed, 14 Dec 2022 01:51:58 +0100 Subject: [PATCH 02/40] =?UTF-8?q?=F0=9F=9A=A7=20Support=20type=20literals?= =?UTF-8?q?=20as=20welll?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eslint-plugin/src/rules/key-spacing.ts | 85 +++++++++---------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 8d5c0806ac44..1180426fc7ae 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; @@ -35,56 +34,56 @@ export default util.createRule({ create(context) { const sourceCode = context.getSourceCode(); const baseRules = baseRule.create(context); - return { - ...baseRules, - "TSTypeAliasDeclaration[typeAnnotation.type='TSTypeLiteral']"( - node: TSESTree.TSTypeAliasDeclaration, - ): void { - console.log('...'); - // Todo - }, - TSInterfaceDeclaration(node): void { - const interfaceBody = node.body; - let minStart = 0; + function validateBody( + body: TSESTree.TSTypeLiteral | TSESTree.TSInterfaceBody, + ): void { + let minStart = 0; - for (const node of interfaceBody.body) { - if ( - node.type === AST_NODE_TYPES.TSPropertySignature && - node.typeAnnotation - ) { - // In case of single-line interface declaration, skip rule - if (node.loc.start.line === interfaceBody.loc.start.line) { - return; - } + // In case of single-line interface declaration, skip rule + if (body.loc.start.line === body.loc.end.line) { + return; + } - minStart = Math.max( - minStart, - node.typeAnnotation.loc.start.column + ': '.length, - ); - } + const members = 'members' in body ? body.members : body.body; + + for (const node of members) { + if ( + (node.type === AST_NODE_TYPES.TSPropertySignature || + node.type === AST_NODE_TYPES.TSIndexSignature) && + node.typeAnnotation + ) { + minStart = Math.max( + minStart, + node.typeAnnotation.loc.start.column + ': '.length, + ); } + } - for (const node of interfaceBody.body) { - if ( - node.type === AST_NODE_TYPES.TSPropertySignature && - node.typeAnnotation - ) { - const start = node.typeAnnotation.typeAnnotation.loc.start.column; + for (const node of members) { + if ( + node.type === AST_NODE_TYPES.TSPropertySignature && + node.typeAnnotation + ) { + const start = node.typeAnnotation.typeAnnotation.loc.start.column; - if (start !== minStart) { - context.report({ - node, - messageId: start > minStart ? 'extraValue' : 'missingValue', - data: { - computed: '', - key: sourceCode.getText(node.key), - }, - }); - } + if (start !== minStart) { + context.report({ + node, + messageId: start > minStart ? 'extraValue' : 'missingValue', + data: { + computed: '', + key: sourceCode.getText(node.key), + }, + }); } } - }, + } + } + return { + ...baseRules, + TSTypeLiteral: validateBody, + TSInterfaceBody: validateBody, }; }, }); From 259a3ca7ae4daac6ed0c6bf1a875f09392706c82 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 00:20:17 +0100 Subject: [PATCH 03/40] =?UTF-8?q?=F0=9F=9A=A7=20Add=20full=20typing=20for?= =?UTF-8?q?=20the=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/eslint-plugin/src/rules/index.ts | 2 + .../eslint-plugin/src/rules/key-spacing.ts | 198 ++++++++++++++++-- .../tests/rules/key-spacing.test.ts | 22 ++ .../eslint-plugin/typings/eslint-rules.d.ts | 12 +- 4 files changed, 215 insertions(+), 19 deletions(-) diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 8a3c2bbf4371..f7e51fdabd58 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -22,6 +22,7 @@ import explicitModuleBoundaryTypes from './explicit-module-boundary-types'; import funcCallSpacing from './func-call-spacing'; import indent from './indent'; import initDeclarations from './init-declarations'; +import keySpacing from './key-spacing'; import keywordSpacing from './keyword-spacing'; import linesBetweenClassMembers from './lines-between-class-members'; import memberDelimiterStyle from './member-delimiter-style'; @@ -153,6 +154,7 @@ export default { 'func-call-spacing': funcCallSpacing, indent: indent, 'init-declarations': initDeclarations, + 'key-spacing': keySpacing, 'keyword-spacing': keywordSpacing, 'lines-between-class-members': linesBetweenClassMembers, 'member-delimiter-style': memberDelimiterStyle, diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 1180426fc7ae..5164d0d8b3b6 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -31,53 +31,215 @@ export default util.createRule({ }, defaultOptions: [{}], - create(context) { + create(context, [options]) { const sourceCode = context.getSourceCode(); const baseRules = baseRule.create(context); - function validateBody( - body: TSESTree.TSTypeLiteral | TSESTree.TSInterfaceBody, + function checkBeforeColon( + node: TSESTree.TSIndexSignature | TSESTree.TSPropertySignature, + key: TSESTree.PropertyName | TSESTree.Parameter, + nBeforeColon: number, ): void { - let minStart = 0; + const colon = node.typeAnnotation!.loc.start.column; + const keyEnd = key.loc.end.column; + const expectedDiff = nBeforeColon; + if (colon - keyEnd !== expectedDiff) { + context.report({ + node, + messageId: colon - keyEnd > expectedDiff ? 'extraKey' : 'missingKey', + data: { + computed: '', + key: sourceCode.getText(key), + }, + }); + } + } - // In case of single-line interface declaration, skip rule - if (body.loc.start.line === body.loc.end.line) { - return; + function checkAfterColon( + node: TSESTree.TSIndexSignature | TSESTree.TSPropertySignature, + key: TSESTree.PropertyName | TSESTree.Parameter, + nAfterColon: number, + ): void { + const colon = node.typeAnnotation!.loc.start.column; + const typeStart = node.typeAnnotation!.typeAnnotation.loc.start.column; + const expectedDiff = nAfterColon + 1; + if (typeStart - colon !== expectedDiff) { + context.report({ + node, + messageId: + typeStart - colon > expectedDiff ? 'extraValue' : 'missingValue', + data: { + computed: '', + key: sourceCode.getText(key), + }, + }); } + } - const members = 'members' in body ? body.members : body.body; + function checkAlignGroup(group: TSESTree.TypeElement[]): void { + let alignColumn = 0; + const align = + (typeof options.align === 'object' + ? options.align.on + : options.align) ?? 'colon'; + const beforeColon = + (typeof options.align === 'object' + ? options.align.beforeColon + : options.multiLine + ? options.multiLine.beforeColon + : options.beforeColon) ?? false; + const nBeforeColon = beforeColon ? 1 : 0; + const afterColon = + (typeof options.align === 'object' + ? options.align.afterColon + : options.multiLine + ? options.multiLine.afterColon + : options.beforeColon) ?? true; + const nAfterColon = afterColon ? 1 : 0; - for (const node of members) { + for (const node of group) { if ( (node.type === AST_NODE_TYPES.TSPropertySignature || node.type === AST_NODE_TYPES.TSIndexSignature) && node.typeAnnotation ) { - minStart = Math.max( - minStart, - node.typeAnnotation.loc.start.column + ': '.length, + const key = + 'key' in node + ? node.key + : node.parameters[node.parameters.length - 1]; + alignColumn = Math.max( + alignColumn, + align === 'colon' + ? key.loc.end.column + nBeforeColon + : node.typeAnnotation.loc.start.column + + ':'.length + + nAfterColon + + nBeforeColon, ); } } - for (const node of members) { + for (const node of group) { if ( - node.type === AST_NODE_TYPES.TSPropertySignature && + (node.type === AST_NODE_TYPES.TSPropertySignature || + node.type === AST_NODE_TYPES.TSIndexSignature) && node.typeAnnotation ) { - const start = node.typeAnnotation.typeAnnotation.loc.start.column; + const key = + 'key' in node + ? node.key + : node.parameters[node.parameters.length - 1]; + const start = + align === 'colon' + ? node.typeAnnotation.loc.start.column + : node.typeAnnotation.typeAnnotation.loc.start.column; - if (start !== minStart) { + if (start !== alignColumn) { context.report({ node, - messageId: start > minStart ? 'extraValue' : 'missingValue', + messageId: start > alignColumn ? 'extraValue' : 'missingValue', data: { computed: '', - key: sourceCode.getText(node.key), + key: sourceCode.getText(key), }, }); } + + if (align === 'colon') { + checkAfterColon(node, key, nAfterColon); + } else { + checkBeforeColon(node, key, nBeforeColon); + } + } + } + } + + function checkIndividualNode( + node: TSESTree.TypeElement, + { singleLine }: { singleLine: boolean }, + ): void { + const beforeColon = + (singleLine + ? options.singleLine + ? options.singleLine.beforeColon + : options.beforeColon + : options.multiLine + ? options.multiLine.beforeColon + : options.beforeColon) ?? false; + const nBeforeColon = beforeColon ? 1 : 0; + const afterColon = + (singleLine + ? options.singleLine + ? options.singleLine.afterColon + : options.afterColon + : options.multiLine + ? options.multiLine.afterColon + : options.afterColon) ?? true; + const nAfterColon = afterColon ? 1 : 0; + + if ( + (node.type === AST_NODE_TYPES.TSPropertySignature || + node.type === AST_NODE_TYPES.TSIndexSignature) && + node.typeAnnotation + ) { + const key = + 'key' in node + ? node.key + : node.parameters[node.parameters.length - 1]; + + checkBeforeColon(node, key, nBeforeColon); + checkAfterColon(node, key, nAfterColon); + } + } + + function validateBody( + body: TSESTree.TSTypeLiteral | TSESTree.TSInterfaceBody, + ): void { + const isSingleLine = body.loc.start.line === body.loc.end.line; + + const members = 'members' in body ? body.members : body.body; + + let alignGroups: TSESTree.TypeElement[][] = []; + let unalignedElements: TSESTree.TypeElement[] = []; + + if (options.align) { + let currentAlignGroup: TSESTree.TypeElement[] = []; + alignGroups.push(currentAlignGroup); + + for (const node of members) { + const prevNode = currentAlignGroup.length + ? currentAlignGroup[currentAlignGroup.length - 1] + : null; + + if (prevNode?.loc.start.line === node.loc.start.line - 1) { + currentAlignGroup.push(node); + } else if (prevNode?.loc.start.line === node.loc.start.line) { + if (currentAlignGroup.length) { + unalignedElements.push(currentAlignGroup.pop()!); + } + unalignedElements.push(node); + } else { + currentAlignGroup = [node]; + alignGroups.push(currentAlignGroup); + } } + + unalignedElements.push( + ...alignGroups + .filter(group => group.length === 1) + .flatMap(group => group), + ); + alignGroups = alignGroups.filter(group => group.length >= 2); + } else { + unalignedElements = members; + } + + for (const group of alignGroups) { + checkAlignGroup(group); + } + + for (const node of unalignedElements) { + checkIndividualNode(node, { singleLine: isSingleLine }); } } return { diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 66a632a78433..75388bf76c4e 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -13,16 +13,38 @@ ruleTester.run('key-spacing', rule, { valid: [ { code: 'interface X {\n a: number;\n abc: string\n};', + options: [{ align: 'value' }], + }, + { + // A blank line between two keys resets the alignment + code: 'interface X {\n a: number;\n\n abc: string\n};', + options: [{ align: 'value' }], }, ], invalid: [ { code: 'interface X {\n a: number;\n abc: string\n};', + options: [{ align: 'value' }], errors: [{ messageId: 'missingValue' }], }, { code: 'interface X {\n a: number;\n abc: string\n};', + options: [{ align: 'value' }], + errors: [{ messageId: 'extraValue' }], + }, + { + // A blank line between two keys resets the alignment + code: 'interface X {\n a: number;\n\n abc : string\n};', + options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }], }, + { + code: 'interface X {\n [x: number]: string;\n}', + errors: [{ messageId: 'extraValue' }], + }, + { + code: 'interface X {\n [x: number]:string;\n}', + errors: [{ messageId: 'missingValue' }], + }, ], }); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index 82825487f86e..434da4cbd793 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -150,7 +150,17 @@ declare module 'eslint/lib/rules/key-spacing' { beforeColon?: boolean; afterColon?: boolean; mode?: 'strict' | 'minimum'; - align?: 'value' | 'colon'; + align?: + | 'value' + | 'colon' + | { + on: 'value' | 'colon'; + beforeColon?: boolean; + afterColon?: boolean; + mode?: 'strict' | 'minimum'; + }; + singleLine?: { beforeColon?: boolean; afterColon?: boolean }; + multiLine?: { beforeColon?: boolean; afterColon?: boolean }; }, ]; type MessageIds = 'extraKey' | 'extraValue' | 'missingKey' | 'missingValue'; From 8f32b5d357b52fd29580cf0d7e23e049caa78268 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 00:46:35 +0100 Subject: [PATCH 04/40] =?UTF-8?q?=F0=9F=9A=A7=20Add=20'mode'=20param?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eslint-plugin/src/rules/key-spacing.ts | 40 +++++++++++++++---- .../eslint-plugin/typings/eslint-rules.d.ts | 13 +++++- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 5164d0d8b3b6..9671a518d956 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -39,11 +39,16 @@ export default util.createRule({ node: TSESTree.TSIndexSignature | TSESTree.TSPropertySignature, key: TSESTree.PropertyName | TSESTree.Parameter, nBeforeColon: number, + mode: 'strict' | 'minimum', ): void { const colon = node.typeAnnotation!.loc.start.column; const keyEnd = key.loc.end.column; const expectedDiff = nBeforeColon; - if (colon - keyEnd !== expectedDiff) { + if ( + mode === 'strict' + ? colon - keyEnd !== expectedDiff + : colon - keyEnd < expectedDiff + ) { context.report({ node, messageId: colon - keyEnd > expectedDiff ? 'extraKey' : 'missingKey', @@ -59,11 +64,16 @@ export default util.createRule({ node: TSESTree.TSIndexSignature | TSESTree.TSPropertySignature, key: TSESTree.PropertyName | TSESTree.Parameter, nAfterColon: number, + mode: 'strict' | 'minimum', ): void { const colon = node.typeAnnotation!.loc.start.column; const typeStart = node.typeAnnotation!.typeAnnotation.loc.start.column; const expectedDiff = nAfterColon + 1; - if (typeStart - colon !== expectedDiff) { + if ( + mode === 'strict' + ? typeStart - colon !== expectedDiff + : typeStart - colon < expectedDiff + ) { context.report({ node, messageId: @@ -81,7 +91,7 @@ export default util.createRule({ const align = (typeof options.align === 'object' ? options.align.on - : options.align) ?? 'colon'; + : options.multiLine?.align ?? options.align) ?? 'colon'; const beforeColon = (typeof options.align === 'object' ? options.align.beforeColon @@ -96,6 +106,12 @@ export default util.createRule({ ? options.multiLine.afterColon : options.beforeColon) ?? true; const nAfterColon = afterColon ? 1 : 0; + const mode = + (typeof options.align === 'object' + ? options.align.mode + : options.multiLine + ? options.multiLine.mode + : options.mode) ?? 'strict'; for (const node of group) { if ( @@ -146,9 +162,9 @@ export default util.createRule({ } if (align === 'colon') { - checkAfterColon(node, key, nAfterColon); + checkAfterColon(node, key, nAfterColon, mode); } else { - checkBeforeColon(node, key, nBeforeColon); + checkBeforeColon(node, key, nBeforeColon, mode); } } } @@ -176,6 +192,14 @@ export default util.createRule({ ? options.multiLine.afterColon : options.afterColon) ?? true; const nAfterColon = afterColon ? 1 : 0; + const mode = + (singleLine + ? options.singleLine + ? options.singleLine.mode + : options.mode + : options.multiLine + ? options.multiLine.mode + : options.mode) ?? 'strict'; if ( (node.type === AST_NODE_TYPES.TSPropertySignature || @@ -187,8 +211,8 @@ export default util.createRule({ ? node.key : node.parameters[node.parameters.length - 1]; - checkBeforeColon(node, key, nBeforeColon); - checkAfterColon(node, key, nAfterColon); + checkBeforeColon(node, key, nBeforeColon, mode); + checkAfterColon(node, key, nAfterColon, mode); } } @@ -202,7 +226,7 @@ export default util.createRule({ let alignGroups: TSESTree.TypeElement[][] = []; let unalignedElements: TSESTree.TypeElement[] = []; - if (options.align) { + if (options.align || options.multiLine?.align) { let currentAlignGroup: TSESTree.TypeElement[] = []; alignGroups.push(currentAlignGroup); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index 434da4cbd793..4315edb23544 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -159,8 +159,17 @@ declare module 'eslint/lib/rules/key-spacing' { afterColon?: boolean; mode?: 'strict' | 'minimum'; }; - singleLine?: { beforeColon?: boolean; afterColon?: boolean }; - multiLine?: { beforeColon?: boolean; afterColon?: boolean }; + singleLine?: { + beforeColon?: boolean; + afterColon?: boolean; + mode?: 'strict' | 'minimum'; + }; + multiLine?: { + beforeColon?: boolean; + afterColon?: boolean; + mode?: 'strict' | 'minimum'; + align?: 'value' | 'colon'; + }; }, ]; type MessageIds = 'extraKey' | 'extraValue' | 'missingKey' | 'missingValue'; From 34bb6d8bb87300f42a8104c761aca34a75a08b66 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 02:06:46 +0100 Subject: [PATCH 05/40] =?UTF-8?q?=F0=9F=90=9B=20Fix=20index=20signatures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eslint-plugin/src/rules/key-spacing.ts | 107 ++++++++++++------ .../tests/rules/key-spacing.test.ts | 3 +- 2 files changed, 75 insertions(+), 35 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 9671a518d956..f6a3682a737c 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -35,26 +35,77 @@ export default util.createRule({ const sourceCode = context.getSourceCode(); const baseRules = baseRule.create(context); + /** + * To handle index signatures, to get the whole text for the parameters + */ + function getKeyText( + node: TSESTree.TSIndexSignature | TSESTree.TSPropertySignature, + ): string { + if ('key' in node) { + return sourceCode.getText(node); + } + + const code = sourceCode.getText(node); + const lastParam = node.parameters[node.parameters.length - 1]; + return code.slice( + 0, + code.indexOf(']', lastParam.range[1] - node.range[0]) + 1, + ); + } + + /** + * To handle index signatures, be able to get the end position of the parameters + */ + function getKeyLocEnd( + node: TSESTree.TSIndexSignature | TSESTree.TSPropertySignature, + ): TSESTree.Position { + if ('key' in node) { + return node.key.loc.end; + } + + // For index signatures, there's no easy way to get the location of the ending ']', we need to look at the source code + const code = sourceCode.getText(node); + const lastParam = node.parameters[node.parameters.length - 1]; + + const remaining = code.slice( + lastParam.range[1] - node.range[0], + code.indexOf(']', lastParam.range[1] - node.range[0]) + 1, + ); + const lines = remaining.split('\n'); + + if (lines.length === 1) { + return { + line: lastParam.loc.end.line, + column: lastParam.loc.end.column + remaining.length, + }; + } + + return { + line: lastParam.loc.end.line + lines.length - 1, + column: lines[lines.length - 1].length, + }; + } + function checkBeforeColon( node: TSESTree.TSIndexSignature | TSESTree.TSPropertySignature, - key: TSESTree.PropertyName | TSESTree.Parameter, nBeforeColon: number, mode: 'strict' | 'minimum', ): void { const colon = node.typeAnnotation!.loc.start.column; - const keyEnd = key.loc.end.column; + const keyEnd = getKeyLocEnd(node); const expectedDiff = nBeforeColon; if ( mode === 'strict' - ? colon - keyEnd !== expectedDiff - : colon - keyEnd < expectedDiff + ? colon - keyEnd.column !== expectedDiff + : colon - keyEnd.column < expectedDiff ) { context.report({ node, - messageId: colon - keyEnd > expectedDiff ? 'extraKey' : 'missingKey', + messageId: + colon - keyEnd.column > expectedDiff ? 'extraKey' : 'missingKey', data: { computed: '', - key: sourceCode.getText(key), + key: getKeyText(node), }, }); } @@ -62,7 +113,6 @@ export default util.createRule({ function checkAfterColon( node: TSESTree.TSIndexSignature | TSESTree.TSPropertySignature, - key: TSESTree.PropertyName | TSESTree.Parameter, nAfterColon: number, mode: 'strict' | 'minimum', ): void { @@ -80,13 +130,13 @@ export default util.createRule({ typeStart - colon > expectedDiff ? 'extraValue' : 'missingValue', data: { computed: '', - key: sourceCode.getText(key), + key: getKeyText(node), }, }); } } - function checkAlignGroup(group: TSESTree.TypeElement[]): void { + function checkAlignGroup(group: TSESTree.Node[]): void { let alignColumn = 0; const align = (typeof options.align === 'object' @@ -119,14 +169,10 @@ export default util.createRule({ node.type === AST_NODE_TYPES.TSIndexSignature) && node.typeAnnotation ) { - const key = - 'key' in node - ? node.key - : node.parameters[node.parameters.length - 1]; alignColumn = Math.max( alignColumn, align === 'colon' - ? key.loc.end.column + nBeforeColon + ? getKeyLocEnd(node).column + nBeforeColon : node.typeAnnotation.loc.start.column + ':'.length + nAfterColon + @@ -141,10 +187,6 @@ export default util.createRule({ node.type === AST_NODE_TYPES.TSIndexSignature) && node.typeAnnotation ) { - const key = - 'key' in node - ? node.key - : node.parameters[node.parameters.length - 1]; const start = align === 'colon' ? node.typeAnnotation.loc.start.column @@ -156,22 +198,22 @@ export default util.createRule({ messageId: start > alignColumn ? 'extraValue' : 'missingValue', data: { computed: '', - key: sourceCode.getText(key), + key: getKeyText(node), }, }); } if (align === 'colon') { - checkAfterColon(node, key, nAfterColon, mode); + checkAfterColon(node, nAfterColon, mode); } else { - checkBeforeColon(node, key, nBeforeColon, mode); + checkBeforeColon(node, nBeforeColon, mode); } } } } function checkIndividualNode( - node: TSESTree.TypeElement, + node: TSESTree.Node, { singleLine }: { singleLine: boolean }, ): void { const beforeColon = @@ -206,28 +248,26 @@ export default util.createRule({ node.type === AST_NODE_TYPES.TSIndexSignature) && node.typeAnnotation ) { - const key = - 'key' in node - ? node.key - : node.parameters[node.parameters.length - 1]; - - checkBeforeColon(node, key, nBeforeColon, mode); - checkAfterColon(node, key, nAfterColon, mode); + checkBeforeColon(node, nBeforeColon, mode); + checkAfterColon(node, nAfterColon, mode); } } function validateBody( - body: TSESTree.TSTypeLiteral | TSESTree.TSInterfaceBody, + body: + | TSESTree.TSTypeLiteral + | TSESTree.TSInterfaceBody + | TSESTree.ClassBody, ): void { const isSingleLine = body.loc.start.line === body.loc.end.line; const members = 'members' in body ? body.members : body.body; - let alignGroups: TSESTree.TypeElement[][] = []; - let unalignedElements: TSESTree.TypeElement[] = []; + let alignGroups: TSESTree.Node[][] = []; + let unalignedElements: TSESTree.Node[] = []; if (options.align || options.multiLine?.align) { - let currentAlignGroup: TSESTree.TypeElement[] = []; + let currentAlignGroup: TSESTree.Node[] = []; alignGroups.push(currentAlignGroup); for (const node of members) { @@ -270,6 +310,7 @@ export default util.createRule({ ...baseRules, TSTypeLiteral: validateBody, TSInterfaceBody: validateBody, + ClassBody: validateBody, }; }, }); diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 75388bf76c4e..6bcf77c4bb8d 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -33,10 +33,9 @@ ruleTester.run('key-spacing', rule, { errors: [{ messageId: 'extraValue' }], }, { - // A blank line between two keys resets the alignment code: 'interface X {\n a: number;\n\n abc : string\n};', options: [{ align: 'value' }], - errors: [{ messageId: 'extraValue' }], + errors: [{ messageId: 'extraValue' }, { messageId: 'extraKey' }], }, { code: 'interface X {\n [x: number]: string;\n}', From ecaafa7926df9c4b6ab8a78cef9b3e808cd1df8b Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 02:26:45 +0100 Subject: [PATCH 06/40] =?UTF-8?q?=E2=9C=A8=20Support=20classes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eslint-plugin/src/rules/key-spacing.ts | 28 ++++++++++++++----- .../tests/rules/key-spacing.test.ts | 24 +++++++++++++++- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index f6a3682a737c..438332930af3 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -39,10 +39,13 @@ export default util.createRule({ * To handle index signatures, to get the whole text for the parameters */ function getKeyText( - node: TSESTree.TSIndexSignature | TSESTree.TSPropertySignature, + node: + | TSESTree.TSIndexSignature + | TSESTree.TSPropertySignature + | TSESTree.PropertyDefinition, ): string { if ('key' in node) { - return sourceCode.getText(node); + return sourceCode.getText(node.key); } const code = sourceCode.getText(node); @@ -57,7 +60,10 @@ export default util.createRule({ * To handle index signatures, be able to get the end position of the parameters */ function getKeyLocEnd( - node: TSESTree.TSIndexSignature | TSESTree.TSPropertySignature, + node: + | TSESTree.TSIndexSignature + | TSESTree.TSPropertySignature + | TSESTree.PropertyDefinition, ): TSESTree.Position { if ('key' in node) { return node.key.loc.end; @@ -87,7 +93,10 @@ export default util.createRule({ } function checkBeforeColon( - node: TSESTree.TSIndexSignature | TSESTree.TSPropertySignature, + node: + | TSESTree.TSIndexSignature + | TSESTree.TSPropertySignature + | TSESTree.PropertyDefinition, nBeforeColon: number, mode: 'strict' | 'minimum', ): void { @@ -112,7 +121,10 @@ export default util.createRule({ } function checkAfterColon( - node: TSESTree.TSIndexSignature | TSESTree.TSPropertySignature, + node: + | TSESTree.TSIndexSignature + | TSESTree.TSPropertySignature + | TSESTree.PropertyDefinition, nAfterColon: number, mode: 'strict' | 'minimum', ): void { @@ -166,7 +178,8 @@ export default util.createRule({ for (const node of group) { if ( (node.type === AST_NODE_TYPES.TSPropertySignature || - node.type === AST_NODE_TYPES.TSIndexSignature) && + node.type === AST_NODE_TYPES.TSIndexSignature || + node.type === AST_NODE_TYPES.PropertyDefinition) && node.typeAnnotation ) { alignColumn = Math.max( @@ -184,7 +197,8 @@ export default util.createRule({ for (const node of group) { if ( (node.type === AST_NODE_TYPES.TSPropertySignature || - node.type === AST_NODE_TYPES.TSIndexSignature) && + node.type === AST_NODE_TYPES.TSIndexSignature || + node.type === AST_NODE_TYPES.PropertyDefinition) && node.typeAnnotation ) { const start = diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 6bcf77c4bb8d..f43974f91f9a 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -16,10 +16,17 @@ ruleTester.run('key-spacing', rule, { options: [{ align: 'value' }], }, { - // A blank line between two keys resets the alignment code: 'interface X {\n a: number;\n\n abc: string\n};', options: [{ align: 'value' }], }, + { + code: 'class X {\n a: number;\n abc: string\n};', + options: [{ align: 'value' }], + }, + { + code: 'class X {\n a: number;\n\n abc: string\n};', + options: [{ align: 'value' }], + }, ], invalid: [ { @@ -27,16 +34,31 @@ ruleTester.run('key-spacing', rule, { options: [{ align: 'value' }], errors: [{ messageId: 'missingValue' }], }, + { + code: 'class X {\n a: number;\n abc: string\n};', + options: [{ align: 'value' }], + errors: [{ messageId: 'missingValue' }], + }, { code: 'interface X {\n a: number;\n abc: string\n};', options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }], }, + { + code: 'class X {\n a: number;\n abc: string\n};', + options: [{ align: 'value' }], + errors: [{ messageId: 'extraValue' }], + }, { code: 'interface X {\n a: number;\n\n abc : string\n};', options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }, { messageId: 'extraKey' }], }, + { + code: 'class X {\n a: number;\n\n abc : string\n};', + options: [{ align: 'value' }], + errors: [{ messageId: 'extraValue' }, { messageId: 'extraKey' }], + }, { code: 'interface X {\n [x: number]: string;\n}', errors: [{ messageId: 'extraValue' }], From b17665be6f99caae4a99a80fd8fb511befcbbce3 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 02:46:05 +0100 Subject: [PATCH 07/40] =?UTF-8?q?=F0=9F=A9=B9=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eslint-plugin/src/rules/key-spacing.ts | 14 +++++-- .../tests/rules/key-spacing.test.ts | 40 +++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 438332930af3..2bb96960c64b 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -166,7 +166,7 @@ export default util.createRule({ ? options.align.afterColon : options.multiLine ? options.multiLine.afterColon - : options.beforeColon) ?? true; + : options.afterColon) ?? true; const nAfterColon = afterColon ? 1 : 0; const mode = (typeof options.align === 'object' @@ -209,7 +209,14 @@ export default util.createRule({ if (start !== alignColumn) { context.report({ node, - messageId: start > alignColumn ? 'extraValue' : 'missingValue', + messageId: + start > alignColumn + ? align === 'colon' + ? 'extraKey' + : 'extraValue' + : align === 'colon' + ? 'missingKey' + : 'missingValue', data: { computed: '', key: getKeyText(node), @@ -259,7 +266,8 @@ export default util.createRule({ if ( (node.type === AST_NODE_TYPES.TSPropertySignature || - node.type === AST_NODE_TYPES.TSIndexSignature) && + node.type === AST_NODE_TYPES.TSIndexSignature || + node.type === AST_NODE_TYPES.PropertyDefinition) && node.typeAnnotation ) { checkBeforeColon(node, nBeforeColon, mode); diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index f43974f91f9a..4bdfec3f5225 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -11,6 +11,7 @@ const ruleTester = new RuleTester({ ruleTester.run('key-spacing', rule, { valid: [ + // align: value { code: 'interface X {\n a: number;\n abc: string\n};', options: [{ align: 'value' }], @@ -27,8 +28,35 @@ ruleTester.run('key-spacing', rule, { code: 'class X {\n a: number;\n\n abc: string\n};', options: [{ align: 'value' }], }, + { + code: 'type X = {\n a: number;\n abc: string\n};', + options: [{ align: 'value' }], + }, + { + code: 'type X = {\n a: number;\n\n abc: string\n};', + options: [{ align: 'value' }], + }, + // align: colon + { + code: 'interface X {\n a : number;\n abc: string\n};', + options: [{ align: 'colon' }], + }, + { + code: 'interface X {\n a :number;\n abc:string\n};', + options: [{ align: 'colon', afterColon: false }], + }, + // no align + { + code: 'interface X {\n a: number;\n abc: string\n};', + options: [{}], + }, + { + code: 'interface X {\n a : number;\n abc : string\n};', + options: [{ beforeColon: true }], + }, ], invalid: [ + // align: value { code: 'interface X {\n a: number;\n abc: string\n};', options: [{ align: 'value' }], @@ -39,6 +67,11 @@ ruleTester.run('key-spacing', rule, { options: [{ align: 'value' }], errors: [{ messageId: 'missingValue' }], }, + { + code: 'type X = {\n a: number;\n abc: string\n};', + options: [{ align: 'value' }], + errors: [{ messageId: 'missingValue' }], + }, { code: 'interface X {\n a: number;\n abc: string\n};', options: [{ align: 'value' }], @@ -59,6 +92,13 @@ ruleTester.run('key-spacing', rule, { options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }, { messageId: 'extraKey' }], }, + // align: colon + { + code: 'interface X {\n a : number;\n abc: string\n};', + options: [{ align: 'colon' }], + errors: [{ messageId: 'extraKey' }], + }, + // no align { code: 'interface X {\n [x: number]: string;\n}', errors: [{ messageId: 'extraValue' }], From 18bb8ab54833c6f39d57fc53e6189709aaf2f0f0 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 02:54:40 +0100 Subject: [PATCH 08/40] =?UTF-8?q?=E2=9C=85=20Add=20tests=20on=20mode,=20mu?= =?UTF-8?q?ltiLine,=20singleLine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/rules/key-spacing.test.ts | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 4bdfec3f5225..1a8f6dc187a5 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -36,6 +36,10 @@ ruleTester.run('key-spacing', rule, { code: 'type X = {\n a: number;\n\n abc: string\n};', options: [{ align: 'value' }], }, + { + code: 'type X = {\n a : number;\n abc: string\n};', + options: [{ align: 'value', mode: 'minimum' }], + }, // align: colon { code: 'interface X {\n a : number;\n abc: string\n};', @@ -45,6 +49,10 @@ ruleTester.run('key-spacing', rule, { code: 'interface X {\n a :number;\n abc:string\n};', options: [{ align: 'colon', afterColon: false }], }, + { + code: 'interface X {\n a : number;\n abc: string\n};', + options: [{ align: 'colon', mode: 'minimum' }], + }, // no align { code: 'interface X {\n a: number;\n abc: string\n};', @@ -54,6 +62,25 @@ ruleTester.run('key-spacing', rule, { code: 'interface X {\n a : number;\n abc : string\n};', options: [{ beforeColon: true }], }, + // singleLine / multiLine + { + code: 'interface X {\n a : number;\n abc : string\n};', + options: [ + { + singleLine: { beforeColon: false, afterColon: false }, + multiLine: { beforeColon: true, afterColon: true }, + }, + ], + }, + { + code: 'interface X { a:number; abc:string; };', + options: [ + { + singleLine: { beforeColon: false, afterColon: false }, + multiLine: { beforeColon: true, afterColon: true }, + }, + ], + }, ], invalid: [ // align: value @@ -67,6 +94,11 @@ ruleTester.run('key-spacing', rule, { options: [{ align: 'value' }], errors: [{ messageId: 'missingValue' }], }, + { + code: 'class X {\n a: number;\n abc: string\n};', + options: [{ align: 'value', mode: 'minimum' }], + errors: [{ messageId: 'missingValue' }], + }, { code: 'type X = {\n a: number;\n abc: string\n};', options: [{ align: 'value' }], @@ -107,5 +139,46 @@ ruleTester.run('key-spacing', rule, { code: 'interface X {\n [x: number]:string;\n}', errors: [{ messageId: 'missingValue' }], }, + // singleLine / multiLine + { + code: 'interface X {\n a:number;\n abc:string\n};', + options: [ + { + singleLine: { beforeColon: false, afterColon: false }, + multiLine: { beforeColon: true, afterColon: true }, + }, + ], + errors: [ + { messageId: 'missingKey' }, + { messageId: 'missingValue' }, + { messageId: 'missingKey' }, + { messageId: 'missingValue' }, + ], + }, + { + code: 'interface X { a : number; abc : string; };', + options: [ + { + singleLine: { beforeColon: false, afterColon: false }, + multiLine: { beforeColon: true, afterColon: true }, + }, + ], + errors: [ + { messageId: 'extraKey' }, + { messageId: 'extraValue' }, + { messageId: 'extraKey' }, + { messageId: 'extraValue' }, + ], + }, + { + code: 'interface X { a : number; abc : string; };', + options: [ + { + singleLine: { beforeColon: false, afterColon: true }, + multiLine: { beforeColon: true, afterColon: true }, + }, + ], + errors: [{ messageId: 'extraKey' }, { messageId: 'extraKey' }], + }, ], }); From 4113478b8191b635683d8a519d288b043a499de1 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 03:15:09 +0100 Subject: [PATCH 09/40] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20Allow=20options.m?= =?UTF-8?q?ultiline.align=20to=20be=20an=20object?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/eslint-plugin/src/rules/key-spacing.ts | 13 ++++++++++--- packages/eslint-plugin/typings/eslint-rules.d.ts | 10 +++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 2bb96960c64b..911c39a0d2ee 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -158,21 +158,28 @@ export default util.createRule({ (typeof options.align === 'object' ? options.align.beforeColon : options.multiLine - ? options.multiLine.beforeColon + ? typeof options.multiLine.align === 'object' + ? options.multiLine.align.beforeColon + : options.multiLine.beforeColon : options.beforeColon) ?? false; const nBeforeColon = beforeColon ? 1 : 0; const afterColon = (typeof options.align === 'object' ? options.align.afterColon : options.multiLine - ? options.multiLine.afterColon + ? typeof options.multiLine.align === 'object' + ? options.multiLine.align.afterColon + : options.multiLine.afterColon : options.afterColon) ?? true; const nAfterColon = afterColon ? 1 : 0; const mode = (typeof options.align === 'object' ? options.align.mode : options.multiLine - ? options.multiLine.mode + ? typeof options.multiLine.align === 'object' + ? // same behavior as in original rule + options.multiLine.align.mode ?? options.multiLine.mode + : options.multiLine.mode : options.mode) ?? 'strict'; for (const node of group) { diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index 4315edb23544..7eda886c91bb 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -168,7 +168,15 @@ declare module 'eslint/lib/rules/key-spacing' { beforeColon?: boolean; afterColon?: boolean; mode?: 'strict' | 'minimum'; - align?: 'value' | 'colon'; + align?: + | 'value' + | 'colon' + | { + on: 'value' | 'colon'; + beforeColon?: boolean; + afterColon?: boolean; + mode?: 'strict' | 'minimum'; + }; }; }, ]; From 5e951ba1976e09f2b8329ede840c5233164dd249 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 09:49:04 +0100 Subject: [PATCH 10/40] =?UTF-8?q?=F0=9F=8E=A8=20Use=20ast=20utils=20to=20l?= =?UTF-8?q?ocate=20last=20character=20before=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eslint-plugin/src/rules/key-spacing.ts | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 911c39a0d2ee..11ca88f81891 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -35,6 +35,16 @@ export default util.createRule({ const sourceCode = context.getSourceCode(); const baseRules = baseRule.create(context); + /** + * Starting from the given a node (a property.key node here) looks forward + * until it finds the last token before a colon punctuator and returns it. + */ + function getLastTokenBeforeColon(node: TSESTree.Node): TSESTree.Token { + const colonToken = sourceCode.getTokenAfter(node, util.isColonToken)!; + + return sourceCode.getTokenBefore(colonToken)!; + } + /** * To handle index signatures, to get the whole text for the parameters */ @@ -52,7 +62,7 @@ export default util.createRule({ const lastParam = node.parameters[node.parameters.length - 1]; return code.slice( 0, - code.indexOf(']', lastParam.range[1] - node.range[0]) + 1, + getLastTokenBeforeColon(lastParam).range[1] - node.range[0], ); } @@ -69,27 +79,9 @@ export default util.createRule({ return node.key.loc.end; } - // For index signatures, there's no easy way to get the location of the ending ']', we need to look at the source code - const code = sourceCode.getText(node); - const lastParam = node.parameters[node.parameters.length - 1]; - - const remaining = code.slice( - lastParam.range[1] - node.range[0], - code.indexOf(']', lastParam.range[1] - node.range[0]) + 1, - ); - const lines = remaining.split('\n'); - - if (lines.length === 1) { - return { - line: lastParam.loc.end.line, - column: lastParam.loc.end.column + remaining.length, - }; - } - - return { - line: lastParam.loc.end.line + lines.length - 1, - column: lines[lines.length - 1].length, - }; + return getLastTokenBeforeColon( + node.parameters[node.parameters.length - 1], + ).loc.end; } function checkBeforeColon( From b12c530209f6a12f80ad1afdd8196a088f9b221d Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 10:17:31 +0100 Subject: [PATCH 11/40] =?UTF-8?q?=E2=9C=A8=20Support=20comments=20in-betwe?= =?UTF-8?q?en=20properties?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eslint-plugin/src/rules/key-spacing.ts | 113 ++++++++++++------ .../tests/rules/key-spacing.test.ts | 17 +++ 2 files changed, 94 insertions(+), 36 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 11ca88f81891..1f6027ed9a53 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -45,15 +45,30 @@ export default util.createRule({ return sourceCode.getTokenBefore(colonToken)!; } + /** + * Relevant nodes to our rule + * + * node.typeAnnotation will aways be defined, but no way to enforce that and keep + * the type compatible with TSEstree.Node (except maybe TS 4.9' "satisfies" keyword) + */ + type KeyTypeNode = + | TSESTree.TSIndexSignature + | TSESTree.TSPropertySignature + | TSESTree.PropertyDefinition; + + function isKeyTypeNode(node: TSESTree.Node): node is KeyTypeNode { + return ( + (node.type === AST_NODE_TYPES.TSPropertySignature || + node.type === AST_NODE_TYPES.TSIndexSignature || + node.type === AST_NODE_TYPES.PropertyDefinition) && + !!node.typeAnnotation + ); + } + /** * To handle index signatures, to get the whole text for the parameters */ - function getKeyText( - node: - | TSESTree.TSIndexSignature - | TSESTree.TSPropertySignature - | TSESTree.PropertyDefinition, - ): string { + function getKeyText(node: KeyTypeNode): string { if ('key' in node) { return sourceCode.getText(node.key); } @@ -85,10 +100,7 @@ export default util.createRule({ } function checkBeforeColon( - node: - | TSESTree.TSIndexSignature - | TSESTree.TSPropertySignature - | TSESTree.PropertyDefinition, + node: KeyTypeNode, nBeforeColon: number, mode: 'strict' | 'minimum', ): void { @@ -113,10 +125,7 @@ export default util.createRule({ } function checkAfterColon( - node: - | TSESTree.TSIndexSignature - | TSESTree.TSPropertySignature - | TSESTree.PropertyDefinition, + node: KeyTypeNode, nAfterColon: number, mode: 'strict' | 'minimum', ): void { @@ -140,6 +149,53 @@ export default util.createRule({ } } + // adapted from https://github.com/eslint/eslint/blob/ba74253e8bd63e9e163bbee0540031be77e39253/lib/rules/key-spacing.js#L356 + function continuesAlignGroup( + lastMember: TSESTree.Node, + candidate: TSESTree.Node, + ): boolean { + const groupEndLine = lastMember.loc.start.line; + const candidateValueStartLine = ( + isKeyTypeNode(candidate) ? candidate.typeAnnotation! : candidate + ).loc.start.line; + + if (candidateValueStartLine === groupEndLine) { + return false; + } + + if (candidateValueStartLine - groupEndLine === 1) { + return true; + } + + /* + * Check that the first comment is adjacent to the end of the group, the + * last comment is adjacent to the candidate property, and that successive + * comments are adjacent to each other. + */ + const leadingComments = sourceCode.getCommentsBefore(candidate); + + if ( + leadingComments.length && + leadingComments[0].loc.start.line - groupEndLine <= 1 && + candidateValueStartLine - + leadingComments[leadingComments.length - 1].loc.end.line <= + 1 + ) { + for (let i = 1; i < leadingComments.length; i++) { + if ( + leadingComments[i].loc.start.line - + leadingComments[i - 1].loc.end.line > + 1 + ) { + return false; + } + } + return true; + } + + return false; + } + function checkAlignGroup(group: TSESTree.Node[]): void { let alignColumn = 0; const align = @@ -175,17 +231,12 @@ export default util.createRule({ : options.mode) ?? 'strict'; for (const node of group) { - if ( - (node.type === AST_NODE_TYPES.TSPropertySignature || - node.type === AST_NODE_TYPES.TSIndexSignature || - node.type === AST_NODE_TYPES.PropertyDefinition) && - node.typeAnnotation - ) { + if (isKeyTypeNode(node)) { alignColumn = Math.max( alignColumn, align === 'colon' ? getKeyLocEnd(node).column + nBeforeColon - : node.typeAnnotation.loc.start.column + + : node.typeAnnotation!.loc.start.column + ':'.length + nAfterColon + nBeforeColon, @@ -194,16 +245,11 @@ export default util.createRule({ } for (const node of group) { - if ( - (node.type === AST_NODE_TYPES.TSPropertySignature || - node.type === AST_NODE_TYPES.TSIndexSignature || - node.type === AST_NODE_TYPES.PropertyDefinition) && - node.typeAnnotation - ) { + if (isKeyTypeNode(node)) { const start = align === 'colon' - ? node.typeAnnotation.loc.start.column - : node.typeAnnotation.typeAnnotation.loc.start.column; + ? node.typeAnnotation!.loc.start.column + : node.typeAnnotation!.typeAnnotation.loc.start.column; if (start !== alignColumn) { context.report({ @@ -263,12 +309,7 @@ export default util.createRule({ ? options.multiLine.mode : options.mode) ?? 'strict'; - if ( - (node.type === AST_NODE_TYPES.TSPropertySignature || - node.type === AST_NODE_TYPES.TSIndexSignature || - node.type === AST_NODE_TYPES.PropertyDefinition) && - node.typeAnnotation - ) { + if (isKeyTypeNode(node)) { checkBeforeColon(node, nBeforeColon, mode); checkAfterColon(node, nAfterColon, mode); } @@ -296,7 +337,7 @@ export default util.createRule({ ? currentAlignGroup[currentAlignGroup.length - 1] : null; - if (prevNode?.loc.start.line === node.loc.start.line - 1) { + if (prevNode && continuesAlignGroup(prevNode, node)) { currentAlignGroup.push(node); } else if (prevNode?.loc.start.line === node.loc.start.line) { if (currentAlignGroup.length) { diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 1a8f6dc187a5..b5f115047d67 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -16,6 +16,18 @@ ruleTester.run('key-spacing', rule, { code: 'interface X {\n a: number;\n abc: string\n};', options: [{ align: 'value' }], }, + { + code: 'interface X {\n a: number;\n // Some comment\n abc: string\n};', + options: [{ align: 'value' }], + }, + { + code: 'interface X {\n a: number;\n // Some comment\n // on multiple lines\n abc: string\n};', + options: [{ align: 'value' }], + }, + { + code: 'interface X {\n a: number;\n /**\n * Doc comment\n */\n abc: string\n};', + options: [{ align: 'value' }], + }, { code: 'interface X {\n a: number;\n\n abc: string\n};', options: [{ align: 'value' }], @@ -124,6 +136,11 @@ ruleTester.run('key-spacing', rule, { options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }, { messageId: 'extraKey' }], }, + { + code: 'interface X {\n a: number;\n // Some comment\n\n // interrupted in the middle\n abc: string\n};', + options: [{ align: 'value' }], + errors: [{ messageId: 'extraValue' }], + }, // align: colon { code: 'interface X {\n a : number;\n abc: string\n};', From 729e6426531045ea7b8140d0ed38dc4349b7056b Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 10:26:51 +0100 Subject: [PATCH 12/40] =?UTF-8?q?=E2=9C=85=20Add=20test=20cases=20for=20ne?= =?UTF-8?q?sted=20type=20declarations=20&=20multiline=20type=20annotations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/rules/key-spacing.test.ts | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index b5f115047d67..8626dba07fca 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -52,6 +52,20 @@ ruleTester.run('key-spacing', rule, { code: 'type X = {\n a : number;\n abc: string\n};', options: [{ align: 'value', mode: 'minimum' }], }, + { + code: +` +interface X { + a: number; + prop: { + abc: number; + a: number; + }, + abc: string +} +`, + options: [{ align: 'value' }], + }, // align: colon { code: 'interface X {\n a : number;\n abc: string\n};', @@ -141,6 +155,51 @@ ruleTester.run('key-spacing', rule, { options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }], }, + { + code: +` +interface X { + a: number; + prop: { + abc: number; + a: number; + }, + abc: string +} +`, + options: [{ align: 'value' }], + errors: [{ messageId: 'missingValue' }], + }, + { + code: +` +interface X { + a: number; + prop: { + abc: number; + a: number; + }, + abc: string +} +`, + options: [{ align: 'value' }], + errors: [{ messageId: 'missingValue' }], + }, + { + code: +` +interface X { + a: number; + prop: { + abc: number; + a: number; + }, + abc: string +} +`, + options: [{ align: 'value' }], + errors: [{ messageId: 'extraValue' }], + }, // align: colon { code: 'interface X {\n a : number;\n abc: string\n};', From d93c1a7cebcbdbd3624227cf34d03e5bbb958190 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 11:03:10 +0100 Subject: [PATCH 13/40] =?UTF-8?q?=E2=9C=A8=20Autofix=20for=20non-aligned?= =?UTF-8?q?=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eslint-plugin/src/rules/key-spacing.ts | 48 ++++++++++++------- .../tests/rules/key-spacing.test.ts | 19 ++++++++ 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 1f6027ed9a53..5b6aed6110a1 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -106,16 +106,24 @@ export default util.createRule({ ): void { const colon = node.typeAnnotation!.loc.start.column; const keyEnd = getKeyLocEnd(node); - const expectedDiff = nBeforeColon; - if ( - mode === 'strict' - ? colon - keyEnd.column !== expectedDiff - : colon - keyEnd.column < expectedDiff - ) { + const difference = colon - keyEnd.column - nBeforeColon; + if (mode === 'strict' ? difference : difference < 0) { context.report({ node, - messageId: - colon - keyEnd.column > expectedDiff ? 'extraKey' : 'missingKey', + messageId: difference > 0 ? 'extraKey' : 'missingKey', + fix: fixer => { + if (difference > 0) { + return fixer.removeRange([ + node.typeAnnotation!.range[0] - difference, + node.typeAnnotation!.range[0], + ]); + } else { + return fixer.insertTextBefore( + node.typeAnnotation!, + ' '.repeat(-difference), + ); + } + }, data: { computed: '', key: getKeyText(node), @@ -131,16 +139,24 @@ export default util.createRule({ ): void { const colon = node.typeAnnotation!.loc.start.column; const typeStart = node.typeAnnotation!.typeAnnotation.loc.start.column; - const expectedDiff = nAfterColon + 1; - if ( - mode === 'strict' - ? typeStart - colon !== expectedDiff - : typeStart - colon < expectedDiff - ) { + const difference = typeStart - colon - 1 - nAfterColon; + if (mode === 'strict' ? difference : difference < 0) { context.report({ node, - messageId: - typeStart - colon > expectedDiff ? 'extraValue' : 'missingValue', + messageId: difference > 0 ? 'extraValue' : 'missingValue', + fix: fixer => { + if (difference > 0) { + return fixer.removeRange([ + node.typeAnnotation!.typeAnnotation.range[0] - difference, + node.typeAnnotation!.typeAnnotation.range[0], + ]); + } else { + return fixer.insertTextBefore( + node.typeAnnotation!.typeAnnotation, + ' '.repeat(-difference), + ); + } + }, data: { computed: '', key: getKeyText(node), diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 8626dba07fca..49de9d0ef4c1 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -142,16 +142,19 @@ interface X { }, { code: 'interface X {\n a: number;\n\n abc : string\n};', + output: 'interface X {\n a: number;\n\n abc: string\n};', options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }, { messageId: 'extraKey' }], }, { code: 'class X {\n a: number;\n\n abc : string\n};', + output: 'class X {\n a: number;\n\n abc: string\n};', options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }, { messageId: 'extraKey' }], }, { code: 'interface X {\n a: number;\n // Some comment\n\n // interrupted in the middle\n abc: string\n};', + output: 'interface X {\n a: number;\n // Some comment\n\n // interrupted in the middle\n abc: string\n};', options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }], }, @@ -196,6 +199,17 @@ interface X { }, abc: string } +`, + output: +` +interface X { + a: number; + prop: { + abc: number; + a: number; + }, + abc: string +} `, options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }], @@ -209,15 +223,18 @@ interface X { // no align { code: 'interface X {\n [x: number]: string;\n}', + output: 'interface X {\n [x: number]: string;\n}', errors: [{ messageId: 'extraValue' }], }, { code: 'interface X {\n [x: number]:string;\n}', + output: 'interface X {\n [x: number]: string;\n}', errors: [{ messageId: 'missingValue' }], }, // singleLine / multiLine { code: 'interface X {\n a:number;\n abc:string\n};', + output: 'interface X {\n a : number;\n abc : string\n};', options: [ { singleLine: { beforeColon: false, afterColon: false }, @@ -233,6 +250,7 @@ interface X { }, { code: 'interface X { a : number; abc : string; };', + output: 'interface X { a:number; abc:string; };', options: [ { singleLine: { beforeColon: false, afterColon: false }, @@ -248,6 +266,7 @@ interface X { }, { code: 'interface X { a : number; abc : string; };', + output: 'interface X { a: number; abc: string; };', options: [ { singleLine: { beforeColon: false, afterColon: true }, From ee519e30c5d08ff59af584e43a291f691450bde2 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 11:22:24 +0100 Subject: [PATCH 14/40] =?UTF-8?q?=E2=9C=A8=20Autofix=20for=20aligned=20val?= =?UTF-8?q?ues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eslint-plugin/src/rules/key-spacing.ts | 72 +++++++++++-------- .../tests/rules/key-spacing.test.ts | 29 ++++++++ 2 files changed, 72 insertions(+), 29 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 5b6aed6110a1..177c34b97e33 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -214,9 +214,11 @@ export default util.createRule({ function checkAlignGroup(group: TSESTree.Node[]): void { let alignColumn = 0; - const align = + const align: 'value' | 'colon' = (typeof options.align === 'object' ? options.align.on + : typeof options.multiLine?.align === 'object' + ? options.multiLine.align.on : options.multiLine?.align ?? options.align) ?? 'colon'; const beforeColon = (typeof options.align === 'object' @@ -261,35 +263,47 @@ export default util.createRule({ } for (const node of group) { - if (isKeyTypeNode(node)) { - const start = - align === 'colon' - ? node.typeAnnotation!.loc.start.column - : node.typeAnnotation!.typeAnnotation.loc.start.column; - - if (start !== alignColumn) { - context.report({ - node, - messageId: - start > alignColumn - ? align === 'colon' - ? 'extraKey' - : 'extraValue' - : align === 'colon' - ? 'missingKey' - : 'missingValue', - data: { - computed: '', - key: getKeyText(node), - }, - }); - } + if (!isKeyTypeNode(node)) { + continue; + } + const toCheck = + align === 'colon' + ? node.typeAnnotation! + : node.typeAnnotation!.typeAnnotation; + const difference = toCheck.loc.start.column - alignColumn; + + if (difference) { + context.report({ + node, + messageId: + difference > 0 + ? align === 'colon' + ? 'extraKey' + : 'extraValue' + : align === 'colon' + ? 'missingKey' + : 'missingValue', + fix: fixer => { + if (difference > 0) { + return fixer.removeRange([ + toCheck.range[0] - difference, + toCheck.range[0], + ]); + } else { + return fixer.insertTextBefore(toCheck, ' '.repeat(-difference)); + } + }, + data: { + computed: '', + key: getKeyText(node), + }, + }); + } - if (align === 'colon') { - checkAfterColon(node, nAfterColon, mode); - } else { - checkBeforeColon(node, nBeforeColon, mode); - } + if (align === 'colon') { + checkAfterColon(node, nAfterColon, mode); + } else { + checkBeforeColon(node, nBeforeColon, mode); } } } diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 49de9d0ef4c1..9fbe24d6843d 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -112,31 +112,37 @@ interface X { // align: value { code: 'interface X {\n a: number;\n abc: string\n};', + output: 'interface X {\n a: number;\n abc: string\n};', options: [{ align: 'value' }], errors: [{ messageId: 'missingValue' }], }, { code: 'class X {\n a: number;\n abc: string\n};', + output: 'class X {\n a: number;\n abc: string\n};', options: [{ align: 'value' }], errors: [{ messageId: 'missingValue' }], }, { code: 'class X {\n a: number;\n abc: string\n};', + output: 'class X {\n a: number;\n abc: string\n};', options: [{ align: 'value', mode: 'minimum' }], errors: [{ messageId: 'missingValue' }], }, { code: 'type X = {\n a: number;\n abc: string\n};', + output: 'type X = {\n a: number;\n abc: string\n};', options: [{ align: 'value' }], errors: [{ messageId: 'missingValue' }], }, { code: 'interface X {\n a: number;\n abc: string\n};', + output: 'interface X {\n a: number;\n abc: string\n};', options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }], }, { code: 'class X {\n a: number;\n abc: string\n};', + output: 'class X {\n a: number;\n abc: string\n};', options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }], }, @@ -169,6 +175,17 @@ interface X { }, abc: string } +`, + output: +` +interface X { + a: number; + prop: { + abc: number; + a: number; + }, + abc: string +} `, options: [{ align: 'value' }], errors: [{ messageId: 'missingValue' }], @@ -184,6 +201,17 @@ interface X { }, abc: string } +`, + output: +` +interface X { + a: number; + prop: { + abc: number; + a: number; + }, + abc: string +} `, options: [{ align: 'value' }], errors: [{ messageId: 'missingValue' }], @@ -217,6 +245,7 @@ interface X { // align: colon { code: 'interface X {\n a : number;\n abc: string\n};', + output: 'interface X {\n a : number;\n abc: string\n};', options: [{ align: 'colon' }], errors: [{ messageId: 'extraKey' }], }, From bc3b5d27fa0b7c6bcce120acb3603b0c717417da Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 11:29:43 +0100 Subject: [PATCH 15/40] =?UTF-8?q?=E2=9C=8F=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/eslint-plugin/src/rules/key-spacing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 177c34b97e33..7bd325172983 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -48,7 +48,7 @@ export default util.createRule({ /** * Relevant nodes to our rule * - * node.typeAnnotation will aways be defined, but no way to enforce that and keep + * node.typeAnnotation will always be defined, but no way to enforce that and keep * the type compatible with TSEstree.Node (except maybe TS 4.9' "satisfies" keyword) */ type KeyTypeNode = From eebb18f2933f70411a632062db4137a86eb95ee0 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 11:36:26 +0100 Subject: [PATCH 16/40] =?UTF-8?q?=F0=9F=9A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/rules/key-spacing.test.ts | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 9fbe24d6843d..64e037b7336d 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -53,8 +53,7 @@ ruleTester.run('key-spacing', rule, { options: [{ align: 'value', mode: 'minimum' }], }, { - code: -` + code: ` interface X { a: number; prop: { @@ -63,7 +62,7 @@ interface X { }, abc: string } -`, + `, options: [{ align: 'value' }], }, // align: colon @@ -160,13 +159,13 @@ interface X { }, { code: 'interface X {\n a: number;\n // Some comment\n\n // interrupted in the middle\n abc: string\n};', - output: 'interface X {\n a: number;\n // Some comment\n\n // interrupted in the middle\n abc: string\n};', + output: + 'interface X {\n a: number;\n // Some comment\n\n // interrupted in the middle\n abc: string\n};', options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }], }, { - code: -` + code: ` interface X { a: number; prop: { @@ -175,9 +174,8 @@ interface X { }, abc: string } -`, - output: -` + `, + output: ` interface X { a: number; prop: { @@ -186,13 +184,12 @@ interface X { }, abc: string } -`, + `, options: [{ align: 'value' }], errors: [{ messageId: 'missingValue' }], }, { - code: -` + code: ` interface X { a: number; prop: { @@ -201,9 +198,8 @@ interface X { }, abc: string } -`, - output: -` + `, + output: ` interface X { a: number; prop: { @@ -212,13 +208,12 @@ interface X { }, abc: string } -`, + `, options: [{ align: 'value' }], errors: [{ messageId: 'missingValue' }], }, { - code: -` + code: ` interface X { a: number; prop: { @@ -227,9 +222,8 @@ interface X { }, abc: string } -`, - output: -` + `, + output: ` interface X { a: number; prop: { @@ -238,7 +232,7 @@ interface X { }, abc: string } -`, + `, options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }], }, From 179aca2a3cf5959a9759b4fbe9727185556ac150 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 11:45:48 +0100 Subject: [PATCH 17/40] =?UTF-8?q?=F0=9F=90=9B=20Support=20optional=20=3F?= =?UTF-8?q?=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/eslint-plugin/src/rules/key-spacing.ts | 16 ++++------------ .../tests/rules/key-spacing.test.ts | 8 ++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 7bd325172983..ea192c3007fd 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -77,25 +77,17 @@ export default util.createRule({ const lastParam = node.parameters[node.parameters.length - 1]; return code.slice( 0, - getLastTokenBeforeColon(lastParam).range[1] - node.range[0], + sourceCode.getTokenAfter(lastParam, util.isClosingBracketToken)! + .range[1] - node.range[0], ); } /** * To handle index signatures, be able to get the end position of the parameters */ - function getKeyLocEnd( - node: - | TSESTree.TSIndexSignature - | TSESTree.TSPropertySignature - | TSESTree.PropertyDefinition, - ): TSESTree.Position { - if ('key' in node) { - return node.key.loc.end; - } - + function getKeyLocEnd(node: KeyTypeNode): TSESTree.Position { return getLastTokenBeforeColon( - node.parameters[node.parameters.length - 1], + 'key' in node ? node.key : node.parameters[node.parameters.length - 1], ).loc.end; } diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 64e037b7336d..8911bce8d484 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -16,6 +16,10 @@ ruleTester.run('key-spacing', rule, { code: 'interface X {\n a: number;\n abc: string\n};', options: [{ align: 'value' }], }, + { + code: 'interface X {\n a?: number;\n abc: string\n};', + options: [{ align: 'value' }], + }, { code: 'interface X {\n a: number;\n // Some comment\n abc: string\n};', options: [{ align: 'value' }], @@ -36,6 +40,10 @@ ruleTester.run('key-spacing', rule, { code: 'class X {\n a: number;\n abc: string\n};', options: [{ align: 'value' }], }, + { + code: 'class X {\n a?: number;\n abc: string\n};', + options: [{ align: 'value' }], + }, { code: 'class X {\n a: number;\n\n abc: string\n};', options: [{ align: 'value' }], From db2d1bd16bdab7e6541b3d2f6c2b013c5ea035ff Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 12:30:15 +0100 Subject: [PATCH 18/40] =?UTF-8?q?=E2=9C=85=20Add=20tests=20with=20class=20?= =?UTF-8?q?property=20assignments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/rules/key-spacing.test.ts | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 8911bce8d484..6a57edc5f392 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -44,6 +44,10 @@ ruleTester.run('key-spacing', rule, { code: 'class X {\n a?: number;\n abc: string\n};', options: [{ align: 'value' }], }, + { + code: 'class X {\n x: number;\n z = 1;\n xbcef: number;\n }', + options: [{ align: 'value' }], + }, { code: 'class X {\n a: number;\n\n abc: string\n};', options: [{ align: 'value' }], @@ -67,8 +71,25 @@ interface X { prop: { abc: number; a: number; - }, + }; + abc: string +} + `, + options: [{ align: 'value' }], + }, + { + code: ` +class X { + a: number; + prop: { + abc: number; + a: number; + }; abc: string + x = 1; + d: number; + z: number = 1; + ef: string; } `, options: [{ align: 'value' }], @@ -153,6 +174,12 @@ interface X { options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }], }, + { + code: 'class X {\n x: number;\n z = 1;\n xbcef: number;\n }', + output: 'class X {\n x: number;\n z = 1;\n xbcef: number;\n }', + options: [{ align: 'value' }], + errors: [{ messageId: 'missingValue' }], + }, { code: 'interface X {\n a: number;\n\n abc : string\n};', output: 'interface X {\n a: number;\n\n abc: string\n};', @@ -244,6 +271,44 @@ interface X { options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }], }, + { + code: ` +class X { + a: number; + prop: { + abc: number; + a?: number; + }; + abc: string; + x = 1; + d: number; + z: number = 1; + ef: string; +} + `, + output: ` +class X { + a: number; + prop: { + abc: number; + a?: number; + }; + abc: string; + x = 1; + d: number; + z: number = 1; + ef: string; +} + `, + options: [{ align: 'value' }], + errors: [ + { messageId: 'extraValue' }, + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + ], + }, // align: colon { code: 'interface X {\n a : number;\n abc: string\n};', From dbe502d16bb1785b6796ee7172ea0a61a917249d Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 12:44:48 +0100 Subject: [PATCH 19/40] =?UTF-8?q?=F0=9F=93=9D=20Add=20documentation=20on?= =?UTF-8?q?=20key-spacing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/eslint-plugin/docs/rules/key-spacing.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/key-spacing.md diff --git a/packages/eslint-plugin/docs/rules/key-spacing.md b/packages/eslint-plugin/docs/rules/key-spacing.md new file mode 100644 index 000000000000..16abf2eeba3d --- /dev/null +++ b/packages/eslint-plugin/docs/rules/key-spacing.md @@ -0,0 +1,12 @@ +--- +description: 'Enforce consistent spacing after keys and before values / type annotations.' +--- + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/key-spacing** for documentation. + +## Examples + +This rule extends the base [`eslint/keyword-spacing`](https://eslint.org/docs/rules/key-spacing) rule. +This version adds support for type annotations on interface, class and type literals properties. From 8d5d3ddb5a950a382e9c62ec1919c5cb01d04983 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 14:26:49 +0100 Subject: [PATCH 20/40] =?UTF-8?q?=F0=9F=8E=A8=20Use=20.at()=20to=20access?= =?UTF-8?q?=20last=20element=20of=20array?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/eslint-plugin/src/rules/key-spacing.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index ea192c3007fd..06c03678fe6a 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -74,11 +74,12 @@ export default util.createRule({ } const code = sourceCode.getText(node); - const lastParam = node.parameters[node.parameters.length - 1]; return code.slice( 0, - sourceCode.getTokenAfter(lastParam, util.isClosingBracketToken)! - .range[1] - node.range[0], + sourceCode.getTokenAfter( + node.parameters.at(-1)!, + util.isClosingBracketToken, + )!.range[1] - node.range[0], ); } @@ -87,7 +88,7 @@ export default util.createRule({ */ function getKeyLocEnd(node: KeyTypeNode): TSESTree.Position { return getLastTokenBeforeColon( - 'key' in node ? node.key : node.parameters[node.parameters.length - 1], + 'key' in node ? node.key : node.parameters.at(-1)!, ).loc.end; } @@ -185,9 +186,7 @@ export default util.createRule({ if ( leadingComments.length && leadingComments[0].loc.start.line - groupEndLine <= 1 && - candidateValueStartLine - - leadingComments[leadingComments.length - 1].loc.end.line <= - 1 + candidateValueStartLine - leadingComments.at(-1)!.loc.end.line <= 1 ) { for (let i = 1; i < leadingComments.length; i++) { if ( @@ -355,9 +354,7 @@ export default util.createRule({ alignGroups.push(currentAlignGroup); for (const node of members) { - const prevNode = currentAlignGroup.length - ? currentAlignGroup[currentAlignGroup.length - 1] - : null; + const prevNode = currentAlignGroup.at(-1); if (prevNode && continuesAlignGroup(prevNode, node)) { currentAlignGroup.push(node); From af0a4891a4d4e3b59a728b876c75718259b4fe75 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 15 Dec 2022 23:15:21 +0100 Subject: [PATCH 21/40] =?UTF-8?q?=E2=9C=85=20Fix=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/eslint-plugin/docs/rules/key-spacing.md | 4 ++-- packages/eslint-plugin/src/configs/all.ts | 2 ++ packages/eslint-plugin/src/rules/key-spacing.ts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/key-spacing.md b/packages/eslint-plugin/docs/rules/key-spacing.md index 16abf2eeba3d..3bfcf5f389f0 100644 --- a/packages/eslint-plugin/docs/rules/key-spacing.md +++ b/packages/eslint-plugin/docs/rules/key-spacing.md @@ -1,5 +1,5 @@ --- -description: 'Enforce consistent spacing after keys and before values / type annotations.' +description: 'Enforce consistent spacing between property names and type annotations in types and interfaces.' --- > 🛑 This file is source code, not the primary documentation location! 🛑 @@ -9,4 +9,4 @@ description: 'Enforce consistent spacing after keys and before values / type ann ## Examples This rule extends the base [`eslint/keyword-spacing`](https://eslint.org/docs/rules/key-spacing) rule. -This version adds support for type annotations on interface, class and type literals properties. +This version adds support for type annotations on interfaces, classes and type literals properties. diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 20ea892f581d..452035c4ebf2 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -37,6 +37,8 @@ export = { '@typescript-eslint/indent': 'error', 'init-declarations': 'off', '@typescript-eslint/init-declarations': 'error', + 'key-spacing': 'off', + '@typescript-eslint/key-spacing': 'error', 'keyword-spacing': 'off', '@typescript-eslint/keyword-spacing': 'error', 'lines-between-class-members': 'off', diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 06c03678fe6a..d053c48d35c7 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -20,7 +20,7 @@ export default util.createRule({ type: 'layout', docs: { description: - 'Enforce consistent spacing between keys and values in types and interfaces', + 'Enforce consistent spacing between property names and type annotations in types and interfaces.', recommended: false, extendsBaseRule: true, }, From c18b9291777360d1c7c8d86423b1c323a11ca525 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Fri, 16 Dec 2022 00:36:22 +0100 Subject: [PATCH 22/40] =?UTF-8?q?fixup!=20=E2=9C=85=20Fix=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/eslint-plugin/src/rules/key-spacing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index d053c48d35c7..c9b71a91b064 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -20,7 +20,7 @@ export default util.createRule({ type: 'layout', docs: { description: - 'Enforce consistent spacing between property names and type annotations in types and interfaces.', + 'Enforce consistent spacing between property names and type annotations in types and interfaces', recommended: false, extendsBaseRule: true, }, From 37eecdc1c708f8381bb4af80730d3019ac447a5c Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Fri, 16 Dec 2022 03:41:44 +0100 Subject: [PATCH 23/40] =?UTF-8?q?=E2=9C=85=20Add=20some=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eslint-plugin/src/rules/key-spacing.ts | 2 +- .../tests/rules/key-spacing.test.ts | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index c9b71a91b064..4bd4421e976f 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -245,7 +245,7 @@ export default util.createRule({ alignColumn, align === 'colon' ? getKeyLocEnd(node).column + nBeforeColon - : node.typeAnnotation!.loc.start.column + + : getKeyLocEnd(node).column + ':'.length + nAfterColon + nBeforeColon, diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 6a57edc5f392..69d12b273c32 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -16,6 +16,10 @@ ruleTester.run('key-spacing', rule, { code: 'interface X {\n a: number;\n abc: string\n};', options: [{ align: 'value' }], }, + { + code: 'interface X {\n a: number;\n abc: string; c: number;\n};', + options: [{ align: 'value' }], + }, { code: 'interface X {\n a?: number;\n abc: string\n};', options: [{ align: 'value' }], @@ -126,6 +130,51 @@ class X { }, ], }, + { + code: 'interface X {\n a : number;\n abc : string\n};', + options: [ + { + singleLine: { beforeColon: false, afterColon: false }, + multiLine: { beforeColon: true, afterColon: true, align: 'value' }, + }, + ], + }, + { + code: 'interface X {\n a : number;\n abc : string\n};', + options: [ + { + singleLine: { beforeColon: false, afterColon: false }, + multiLine: { + beforeColon: true, + afterColon: true, + align: { + on: 'colon', + mode: 'strict', + afterColon: true, + beforeColon: true, + }, + }, + }, + ], + }, + { + code: 'interface X {\n a : number;\n abc: string\n\n xadzd : number;\n};', + options: [ + { + singleLine: { beforeColon: false, afterColon: false }, + multiLine: { + beforeColon: true, + afterColon: true, + align: { + on: 'colon', + mode: 'strict', + afterColon: true, + beforeColon: false, + }, + }, + }, + ], + }, { code: 'interface X { a:number; abc:string; };', options: [ From 1b62445210f59205cdf3d6fa8911dd77dcc43961 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Fri, 16 Dec 2022 11:54:12 +0100 Subject: [PATCH 24/40] =?UTF-8?q?=F0=9F=90=9B=20Fix=20edge=20case=20in=20d?= =?UTF-8?q?etermining=20aligned=20groups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In case there is three statements in one line, and one statement in the line after --- packages/eslint-plugin/src/rules/key-spacing.ts | 13 ++++++++++--- .../eslint-plugin/tests/rules/key-spacing.test.ts | 4 ++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 4bd4421e976f..2112c5d59fab 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -353,13 +353,18 @@ export default util.createRule({ let currentAlignGroup: TSESTree.Node[] = []; alignGroups.push(currentAlignGroup); + let prevNode: TSESTree.Node | undefined = undefined; + for (const node of members) { - const prevNode = currentAlignGroup.at(-1); + let prevAlignedNode = currentAlignGroup.at(-1); + if (prevAlignedNode !== prevNode) { + prevAlignedNode = undefined; + } - if (prevNode && continuesAlignGroup(prevNode, node)) { + if (prevAlignedNode && continuesAlignGroup(prevAlignedNode, node)) { currentAlignGroup.push(node); } else if (prevNode?.loc.start.line === node.loc.start.line) { - if (currentAlignGroup.length) { + if (prevAlignedNode /* prevNode === prevAlignedNode */) { unalignedElements.push(currentAlignGroup.pop()!); } unalignedElements.push(node); @@ -367,6 +372,8 @@ export default util.createRule({ currentAlignGroup = [node]; alignGroups.push(currentAlignGroup); } + + prevNode = node; } unalignedElements.push( diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 69d12b273c32..3c60c9f8caeb 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -20,6 +20,10 @@ ruleTester.run('key-spacing', rule, { code: 'interface X {\n a: number;\n abc: string; c: number;\n};', options: [{ align: 'value' }], }, + { + code: 'interface X {\n a: number;\n abc: string; c: number; de: boolean;\n abcef: number;\n};', + options: [{ align: 'colon' }], + }, { code: 'interface X {\n a?: number;\n abc: string\n};', options: [{ align: 'value' }], From 4b0759c04eac7f10b677806b0a085347ba3b1f94 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Fri, 16 Dec 2022 14:04:21 +0100 Subject: [PATCH 25/40] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Use=20Array.concat?= =?UTF-8?q?=20instead=20of=20.push(...)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .push could error if 60k + arguments --- packages/eslint-plugin/src/rules/key-spacing.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 2112c5d59fab..7748b91cf4a9 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -376,8 +376,8 @@ export default util.createRule({ prevNode = node; } - unalignedElements.push( - ...alignGroups + unalignedElements = unalignedElements.concat( + alignGroups .filter(group => group.length === 1) .flatMap(group => group), ); From 6d61144c2ece5a760ba08ef3c09299e71babc969 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Tue, 24 Jan 2023 11:07:48 +0100 Subject: [PATCH 26/40] =?UTF-8?q?=F0=9F=8E=A8=20Improve=20readability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eslint-plugin/src/rules/key-spacing.ts | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 7748b91cf4a9..7d5f931ba3ea 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -94,12 +94,12 @@ export default util.createRule({ function checkBeforeColon( node: KeyTypeNode, - nBeforeColon: number, + expectedWhitespaceBeforeColon: number, mode: 'strict' | 'minimum', ): void { const colon = node.typeAnnotation!.loc.start.column; const keyEnd = getKeyLocEnd(node); - const difference = colon - keyEnd.column - nBeforeColon; + const difference = colon - keyEnd.column - expectedWhitespaceBeforeColon; if (mode === 'strict' ? difference : difference < 0) { context.report({ node, @@ -127,12 +127,12 @@ export default util.createRule({ function checkAfterColon( node: KeyTypeNode, - nAfterColon: number, + expectedWhitespaceAfterColon: number, mode: 'strict' | 'minimum', ): void { const colon = node.typeAnnotation!.loc.start.column; const typeStart = node.typeAnnotation!.typeAnnotation.loc.start.column; - const difference = typeStart - colon - 1 - nAfterColon; + const difference = typeStart - colon - 1 - expectedWhitespaceAfterColon; if (mode === 'strict' ? difference : difference < 0) { context.report({ node, @@ -219,7 +219,7 @@ export default util.createRule({ ? options.multiLine.align.beforeColon : options.multiLine.beforeColon : options.beforeColon) ?? false; - const nBeforeColon = beforeColon ? 1 : 0; + const expectedWhitespaceBeforeColon = beforeColon ? 1 : 0; const afterColon = (typeof options.align === 'object' ? options.align.afterColon @@ -228,7 +228,7 @@ export default util.createRule({ ? options.multiLine.align.afterColon : options.multiLine.afterColon : options.afterColon) ?? true; - const nAfterColon = afterColon ? 1 : 0; + const expectedWhitespaceAfterColon = afterColon ? 1 : 0; const mode = (typeof options.align === 'object' ? options.align.mode @@ -244,11 +244,11 @@ export default util.createRule({ alignColumn = Math.max( alignColumn, align === 'colon' - ? getKeyLocEnd(node).column + nBeforeColon + ? getKeyLocEnd(node).column + expectedWhitespaceBeforeColon : getKeyLocEnd(node).column + ':'.length + - nAfterColon + - nBeforeColon, + expectedWhitespaceAfterColon + + expectedWhitespaceBeforeColon, ); } } @@ -292,9 +292,9 @@ export default util.createRule({ } if (align === 'colon') { - checkAfterColon(node, nAfterColon, mode); + checkAfterColon(node, expectedWhitespaceAfterColon, mode); } else { - checkBeforeColon(node, nBeforeColon, mode); + checkBeforeColon(node, expectedWhitespaceBeforeColon, mode); } } } @@ -311,7 +311,7 @@ export default util.createRule({ : options.multiLine ? options.multiLine.beforeColon : options.beforeColon) ?? false; - const nBeforeColon = beforeColon ? 1 : 0; + const expectedWhitespaceBeforeColon = beforeColon ? 1 : 0; const afterColon = (singleLine ? options.singleLine @@ -320,7 +320,7 @@ export default util.createRule({ : options.multiLine ? options.multiLine.afterColon : options.afterColon) ?? true; - const nAfterColon = afterColon ? 1 : 0; + const expectedWhitespaceAfterColon = afterColon ? 1 : 0; const mode = (singleLine ? options.singleLine @@ -331,8 +331,8 @@ export default util.createRule({ : options.mode) ?? 'strict'; if (isKeyTypeNode(node)) { - checkBeforeColon(node, nBeforeColon, mode); - checkAfterColon(node, nAfterColon, mode); + checkBeforeColon(node, expectedWhitespaceBeforeColon, mode); + checkAfterColon(node, expectedWhitespaceAfterColon, mode); } } @@ -364,8 +364,10 @@ export default util.createRule({ if (prevAlignedNode && continuesAlignGroup(prevAlignedNode, node)) { currentAlignGroup.push(node); } else if (prevNode?.loc.start.line === node.loc.start.line) { - if (prevAlignedNode /* prevNode === prevAlignedNode */) { - unalignedElements.push(currentAlignGroup.pop()!); + if (prevAlignedNode) { + // Here, prevNode === prevAlignedNode === currentAlignGroup.at(-1) + unalignedElements.push(prevAlignedNode); + currentAlignGroup.pop(); } unalignedElements.push(node); } else { @@ -377,9 +379,7 @@ export default util.createRule({ } unalignedElements = unalignedElements.concat( - alignGroups - .filter(group => group.length === 1) - .flatMap(group => group), + ...alignGroups.filter(group => group.length === 1), ); alignGroups = alignGroups.filter(group => group.length >= 2); } else { From 47156ecedfa038eb346890677c7f3ffcc0655786 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Tue, 24 Jan 2023 11:20:40 +0100 Subject: [PATCH 27/40] =?UTF-8?q?=F0=9F=8E=A8=20Use=20tempate=20literals?= =?UTF-8?q?=20in=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/rules/key-spacing.test.ts | 406 +++++++++++++++--- 1 file changed, 348 insertions(+), 58 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 3c60c9f8caeb..53a0a92dff1c 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -13,63 +13,149 @@ ruleTester.run('key-spacing', rule, { valid: [ // align: value { - code: 'interface X {\n a: number;\n abc: string\n};', + code: ` +interface X { + a: number; + abc: string +}; + `, options: [{ align: 'value' }], }, { - code: 'interface X {\n a: number;\n abc: string; c: number;\n};', + code: ` +interface X { + a: number; + abc: string; c: number; +}; + `, options: [{ align: 'value' }], }, { - code: 'interface X {\n a: number;\n abc: string; c: number; de: boolean;\n abcef: number;\n};', + code: ` +interface X { + a: number; + abc: string; c: number; de: boolean; + abcef: number; +}; + `, options: [{ align: 'colon' }], }, { - code: 'interface X {\n a?: number;\n abc: string\n};', + code: ` +interface X { + a?: number; + abc: string +}; + `, options: [{ align: 'value' }], }, { - code: 'interface X {\n a: number;\n // Some comment\n abc: string\n};', + code: ` +interface X { + a: number; + // Some comment + abc: string +}; + `, options: [{ align: 'value' }], }, { - code: 'interface X {\n a: number;\n // Some comment\n // on multiple lines\n abc: string\n};', + code: ` +interface X { + a: number; + // Some comment + // on multiple lines + abc: string +}; + `, options: [{ align: 'value' }], }, { - code: 'interface X {\n a: number;\n /**\n * Doc comment\n */\n abc: string\n};', + code: ` +interface X { + a: number; + /** + * Doc comment + */ + abc: string +}; + `, options: [{ align: 'value' }], }, { - code: 'interface X {\n a: number;\n\n abc: string\n};', + code: ` +interface X { + a: number; + + abc: string +}; + `, options: [{ align: 'value' }], }, { - code: 'class X {\n a: number;\n abc: string\n};', + code: ` +class X { + a: number; + abc: string +}; + `, options: [{ align: 'value' }], }, { - code: 'class X {\n a?: number;\n abc: string\n};', + code: ` +class X { + a?: number; + abc: string +}; + `, options: [{ align: 'value' }], }, { - code: 'class X {\n x: number;\n z = 1;\n xbcef: number;\n }', + code: ` +class X { + x: number; + z = 1; + xbcef: number; + } + `, options: [{ align: 'value' }], }, { - code: 'class X {\n a: number;\n\n abc: string\n};', + code: ` +class X { + a: number; + + abc: string +}; + `, options: [{ align: 'value' }], }, { - code: 'type X = {\n a: number;\n abc: string\n};', + code: ` +type X = { + a: number; + abc: string +}; + `, options: [{ align: 'value' }], }, { - code: 'type X = {\n a: number;\n\n abc: string\n};', + code: ` +type X = { + a: number; + + abc: string +}; + `, options: [{ align: 'value' }], }, { - code: 'type X = {\n a : number;\n abc: string\n};', + code: ` +type X = { + a : number; + abc: string +}; + `, options: [{ align: 'value', mode: 'minimum' }], }, { @@ -104,29 +190,59 @@ class X { }, // align: colon { - code: 'interface X {\n a : number;\n abc: string\n};', + code: ` +interface X { + a : number; + abc: string +}; + `, options: [{ align: 'colon' }], }, { - code: 'interface X {\n a :number;\n abc:string\n};', + code: ` +interface X { + a :number; + abc:string +}; + `, options: [{ align: 'colon', afterColon: false }], }, { - code: 'interface X {\n a : number;\n abc: string\n};', + code: ` +interface X { + a : number; + abc: string +}; + `, options: [{ align: 'colon', mode: 'minimum' }], }, // no align { - code: 'interface X {\n a: number;\n abc: string\n};', + code: ` +interface X { + a: number; + abc: string +}; + `, options: [{}], }, { - code: 'interface X {\n a : number;\n abc : string\n};', + code: ` +interface X { + a : number; + abc : string +}; + `, options: [{ beforeColon: true }], }, // singleLine / multiLine { - code: 'interface X {\n a : number;\n abc : string\n};', + code: ` +interface X { + a : number; + abc : string +}; + `, options: [ { singleLine: { beforeColon: false, afterColon: false }, @@ -135,7 +251,12 @@ class X { ], }, { - code: 'interface X {\n a : number;\n abc : string\n};', + code: ` +interface X { + a : number; + abc : string +}; + `, options: [ { singleLine: { beforeColon: false, afterColon: false }, @@ -144,7 +265,12 @@ class X { ], }, { - code: 'interface X {\n a : number;\n abc : string\n};', + code: ` +interface X { + a : number; + abc : string +}; + `, options: [ { singleLine: { beforeColon: false, afterColon: false }, @@ -162,7 +288,14 @@ class X { ], }, { - code: 'interface X {\n a : number;\n abc: string\n\n xadzd : number;\n};', + code: ` +interface X { + a : number; + abc: string + + xadzd : number; +}; + `, options: [ { singleLine: { beforeColon: false, afterColon: false }, @@ -180,7 +313,9 @@ class X { ], }, { - code: 'interface X { a:number; abc:string; };', + code: ` +interface X { a:number; abc:string; }; + `, options: [ { singleLine: { beforeColon: false, afterColon: false }, @@ -192,63 +327,174 @@ class X { invalid: [ // align: value { - code: 'interface X {\n a: number;\n abc: string\n};', - output: 'interface X {\n a: number;\n abc: string\n};', + code: ` +interface X { + a: number; + abc: string +}; + `, + output: ` +interface X { + a: number; + abc: string +}; + `, options: [{ align: 'value' }], errors: [{ messageId: 'missingValue' }], }, { - code: 'class X {\n a: number;\n abc: string\n};', - output: 'class X {\n a: number;\n abc: string\n};', + code: ` +class X { + a: number; + abc: string +}; + `, + output: ` +class X { + a: number; + abc: string +}; + `, options: [{ align: 'value' }], errors: [{ messageId: 'missingValue' }], }, { - code: 'class X {\n a: number;\n abc: string\n};', - output: 'class X {\n a: number;\n abc: string\n};', + code: ` +class X { + a: number; + abc: string +}; + `, + output: ` +class X { + a: number; + abc: string +}; + `, options: [{ align: 'value', mode: 'minimum' }], errors: [{ messageId: 'missingValue' }], }, { - code: 'type X = {\n a: number;\n abc: string\n};', - output: 'type X = {\n a: number;\n abc: string\n};', + code: ` +type X = { + a: number; + abc: string +}; + `, + output: ` +type X = { + a: number; + abc: string +}; + `, options: [{ align: 'value' }], errors: [{ messageId: 'missingValue' }], }, { - code: 'interface X {\n a: number;\n abc: string\n};', - output: 'interface X {\n a: number;\n abc: string\n};', + code: ` +interface X { + a: number; + abc: string +}; + `, + output: ` +interface X { + a: number; + abc: string +}; + `, options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }], }, { - code: 'class X {\n a: number;\n abc: string\n};', - output: 'class X {\n a: number;\n abc: string\n};', + code: ` +class X { + a: number; + abc: string +}; + `, + output: ` +class X { + a: number; + abc: string +}; + `, options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }], }, { - code: 'class X {\n x: number;\n z = 1;\n xbcef: number;\n }', - output: 'class X {\n x: number;\n z = 1;\n xbcef: number;\n }', + code: ` +class X { + x: number; + z = 1; + xbcef: number; + } + `, + output: ` +class X { + x: number; + z = 1; + xbcef: number; + } + `, options: [{ align: 'value' }], errors: [{ messageId: 'missingValue' }], }, { - code: 'interface X {\n a: number;\n\n abc : string\n};', - output: 'interface X {\n a: number;\n\n abc: string\n};', + code: ` +interface X { + a: number; + + abc : string +}; + `, + output: ` +interface X { + a: number; + + abc: string +}; + `, options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }, { messageId: 'extraKey' }], }, { - code: 'class X {\n a: number;\n\n abc : string\n};', - output: 'class X {\n a: number;\n\n abc: string\n};', + code: ` +class X { + a: number; + + abc : string +}; + `, + output: ` +class X { + a: number; + + abc: string +}; + `, options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }, { messageId: 'extraKey' }], }, { - code: 'interface X {\n a: number;\n // Some comment\n\n // interrupted in the middle\n abc: string\n};', - output: - 'interface X {\n a: number;\n // Some comment\n\n // interrupted in the middle\n abc: string\n};', + code: ` +interface X { + a: number; + // Some comment + + // interrupted in the middle + abc: string +}; + `, + output: ` +interface X { + a: number; + // Some comment + + // interrupted in the middle + abc: string +}; + `, options: [{ align: 'value' }], errors: [{ messageId: 'extraValue' }], }, @@ -364,26 +610,62 @@ class X { }, // align: colon { - code: 'interface X {\n a : number;\n abc: string\n};', - output: 'interface X {\n a : number;\n abc: string\n};', + code: ` +interface X { + a : number; + abc: string +}; + `, + output: ` +interface X { + a : number; + abc: string +}; + `, options: [{ align: 'colon' }], errors: [{ messageId: 'extraKey' }], }, // no align { - code: 'interface X {\n [x: number]: string;\n}', - output: 'interface X {\n [x: number]: string;\n}', + code: ` +interface X { + [x: number]: string; +} + `, + output: ` +interface X { + [x: number]: string; +} + `, errors: [{ messageId: 'extraValue' }], }, { - code: 'interface X {\n [x: number]:string;\n}', - output: 'interface X {\n [x: number]: string;\n}', + code: ` +interface X { + [x: number]:string; +} + `, + output: ` +interface X { + [x: number]: string; +} + `, errors: [{ messageId: 'missingValue' }], }, // singleLine / multiLine { - code: 'interface X {\n a:number;\n abc:string\n};', - output: 'interface X {\n a : number;\n abc : string\n};', + code: ` +interface X { + a:number; + abc:string +}; + `, + output: ` +interface X { + a : number; + abc : string +}; + `, options: [ { singleLine: { beforeColon: false, afterColon: false }, @@ -398,8 +680,12 @@ class X { ], }, { - code: 'interface X { a : number; abc : string; };', - output: 'interface X { a:number; abc:string; };', + code: ` +interface X { a : number; abc : string; }; + `, + output: ` +interface X { a:number; abc:string; }; + `, options: [ { singleLine: { beforeColon: false, afterColon: false }, @@ -414,8 +700,12 @@ class X { ], }, { - code: 'interface X { a : number; abc : string; };', - output: 'interface X { a: number; abc: string; };', + code: ` +interface X { a : number; abc : string; }; + `, + output: ` +interface X { a: number; abc: string; }; + `, options: [ { singleLine: { beforeColon: false, afterColon: true }, From ae33b9b67c6a2a689c2bc3881ab2fc18b12c15fc Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Tue, 24 Jan 2023 11:27:58 +0100 Subject: [PATCH 28/40] =?UTF-8?q?=E2=9C=85=20Add=20test=20with=20anonymous?= =?UTF-8?q?=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/rules/key-spacing.test.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 53a0a92dff1c..fdd6cfff4486 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -23,6 +23,15 @@ interface X { }, { code: ` +let x: { + a: number; + abc: string +}; + `, + options: [{ align: 'value' }], + }, + { + code: ` interface X { a: number; abc: string; c: number; @@ -344,6 +353,22 @@ interface X { }, { code: ` +let x: { + a: number; + abc: string +}; + `, + output: ` +let x: { + a: number; + abc: string +}; + `, + options: [{ align: 'value' }], + errors: [{ messageId: 'missingValue' }], + }, + { + code: ` class X { a: number; abc: string From 722e092641e550fe1ce03b143c9b7a2cf5f609c0 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Tue, 24 Jan 2023 12:01:42 +0100 Subject: [PATCH 29/40] =?UTF-8?q?=E2=9C=85=20Add=20test=20with=20quoted=20?= =?UTF-8?q?keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/rules/key-spacing.test.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index fdd6cfff4486..8fc67861d30d 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -23,6 +23,15 @@ interface X { }, { code: ` +interface X { + "a:b": number; + abcde: string +}; + `, + options: [{ align: 'value' }], + }, + { + code: ` let x: { a: number; abc: string @@ -353,6 +362,22 @@ interface X { }, { code: ` +interface X { + a: number; + "a:c": string +}; + `, + output: ` +interface X { + a: number; + "a:c": string +}; + `, + options: [{ align: 'value' }], + errors: [{ messageId: 'missingValue' }], + }, + { + code: ` let x: { a: number; abc: string From 301731b3e645532be07255fad48c538eef9d0f4c Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Tue, 24 Jan 2023 12:38:55 +0100 Subject: [PATCH 30/40] =?UTF-8?q?=E2=9E=95=20Add=20grapheme-splitter=20to?= =?UTF-8?q?=20deal=20with=20emojis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/eslint-plugin/package.json | 1 + .../eslint-plugin/src/rules/key-spacing.ts | 30 ++++++++++++-- .../tests/rules/key-spacing.test.ts | 40 +++++++++++++++++++ yarn.lock | 5 +++ 4 files changed, 73 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 31cf59e366d0..c83888a463a3 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -49,6 +49,7 @@ "@typescript-eslint/utils": "5.46.1", "debug": "^4.3.4", "ignore": "^5.2.0", + "grapheme-splitter": "^1.0.4", "natural-compare-lite": "^1.4.0", "regexpp": "^3.2.0", "semver": "^7.3.7", diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 7d5f931ba3ea..58cbc8d7a397 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -1,9 +1,24 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import GraphemeSplitter from 'grapheme-splitter'; import * as util from '../util'; import { getESLintCoreRule } from '../util/getESLintCoreRule'; +let splitter: GraphemeSplitter; +function isASCII(value: string): boolean { + return /^[\u0020-\u007f]*$/u.test(value); +} +function getStringLength(value: string): number { + if (isASCII(value)) { + return value.length; + } + + splitter ??= new GraphemeSplitter(); + + return splitter.countGraphemes(value); +} + const baseRule = getESLintCoreRule('key-spacing'); export type Options = util.InferOptionsTypeFromRule; @@ -35,6 +50,14 @@ export default util.createRule({ const sourceCode = context.getSourceCode(); const baseRules = baseRule.create(context); + /** + * @returns the column of the position after converting all unicode characters in the line to 1 char length + */ + function adjustedColumn(position: TSESTree.Position): number { + const line = position.line - 1; // position.line is 1-indexed + return getStringLength(sourceCode.lines[line].slice(0, position.column)); + } + /** * Starting from the given a node (a property.key node here) looks forward * until it finds the last token before a colon punctuator and returns it. @@ -241,11 +264,12 @@ export default util.createRule({ for (const node of group) { if (isKeyTypeNode(node)) { + const keyEnd = adjustedColumn(getKeyLocEnd(node)); alignColumn = Math.max( alignColumn, align === 'colon' - ? getKeyLocEnd(node).column + expectedWhitespaceBeforeColon - : getKeyLocEnd(node).column + + ? keyEnd + expectedWhitespaceBeforeColon + : keyEnd + ':'.length + expectedWhitespaceAfterColon + expectedWhitespaceBeforeColon, @@ -261,7 +285,7 @@ export default util.createRule({ align === 'colon' ? node.typeAnnotation! : node.typeAnnotation!.typeAnnotation; - const difference = toCheck.loc.start.column - alignColumn; + const difference = adjustedColumn(toCheck.loc.start) - alignColumn; if (difference) { context.report({ diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 8fc67861d30d..83b308c3008d 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -41,6 +41,20 @@ let x: { }, { code: ` +let x: { + a: number; + "𐌘": string; + [𐌘]: Date; + "🌷": "bar", // 2 code points + "🎁": "baz", // 2 code points + "🇮🇳": "qux", // 4 code points + "🏳️‍🌈": "xyz", // 6 code points +}; + `, + options: [{ align: 'value' }], + }, + { + code: ` interface X { a: number; abc: string; c: number; @@ -394,6 +408,32 @@ let x: { }, { code: ` +let x: { + a: number; + "🌷": "bar", // 2 code points + "🎁": "baz", // 2 code points + "🇮🇳": "qux", // 4 code points + "🏳️‍🌈": "xyz", // 6 code points + [𐌘]: string + "𐌘": string +}; + `, + output: ` +let x: { + a: number; + "🌷": "bar", // 2 code points + "🎁": "baz", // 2 code points + "🇮🇳": "qux", // 4 code points + "🏳️‍🌈": "xyz", // 6 code points + [𐌘]: string + "𐌘": string +}; + `, + options: [{ align: 'value' }], + errors: [{ messageId: 'missingValue' }], + }, + { + code: ` class X { a: number; abc: string diff --git a/yarn.lock b/yarn.lock index 0c714b7280aa..8186b40cd6da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7908,6 +7908,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + gray-matter@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" From 9eaebb2c3fd397f7c2d1f933639783b98d1ad5c0 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Tue, 24 Jan 2023 12:55:37 +0100 Subject: [PATCH 31/40] =?UTF-8?q?=E2=9C=85=20Add=20test=20case=20for=20mul?= =?UTF-8?q?tiline=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/rules/key-spacing.test.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 83b308c3008d..a6cdcfe2160c 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -104,6 +104,19 @@ interface X { }, { code: ` +interface X { + a: number; + /** + * Some comment + * on multiple lines + */ + abc: string +}; + `, + options: [{ align: 'value' }], + }, + { + code: ` interface X { a: number; /** @@ -590,6 +603,32 @@ interface X { }, { code: ` +interface X { + a: number; + /** + * Multiline comment + */ + + /** interrupted in the middle */ + abc: string +}; + `, + output: ` +interface X { + a: number; + /** + * Multiline comment + */ + + /** interrupted in the middle */ + abc: string +}; + `, + options: [{ align: 'value' }], + errors: [{ messageId: 'extraValue' }], + }, + { + code: ` interface X { a: number; prop: { From ee6d9bc3476c33b87df8b5274f9fb74c7a1fb85d Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Tue, 24 Jan 2023 14:55:09 +0100 Subject: [PATCH 32/40] =?UTF-8?q?=F0=9F=9A=A8=20Remove=20'in'=20statements?= =?UTF-8?q?,=20reduce=20amount=20of=20null-assertions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eslint-plugin/src/rules/key-spacing.ts | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 58cbc8d7a397..a67dfd224825 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -92,7 +92,7 @@ export default util.createRule({ * To handle index signatures, to get the whole text for the parameters */ function getKeyText(node: KeyTypeNode): string { - if ('key' in node) { + if (node.type !== AST_NODE_TYPES.TSIndexSignature) { return sourceCode.getText(node.key); } @@ -111,7 +111,9 @@ export default util.createRule({ */ function getKeyLocEnd(node: KeyTypeNode): TSESTree.Position { return getLastTokenBeforeColon( - 'key' in node ? node.key : node.parameters.at(-1)!, + node.type !== AST_NODE_TYPES.TSIndexSignature + ? node.key + : node.parameters.at(-1)!, ).loc.end; } @@ -120,7 +122,9 @@ export default util.createRule({ expectedWhitespaceBeforeColon: number, mode: 'strict' | 'minimum', ): void { - const colon = node.typeAnnotation!.loc.start.column; + // KeyTypeNode always has type annotation + const typeAnnotation = node.typeAnnotation!; + const colon = typeAnnotation.loc.start.column; const keyEnd = getKeyLocEnd(node); const difference = colon - keyEnd.column - expectedWhitespaceBeforeColon; if (mode === 'strict' ? difference : difference < 0) { @@ -130,12 +134,12 @@ export default util.createRule({ fix: fixer => { if (difference > 0) { return fixer.removeRange([ - node.typeAnnotation!.range[0] - difference, - node.typeAnnotation!.range[0], + typeAnnotation.range[0] - difference, + typeAnnotation.range[0], ]); } else { return fixer.insertTextBefore( - node.typeAnnotation!, + typeAnnotation, ' '.repeat(-difference), ); } @@ -153,8 +157,10 @@ export default util.createRule({ expectedWhitespaceAfterColon: number, mode: 'strict' | 'minimum', ): void { - const colon = node.typeAnnotation!.loc.start.column; - const typeStart = node.typeAnnotation!.typeAnnotation.loc.start.column; + // KeyTypeNode always has type annotation + const typeAnnotation = node.typeAnnotation!; + const colon = typeAnnotation.loc.start.column; + const typeStart = typeAnnotation.typeAnnotation.loc.start.column; const difference = typeStart - colon - 1 - expectedWhitespaceAfterColon; if (mode === 'strict' ? difference : difference < 0) { context.report({ @@ -163,12 +169,12 @@ export default util.createRule({ fix: fixer => { if (difference > 0) { return fixer.removeRange([ - node.typeAnnotation!.typeAnnotation.range[0] - difference, - node.typeAnnotation!.typeAnnotation.range[0], + typeAnnotation.typeAnnotation.range[0] - difference, + typeAnnotation.typeAnnotation.range[0], ]); } else { return fixer.insertTextBefore( - node.typeAnnotation!.typeAnnotation, + typeAnnotation.typeAnnotation, ' '.repeat(-difference), ); } @@ -281,10 +287,10 @@ export default util.createRule({ if (!isKeyTypeNode(node)) { continue; } + // KeyTypeNode always has type annotation + const typeAnnotation = node.typeAnnotation!; const toCheck = - align === 'colon' - ? node.typeAnnotation! - : node.typeAnnotation!.typeAnnotation; + align === 'colon' ? typeAnnotation : typeAnnotation.typeAnnotation; const difference = adjustedColumn(toCheck.loc.start) - alignColumn; if (difference) { @@ -368,7 +374,8 @@ export default util.createRule({ ): void { const isSingleLine = body.loc.start.line === body.loc.end.line; - const members = 'members' in body ? body.members : body.body; + const members = + body.type === AST_NODE_TYPES.TSTypeLiteral ? body.members : body.body; let alignGroups: TSESTree.Node[][] = []; let unalignedElements: TSESTree.Node[] = []; From dbc6a91963c3148f3e74a921f1a5c3033808adf3 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Tue, 24 Jan 2023 15:04:54 +0100 Subject: [PATCH 33/40] =?UTF-8?q?=E2=9C=85=20Add=20test=20case=20for=20pro?= =?UTF-8?q?perties=20without=20type=20annotation=20or=20assignments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/rules/key-spacing.test.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index a6cdcfe2160c..a1a2843b1db6 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -74,6 +74,16 @@ interface X { }, { code: ` +interface X { + a : number; + abc; + abcef: number; +}; + `, + options: [{ align: 'colon' }], + }, + { + code: ` interface X { a?: number; abc: string @@ -479,6 +489,24 @@ class X { }, { code: ` +class X { + a: number; + b; + abc: string +}; + `, + output: ` +class X { + a: number; + b; + abc: string +}; + `, + options: [{ align: 'value', mode: 'minimum' }], + errors: [{ messageId: 'missingValue' }], + }, + { + code: ` type X = { a: number; abc: string From b1033ef7b002381b56dfd7f6d6de47d619d5bd42 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Tue, 24 Jan 2023 15:32:52 +0100 Subject: [PATCH 34/40] =?UTF-8?q?=E2=9C=85=20Add=20wacky=20test=20cases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/rules/key-spacing.test.ts | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index a1a2843b1db6..1e35608c0b0e 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -871,5 +871,121 @@ interface X { a: number; abc: string; }; ], errors: [{ messageId: 'extraKey' }, { messageId: 'extraKey' }], }, + { + code: ` +type Wacky = { + a: number; + b: string; + agc: number; + middle: Date | { + inner: { + a: boolean; + bc: boolean; + "🌷": "rose"; + } + [x: number]: string; + abc: boolean; + } +} & { + a: "string"; + abc: number; +} + `, + output: ` +type Wacky = { + a: number; + b: string; + agc: number; + middle: Date | { + inner: { + a: boolean; + bc: boolean; + "🌷": "rose"; + } + [x: number]: string; + abc: boolean; + } +} & { + a: "string"; + abc: number; +} + `, + options: [{ align: 'value' }], + errors: [ + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + ], + }, + { + code: ` +class Wacky { + a: number; + b?: string; + public z: number; + abc = 10; + private override xy: number; + static x = "test"; + static abcdef: number = 1; + get fn(): number { return 0; }; + inter: number; + get fn2(): number { + return 1; + }; + agc: number; + middle: Date | { + inner: { + a: boolean; + bc: boolean; + "🌷": "rose"; + } + [x: number]: string; + abc: boolean; + } +} + `, + output: ` +class Wacky { + a: number; + b?: string; + public z: number; + abc = 10; + private override xy: number; + static x = "test"; + static abcdef: number = 1; + get fn(): number { return 0; }; + inter: number; + get fn2(): number { + return 1; + }; + agc: number; + middle: Date | { + inner: { + a: boolean; + bc: boolean; + "🌷": "rose"; + } + [x: number]: string; + abc: boolean; + } +} + `, + options: [{ align: 'value' }], + errors: [ + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + { messageId: 'missingValue' }, + ], + }, ], }); From a7a5b2c39ae23847724da828434b992da46a2509 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Tue, 24 Jan 2023 16:00:29 +0100 Subject: [PATCH 35/40] =?UTF-8?q?=E2=9C=85=20Add=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/rules/key-spacing.test.ts | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 1e35608c0b0e..716fc8ea48f3 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -344,6 +344,26 @@ interface X { }, { code: ` +interface X { + a : number; + abc : string +}; + `, + options: [ + { + beforeColon: true, + afterColon: true, + align: { + on: 'colon', + mode: 'strict', + afterColon: true, + beforeColon: true, + }, + }, + ], + }, + { + code: ` interface X { a : number; abc: string @@ -782,6 +802,22 @@ interface X { options: [{ align: 'colon' }], errors: [{ messageId: 'extraKey' }], }, + { + code: ` +interface X { + a : number; + abc: string +}; + `, + output: ` +interface X { + a : number; + abc : string +}; + `, + options: [{ align: 'colon', beforeColon: true, afterColon: true }], + errors: [{ messageId: 'missingKey' }], + }, // no align { code: ` @@ -873,6 +909,86 @@ interface X { a: number; abc: string; }; }, { code: ` +interface X { a:number; abc:string; }; + `, + output: ` +interface X { a : number; abc : string; }; + `, + options: [ + { + singleLine: { beforeColon: true, afterColon: true, mode: 'strict' }, + multiLine: { beforeColon: true, afterColon: true }, + }, + ], + errors: [ + { messageId: 'missingKey' }, + { messageId: 'missingValue' }, + { messageId: 'missingKey' }, + { messageId: 'missingValue' }, + ], + }, + { + code: ` +interface X { a:number; abc: string; }; + `, + output: ` +interface X { a : number; abc : string; }; + `, + options: [ + { + singleLine: { beforeColon: true, afterColon: true, mode: 'minimum' }, + multiLine: { beforeColon: true, afterColon: true }, + }, + ], + errors: [ + { messageId: 'missingKey' }, + { messageId: 'missingValue' }, + { messageId: 'missingKey' }, + ], + }, + { + code: ` +interface X { a : number; abc : string; }; + `, + output: ` +interface X { a:number; abc:string; }; + `, + options: [ + { + beforeColon: false, + afterColon: false, + }, + ], + errors: [ + { messageId: 'extraKey' }, + { messageId: 'extraValue' }, + { messageId: 'extraKey' }, + { messageId: 'extraValue' }, + ], + }, + { + code: ` +interface X { a:number; abc:string; }; + `, + output: ` +interface X { a : number; abc : string; }; + `, + options: [ + { + beforeColon: true, + afterColon: true, + mode: 'strict', + }, + ], + errors: [ + { messageId: 'missingKey' }, + { messageId: 'missingValue' }, + { messageId: 'missingKey' }, + { messageId: 'missingValue' }, + ], + }, + { + code: ` type Wacky = { a: number; b: string; From cb5876e2bd44f06a6a01a67be42f7ef79951482b Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Tue, 24 Jan 2023 16:42:05 +0100 Subject: [PATCH 36/40] =?UTF-8?q?=E2=9C=85=20Add=20coverage,=20again?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/rules/key-spacing.test.ts | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index 716fc8ea48f3..d3c96d293f9c 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -215,6 +215,24 @@ type X = { }, { code: ` +type X = { + a : number; + abc: string +}; + `, + options: [ + { + align: { + on: 'value', + mode: 'minimum', + beforeColon: false, + afterColon: true, + }, + }, + ], + }, + { + code: ` interface X { a: number; prop: { @@ -389,6 +407,56 @@ interface X { }, { code: ` +interface X { + a : number; + abc: string + + xadzd : number; +}; + `, + options: [ + { + singleLine: { beforeColon: false, afterColon: false }, + multiLine: { + beforeColon: true, + afterColon: true, + mode: 'strict', + align: { + on: 'colon', + afterColon: true, + beforeColon: false, + }, + }, + }, + ], + }, + { + code: ` +interface X { + a : number; + abc: string + + xadzd : number; +}; + `, + options: [ + { + singleLine: { beforeColon: false, afterColon: false }, + multiLine: { + beforeColon: true, + afterColon: true, + mode: 'minimum', + align: { + on: 'colon', + afterColon: true, + beforeColon: false, + }, + }, + }, + ], + }, + { + code: ` interface X { a:number; abc:string; }; `, options: [ From 27877629bbbd62dbbf7871a7dded5d6ce48281f9 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Tue, 24 Jan 2023 17:08:54 +0100 Subject: [PATCH 37/40] =?UTF-8?q?=E2=9C=85=20Add=20coverage,=20again?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/rules/key-spacing.test.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index d3c96d293f9c..d9aaf562c9d1 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -328,6 +328,21 @@ interface X { interface X { a : number; abc : string +}; + `, + options: [ + { + align: { on: 'value', beforeColon: true, afterColon: true }, + singleLine: { beforeColon: false, afterColon: false }, + multiLine: { beforeColon: false, afterColon: false }, + }, + ], + }, + { + code: ` +interface X { + a : number; + abc : string }; `, options: [ @@ -519,6 +534,22 @@ let x: { }, { code: ` +let x: { + a: number; + abc: string +}; + `, + output: ` +let x: { + a: number; + abc: string +}; + `, + options: [{ align: { on: 'value' } }], + errors: [{ messageId: 'missingValue' }], + }, + { + code: ` let x: { a: number; "🌷": "bar", // 2 code points @@ -878,6 +909,22 @@ interface X { }; `, output: ` +interface X { + a : number; + abc: string +}; + `, + options: [{ align: { on: 'colon' } }], + errors: [{ messageId: 'extraKey' }], + }, + { + code: ` +interface X { + a : number; + abc: string +}; + `, + output: ` interface X { a : number; abc : string From 79d8ec0471f9764546c43ad6a0c446628409e679 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Tue, 24 Jan 2023 17:47:54 +0100 Subject: [PATCH 38/40] =?UTF-8?q?=E2=9C=85=20Add=20coverage=20when=20align?= =?UTF-8?q?=20is=20an=20object,=20but=20align.on=20is=20missing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It defaults to 'colon' in this case --- .../tests/rules/key-spacing.test.ts | 56 +++++++++++++++++++ .../eslint-plugin/typings/eslint-rules.d.ts | 4 +- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/key-spacing.test.ts b/packages/eslint-plugin/tests/rules/key-spacing.test.ts index d9aaf562c9d1..40206258671c 100644 --- a/packages/eslint-plugin/tests/rules/key-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/key-spacing.test.ts @@ -340,6 +340,21 @@ interface X { }, { code: ` +interface X { + a : number; + abc : string +}; + `, + options: [ + { + align: { beforeColon: true, afterColon: true }, // defaults to 'colon' + singleLine: { beforeColon: false, afterColon: false }, + multiLine: { beforeColon: false, afterColon: false }, + }, + ], + }, + { + code: ` interface X { a : number; abc : string @@ -380,6 +395,28 @@ interface X { interface X { a : number; abc : string +}; + `, + options: [ + { + singleLine: { beforeColon: false, afterColon: false }, + multiLine: { + beforeColon: true, + afterColon: true, + align: { + mode: 'strict', + afterColon: true, + beforeColon: true, + }, + }, + }, + ], + }, + { + code: ` +interface X { + a : number; + abc : string }; `, options: [ @@ -397,6 +434,25 @@ interface X { }, { code: ` +interface X { + a : number; + abc : string +}; + `, + options: [ + { + beforeColon: true, + afterColon: true, + align: { + mode: 'strict', + afterColon: true, + beforeColon: true, + }, + }, + ], + }, + { + code: ` interface X { a : number; abc: string diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index 7eda886c91bb..38682f60c5b2 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -154,7 +154,7 @@ declare module 'eslint/lib/rules/key-spacing' { | 'value' | 'colon' | { - on: 'value' | 'colon'; + on?: 'value' | 'colon'; beforeColon?: boolean; afterColon?: boolean; mode?: 'strict' | 'minimum'; @@ -172,7 +172,7 @@ declare module 'eslint/lib/rules/key-spacing' { | 'value' | 'colon' | { - on: 'value' | 'colon'; + on?: 'value' | 'colon'; beforeColon?: boolean; afterColon?: boolean; mode?: 'strict' | 'minimum'; From 2092487bb9d6856bbd73611bd7a15099b5c5775a Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 24 Jan 2023 17:01:52 -0500 Subject: [PATCH 39/40] KeyTypeNodeWithTypeAnnotation --- .../eslint-plugin/src/rules/key-spacing.ts | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index a67dfd224825..dfd726708c30 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -68,18 +68,18 @@ export default util.createRule({ return sourceCode.getTokenBefore(colonToken)!; } - /** - * Relevant nodes to our rule - * - * node.typeAnnotation will always be defined, but no way to enforce that and keep - * the type compatible with TSEstree.Node (except maybe TS 4.9' "satisfies" keyword) - */ type KeyTypeNode = | TSESTree.TSIndexSignature | TSESTree.TSPropertySignature | TSESTree.PropertyDefinition; - function isKeyTypeNode(node: TSESTree.Node): node is KeyTypeNode { + type KeyTypeNodeWithTypeAnnotation = KeyTypeNode & { + typeAnnotation: TSESTree.TSTypeAnnotation; + }; + + function isKeyTypeNode( + node: TSESTree.Node, + ): node is KeyTypeNodeWithTypeAnnotation { return ( (node.type === AST_NODE_TYPES.TSPropertySignature || node.type === AST_NODE_TYPES.TSIndexSignature || @@ -91,7 +91,7 @@ export default util.createRule({ /** * To handle index signatures, to get the whole text for the parameters */ - function getKeyText(node: KeyTypeNode): string { + function getKeyText(node: KeyTypeNodeWithTypeAnnotation): string { if (node.type !== AST_NODE_TYPES.TSIndexSignature) { return sourceCode.getText(node.key); } @@ -109,7 +109,9 @@ export default util.createRule({ /** * To handle index signatures, be able to get the end position of the parameters */ - function getKeyLocEnd(node: KeyTypeNode): TSESTree.Position { + function getKeyLocEnd( + node: KeyTypeNodeWithTypeAnnotation, + ): TSESTree.Position { return getLastTokenBeforeColon( node.type !== AST_NODE_TYPES.TSIndexSignature ? node.key @@ -118,12 +120,11 @@ export default util.createRule({ } function checkBeforeColon( - node: KeyTypeNode, + node: KeyTypeNodeWithTypeAnnotation, expectedWhitespaceBeforeColon: number, mode: 'strict' | 'minimum', ): void { - // KeyTypeNode always has type annotation - const typeAnnotation = node.typeAnnotation!; + const { typeAnnotation } = node; const colon = typeAnnotation.loc.start.column; const keyEnd = getKeyLocEnd(node); const difference = colon - keyEnd.column - expectedWhitespaceBeforeColon; @@ -153,12 +154,11 @@ export default util.createRule({ } function checkAfterColon( - node: KeyTypeNode, + node: KeyTypeNodeWithTypeAnnotation, expectedWhitespaceAfterColon: number, mode: 'strict' | 'minimum', ): void { - // KeyTypeNode always has type annotation - const typeAnnotation = node.typeAnnotation!; + const { typeAnnotation } = node; const colon = typeAnnotation.loc.start.column; const typeStart = typeAnnotation.typeAnnotation.loc.start.column; const difference = typeStart - colon - 1 - expectedWhitespaceAfterColon; @@ -194,7 +194,7 @@ export default util.createRule({ ): boolean { const groupEndLine = lastMember.loc.start.line; const candidateValueStartLine = ( - isKeyTypeNode(candidate) ? candidate.typeAnnotation! : candidate + isKeyTypeNode(candidate) ? candidate.typeAnnotation : candidate ).loc.start.line; if (candidateValueStartLine === groupEndLine) { @@ -287,8 +287,7 @@ export default util.createRule({ if (!isKeyTypeNode(node)) { continue; } - // KeyTypeNode always has type annotation - const typeAnnotation = node.typeAnnotation!; + const { typeAnnotation } = node; const toCheck = align === 'colon' ? typeAnnotation : typeAnnotation.typeAnnotation; const difference = adjustedColumn(toCheck.loc.start) - alignColumn; From 0184de977a8b59d794dcdc89796cdbb784a4ab4a Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 24 Jan 2023 18:05:20 -0500 Subject: [PATCH 40/40] Extract to shared helper --- .../eslint-plugin/src/rules/ban-ts-comment.ts | 18 ++---------------- .../eslint-plugin/src/rules/key-spacing.ts | 19 +++---------------- .../eslint-plugin/src/util/getStringLength.ts | 17 +++++++++++++++++ packages/eslint-plugin/src/util/index.ts | 1 + 4 files changed, 23 insertions(+), 32 deletions(-) create mode 100644 packages/eslint-plugin/src/util/getStringLength.ts diff --git a/packages/eslint-plugin/src/rules/ban-ts-comment.ts b/packages/eslint-plugin/src/rules/ban-ts-comment.ts index f0bfce93ff6e..511a951280e7 100644 --- a/packages/eslint-plugin/src/rules/ban-ts-comment.ts +++ b/packages/eslint-plugin/src/rules/ban-ts-comment.ts @@ -1,22 +1,7 @@ import { AST_TOKEN_TYPES } from '@typescript-eslint/utils'; -import GraphemeSplitter from 'grapheme-splitter'; import * as util from '../util'; -let splitter: GraphemeSplitter; -function isASCII(value: string): boolean { - return /^[\u0020-\u007f]*$/u.test(value); -} -function getStringLength(value: string): number { - if (isASCII(value)) { - return value.length; - } - - splitter ??= new GraphemeSplitter(); - - return splitter.countGraphemes(value); -} - type DirectiveConfig = | boolean | 'allow-with-description' @@ -163,7 +148,8 @@ export default util.createRule<[Options], MessageIds>({ } = options; const format = descriptionFormats.get(fullDirective); if ( - getStringLength(description.trim()) < minimumDescriptionLength + util.getStringLength(description.trim()) < + minimumDescriptionLength ) { context.report({ data: { directive, minimumDescriptionLength }, diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index dfd726708c30..587d2674f4f3 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -1,24 +1,9 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import GraphemeSplitter from 'grapheme-splitter'; import * as util from '../util'; import { getESLintCoreRule } from '../util/getESLintCoreRule'; -let splitter: GraphemeSplitter; -function isASCII(value: string): boolean { - return /^[\u0020-\u007f]*$/u.test(value); -} -function getStringLength(value: string): number { - if (isASCII(value)) { - return value.length; - } - - splitter ??= new GraphemeSplitter(); - - return splitter.countGraphemes(value); -} - const baseRule = getESLintCoreRule('key-spacing'); export type Options = util.InferOptionsTypeFromRule; @@ -55,7 +40,9 @@ export default util.createRule({ */ function adjustedColumn(position: TSESTree.Position): number { const line = position.line - 1; // position.line is 1-indexed - return getStringLength(sourceCode.lines[line].slice(0, position.column)); + return util.getStringLength( + sourceCode.lines[line].slice(0, position.column), + ); } /** diff --git a/packages/eslint-plugin/src/util/getStringLength.ts b/packages/eslint-plugin/src/util/getStringLength.ts new file mode 100644 index 000000000000..65a22551949a --- /dev/null +++ b/packages/eslint-plugin/src/util/getStringLength.ts @@ -0,0 +1,17 @@ +import GraphemeSplitter from 'grapheme-splitter'; + +let splitter: GraphemeSplitter; + +function isASCII(value: string): boolean { + return /^[\u0020-\u007f]*$/u.test(value); +} + +export function getStringLength(value: string): number { + if (isASCII(value)) { + return value.length; + } + + splitter ??= new GraphemeSplitter(); + + return splitter.countGraphemes(value); +} diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index b2ad2927773a..53a19a96d368 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -5,6 +5,7 @@ export * from './collectUnusedVariables'; export * from './createRule'; export * from './getFunctionHeadLoc'; export * from './getOperatorPrecedence'; +export * from './getStringLength'; export * from './getThisExpression'; export * from './getWrappingFixer'; export * from './isNodeEqual'; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy