From 53fc110a8a4aafd24683f1c3fa4e36af8d329adb Mon Sep 17 00:00:00 2001 From: ddosakura Date: Mon, 28 Oct 2024 13:39:41 +0000 Subject: [PATCH] fix(eslint-config): @keep-dto support handle Utility Types & Custom Utility Types --- .changeset/tame-dolphins-float.md | 5 ++ src/commands/keep-dto.md | 44 ++++++++++++ src/commands/keep-dto.test.ts | 114 ++++++++++++++++-------------- src/commands/keep-dto.ts | 25 ++++++- 4 files changed, 132 insertions(+), 56 deletions(-) create mode 100644 .changeset/tame-dolphins-float.md diff --git a/.changeset/tame-dolphins-float.md b/.changeset/tame-dolphins-float.md new file mode 100644 index 0000000..651b3da --- /dev/null +++ b/.changeset/tame-dolphins-float.md @@ -0,0 +1,5 @@ +--- +"@ddosakura/eslint-config": patch +--- + +@keep-dto support handle Utility Types & Custom Utility Types diff --git a/src/commands/keep-dto.md b/src/commands/keep-dto.md index df459b5..20b5e28 100644 --- a/src/commands/keep-dto.md +++ b/src/commands/keep-dto.md @@ -12,6 +12,7 @@ Keep the DTO(data transfer object) interface normalized. ```ts interface A { a: 1 + b: 1 | 2 } ``` @@ -21,6 +22,7 @@ Will be converted to: /// keep-dto interface A { a: number + b: 1 | 2 } ``` @@ -107,3 +109,45 @@ interface A { b: number } ``` + +#### TSUnionType & TSIntersectionType + +```ts +export type A = 1 | { a_b: 2 } & 3 +``` + +Will be converted to: + +```ts +// @keep-dto +export type A = 1 | { aB: number } & 3 +``` + +```ts +// @keep-dto { "literal": "all", "ignores": ["0", "1.0.a_b"] } +export type A = 1 | { a_b: 2 } & number +``` + +#### Utility Types & Custom Utility Types + +buildin: + +- Array +- Partial +- Required +- Readonly +- NonNullable +- Pick +- Omit +- Record + +custom: + +```ts +type X = A | B | C +// @keep-dto { "utilities": { "Awaited": 0, "Promise": 0, "X": [0, 2] } } +export interface A { + a: Awaited> + b: X<{ a: number }, { b: 2 }, { c: number }> +} +``` diff --git a/src/commands/keep-dto.test.ts b/src/commands/keep-dto.test.ts index ceddde6..3f827c0 100644 --- a/src/commands/keep-dto.test.ts +++ b/src/commands/keep-dto.test.ts @@ -466,58 +466,64 @@ run( `, errors: ['command-fix'], }, - // { - // description: 'Utility Types #1: Partial&Required&Readonly&NonNullable', - // code: $` - // // @keep-dto - // export interface A { - // a: Partial<{ a: 1 }> - // b: Required<{ a?: 1 }> - // c: Readonly<{ a: 1 }> - // d: NonNullable<{ a: 1 } | null> - // } - // `, - // output: $` - // // @keep-dto { "ignores": ["a_b"] } - // a: Partial<{ a: number }> - // b: Required<{ a?: number }> - // c: Readonly<{ a: number }> - // d: NonNullable<{ a: number } | null> - // `, - // errors: ['command-fix'], - // }, - // { - // description: 'Utility Types #2: Pick&Omit', - // code: $` - // // @keep-dto - // export interface A { - // a: Pick<{ a: 1 }, 'a'> - // b: Omit<{ a: 1 }, 'a'> - // } - // `, - // output: $` - // // @keep-dto - // export interface A { - // a: Pick<{ a: number }, 'a'> - // b: Omit<{ a: number }, 'a'> - // } - // `, - // errors: ['command-fix'], - // }, - // { - // description: 'Utility Types #3: Custom', - // code: $` - // // @keep-dto { "utilities": { Awaited: 0, Promise: 0 } } - // export interface A { - // a: Awaited> - // } - // `, - // output: $` - // // @keep-dto { "ignores": ["a_b"] } - // export interface A { - // a: Awaited> - // } - // `, - // errors: ['command-fix'], - // }, + { + description: 'Utility Types #1: Partial&Required&Readonly&NonNullable', + code: $` + // @keep-dto + export interface A { + a: Partial<{ a: 1 }> + b: Required<{ a?: 1 }> + c: Readonly<{ a: 1 }> + d: NonNullable<{ a: 1 } | null> + } + `, + output: $` + // @keep-dto + export interface A { + a: Partial<{ a: number }> + b: Required<{ a?: number }> + c: Readonly<{ a: number }> + d: NonNullable<{ a: number } | null> + } + `, + errors: ['command-fix'], + }, + { + description: 'Utility Types #2: Pick&Omit', + code: $` + // @keep-dto + export interface A { + a: Pick<{ a: 1 }, 'a'> + b: Omit<{ a: 1 }, 'a'> + } + `, + output: $` + // @keep-dto + export interface A { + a: Pick<{ a: number }, 'a'> + b: Omit<{ a: number }, 'a'> + } + `, + errors: ['command-fix'], + }, + { + description: 'Utility Types #3: Custom', + code: $` + type X = A | B | C + // @keep-dto { "utilities": { "Awaited": 0, "Promise": 0, "X": [0, 2] } } + export interface A { + a: Awaited> + b: X<{ a: 1 }, { b: 2 }, { c: 3 }> + } + `, + output: $` + type X = A | B | C + // @keep-dto { "utilities": { "Awaited": 0, "Promise": 0, "X": [0, 2] } } + export interface A { + a: Awaited> + b: X<{ a: number }, { b: 2 }, { c: number }> + } + `, + errors: ['command-fix'], + }, ) diff --git a/src/commands/keep-dto.ts b/src/commands/keep-dto.ts index efb7592..86f95ff 100644 --- a/src/commands/keep-dto.ts +++ b/src/commands/keep-dto.ts @@ -25,6 +25,10 @@ export interface KeepDtoInlineOptions { * @defalut false */ literal: false | 'all' | 'union' | 'intersection' + /** + * Custom Utility Types + */ + utilities: Record } function normalizeKey(key: string, mode: KeepDtoInlineOptions['key'] = 'camelize') { @@ -48,6 +52,12 @@ const reLine = /^[/@:]\s*(?:keep-dto|dto)\s*(\{.*\})?$/ // @regex101 https://regex101.com/?regex=%28%3F%3A%5Cb%7C%5Cs%29%40keep-dto%5Cs*%28%5C%7B.*%5C%7D%29%3F%28%3F%3A%5Cb%7C%5Cs%7C%24%29&flavor=javascript const reBlock = /(?:\b|\s)@keep-dto\s*(\{.*\})?(?:\b|\s|$)/ +const typeReferences = { + $1_0: ['Array', 'Partial', 'Required', 'Readonly', 'NonNullable'], + $2_0: ['Pick', 'Omit'], + $2_1: ['Record'], +} + export const keepDto = defineCommand({ name: 'keep-dto', commentType: 'both', @@ -149,12 +159,23 @@ export const keepDto = defineCommand({ else if (typeNode.type === AST_NODE_TYPES.TSTypeReference) { const params = typeNode.typeArguments?.params if (typeNode.typeName.type === AST_NODE_TYPES.Identifier) { - if (typeNode.typeName.name === 'Array' && params?.length === 1) { + if (typeReferences.$1_0.includes(typeNode.typeName.name) && params?.length === 1) { formatTypeNode(params[0], prefix) } - else if (typeNode.typeName.name === 'Record' && params?.length === 2) { + else if (typeReferences.$2_0.includes(typeNode.typeName.name) && params?.length === 2) { + formatTypeNode(params[0], prefix) + } + else if (typeReferences.$2_1.includes(typeNode.typeName.name) && params?.length === 2) { formatTypeNode(params[1], prefix) } + else { + const opt = options?.utilities?.[typeNode.typeName.name] ?? [] + const indexes = Array.isArray(opt) ? opt : [opt] + const max = Math.max(...indexes) + if (params && params.length > max) { + indexes.forEach(index => formatTypeNode(params[index], prefix)) + } + } } } }