Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
31 changes: 30 additions & 1 deletion src/modules/helpers/eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ const REGEX_DOT_OR_BRACKET = /\.|\(/;
* const airlineCode = fakeEval('airline.airline.iataCode', faker); // 'EY'
* const airlineName = fakeEval('airline.airline().name', faker); // 'Etihad Airways'
* const airlineMethodName = fakeEval('airline.airline.name', faker); // 'bound airline'
* const isoDate = fakeEval('date.anytime.toISOString', faker); // '2025-01-01T00:00:00.000Z'
* ```
*
* Properties and prototype methods on generated values can also be resolved.
*
* It is not possible to execute arbitrary JavaScript through this method;
* expressions can only resolve properties and methods reachable from the given entrypoints.
*
Expand Down Expand Up @@ -222,11 +225,37 @@ function resolveProperty(entrypoint: unknown, key: string): unknown {
}

case 'object': {
return entrypoint?.[key as keyof typeof entrypoint];
return getProperty(entrypoint, key);
Comment thread
hugosmoreira marked this conversation as resolved.
Outdated
}

case 'bigint':
case 'boolean':
case 'number':
case 'string': {
return getProperty(entrypoint, key);
}

default: {
return undefined;
}
}
}

type Callable = (this: unknown, ...args: never[]) => unknown;

/**
* Resolves the given property and binds methods to their source value.
*
* @param entrypoint The entrypoint to resolve the property on.
* @param key The property name to resolve.
*/
function getProperty(entrypoint: unknown, key: string): unknown {
if (entrypoint == null) {
return undefined;
}

const value = (new Object(entrypoint) as Record<string, unknown>)[key];
Comment thread
hugosmoreira marked this conversation as resolved.
return typeof value === 'function'
? (value as Callable).bind(entrypoint)
: value;
}
21 changes: 21 additions & 0 deletions src/modules/helpers/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,13 @@ export class HelpersModule extends SimpleHelpersModule {
* const message = faker.helpers.fake('Your pin is {{string.numeric(4, {"allowLeadingZeros": true})}}.');
* ```
*
* Properties and prototype methods on generated values can also be resolved:
*
* ```js
* const airlineCode = faker.helpers.fake('{{airline.airline.iataCode}}'); // 'AA'
* const timestamp = faker.helpers.fake('{{date.anytime.toISOString}}'); // '2025-01-01T00:00:00.000Z'
* ```
*
Comment thread
hugosmoreira marked this conversation as resolved.
* The pattern is not evaluated as JavaScript: only faker methods can be called, and any
* parameters are parsed as JSON or plain strings. Nevertheless, it is possible for certain
* maliciously crafted patterns to use large amounts of memory or CPU time, so the pattern
Expand Down Expand Up @@ -1253,6 +1260,13 @@ export class HelpersModule extends SimpleHelpersModule {
* const message = faker.helpers.fake(['Your pin is {{string.numeric(4, {"allowLeadingZeros": true})}}.']);
* ```
*
* Properties and prototype methods on generated values can also be resolved:
*
* ```js
* const airlineCode = faker.helpers.fake(['{{airline.airline.iataCode}}']); // 'AA'
* const timestamp = faker.helpers.fake(['{{date.anytime.toISOString}}']); // '2025-01-01T00:00:00.000Z'
* ```
*
* The pattern is not evaluated as JavaScript: only faker methods can be called, and any
* parameters are parsed as JSON or plain strings. Nevertheless, it is possible for certain
* maliciously crafted patterns to use large amounts of memory or CPU time, so the pattern
Expand Down Expand Up @@ -1300,6 +1314,13 @@ export class HelpersModule extends SimpleHelpersModule {
* const message = faker.helpers.fake('Your pin is {{string.numeric(4, {"allowLeadingZeros": true})}}.');
* ```
*
* Properties and prototype methods on generated values can also be resolved:
*
* ```js
* const airlineCode = faker.helpers.fake('{{airline.airline.iataCode}}'); // 'AA'
* const timestamp = faker.helpers.fake('{{date.anytime.toISOString}}'); // '2025-01-01T00:00:00.000Z'
* ```
*
* The pattern is not evaluated as JavaScript: only faker methods can be called, and any
* parameters are parsed as JSON or plain strings. Nevertheless, it is possible for certain
* maliciously crafted patterns to use large amounts of memory or CPU time, so the pattern
Expand Down
24 changes: 24 additions & 0 deletions test/modules/helpers-eval.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,29 @@ describe('fakeEval()', () => {
).toContain(actual);
});

it('supports properties and prototype methods after generated values', () => {
const entrypoint = {
date: () => new Date('2025-01-01T00:00:00.000Z'),
number: () => 1234,
string: () => ' value ',
};

expect(fakeEval('number.toPrecision(3)', faker, [entrypoint])).toBe(
'1.23e+3'
);
expect(fakeEval('number().toPrecision(3)', faker, [entrypoint])).toBe(
'1.23e+3'
);
expect(fakeEval('string.trim', faker, [entrypoint])).toBe('value');
expect(fakeEval('string().length', faker, [entrypoint])).toBe(9);
expect(fakeEval('date.toISOString', faker, [entrypoint])).toBe(
'2025-01-01T00:00:00.000Z'
);
expect(fakeEval('date().toISOString', faker, [entrypoint])).toBe(
'2025-01-01T00:00:00.000Z'
);
});

it('requires a dot after a function call', () => {
expect(() => fakeEval('airline.airline()iataCode', faker)).toThrow(
new FakerError(
Expand Down Expand Up @@ -187,6 +210,7 @@ describe('fakeEval()', () => {
'do.evil.constructor.constructor(globalThis.__fakerPwned = true)',
'do.evil.constructor.constructor(globalThis.__fakerPwned = true)()',
'string.constructor.constructor(globalThis.__fakerPwned = true)()',
'string.alpha(1).constructor.constructor(globalThis.__fakerPwned = true)()',
];

beforeEach(() => {
Expand Down
12 changes: 12 additions & 0 deletions test/modules/helpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1077,6 +1077,18 @@ describe('helpers', () => {
expect(faker.helpers.fake('{{string.alphanumeric(0)}}')).toBe('');
});

it('should resolve properties after generated primitive values', () => {
expect(faker.helpers.fake('{{string.alpha(10).length}}')).toBe('10');
});

it('should call prototype methods with their generated receiver', () => {
expect(
faker.helpers.fake(
'{{date.anytime({"refDate":"2025-01-01T00:00:00.000Z"}).toISOString}}'
)
).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
});

it('should be able to return locale definition strings', () => {
expect(faker.definitions.cell_phone?.formats).toContain(
faker.helpers.fake('{{cell_phone.formats}}')
Expand Down