diff --git a/packages/fast-check/src/arbitrary/_internals/builders/CharacterRangeArbitraryBuilder.ts b/packages/fast-check/src/arbitrary/_internals/builders/CharacterRangeArbitraryBuilder.ts index de207f65551..047b2e2ca43 100644 --- a/packages/fast-check/src/arbitrary/_internals/builders/CharacterRangeArbitraryBuilder.ts +++ b/packages/fast-check/src/arbitrary/_internals/builders/CharacterRangeArbitraryBuilder.ts @@ -2,8 +2,9 @@ import { fullUnicode } from '../../fullUnicode'; import type { Arbitrary } from '../../../check/arbitrary/definition/Arbitrary'; import { oneof } from '../../oneof'; import { mapToConstant } from '../../mapToConstant'; -import { safeCharCodeAt, safeNumberToString, encodeURIComponent } from '../../../utils/globals'; +import { safeCharCodeAt, safeNumberToString, encodeURIComponent, safeMapGet, safeMapSet } from '../../../utils/globals'; +const SMap = Map; const safeStringFromCharCode = String.fromCharCode; /** @internal */ @@ -32,18 +33,56 @@ function percentCharArbUnmapper(value: unknown): string { /** @internal */ const percentCharArb = fullUnicode().map(percentCharArbMapper, percentCharArbUnmapper); +let lowerAlphaArbitrary: Arbitrary | undefined = undefined; + /** @internal */ -export const buildLowerAlphaArbitrary = (others: string[]): Arbitrary => - mapToConstant(lowerCaseMapper, { num: others.length, build: (v) => others[v] }); +export function getOrCreateLowerAlphaArbitrary(): Arbitrary { + if (lowerAlphaArbitrary === undefined) { + lowerAlphaArbitrary = mapToConstant(lowerCaseMapper); + } + return lowerAlphaArbitrary; +} + +let lowerAlphaNumericArbitraries: Map> | undefined = undefined; /** @internal */ -export const buildLowerAlphaNumericArbitrary = (others: string[]): Arbitrary => - mapToConstant(lowerCaseMapper, numericMapper, { num: others.length, build: (v) => others[v] }); +export function getOrCreateLowerAlphaNumericArbitrary(others: string): Arbitrary { + if (lowerAlphaNumericArbitraries === undefined) { + lowerAlphaNumericArbitraries = new SMap(); + } + let match = safeMapGet(lowerAlphaNumericArbitraries, others); + if (match === undefined) { + match = mapToConstant(lowerCaseMapper, numericMapper, { + num: others.length, + build: (v) => others[v], + }); + safeMapSet(lowerAlphaNumericArbitraries, others, match); + } + return match; +} /** @internal */ -export const buildAlphaNumericArbitrary = (others: string[]): Arbitrary => - mapToConstant(lowerCaseMapper, upperCaseMapper, numericMapper, { num: others.length, build: (v) => others[v] }); +function buildAlphaNumericArbitrary(others: string): Arbitrary { + return mapToConstant(lowerCaseMapper, upperCaseMapper, numericMapper, { + num: others.length, + build: (v) => others[v], + }); +} + +let alphaNumericPercentArbitraries: Map> | undefined = undefined; /** @internal */ -export const buildAlphaNumericPercentArbitrary = (others: string[]): Arbitrary => - oneof({ weight: 10, arbitrary: buildAlphaNumericArbitrary(others) }, { weight: 1, arbitrary: percentCharArb }); +export function getOrCreateAlphaNumericPercentArbitrary(others: string): Arbitrary { + if (alphaNumericPercentArbitraries === undefined) { + alphaNumericPercentArbitraries = new SMap(); + } + let match = safeMapGet(alphaNumericPercentArbitraries, others); + if (match === undefined) { + match = oneof( + { weight: 10, arbitrary: buildAlphaNumericArbitrary(others) }, + { weight: 1, arbitrary: percentCharArb }, + ); + safeMapSet(alphaNumericPercentArbitraries, others, match); + } + return match; +} diff --git a/packages/fast-check/src/arbitrary/_internals/builders/UriQueryOrFragmentArbitraryBuilder.ts b/packages/fast-check/src/arbitrary/_internals/builders/UriQueryOrFragmentArbitraryBuilder.ts index d5cf4b2b8a9..1befcaec06f 100644 --- a/packages/fast-check/src/arbitrary/_internals/builders/UriQueryOrFragmentArbitraryBuilder.ts +++ b/packages/fast-check/src/arbitrary/_internals/builders/UriQueryOrFragmentArbitraryBuilder.ts @@ -1,12 +1,11 @@ import type { Arbitrary } from '../../../check/arbitrary/definition/Arbitrary'; -import { buildAlphaNumericPercentArbitrary } from './CharacterRangeArbitraryBuilder'; -import { stringOf } from '../../stringOf'; +import { getOrCreateAlphaNumericPercentArbitrary } from './CharacterRangeArbitraryBuilder'; +import { string } from '../../string'; import type { SizeForArbitrary } from '../helpers/MaxLengthFromMinLength'; /** @internal */ export function buildUriQueryOrFragmentArbitrary(size: Exclude): Arbitrary { // query = *( pchar / "/" / "?" ) // fragment = *( pchar / "/" / "?" ) - const others = ['-', '.', '_', '~', '!', '$', '&', "'", '(', ')', '*', '+', ',', ';', '=', ':', '@', '/', '?']; - return stringOf(buildAlphaNumericPercentArbitrary(others), { size }); + return string({ unit: getOrCreateAlphaNumericPercentArbitrary("-._~!$&'()*+,;=:@/?"), size }); } diff --git a/packages/fast-check/src/arbitrary/domain.ts b/packages/fast-check/src/arbitrary/domain.ts index 07dd4b7f043..42a3c817d80 100644 --- a/packages/fast-check/src/arbitrary/domain.ts +++ b/packages/fast-check/src/arbitrary/domain.ts @@ -1,10 +1,10 @@ import { array } from './array'; import { - buildLowerAlphaArbitrary, - buildLowerAlphaNumericArbitrary, + getOrCreateLowerAlphaArbitrary, + getOrCreateLowerAlphaNumericArbitrary, } from './_internals/builders/CharacterRangeArbitraryBuilder'; import { option } from './option'; -import { stringOf } from './stringOf'; +import { string } from './string'; import { tuple } from './tuple'; import type { Arbitrary } from '../check/arbitrary/definition/Arbitrary'; import { filterInvalidSubdomainLabel } from './_internals/helpers/InvalidSubdomainLabelFiIter'; @@ -31,8 +31,8 @@ function toSubdomainLabelUnmapper(value: unknown): [string, [string, string] | n /** @internal */ function subdomainLabel(size: Size) { - const alphaNumericArb = buildLowerAlphaNumericArbitrary([]); - const alphaNumericHyphenArb = buildLowerAlphaNumericArbitrary(['-']); + const alphaNumericArb = getOrCreateLowerAlphaNumericArbitrary(''); + const alphaNumericHyphenArb = getOrCreateLowerAlphaNumericArbitrary('-'); // Rq: maxLength = 61 because max length of a label is 63 according to RFC 1034 // and we add 2 characters to this generated value // According to RFC 1034 (confirmed by RFC 1035): @@ -48,7 +48,7 @@ function subdomainLabel(size: Size) { // restriction on the first character is relaxed to allow either a letter or a digit. Host software MUST support this more liberal syntax." return tuple( alphaNumericArb, - option(tuple(stringOf(alphaNumericHyphenArb, { size, maxLength: 61 }), alphaNumericArb)), + option(tuple(string({ unit: alphaNumericHyphenArb, size, maxLength: 61 }), alphaNumericArb)), ) .map(toSubdomainLabelMapper, toSubdomainLabelUnmapper) .filter(filterInvalidSubdomainLabel); @@ -118,8 +118,8 @@ export function domain(constraints: DomainConstraints = {}): Arbitrary { // A list of public suffixes can be found here: https://publicsuffix.org/list/public_suffix_list.dat // our current implementation does not follow this list and generate a fully randomized suffix // which is probably not in this list (probability would be low) - const alphaNumericArb = buildLowerAlphaArbitrary([]); - const publicSuffixArb = stringOf(alphaNumericArb, { minLength: 2, maxLength: 63, size: resolvedSizeMinusOne }); + const lowerAlphaArb = getOrCreateLowerAlphaArbitrary(); + const publicSuffixArb = string({ unit: lowerAlphaArb, minLength: 2, maxLength: 63, size: resolvedSizeMinusOne }); return ( // labels have between 1 and 63 characters // domains are made of dot-separated labels and have up to 255 characters so that are made of up-to 128 labels diff --git a/packages/fast-check/src/arbitrary/emailAddress.ts b/packages/fast-check/src/arbitrary/emailAddress.ts index 6eb0bf287cf..04d55761420 100644 --- a/packages/fast-check/src/arbitrary/emailAddress.ts +++ b/packages/fast-check/src/arbitrary/emailAddress.ts @@ -1,7 +1,7 @@ import { array } from './array'; -import { buildLowerAlphaNumericArbitrary } from './_internals/builders/CharacterRangeArbitraryBuilder'; +import { getOrCreateLowerAlphaNumericArbitrary } from './_internals/builders/CharacterRangeArbitraryBuilder'; import { domain } from './domain'; -import { stringOf } from './stringOf'; +import { string } from './string'; import { tuple } from './tuple'; import type { Arbitrary } from '../check/arbitrary/definition/Arbitrary'; import type { SizeForArbitrary } from './_internals/helpers/MaxLengthFromMinLength'; @@ -71,15 +71,15 @@ export interface EmailAddressConstraints { * @public */ export function emailAddress(constraints: EmailAddressConstraints = {}): Arbitrary { - const others = ['!', '#', '$', '%', '&', "'", '*', '+', '-', '/', '=', '?', '^', '_', '`', '{', '|', '}', '~']; - const atextArb = buildLowerAlphaNumericArbitrary(others); + const atextArb = getOrCreateLowerAlphaNumericArbitrary("!#$%&'*+-/=?^_`{|}~"); const localPartArb = adapter( // Maximal length for the output of dotMapper is 64, // In other words: // - `stringOf(atextArb, ...)` cannot produce values having more than 64 characters // - `array(...)` cannot produce more than 32 values array( - stringOf(atextArb, { + string({ + unit: atextArb, minLength: 1, maxLength: 64, size: constraints.size, diff --git a/packages/fast-check/src/arbitrary/webAuthority.ts b/packages/fast-check/src/arbitrary/webAuthority.ts index 688f6e05f1b..77f44ef2364 100644 --- a/packages/fast-check/src/arbitrary/webAuthority.ts +++ b/packages/fast-check/src/arbitrary/webAuthority.ts @@ -1,5 +1,5 @@ import type { Arbitrary } from '../check/arbitrary/definition/Arbitrary'; -import { buildAlphaNumericPercentArbitrary } from './_internals/builders/CharacterRangeArbitraryBuilder'; +import { getOrCreateAlphaNumericPercentArbitrary } from './_internals/builders/CharacterRangeArbitraryBuilder'; import { constant } from './constant'; import { domain } from './domain'; import { ipV4 } from './ipV4'; @@ -8,14 +8,13 @@ import { ipV6 } from './ipV6'; import { nat } from './nat'; import { oneof } from './oneof'; import { option } from './option'; -import { stringOf } from './stringOf'; +import { string } from './string'; import { tuple } from './tuple'; import type { SizeForArbitrary } from './_internals/helpers/MaxLengthFromMinLength'; /** @internal */ function hostUserInfo(size: SizeForArbitrary): Arbitrary { - const others = ['-', '.', '_', '~', '!', '$', '&', "'", '(', ')', '*', '+', ',', ';', '=', ':']; - return stringOf(buildAlphaNumericPercentArbitrary(others), { size }); + return string({ unit: getOrCreateAlphaNumericPercentArbitrary("-._~!$&'()*+,;=:"), size }); } /** @internal */ diff --git a/packages/fast-check/src/arbitrary/webSegment.ts b/packages/fast-check/src/arbitrary/webSegment.ts index 15b3e2bcdb2..8b8b17b831a 100644 --- a/packages/fast-check/src/arbitrary/webSegment.ts +++ b/packages/fast-check/src/arbitrary/webSegment.ts @@ -1,6 +1,6 @@ import type { Arbitrary } from '../check/arbitrary/definition/Arbitrary'; -import { buildAlphaNumericPercentArbitrary } from './_internals/builders/CharacterRangeArbitraryBuilder'; -import { stringOf } from './stringOf'; +import { getOrCreateAlphaNumericPercentArbitrary } from './_internals/builders/CharacterRangeArbitraryBuilder'; +import { string } from './string'; import type { SizeForArbitrary } from './_internals/helpers/MaxLengthFromMinLength'; /** @@ -31,6 +31,5 @@ export interface WebSegmentConstraints { export function webSegment(constraints: WebSegmentConstraints = {}): Arbitrary { // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" // segment = *pchar - const others = ['-', '.', '_', '~', '!', '$', '&', "'", '(', ')', '*', '+', ',', ';', '=', ':', '@']; - return stringOf(buildAlphaNumericPercentArbitrary(others), { size: constraints.size }); + return string({ unit: getOrCreateAlphaNumericPercentArbitrary("-._~!$&'()*+,;=:@"), size: constraints.size }); } diff --git a/packages/fast-check/src/utils/globals.ts b/packages/fast-check/src/utils/globals.ts index a2c0edb7aa5..5ec2efaf0f1 100644 --- a/packages/fast-check/src/utils/globals.ts +++ b/packages/fast-check/src/utils/globals.ts @@ -327,6 +327,37 @@ export function safeGet(instance: WeakMap, key: T): U return safeApply(untouchedGet, instance, [key]); } +// Map + +const untouchedMapSet = Map.prototype.set; +const untouchedMapGet = Map.prototype.get; +function extractMapSet(instance: Map) { + try { + return instance.set; + } catch (err) { + return undefined; + } +} +function extractMapGet(instance: Map) { + try { + return instance.get; + } catch (err) { + return undefined; + } +} +export function safeMapSet(instance: Map, key: T, value: U): Map { + if (extractMapSet(instance) === untouchedMapSet) { + return instance.set(key, value); + } + return safeApply(untouchedMapSet, instance, [key, value]); +} +export function safeMapGet(instance: Map, key: T): U | undefined { + if (extractMapGet(instance) === untouchedMapGet) { + return instance.get(key); + } + return safeApply(untouchedMapGet, instance, [key]); +} + // String const untouchedSplit: (separator: string | RegExp, limit?: number) => string[] = String.prototype.split;