Skip to content

Commit

Permalink
fix: object properties or class fields/methods must not convert to sn…
Browse files Browse the repository at this point in the history
…ake_case (#117)
  • Loading branch information
yutak23 authored Jun 29, 2023
1 parent 9ff7750 commit 3ab58ee
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 11 deletions.
13 changes: 6 additions & 7 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Options as SnakeCaseOptions } from "snake-case";

// eslint-disable-next-line @typescript-eslint/ban-types
type EmptyTuple = [];
type ObjectOptional = Record<string, unknown> | undefined;

/**
Return a default type if input type is nil.
Expand Down Expand Up @@ -44,26 +45,24 @@ declare namespace snakecaseKeys {
@template Path - Path of keys.
*/
export type SnakeCaseKeys<
T extends Record<string, any> | readonly any[],
T extends ObjectOptional | readonly any[],
Deep extends boolean = true,
Exclude extends readonly unknown[] = EmptyTuple,
Path extends string = ""
> = T extends readonly any[]
? // Handle arrays or tuples.
{
[P in keyof T]: T[P] extends Record<string, any> | readonly any[]
[P in keyof T]: T[P] extends Record<string, unknown> | readonly any[]
? SnakeCaseKeys<T[P], Deep, Exclude>
: T[P];
}
: T extends Record<string, Date | Error | RegExp>
? T // Date, Error, and RegExp objects are not converted.
: T extends Record<string, any>
: T extends Record<string, unknown>
? // Handle objects.
{
[P in keyof T as [Includes<Exclude, P>] extends [true]
? P
: SnakeCase<P>]: [Deep] extends [true]
? T[P] extends Record<string, any> | undefined
? T[P] extends ObjectOptional | readonly any[]
? SnakeCaseKeys<T[P], Deep, Exclude, AppendPath<Path, P & string>>
: T[P]
: T[P];
Expand Down Expand Up @@ -98,7 +97,7 @@ Convert object keys to snake using [`to-snake-case`](https://github.com/ianstorm
@param options - Options of conversion.
*/
declare function snakecaseKeys<
T extends Record<string, any> | readonly any[],
T extends Record<string, unknown> | readonly any[],
Options extends snakecaseKeys.Options
>(
input: T,
Expand Down
51 changes: 47 additions & 4 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,35 @@ import { expectAssignable, expectType, expectNotType } from "tsd";
import type { SnakeCaseKeys } from ".";
import snakecaseKeys from ".";

class Point {
x: number;
y: number;

addPoint(point: Point): Point {
return new Point(this.x + point.x, this.y + point.y);
}

constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
const point = new Point(0, 10);

interface Person {
firstName: string;
lastName: string;
age: number;
}

const person: Person = {
firstName: 'firstName',
lastName: 'lastName',
age: 30
};

// Object
expectType<{}>(snakecaseKeys({}));
expectType<{ foo_bar: boolean }>(snakecaseKeys({ fooBar: true }));
expectAssignable<{ [key: string]: boolean }>(snakecaseKeys({ fooBar: true }));

Expand All @@ -16,14 +44,14 @@ expectAssignable<Array<{ [key: string]: boolean }>>(
expectType<string[]>(snakecaseKeys(["name 1", "name 2"]));

// Deep
expectType<{ foo_bar: { "foo-bar": { "foo bar": true } } }>(
expectType< { foo_bar: { "foo-bar": { "foo bar": true; }; }; nested: { pointObject: Point; }; }>(
snakecaseKeys(
{ foo_bar: { "foo-bar": { "foo bar": true } } },
{ foo_bar: { "foo-bar": { "foo bar": true } }, nested: { pointObject: point } },
{ deep: false }
)
);
expectType<{ foo_bar: { foo_bar: { foo_bar: boolean } } }>(
snakecaseKeys({ foo_bar: { "foo-bar": { "foo bar": true } } }, { deep: true })
expectType<{ foo_bar: { foo_bar: { foo_bar: boolean; }; }; nested: { point_object: Point; }; }>(
snakecaseKeys({ foo_bar: { "foo-bar": { "foo bar": true } }, nested: { pointObject: point } }, { deep: true })
);
expectType<{ foo_bar: { foo_bar: boolean } }[]>(
snakecaseKeys([{ "foo-bar": { foo_bar: true } }], { deep: true })
Expand All @@ -37,6 +65,18 @@ expectType<{ regexp: RegExp }[]>(
expectType<{ error: Error }[]>(
snakecaseKeys([{ error: new Error() }], { deep: true })
);
expectType<{ point_object: Point }[]>(
snakecaseKeys([{ pointObject: point }], { deep: true })
);
expectType<{ person_object: Person }[]>(
snakecaseKeys([{ personObject: person }], { deep: true })
);
expectType<{ date: Date, person_object: Person }[]>(
snakecaseKeys([{ date: new Date(), personObject: person }], { deep: true })
);
expectType<{ foo_bar: { foo_baz: { foo_bar: Point; }[]; }[]; }>(
snakecaseKeys({ fooBar: [{ fooBaz: [{ fooBar: point }] }] }, { deep: true })
);

// Deep with defalt(no deep option)
expectType<{ foo_bar: { foo_bar: { foo_bar: boolean } } }>(
Expand All @@ -48,6 +88,9 @@ expectType<{ foo_bar: { foo_bar: boolean } }[]>(
expectType<{ date: Date }[]>(
snakecaseKeys([{ date: new Date() }])
);
expectType<{ foo_bar: { foo_baz: { foo_bar: Point; }[]; }[]; }>(
snakecaseKeys({ fooBar: [{ fooBaz: [{ fooBar: point }] }] })
);

// Exclude
expectType<{ foo_bar: boolean; barBaz: true }>(
Expand Down

0 comments on commit 3ab58ee

Please sign in to comment.