Skip to content

Commit 507d5d0

Browse files
authored
Add preserveConsecutiveUppercase option (#115)
1 parent 8f189be commit 507d5d0

File tree

5 files changed

+69
-4
lines changed

5 files changed

+69
-4
lines changed

index.d.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export type CamelCaseKeys<
4141
T extends ObjectOptional | readonly any[],
4242
Deep extends boolean = false,
4343
IsPascalCase extends boolean = false,
44+
PreserveConsecutiveUppercase extends boolean = false,
4445
Exclude extends readonly unknown[] = EmptyTuple,
4546
StopPaths extends readonly string[] = EmptyTuple,
4647
Path extends string = '',
@@ -52,6 +53,7 @@ export type CamelCaseKeys<
5253
T[P],
5354
Deep,
5455
IsPascalCase,
56+
PreserveConsecutiveUppercase,
5557
Exclude,
5658
StopPaths
5759
>
@@ -64,7 +66,7 @@ export type CamelCaseKeys<
6466
? P
6567
: [IsPascalCase] extends [true]
6668
? PascalCase<P>
67-
: CamelCase<P>]: [IsInclude<StopPaths, AppendPath<Path, P & string>>] extends [
69+
: CamelCase<P, {preserveConsecutiveUppercase: PreserveConsecutiveUppercase}>]: [IsInclude<StopPaths, AppendPath<Path, P & string>>] extends [
6870
true,
6971
]
7072
? T[P]
@@ -74,6 +76,7 @@ export type CamelCaseKeys<
7476
T[P],
7577
Deep,
7678
IsPascalCase,
79+
PreserveConsecutiveUppercase,
7780
Exclude,
7881
StopPaths,
7982
AppendPath<Path, P & string>
@@ -145,6 +148,14 @@ type Options = {
145148
@default false
146149
*/
147150
readonly pascalCase?: boolean;
151+
152+
/**
153+
Preserve consecutive uppercase characters: `foo_BAR` → `FooBAR`.
154+
155+
@default false
156+
*/
157+
readonly preserveConsecutiveUppercase?: boolean;
158+
148159
};
149160

