11import { LazyArbitrary } from './_internals/LazyArbitrary' ;
22import type { Arbitrary } from '../check/arbitrary/definition/Arbitrary' ;
3- import { safeHasOwnProperty } from '../utils/globals' ;
3+ import { safeAdd , safeHas , safeHasOwnProperty } from '../utils/globals' ;
4+ import { invertSize , resolveSize , type SizeForArbitrary } from './_internals/helpers/MaxLengthFromMinLength' ;
5+ import { nat } from './nat.js' ;
6+ import { record } from './record.js' ;
7+ import { array } from './array.js' ;
8+ import { noShrink } from './noShrink.js' ;
49
10+ const safeArrayIsArray = Array . isArray ;
511const safeObjectCreate = Object . create ;
12+ const safeObjectEntries = Object . entries ;
613
714/**
815 * Type of the value produced by {@link letrec}
@@ -50,6 +57,154 @@ export type LetrecLooselyTypedTie = (key: string) => Arbitrary<unknown>;
5057 */
5158export type LetrecLooselyTypedBuilder < T > = ( tie : LetrecLooselyTypedTie ) => LetrecValue < T > ;
5259
60+ /**
61+ * Constraints to be applied on {@link letrec}
62+ * @remarks Since 4.4.0
63+ * @public
64+ */
65+ export interface LetrecConstraints {
66+ /**
67+ * Generate objects with circular references
68+ * @defaultValue false
69+ * @remarks Since 4.4.0
70+ */
71+ withCycles ?: boolean | CycleConstraints ;
72+ }
73+
74+ /**
75+ * Constraints to be applied on {@link LetrecConstraints.withCycles}
76+ * @remarks Since 4.4.0
77+ * @public
78+ */
79+ export interface CycleConstraints {
80+ /**
81+ * Define how frequently cycles should occur in the generated values (at max)
82+ * @remarks Since 4.4.0
83+ */
84+ frequencySize ?: Exclude < SizeForArbitrary , 'max' > ;
85+ }
86+
87+ /** @internal */
88+ function letrecWithoutCycles < T > ( builder : LetrecLooselyTypedBuilder < T > | LetrecTypedBuilder < T > ) : LetrecValue < T > {
89+ const lazyArbs : { [ K in keyof T ] ?: LazyArbitrary < unknown > } = safeObjectCreate ( null ) ;
90+ const tie = ( key : keyof T ) : Arbitrary < any > => {
91+ if ( ! safeHasOwnProperty ( lazyArbs , key ) ) {
92+ // Call to hasOwnProperty ensures that the property key will be defined
93+ lazyArbs [ key ] = new LazyArbitrary ( String ( key ) ) ;
94+ }
95+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
96+ return lazyArbs [ key ] ! ;
97+ } ;
98+ const strictArbs = builder ( tie as any ) ;
99+ for ( const key in strictArbs ) {
100+ if ( ! safeHasOwnProperty ( strictArbs , key ) ) {
101+ // Prevents accidental iteration over properties inherited from an object’s prototype
102+ continue ;
103+ }
104+ const lazyAtKey : LazyArbitrary < unknown > | undefined = lazyArbs [ key ] ;
105+ const lazyArb = lazyAtKey !== undefined ? lazyAtKey : new LazyArbitrary ( key ) ;
106+ lazyArb . underlying = strictArbs [ key ] ;
107+ lazyArbs [ key ] = lazyArb ;
108+ }
109+ return strictArbs ;
110+ }
111+
112+ /** @internal */
113+ export function derefPools < T > ( pools : { [ K in keyof T ] : unknown [ ] } , placeholderSymbol : symbol ) : void {
114+ const visited = new Set ( ) ;
115+ function deref ( value : unknown , source ?: Record < PropertyKey , unknown > , sourceKey ?: PropertyKey ) {
116+ if ( typeof value !== 'object' || value === null ) {
117+ return ;
118+ }
119+
120+ if ( safeHas ( visited , value ) ) {
121+ return ;
122+ }
123+ safeAdd ( visited , value ) ;
124+
125+ if ( safeHasOwnProperty ( value , placeholderSymbol ) ) {
126+ // This is a while loop because it's possible for an arbitrary to be defined as just `arb: tie('otherArb')`, in
127+ // which case what the `arb` generates is also a placeholder.
128+ let currentValue : unknown = value ;
129+ do {
130+ const { key, index } = ( currentValue as { [ placeholderSymbol ] : { key : keyof T ; index : number } } ) [
131+ placeholderSymbol
132+ ] ;
133+ const pool = pools [ key ] ;
134+ currentValue = pool [ index % pool . length ] ;
135+ if ( source !== undefined && sourceKey !== undefined ) {
136+ source [ sourceKey ] = currentValue ;
137+ }
138+ } while ( safeHasOwnProperty ( currentValue , placeholderSymbol ) ) ;
139+ return ;
140+ }
141+
142+ if ( safeArrayIsArray ( value ) ) {
143+ for ( let i = 0 ; i < value . length ; i ++ ) {
144+ deref ( value [ i ] , value as unknown as Record < PropertyKey , unknown > , i ) ;
145+ }
146+ } else {
147+ for ( const [ key , item ] of safeObjectEntries ( value ) ) {
148+ deref ( item , value as Record < PropertyKey , unknown > , key ) ;
149+ }
150+ }
151+ }
152+ deref ( pools ) ;
153+ }
154+
155+ /** @internal */
156+ function letrecWithCycles < T > (
157+ builder : LetrecLooselyTypedBuilder < T > | LetrecTypedBuilder < T > ,
158+ constraints : CycleConstraints ,
159+ ) : LetrecValue < T > {
160+ const lazyArbs : { [ K in keyof T ] ?: LazyArbitrary < unknown > } = safeObjectCreate ( null ) ;
161+ const tie = ( key : keyof T ) : Arbitrary < any > => {
162+ if ( ! safeHasOwnProperty ( lazyArbs , key ) ) {
163+ // Call to hasOwnProperty ensures that the property key will be defined
164+ lazyArbs [ key ] = new LazyArbitrary ( String ( key ) ) ;
165+ }
166+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
167+ return lazyArbs [ key ] ! ;
168+ } ;
169+ const strictArbs = builder ( tie as any ) ;
170+
171+ // Symbol to replace with a potentially circular reference later.
172+ const placeholderSymbol = Symbol ( 'placeholder' ) ;
173+ const poolArbs : { [ K in keyof T ] : Arbitrary < unknown [ ] > } = safeObjectCreate ( null ) ;
174+ const poolConstraints = {
175+ minLength : 1 ,
176+ // Higher cycle frequency is achieved by using a smaller pool of objects, so we invert the input `frequency`.
177+ size : invertSize ( resolveSize ( constraints . frequencySize ) ) ,
178+ } ;
179+ for ( const key in strictArbs ) {
180+ if ( ! safeHasOwnProperty ( strictArbs , key ) ) {
181+ // Prevents accidental iteration over properties inherited from an object’s prototype
182+ continue ;
183+ }
184+ const lazyAtKey : LazyArbitrary < unknown > | undefined = lazyArbs [ key ] ;
185+ const lazyArb = lazyAtKey !== undefined ? lazyAtKey : new LazyArbitrary ( key ) ;
186+ lazyArb . underlying = noShrink ( nat ( ) . map ( ( index ) => ( { [ placeholderSymbol ] : { key, index } } ) ) ) ;
187+ lazyArbs [ key ] = lazyArb ;
188+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
189+ poolArbs [ key ] = array ( strictArbs [ key ] ! , poolConstraints ) ;
190+ }
191+
192+ for ( const key in strictArbs ) {
193+ if ( ! safeHasOwnProperty ( strictArbs , key ) ) {
194+ // Prevents accidental iteration over properties inherited from an object’s prototype
195+ continue ;
196+ }
197+
198+ const poolsArb = record ( poolArbs as any ) as Arbitrary < { [ K in keyof T ] : unknown [ ] } > ;
199+ strictArbs [ key ] = poolsArb . map ( ( pools ) => {
200+ derefPools ( pools , placeholderSymbol ) ;
201+ return pools [ key ] [ 0 ] ;
202+ } ) as ( typeof strictArbs ) [ typeof key ] ;
203+ }
204+
205+ return strictArbs ;
206+ }
207+
53208/**
54209 * For mutually recursive types
55210 *
@@ -72,7 +227,10 @@ export type LetrecLooselyTypedBuilder<T> = (tie: LetrecLooselyTypedTie) => Letre
72227 * @remarks Since 1.16.0
73228 * @public
74229 */
75- export function letrec < T > ( builder : T extends Record < string , unknown > ? LetrecTypedBuilder < T > : never ) : LetrecValue < T > ;
230+ export function letrec < T > (
231+ builder : T extends Record < string , unknown > ? LetrecTypedBuilder < T > : never ,
232+ constraints ?: LetrecConstraints ,
233+ ) : LetrecValue < T > ;
76234/**
77235 * For mutually recursive types
78236 *
@@ -92,27 +250,12 @@ export function letrec<T>(builder: T extends Record<string, unknown> ? LetrecTyp
92250 * @remarks Since 1.16.0
93251 * @public
94252 */
95- export function letrec < T > ( builder : LetrecLooselyTypedBuilder < T > ) : LetrecValue < T > ;
96- export function letrec < T > ( builder : LetrecLooselyTypedBuilder < T > | LetrecTypedBuilder < T > ) : LetrecValue < T > {
97- const lazyArbs : { [ K in keyof T ] ?: LazyArbitrary < unknown > } = safeObjectCreate ( null ) ;
98- const tie = ( key : keyof T ) : Arbitrary < any > => {
99- if ( ! safeHasOwnProperty ( lazyArbs , key ) ) {
100- // Call to hasOwnProperty ensures that the property key will be defined
101- lazyArbs [ key ] = new LazyArbitrary ( String ( key ) ) ;
102- }
103- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
104- return lazyArbs [ key ] ! ;
105- } ;
106- const strictArbs = builder ( tie as any ) ;
107- for ( const key in strictArbs ) {
108- if ( ! safeHasOwnProperty ( strictArbs , key ) ) {
109- // Prevents accidental iteration over properties inherited from an object’s prototype
110- continue ;
111- }
112- const lazyAtKey : LazyArbitrary < unknown > | undefined = lazyArbs [ key ] ;
113- const lazyArb = lazyAtKey !== undefined ? lazyAtKey : new LazyArbitrary ( key ) ;
114- lazyArb . underlying = strictArbs [ key ] ;
115- lazyArbs [ key ] = lazyArb ;
116- }
117- return strictArbs ;
253+ export function letrec < T > ( builder : LetrecLooselyTypedBuilder < T > , constraints ?: LetrecConstraints ) : LetrecValue < T > ;
254+ export function letrec < T > (
255+ builder : LetrecLooselyTypedBuilder < T > | LetrecTypedBuilder < T > ,
256+ constraints : LetrecConstraints = { } ,
257+ ) : LetrecValue < T > {
258+ return constraints . withCycles
259+ ? letrecWithCycles ( builder , constraints . withCycles === true ? { } : constraints . withCycles )
260+ : letrecWithoutCycles ( builder ) ;
118261}
0 commit comments