From 0ff5e45fdd07bc652cef9d1e522f406f6a4de282 Mon Sep 17 00:00:00 2001 From: Ted Xu Date: Tue, 1 Aug 2023 22:58:56 +0800 Subject: [PATCH] feat: format for named tuple (#229) --- packages/eslint-config-ts/index.js | 1 + packages/eslint-plugin-antfu/src/index.ts | 2 + .../src/rules/named-tuple-spacing.test.ts | 95 +++++++++++++++++++ .../src/rules/named-tuple-spacing.ts | 79 +++++++++++++++ 4 files changed, 177 insertions(+) create mode 100644 packages/eslint-plugin-antfu/src/rules/named-tuple-spacing.test.ts create mode 100644 packages/eslint-plugin-antfu/src/rules/named-tuple-spacing.ts diff --git a/packages/eslint-config-ts/index.js b/packages/eslint-config-ts/index.js index eb29faf443..ae4112f22b 100644 --- a/packages/eslint-config-ts/index.js +++ b/packages/eslint-config-ts/index.js @@ -161,6 +161,7 @@ module.exports = { 'antfu/no-cjs-exports': 'error', 'antfu/no-ts-export-equal': 'error', 'antfu/no-const-enum': 'error', + 'antfu/named-tuple-spacing': 'error', // off '@typescript-eslint/consistent-indexed-object-style': 'off', diff --git a/packages/eslint-plugin-antfu/src/index.ts b/packages/eslint-plugin-antfu/src/index.ts index 98e1ba9908..8522a6fcbd 100644 --- a/packages/eslint-plugin-antfu/src/index.ts +++ b/packages/eslint-plugin-antfu/src/index.ts @@ -6,6 +6,7 @@ import topLevelFunction from './rules/top-level-function' import noTsExportEqual from './rules/no-ts-export-equal' import noCjsExports from './rules/no-cjs-exports' import noConstEnum from './rules/no-const-enum' +import namedTupleSpacing from './rules/named-tuple-spacing' export default { rules: { @@ -17,5 +18,6 @@ export default { 'no-cjs-exports': noCjsExports, 'no-ts-export-equal': noTsExportEqual, 'no-const-enum': noConstEnum, + 'named-tuple-spacing': namedTupleSpacing, }, } diff --git a/packages/eslint-plugin-antfu/src/rules/named-tuple-spacing.test.ts b/packages/eslint-plugin-antfu/src/rules/named-tuple-spacing.test.ts new file mode 100644 index 0000000000..073cf79222 --- /dev/null +++ b/packages/eslint-plugin-antfu/src/rules/named-tuple-spacing.test.ts @@ -0,0 +1,95 @@ +import { RuleTester } from '@typescript-eslint/utils/dist/ts-eslint' +import { it } from 'vitest' +import rule, { RULE_NAME } from './named-tuple-spacing' + +const valids = [ + 'type T = [i: number]', + 'type T = [i?: number]', + 'type T = [i: number, j: number]', + `const emit = defineEmits<{ + change: [id: number] + update: [value: string] + }>()`, +] + +it('runs', () => { + const ruleTester: RuleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + }) + + ruleTester.run(RULE_NAME, rule, { + valid: valids, + invalid: [ + { + code: 'type T = [i:number]', + output: 'type T = [i: number]', + errors: [{ messageId: 'expectedSpaceAfter' }], + }, + { + code: 'type T = [i: number]', + output: 'type T = [i: number]', + errors: [{ messageId: 'expectedSpaceAfter' }], + }, + { + code: 'type T = [i?:number]', + output: 'type T = [i?: number]', + errors: [{ messageId: 'expectedSpaceAfter' }], + }, + { + code: 'type T = [i? :number]', + output: 'type T = [i?: number]', + errors: [{ messageId: 'unexpectedSpaceBetween' }, { messageId: 'expectedSpaceAfter' }], + }, + { + code: 'type T = [i : number]', + output: 'type T = [i: number]', + errors: [{ messageId: 'unexpectedSpaceBefore' }], + }, + { + code: 'type T = [i : number]', + output: 'type T = [i: number]', + errors: [{ messageId: 'unexpectedSpaceBefore' }], + }, + { + code: 'type T = [i ? : number]', + output: 'type T = [i?: number]', + errors: [{ messageId: 'unexpectedSpaceBetween' }, { messageId: 'unexpectedSpaceBefore' }], + }, + { + code: 'type T = [i:number, j:number]', + output: 'type T = [i: number, j: number]', + errors: [{ messageId: 'expectedSpaceAfter' }, { messageId: 'expectedSpaceAfter' }], + }, + { + code: ` + const emit = defineEmits<{ + change: [id:number] + update: [value:string] + }>() + `, + output: ` + const emit = defineEmits<{ + change: [id: number] + update: [value: string] + }>() + `, + errors: [{ messageId: 'expectedSpaceAfter' }, { messageId: 'expectedSpaceAfter' }], + }, + { + code: ` + const emit = defineEmits<{ + change: [id? :number] + update: [value:string] + }>() + `, + output: ` + const emit = defineEmits<{ + change: [id?: number] + update: [value: string] + }>() + `, + errors: [{ messageId: 'unexpectedSpaceBetween' }, { messageId: 'expectedSpaceAfter' }, { messageId: 'expectedSpaceAfter' }], + }, + ], + }) +}) diff --git a/packages/eslint-plugin-antfu/src/rules/named-tuple-spacing.ts b/packages/eslint-plugin-antfu/src/rules/named-tuple-spacing.ts new file mode 100644 index 0000000000..9aadb9e62f --- /dev/null +++ b/packages/eslint-plugin-antfu/src/rules/named-tuple-spacing.ts @@ -0,0 +1,79 @@ +import { createEslintRule } from '../utils' + +export const RULE_NAME = 'named-tuple-spacing' +export type MessageIds = 'expectedSpaceAfter' | 'unexpectedSpaceBetween' | 'unexpectedSpaceBefore' +export type Options = [] + +export default createEslintRule({ + name: RULE_NAME, + meta: { + type: 'suggestion', + docs: { + description: 'Expect space before type declaration in named tuple', + recommended: 'error', + }, + fixable: 'code', + schema: [], + messages: { + expectedSpaceAfter: 'Expected a space after the \':\'.', + unexpectedSpaceBetween: 'Unexpected space between \'?\' and the \':\'.', + unexpectedSpaceBefore: 'Unexpected space before the \':\'.', + }, + }, + defaultOptions: [], + create: (context) => { + const sourceCode = context.getSourceCode() + return { + TSNamedTupleMember: (node) => { + const code = sourceCode.text.slice(node.range[0], node.range[1]) + + const reg = /(\w+)(\s*)(\?\s*)?:(\s*)(\w+)/ + + const labelName = node.label.name + const spaceBeforeColon = code.match(reg)?.[2] + const optionalMark = code.match(reg)?.[3] + const spacesAfterColon = code.match(reg)?.[4] + const elementType = code.match(reg)?.[5] + + function getReplaceValue() { + let ret = labelName + if (node.optional) + ret += '?' + ret += ': ' + ret += elementType + return ret + } + + if (optionalMark?.length > 1) { + context.report({ + node, + messageId: 'unexpectedSpaceBetween', + *fix(fixer) { + yield fixer.replaceTextRange(node.range, code.replace(reg, getReplaceValue())) + }, + }) + } + + if (spaceBeforeColon?.length) { + context.report({ + node, + messageId: 'unexpectedSpaceBefore', + *fix(fixer) { + yield fixer.replaceTextRange(node.range, code.replace(reg, getReplaceValue())) + }, + }) + } + + if (spacesAfterColon.length !== 1) { + context.report({ + node, + messageId: 'expectedSpaceAfter', + *fix(fixer) { + yield fixer.replaceTextRange(node.range, code.replace(reg, getReplaceValue())) + }, + }) + } + }, + } + }, +})