Skip to content

Commit

Permalink
Add preserveConsecutiveUppercase option (#115)
Browse files Browse the repository at this point in the history
  • Loading branch information
simonradier committed Sep 29, 2023
1 parent 8f189be commit 507d5d0
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 4 deletions.
14 changes: 13 additions & 1 deletion index.d.ts
Expand Up @@ -41,6 +41,7 @@ export type CamelCaseKeys<
T extends ObjectOptional | readonly any[],
Deep extends boolean = false,
IsPascalCase extends boolean = false,
PreserveConsecutiveUppercase extends boolean = false,
Exclude extends readonly unknown[] = EmptyTuple,
StopPaths extends readonly string[] = EmptyTuple,
Path extends string = '',
Expand All @@ -52,6 +53,7 @@ export type CamelCaseKeys<
T[P],
Deep,
IsPascalCase,
PreserveConsecutiveUppercase,
Exclude,
StopPaths
>
Expand All @@ -64,7 +66,7 @@ export type CamelCaseKeys<
? P
: [IsPascalCase] extends [true]
? PascalCase<P>
: CamelCase<P>]: [IsInclude<StopPaths, AppendPath<Path, P & string>>] extends [
: CamelCase<P, {preserveConsecutiveUppercase: PreserveConsecutiveUppercase}>]: [IsInclude<StopPaths, AppendPath<Path, P & string>>] extends [
true,
]
? T[P]
Expand All @@ -74,6 +76,7 @@ export type CamelCaseKeys<
T[P],
Deep,
IsPascalCase,
PreserveConsecutiveUppercase,
Exclude,
StopPaths,
AppendPath<Path, P & string>
Expand Down Expand Up @@ -145,6 +148,14 @@ type Options = {
@default false
*/
readonly pascalCase?: boolean;

/**
Preserve consecutive uppercase characters: `foo_BAR` → `FooBAR`.
@default false
*/
readonly preserveConsecutiveUppercase?: boolean;

};

