aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorST-DDT <[email protected]>2023-04-23 18:51:27 +0200
committerGitHub <[email protected]>2023-04-23 18:51:27 +0200
commit8a0bbf5faa03c294d308a13fe210ba6aaeef6968 (patch)
treec3fc64e877992b09b0c7699201b103aac1b06071 /src
parent2675ec20fe28ebf89fc8b5b939c9ae7fbde7559f (diff)
downloadfaker-8a0bbf5faa03c294d308a13fe210ba6aaeef6968.tar.xz
faker-8a0bbf5faa03c294d308a13fe210ba6aaeef6968.zip
feat: introduce locale proxy (#2004)
Diffstat (limited to 'src')
-rw-r--r--src/definitions/definitions.ts2
-rw-r--r--src/faker.ts8
-rw-r--r--src/locale-proxy.ts91
-rw-r--r--src/modules/helpers/index.ts2
-rw-r--r--src/modules/location/index.ts3
-rw-r--r--src/modules/person/index.ts8
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,