Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce locale proxy #2004

Merged
merged 46 commits into from
Apr 23, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
f688434
feat: introduce locale access proxy
ST-DDT Apr 1, 2023
9db8ff9
chore: fix typo
ST-DDT Apr 2, 2023
bef53d7
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 2, 2023
b495965
test: extend proxy tests
ST-DDT Apr 2, 2023
c0be7e1
chore: fix import order
ST-DDT Apr 2, 2023
109ef31
test: use describe instead of comment sections
ST-DDT Apr 2, 2023
0f42fda
chore: apply suggestions
ST-DDT Apr 2, 2023
8530492
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 2, 2023
149ec6b
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 2, 2023
eebccd0
docs: extend migration guide
ST-DDT Apr 2, 2023
5c70e32
chore: typo
ST-DDT Apr 2, 2023
5c73725
chore: reword
ST-DDT Apr 2, 2023
df38ac7
chore: mark string type
ST-DDT Apr 2, 2023
d8fa41f
docs: add examples
ST-DDT Apr 2, 2023
05d2cab
docs: improve example
ST-DDT Apr 2, 2023
79cb31a
docs: apply suggestions
ST-DDT Apr 2, 2023
885627b
chore: fix typo
ST-DDT Apr 2, 2023
0ff62af
chore: apply suggestion
ST-DDT Apr 2, 2023
42443fa
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 2, 2023
419edfe
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 4, 2023
e0b8ac2
test: remove expected failure
ST-DDT Apr 4, 2023
fff9ccb
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 5, 2023
fe435ad
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 12, 2023
021e851
chore: apply suggestions
ST-DDT Apr 12, 2023
f69b474
chore: lost commit
ST-DDT Apr 12, 2023
d2a5312
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 13, 2023
dec1659
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 15, 2023
2a4cc3f
chore: apply suggestions
ST-DDT Apr 15, 2023
372e1f2
chore: apply suggestion
ST-DDT Apr 15, 2023
fd80d4f
test: update error message
ST-DDT Apr 15, 2023
4b92071
Update test/faker.spec.ts
ST-DDT Apr 15, 2023
385d4fa
chore: format
ST-DDT Apr 15, 2023
f107258
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 16, 2023
9b298c7
test: add tests for Object.keys
ST-DDT Apr 17, 2023
316941d
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 20, 2023
f99f72c
fix: proxy-impl
ST-DDT Apr 20, 2023
106bf70
Apply suggestions from code review
ST-DDT Apr 20, 2023
d722c6a
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 21, 2023
fbde3ac
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 21, 2023
24e14cf
refactor: use null as not applicable data
ST-DDT Apr 21, 2023
6acf6de
Merge branch 'next' into feat/locale-access-proxy
matthewmayer Apr 22, 2023
aafe702
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 22, 2023
1f29440
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 23, 2023
2ab6023
chore: own review
ST-DDT Apr 23, 2023
783b7ba
Update src/locale-proxy.ts
ST-DDT Apr 23, 2023
f915d49
Merge branch 'next' into feat/locale-access-proxy
ST-DDT Apr 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/definitions/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,4 @@ export type LocaleDefinition = {
system?: SystemDefinitions;
vehicle?: VehicleDefinitions;
word?: WordDefinitions;
} & Record<string, Record<string, unknown>>;
} & Record<string, Record<string, unknown> | undefined>;
8 changes: 6 additions & 2 deletions src/faker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { FakerError } from './errors/faker-error';
import { deprecated } from './internal/deprecated';
import type { Mersenne } from './internal/mersenne/mersenne';
import mersenne from './internal/mersenne/mersenne';
import type { LocaleAccess } from './locale-proxy';
import { createLocaleAccess } from './locale-proxy';
import { AirlineModule } from './modules/airline';
import { AnimalModule } from './modules/animal';
import { ColorModule } from './modules/color';
Expand Down Expand Up @@ -60,7 +62,8 @@ import { mergeLocales } from './utils/merge-locales';
* customFaker.music.genre(); // throws Error as this data is not available in `es`
*/
export class Faker {
readonly definitions: LocaleDefinition;
readonly rawDefinitions: LocaleDefinition;
readonly definitions: LocaleAccess;
private _defaultRefDate: () => Date = () => new Date();

/**
Expand Down Expand Up @@ -330,7 +333,8 @@ export class Faker {
locale = mergeLocales(locale);
}

this.definitions = locale as LocaleDefinition;
this.rawDefinitions = locale as LocaleDefinition;
this.definitions = createLocaleAccess(this.rawDefinitions);
}

/**
Expand Down
98 changes: 98 additions & 0 deletions src/locale-proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type { LocaleDefinition } from './definitions';
import { FakerError } from './errors/faker-error';

/**
* A proxy for LocaleDefinitions that marks all properties as required and throws an error when an entry is accessed that is not defined.
*/
export type LocaleAccess = Readonly<{
Shinigami92 marked this conversation as resolved.
Show resolved Hide resolved
[key in keyof LocaleDefinition]-?: Readonly<
Required<NonNullable<LocaleDefinition[key]>>
>;
}>;

/**
* Creates a proxy for LocaleDefinition that throws an error when a property is accessed that is not defined.
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
*
* @param locale The locale definition to create the proxy for.
*/
export function createLocaleAccess(locale: LocaleDefinition): LocaleAccess {
return new Proxy({} as LocaleDefinition, {
has(): true {
return true;
},
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved

get(
target,
categoryName: keyof LocaleDefinition
): LocaleDefinition[keyof LocaleDefinition] {
if (categoryName in target) {
return target[categoryName];
}

return (target[categoryName] = createCategoryProxy(
categoryName,
locale[categoryName]
));
},

set(): never {
throw new FakerError('LocaleAccess is read-only.');
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
},

deleteProperty(): never {
throw new FakerError('LocaleAccess is read-only.');
},
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved

ownKeys(): Array<keyof LocaleAccess> {
return Object.keys(locale);
},
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
}) as LocaleAccess;
}

/**
* Creates a proxy for a category that throws an error when a property is accessed that is not defined.
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
*
* @param categoryName The name of the category.
* @param categoryData The module to create the proxy for.
*/
function createCategoryProxy<T extends Record<string | symbol, unknown>>(
Shinigami92 marked this conversation as resolved.
Show resolved Hide resolved
categoryName: string,
categoryData: T = {} as T
): Required<T> {
return new Proxy({} as Required<T>, {
has(_, entryName: keyof T): boolean {
const value = categoryData[entryName];
return value != null && (!Array.isArray(value) || value.length !== 0);
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
},
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved

get(_, entryName: keyof T): T[keyof T] {
const value = categoryData[entryName];
if (value == null) {
throw new FakerError(
`The locale data for '${categoryName}.${entryName.toString()}' are missing in this locale.
Please contribute the missing data to the project or use a locale/Faker instance that has these data.
For more information see https://next.fakerjs.dev/guide/localization.html`
);
} else if (Array.isArray(value) && value.length === 0) {
throw new FakerError(
`The locale data for '${categoryName}.${entryName.toString()}' aren't applicable to this locale.
If you think this is a bug, please report it at: https://github.com/faker-js/faker`
);
} else {
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
return value;
}
},

set(): never {
throw new FakerError('LocaleAccess is read-only.');
},

deleteProperty(): never {
throw new FakerError('LocaleAccess is read-only.');
},
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved

ownKeys(): Array<string | symbol> {
return Object.keys(categoryData);
},
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
});
}
2 changes: 1 addition & 1 deletion src/modules/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1139,7 +1139,7 @@ export class HelpersModule {
const parts = method.split('.');

let currentModuleOrMethod: unknown = this.faker;
let currentDefinitions: unknown = this.faker.definitions;
let currentDefinitions: unknown = this.faker.rawDefinitions;
xDivisionByZerox marked this conversation as resolved.
Show resolved Hide resolved

// Search for the requested method or definition
for (const part of parts) {
Expand Down
3 changes: 2 additions & 1 deletion src/modules/location/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ export class LocationModule {

const { state } = options;

const zipRange = this.faker.definitions.location.postcode_by_state?.[state];
const zipRange =
this.faker.rawDefinitions.location?.postcode_by_state?.[state];
if (zipRange) {
return String(this.faker.number.int(zipRange));
}
Expand Down
19 changes: 9 additions & 10 deletions src/modules/person/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,20 @@ function selectDefinition<T>(
* Module to generate people's personal information such as names and job titles. Prior to Faker 8.0.0, this module was known as `faker.name`.
*
* ### Overview
*
*
* To generate a full name, use [`fullName`](https://next.fakerjs.dev/api/person.html#fullname). Note that this is not the same as simply concatenating [`firstName`](https://next.fakerjs.dev/api/person.html#firstname) and [`lastName`](https://next.fakerjs.dev/api/person.html#lastname), as the full name may contain a prefix, suffix, or both. Additionally, different supported locales will have differing name patterns. For example, the last name may appear before the first name, or there may be a double or hyphenated first or last name.
*
*
* You can also generate the parts of a name separately, using [`prefix`](https://next.fakerjs.dev/api/person.html#prefix), [`firstName`](https://next.fakerjs.dev/api/person.html#firstname), [`middleName`](https://next.fakerjs.dev/api/person.html#middlename), [`lastName`](https://next.fakerjs.dev/api/person.html#lastname), and [`suffix`](https://next.fakerjs.dev/api/person.html#suffix). Not all locales support all of these parts.
*
* Many of the methods in this module can optionally choose either female, male or mixed names.
*
* Job-related data is also available. To generate a job title, use [`jobTitle`](https://next.fakerjs.dev/api/person.html#jobtitle).
*
* Job-related data is also available. To generate a job title, use [`jobTitle`](https://next.fakerjs.dev/api/person.html#jobtitle).
*
* This module can also generate other personal information which might appear in user profiles, such as [`gender`](https://next.fakerjs.dev/api/person.html#gender), [`zodiacSign`](https://next.fakerjs.dev/api/person.html#zodiacsign), and [`bio`](https://next.fakerjs.dev/api/person.html#bio).
*
*
* ### Related modules
*
* For personal contact information like phone numbers and email addresses, see the [`faker.phone`](https://next.fakerjs.dev/api/phone.html) and [`faker.internet`](https://next.fakerjs.dev/api/internet.html) modules.

*/
export class PersonModule {
constructor(private readonly faker: Faker) {
Expand Down Expand Up @@ -102,7 +101,7 @@ export class PersonModule {
*/
firstName(sex?: SexType): string {
const { first_name, female_first_name, male_first_name } =
this.faker.definitions.person;
this.faker.rawDefinitions.person ?? {};

return selectDefinition(this.faker, this.faker.helpers.arrayElement, sex, {
generic: first_name,
Expand Down Expand Up @@ -132,7 +131,7 @@ export class PersonModule {
last_name_patterns,
male_last_name_patterns,
female_last_name_patterns,
} = this.faker.definitions.person;
} = this.faker.rawDefinitions.person ?? {};

if (
last_name_patterns != null ||
Expand Down Expand Up @@ -179,7 +178,7 @@ export class PersonModule {
*/
middleName(sex?: SexType): string {
const { middle_name, female_middle_name, male_middle_name } =
this.faker.definitions.person;
this.faker.rawDefinitions.person ?? {};

return selectDefinition(this.faker, this.faker.helpers.arrayElement, sex, {
generic: middle_name,
Expand Down Expand Up @@ -320,7 +319,7 @@ export class PersonModule {
*/
prefix(sex?: SexType): string {
const { prefix, female_prefix, male_prefix } =
this.faker.definitions.person;
this.faker.rawDefinitions.person ?? {};

return selectDefinition(this.faker, this.faker.helpers.arrayElement, sex, {
generic: prefix,
Expand Down
6 changes: 0 additions & 6 deletions test/__snapshots__/location.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,6 @@ exports[`location > 42 > streetAddress > with boolean 1`] = `"7917 Miller Park"`

exports[`location > 42 > streetAddress > with useFullAddress options 1`] = `"7917 Miller Park Apt. 410"`;

exports[`location > 42 > streetName 1`] = `"b"`;

exports[`location > 42 > timeZone 1`] = `"America/North_Dakota/New_Salem"`;

exports[`location > 42 > zipCode > noArgs 1`] = `"79177"`;
Expand Down Expand Up @@ -298,8 +296,6 @@ exports[`location > 1211 > streetAddress > with boolean 1`] = `"487 Breana Wells

exports[`location > 1211 > streetAddress > with useFullAddress options 1`] = `"487 Breana Wells Apt. 616"`;

exports[`location > 1211 > streetName 1`] = `"c"`;

exports[`location > 1211 > timeZone 1`] = `"Pacific/Fiji"`;

exports[`location > 1211 > zipCode > noArgs 1`] = `"48721-9061"`;
Expand Down Expand Up @@ -456,8 +452,6 @@ exports[`location > 1337 > streetAddress > with boolean 1`] = `"51225 Alexys Gat

exports[`location > 1337 > streetAddress > with useFullAddress options 1`] = `"51225 Alexys Gateway Apt. 552"`;

exports[`location > 1337 > streetName 1`] = `"a"`;

exports[`location > 1337 > timeZone 1`] = `"America/Guatemala"`;

exports[`location > 1337 > zipCode > noArgs 1`] = `"51225"`;
Expand Down
43 changes: 43 additions & 0 deletions test/all_functional.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { allLocales, Faker, RandomModule } from '../src';
import { allFakers, fakerEN } from '../src';

const IGNORED_MODULES = [
'rawDefinitions',
'definitions',
'helpers',
'_mersenne',
Expand All @@ -28,6 +29,48 @@ const BROKEN_LOCALE_METHODS = {
companySuffix: ['az'],
},
location: {
streetName: [
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
'af_ZA',
'ar',
'dv',
'el',
'en',
'en_AU',
'en_BORK',
'en_CA',
'en_GB',
'en_GH',
'en_IE',
'en_IN',
'en_NG',
'en_US',
'en_ZA',
'es',
'fa',
'fi',
'fr',
'fr_BE',
'fr_CA',
'fr_CH',
'fr_LU',
'hu',
'hy',
'id_ID',
'it',
'ja',
'ne',
'nl',
'nl_BE',
'pl',
'pt_BR',
'pt_PT',
'ur',
'vi',
'zh_CN',
'zh_TW',
'zu_ZA',
],
streetAddress: ['fr_CH'],
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
state: ['az', 'nb_NO', 'sk'],
stateAbbr: ['sk'],
},
Expand Down
17 changes: 15 additions & 2 deletions test/faker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ describe('faker', () => {
}
});

describe('rawDefinitions', () => {
it('locale definition accessability', () => {
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
// Metadata
expect(faker.rawDefinitions.metadata.title).toBeDefined();
// Standard modules
expect(faker.rawDefinitions.location?.city_name).toBeDefined();
// Custom modules
expect(faker.rawDefinitions.business?.credit_card_types).toBeDefined();
expect(faker.rawDefinitions.missing).toBeUndefined();
expect(faker.rawDefinitions.business?.missing).toBeUndefined();
});
});

describe('definitions', () => {
it('locale definition accessability', () => {
// Metadata
Expand All @@ -38,8 +51,8 @@ describe('faker', () => {
expect(faker.definitions.location.city_name).toBeDefined();
// Custom modules
expect(faker.definitions.business.credit_card_types).toBeDefined();
expect(faker.definitions.missing).toBeUndefined();
expect(faker.definitions.business.missing).toBeUndefined();
expect(faker.definitions.missing).toBeDefined();
expect(() => faker.definitions.business.missing).toThrow();
});
});

Expand Down
Loading