diff --git a/source/paths.d.ts b/source/paths.d.ts index 97648272d..3d4a232a3 100644 --- a/source/paths.d.ts +++ b/source/paths.d.ts @@ -3,8 +3,22 @@ import type {EmptyObject} from './empty-object'; import type {IsAny} from './is-any'; import type {IsNever} from './is-never'; import type {UnknownArray} from './unknown-array'; -import type {Sum} from './sum'; -import type {LessThan} from './less-than'; +import type {Subtract} from './subtract'; +import type {GreaterThan} from './greater-than'; + +/** +Paths options. + +@see {@link Paths} +*/ +export type PathsOptions = { + /** + The maximum depth to recurse when searching for paths. + + @default 10 + */ + maxRecursionDepth?: number; +}; /** Generate a union of all possible paths to properties in the given object. @@ -47,9 +61,7 @@ open('listB.1'); // TypeError. Because listB only has one element. @category Object @category Array */ -export type Paths = Paths_; - -type Paths_ = +export type Paths = T extends NonRecursiveType | ReadonlyMap | ReadonlySet ? never : IsAny extends true @@ -57,29 +69,33 @@ type Paths_ = : T extends UnknownArray ? number extends T['length'] // We need to handle the fixed and non-fixed index part of the array separately. - ? InternalPaths, Depth> - | InternalPaths[number]>, Depth> - : InternalPaths + ? InternalPaths, Options> + | InternalPaths[number]>, Options> + : InternalPaths : T extends object - ? InternalPaths + ? InternalPaths : never; -export type InternalPaths<_T, Depth extends number = 0, T = Required<_T>> = - T extends EmptyObject | readonly [] - ? never - : { - [Key in keyof T]: - Key extends string | number // Limit `Key` to string or number. - // If `Key` is a number, return `Key | `${Key}``, because both `array[0]` and `array['0']` work. - ? - | Key - | ToString - | ( - LessThan extends true // Limit the depth to prevent infinite recursion - ? IsNever>> extends false - ? `${Key}.${Paths_>}` - : never +type InternalPaths = + (Options['maxRecursionDepth'] extends number ? Options['maxRecursionDepth'] : 10) extends infer MaxDepth extends number + ? Required extends infer T + ? T extends EmptyObject | readonly [] + ? never + : { + [Key in keyof T]: + Key extends string | number // Limit `Key` to string or number. + // If `Key` is a number, return `Key | `${Key}``, because both `array[0]` and `array['0']` work. + ? + | Key + | ToString + | ( + GreaterThan extends true // Limit the depth to prevent infinite recursion + ? IsNever}>> extends false + ? `${Key}.${Paths}>}` + : never + : never + ) : never - ) - : never - }[keyof T & (T extends UnknownArray ? number : unknown)]; + }[keyof T & (T extends UnknownArray ? number : unknown)] + : never + : never; diff --git a/test-d/paths.ts b/test-d/paths.ts index b1f151882..7e54ccff0 100644 --- a/test-d/paths.ts +++ b/test-d/paths.ts @@ -1,5 +1,5 @@ import {expectAssignable, expectType} from 'tsd'; -import type {Paths, PickDeep} from '../index'; +import type {Paths} from '../index'; declare const normal: Paths<{foo: string}>; expectType<'foo'>(normal); @@ -108,3 +108,13 @@ type MyOtherEntity = { }; type MyEntityPaths = Paths; expectAssignable({} as MyEntityPaths); + +// By default, the recursion limit should be reasonably long +type RecursiveFoo = {foo: RecursiveFoo}; +expectAssignable>('foo.foo.foo.foo.foo.foo.foo.foo'); + +declare const recursion0: Paths; +expectType<'foo'>(recursion0); + +declare const recursion1: Paths; +expectType<'foo' | 'foo.foo'>(recursion1);