From 3ca34cfae2b94a7ab12cb45017cd5d7946f1caaa Mon Sep 17 00:00:00 2001 From: Yuta Katayama Date: Mon, 26 Jun 2023 21:15:27 +0900 Subject: [PATCH] fix: object properties, func, and so on must not convert to camelCase --- index.d.ts | 38 ++++++++++++++++----------------- index.js | 19 ++++++++--------- index.test-d.ts | 57 +++++++++++++++++++++++++++++++++++++++++++++---- package.json | 14 ++++++------ 4 files changed, 88 insertions(+), 40 deletions(-) diff --git a/index.d.ts b/index.d.ts index b7bcd1d..6c8e895 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,8 +1,10 @@ -import {CamelCase, PascalCase} from 'type-fest'; +import type {CamelCase, PascalCase} from 'type-fest'; // eslint-disable-next-line @typescript-eslint/ban-types type EmptyTuple = []; +type ObjectOptional = Record | undefined; + /** Return a default type if input type is nil. @@ -36,7 +38,7 @@ type AppendPath = S extends '' Convert keys of an object to camelcase strings. */ export type CamelCaseKeys< - T extends Record | readonly any[], + T extends ObjectOptional | readonly any[], Deep extends boolean = false, IsPascalCase extends boolean = false, Exclude extends readonly unknown[] = EmptyTuple, @@ -45,20 +47,17 @@ export type CamelCaseKeys< > = T extends readonly any[] // Handle arrays or tuples. ? { - [P in keyof T]: T[P] extends Record | readonly any[] - // eslint-disable-next-line @typescript-eslint/ban-types - ? {} extends CamelCaseKeys - ? T[P] - : CamelCaseKeys< - T[P], - Deep, - IsPascalCase, - Exclude, - StopPaths - > + [P in keyof T]: T[P] extends Record | readonly any[] + ? CamelCaseKeys< + T[P], + Deep, + IsPascalCase, + Exclude, + StopPaths + > : T[P]; } - : T extends Record + : T extends Record // Handle objects. ? { [P in keyof T as [IsInclude] extends [true] @@ -69,10 +68,8 @@ export type CamelCaseKeys< true, ] ? T[P] - // eslint-disable-next-line @typescript-eslint/ban-types - : {} extends CamelCaseKeys - ? T[P] - : [Deep] extends [true] + : [Deep] extends [true] + ? T[P] extends ObjectOptional | readonly any[] ? CamelCaseKeys< T[P], Deep, @@ -81,7 +78,8 @@ export type CamelCaseKeys< StopPaths, AppendPath > - : T[P]; + : T[P] + : T[P]; } // Return anything else as-is. : T; @@ -190,7 +188,7 @@ camelcaseKeys(commandLineArguments); ``` */ export default function camelcaseKeys< - T extends Record | readonly any[], + T extends Record | readonly any[], OptionsType extends Options = Options, >( input: T, diff --git a/index.js b/index.js index ce85f1a..3b0de0b 100644 --- a/index.js +++ b/index.js @@ -22,18 +22,17 @@ const isObject = value => && !(value instanceof Error) && !(value instanceof Date); -const camelCaseConvert = (input, options) => { +const transform = (input, options = {}) => { if (!isObject(input)) { return input; } - options = { - deep: false, - pascalCase: false, - ...options, - }; - - const {exclude, pascalCase, stopPaths, deep} = options; + const { + exclude, + pascalCase = false, + stopPaths, + deep = false, + } = options; const stopPathsSet = new Set(stopPaths); @@ -70,8 +69,8 @@ const camelCaseConvert = (input, options) => { export default function camelcaseKeys(input, options) { if (Array.isArray(input)) { - return Object.keys(input).map(key => camelCaseConvert(input[key], options)); + return Object.keys(input).map(key => transform(input[key], options)); } - return camelCaseConvert(input, options); + return transform(input, options); } diff --git a/index.test-d.ts b/index.test-d.ts index 6017f4d..d7f9d6b 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -33,8 +33,11 @@ expectType<{readonly fooBar: true}>(camelcaseKeys({'foo bar': true} as const)); expectType<{fooBar: {fooBar: {fooBar: boolean}}}>( camelcaseKeys({'foo-bar': {foo_bar: {'foo bar': true}}}, {deep: true}), ); +expectType<{fooBar: Array<{fooBar: {fooBar: boolean}}>}>( + camelcaseKeys({'foo-bar': [{foo_bar: {'foo bar': true}}]}, {deep: true}), +); -interface ObjectOrUndefined { +type ObjectOrUndefined = { foo_bar: { foo_bar: | { @@ -42,7 +45,7 @@ interface ObjectOrUndefined { } | undefined; }; -} +}; const objectOrUndefined: ObjectOrUndefined = { foo_bar: { @@ -103,9 +106,9 @@ expectAssignable>( camelcaseKeys({} as Record, {deep: true}), ); -interface SomeObject { +type SomeObject = { someProperty: string; -} +}; const someObject: SomeObject = { someProperty: 'hello', @@ -113,6 +116,8 @@ const someObject: SomeObject = { expectType(camelcaseKeys(someObject)); expectType(camelcaseKeys([someObject])); +expectType<{someObject: SomeObject}>(camelcaseKeys({some_object: someObject})); +expectType>(camelcaseKeys([{some_object: someObject}])); type SomeTypeAlias = { someProperty: string; @@ -280,6 +285,13 @@ type DeepObjectType = { bar_baz?: string; }; }; + optional_first_level?: { + foo_bar?: string; + bar_baz?: true; + optional_second_level?: { + foo_bar: number; + }; + }; }; type InvalidConvertedDeepObjectDataType = { fooBar?: string; @@ -293,6 +305,13 @@ type InvalidConvertedDeepObjectDataType = { barBaz?: string; }; }; + optional_first_level?: { + fooBar?: string; + barBaz?: true; + optional_second_level?: { + fooBar: number; + }; + }; }; type ConvertedDeepObjectDataType = { fooBar?: string; @@ -306,6 +325,33 @@ type ConvertedDeepObjectDataType = { bar_baz?: string; }; }; + optionalFirstLevel?: { + foo_bar?: string; + bar_baz?: true; + optional_second_level?: { + foo_bar: number; + }; + }; +}; +type ConvertedDeepObjectDataTypeWithDeepTrue = { + fooBar?: string | undefined; + barBaz?: string | undefined; + baz: string; + firstLevel: { + fooBar?: string | undefined; + barBaz?: string | undefined; + secondLevel: { + fooBar: string; + barBaz?: string | undefined; + }; + }; + optionalFirstLevel?: { + fooBar?: string; + barBaz?: true; + optionalSecondLevel?: { + fooBar: number; + }; + }; }; const deepInputData: DeepObjectType = { foo_bar: 'foo_bar', @@ -320,6 +366,9 @@ const deepInputData: DeepObjectType = { expectType( camelcaseKeys(deepInputData, {deep: false}), ); +expectType( + camelcaseKeys(deepInputData, {deep: true}), +); expectNotType( camelcaseKeys(deepInputData, {deep: false}), ); diff --git a/package.json b/package.json index 439c843..648df6d 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,10 @@ "url": "https://sindresorhus.com" }, "type": "module", - "exports": "./index.js", - "types": "./index.d.ts", + "exports": { + "types": "./index.d.ts", + "default": "./index.js" + }, "engines": { "node": ">=14.16" }, @@ -56,13 +58,13 @@ "camelcase": "^7.0.0", "map-obj": "^4.3.0", "quick-lru": "^6.1.1", - "type-fest": "^2.13.0" + "type-fest": "^3.1.0" }, "devDependencies": { - "ava": "^4.3.0", + "ava": "^5.0.1", "matcha": "^0.7.0", - "tsd": "^0.23.0", - "xo": "^0.49.0" + "tsd": "^0.24.1", + "xo": "^0.54.2" }, "xo": { "overrides": [