diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/definitions/definitions.ts | 2 | ||||
| -rw-r--r-- | src/faker.ts | 8 | ||||
| -rw-r--r-- | src/locale-proxy.ts | 91 | ||||
| -rw-r--r-- | src/modules/helpers/index.ts | 2 | ||||
| -rw-r--r-- | src/modules/location/index.ts | 3 | ||||
| -rw-r--r-- | src/modules/person/index.ts | 8 |
6 files changed, 104 insertions, 10 deletions
diff --git a/src/definitions/definitions.ts b/src/definitions/definitions.ts index 413b2bd7..87399408 100644 --- a/src/definitions/definitions.ts +++ b/src/definitions/definitions.ts @@ -52,4 +52,4 @@ export type LocaleDefinition = { system?: SystemDefinitions; vehicle?: VehicleDefinitions; word?: WordDefinitions; -} & Record<string, Record<string, unknown>>; +} & Record<string, Record<string, unknown> | undefined>; diff --git a/src/faker.ts b/src/faker.ts index a694fcc2..e715234d 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -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 { LocaleProxy } from './locale-proxy'; +import { createLocaleProxy } from './locale-proxy'; import { AirlineModule } from './modules/airline'; import { AnimalModule } from './modules/animal'; import { ColorModule } from './modules/color'; @@ -59,7 +61,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: LocaleProxy; private _defaultRefDate: () => Date = () => new Date(); /** @@ -329,7 +332,8 @@ export class Faker { locale = mergeLocales(locale); } - this.definitions = locale as LocaleDefinition; + this.rawDefinitions = locale as LocaleDefinition; + this.definitions = createLocaleProxy(this.rawDefinitions); } /** diff --git a/src/locale-proxy.ts b/src/locale-proxy.ts new file mode 100644 index 00000000..c5c6aa1b --- /dev/null +++ b/src/locale-proxy.ts @@ -0,0 +1,91 @@ +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 LocaleProxy = Readonly<{ + [key in keyof LocaleDefinition]-?: Readonly< + Required<NonNullable<LocaleDefinition[key]>> + >; +}>; + +const throwReadOnlyError: () => never = () => { + throw new FakerError('You cannot edit the locale data on the faker instance'); +}; + +/** + * Creates a proxy for LocaleDefinition that throws an error if an undefined property is accessed. + * + * @param locale The locale definition to create the proxy for. + */ +export function createLocaleProxy(locale: LocaleDefinition): LocaleProxy { + const proxies = {} as LocaleDefinition; + return new Proxy(locale, { + has(): true { + // Categories are always present (proxied), that's why we return true. + return true; + }, + + get( + target: LocaleDefinition, + categoryName: keyof LocaleDefinition + ): LocaleDefinition[keyof LocaleDefinition] { + if (categoryName in proxies) { + return proxies[categoryName]; + } + + return (proxies[categoryName] = createCategoryProxy( + categoryName, + target[categoryName] + )); + }, + + set: throwReadOnlyError, + deleteProperty: throwReadOnlyError, + }) as LocaleProxy; +} + +/** + * Creates a proxy for a category that throws an error when accessing an undefined property. + * + * @param categoryName The name of the category. + * @param categoryData The module to create the proxy for. + */ +function createCategoryProxy< + CategoryData extends Record<string | symbol, unknown> +>( + categoryName: string, + categoryData: CategoryData = {} as CategoryData +): Required<CategoryData> { + return new Proxy(categoryData, { + has(target: CategoryData, entryName: keyof CategoryData): boolean { + const value = target[entryName]; + return value != null; + }, + + get( + target: CategoryData, + entryName: keyof CategoryData + ): CategoryData[keyof CategoryData] { + const value = target[entryName]; + if (value === null) { + 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 if (value === undefined) { + 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 { + return value; + } + }, + + set: throwReadOnlyError, + deleteProperty: throwReadOnlyError, + }) as Required<CategoryData>; +} diff --git a/src/modules/helpers/index.ts b/src/modules/helpers/index.ts index 48c8cea0..0bb54fb6 100644 --- a/src/modules/helpers/index.ts +++ b/src/modules/helpers/index.ts @@ -1160,7 +1160,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; // Search for the requested method or definition for (const part of parts) { diff --git a/src/modules/location/index.ts b/src/modules/location/index.ts index c39711b7..26ead8ad 100644 --- a/src/modules/location/index.ts +++ b/src/modules/location/index.ts @@ -73,8 +73,7 @@ export class LocationModule { const { state } = options; if (state) { - const zipRange = - this.faker.definitions.location.postcode_by_state?.[state]; + const zipRange = this.faker.definitions.location.postcode_by_state[state]; if (zipRange) { return String(this.faker.number.int(zipRange)); diff --git a/src/modules/person/index.ts b/src/modules/person/index.ts index 789cd58c..428b1373 100644 --- a/src/modules/person/index.ts +++ b/src/modules/person/index.ts @@ -102,7 +102,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, @@ -132,7 +132,7 @@ export class PersonModule { last_name_pattern, male_last_name_pattern, female_last_name_pattern, - } = this.faker.definitions.person; + } = this.faker.rawDefinitions.person ?? {}; if ( last_name_pattern != null || @@ -174,7 +174,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, @@ -315,7 +315,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, |