/**
Expand Down Expand Up @@ -197,6 +208,7 @@ export default function camelcaseKeys<
T,
WithDefault<OptionsType['deep'], false>,
WithDefault<OptionsType['pascalCase'], false>,
WithDefault<OptionsType['preserveConsecutiveUppercase'], false>,
WithDefault<OptionsType['exclude'], EmptyTuple>,
WithDefault<OptionsType['stopPaths'], EmptyTuple>
>;
3 changes: 2 additions & 1 deletion index.js
Expand Up @@ -32,6 +32,7 @@ const transform = (input, options = {}) => {
pascalCase = false,
stopPaths,
deep = false,
preserveConsecutiveUppercase = false,
} = options;

const stopPathsSet = new Set(stopPaths);
Expand All @@ -51,7 +52,7 @@ const transform = (input, options = {}) => {
if (cache.has(cacheKey)) {
key = cache.get(cacheKey);
} else {
const returnValue = camelCase(key, {pascalCase, locale: false});
const returnValue = camelCase(key, {pascalCase, locale: false, preserveConsecutiveUppercase});

if (key.length < 100) { // Prevent abuse
cache.set(cacheKey, returnValue);
Expand Down
30 changes: 28 additions & 2 deletions index.test-d.ts
Expand Up @@ -81,6 +81,31 @@ expectType<{FooBar: {FooBar: {FooBar: boolean}}}>(
),
);

expectType<{fooBAR: boolean}>(
camelcaseKeys({'foo-BAR': true}, {preserveConsecutiveUppercase: true}),
);
expectType<{readonly fooBAR: true}>(
camelcaseKeys({foo_BAR: true} as const, {preserveConsecutiveUppercase: true}),
);
expectType<{fooBAR: boolean}>(
camelcaseKeys({'--foo-BAR': true}, {preserveConsecutiveUppercase: true}),
);
expectType<{fooBAR: boolean}>(
camelcaseKeys({foo_BAR: true}, {preserveConsecutiveUppercase: true}),
);
expectType<{fooBAR: boolean}>(
camelcaseKeys({'foo BAR': true}, {preserveConsecutiveUppercase: true}),
);
expectType<{FooBAR: boolean}>(
camelcaseKeys({'foo BAR': true}, {preserveConsecutiveUppercase: true, pascalCase: true}),
);
expectType<{fooBAR: {fooBAR: {fooBAR: boolean}}}>(
camelcaseKeys(
{'foo-BAR': {foo_BAR: {'foo BAR': true}}},
{deep: true, preserveConsecutiveUppercase: true},
),
);

expectType<{fooBar: boolean; foo_bar: true}>(
camelcaseKeys(
{'foo-bar': true, foo_bar: true},
Expand Down Expand Up @@ -193,13 +218,13 @@ expectType<CamelCaseKeys<typeof nestedItem, true, true>>(
const data = {'foo-bar': true, foo_bar: true};
const exclude = ['foo', 'foo_bar', /bar/] as const;

expectType<CamelCaseKeys<typeof data, false, false, typeof exclude>>(
expectType<CamelCaseKeys<typeof data, false, false, false, typeof exclude>>(
camelcaseKeys(data, {exclude}),
);

const nonNestedWithStopPathData = {'foo-bar': true, foo_bar: true};
expectType<
CamelCaseKeys<typeof nonNestedWithStopPathData, false, false, ['foo']>
CamelCaseKeys<typeof nonNestedWithStopPathData, false, false, false, ['foo']>
>(camelcaseKeys({'foo-bar': true}, {stopPaths: ['foo']}));
const nestedWithStopPathData = {
'top-level': {'foo-bar': {'bar-baz': true}},
Expand All @@ -211,6 +236,7 @@ CamelCaseKeys<
typeof nestedWithStopPathData,
true,
false,
false,
// eslint-disable-next-line @typescript-eslint/ban-types
[],
typeof stopPaths
Expand Down
13 changes: 13 additions & 0 deletions readme.md
Expand Up @@ -27,6 +27,12 @@ camelcaseKeys({'foo-bar': true, nested: {unicorn_rainbow: true}}, {deep: true});
camelcaseKeys({a_b: 1, a_c: {c_d: 1, c_e: {e_f: 1}}}, {deep: true, stopPaths: ['a_c.c_e']}),
//=> {aB: 1, aC: {cD: 1, cE: {e_f: 1}}}

// preserve Uppercase if they are consecutive
camelcaseKeys({'foo-BAR': true, nested: {unicorn_RAINbow: true}}, {deep: true, preserveConsecutiveUppercase: false});
//=> {fooBar: true, nested: {unicornRainbow: true}}
camelcaseKeys({'foo-BAR': true, nested: {unicorn_RAINbow: true}}, {deep: true, preserveConsecutiveUppercase: true});
//=> {fooBAR: true, nested: {unicornRAINbow: true}}

// Convert object keys to pascal case
camelcaseKeys({'foo-bar': true, nested: {unicorn_rainbow: true}}, {deep: true, pascalCase: true});
//=> {FooBar: true, Nested: {UnicornRainbow: true}}
Expand Down Expand Up @@ -113,6 +119,13 @@ Default: `false`

Uppercase the first character as in `bye-bye``ByeBye`.

##### preserveConsecutiveUppercase

Type: `boolean`\
Default: `false`

Preserve consecutive uppercase characters: `foo-BAR``FooBAR` if true vs `foo-BAR``FooBar` if false

## Related

- [decamelize-keys](https://github.com/sindresorhus/decamelize-keys) - The inverse of this package
Expand Down
13 changes: 13 additions & 0 deletions test.js
Expand Up @@ -52,6 +52,19 @@ test('stopPaths option', t => {
);
});

test('preserveConsecutiveUppercase option only', t => {
// eslint-disable-next-line camelcase
t.true(camelcaseKeys({new_foo_BAR: true}, {preserveConsecutiveUppercase: true}).newFooBAR);
});

test('preserveConsecutiveUppercase and deep options', t => {
t.deepEqual(
// eslint-disable-next-line camelcase
camelcaseKeys({p_FOO_bar: true, p_obj: {p_two: false, p_arr: [{p_THREE_four: true}]}}, {deep: true, preserveConsecutiveUppercase: true}),
{pFOOBar: true, pObj: {pTwo: false, pArr: [{pTHREEFour: true}]}},
);
});

test('pascalCase option only', t => {
t.true(camelcaseKeys({'new-foo-bar': true}, {pascalCase: true}).NewFooBar);
});
Expand Down

0 comments on commit 507d5d0

Please sign in to comment.