150161
/**
@@ -197,6 +208,7 @@ export default function camelcaseKeys<
197208
T,
198209
WithDefault<OptionsType['deep'], false>,
199210
WithDefault<OptionsType['pascalCase'], false>,
211+
WithDefault<OptionsType['preserveConsecutiveUppercase'], false>,
200212
WithDefault<OptionsType['exclude'], EmptyTuple>,
201213
WithDefault<OptionsType['stopPaths'], EmptyTuple>
202214
>;

index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const transform = (input, options = {}) => {
3232
pascalCase = false,
3333
stopPaths,
3434
deep = false,
35+
preserveConsecutiveUppercase = false,
3536
} = options;
3637

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

5657
if (key.length < 100) { // Prevent abuse
5758
cache.set(cacheKey, returnValue);

index.test-d.ts

+28-2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,31 @@ expectType<{FooBar: {FooBar: {FooBar: boolean}}}>(
8181
),
8282
);
8383

84+
expectType<{fooBAR: boolean}>(
85+
camelcaseKeys({'foo-BAR': true}, {preserveConsecutiveUppercase: true}),
86+
);
87+
expectType<{readonly fooBAR: true}>(
88+
camelcaseKeys({foo_BAR: true} as const, {preserveConsecutiveUppercase: true}),
89+
);
90+
expectType<{fooBAR: boolean}>(
91+
camelcaseKeys({'--foo-BAR': true}, {preserveConsecutiveUppercase: true}),
92+
);
93+
expectType<{fooBAR: boolean}>(
94+
camelcaseKeys({foo_BAR: true}, {preserveConsecutiveUppercase: true}),
95+
);
96+
expectType<{fooBAR: boolean}>(
97+
camelcaseKeys({'foo BAR': true}, {preserveConsecutiveUppercase: true}),
98+
);
99+
expectType<{FooBAR: boolean}>(
100+
camelcaseKeys({'foo BAR': true}, {preserveConsecutiveUppercase: true, pascalCase: true}),
101+
);
102+
expectType<{fooBAR: {fooBAR: {fooBAR: boolean}}}>(
103+
camelcaseKeys(
104+
{'foo-BAR': {foo_BAR: {'foo BAR': true}}},
105+
{deep: true, preserveConsecutiveUppercase: true},
106+
),
107+
);
108+
84109
expectType<{fooBar: boolean; foo_bar: true}>(
85110
camelcaseKeys(
86111
{'foo-bar': true, foo_bar: true},
@@ -193,13 +218,13 @@ expectType<CamelCaseKeys<typeof nestedItem, true, true>>(
193218
const data = {'foo-bar': true, foo_bar: true};
194219
const exclude = ['foo', 'foo_bar', /bar/] as const;
195220

196-
expectType<CamelCaseKeys<typeof data, false, false, typeof exclude>>(
221+
expectType<CamelCaseKeys<typeof data, false, false, false, typeof exclude>>(
197222
camelcaseKeys(data, {exclude}),
198223
);
199224

200225
const nonNestedWithStopPathData = {'foo-bar': true, foo_bar: true};
201226
expectType<
202-
CamelCaseKeys<typeof nonNestedWithStopPathData, false, false, ['foo']>
227+
CamelCaseKeys<typeof nonNestedWithStopPathData, false, false, false, ['foo']>
203228
>(camelcaseKeys({'foo-bar': true}, {stopPaths: ['foo']}));
204229
const nestedWithStopPathData = {
205230
'top-level': {'foo-bar': {'bar-baz': true}},
@@ -211,6 +236,7 @@ CamelCaseKeys<
211236
typeof nestedWithStopPathData,
212237
true,
213238
false,
239+
false,
214240
// eslint-disable-next-line @typescript-eslint/ban-types
215241
[],
216242
typeof stopPaths

readme.md

+13
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ camelcaseKeys({'foo-bar': true, nested: {unicorn_rainbow: true}}, {deep: true});
2727
camelcaseKeys({a_b: 1, a_c: {c_d: 1, c_e: {e_f: 1}}}, {deep: true, stopPaths: ['a_c.c_e']}),
2828
//=> {aB: 1, aC: {cD: 1, cE: {e_f: 1}}}
2929

30+
// preserve Uppercase if they are consecutive
31+
camelcaseKeys({'foo-BAR': true, nested: {unicorn_RAINbow: true}}, {deep: true, preserveConsecutiveUppercase: false});
32+
//=> {fooBar: true, nested: {unicornRainbow: true}}
33+
camelcaseKeys({'foo-BAR': true, nested: {unicorn_RAINbow: true}}, {deep: true, preserveConsecutiveUppercase: true});
34+
//=> {fooBAR: true, nested: {unicornRAINbow: true}}
35+
3036
// Convert object keys to pascal case
3137
camelcaseKeys({'foo-bar': true, nested: {unicorn_rainbow: true}}, {deep: true, pascalCase: true});
3238
//=> {FooBar: true, Nested: {UnicornRainbow: true}}
@@ -113,6 +119,13 @@ Default: `false`
113119

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

122+
##### preserveConsecutiveUppercase
123+
124+
Type: `boolean`\
125+
Default: `false`
126+
127+
Preserve consecutive uppercase characters: `foo-BAR``FooBAR` if true vs `foo-BAR``FooBar` if false
128+
116129
## Related
117130

118131
- [decamelize-keys](https://github.com/sindresorhus/decamelize-keys) - The inverse of this package

test.js

+13
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ test('stopPaths option', t => {
5252
);
5353
});
5454

55+
test('preserveConsecutiveUppercase option only', t => {
56+
// eslint-disable-next-line camelcase
57+
t.true(camelcaseKeys({new_foo_BAR: true}, {preserveConsecutiveUppercase: true}).newFooBAR);
58+
});
59+
60+
test('preserveConsecutiveUppercase and deep options', t => {
61+
t.deepEqual(
62+
// eslint-disable-next-line camelcase
63+
camelcaseKeys({p_FOO_bar: true, p_obj: {p_two: false, p_arr: [{p_THREE_four: true}]}}, {deep: true, preserveConsecutiveUppercase: true}),
64+
{pFOOBar: true, pObj: {pTwo: false, pArr: [{pTHREEFour: true}]}},
65+
);
66+
});
67+
5568
test('pascalCase option only', t => {
5669
t.true(camelcaseKeys({'new-foo-bar': true}, {pascalCase: true}).NewFooBar);
5770
});

0 commit comments

Comments
 (0)