diff --git a/package.json b/package.json index 67bd26e8e..69dc9380a 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ }, "xo": { "rules": { + "@typescript-eslint/no-extraneous-class": "off", "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-types": "off", "@typescript-eslint/naming-convention": "off", diff --git a/source/paths.d.ts b/source/paths.d.ts index f7d18214c..97648272d 100644 --- a/source/paths.d.ts +++ b/source/paths.d.ts @@ -3,6 +3,8 @@ 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'; /** Generate a union of all possible paths to properties in the given object. @@ -45,7 +47,9 @@ open('listB.1'); // TypeError. Because listB only has one element. @category Object @category Array */ -export type Paths = +export type Paths = Paths_; + +type Paths_ = T extends NonRecursiveType | ReadonlyMap | ReadonlySet ? never : IsAny extends true @@ -53,14 +57,14 @@ export 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> - | InternalPaths[number]>> - : InternalPaths + ? InternalPaths, Depth> + | InternalPaths[number]>, Depth> + : InternalPaths : T extends object - ? InternalPaths + ? InternalPaths : never; -export type InternalPaths<_T, T = Required<_T>> = +export type InternalPaths<_T, Depth extends number = 0, T = Required<_T>> = T extends EmptyObject | readonly [] ? never : { @@ -71,8 +75,10 @@ export type InternalPaths<_T, T = Required<_T>> = | Key | ToString | ( - IsNever> extends false - ? `${Key}.${Paths}` + LessThan extends true // Limit the depth to prevent infinite recursion + ? IsNever>> extends false + ? `${Key}.${Paths_>}` + : never : never ) : never diff --git a/test-d/paths.ts b/test-d/paths.ts index 7d4b8657a..b1f151882 100644 --- a/test-d/paths.ts +++ b/test-d/paths.ts @@ -1,5 +1,5 @@ -import {expectType} from 'tsd'; -import type {Paths} from '../index'; +import {expectAssignable, expectType} from 'tsd'; +import type {Paths, PickDeep} from '../index'; declare const normal: Paths<{foo: string}>; expectType<'foo'>(normal); @@ -98,3 +98,13 @@ expectType(leadingSpreadTu declare const leadingSpreadTuple1: Paths<[...Array<{a: string}>, {b: number}, {c: number}]>; expectType(leadingSpreadTuple1); + +// Circularly references +type MyEntity = { + myOtherEntity?: MyOtherEntity; +}; +type MyOtherEntity = { + myEntity?: MyEntity; +}; +type MyEntityPaths = Paths; +expectAssignable({} as MyEntityPaths);