diff --git a/index.d.ts b/index.d.ts index b26c88c..91e3407 100644 --- a/index.d.ts +++ b/index.d.ts @@ -36,12 +36,12 @@ type AppendPath = S extends '' /** Convert keys of an object to camelcase strings. */ -type CamelCaseKeys< +export type CamelCaseKeys< T extends Record | readonly any[], - Deep extends boolean, - IsPascalCase extends boolean, - Exclude extends readonly unknown[], - StopPaths extends readonly string[], + Deep extends boolean = false, + IsPascalCase extends boolean = false, + Exclude extends readonly unknown[] = EmptyTuple, + StopPaths extends readonly string[] = EmptyTuple, Path extends string = '' > = T extends readonly any[] // Handle arrays or tuples. @@ -57,11 +57,11 @@ type CamelCaseKeys< : T extends Record // Handle objects. ? { - [P in keyof T & string as [IsInclude] extends [true] + [P in keyof T as [IsInclude] extends [true] ? P : [IsPascalCase] extends [true] ? PascalCase

- : CamelCase

]: [IsInclude>] extends [ + : CamelCase

]: [IsInclude>] extends [ true ] ? T[P] @@ -72,7 +72,7 @@ type CamelCaseKeys< IsPascalCase, Exclude, StopPaths, - AppendPath + AppendPath > : T[P]; } @@ -189,4 +189,4 @@ WithDefault, WithDefault >; -export = camelcaseKeys; +export default camelcaseKeys; diff --git a/index.test-d.ts b/index.test-d.ts index bf761e8..054eb27 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,46 +1,46 @@ -import {expectType, expectAssignable} from 'tsd'; -import camelcaseKeys = require('.'); +import {expectType, expectAssignable, expectNotType} from 'tsd'; +import type {CamelCaseKeys} from '.'; +import camelcaseKeys from '.'; const fooBarObject = {'foo-bar': true}; const camelFooBarObject = camelcaseKeys(fooBarObject); -expectType<{fooBar: boolean}>(camelFooBarObject); +expectType<{ fooBar: boolean }>(camelFooBarObject); const fooBarArray = [{'foo-bar': true}]; const camelFooBarArray = camelcaseKeys(fooBarArray); -expectType>(camelFooBarArray); +expectType>(camelFooBarArray); -expectType>(camelcaseKeys([{'foo-bar': true}])); +expectType>(camelcaseKeys([{'foo-bar': true}])); expectType(camelcaseKeys(['name 1', 'name 2'])); expectType(camelcaseKeys(['name 1', 'name 2'], {deep: true})); -expectType( +expectType( camelcaseKeys([{'foo-bar': true}, {'foo-baz': true}] as const) ); -expectType<{fooBar: boolean}>(camelcaseKeys({'foo-bar': true})); -expectType<{fooBar: boolean}>(camelcaseKeys({'--foo-bar': true})); -expectType<{fooBar: boolean}>(camelcaseKeys({foo_bar: true})); -expectType<{fooBar: boolean}>(camelcaseKeys({'foo bar': true})); +expectType<{ fooBar: boolean }>(camelcaseKeys({'foo-bar': true})); +expectType<{ fooBar: boolean }>(camelcaseKeys({'--foo-bar': true})); +expectType<{ fooBar: boolean }>(camelcaseKeys({foo_bar: true})); +expectType<{ fooBar: boolean }>(camelcaseKeys({'foo bar': true})); -expectType<{fooBar: true}>(camelcaseKeys({'foo-bar': true} as const)); -expectType<{fooBar: true}>(camelcaseKeys({'--foo-bar': true} as const)); -expectType<{fooBar: true}>(camelcaseKeys({foo_bar: true} as const)); -expectType<{fooBar: true}>(camelcaseKeys({'foo bar': true} as const)); +expectType<{ readonly fooBar: true }>(camelcaseKeys({'foo-bar': true} as const)); +expectType<{ readonly fooBar: true }>(camelcaseKeys({'--foo-bar': true} as const)); +expectType<{ readonly fooBar: true }>(camelcaseKeys({foo_bar: true} as const)); +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: { fooBar: { fooBar: boolean } } }>( + camelcaseKeys({'foo-bar': {foo_bar: {'foo bar': true}}}, {deep: true}) ); interface ObjectOrUndefined { foo_bar: { - foo_bar: { + foo_bar: + | { foo_bar: boolean; - } | undefined; + } + | undefined; }; } @@ -52,43 +52,43 @@ const objectOrUndefined: ObjectOrUndefined = { } }; -expectType<{fooBar: {fooBar: {fooBar: boolean} | undefined}}>( +expectType<{ fooBar: { fooBar: { fooBar: boolean } | undefined } }>( camelcaseKeys(objectOrUndefined, {deep: true}) ); -expectType<{FooBar: boolean}>( +expectType<{ FooBar: boolean }>( camelcaseKeys({'foo-bar': true}, {pascalCase: true}) ); -expectType<{FooBar: true}>( +expectType<{ readonly FooBar: true }>( camelcaseKeys({'foo-bar': true} as const, {pascalCase: true}) ); -expectType<{FooBar: boolean}>( +expectType<{ FooBar: boolean }>( camelcaseKeys({'--foo-bar': true}, {pascalCase: true}) ); -expectType<{FooBar: boolean}>( +expectType<{ FooBar: boolean }>( camelcaseKeys({foo_bar: true}, {pascalCase: true}) ); -expectType<{FooBar: boolean}>( +expectType<{ FooBar: boolean }>( camelcaseKeys({'foo bar': true}, {pascalCase: true}) ); -expectType<{FooBar: {FooBar: {FooBar: boolean}}}>( +expectType<{ FooBar: { FooBar: { FooBar: boolean } } }>( camelcaseKeys( {'foo-bar': {foo_bar: {'foo bar': true}}}, {deep: true, pascalCase: true} ) ); -expectType<{fooBar: boolean; foo_bar: true}>( +expectType<{ fooBar: boolean; foo_bar: true }>( camelcaseKeys( {'foo-bar': true, foo_bar: true}, {exclude: ['foo', 'foo_bar', /bar/] as const} ) ); -expectType<{fooBar: boolean}>( +expectType<{ fooBar: boolean }>( camelcaseKeys({'foo-bar': true}, {stopPaths: ['foo']}) ); -expectType<{topLevel: {fooBar: {'bar-baz': boolean}}; fooFoo: boolean}>( +expectType<{ topLevel: { fooBar: { 'bar-baz': boolean } }; fooFoo: boolean }>( camelcaseKeys( {'top-level': {'foo-bar': {'bar-baz': true}}, 'foo-foo': true}, {deep: true, stopPaths: ['top-level.foo-bar'] as const} @@ -124,3 +124,229 @@ const objectWithTypeAlias = { expectType(camelcaseKeys(objectWithTypeAlias)); expectType(camelcaseKeys([objectWithTypeAlias])); + +// Using exported type +expectType>(camelFooBarArray); + +const arrayItems = [{fooBar: true}, {fooBaz: true}] as const; +expectType>(camelcaseKeys(arrayItems)); + +expectType>( + camelcaseKeys({'foo-bar': true}) +); +expectType>( + camelcaseKeys({'--foo-bar': true}) +); +expectType>( + camelcaseKeys({foo_bar: true}) +); +expectType>( + camelcaseKeys({'foo bar': true}) +); + +expectType>( + camelcaseKeys({'foo-bar': true} as const) +); +expectType>( + camelcaseKeys({'--foo-bar': true} as const) +); +expectType>( + camelcaseKeys({foo_bar: true} as const) +); +expectType>( + camelcaseKeys({'foo bar': true} as const) +); + +const nestedItem = {'foo-bar': {foo_bar: {'foo bar': true}}}; +expectType>( + camelcaseKeys(nestedItem, {deep: true}) +); + +expectType>( + camelcaseKeys(objectOrUndefined, {deep: true}) +); + +expectType>( + camelcaseKeys({'foo-bar': true}, {pascalCase: true}) +); +expectType>( + camelcaseKeys({'foo-bar': true} as const, {pascalCase: true}) +); +expectType>( + camelcaseKeys({'foo-bar': true}, {pascalCase: true}) +); +expectType>( + camelcaseKeys({'foo-bar': true}, {pascalCase: true}) +); +expectType>( + camelcaseKeys({'foo-bar': true}, {pascalCase: true}) +); +expectType>( + camelcaseKeys(nestedItem, {deep: true, pascalCase: true}) +); + +const data = {'foo-bar': true, foo_bar: true}; +const exclude = ['foo', 'foo_bar', /bar/] as const; + +expectType>( + camelcaseKeys(data, {exclude}) +); + +const nonNestedWithStopPathData = {'foo-bar': true, foo_bar: true}; +expectType< +CamelCaseKeys +>(camelcaseKeys({'foo-bar': true}, {stopPaths: ['foo']})); +const nestedWithStopPathData = { + 'top-level': {'foo-bar': {'bar-baz': true}}, + 'foo-foo': true +}; +const stopPaths = ['top-level.foo-bar'] as const; +expectType< +CamelCaseKeys< + typeof nestedWithStopPathData, +true, +false, +// eslint-disable-next-line @typescript-eslint/ban-types +[], + typeof stopPaths +> +>(camelcaseKeys(nestedWithStopPathData, {deep: true, stopPaths})); + +expectAssignable>>( + camelcaseKeys({} as Record) +); + +expectAssignable, true>>( + camelcaseKeys({} as Record, {deep: true}) +); + +expectType>(camelcaseKeys(someObject)); +expectType>(camelcaseKeys([someObject])); + +expectType>(camelcaseKeys(objectWithTypeAlias)); +expectType>( + camelcaseKeys([objectWithTypeAlias]) +); + +// Verify exported type `CamelcaseKeys` +// Mapping types and retaining properties of keys +// https://github.com/microsoft/TypeScript/issues/13224 + +type ObjectDataType = { + foo_bar?: string; + bar_baz?: string; + baz: string; +}; +type InvalidConvertedObjectDataType = { + fooBar: string; + barBaz: string; + baz: string; +}; +type ConvertedObjectDataType = { + fooBar?: string; + barBaz?: string; + baz: string; +}; + +const objectInputData: ObjectDataType = { + foo_bar: 'foo_bar', + baz: 'baz' +}; +expectType(camelcaseKeys(objectInputData)); +expectNotType(camelcaseKeys(objectInputData)); + +// Array +type ArrayDataType = ObjectDataType[]; + +const arrayInputData: ArrayDataType = [ + { + foo_bar: 'foo_bar', + baz: 'baz' + } +]; +expectType(camelcaseKeys(arrayInputData)); +expectNotType(camelcaseKeys(arrayInputData)); + +// Deep +type DeepObjectType = { + foo_bar?: string; + bar_baz?: string; + baz: string; + first_level: { + foo_bar?: string; + bar_baz?: string; + second_level: { + foo_bar: string; + bar_baz?: string; + }; + }; +}; +type InvalidConvertedDeepObjectDataType = { + fooBar?: string; + barBaz?: string; + baz: string; + first_level?: { + fooBar?: string; + barBaz?: string; + second_level?: { + fooBar: string; + barBaz?: string; + }; + }; +}; +type ConvertedDeepObjectDataType = { + fooBar?: string; + barBaz?: string; + baz: string; + firstLevel: { + foo_bar?: string; + bar_baz?: string; + second_level: { + foo_bar: string; + bar_baz?: string; + }; + }; +}; +const deepInputData: DeepObjectType = { + foo_bar: 'foo_bar', + baz: 'baz', + first_level: { + bar_baz: 'bar_baz', + second_level: { + foo_bar: 'foo_bar' + } + } +}; +expectType( + camelcaseKeys(deepInputData, {deep: false}) +); +expectNotType( + camelcaseKeys(deepInputData, {deep: false}) +); + +// Exclude +type InvalidConvertedExcludeObjectDataType = { + foo_bar?: string; + bar_baz?: string; + baz: string; +}; +type ConvertedExcludeObjectDataType = { + foo_bar?: string; + barBaz?: string; + baz: string; +}; +const excludeInputData: ObjectDataType = { + foo_bar: 'foo_bar', + bar_baz: 'bar_baz', + baz: 'baz' +}; +expectType( + camelcaseKeys(excludeInputData, { + exclude + }) +); +expectNotType( + camelcaseKeys(excludeInputData, { + exclude + }) +);